dworshak-access 0.1.9__tar.gz → 0.1.13__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dworshak-access
3
- Version: 0.1.9
3
+ Version: 0.1.13
4
4
  Summary: **dworshak-access** is a light-weight library for local credential access. It exposes the **get_secret()** function, to allow a program to leverage credentials that have been established using the Drowshak CLI tool, which is a separate package.
5
5
  Requires-Python: >=3.9
6
6
  Description-Content-Type: text/markdown
@@ -11,9 +11,14 @@ Dynamic: license-file
11
11
  **dworshak-access** is a light-weight library for local credential access. By adding **dworshak-access** as a dependency to your Python project, you enable your program or script to leverage credentials that have been established using the Drowshak CLI tool, which is a separate package.
12
12
 
13
13
  ## Functions exposed in **dworshak-access**:
14
- - check_vault() # For troubleshooting automated testing.
15
- - get_secret() # The meat and potatoes.
14
+ - `initialize_vault()` Create the vault directory, encryption key, and SQLite database. Safe to call multiple times.
15
+ - `check_vault()` Check the health of the vault (directory, key, DB).
16
+ - `store_secret(service: str, item: str, plaintext: str)` – Encrypt and store a credential in the vault.
17
+ - `get_secret(service: str, item: str) -> str` – Retrieve and decrypt a credential.
18
+ - `list_credentials() -> list[tuple[str, str]]` – List all stored service/item pairs.
16
19
 
20
+ All secrets are stored Fernet-encrypted in the database under the secret column.
21
+ No opaque blobs — every entry is meaningful and decryptable via the library.
17
22
 
18
23
  ### Example
19
24
 
@@ -22,17 +27,22 @@ uv add dworshak-access
22
27
  ```
23
28
 
24
29
  ```python
25
- from dworshak_access import get_secret
30
+ from dworshak_access import initialize_vault, store_secret, get_secret, list_credentials
26
31
 
27
- service_name = "MyThirdFavoriteAPI"
28
- item_id_u = "username"
29
- item_id_p = "password"
32
+ # Initialize the vault (create key and DB if missing)
33
+ initialize_vault()
30
34
 
31
- un = get_secret(service_name,item_id_u)
32
- pw = get_secret(service_name,item_id_p)
35
+ # Store credentials
36
+ store_secret("rjn_api", "username", "admin")
37
+ store_secret("rjn_api", "password", "s3cr3t")
33
38
 
34
- # Then use these in your program
39
+ # Retrieve credentials
40
+ username = get_secret("rjn_api", "username")
41
+ password = get_secret("rjn_api", "password")
35
42
 
43
+ # List stored items
44
+ for service, item in list_credentials():
45
+ print(f"{service}/{item}")
36
46
  ```
37
47
 
38
48
  ---
@@ -1,9 +1,14 @@
1
1
  **dworshak-access** is a light-weight library for local credential access. By adding **dworshak-access** as a dependency to your Python project, you enable your program or script to leverage credentials that have been established using the Drowshak CLI tool, which is a separate package.
2
2
 
3
3
  ## Functions exposed in **dworshak-access**:
4
- - check_vault() # For troubleshooting automated testing.
5
- - get_secret() # The meat and potatoes.
4
+ - `initialize_vault()` Create the vault directory, encryption key, and SQLite database. Safe to call multiple times.
5
+ - `check_vault()` Check the health of the vault (directory, key, DB).
6
+ - `store_secret(service: str, item: str, plaintext: str)` – Encrypt and store a credential in the vault.
7
+ - `get_secret(service: str, item: str) -> str` – Retrieve and decrypt a credential.
8
+ - `list_credentials() -> list[tuple[str, str]]` – List all stored service/item pairs.
6
9
 
10
+ All secrets are stored Fernet-encrypted in the database under the secret column.
11
+ No opaque blobs — every entry is meaningful and decryptable via the library.
7
12
 
8
13
  ### Example
9
14
 
@@ -12,17 +17,22 @@ uv add dworshak-access
12
17
  ```
13
18
 
14
19
  ```python
15
- from dworshak_access import get_secret
20
+ from dworshak_access import initialize_vault, store_secret, get_secret, list_credentials
16
21
 
17
- service_name = "MyThirdFavoriteAPI"
18
- item_id_u = "username"
19
- item_id_p = "password"
22
+ # Initialize the vault (create key and DB if missing)
23
+ initialize_vault()
20
24
 
21
- un = get_secret(service_name,item_id_u)
22
- pw = get_secret(service_name,item_id_p)
25
+ # Store credentials
26
+ store_secret("rjn_api", "username", "admin")
27
+ store_secret("rjn_api", "password", "s3cr3t")
23
28
 
24
- # Then use these in your program
29
+ # Retrieve credentials
30
+ username = get_secret("rjn_api", "username")
31
+ password = get_secret("rjn_api", "password")
25
32
 
33
+ # List stored items
34
+ for service, item in list_credentials():
35
+ print(f"{service}/{item}")
26
36
  ```
