Copy
import sqlite3
from typing import List, Optional, Tuple
from keepersdk import errors
from keepersdk.authentication import keeper_auth
from keepersdk.enterprise import enterprise_data as enterprise_data_types
from keepersdk.vault import sqlite_storage, vault_online, ksm_management
def open_vault(keeper_auth_context: keeper_auth.KeeperAuth) -> vault_online.VaultOnline:
"""Create vault, sync down, and return VaultOnline."""
conn = sqlite3.Connection('file::memory:', uri=True)
vault_storage = sqlite_storage.SqliteVaultStorage(
lambda: conn,
vault_owner=bytes(keeper_auth_context.auth_context.username, 'utf-8'),
)
vault = vault_online.VaultOnline(keeper_auth_context, vault_storage)
vault.sync_down()
return vault
def resolve_app_uid(vault: vault_online.VaultOnline, app_uid_or_name: str) -> str:
"""Resolve KSM app UID from UID or application name."""
app = ksm_management.get_secrets_manager_app(vault, app_uid_or_name)
return app.uid
def get_app_master_key(vault: vault_online.VaultOnline, app_uid: str) -> bytes:
"""Get decrypted record key for the KSM application."""
master_key = vault.vault_data.get_record_key(app_uid)
if not master_key:
raise ValueError(f'Could not resolve record key for application {app_uid}')
return master_key
# ---------------------------------------------------------------------------
# ADD — share record(s) or shared folder(s) TO a KSM app
# CLI: secrets-manager-share --command add --app ... --secret "uid1 uid2" [--editable]
# ---------------------------------------------------------------------------
def add_secrets_to_ksm_app(
vault: vault_online.VaultOnline,
app_uid_or_name: str,
secret_uids: List[str],
*,
is_editable: bool = False,
enterprise: Optional[enterprise_data_types.EnterpriseData] = None,
) -> List[Tuple[str, str]]:
"""
Add secrets to a KSM application.
Args:
vault: Synced VaultOnline instance.
app_uid_or_name: KSM app name or UID.
secret_uids: Record or shared-folder UIDs to share.
is_editable: True = editable by KSM client, False = read-only (default).
enterprise: Enterprise data (used to refresh user permissions on shared items).
Pass None if you only need the KSM share add API call.
Returns:
List of (secret_uid, secret_type) tuples for successfully added secrets.
"""
app_uid = resolve_app_uid(vault, app_uid_or_name)
master_key = get_app_master_key(vault, app_uid)
# Shares secrets via KSM app-share API; sync_down() runs inside
added = ksm_management.KSMShareManagement.add_secrets_to_ksm_app(
vault=vault,
enterprise=enterprise,
app_uid=app_uid,
master_key=master_key,
secret_uids=secret_uids,
is_editable=is_editable,
)
perm = 'editable' if is_editable else 'read-only'
print(f'Added {len(added)} secret(s) to app {app_uid} ({perm}):')
for secret_uid, secret_type in added:
print(f' {secret_uid}\t{secret_type}')
return added
# ---------------------------------------------------------------------------
# REMOVE — remove existing secret shares FROM a KSM app
# CLI: secrets-manager-share --command remove --app ... --secret "uid1 uid2"
# ---------------------------------------------------------------------------
def remove_secrets_from_ksm_app(
vault: vault_online.VaultOnline,
app_uid_or_name: str,
secret_uids: List[str],
) -> List[str]:
"""
Remove secrets already shared with a KSM application.
Returns:
List of UIDs that were actually removed.
"""
if not secret_uids:
raise ValueError('At least one secret UID is required')
app_uid = resolve_app_uid(vault, app_uid_or_name)
# Optional: validate UIDs are currently shared (same as CLI behavior)
app_infos = ksm_management.get_app_info(vault, app_uid)
if not app_infos:
raise ValueError(f'Could not retrieve application info for UID: {app_uid}')
current_shared = {
share.secretUid if isinstance(share.secretUid, str)
else __import__('keepersdk.utils', fromlist=['utils']).utils.base64_url_encode(share.secretUid)
for share in getattr(app_infos[0], 'shares', [])
}
# Normalize to base64-url UIDs
from keepersdk import utils
current_shared = {
uid if len(uid) == 22 else utils.base64_url_encode(uid)
for uid in current_shared
}
valid_uids = [uid for uid in secret_uids if uid in current_shared]
invalid_uids = [uid for uid in secret_uids if uid not in current_shared]
for uid in invalid_uids:
print(f'Warning: {uid} is not shared with this application; skipping.')
if not valid_uids:
print('No valid secrets to remove.')
return []
ksm_management.KSMShareManagement.remove_secrets_from_ksm_app(
vault, app_uid, valid_uids
)
vault.sync_down() # remove API does not sync automatically
print(f'Removed {len(valid_uids)} secret(s) from app {app_uid}:')
for uid in valid_uids:
print(f' {uid}')
return valid_uids
# ---------------------------------------------------------------------------
# UPDATE — change editable <-> read-only on secrets ALREADY shared with the app
# CLI: secrets-manager-share --command update --app ... --secret "uid1" --editable|--readonly
# ---------------------------------------------------------------------------
def update_secrets_manager_app_shares(
vault: vault_online.VaultOnline,
app_uid_or_name: str,
secret_uids: List[str],
*,
is_editable: bool,
) -> List[str]:
"""
Update share permissions on secrets already in the KSM app (remove + re-add).
Returns:
List of UIDs that were actually updated.
"""
updated_uids = ksm_management.update_secrets_manager_app_shares(
vault=vault,
uid_or_name=app_uid_or_name,
secret_uids=secret_uids,
is_editable=is_editable,
)
if updated_uids:
perm = 'editable' if is_editable else 'read-only'
print(f'Updated share permissions to {perm}:')
for uid in updated_uids:
print(f' {uid}')
else:
print('No shares were updated (may already match, or not shared with app).')
return updated_uids
# ---------------------------------------------------------------------------
# Example usage
# ---------------------------------------------------------------------------
def main():
keeper_auth_context = ... # from your login flow
enterprise_data = ... # optional for add; EnterpriseData if enterprise admin
vault = open_vault(keeper_auth_context)
app_uid_or_name = 'My KSM App'
secret_uids = ['IQ_recordUid1', 'IQ_recordUid2', 'IQ_sharedFolderUid3']
try:
# 1) ADD secrets to the app (read-only by default; pass is_editable=True for editable)
add_secrets_to_ksm_app(
vault,
app_uid_or_name,
secret_uids,
is_editable=False,
enterprise=enterprise_data,
)
# 2) UPDATE permissions to editable
update_secrets_manager_app_shares(
vault,
app_uid_or_name,
secret_uids=['IQ_recordUid1'],
is_editable=True,
)
# 3) UPDATE permissions to read-only
update_secrets_manager_app_shares(
vault,
app_uid_or_name,
secret_uids=['IQ_recordUid1'],
is_editable=False,
)
# 4) REMOVE secrets from the app
remove_secrets_from_ksm_app(
vault,
app_uid_or_name,
secret_uids=['IQ_recordUid2'],
)
except ValueError as e:
print(f'Validation error: {e}')
except errors.KeeperApiError as e:
print(f'API error ({e.result_code}): {e.message}')
except Exception as e:
print(f'Error: {e}')
finally:
vault.close()
keeper_auth_context.close()
if __name__ == '__main__':
main()