-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 62be512
Showing
11 changed files
with
883 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
**/__pycache__/ | ||
**/.DS_Store |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .client import Client |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
Oops, something went wrong.