27
37
 
28
38
  ---
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "dworshak-access"
3
- version = "0.1.9"
3
+ version = "0.1.13"
4
4
  description = "**dworshak-access** is a light-weight library for local credential access. It exposes the **get_secret()** function, to allow a program to leverage credentials that have been established using the Drowshak CLI tool, which is a separate package."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9"
@@ -18,5 +18,10 @@ resolution = "highest"
18
18
  requires = ["setuptools>=64", "wheel"]
19
19
  build-backend = "setuptools.build_meta"
20
20
 
21
+ [dependency-groups]
22
+ dev = [
23
+ "pytest>=8.4.2",
24
+ ]
25
+
21
26
  [tool.setuptools.packages.find]
22
27
  where = ["src"]
@@ -0,0 +1,18 @@
1
+ # src/dowrshak_access/__init__.py
2
+ from __future__ import annotations
3
+
4
+ from .vault import (
5
+ initialize_vault,
6
+ check_vault,
7
+ store_secret,
8
+ get_secret,
9
+ list_credentials,
10
+ )
11
+
12
+ __all__ = [
13
+ "initialize_vault",
14
+ "check_vault",
15
+ "store_secret",
16
+ "get_secret",
17
+ "list_credentials",
18
+ ]
@@ -0,0 +1,8 @@
1
+ # src/dowrshak_access/paths.py
2
+ from __future__ import annotations
3
+ from pathlib import Path
4
+
5
+ APP_DIR = Path.home() / ".dworshak"
6
+ DB_FILE = APP_DIR / "vault.db"
7
+ KEY_FILE = APP_DIR / ".key"
8
+ CONFIG_FILE = APP_DIR / "config.json"
@@ -0,0 +1,20 @@
1
+ # src/dowrshak_access/security.py
2
+ from __future__ import annotations
3
+ from cryptography.fernet import Fernet
4
+ from .paths import KEY_FILE
5
+ import os
6
+
7
+ def get_fernet() -> Fernet:
8
+ """
9
+ Returns a Fernet instance using the master key.
10
+ Generates key if missing.
11
+ """
12
+ if not KEY_FILE.exists():
13
+ APP_DIR = KEY_FILE.parent
14
+ APP_DIR.mkdir(parents=True, exist_ok=True)
15
+ key = Fernet.generate_key()
16
+ KEY_FILE.write_bytes(key)
17
+ os.chmod(KEY_FILE, 0o600)
18
+
19
+ key_bytes = KEY_FILE.read_bytes()
20
+ return Fernet(key_bytes)
@@ -0,0 +1,76 @@
1
+ # src/dowrshak_access/vault.py
2
+ from __future__ import annotations
3
+ import sqlite3
4
+ from pathlib import Path
5
+ from typing import NamedTuple, List
6
+ from .paths import DB_FILE, APP_DIR
7
+ from .security import get_fernet
8
+
9
+ class VaultStatus(NamedTuple):
10
+ is_valid: bool
11
+ message: str
12
+ root_path: Path
13
+
14
+ def initialize_vault() -> None:
15
+ """Create vault DB with encrypted secret column."""
16
+ APP_DIR.mkdir(parents=True, exist_ok=True)
17
+ conn = sqlite3.connect(DB_FILE)
18
+ conn.execute("""
19
+ CREATE TABLE IF NOT EXISTS credentials (
20
+ service TEXT NOT NULL,
21
+ item TEXT NOT NULL,
22
+ secret BLOB NOT NULL,
23
+ PRIMARY KEY(service, item)
24
+ )
25
+ """)
26
+ conn.execute("""
27
+ CREATE TABLE IF NOT EXISTS schema_version (
28
+ version INTEGER NOT NULL
29
+ )
30
+ """)
31
+ # Set version 1 if empty
32
+ cursor = conn.execute("SELECT COUNT(*) FROM schema_version")
33
+ if cursor.fetchone()[0] == 0:
34
+ conn.execute("INSERT INTO schema_version (version) VALUES (1)")
35
+ conn.commit()
36
+ conn.close()
37
+
38
+ def check_vault() -> VaultStatus:
39
+ if not APP_DIR.exists():
40
+ return VaultStatus(False, "Vault directory missing", APP_DIR)
41
+ if not DB_FILE.exists():
42
+ return VaultStatus(False, "Vault DB missing", APP_DIR)
43
+ if not get_fernet():
44
+ return VaultStatus(False, "Encryption key missing", APP_DIR)
45
+ return VaultStatus(True, "Vault healthy", APP_DIR)
46
+
47
+ def store_secret(service: str, item: str, plaintext: str):
48
+ f = get_fernet()
49
+ encrypted_secret = f.encrypt(plaintext.encode())
50
+ conn = sqlite3.connect(DB_FILE)
51
+ conn.execute(
52
+ "INSERT OR REPLACE INTO credentials (service, item, secret) VALUES (?, ?, ?)",
53
+ (service, item, encrypted_secret)
54
+ )
55
+ conn.commit()
56
+ conn.close()
57
+
58
+ def get_secret(service: str, item: str) -> str:
59
+ f = get_fernet()
60
+ conn = sqlite3.connect(DB_FILE)
61
+ cursor = conn.execute(
62
+ "SELECT secret FROM credentials WHERE service = ? AND item = ?",
63
+ (service, item)
64
+ )
65
+ row = cursor.fetchone()
66
+ conn.close()
67
+ if not row:
68
+ raise KeyError(f"No credential found for {service}/{item}")
69
+ return f.decrypt(row[0]).decode()
70
+
71
+ def list_credentials() -> List[tuple[str, str]]:
72
+ conn = sqlite3.connect(DB_FILE)
73
+ cursor = conn.execute("SELECT service, item FROM credentials")
74
+ rows = cursor.fetchall()
75
+ conn.close()
76
+ return rows
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dworshak-access
3
- Version: 0.1.9
3
+ Version: 0.1.13
4
4
  Summary: **dworshak-access** is a light-weight library for local credential access. It exposes the **get_secret()** function, to allow a program to leverage credentials that have been established using the Drowshak CLI tool, which is a separate package.
