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

SM-417: Add project create, update, and delete commands #53

Merged
merged 8 commits into from
Jun 15, 2023
3 changes: 3 additions & 0 deletions crates/bitwarden-json/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ impl Client {

Command::Projects(cmd) => match cmd {
ProjectsCommand::Get(req) => self.0.projects().get(&req).await.into_string(),
ProjectsCommand::Create(req) => self.0.projects().create(&req).await.into_string(),
ProjectsCommand::List(req) => self.0.projects().list(&req).await.into_string(),
ProjectsCommand::Update(req) => self.0.projects().update(&req).await.into_string(),
ProjectsCommand::Delete(req) => self.0.projects().delete(req).await.into_string(),
},
}
}
Expand Down
65 changes: 63 additions & 2 deletions crates/bitwarden-napi/src-ts/bitwarden_client/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,47 @@ export interface PasswordLoginRequest {
* Returns: [ProjectResponse](crate::sdk::response::projects_response::ProjectResponse)
*
* > Requires Authentication > Requires using an Access Token for login or calling Sync at
* least once Creates a new project in the provided organization using the given data
*
* Returns: [ProjectResponse](crate::sdk::response::projects_response::ProjectResponse)
*
* > Requires Authentication > Requires using an Access Token for login or calling Sync at
* least once Lists all projects of the given organization
*
* Returns: [ProjectsResponse](crate::sdk::response::projects_response::ProjectsResponse)
*
* > Requires Authentication > Requires using an Access Token for login or calling Sync at
* least once Updates an existing project with the provided ID using the given data
*
* Returns: [ProjectResponse](crate::sdk::response::projects_response::ProjectResponse)
*
* > Requires Authentication > Requires using an Access Token for login or calling Sync at
* least once Deletes all the projects whose IDs match the provided ones
*
* Returns:
* [ProjectsDeleteResponse](crate::sdk::response::projects_response::ProjectsDeleteResponse)
*/
export interface ProjectsCommand {
get?: ProjectGetRequest;
list?: ProjectsListRequest;
get?: ProjectGetRequest;
create?: ProjectCreateRequest;
list?: ProjectsListRequest;
update?: ProjectPutRequest;
delete?: ProjectsDeleteRequest;
}

export interface ProjectCreateRequest {
name: string;
/**
* Organization where the project will be created
*/
organizationId: string;
}

export interface ProjectsDeleteRequest {
/**
* IDs of the projects to delete
*/
ids: string[];
}

export interface ProjectGetRequest {
Expand All @@ -226,6 +260,18 @@ export interface ProjectsListRequest {
organizationId: string;
}

export interface ProjectPutRequest {
/**
* ID of the project to modify
*/
id: string;
name: string;
/**
* Organization ID of the project to modify
*/
organizationId: string;
}

/**
* > Requires Authentication > Requires using an Access Token for login or calling Sync at
* least once Retrieve a secret by the provided identifier
Expand Down Expand Up @@ -966,14 +1012,29 @@ const typeMap: any = {
], false),
"ProjectsCommand": o([
{ json: "get", js: "get", typ: u(undefined, r("ProjectGetRequest")) },
{ json: "create", js: "create", typ: u(undefined, r("ProjectCreateRequest")) },
{ json: "list", js: "list", typ: u(undefined, r("ProjectsListRequest")) },
{ json: "update", js: "update", typ: u(undefined, r("ProjectPutRequest")) },
{ json: "delete", js: "delete", typ: u(undefined, r("ProjectsDeleteRequest")) },
], false),
"ProjectCreateRequest": o([
{ json: "name", js: "name", typ: "" },
{ json: "organizationId", js: "organizationId", typ: "" },
], false),
"ProjectsDeleteRequest": o([
{ json: "ids", js: "ids", typ: a("") },
], false),
"ProjectGetRequest": o([
{ json: "id", js: "id", typ: "" },
], false),
"ProjectsListRequest": o([
{ json: "organizationId", js: "organizationId", typ: "" },
], false),
"ProjectPutRequest": o([
{ json: "id", js: "id", typ: "" },
{ json: "name", js: "name", typ: "" },
{ json: "organizationId", js: "organizationId", typ: "" },
], false),
"SecretsCommand": o([
{ json: "get", js: "get", typ: u(undefined, r("SecretGetRequest")) },
{ json: "create", js: "create", typ: u(undefined, r("SecretCreateRequest")) },
Expand Down
21 changes: 18 additions & 3 deletions crates/bitwarden/src/client/client_projects.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use crate::{
commands::{get_project, list_projects},
commands::{create_project, delete_projects, get_project, list_projects, update_project},
error::Result,
sdk::{
request::projects_request::{ProjectGetRequest, ProjectsListRequest},
response::projects_response::{ProjectResponse, ProjectsResponse},
request::projects_request::{
ProjectCreateRequest, ProjectGetRequest, ProjectPutRequest, ProjectsDeleteRequest,
ProjectsListRequest,
},
response::projects_response::{ProjectResponse, ProjectsDeleteResponse, ProjectsResponse},
},
};

Expand All @@ -16,7 +19,19 @@ impl<'a> ClientProjects<'a> {
get_project(self.client, input).await
}

pub async fn create(&mut self, input: &ProjectCreateRequest) -> Result<ProjectResponse> {
create_project(self.client, input).await
}

pub async fn list(&mut self, input: &ProjectsListRequest) -> Result<ProjectsResponse> {
list_projects(self.client, input).await
}

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

pub async fn delete(&mut self, input: ProjectsDeleteRequest) -> Result<ProjectsDeleteResponse> {
delete_projects(self.client, input).await
}
}
80 changes: 78 additions & 2 deletions crates/bitwarden/src/commands/projects.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
use bitwarden_api_api::models::{ProjectCreateRequestModel, ProjectUpdateRequestModel};

use crate::{
client::Client,
error::{Error, Result},
sdk::{
request::projects_request::{ProjectGetRequest, ProjectsListRequest},
response::projects_response::{ProjectResponse, ProjectsResponse},
request::projects_request::{
ProjectCreateRequest, ProjectGetRequest, ProjectPutRequest, ProjectsDeleteRequest,
ProjectsListRequest,
},
response::projects_response::{ProjectResponse, ProjectsDeleteResponse, ProjectsResponse},
},
};

Expand All @@ -23,6 +28,37 @@ pub(crate) async fn get_project(
ProjectResponse::process_response(res, enc)
}

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

let org_id = Some(input.organization_id);

let project = Some(ProjectCreateRequestModel {
name: enc.encrypt(input.name.as_bytes(), org_id)?.to_string(),
});

let config = client.get_api_configurations().await;
let res = bitwarden_api_api::apis::projects_api::organizations_organization_id_projects_post(
&config.api,
input.organization_id,
project,
)
.await?;

let enc = client
.get_encryption_settings()
.as_ref()
.ok_or(Error::VaultLocked)?;
Comment on lines +54 to +57
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't enc already defined above? I think we can reuse it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, any recommendations on a better way to do this? By removing it we break one of the borrowing rules. With it removed we have the following error, the compiler delineates the following:

  • immutable borrow on line 35/36
  • mutable borrow on line 46
  • immutable borrow on line 54

cannot borrow *client as mutable because it is also borrowed as immutable

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed with @dani-garcia, addressing this in this PR: #70


ProjectResponse::process_response(res, enc)
}

pub(crate) async fn list_projects(
client: &mut Client,
input: &ProjectsListRequest,
Expand All @@ -41,3 +77,43 @@ pub(crate) async fn list_projects(

ProjectsResponse::process_response(res, enc)
}

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

let org_id = Some(input.organization_id);

let project = Some(ProjectUpdateRequestModel {
name: enc.encrypt(input.name.as_bytes(), org_id)?.to_string(),
});

let config = client.get_api_configurations().await;
let res =
bitwarden_api_api::apis::projects_api::projects_id_put(&config.api, input.id, project)
.await?;

let enc = client
.get_encryption_settings()
.as_ref()
.ok_or(Error::VaultLocked)?;
Comment on lines +101 to +104
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update here


ProjectResponse::process_response(res, enc)
}

pub(crate) async fn delete_projects(
client: &mut Client,
input: ProjectsDeleteRequest,
) -> Result<ProjectsDeleteResponse> {
let config = client.get_api_configurations().await;
let res =
bitwarden_api_api::apis::projects_api::projects_delete_post(&config.api, Some(input.ids))
.await?;

ProjectsDeleteResponse::process_response(res)
}
29 changes: 28 additions & 1 deletion crates/bitwarden/src/sdk/request/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ use serde::{Deserialize, Serialize};
use crate::sdk::{
auth::request::{AccessTokenLoginRequest, ApiKeyLoginRequest, PasswordLoginRequest},
request::{
projects_request::{ProjectGetRequest, ProjectsListRequest},
projects_request::{
ProjectCreateRequest, ProjectGetRequest, ProjectPutRequest, ProjectsDeleteRequest,
ProjectsListRequest,
},
secret_verification_request::SecretVerificationRequest,
secrets_request::{
SecretCreateRequest, SecretGetRequest, SecretIdentifiersRequest, SecretPutRequest,
Expand Down Expand Up @@ -133,11 +136,35 @@ pub enum ProjectsCommand {
///
Get(ProjectGetRequest),

/// > Requires Authentication
/// > Requires using an Access Token for login or calling Sync at least once
/// Creates a new project in the provided organization using the given data
///
/// Returns: [ProjectResponse](crate::sdk::response::projects_response::ProjectResponse)
///
Create(ProjectCreateRequest),

/// > Requires Authentication
/// > Requires using an Access Token for login or calling Sync at least once
/// Lists all projects of the given organization
///
/// Returns: [ProjectsResponse](crate::sdk::response::projects_response::ProjectsResponse)
///
List(ProjectsListRequest),

/// > Requires Authentication
/// > Requires using an Access Token for login or calling Sync at least once
/// Updates an existing project with the provided ID using the given data
///
/// Returns: [ProjectResponse](crate::sdk::response::projects_response::ProjectResponse)
///
Update(ProjectPutRequest),

/// > Requires Authentication
/// > Requires using an Access Token for login or calling Sync at least once
/// Deletes all the projects whose IDs match the provided ones
///
/// Returns: [ProjectsDeleteResponse](crate::sdk::response::projects_response::ProjectsDeleteResponse)
///
Delete(ProjectsDeleteRequest),
}
27 changes: 27 additions & 0 deletions crates/bitwarden/src/sdk/request/projects_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,36 @@ pub struct ProjectGetRequest {
pub id: Uuid,
}

#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct ProjectCreateRequest {
/// Organization where the project will be created
pub organization_id: Uuid,

pub name: String,
}

#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct ProjectPutRequest {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be called updateRequest? Put feels like an implementation detail.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was to follow the convention in secrets_request.rs which uses SecretPutRequest. We definitely can refactor out Put in multiple locations and use Update though. This could be done in a different ticket, this one, or we take a different approach?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed this, we will address this in a separate PR

/// ID of the project to modify
pub id: Uuid,
/// Organization ID of the project to modify
pub organization_id: Uuid,

pub name: String,
}

#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct ProjectsListRequest {
/// Organization to retrieve all the projects from
pub organization_id: Uuid,
}

#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct ProjectsDeleteRequest {
/// IDs of the projects to delete
pub ids: Vec<Uuid>,
}
44 changes: 43 additions & 1 deletion crates/bitwarden/src/sdk/response/projects_response.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use bitwarden_api_api::models::{ProjectResponseModel, ProjectResponseModelListResponseModel};
use bitwarden_api_api::models::{
BulkDeleteResponseModel, BulkDeleteResponseModelListResponseModel, ProjectResponseModel,
ProjectResponseModelListResponseModel,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
Expand Down Expand Up @@ -65,3 +68,42 @@ impl ProjectsResponse {
})
}
}

#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct ProjectsDeleteResponse {
pub data: Vec<ProjectDeleteResponse>,
}

impl ProjectsDeleteResponse {
pub(crate) fn process_response(
response: BulkDeleteResponseModelListResponseModel,
) -> Result<ProjectsDeleteResponse> {
Ok(ProjectsDeleteResponse {
data: response
.data
.unwrap_or_default()
.into_iter()
.map(ProjectDeleteResponse::process_response)
.collect::<Result<_, _>>()?,
})
}
}

#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct ProjectDeleteResponse {
pub id: Uuid,
pub error: Option<String>,
}

impl ProjectDeleteResponse {
pub(crate) fn process_response(
response: BulkDeleteResponseModel,
) -> Result<ProjectDeleteResponse> {
Ok(ProjectDeleteResponse {
id: response.id.ok_or(Error::MissingFields)?,
error: response.error,
})
}
}
Loading