Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
John2360 committed Aug 1, 2024
0 parents commit 62be512
Show file tree
Hide file tree
Showing 11 changed files with 883 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
**/__pycache__/
**/.DS_Store
60 changes: 60 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Fortress Python SDK

Welcome to the Fortress Python SDK. This SDK provides a way for you to leverage the power of the Fortress platform in your Python applications.

## Installation

You can install the SDK using pip. Simply run the following command:

```bash
pip install fortress-platform-sdk
```

## Quick Start

Here is a quick example to get you started with the SDK:

```python
from fortress_python_sdk import Client as FortressClient

# Initialize the client with your API key
client = FortressClient(org_id='your_org_id', api_key='your_api_key')

# Connect to a database
conn = client.connect(database_name='your_database_name')
cursor = conn.cursor()

# Execute a query
cursor.execute('SELECT * FROM your_table_name')

# Fetch the results
results = cursor.fetchall()

# Print the results
for row in results:
print(row)

# Close the connection
conn.close()
```

## Documentation

Below is a list of the available functionality in the SDK.

- `create_database(database_name: str)`: Creates a new database.
- `delete_database(database_name: str)`: Connects to a database.
- `list_databases()`: Lists all databases.
- `connect(database_name: str)`: Connects to a database and turns a LibsqlConnection object.

## Configuration

To use the SDK, generate an API key from the Fortress dashboard to initialize the client. Also, provide the organization ID, which is available under the API Keys page on the platform website.

## License

This SDK is licensed under the MIT License.

## Support

If you have any questions or need help, don't hesitate to get in touch with our support team at [email protected].
21 changes: 21 additions & 0 deletions examples/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from fortress_sdk_python import Client

fortress = Client(
org_id="myOrgId",
api_key="myApiKey",
)
response = fortress.create_database("sdk_test")
if response.success:
print("Database created")


response = fortress.connect("sdk_text")
conn = response.cursor()
result = conn.execute(
"CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY, name TEXT)"
)
result = conn.execute("INSERT INTO test (name) VALUES ('test')")
result = conn.execute("SELECT * FROM test")

for row in result.fetchall():
print(row)
1 change: 1 addition & 0 deletions fortress_sdk_python/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .client import Client
42 changes: 42 additions & 0 deletions fortress_sdk_python/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from .fortress import (
DatabaseCreateResponse,
DatabaseDeleteResponse,
DatabaseListResponse,
Fortress,
)
from .database import ConnectionInterface
from .libsql import LibsqlClient


class Client:
def __init__(self, org_id: str, api_key: str) -> None:
"""Initialize the Fortress client"""
if not org_id:
raise ValueError("Organization ID is required")
if not api_key:
raise ValueError("API Key is required")

self.__fortress = Fortress(org_id, api_key)

def connect(self, database_name: str) -> ConnectionInterface:
"""Connect to a database on the Fortress platform"""
response = self.__fortress.get_uri(database_name)
if not response.success:
raise ValueError(response.message)

return LibsqlClient(
url=response.url,
token=response.token,
).connect()

def create_database(self, database_name: str) -> DatabaseCreateResponse:
"""Create a new database on the Fortress platform"""
return self.__fortress.create_database(database_name)

def delete_database(self, database_name: str) -> DatabaseDeleteResponse:
"""Delete a database on the Fortress platform"""
return self.__fortress.delete_database(database_name)

def list_databases(self) -> DatabaseListResponse:
"""List all databases on the Fortress platform"""
return self.__fortress.list_databases()
70 changes: 70 additions & 0 deletions fortress_sdk_python/crypto.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hmac
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
import hashlib
import hmac
import base64


def decrypt(private_key, ciphertext):
"""Decrypt a ciphertext using the provided private key"""

# Load the private key
private_key = serialization.load_pem_private_key(
f"-----BEGIN EC PRIVATE KEY-----\n{private_key}\n-----END EC PRIVATE KEY-----".encode(),
password=None,
backend=default_backend(),
)