5
5
  Requires-Python: >=3.9
6
6
  Description-Content-Type: text/markdown
@@ -11,9 +11,14 @@ Dynamic: license-file
11
11
  **dworshak-access** is a light-weight library for local credential access. By adding **dworshak-access** as a dependency to your Python project, you enable your program or script to leverage credentials that have been established using the Drowshak CLI tool, which is a separate package.
12
12
 
13
13
  ## Functions exposed in **dworshak-access**:
14
- - check_vault() # For troubleshooting automated testing.
15
- - get_secret() # The meat and potatoes.
14
+ - `initialize_vault()` Create the vault directory, encryption key, and SQLite database. Safe to call multiple times.
15
+ - `check_vault()` Check the health of the vault (directory, key, DB).
16
+ - `store_secret(service: str, item: str, plaintext: str)` – Encrypt and store a credential in the vault.
17
+ - `get_secret(service: str, item: str) -> str` – Retrieve and decrypt a credential.
18
+ - `list_credentials() -> list[tuple[str, str]]` – List all stored service/item pairs.
16
19
 
20
+ All secrets are stored Fernet-encrypted in the database under the secret column.
21
+ No opaque blobs — every entry is meaningful and decryptable via the library.
17
22
 
18
23
  ### Example
19
24
 
@@ -22,17 +27,22 @@ uv add dworshak-access
22
27
  ```
23
28
 
24
29
  ```python
25
- from dworshak_access import get_secret
30
+ from dworshak_access import initialize_vault, store_secret, get_secret, list_credentials
26
31
 
27
- service_name = "MyThirdFavoriteAPI"
28
- item_id_u = "username"
29
- item_id_p = "password"
32
+ # Initialize the vault (create key and DB if missing)
33
+ initialize_vault()
30
34
 
31
- un = get_secret(service_name,item_id_u)
32
- pw = get_secret(service_name,item_id_p)
35
+ # Store credentials
36
+ store_secret("rjn_api", "username", "admin")
37
+ store_secret("rjn_api", "password", "s3cr3t")
33
38
 
34
- # Then use these in your program
39
+ # Retrieve credentials
40
+ username = get_secret("rjn_api", "username")
41
+ password = get_secret("rjn_api", "password")
35
42
 
