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-874: Fix Python Integration #229

Merged
merged 24 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
007bae3
SM-874: Update BitwardenClient in login.py
coltonhurst Aug 29, 2023
c384428
Merge branch 'master' into sm/sm-874
coltonhurst Sep 11, 2023
bfa8d94
sm/sm-874: Fix Python SDK integration
coltonhurst Sep 12, 2023
7628b8a
Merge branch 'master' into sm/sm-874
coltonhurst Sep 12, 2023
1833336
SM-874: Add project_ids param to SecretsClient update function in bit…
coltonhurst Sep 13, 2023
82f0fe1
Merge branch 'master' into sm/sm-874
coltonhurst Sep 14, 2023
8c6b527
SM-874: Add a ProjectsClient and address PR comments
coltonhurst Sep 15, 2023
279253e
Merge branch 'master' into sm/sm-874
coltonhurst Sep 15, 2023
4212204
SM-874: Rename login.py and remove unused functions in bitwarden_clie…
coltonhurst Sep 19, 2023
7559397
Merge branch 'master' into sm/sm-874
coltonhurst Sep 19, 2023
87e4c22
Merge branch 'master' into sm/sm-874
coltonhurst Sep 26, 2023
b10fd14
Merge branch 'master' into sm/sm-874
coltonhurst Oct 17, 2023
8f0885e
SM-874: Handle error cases on secret deletes
coltonhurst Oct 18, 2023
578f41c
SM-874: Fix linting issue
coltonhurst Oct 18, 2023
aca61bf
SM-874: Handle error cases on project deletes
coltonhurst Oct 18, 2023
b489e6d
Merge branch 'master' into sm/sm-874
coltonhurst Oct 19, 2023
171d8a3
SM-874: Revert error.rs, projects/delete.rs, and secrets/delete.rs
coltonhurst Oct 23, 2023
14af6a7
Merge branch 'master' into sm/sm-874
coltonhurst Oct 23, 2023
2d421cb
Merge branch 'master' into sm/sm-874
coltonhurst Nov 16, 2023
f80c4dd
SM-874: Move failure check to run_command function
coltonhurst Nov 16, 2023
c992350
Remove return value from access_token_login
coltonhurst Nov 16, 2023
39c2982
Merge branch 'master' into sm/sm-874
coltonhurst Nov 17, 2023
e8a445b
SM-874: In example.py, secrets.get should pass the id, not an array o…
coltonhurst Nov 17, 2023
08f92db
Merge branch 'master' into sm/sm-874
coltonhurst Nov 20, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/sdk-schemas/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
90 changes: 65 additions & 25 deletions languages/python/BitwardenClient/bitwarden_client.py
Original file line number Diff line number Diff line change
@@ -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, 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):
Expand All @@ -12,32 +12,25 @@ 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 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))
def access_token_login(self, access_token: str):
self._run_command(
Command(access_token_login=AccessTokenLoginRequest(access_token))
)
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)

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)
response = json.loads(response_json)

if response["success"] == False:
raise Exception(response["errorMessage"])

return response

class SecretsClient:
def __init__(self, client: BitwardenClient):
Expand All @@ -52,10 +45,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)

Expand All @@ -70,10 +65,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)

Expand All @@ -82,3 +79,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)
6 changes: 5 additions & 1 deletion languages/python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
```bash
pip install setuptools_rust
```
- dateutil
```bash
pip install python-dateutil
```

# Installation

Expand All @@ -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

Expand Down
44 changes: 44 additions & 0 deletions languages/python/example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import json
import logging
import sys
from BitwardenClient.bitwarden_client import BitwardenClient
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": 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"

# Attempt to authenticate with the Secrets Manager Access Token
client.access_token_login("access_token_here")

# -- 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))
24 changes: 0 additions & 24 deletions languages/python/login.py

This file was deleted.