Skip to content
Closed
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
32 changes: 19 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,25 +51,26 @@ import asyncio
import os
from onepassword.client import Client, DesktopAuth


async def main():
# Connects to 1Password. Fill in your own integration name and version.
client = await Client.authenticate(
auth=DesktopAuth(
# TODO: Set to your 1Password account name.
account_name="your-account-name"
),
# TODO: Set to your own integration name and version.
integration_name="My 1Password Integration",
integration_version="v1.0.0",
)
auth=DesktopAuth(
# TODO: Set to your 1Password account name.
account_name="your-account-name"
),
# TODO: Set to your own integration name and version.
integration_name="My 1Password Integration",
integration_version="v1.0.0",
)

# Retrieves a secret from 1Password. Takes a secret reference as input and returns the secret to which it points.
value = await client.secrets.resolve("op://vault/item/field")
# use value here

if __name__ == '__main__':
asyncio.run(main())

if __name__ == "__main__":
asyncio.run(main())
```

Make sure to use [secret reference URIs](https://developer.1password.com/docs/cli/secret-reference-syntax/) with the syntax `op://vault/item/field` to securely load secrets from 1Password into your code.
Expand Down Expand Up @@ -106,20 +107,25 @@ import asyncio
import os
from onepassword.client import Client


async def main():
# Gets your service account token from the OP_SERVICE_ACCOUNT_TOKEN environment variable.
token = os.getenv("OP_SERVICE_ACCOUNT_TOKEN")

# Connects to 1Password. Fill in your own integration name and version.
client = await Client.authenticate(auth=token, integration_name="My 1Password Integration", integration_version="v1.0.0")
client = await Client.authenticate(
auth=token,
integration_name="My 1Password Integration",
integration_version="v1.0.0",
)

# Retrieves a secret from 1Password. Takes a secret reference as input and returns the secret to which it points.
value = await client.secrets.resolve("op://vault/item/field")
# use value here

if __name__ == '__main__':
asyncio.run(main())

if __name__ == "__main__":
asyncio.run(main())
```

Make sure to use [secret reference URIs](https://developer.1password.com/docs/cli/secret-reference-syntax/) with the syntax `op://vault/item/field` to securely load secrets from 1Password into your code.
Expand Down
12 changes: 7 additions & 5 deletions example/desktop_app.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# [developer-docs.sdk.python.sdk-import]-start
from onepassword import *

# [developer-docs.sdk.python.sdk-import]-end
import asyncio
import os
Expand Down Expand Up @@ -107,7 +108,9 @@ async def showcase_vault_operations(client: Client):
# [developer-docs.sdk.python.list-vault]-end


async def showcase_group_permission_operations(client: Client, vault_id: str, group_id: str):
async def showcase_group_permission_operations(
client: Client, vault_id: str, group_id: str
):
# [developer-docs.sdk.python.grant-group-permissions]-start
# Grant group permissions in a vault
await client.vaults.grant_group_permissions(
Expand Down Expand Up @@ -202,8 +205,7 @@ async def showcase_batch_item_operations(client: Client, vault_id: str):
item_ids = []
for res in batchCreateResponse.individual_responses:
if res.content is not None:
print('Created item "{}" ({})'.format(
res.content.title, res.content.id))
print('Created item "{}" ({})'.format(res.content.title, res.content.id))
item_ids.append(res.content.id)
elif res.error is not None:
print("[Batch create] Something went wrong: {}".format(res.error))
Expand All @@ -214,8 +216,7 @@ async def showcase_batch_item_operations(client: Client, vault_id: str):
batchGetReponse = await client.items.get_all(vault_id, item_ids)
for res in batchGetReponse.individual_responses:
if res.content is not None:
print('Obtained item "{}" ({})'.format(
res.content.title, res.content.id))
print('Obtained item "{}" ({})'.format(res.content.title, res.content.id))
elif res.error is not None:
print("[Batch get] Something went wrong: {}".format(res.error))
# [developer-docs.sdk.python.batch-get-items]-end
Expand All @@ -230,5 +231,6 @@ async def showcase_batch_item_operations(client: Client, vault_id: str):
print("Deleted item {}".format(id))
# [developer-docs.sdk.python.batch-delete-items]-end


if __name__ == "__main__":
asyncio.run(main())
20 changes: 11 additions & 9 deletions example/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,9 @@ async def main():
# [developer-docs.sdk.python.delete-item]-start
# Delete an item
await client.items.delete(created_item.vault_id, updated_item.id)
print(f"Item {updated_item.id} successfully deleted from vault {created_item.vault_id}.")
print(
f"Item {updated_item.id} successfully deleted from vault {created_item.vault_id}."
)
# [developer-docs.sdk.python.delete-item]-end

await showcase_vault_operations(client)
Expand Down Expand Up @@ -242,7 +244,7 @@ async def showcase_vault_operations(client: Client):
updated_vault = await client.vaults.update(created_vault.id, update_params)
print(
f'Updated vault "{updated_vault.title}" ({updated_vault.id}): '
f'{updated_vault.description!r}'
f"{updated_vault.description!r}"
)
# [developer-docs.sdk.python.update-vault]-end

Expand Down Expand Up @@ -321,8 +323,7 @@ async def showcase_batch_item_operations(client: Client, vault_id: str):
item_ids = []
for res in batchCreateResponse.individual_responses:
if res.content is not None:
print('Created item "{}" ({})'.format(
res.content.title, res.content.id))
print('Created item "{}" ({})'.format(res.content.title, res.content.id))
item_ids.append(res.content.id)
elif res.error is not None:
print("[Batch create] Something went wrong: {}".format(res.error))
Expand All @@ -333,8 +334,7 @@ async def showcase_batch_item_operations(client: Client, vault_id: str):
batchGetReponse = await client.items.get_all(vault_id, item_ids)
for res in batchGetReponse.individual_responses:
if res.content is not None:
print('Obtained item "{}" ({})'.format(
res.content.title, res.content.id))
print('Obtained item "{}" ({})'.format(res.content.title, res.content.id))
elif res.error is not None:
print("[Batch get] Something went wrong: {}".format(res.error))
# [developer-docs.sdk.python.batch-get-items]-end
Expand Down Expand Up @@ -633,7 +633,10 @@ def generate_special_item_fields():
],
)