43
+ # List stored items
44
+ for service, item in list_credentials():
45
+ print(f"{service}/{item}")
36
46
  ```
37
47
 
38
48
  ---
@@ -2,9 +2,13 @@ LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
4
  src/dworshak_access/__init__.py
5
+ src/dworshak_access/paths.py
6
+ src/dworshak_access/security.py
5
7
  src/dworshak_access/vault.py
6
8
  src/dworshak_access.egg-info/PKG-INFO
7
9
  src/dworshak_access.egg-info/SOURCES.txt
8
10
  src/dworshak_access.egg-info/dependency_links.txt
9
11
  src/dworshak_access.egg-info/requires.txt
10
- src/dworshak_access.egg-info/top_level.txt
12
+ src/dworshak_access.egg-info/top_level.txt
13
+ tests/test_check_vault.py
14
+ tests/test_get_secret.py
@@ -0,0 +1,18 @@
1
+ from __future__ import annotations
2
+ from pathlib import Path
3
+ from dworshak_access.vault import check_vault
4
+
5
+ def test_check_vault_reports_missing_dir(tmp_path):
6
+ """
7
+ If the folder doesn't exist, check_vault should
8
+ return a valid status object indicating it's missing.
9
+ """
10
+ # Point to a directory we know doesn't exist
11
+ fake_path = tmp_path / "non_existent_vault"
12
+
13
+ status = check_vault(root=fake_path)
14
+
15
+ # We don't want a crash/exception; we want a 'False' status
16
+ assert status.is_valid is False
17
+ assert "does not exist" in status.message
18
+ assert status.root_path == fake_path
@@ -0,0 +1,27 @@
1
+ from __future__ import annotations
2
+ import pytest
3
+ from unittest.mock import patch, MagicMock, mock_open
4
+ from dworshak_access.vault import get_secret
5
+
6
+ @patch("dworshak_access.vault.Fernet")
7
+ @patch("sqlite3.connect")
8
+ @patch("pathlib.Path.exists", return_value=True)
9
+ @patch("pathlib.Path.read_bytes", return_value=b"fake-key-bytes")
10
+ def test_get_secret_logic(mock_read, mock_exists, mock_connect, mock_fernet):
11
+ # 1. Setup the Mock Database Response
12
+ mock_conn = MagicMock()
13
+ mock_cursor = MagicMock()
14
+ # Simulate the DB returning an encrypted blob
15
+ mock_cursor.fetchone.return_value = (b"encrypted-blob",)
16
+ mock_conn.execute.return_value = mock_cursor
17
+ mock_connect.return_value.__enter__.return_value = mock_conn
18
+
19
+ # 2. Setup the Mock Decryption
20
+ mock_fernet_instance = mock_fernet.return_value
21
+ mock_fernet_instance.decrypt.return_value = b"decrypted-password"
22
+
23
+ # 3. Execution
24
+ result = get_secret("service", "item")
25
+
26
+ # 4. Assertion
27
+ assert result == "decrypted-password"
@@ -1,9 +0,0 @@
1
- # src/dowrshak_access/__init__.py
2
- from __future__ import annotations
3
- from dworshak_access.vault import get_secret, check_vault
4
-
5
- __all__ = [
6
- "get_secret",
7
- "check_vault"
8
- ]
9
-
@@ -1,53 +0,0 @@
1
- # src/dworshak-access/vault.py
2
- from __future__ import annotations
3
- import sqlite3
4
- import os
5
- from pathlib import Path
6
- from typing import NamedTuple, List, Tuple, Optional
7
- from cryptography.fernet import Fernet
8
-
9
- DEFAULT_ROOT = Path.home() / ".dworshak"
10
-
11
- class VaultStatus(NamedTuple):
12
- is_valid: bool
13
- message: str
14
- root_path: Path
15
-
16
- def check_vault(root: Path = DEFAULT_ROOT) -> VaultStatus:
17
- """Diagnose the health of the Dworshak environment."""
18
- if not root.exists():
19
- return VaultStatus(False, "Vault directory does not exist.", root)
20
- if not (root / ".key").exists():
21
- return VaultStatus(False, "Security key (.key) is missing.", root)
22
- if not (root / "vault.db").exists():
23
- return VaultStatus(False, "Credential database (vault.db) is missing.", root)
24
-
25
- try:
26
- with sqlite3.connect(root / "vault.db") as conn:
27
- conn.execute("SELECT 1 FROM credentials LIMIT 1")
28
- except sqlite3.Error as e:
29
- return VaultStatus(False, f"Database error: {e}", root)
30
-
31
- return VaultStatus(True, "Vault is healthy.", root)
32
-
33
- def get_secret(service: str, item: str, root: Path = DEFAULT_ROOT) -> str:
34
- """Retrieve and decrypt a specific secret."""
35
- key_path = root / ".key"
36
- db_path = root / "vault.db"
37
-
38
- if not key_path.exists():
39
- raise FileNotFoundError(f"Dworshak key not found at {key_path}")
40
-
41
- fernet = Fernet(key_path.read_bytes())
42
-
43
- with sqlite3.connect(db_path) as conn:
44
- cursor = conn.execute(
45
- "SELECT secret FROM credentials WHERE service = ? AND item = ?",
46
- (service, item)
47
- )
48
- row = cursor.fetchone()
49
-
50
- if not row:
51
- raise KeyError(f"No credential found for {service}/{item}")
52
-
53
- return fernet.decrypt(row[0]).decode()