# Decode the ciphertext
ciphertext = base64.b64decode(ciphertext)

# Extract the ephemeral public key
ephemeral_size = int(ciphertext[0])
ephemeral_public_key = ciphertext[1 : 1 + ephemeral_size]

# Extract the MAC and AES-GCM ciphertext
sha1_size = 20
aes_size = 16
ciphertext = ciphertext[1 + ephemeral_size :]

# Verify the ciphertext length
if len(ciphertext) < sha1_size + aes_size:
raise ValueError("Invalid ciphertext")

# Derive the public key
eph_pub = ec.EllipticCurvePublicKey.from_encoded_point(
ec.SECP256R1(), ephemeral_public_key
)

# Perform the ECDH key exchange
shared_key = private_key.exchange(ec.ECDH(), eph_pub)

# Derive the shared key
shared = hashlib.sha256(shared_key).digest()

# Verify the MAC
tagStart = len(ciphertext) - sha1_size
h = hmac.new(shared[16:], digestmod=hashlib.sha1)
h.update(ciphertext[:tagStart])
mac = h.digest()

if not hmac.compare_digest(mac, ciphertext[tagStart:]):
raise ValueError("Invalid MAC")

# Decrypt the ciphertext using AES-GCM
decryptor = Cipher(
algorithms.AES(shared[:16]),
modes.CBC(
ciphertext[:aes_size],
),
backend=default_backend(),
).decryptor()

plaintext = decryptor.update(ciphertext[aes_size:tagStart]) + decryptor.finalize()

# Remove padding
plaintext = plaintext[: -plaintext[-1]]
return plaintext.decode()
89 changes: 89 additions & 0 deletions fortress_sdk_python/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
from typing import Any


class CursorInterface:
def __init__(self, cursor):
self.__cursor: "CursorInterface" = NotImplementedError
self.arraysize: int = NotImplementedError

@property
def description(self) -> tuple[tuple[Any, ...], ...] | None:
raise NotImplementedError

@property
def rowcount(self) -> int:
raise NotImplementedError

@property
def lastrowid(self) -> int | None:
raise NotImplementedError

def close(self) -> None:
raise NotImplementedError

def execute(self, sql: str, parameters: tuple[Any] = ...) -> "CursorInterface":
raise NotImplementedError

def executemany(
self, sql: str, parameters: list[tuple[Any]] = ...
) -> "CursorInterface":
raise NotImplementedError

def fetchone(self) -> tuple[Any] | None:
raise NotImplementedError

def fetchmany(self, size: int = ...) -> list[tuple[Any, ...]]:
raise NotImplementedError

def fetchall(self) -> list[tuple[Any]]:
raise NotImplementedError


class ConnectionInterface:
def __init__(self, connection):
self.__connection: "ConnectionInterface" = ...
self.in_transaction: bool = ...

def commit(self) -> None:
raise NotImplementedError

def cursor(self) -> CursorInterface:
raise NotImplementedError

def sync(self) -> None:
raise NotImplementedError

def rollback(self) -> None:
raise NotImplementedError

def execute(self, sql: str, parameters: tuple[Any] = ...) -> CursorInterface:
raise NotImplementedError

def executemany(
self, sql: str, parameters: list[tuple[Any]] = ...
) -> CursorInterface:
raise NotImplementedError

def executescript(self, script: str) -> None:
raise NotImplementedError

def close(self):
raise NotImplementedError


class DatabaseClient:
def __init__(self) -> None:
"""Initialize the Database client"""
raise NotImplementedError

def connect(self) -> ConnectionInterface:
"""Connect return an active connection to the database"""
raise NotImplementedError

def close(self) -> None:
"""Close the connection to the database"""
raise NotImplementedError

def __del__(self):
"""Close the connection to the database"""
self.close()
Loading

0 comments on commit 62be512

Please sign in to comment.