async def showcase_group_permission_operations(client: Client, vault_id: str, group_id: str):

async def showcase_group_permission_operations(
client: Client, vault_id: str, group_id: str
):
# [developer-docs.sdk.python.grant-group-permissions]-start
# Grant group permissions in a vault
await client.vaults.grant_group_permissions(
Expand All @@ -655,7 +658,7 @@ async def showcase_group_permission_operations(client: Client, vault_id: str, gr
GroupVaultAccess(
vault_id=vault_id,
group_id=group_id,
permissions= READ_ITEMS | CREATE_ITEMS | UPDATE_ITEMS,
permissions=READ_ITEMS | CREATE_ITEMS | UPDATE_ITEMS,
)
],
)
Expand All @@ -678,6 +681,5 @@ async def showcase_group_permission_operations(client: Client, vault_id: str, gr
# [developer-docs.sdk.python.get-group]-end



if __name__ == "__main__":
asyncio.run(main())
4 changes: 3 additions & 1 deletion src/onepassword/core.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from __future__ import annotations
import json
import platform
from typing import Any, Protocol
from onepassword.desktop_core import DesktopCore
from onepassword.errors import raise_typed_exception, DesktopSessionExpiredException

Check failure on line 6 in src/onepassword/core.py

View workflow job for this annotation

GitHub Actions / Lint

ruff (I001)

src/onepassword/core.py:1:1: I001 Import block is un-sorted or un-formatted help: Organize imports

Check failure on line 6 in src/onepassword/core.py

View workflow job for this annotation

GitHub Actions / Lint

ruff (I001)

src/onepassword/core.py:1:1: I001 Import block is un-sorted or un-formatted help: Organize imports

# In empirical tests, we determined that maximum message size that can cross the FFI boundary
# is ~128MB. Past this limit, FFI will throw an error and the program will crash.
Expand All @@ -23,7 +23,9 @@
core: DesktopCore | UniffiCore
config: dict[str, Any]

def __init__(self, client_id: int, core: "DesktopCore | UniffiCore", config: dict[str, any]):
def __init__(
self, client_id: int, core: "DesktopCore | UniffiCore", config: dict[str, any]

Check failure on line 27 in src/onepassword/core.py

View workflow job for this annotation

GitHub Actions / Lint

ruff (UP037)

src/onepassword/core.py:27:37: UP037 Remove quotes from type annotation help: Remove quotes

Check failure on line 27 in src/onepassword/core.py

View workflow job for this annotation

GitHub Actions / Lint

ruff (UP037)

src/onepassword/core.py:27:37: UP037 Remove quotes from type annotation help: Remove quotes
):
self.client_id = client_id
self.core = core
self.config = config
Expand All @@ -37,7 +39,7 @@
invoke_config["invocation"]["clientId"] = self.client_id
return await self.core.invoke(invoke_config)
except Exception as e:
raise e

Check failure on line 42 in src/onepassword/core.py

View workflow job for this annotation

GitHub Actions / Lint

ruff (TRY201)

src/onepassword/core.py:42:19: TRY201 Use `raise` without specifying exception name help: Remove exception name

Check failure on line 42 in src/onepassword/core.py

View workflow job for this annotation

GitHub Actions / Lint

ruff (TRY201)

src/onepassword/core.py:42:19: TRY201 Use `raise` without specifying exception name help: Remove exception name


class UniffiCore:
Expand All @@ -59,7 +61,7 @@
"""Creates a client instance in the current core module and returns its unique ID."""
try:
return await self.core.init_client(json.dumps(client_config))
except Exception as e:

Check failure on line 64 in src/onepassword/core.py

View workflow job for this annotation

GitHub Actions / Lint

ruff (BLE001)

src/onepassword/core.py:64:16: BLE001 Do not catch blind exception: `Exception`

Check failure on line 64 in src/onepassword/core.py

View workflow job for this annotation

GitHub Actions / Lint

ruff (BLE001)

src/onepassword/core.py:64:16: BLE001 Do not catch blind exception: `Exception`
raise_typed_exception(e)

async def invoke(self, invoke_config: dict):
Expand Down
2 changes: 2 additions & 0 deletions src/onepassword/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
DEFAULT_REQUEST_LIBRARY_VERSION = "0.11.24"
DEFAULT_OS_VERSION = "0.0.0"


class DesktopAuth:
def __init__(self, account_name: str):
"""
Expand All @@ -19,6 +20,7 @@ def __init__(self, account_name: str):
"""
self.account_name = account_name


# Generates a configuration dictionary with the user's parameters
def new_default_config(auth: DesktopAuth | str, integration_name, integration_version):
client_config_dict = {
Expand Down
23 changes: 14 additions & 9 deletions src/onepassword/desktop_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,22 @@ def find_1password_lib_path():
if os_name == "Darwin": # macOS
locations = [
"/Applications/1Password.app/Contents/Frameworks/libop_sdk_ipc_client.dylib",
str(Path.home() / "Applications/1Password.app/Contents/Frameworks/libop_sdk_ipc_client.dylib"),
str(
Path.home()
/ "Applications/1Password.app/Contents/Frameworks/libop_sdk_ipc_client.dylib"
),
]
elif os_name == "Linux":
locations = [
"/usr/bin/1password/libop_sdk_ipc_client.so",
"/opt/1Password/libop_sdk_ipc_client.so",
"/snap/bin/1password/libop_sdk_ipc_client.so",
"/opt/1Password/libop_sdk_ipc_client.so",
"/snap/bin/1password/libop_sdk_ipc_client.so",
]
elif os_name == "Windows":
locations = [
str(Path.home() / r"AppData\Local\1Password\op_sdk_ipc_client.dll"),
r"C:\Program Files\1Password\app\8\op_sdk_ipc_client.dll",
r"C:\Program Files (x86)\1Password\app\8\op_sdk_ipc_client.dll",
r"C:\Program Files (x86)\1Password\app\8\op_sdk_ipc_client.dll",
str(Path.home() / r"AppData\Local\1Password\app\8\op_sdk_ipc_client.dll"),
]
else:
Expand All @@ -40,6 +43,7 @@ def find_1password_lib_path():

raise FileNotFoundError("1Password desktop application not found")


class DesktopCore:
def __init__(self, account_name: str):
# Determine the path to the desktop app.
Expand All @@ -51,11 +55,11 @@ def __init__(self, account_name: str):
# Bind the Rust-exported functions
self.send_message = self.lib.op_sdk_ipc_send_message
self.send_message.argtypes = [
POINTER(c_uint8), # msg_ptr
c_size_t, # msg_len
POINTER(POINTER(c_uint8)), # out_buf
POINTER(c_size_t), # out_len
POINTER(c_size_t), # out_cap
POINTER(c_uint8), # msg_ptr
c_size_t, # msg_len
POINTER(POINTER(c_uint8)), # out_buf
POINTER(c_size_t), # out_len
POINTER(c_size_t), # out_cap
]
self.send_message.restype = c_int32

Expand Down Expand Up @@ -123,6 +127,7 @@ def release_client(self, client_id: int):
except Exception as e:
print(f"failed to release client: {e}")


ERR_CHANNEL_CLOSED = (
"desktop app connection channel is closed. "
"Make sure Settings > Developer > Integrate with other apps is enabled, "
Expand Down
3 changes: 2 additions & 1 deletion src/onepassword/environments.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@

class Environments:
"""
The Environments API holds all the operations the SDK client can perform on 1Password Environments.
The Environments API holds all the operations the SDK client can perform
on 1Password Environments and the secrets inside them.
"""

def __init__(self, inner_client: InnerClient):
Expand Down
8 changes: 8 additions & 0 deletions src/onepassword/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ def __init__(self, message):
super().__init__(self.message)


class AuthExpiredException(Exception):
def __init__(self, message):
self.message = message
super().__init__(self.message)


def raise_typed_exception(e: Exception):
try:
typed_error = json.loads(e.msg)
Expand All @@ -28,6 +34,8 @@ def raise_typed_exception(e: Exception):
raise DesktopSessionExpiredException(message)
elif error_name == "RateLimitExceeded":
raise RateLimitExceededException(message)
elif error_name == "AuthExpired":
raise AuthExpiredException(message)
elif message is not None:
raise Exception(message)
else:
Expand Down
Binary file modified src/onepassword/lib/aarch64/libop_uniffi_core.dylib
100755 → 100644
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading
Loading