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
9 changes: 8 additions & 1 deletion .github/check_entitlements.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#!/bin/bash
#!/usr/bin/env bash

set -euo pipefail

# Derive additional environment variables
TOKEN_URL="${OIDC_OP_TOKEN_ENDPOINT}"
Expand All @@ -24,6 +26,11 @@ get_token() {

echo "🔐 Getting access token..."
BEARER=$( get_token | jq -r '.access_token' )
if [ -z "$BEARER" ]; then
echo "❌ Failed to get access token."
exit 1
fi
Comment on lines +29 to +32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This check is great for robustness! To make it more concise and idiomatic to shell scripting, you can use a short-circuit || operator. This also provides an opportunity to redirect the error message to stderr (>&2), which is a best practice for error reporting.

Suggested change
if [ -z "$BEARER" ]; then
echo "❌ Failed to get access token."
exit 1
fi
[ -n "$BEARER" ] || { echo "❌ Failed to get access token." >&2; exit 1; }

Comment on lines +29 to +32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The check for an empty BEARER token is a good addition. However, it doesn't account for the case where the curl command succeeds but the JSON response from the token endpoint does not contain an access_token. In that scenario, jq -r '.access_token' will output the string "null", causing the BEARER variable to be assigned this string. The current check [ -z "$BEARER" ] would pass, and the script would proceed with an invalid token.

To make this check more robust, you should also test if $BEARER is equal to the string "null".

Suggested change
if [ -z "$BEARER" ]; then
echo "❌ Failed to get access token."
exit 1
fi
if [ -z "$BEARER" ] || [ "$BEARER" == "null" ]; then
echo "❌ Failed to get access token."
exit 1
fi


# NOTE: It's always okay to print this token, because it will
# only be valid / available in dummy / dev scenarios
[[ "${DEBUG:-}" == "1" ]] && echo "Got Access Token: ${BEARER}"
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/platform-integration-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ jobs:
with:
enable-cache: true
cache-dependency-glob: "uv.lock"
python-version: ${{ inputs.python_version }}

- name: Run all tests, minus integration tests
env:
Expand Down
84 changes: 5 additions & 79 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,65 +13,6 @@ Unofficial OpenTDF SDK for Python

A legacy version (0.2.x) of this project is available for users who need the previous implementation. For more information, see [LEGACY_VERSION.md](docs/LEGACY_VERSION.md) or visit the [legacy branch on GitHub](https://github.com/b-long/opentdf-python-sdk/tree/0.2.x).

## Prerequisites

This project uses [uv](https://docs.astral.sh/uv/) for dependency management and task running.

### Installing uv

Install `uv` using one of the following methods:

**macOS/Linux:**
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```

**Windows:**
```powershell
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
```

**Using Homebrew (macOS):**
```bash
brew install uv
```

For more installation options, see the [uv installation guide](https://docs.astral.sh/uv/getting-started/installation/).

## Development Setup

1. Clone the repository:
```bash
git clone <repository-url>
cd opentdf-python-sdk
```

2. Install dependencies:
```bash
uv sync
```

## Running Tests

Run the full test suite:
```bash
uv run pytest tests/
```

Run specific test files:
```bash
uv run pytest tests/test_sdk.py
```

Run tests with verbose output:
```bash
uv run pytest tests/ -v
```

Run integration tests only:
```bash
uv run pytest tests/ -m integration
```

## Installation

Expand All @@ -80,21 +21,6 @@ Install from PyPI:
pip install otdf-python
```


## Protobuf & Connect RPC Generation

This project uses a dedicated submodule, `otdf-python-proto/`, for generating Python protobuf files and Connect RPC clients from OpenTDF platform proto definitions.

### Regenerating Protobuf & Connect RPC Files

From the submodule:
```bash
cd otdf-python-proto
uv run python scripts/generate_connect_proto.py
```

See [`otdf-python-proto/README.md`](otdf-python-proto/README.md) and [`PROTOBUF_SETUP.md`](PROTOBUF_SETUP.md) for details.

## Quick Start

### Basic Configuration
Expand Down Expand Up @@ -141,7 +67,7 @@ sdk = builder.build()
from io import BytesIO

# Create TDF configuration with attributes
config = sdk.new_tdf_config(attributes=["https://example.com/attr/classification/value/public"])
config = sdk.new_tdf_config(attributes=["https://example.net/attr/attr1/value/value1"])

# Encrypt data to TDF format
input_data = b"Hello, World!"
Expand All @@ -164,8 +90,7 @@ with open("encrypted.tdf", "rb") as f:
encrypted_data = f.read()

# Decrypt TDF
reader_config = TDFReaderConfig()
tdf_reader = sdk.load_tdf(encrypted_data, reader_config)
tdf_reader = sdk.load_tdf(encrypted_data)
decrypted_data = tdf_reader.payload

# Save decrypted data
Expand All @@ -189,21 +114,22 @@ src/otdf_python/
└── ... # Additional modules
tests/
└── ... # Various tests
```

## Contributing

1. Fork the repository
2. Create a feature branch: `git checkout -b feature-name`
3. Make your changes
4. Run tests: `uv run pytest tests/`
5. Commit your changes: `git commit -am 'Add feature'`
5. Commit your changes: `git commit -am 'feat: add feature'`
6. Push to the branch: `git push origin feature-name`
7. Submit a pull request

### Release Process

For maintainers and contributors working on releases:
- See [RELEASES.md](RELEASES.md) for comprehensive release documentation
- See [RELEASES.md](docs/RELEASES.md) for comprehensive release documentation
- Feature branch alpha releases available for testing changes before merge
- Automated releases via Release Please on the main branch

Expand Down
77 changes: 77 additions & 0 deletions docs/DEVELOPING.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,80 @@ You can run the following command in your terminal:
```

Using this script will automatically enable direct access grants in Keycloak for you.

## Dependency Management

### Prerequisites

This project uses [uv](https://docs.astral.sh/uv/) for dependency management and task running.

#### Installing uv

Install `uv` using one of the following methods:

**macOS/Linux:**
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```

**Windows:**
```powershell
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
```

**Using Homebrew (macOS):**
```bash
brew install uv
```

For more installation options, see the [uv installation guide](https://docs.astral.sh/uv/getting-started/installation/).

## Development Setup

1. Clone the repository:
```bash
git clone https://github.com/b-long/opentdf-python-sdk.git
cd opentdf-python-sdk
```

2. Install dependencies:
```bash
uv sync
```

### Running Tests

Run the full test suite:
```bash
uv run pytest tests/
```

Run specific test files:
```bash
uv run pytest tests/test_sdk.py
```

Run tests with verbose output:
```bash
uv run pytest tests/ -v
```

Run integration tests only:
```bash
uv run pytest tests/ -m integration
```


### Protobuf & Connect RPC Generation

This project uses a dedicated submodule, `otdf-python-proto/`, for generating Python protobuf files and Connect RPC clients from OpenTDF platform proto definitions.

#### Regenerating Protobuf & Connect RPC Files

From the submodule:
```bash
cd otdf-python-proto
uv run python scripts/generate_connect_proto.py
```

See [`otdf-python-proto/README.md`](../otdf-python-proto/README.md) and [`PROTOBUF_SETUP.md`](./PROTOBUF_SETUP.md) for details.
14 changes: 3 additions & 11 deletions src/otdf_python/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
from otdf_python.sdk import SDK
from otdf_python.sdk_builder import SDKBuilder
from otdf_python.sdk_exceptions import SDKException
from otdf_python.tdf import TDFReaderConfig

try:
__version__ = metadata.version("otdf-python")
Expand Down Expand Up @@ -310,10 +309,7 @@ def cmd_decrypt(args):
if encrypted_data.startswith(b"PK"):
# Regular TDF (ZIP format)
logger.debug("Decrypting TDF")
reader_config = TDFReaderConfig()
tdf_reader = sdk.load_tdf_with_config(
encrypted_data, reader_config
)
tdf_reader = sdk.load_tdf(encrypted_data)
# Access payload directly from TDFReader
payload_bytes = tdf_reader.payload
output_file.write(payload_bytes)
Expand All @@ -336,8 +332,7 @@ def cmd_decrypt(args):
if encrypted_data.startswith(b"PK"):
# Regular TDF (ZIP format)
logger.debug("Decrypting TDF")
reader_config = TDFReaderConfig()
tdf_reader = sdk.load_tdf_with_config(encrypted_data, reader_config)
tdf_reader = sdk.load_tdf(encrypted_data)
payload_bytes = tdf_reader.payload
output_file.write(payload_bytes)
logger.info("Successfully decrypted TDF")
Expand Down Expand Up @@ -370,10 +365,7 @@ def cmd_inspect(args):
if encrypted_data.startswith(b"PK"):
# Regular TDF
logger.debug("Inspecting TDF")
reader_config = TDFReaderConfig()
tdf_reader = sdk.load_tdf_with_config(
BytesIO(encrypted_data), reader_config
)
tdf_reader = sdk.load_tdf(BytesIO(encrypted_data))
manifest = tdf_reader.manifest

# Try to get data attributes
Expand Down
30 changes: 8 additions & 22 deletions src/otdf_python/sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,11 +348,13 @@ def get_platform_url(self) -> str | None:
"""Returns the platform URL if set"""
return self.platform_url

def load_tdf_with_config(
self, tdf_data: bytes | BinaryIO | BytesIO, config: TDFReaderConfig
def load_tdf(
self,
tdf_data: bytes | BinaryIO | BytesIO,
config: TDFReaderConfig | None = None,
) -> TDFReader:
"""
Loads a TDF from the provided data according to the config.
Loads a TDF from the provided data, optionally according to the config.

Args:
tdf_data: The TDF data as bytes, file object, or BytesIO
Expand All @@ -365,26 +367,10 @@ def load_tdf_with_config(
SDKException: If there's an error loading the TDF
"""
tdf = TDF(self.services)
return tdf.load_tdf(tdf_data, config)

def load_tdf_without_config(
self, tdf_data: bytes | BinaryIO | BytesIO
) -> TDFReader:
"""
Loads a TDF from the provided data.

Args:
tdf_data: The TDF data as bytes, file object, or BytesIO
if config is None:
config = TDFReaderConfig()

Returns:
TDFReader: Contains payload and manifest

Raises:
SDKException: If there's an error loading the TDF
"""
tdf = TDF(self.services)
default = TDFReaderConfig()
return tdf.load_tdf(tdf_data, default)
return tdf.load_tdf(tdf_data, config)

def create_tdf(
self,
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/test_pe_interaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def decrypt(input_path: Path, output_path: Path, sdk: SDK):
with open(input_path, "rb") as infile, open(output_path, "wb") as outfile:
try:
logger.debug("Decrypting TDF")
tdf_reader = sdk.load_tdf_without_config(infile.read())
tdf_reader = sdk.load_tdf(infile.read())
# Access payload directly from TDFReader
payload_bytes = tdf_reader.payload
outfile.write(payload_bytes)
Expand Down
8 changes: 1 addition & 7 deletions tests/test_validate_otdf_python.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@

import pytest

from otdf_python.tdf import TDFReaderConfig
from tests.integration.support_sdk import get_sdk

# Set up detailed logging
Expand Down Expand Up @@ -52,13 +51,8 @@ def decrypt_file(encrypted_path: Path) -> Path:

output_path = encrypted_path.with_suffix(".decrypted")
with open(encrypted_path, "rb") as infile, open(output_path, "wb") as outfile:
# Include attributes for policy enforcement
reader_config = TDFReaderConfig(
attributes=_test_attributes # Same attributes used in encrypt_file
)

# Use KAS client for key unwrapping
reader = sdk.load_tdf_with_config(infile.read(), reader_config)
reader = sdk.load_tdf(infile.read())
# TDFReader is a dataclass with payload attribute
outfile.write(reader.payload)
return output_path
Expand Down
Loading