From 007bae33d49fd42db5a01fe37a28be75db58adc5 Mon Sep 17 00:00:00 2001 From: Colton Hurst Date: Tue, 29 Aug 2023 16:03:07 -0400 Subject: [PATCH 01/12] SM-874: Update BitwardenClient in login.py --- languages/python/login.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/languages/python/login.py b/languages/python/login.py index 9b756a005..e23694600 100644 --- a/languages/python/login.py +++ b/languages/python/login.py @@ -4,9 +4,10 @@ from BitwardenClient.schemas import client_settings_from_dict client = BitwardenClient(client_settings_from_dict({ - "api_url": "http://localhost:4000", - "identity_url": "http://localhost:33656", - "user_agent": "Python", + "apiUrl": "http://localhost:4000", + "deviceType": "SDK", + "identityUrl": "http://localhost:33656", + "userAgent": "Python", })) logging.basicConfig(level=logging.DEBUG) From bfa8d949b72dbc2cc2684b031480034f7e2d089a Mon Sep 17 00:00:00 2001 From: Colton Hurst Date: Tue, 12 Sep 2023 17:45:50 -0400 Subject: [PATCH 02/12] sm/sm-874: Fix Python SDK integration --- crates/sdk-schemas/src/main.rs | 1 + .../BitwardenClient/bitwarden_client.py | 19 +++++++++++++------ languages/python/README.md | 6 +++++- languages/python/login.py | 10 +++------- 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/crates/sdk-schemas/src/main.rs b/crates/sdk-schemas/src/main.rs index 9d7e52a47..ef7a2db83 100644 --- a/crates/sdk-schemas/src/main.rs +++ b/crates/sdk-schemas/src/main.rs @@ -99,6 +99,7 @@ fn main() -> Result<()> { write_schema_for_response! { bitwarden::auth::login::ApiKeyLoginResponse, bitwarden::auth::login::PasswordLoginResponse, + bitwarden::auth::login::AccessTokenLoginResponse, bitwarden::secrets_manager::secrets::SecretIdentifiersResponse, bitwarden::secrets_manager::secrets::SecretResponse, bitwarden::secrets_manager::secrets::SecretsResponse, diff --git a/languages/python/BitwardenClient/bitwarden_client.py b/languages/python/BitwardenClient/bitwarden_client.py index 6a0a77ed8..0d54686b7 100644 --- a/languages/python/BitwardenClient/bitwarden_client.py +++ b/languages/python/BitwardenClient/bitwarden_client.py @@ -1,8 +1,8 @@ import json -from typing import Any, List +from typing import Any, List, Optional +from uuid import UUID import bitwarden_py -from .schemas import ClientSettings, Command, PasswordLoginRequest, PasswordLoginResponse, ResponseForPasswordLoginResponse, ResponseForSecretIdentifiersResponse, ResponseForSecretResponse, ResponseForSecretsDeleteResponse, ResponseForSyncResponse, ResponseForUserAPIKeyResponse, SecretCreateRequest, SecretGetRequest, SecretIdentifiersRequest, SecretIdentifiersResponse, SecretPutRequest, SecretResponse, SecretVerificationRequest, SecretsCommand, SecretsDeleteRequest, SecretsDeleteResponse, SyncRequest, SyncResponse, UserAPIKeyResponse - +from .schemas import ClientSettings, Command, PasswordLoginRequest, PasswordLoginResponse, ResponseForPasswordLoginResponse, ResponseForSecretIdentifiersResponse, ResponseForSecretResponse, ResponseForSecretsDeleteResponse, ResponseForSyncResponse, ResponseForUserAPIKeyResponse, SecretCreateRequest, SecretGetRequest, SecretIdentifiersRequest, SecretIdentifiersResponse, SecretPutRequest, SecretResponse, SecretVerificationRequest, SecretsCommand, SecretsDeleteRequest, SecretsDeleteResponse, SyncRequest, SyncResponse, UserAPIKeyResponse, AccessTokenLoginRequest, AccessTokenLoginResponse, ResponseForAccessTokenLoginResponse class BitwardenClient: def __init__(self, settings: ClientSettings = None): @@ -18,6 +18,12 @@ def password_login(self, email: str, password: str) -> ResponseForPasswordLoginR ) return ResponseForPasswordLoginResponse.from_dict(result) + def access_token_login(self, access_token: str) -> AccessTokenLoginResponse: + result = self._run_command( + Command(access_token_login=AccessTokenLoginRequest(access_token)) + ) + return ResponseForAccessTokenLoginResponse.from_dict(result) + def get_user_api_key(self, secret: str, is_otp: bool = False) -> ResponseForUserAPIKeyResponse: result = self._run_command( Command(get_user_api_key=SecretVerificationRequest( @@ -38,7 +44,6 @@ def _run_command(self, command: Command) -> Any: response_json = self.inner.run_command(json.dumps(command.to_dict())) return json.loads(response_json) - class SecretsClient: def __init__(self, client: BitwardenClient): self.client = client @@ -52,10 +57,12 @@ def get(self, id: str) -> ResponseForSecretResponse: def create(self, key: str, note: str, organization_id: str, - value: str) -> ResponseForSecretResponse: + value: str, + project_ids: Optional[List[UUID]] = None + ) -> ResponseForSecretResponse: result = self.client._run_command( Command(secrets=SecretsCommand( - create=SecretCreateRequest(key, note, organization_id, value))) + create=SecretCreateRequest(key, note, organization_id, value, project_ids))) ) return ResponseForSecretResponse.from_dict(result) diff --git a/languages/python/README.md b/languages/python/README.md index 03a4ce572..871be274f 100644 --- a/languages/python/README.md +++ b/languages/python/README.md @@ -9,6 +9,10 @@ ```bash pip install setuptools_rust ``` +- dateutil + ```bash + pip install python-dateutil + ``` # Installation @@ -18,7 +22,7 @@ From the `languages/python/` directory, python3 ./setup.py develop ``` -Move the the resulting `.so` file to `bitwarden_py.so`, if it isn't already there. +Rename the the resulting `.so` file to `bitwarden_py.so`, if it isn't already there. # Run diff --git a/languages/python/login.py b/languages/python/login.py index e23694600..5c168de37 100644 --- a/languages/python/login.py +++ b/languages/python/login.py @@ -12,14 +12,10 @@ logging.basicConfig(level=logging.DEBUG) -result = client.password_login("test@bitwarden.com", "asdfasdf") -print(result) -print(client.get_user_api_key("asdfasdf")) +result = client.access_token_login("access token here") -sync = client.sync() +secret = client.secrets().create("TEST_SECRET", "This is a test secret", "organization id here", "Secret1234!", ["project id here"]) -secret = client.secrets().create("TEST_SECRET", "This is a test secret", - sync.data.profile.organizations[0].id, "Secret1234!") -print(secret) +input("Press Enter to delete the secret...") client.secrets().delete([secret.data.id]) From 1833336d7efb955f758060722a5aa9068113a8ac Mon Sep 17 00:00:00 2001 From: Colton Hurst Date: Wed, 13 Sep 2023 17:14:30 -0400 Subject: [PATCH 03/12] SM-874: Add project_ids param to SecretsClient update function in bitwarden_client.py --- languages/python/BitwardenClient/bitwarden_client.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/languages/python/BitwardenClient/bitwarden_client.py b/languages/python/BitwardenClient/bitwarden_client.py index 0d54686b7..02045620c 100644 --- a/languages/python/BitwardenClient/bitwarden_client.py +++ b/languages/python/BitwardenClient/bitwarden_client.py @@ -77,10 +77,12 @@ def update(self, id: str, key: str, note: str, organization_id: str, - value: str) -> ResponseForSecretResponse: + value: str, + project_ids: Optional[List[UUID]] = None + ) -> ResponseForSecretResponse: result = self.client._run_command( Command(secrets=SecretsCommand(update=SecretPutRequest( - id, key, note, organization_id, value))) + id, key, note, organization_id, value, project_ids))) ) return ResponseForSecretResponse.from_dict(result) From 8c6b527633c624b620eb5c26d4e4ef0c6c6b08a2 Mon Sep 17 00:00:00 2001 From: Colton Hurst Date: Fri, 15 Sep 2023 10:47:47 -0400 Subject: [PATCH 04/12] SM-874: Add a ProjectsClient and address PR comments --- .../BitwardenClient/bitwarden_client.py | 48 ++++++++++++++++++- languages/python/login.py | 35 ++++++++++++-- 2 files changed, 77 insertions(+), 6 deletions(-) diff --git a/languages/python/BitwardenClient/bitwarden_client.py b/languages/python/BitwardenClient/bitwarden_client.py index 02045620c..db36876b7 100644 --- a/languages/python/BitwardenClient/bitwarden_client.py +++ b/languages/python/BitwardenClient/bitwarden_client.py @@ -2,7 +2,7 @@ from typing import Any, List, Optional from uuid import UUID import bitwarden_py -from .schemas import ClientSettings, Command, PasswordLoginRequest, PasswordLoginResponse, ResponseForPasswordLoginResponse, ResponseForSecretIdentifiersResponse, ResponseForSecretResponse, ResponseForSecretsDeleteResponse, ResponseForSyncResponse, ResponseForUserAPIKeyResponse, SecretCreateRequest, SecretGetRequest, SecretIdentifiersRequest, SecretIdentifiersResponse, SecretPutRequest, SecretResponse, SecretVerificationRequest, SecretsCommand, SecretsDeleteRequest, SecretsDeleteResponse, SyncRequest, SyncResponse, UserAPIKeyResponse, AccessTokenLoginRequest, AccessTokenLoginResponse, ResponseForAccessTokenLoginResponse +from .schemas import ClientSettings, Command, PasswordLoginRequest, PasswordLoginResponse, ResponseForPasswordLoginResponse, ResponseForSecretIdentifiersResponse, ResponseForSecretResponse, ResponseForSecretsDeleteResponse, ResponseForSyncResponse, ResponseForUserAPIKeyResponse, SecretCreateRequest, SecretGetRequest, SecretIdentifiersRequest, SecretIdentifiersResponse, SecretPutRequest, SecretResponse, SecretVerificationRequest, SecretsCommand, SecretsDeleteRequest, SecretsDeleteResponse, SyncRequest, SyncResponse, UserAPIKeyResponse, AccessTokenLoginRequest, AccessTokenLoginResponse, ResponseForAccessTokenLoginResponse, ResponseForProjectResponse, ProjectsCommand, ProjectCreateRequest, ProjectGetRequest, ProjectPutRequest, ProjectsListRequest, ResponseForProjectsResponse, ResponseForProjectsDeleteResponse, ProjectsDeleteRequest class BitwardenClient: def __init__(self, settings: ClientSettings = None): @@ -40,6 +40,9 @@ def sync(self, exclude_subdomains: bool = False) -> ResponseForSyncResponse: def secrets(self): return SecretsClient(self) + def projects(self): + return ProjectsClient(self) + def _run_command(self, command: Command) -> Any: response_json = self.inner.run_command(json.dumps(command.to_dict())) return json.loads(response_json) @@ -91,3 +94,46 @@ def delete(self, ids: List[str]) -> ResponseForSecretsDeleteResponse: Command(secrets=SecretsCommand(delete=SecretsDeleteRequest(ids))) ) return ResponseForSecretsDeleteResponse.from_dict(result) + +class ProjectsClient: + def __init__(self, client: BitwardenClient): + self.client = client + + def get(self, id: str) -> ResponseForProjectResponse: + result = self.client._run_command( + Command(projects=ProjectsCommand(get=ProjectGetRequest(id))) + ) + return ResponseForProjectResponse.from_dict(result) + + def create(self, + name: str, + organization_id: str, + ) -> ResponseForProjectResponse: + result = self.client._run_command( + Command(projects=ProjectsCommand( + create=ProjectCreateRequest(name, organization_id))) + ) + return ResponseForProjectResponse.from_dict(result) + + def list(self, organization_id: str) -> ResponseForProjectsResponse: + result = self.client._run_command( + Command(projects=ProjectsCommand( + list=ProjectsListRequest(organization_id))) + ) + return ResponseForProjectsResponse.from_dict(result) + + def update(self, id: str, + name: str, + organization_id: str, + ) -> ResponseForProjectResponse: + result = self.client._run_command( + Command(projects=ProjectsCommand(update=ProjectPutRequest( + id, name, organization_id))) + ) + return ResponseForProjectResponse.from_dict(result) + + def delete(self, ids: List[str]) -> ResponseForProjectsDeleteResponse: + result = self.client._run_command( + Command(projects=ProjectsCommand(delete=ProjectsDeleteRequest(ids))) + ) + return ResponseForProjectsDeleteResponse.from_dict(result) diff --git a/languages/python/login.py b/languages/python/login.py index 5c168de37..9cac60090 100644 --- a/languages/python/login.py +++ b/languages/python/login.py @@ -1,21 +1,46 @@ import json import logging from BitwardenClient.bitwarden_client import BitwardenClient -from BitwardenClient.schemas import client_settings_from_dict +from BitwardenClient.schemas import client_settings_from_dict, DeviceType +# Create the BitwardenClient, which is used to interact with the SDK client = BitwardenClient(client_settings_from_dict({ "apiUrl": "http://localhost:4000", - "deviceType": "SDK", + "deviceType": DeviceType.SDK, "identityUrl": "http://localhost:33656", "userAgent": "Python", })) +# Add some logging & set the org id logging.basicConfig(level=logging.DEBUG) +organization_id = "org_id_here" -result = client.access_token_login("access token here") +# Attempt to authenticate with the Secrets Manager Access Token +result = client.access_token_login("access_token_here") -secret = client.secrets().create("TEST_SECRET", "This is a test secret", "organization id here", "Secret1234!", ["project id here"]) +if result.success == False: + sys.exit(result.error_message) -input("Press Enter to delete the secret...") +# -- Example Project Commands -- + +project = client.projects().create("ProjectName", organization_id) +project2 = client.projects().create("Project - Don't Delete Me!", organization_id) +updated_project = client.projects().update(project.data.id, "Cool New Project Name", organization_id) +get_that_project = client.projects().get(project.data.id) + +input("Press Enter to delete the project...") +client.projects().delete([project.data.id]) + +print(client.projects().list(organization_id)) +# -- Example Secret Commands -- + +secret = client.secrets().create("TEST_SECRET", "This is a test secret", organization_id, "Secret1234!", [project2.data.id]) +secret2 = client.secrets().create("Secret - Don't Delete Me!", "This is a test secret that will stay", organization_id, "Secret1234!", [project2.data.id]) +secret_updated = client.secrets().update(secret.data.id, "TEST_SECRET_UPDATED", "This as an updated test secret", organization_id, "Secret1234!_updated", [project2.data.id]) +secret_retrieved = client.secrets().get([secret.data.id]) + +input("Press Enter to delete the secret...") client.secrets().delete([secret.data.id]) + +print(client.secrets().list(organization_id)) From 42122044c1c2731c561bc95a62d2a7f2cb67543b Mon Sep 17 00:00:00 2001 From: Colton Hurst Date: Tue, 19 Sep 2023 16:33:40 -0400 Subject: [PATCH 05/12] SM-874: Rename login.py and remove unused functions in bitwarden_client.py --- .../BitwardenClient/bitwarden_client.py | 21 +------------------ languages/python/{login.py => example.py} | 1 + 2 files changed, 2 insertions(+), 20 deletions(-) rename languages/python/{login.py => example.py} (99%) diff --git a/languages/python/BitwardenClient/bitwarden_client.py b/languages/python/BitwardenClient/bitwarden_client.py index db36876b7..0f0f61407 100644 --- a/languages/python/BitwardenClient/bitwarden_client.py +++ b/languages/python/BitwardenClient/bitwarden_client.py @@ -2,7 +2,7 @@ from typing import Any, List, Optional from uuid import UUID import bitwarden_py -from .schemas import ClientSettings, Command, PasswordLoginRequest, PasswordLoginResponse, ResponseForPasswordLoginResponse, ResponseForSecretIdentifiersResponse, ResponseForSecretResponse, ResponseForSecretsDeleteResponse, ResponseForSyncResponse, ResponseForUserAPIKeyResponse, SecretCreateRequest, SecretGetRequest, SecretIdentifiersRequest, SecretIdentifiersResponse, SecretPutRequest, SecretResponse, SecretVerificationRequest, SecretsCommand, SecretsDeleteRequest, SecretsDeleteResponse, SyncRequest, SyncResponse, UserAPIKeyResponse, AccessTokenLoginRequest, AccessTokenLoginResponse, ResponseForAccessTokenLoginResponse, ResponseForProjectResponse, ProjectsCommand, ProjectCreateRequest, ProjectGetRequest, ProjectPutRequest, ProjectsListRequest, ResponseForProjectsResponse, ResponseForProjectsDeleteResponse, ProjectsDeleteRequest +from .schemas import ClientSettings, Command, ResponseForSecretIdentifiersResponse, ResponseForSecretResponse, ResponseForSecretsDeleteResponse, SecretCreateRequest, SecretGetRequest, SecretIdentifiersRequest, SecretIdentifiersResponse, SecretPutRequest, SecretResponse, SecretsCommand, SecretsDeleteRequest, SecretsDeleteResponse, AccessTokenLoginRequest, AccessTokenLoginResponse, ResponseForAccessTokenLoginResponse, ResponseForProjectResponse, ProjectsCommand, ProjectCreateRequest, ProjectGetRequest, ProjectPutRequest, ProjectsListRequest, ResponseForProjectsResponse, ResponseForProjectsDeleteResponse, ProjectsDeleteRequest class BitwardenClient: def __init__(self, settings: ClientSettings = None): @@ -12,31 +12,12 @@ def __init__(self, settings: ClientSettings = None): settings_json = json.dumps(settings.to_dict()) self.inner = bitwarden_py.BitwardenClient(settings_json) - def password_login(self, email: str, password: str) -> ResponseForPasswordLoginResponse: - result = self._run_command( - Command(password_login=PasswordLoginRequest(email, password)) - ) - return ResponseForPasswordLoginResponse.from_dict(result) - def access_token_login(self, access_token: str) -> AccessTokenLoginResponse: result = self._run_command( Command(access_token_login=AccessTokenLoginRequest(access_token)) ) return ResponseForAccessTokenLoginResponse.from_dict(result) - def get_user_api_key(self, secret: str, is_otp: bool = False) -> ResponseForUserAPIKeyResponse: - result = self._run_command( - Command(get_user_api_key=SecretVerificationRequest( - secret if not is_otp else None, secret if is_otp else None)) - ) - return ResponseForUserAPIKeyResponse.from_dict(result) - - def sync(self, exclude_subdomains: bool = False) -> ResponseForSyncResponse: - result = self._run_command( - Command(sync=SyncRequest(exclude_subdomains)) - ) - return ResponseForSyncResponse.from_dict(result) - def secrets(self): return SecretsClient(self) diff --git a/languages/python/login.py b/languages/python/example.py similarity index 99% rename from languages/python/login.py rename to languages/python/example.py index 9cac60090..9cac01089 100644 --- a/languages/python/login.py +++ b/languages/python/example.py @@ -1,5 +1,6 @@ import json import logging +import sys from BitwardenClient.bitwarden_client import BitwardenClient from BitwardenClient.schemas import client_settings_from_dict, DeviceType From 8f0885ef68ab52153e7ec12398973c90b0e1a8ad Mon Sep 17 00:00:00 2001 From: Colton Hurst Date: Wed, 18 Oct 2023 09:27:13 -0400 Subject: [PATCH 06/12] SM-874: Handle error cases on secret deletes --- crates/bitwarden/src/error.rs | 4 ++ .../src/secrets_manager/secrets/delete.rs | 43 +++++++++++++------ 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/crates/bitwarden/src/error.rs b/crates/bitwarden/src/error.rs index 6a9b0d8aa..fcbdb208a 100644 --- a/crates/bitwarden/src/error.rs +++ b/crates/bitwarden/src/error.rs @@ -6,6 +6,7 @@ use bitwarden_api_api::apis::Error as ApiError; use bitwarden_api_identity::apis::Error as IdentityError; use reqwest::StatusCode; use thiserror::Error; +use uuid::Uuid; #[derive(Debug, Error)] pub enum Error { @@ -46,6 +47,9 @@ pub enum Error { #[error("Received error message from server: [{}] {}", .status, .message)] ResponseContent { status: StatusCode, message: String }, + #[error("Received error messages from the API: {0:?}")] + ApiError(Vec<(Uuid, String)>), + #[error("Internal error: {0}")] Internal(&'static str), } diff --git a/crates/bitwarden/src/secrets_manager/secrets/delete.rs b/crates/bitwarden/src/secrets_manager/secrets/delete.rs index 19337e7a1..01c7e47ee 100644 --- a/crates/bitwarden/src/secrets_manager/secrets/delete.rs +++ b/crates/bitwarden/src/secrets_manager/secrets/delete.rs @@ -39,14 +39,28 @@ impl SecretsDeleteResponse { pub(crate) fn process_response( response: BulkDeleteResponseModelListResponseModel, ) -> Result { - Ok(SecretsDeleteResponse { - data: response - .data - .unwrap_or_default() - .into_iter() - .map(SecretDeleteResponse::process_response) - .collect::>()?, - }) + let mut successes = Vec::new(); + let mut failures = Vec::new(); + + for item in response.data.unwrap_or_default() { + match SecretDeleteResponse::process_response(item) { + Ok(data) => { + successes.push(data); + } + Err(Error::ApiError(error)) => { + failures.extend_from_slice(&error); + } + Err(_) => { + unreachable!(); + } + } + } + + if failures.is_empty() { + Ok(SecretsDeleteResponse { data: successes }) + } else { + Err(Error::ApiError(failures)) + } } } @@ -61,9 +75,14 @@ impl SecretDeleteResponse { pub(crate) fn process_response( response: BulkDeleteResponseModel, ) -> Result { - Ok(SecretDeleteResponse { - id: response.id.ok_or(Error::MissingFields)?, - error: response.error, - }) + let id = response.id.ok_or(Error::MissingFields)?; + + match response.error { + Some(error) => Err(Error::ApiError(vec![(id, error)])), + None => Ok(SecretDeleteResponse { + id: id, + error: None, + }), + } } } From 578f41cef05423831d8306a464cfa664fc5344ba Mon Sep 17 00:00:00 2001 From: Colton Hurst Date: Wed, 18 Oct 2023 09:44:22 -0400 Subject: [PATCH 07/12] SM-874: Fix linting issue --- crates/bitwarden/src/secrets_manager/secrets/delete.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/bitwarden/src/secrets_manager/secrets/delete.rs b/crates/bitwarden/src/secrets_manager/secrets/delete.rs index 01c7e47ee..3cc46cbcd 100644 --- a/crates/bitwarden/src/secrets_manager/secrets/delete.rs +++ b/crates/bitwarden/src/secrets_manager/secrets/delete.rs @@ -79,10 +79,7 @@ impl SecretDeleteResponse { match response.error { Some(error) => Err(Error::ApiError(vec![(id, error)])), - None => Ok(SecretDeleteResponse { - id: id, - error: None, - }), + None => Ok(SecretDeleteResponse { id, error: None }), } } } From aca61bf6a65d0084651b275f61dfa7b3ee148fd7 Mon Sep 17 00:00:00 2001 From: Colton Hurst Date: Wed, 18 Oct 2023 10:19:41 -0400 Subject: [PATCH 08/12] SM-874: Handle error cases on project deletes --- .../src/secrets_manager/projects/delete.rs | 40 +++++++++++++------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/crates/bitwarden/src/secrets_manager/projects/delete.rs b/crates/bitwarden/src/secrets_manager/projects/delete.rs index 55f792669..fcdb74697 100644 --- a/crates/bitwarden/src/secrets_manager/projects/delete.rs +++ b/crates/bitwarden/src/secrets_manager/projects/delete.rs @@ -39,14 +39,28 @@ impl ProjectsDeleteResponse { pub(crate) fn process_response( response: BulkDeleteResponseModelListResponseModel, ) -> Result { - Ok(ProjectsDeleteResponse { - data: response - .data - .unwrap_or_default() - .into_iter() - .map(ProjectDeleteResponse::process_response) - .collect::>()?, - }) + let mut successes = Vec::new(); + let mut failures = Vec::new(); + + for item in response.data.unwrap_or_default() { + match ProjectDeleteResponse::process_response(item) { + Ok(data) => { + successes.push(data); + } + Err(Error::ApiError(error)) => { + failures.extend_from_slice(&error); + } + Err(_) => { + unreachable!(); + } + } + } + + if failures.is_empty() { + Ok(ProjectsDeleteResponse { data: successes }) + } else { + Err(Error::ApiError(failures)) + } } } @@ -61,9 +75,11 @@ impl ProjectDeleteResponse { pub(crate) fn process_response( response: BulkDeleteResponseModel, ) -> Result { - Ok(ProjectDeleteResponse { - id: response.id.ok_or(Error::MissingFields)?, - error: response.error, - }) + let id = response.id.ok_or(Error::MissingFields)?; + + match response.error { + Some(error) => Err(Error::ApiError(vec![(id, error)])), + None => Ok(ProjectDeleteResponse { id, error: None }), + } } } From 171d8a3a8f4c0515fc4962764c4a52b8ca65ed8d Mon Sep 17 00:00:00 2001 From: Colton Hurst Date: Mon, 23 Oct 2023 08:30:20 -0400 Subject: [PATCH 09/12] SM-874: Revert error.rs, projects/delete.rs, and secrets/delete.rs --- crates/bitwarden/src/error.rs | 4 -- .../src/secrets_manager/projects/delete.rs | 40 ++++++------------- .../src/secrets_manager/secrets/delete.rs | 40 ++++++------------- 3 files changed, 24 insertions(+), 60 deletions(-) diff --git a/crates/bitwarden/src/error.rs b/crates/bitwarden/src/error.rs index fcbdb208a..6a9b0d8aa 100644 --- a/crates/bitwarden/src/error.rs +++ b/crates/bitwarden/src/error.rs @@ -6,7 +6,6 @@ use bitwarden_api_api::apis::Error as ApiError; use bitwarden_api_identity::apis::Error as IdentityError; use reqwest::StatusCode; use thiserror::Error; -use uuid::Uuid; #[derive(Debug, Error)] pub enum Error { @@ -47,9 +46,6 @@ pub enum Error { #[error("Received error message from server: [{}] {}", .status, .message)] ResponseContent { status: StatusCode, message: String }, - #[error("Received error messages from the API: {0:?}")] - ApiError(Vec<(Uuid, String)>), - #[error("Internal error: {0}")] Internal(&'static str), } diff --git a/crates/bitwarden/src/secrets_manager/projects/delete.rs b/crates/bitwarden/src/secrets_manager/projects/delete.rs index fcdb74697..55f792669 100644 --- a/crates/bitwarden/src/secrets_manager/projects/delete.rs +++ b/crates/bitwarden/src/secrets_manager/projects/delete.rs @@ -39,28 +39,14 @@ impl ProjectsDeleteResponse { pub(crate) fn process_response( response: BulkDeleteResponseModelListResponseModel, ) -> Result { - let mut successes = Vec::new(); - let mut failures = Vec::new(); - - for item in response.data.unwrap_or_default() { - match ProjectDeleteResponse::process_response(item) { - Ok(data) => { - successes.push(data); - } - Err(Error::ApiError(error)) => { - failures.extend_from_slice(&error); - } - Err(_) => { - unreachable!(); - } - } - } - - if failures.is_empty() { - Ok(ProjectsDeleteResponse { data: successes }) - } else { - Err(Error::ApiError(failures)) - } + Ok(ProjectsDeleteResponse { + data: response + .data + .unwrap_or_default() + .into_iter() + .map(ProjectDeleteResponse::process_response) + .collect::>()?, + }) } } @@ -75,11 +61,9 @@ impl ProjectDeleteResponse { pub(crate) fn process_response( response: BulkDeleteResponseModel, ) -> Result { - let id = response.id.ok_or(Error::MissingFields)?; - - match response.error { - Some(error) => Err(Error::ApiError(vec![(id, error)])), - None => Ok(ProjectDeleteResponse { id, error: None }), - } + Ok(ProjectDeleteResponse { + id: response.id.ok_or(Error::MissingFields)?, + error: response.error, + }) } } diff --git a/crates/bitwarden/src/secrets_manager/secrets/delete.rs b/crates/bitwarden/src/secrets_manager/secrets/delete.rs index 3cc46cbcd..19337e7a1 100644 --- a/crates/bitwarden/src/secrets_manager/secrets/delete.rs +++ b/crates/bitwarden/src/secrets_manager/secrets/delete.rs @@ -39,28 +39,14 @@ impl SecretsDeleteResponse { pub(crate) fn process_response( response: BulkDeleteResponseModelListResponseModel, ) -> Result { - let mut successes = Vec::new(); - let mut failures = Vec::new(); - - for item in response.data.unwrap_or_default() { - match SecretDeleteResponse::process_response(item) { - Ok(data) => { - successes.push(data); - } - Err(Error::ApiError(error)) => { - failures.extend_from_slice(&error); - } - Err(_) => { - unreachable!(); - } - } - } - - if failures.is_empty() { - Ok(SecretsDeleteResponse { data: successes }) - } else { - Err(Error::ApiError(failures)) - } + Ok(SecretsDeleteResponse { + data: response + .data + .unwrap_or_default() + .into_iter() + .map(SecretDeleteResponse::process_response) + .collect::>()?, + }) } } @@ -75,11 +61,9 @@ impl SecretDeleteResponse { pub(crate) fn process_response( response: BulkDeleteResponseModel, ) -> Result { - let id = response.id.ok_or(Error::MissingFields)?; - - match response.error { - Some(error) => Err(Error::ApiError(vec![(id, error)])), - None => Ok(SecretDeleteResponse { id, error: None }), - } + Ok(SecretDeleteResponse { + id: response.id.ok_or(Error::MissingFields)?, + error: response.error, + }) } } From f80c4dd889eb46af512b317f0a77a56a71656fe7 Mon Sep 17 00:00:00 2001 From: Colton Hurst Date: Thu, 16 Nov 2023 11:04:41 -0500 Subject: [PATCH 10/12] SM-874: Move failure check to run_command function --- languages/python/BitwardenClient/bitwarden_client.py | 7 ++++++- languages/python/example.py | 3 --- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/languages/python/BitwardenClient/bitwarden_client.py b/languages/python/BitwardenClient/bitwarden_client.py index 0f0f61407..291c14327 100644 --- a/languages/python/BitwardenClient/bitwarden_client.py +++ b/languages/python/BitwardenClient/bitwarden_client.py @@ -26,7 +26,12 @@ def projects(self): def _run_command(self, command: Command) -> Any: response_json = self.inner.run_command(json.dumps(command.to_dict())) - return json.loads(response_json) + response = json.loads(response_json) + + if response["success"] == False: + raise Exception(response["errorMessage"]) + + return response class SecretsClient: def __init__(self, client: BitwardenClient): diff --git a/languages/python/example.py b/languages/python/example.py index 9cac01089..897187b87 100644 --- a/languages/python/example.py +++ b/languages/python/example.py @@ -19,9 +19,6 @@ # Attempt to authenticate with the Secrets Manager Access Token result = client.access_token_login("access_token_here") -if result.success == False: - sys.exit(result.error_message) - # -- Example Project Commands -- project = client.projects().create("ProjectName", organization_id) From c99235045e854e0447a7d30c4e74dcbb25cacd8b Mon Sep 17 00:00:00 2001 From: Colton Hurst Date: Thu, 16 Nov 2023 11:08:17 -0500 Subject: [PATCH 11/12] Remove return value from access_token_login --- languages/python/BitwardenClient/bitwarden_client.py | 5 ++--- languages/python/example.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/languages/python/BitwardenClient/bitwarden_client.py b/languages/python/BitwardenClient/bitwarden_client.py index 291c14327..cb669a40e 100644 --- a/languages/python/BitwardenClient/bitwarden_client.py +++ b/languages/python/BitwardenClient/bitwarden_client.py @@ -12,11 +12,10 @@ def __init__(self, settings: ClientSettings = None): settings_json = json.dumps(settings.to_dict()) self.inner = bitwarden_py.BitwardenClient(settings_json) - def access_token_login(self, access_token: str) -> AccessTokenLoginResponse: - result = self._run_command( + def access_token_login(self, access_token: str): + self._run_command( Command(access_token_login=AccessTokenLoginRequest(access_token)) ) - return ResponseForAccessTokenLoginResponse.from_dict(result) def secrets(self): return SecretsClient(self) diff --git a/languages/python/example.py b/languages/python/example.py index 897187b87..7d7125460 100644 --- a/languages/python/example.py +++ b/languages/python/example.py @@ -17,7 +17,7 @@ organization_id = "org_id_here" # Attempt to authenticate with the Secrets Manager Access Token -result = client.access_token_login("access_token_here") +client.access_token_login("access_token_here") # -- Example Project Commands -- From e8a445b5c55829740a173526643eb576bd60e6af Mon Sep 17 00:00:00 2001 From: Colton Hurst Date: Fri, 17 Nov 2023 11:54:09 -0500 Subject: [PATCH 12/12] SM-874: In example.py, secrets.get should pass the id, not an array of id's --- languages/python/example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/languages/python/example.py b/languages/python/example.py index 7d7125460..2ee1cb280 100644 --- a/languages/python/example.py +++ b/languages/python/example.py @@ -36,7 +36,7 @@ secret = client.secrets().create("TEST_SECRET", "This is a test secret", organization_id, "Secret1234!", [project2.data.id]) secret2 = client.secrets().create("Secret - Don't Delete Me!", "This is a test secret that will stay", organization_id, "Secret1234!", [project2.data.id]) secret_updated = client.secrets().update(secret.data.id, "TEST_SECRET_UPDATED", "This as an updated test secret", organization_id, "Secret1234!_updated", [project2.data.id]) -secret_retrieved = client.secrets().get([secret.data.id]) +secret_retrieved = client.secrets().get(secret.data.id) input("Press Enter to delete the secret...") client.secrets().delete([secret.data.id])