Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,23 @@ pip install python-vaultwarden
```python
from vaultwarden.clients.vaultwarden import VaultwardenAdminClient

client = VaultwardenAdminClient(url="https://vaultwarden.example.com", admin_secret_token="admin_token")
client = VaultwardenAdminClient(url="https://vaultwarden.example.com", admin_secret_token="admin_token", preload_users=True)

client.invite("[email protected]")

all_users = client.get_all_users()
# Get all users
all_users = client.users()

client.delete(all_users[0].id)
# Get a specific user by email
user = client.user(email="[email protected]")

# Delete/Disable/Enable a user by ID
client.delete(user.Id)
client.disable(user.Id)
client.enable(user.Id)

# Set enabled status of a user
client.set_user_enabled(user.Id, enabled=True)
```

### Bitwarden client
Expand Down
68 changes: 51 additions & 17 deletions src/vaultwarden/clients/vaultwarden.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,36 +133,70 @@ def users(

# User Management Part
def invite(self, email: str) -> bool:
res = True
try:
self._admin_request("POST", "invite", json={"email": email})
except HTTPStatusError as e:
res = e.response.status_code == http.HTTPStatus.CONFLICT
if not res:
logger.warning(f"Failed to invite {email}")
else:
self._load_users()
return res
if e.response.status_code != http.HTTPStatus.CONFLICT:
logger.warning(f"Failed to invite {email} {e}")
return False
self._load_users()
return True

def delete(self, identifier: str | UUID) -> bool:
logger.info(f"Deleting {identifier} account")
res = True
try:
self._admin_request("POST", f"users/{identifier}/delete")
except HTTPStatusError:
res = False
if not res:
logger.warning(f"Failed to delete {identifier}")
else:
self._load_users()
return res
except HTTPStatusError as e:
logger.warning(f"Failed to delete {identifier} {e}")
return False
self._load_users()
logger.info(f"Successfully deleted account: {identifier}")
return True

def disable(self, identifier: str | UUID) -> bool:
logger.info(f"Disabling {identifier} account")
try:
self._admin_request(
"POST",
f"users/{identifier}/disable",
headers={"Content-Type": "application/json"},
)
except HTTPStatusError as e:
logger.warning(f"Failed to disable {identifier} {e}")
return False
self._load_users()
logger.info(f"Successfully disabled account: {identifier}")
return True

def enable(self, identifier: str | UUID) -> bool:
logger.info(f"Enabling {identifier} account")
try:
self._admin_request(
"POST",
f"users/{identifier}/enable",
headers={"Content-Type": "application/json"},
)
except HTTPStatusError as e:
logger.warning(f"Failed to enable {identifier} {e}")
return False
self._load_users()
logger.info(f"Successfully enabled account: {identifier}")
return True

def set_user_enabled(self, identifier: str | UUID, enabled: bool) -> None:
"""Disabling a user also deauthorizes all its sessions"""
if enabled:
resp = self._admin_request("POST", f"users/{identifier}/enable")
resp = self._admin_request(
"POST",
f"users/{identifier}/enable",
headers={"Content-Type": "application/json"},
)
else:
resp = self._admin_request("POST", f"users/{identifier}/disable")
resp = self._admin_request(
"POST",
f"users/{identifier}/disable",
headers={"Content-Type": "application/json"},
)
resp.raise_for_status()

def remove_2fa(self, uuid=None, email=None) -> bool:
Expand Down