From fa0c20f283c92a1e3da218fff6d1c732a2ea7adc Mon Sep 17 00:00:00 2001 From: Daniele Briggi Date: Wed, 21 May 2025 16:08:51 +0000 Subject: [PATCH 1/2] feat(tokens): add support - fix(bindings): executemany and named-style bindings were sent to Core multiple times instead of only one per named binding - chore(precommit): upgrade to support py3.9 and above, the latest supported python version --- .../{py3.6-dev => py3.6-testing}/Dockerfile | 0 .../devcontainer.json | 2 +- .../install-legacy-extensions.sh | 0 .../{py3.7 => py3.9-dev}/devcontainer.json | 7 +- .env.example | 1 + .github/workflows/test.yaml | 2 +- .pre-commit-config.yaml | 13 +- bandit-baseline.json | 713 +----------------- bandit.yaml | 1 + requirements-dev.txt | 2 +- src/sqlitecloud/datatypes.py | 29 +- src/sqlitecloud/dbapi2.py | 5 +- src/sqlitecloud/driver.py | 5 +- src/tests/integration/test_client.py | 23 +- src/tests/integration/test_driver.py | 26 + src/tests/integration/test_sqlite_parity.py | 25 +- src/tests/unit/test_client.py | 2 +- src/tests/unit/test_driver.py | 18 + src/tests/unit/test_types.py | 19 + 19 files changed, 161 insertions(+), 732 deletions(-) rename .devcontainer/{py3.6-dev => py3.6-testing}/Dockerfile (100%) rename .devcontainer/{py3.6-dev => py3.6-testing}/devcontainer.json (97%) rename .devcontainer/{py3.6-dev => py3.6-testing}/install-legacy-extensions.sh (100%) rename .devcontainer/{py3.7 => py3.9-dev}/devcontainer.json (89%) diff --git a/.devcontainer/py3.6-dev/Dockerfile b/.devcontainer/py3.6-testing/Dockerfile similarity index 100% rename from .devcontainer/py3.6-dev/Dockerfile rename to .devcontainer/py3.6-testing/Dockerfile diff --git a/.devcontainer/py3.6-dev/devcontainer.json b/.devcontainer/py3.6-testing/devcontainer.json similarity index 97% rename from .devcontainer/py3.6-dev/devcontainer.json rename to .devcontainer/py3.6-testing/devcontainer.json index b730212..3507265 100644 --- a/.devcontainer/py3.6-dev/devcontainer.json +++ b/.devcontainer/py3.6-testing/devcontainer.json @@ -1,7 +1,7 @@ // For format details, see https://aka.ms/devcontainer.json. For config options, see the // README at: https://github.com/devcontainers/templates/tree/main/src/python { - "name": "Python 3.6 For development", + "name": "Python 3.6 For Legacy Testing", // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile "build": { "dockerfile": "Dockerfile" diff --git a/.devcontainer/py3.6-dev/install-legacy-extensions.sh b/.devcontainer/py3.6-testing/install-legacy-extensions.sh similarity index 100% rename from .devcontainer/py3.6-dev/install-legacy-extensions.sh rename to .devcontainer/py3.6-testing/install-legacy-extensions.sh diff --git a/.devcontainer/py3.7/devcontainer.json b/.devcontainer/py3.9-dev/devcontainer.json similarity index 89% rename from .devcontainer/py3.7/devcontainer.json rename to .devcontainer/py3.9-dev/devcontainer.json index 7875428..6427a8c 100644 --- a/.devcontainer/py3.7/devcontainer.json +++ b/.devcontainer/py3.9-dev/devcontainer.json @@ -1,9 +1,9 @@ // For format details, see https://aka.ms/devcontainer.json. For config options, see the // README at: https://github.com/devcontainers/templates/tree/main/src/python { - "name": "Python 3.7", + "name": "Python 3.9 for development", // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile - "image": "mcr.microsoft.com/devcontainers/python:3.7", + "image": "mcr.microsoft.com/devcontainers/python:3.9", "features": { "ghcr.io/warrenbuckley/codespace-features/sqlite:1": {} }, @@ -28,7 +28,8 @@ "ms-python.debugpy", "ms-python.black-formatter", "ms-python.isort", - "ms-toolsai.jupyter" + "ms-toolsai.jupyter", + "eamodio.gitlens" ] } } diff --git a/.env.example b/.env.example index d75af5e..4c9d19d 100644 --- a/.env.example +++ b/.env.example @@ -2,6 +2,7 @@ SQLITE_CONNECTION_STRING=sqlitecloud://myhost.sqlite.cloud SQLITE_USER=admin SQLITE_PASSWORD= SQLITE_API_KEY= +SQLITE_ACCESS_TOKEN= SQLITE_HOST=myhost.sqlite.cloud SQLITE_DB=chinook.sqlite SQLITE_PORT=8860 diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index a980240..18c10ad 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -14,7 +14,7 @@ jobs: fail-fast: false matrix: # last supported for sqlitecloud, last security maintained, last release - python-version: ["3.9", "3.10", "3.12"] + python-version: ["3.9", "3.10", "3.13"] steps: - uses: actions/checkout@v4 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fec2bd2..c6b7b4e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ # repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.2.0 + rev: v5.0.0 hooks: - id: trailing-whitespace exclude: '\.md$' @@ -12,21 +12,21 @@ repos: - id: check-merge-conflict # Using this mirror lets us use mypyc-compiled black, which is about 2x faster - repo: https://github.com/pycqa/isort - rev: 5.10.1 + rev: 6.0.1 hooks: - id: isort name: isort - repo: https://github.com/psf/black-pre-commit-mirror - rev: 22.8.0 + rev: 25.1.0 hooks: - id: black # It is recommended to specify the latest version of Python # supported by your project here, or alternatively use # pre-commit's default_language_version, see # https://pre-commit.com/#top_level-default_language_version - language_version: python3.6 + language_version: python3.9 - repo: https://github.com/PyCQA/autoflake - rev: v1.4 + rev: v2.3.1 hooks: - id: autoflake name: autoflake @@ -37,11 +37,10 @@ repos: - "--remove-unused-variables" - "--remove-all-unused-imports" - repo: https://github.com/pycqa/flake8 - rev: 5.0.4 + rev: 7.2.0 hooks: - id: flake8 name: flake8 - # stages: [push] - repo: https://github.com/PyCQA/bandit rev: 1.7.1 hooks: diff --git a/bandit-baseline.json b/bandit-baseline.json index 418161f..39c4dc9 100644 --- a/bandit-baseline.json +++ b/bandit-baseline.json @@ -1,17 +1,17 @@ { "errors": [], - "generated_at": "2024-08-26T09:15:36Z", + "generated_at": "2025-05-21T16:06:08Z", "metrics": { "_totals": { - "CONFIDENCE.HIGH": 17.0, - "CONFIDENCE.LOW": 4.0, - "CONFIDENCE.MEDIUM": 12.0, + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 1.0, "CONFIDENCE.UNDEFINED": 0.0, "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 18.0, - "SEVERITY.MEDIUM": 15.0, + "SEVERITY.LOW": 1.0, + "SEVERITY.MEDIUM": 0.0, "SEVERITY.UNDEFINED": 0.0, - "loc": 5145, + "loc": 2294, "nosec": 0 }, "src/setup.py": { @@ -35,7 +35,7 @@ "SEVERITY.LOW": 0.0, "SEVERITY.MEDIUM": 0.0, "SEVERITY.UNDEFINED": 0.0, - "loc": 25, + "loc": 2, "nosec": 0 }, "src/sqlitecloud/client.py": { @@ -59,7 +59,7 @@ "SEVERITY.LOW": 1.0, "SEVERITY.MEDIUM": 0.0, "SEVERITY.UNDEFINED": 0.0, - "loc": 173, + "loc": 178, "nosec": 0 }, "src/sqlitecloud/dbapi2.py": { @@ -71,7 +71,7 @@ "SEVERITY.LOW": 0.0, "SEVERITY.MEDIUM": 0.0, "SEVERITY.UNDEFINED": 0.0, - "loc": 750, + "loc": 752, "nosec": 0 }, "src/sqlitecloud/download.py": { @@ -95,7 +95,7 @@ "SEVERITY.LOW": 0.0, "SEVERITY.MEDIUM": 0.0, "SEVERITY.UNDEFINED": 0.0, - "loc": 834, + "loc": 838, "nosec": 0 }, "src/sqlitecloud/exceptions.py": { @@ -145,215 +145,11 @@ "SEVERITY.UNDEFINED": 0.0, "loc": 51, "nosec": 0 - }, - "src/tests/__init__.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 0, - "nosec": 0 - }, - "src/tests/conftest.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 76, - "nosec": 0 - }, - "src/tests/integration/__init__.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 0, - "nosec": 0 - }, - "src/tests/integration/test_client.py": { - "CONFIDENCE.HIGH": 1.0, - "CONFIDENCE.LOW": 1.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 1.0, - "SEVERITY.MEDIUM": 1.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 552, - "nosec": 0 - }, - "src/tests/integration/test_dbapi2.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 319, - "nosec": 0 - }, - "src/tests/integration/test_download.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 20, - "nosec": 0 - }, - "src/tests/integration/test_driver.py": { - "CONFIDENCE.HIGH": 4.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 4.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 86, - "nosec": 0 - }, - "src/tests/integration/test_pandas.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 51, - "nosec": 0 - }, - "src/tests/integration/test_pubsub.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 1.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 1.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 128, - "nosec": 0 - }, - "src/tests/integration/test_sqlalchemy.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 55, - "nosec": 0 - }, - "src/tests/integration/test_sqlite_parity.py": { - "CONFIDENCE.HIGH": 12.0, - "CONFIDENCE.LOW": 1.0, - "CONFIDENCE.MEDIUM": 11.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 12.0, - "SEVERITY.MEDIUM": 12.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 1011, - "nosec": 0 - }, - "src/tests/integration/test_upload.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 1.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 1.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 20, - "nosec": 0 - }, - "src/tests/unit/test_client.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 48, - "nosec": 0 - }, - "src/tests/unit/test_dbapi2.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 226, - "nosec": 0 - }, - "src/tests/unit/test_driver.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 134, - "nosec": 0 - }, - "src/tests/unit/test_resultset.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 99, - "nosec": 0 - }, - "src/tests/unit/test_types.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 14, - "nosec": 0 } }, "results": [ { - "code": "93 class SQLiteCloudAccount:\n94 def __init__(\n95 self,\n96 username: Optional[str] = \"\",\n97 password: Optional[str] = \"\",\n98 hostname: str = \"\",\n99 dbname: Optional[str] = \"\",\n100 port: int = SQLITECLOUD_DEFAULT.PORT.value,\n101 apikey: Optional[str] = \"\",\n102 ) -> None:\n103 # User name is required unless connectionstring is provided\n104 self.username = username\n105 # Password is required unless connection string is provided\n106 self.password = password\n107 # Password is hashed\n108 self.password_hashed = False\n109 # API key instead of username and password\n110 self.apikey = apikey\n111 # Name of database to open\n112 self.dbname = dbname\n113 # Like mynode.sqlitecloud.io\n114 self.hostname = hostname\n115 self.port = port\n116 \n", + "code": "93 class SQLiteCloudAccount:\n94 def __init__(\n95 self,\n96 username: Optional[str] = \"\",\n97 password: Optional[str] = \"\",\n98 hostname: str = \"\",\n99 dbname: Optional[str] = \"\",\n100 port: int = SQLITECLOUD_DEFAULT.PORT.value,\n101 apikey: Optional[str] = \"\",\n102 token: Optional[str] = \"\",\n103 ) -> None:\n104 # User name is required unless connectionstring is provided\n105 self.username = username\n106 # Password is required unless connection string is provided\n107 self.password = password\n108 # Password is hashed\n109 self.password_hashed = False\n110 # API key\n111 self.apikey = apikey\n112 # Access Token\n113 self.token = token\n114 # Name of database to open\n115 self.dbname = dbname\n116 # Like mynode.sqlitecloud.io\n117 self.hostname = hostname\n118 self.port = port\n119 \n", "col_offset": 4, "filename": "src/sqlitecloud/datatypes.py", "issue_confidence": "MEDIUM", @@ -382,491 +178,14 @@ 112, 113, 114, - 115 + 115, + 116, + 117, + 118 ], "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b107_hardcoded_password_default.html", "test_id": "B107", "test_name": "hardcoded_password_default" - }, - { - "code": "648 \n649 table_name = \"TestCompress\" + str(random.randint(0, 99999))\n650 try:\n", - "col_offset": 42, - "filename": "src/tests/integration/test_client.py", - "issue_confidence": "HIGH", - "issue_severity": "LOW", - "issue_text": "Standard pseudo-random generators are not suitable for security/cryptographic purposes.", - "line_number": 649, - "line_range": [ - 649 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/blacklists/blacklist_calls.html#b311-random", - "test_id": "B311", - "test_name": "blacklist" - }, - { - "code": "664 rowset = client.exec_query(\n665 f\"SELECT * from {table_name}\",\n666 connection,\n", - "col_offset": 16, - "filename": "src/tests/integration/test_client.py", - "issue_confidence": "LOW", - "issue_severity": "MEDIUM", - "issue_text": "Possible SQL injection vector through string-based query construction.", - "line_number": 665, - "line_range": [ - 665 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b608_hardcoded_sql_expressions.html", - "test_id": "B608", - "test_name": "hardcoded_sql_expressions" - }, - { - "code": "50 \n51 name = \"MyGenre\" + str(random.randint(0, 1000))\n52 query = \"INSERT INTO genres (Name) VALUES (?)\"\n", - "col_offset": 31, - "filename": "src/tests/integration/test_driver.py", - "issue_confidence": "HIGH", - "issue_severity": "LOW", - "issue_text": "Standard pseudo-random generators are not suitable for security/cryptographic purposes.", - "line_number": 51, - "line_range": [ - 51 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/blacklists/blacklist_calls.html#b311-random", - "test_id": "B311", - "test_name": "blacklist" - }, - { - "code": "73 \n74 name = \"MyGenre\" + str(random.randint(0, 1000))\n75 result_insert = driver.execute_statement(\n", - "col_offset": 31, - "filename": "src/tests/integration/test_driver.py", - "issue_confidence": "HIGH", - "issue_severity": "LOW", - "issue_text": "Standard pseudo-random generators are not suitable for security/cryptographic purposes.", - "line_number": 74, - "line_range": [ - 74 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/blacklists/blacklist_calls.html#b311-random", - "test_id": "B311", - "test_name": "blacklist" - }, - { - "code": "95 \n96 name = \"MyGenre\" + str(random.randint(0, 1000))\n97 result_insert = driver.execute_statement(\n", - "col_offset": 31, - "filename": "src/tests/integration/test_driver.py", - "issue_confidence": "HIGH", - "issue_severity": "LOW", - "issue_text": "Standard pseudo-random generators are not suitable for security/cryptographic purposes.", - "line_number": 96, - "line_range": [ - 96 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/blacklists/blacklist_calls.html#b311-random", - "test_id": "B311", - "test_name": "blacklist" - }, - { - "code": "100 \n101 new_name = \"AnotherMyGenre\" + str(random.randint(0, 1000))\n102 result = driver.execute_statement(\n", - "col_offset": 42, - "filename": "src/tests/integration/test_driver.py", - "issue_confidence": "HIGH", - "issue_severity": "LOW", - "issue_text": "Standard pseudo-random generators are not suitable for security/cryptographic purposes.", - "line_number": 101, - "line_range": [ - 101 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/blacklists/blacklist_calls.html#b311-random", - "test_id": "B311", - "test_name": "blacklist" - }, - { - "code": "177 client.exec_query(\n178 f\"UPDATE genres SET Name = '{new_name}' WHERE GenreId = 1;\", connection\n179 )\n", - "col_offset": 12, - "filename": "src/tests/integration/test_pubsub.py", - "issue_confidence": "LOW", - "issue_severity": "MEDIUM", - "issue_text": "Possible SQL injection vector through string-based query construction.", - "line_number": 178, - "line_range": [ - 178 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b608_hardcoded_sql_expressions.html", - "test_id": "B608", - "test_name": "hardcoded_sql_expressions" - }, - { - "code": "54 \n55 table = \"sqlitetest\" + str(random.randint(0, 99999))\n56 try:\n", - "col_offset": 35, - "filename": "src/tests/integration/test_sqlite_parity.py", - "issue_confidence": "HIGH", - "issue_severity": "LOW", - "issue_text": "Standard pseudo-random generators are not suitable for security/cryptographic purposes.", - "line_number": 55, - "line_range": [ - 55 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/blacklists/blacklist_calls.html#b311-random", - "test_id": "B311", - "test_name": "blacklist" - }, - { - "code": "63 \n64 select_query = f\"SELECT * FROM {table}\"\n65 cursor = connection.execute(select_query)\n", - "col_offset": 27, - "filename": "src/tests/integration/test_sqlite_parity.py", - "issue_confidence": "LOW", - "issue_severity": "MEDIUM", - "issue_text": "Possible SQL injection vector through string-based query construction.", - "line_number": 64, - "line_range": [ - 64 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b608_hardcoded_sql_expressions.html", - "test_id": "B608", - "test_name": "hardcoded_sql_expressions" - }, - { - "code": "619 \n620 tableName = \"TestTextFactory\" + str(random.randint(0, 99999))\n621 try:\n", - "col_offset": 44, - "filename": "src/tests/integration/test_sqlite_parity.py", - "issue_confidence": "HIGH", - "issue_severity": "LOW", - "issue_text": "Standard pseudo-random generators are not suitable for security/cryptographic purposes.", - "line_number": 620, - "line_range": [ - 620 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/blacklists/blacklist_calls.html#b311-random", - "test_id": "B311", - "test_name": "blacklist" - }, - { - "code": "624 cursor.execute(f\"INSERT INTO {tableName}(p) VALUES (?)\", (15,))\n625 cursor.execute(f\"SELECT p FROM {tableName}\")\n626 \n", - "col_offset": 27, - "filename": "src/tests/integration/test_sqlite_parity.py", - "issue_confidence": "MEDIUM", - "issue_severity": "MEDIUM", - "issue_text": "Possible SQL injection vector through string-based query construction.", - "line_number": 625, - "line_range": [ - 625 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b608_hardcoded_sql_expressions.html", - "test_id": "B608", - "test_name": "hardcoded_sql_expressions" - }, - { - "code": "648 \n649 tableName = \"TestTextFactory\" + str(random.randint(0, 99999))\n650 try:\n", - "col_offset": 44, - "filename": "src/tests/integration/test_sqlite_parity.py", - "issue_confidence": "HIGH", - "issue_severity": "LOW", - "issue_text": "Standard pseudo-random generators are not suitable for security/cryptographic purposes.", - "line_number": 649, - "line_range": [ - 649 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/blacklists/blacklist_calls.html#b311-random", - "test_id": "B311", - "test_name": "blacklist" - }, - { - "code": "653 cursor.execute(f\"INSERT INTO {tableName}(p) VALUES (?)\", (\"15\",))\n654 cursor.execute(f\"SELECT p FROM {tableName}\")\n655 \n", - "col_offset": 27, - "filename": "src/tests/integration/test_sqlite_parity.py", - "issue_confidence": "MEDIUM", - "issue_severity": "MEDIUM", - "issue_text": "Possible SQL injection vector through string-based query construction.", - "line_number": 654, - "line_range": [ - 654 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b608_hardcoded_sql_expressions.html", - "test_id": "B608", - "test_name": "hardcoded_sql_expressions" - }, - { - "code": "710 \n711 tableName = \"TestParseDeclTypes\" + str(random.randint(0, 99999))\n712 try:\n", - "col_offset": 47, - "filename": "src/tests/integration/test_sqlite_parity.py", - "issue_confidence": "HIGH", - "issue_severity": "LOW", - "issue_text": "Standard pseudo-random generators are not suitable for security/cryptographic purposes.", - "line_number": 711, - "line_range": [ - 711 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/blacklists/blacklist_calls.html#b311-random", - "test_id": "B311", - "test_name": "blacklist" - }, - { - "code": "715 cursor.executemany(f\"INSERT INTO {tableName}(p) VALUES (?)\", [(p1,), (p2,)])\n716 cursor.execute(f\"SELECT p FROM {tableName}\")\n717 \n", - "col_offset": 27, - "filename": "src/tests/integration/test_sqlite_parity.py", - "issue_confidence": "MEDIUM", - "issue_severity": "MEDIUM", - "issue_text": "Possible SQL injection vector through string-based query construction.", - "line_number": 716, - "line_range": [ - 716 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b608_hardcoded_sql_expressions.html", - "test_id": "B608", - "test_name": "hardcoded_sql_expressions" - }, - { - "code": "899 \n900 tableName = \"TestParseDeclTypes\" + str(random.randint(0, 99999))\n901 try:\n", - "col_offset": 47, - "filename": "src/tests/integration/test_sqlite_parity.py", - "issue_confidence": "HIGH", - "issue_severity": "LOW", - "issue_text": "Standard pseudo-random generators are not suitable for security/cryptographic purposes.", - "line_number": 900, - "line_range": [ - 900 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/blacklists/blacklist_calls.html#b311-random", - "test_id": "B311", - "test_name": "blacklist" - }, - { - "code": "904 cursor.execute(f\"INSERT INTO {tableName}(p) VALUES (?)\", (str(p),))\n905 cursor.execute(f\"SELECT p FROM {tableName}\")\n906 \n", - "col_offset": 27, - "filename": "src/tests/integration/test_sqlite_parity.py", - "issue_confidence": "MEDIUM", - "issue_severity": "MEDIUM", - "issue_text": "Possible SQL injection vector through string-based query construction.", - "line_number": 905, - "line_range": [ - 905 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b608_hardcoded_sql_expressions.html", - "test_id": "B608", - "test_name": "hardcoded_sql_expressions" - }, - { - "code": "924 \n925 tableName = \"TestParseDeclTypes\" + str(random.randint(0, 99999))\n926 try:\n", - "col_offset": 47, - "filename": "src/tests/integration/test_sqlite_parity.py", - "issue_confidence": "HIGH", - "issue_severity": "LOW", - "issue_text": "Standard pseudo-random generators are not suitable for security/cryptographic purposes.", - "line_number": 925, - "line_range": [ - 925 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/blacklists/blacklist_calls.html#b311-random", - "test_id": "B311", - "test_name": "blacklist" - }, - { - "code": "929 cursor.execute(f\"INSERT INTO {tableName}(p) VALUES (?)\", (\"1.0,2.0\",))\n930 cursor.execute(f\"SELECT p FROM {tableName}\")\n931 \n", - "col_offset": 27, - "filename": "src/tests/integration/test_sqlite_parity.py", - "issue_confidence": "MEDIUM", - "issue_severity": "MEDIUM", - "issue_text": "Possible SQL injection vector through string-based query construction.", - "line_number": 930, - "line_range": [ - 930 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b608_hardcoded_sql_expressions.html", - "test_id": "B608", - "test_name": "hardcoded_sql_expressions" - }, - { - "code": "952 \n953 tableName = \"TestParseDeclTypes\" + str(random.randint(0, 99999))\n954 try:\n", - "col_offset": 47, - "filename": "src/tests/integration/test_sqlite_parity.py", - "issue_confidence": "HIGH", - "issue_severity": "LOW", - "issue_text": "Standard pseudo-random generators are not suitable for security/cryptographic purposes.", - "line_number": 953, - "line_range": [ - 953 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/blacklists/blacklist_calls.html#b311-random", - "test_id": "B311", - "test_name": "blacklist" - }, - { - "code": "957 cursor.execute(f\"INSERT INTO {tableName}(p) VALUES (?)\", (mynumber,))\n958 cursor.execute(f\"SELECT p FROM {tableName}\")\n959 \n", - "col_offset": 27, - "filename": "src/tests/integration/test_sqlite_parity.py", - "issue_confidence": "MEDIUM", - "issue_severity": "MEDIUM", - "issue_text": "Possible SQL injection vector through string-based query construction.", - "line_number": 958, - "line_range": [ - 958 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b608_hardcoded_sql_expressions.html", - "test_id": "B608", - "test_name": "hardcoded_sql_expressions" - }, - { - "code": "985 \n986 tableName = \"TestParseDeclTypes\" + str(random.randint(0, 99999))\n987 try:\n", - "col_offset": 47, - "filename": "src/tests/integration/test_sqlite_parity.py", - "issue_confidence": "HIGH", - "issue_severity": "LOW", - "issue_text": "Standard pseudo-random generators are not suitable for security/cryptographic purposes.", - "line_number": 986, - "line_range": [ - 986 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/blacklists/blacklist_calls.html#b311-random", - "test_id": "B311", - "test_name": "blacklist" - }, - { - "code": "990 cursor.execute(f\"INSERT INTO {tableName}(p) VALUES (?)\", (pippo,))\n991 cursor.execute(f\"SELECT p FROM {tableName}\")\n992 \n", - "col_offset": 27, - "filename": "src/tests/integration/test_sqlite_parity.py", - "issue_confidence": "MEDIUM", - "issue_severity": "MEDIUM", - "issue_text": "Possible SQL injection vector through string-based query construction.", - "line_number": 991, - "line_range": [ - 991 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b608_hardcoded_sql_expressions.html", - "test_id": "B608", - "test_name": "hardcoded_sql_expressions" - }, - { - "code": "1014 \n1015 tableName = \"TestParseDeclTypes\" + str(random.randint(0, 99999))\n1016 try:\n", - "col_offset": 47, - "filename": "src/tests/integration/test_sqlite_parity.py", - "issue_confidence": "HIGH", - "issue_severity": "LOW", - "issue_text": "Standard pseudo-random generators are not suitable for security/cryptographic purposes.", - "line_number": 1015, - "line_range": [ - 1015 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/blacklists/blacklist_calls.html#b311-random", - "test_id": "B311", - "test_name": "blacklist" - }, - { - "code": "1019 cursor.execute(f\"INSERT INTO {tableName}(p) VALUES (?)\", (mynumber,))\n1020 cursor.execute(f\"SELECT p FROM {tableName}\")\n1021 \n", - "col_offset": 27, - "filename": "src/tests/integration/test_sqlite_parity.py", - "issue_confidence": "MEDIUM", - "issue_severity": "MEDIUM", - "issue_text": "Possible SQL injection vector through string-based query construction.", - "line_number": 1020, - "line_range": [ - 1020 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b608_hardcoded_sql_expressions.html", - "test_id": "B608", - "test_name": "hardcoded_sql_expressions" - }, - { - "code": "1041 \n1042 tableName = \"TestParseDeclTypes\" + str(random.randint(0, 99999))\n1043 try:\n", - "col_offset": 47, - "filename": "src/tests/integration/test_sqlite_parity.py", - "issue_confidence": "HIGH", - "issue_severity": "LOW", - "issue_text": "Standard pseudo-random generators are not suitable for security/cryptographic purposes.", - "line_number": 1042, - "line_range": [ - 1042 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/blacklists/blacklist_calls.html#b311-random", - "test_id": "B311", - "test_name": "blacklist" - }, - { - "code": "1054 )\n1055 cursor.execute(f\"SELECT d, t FROM {tableName}\")\n1056 \n", - "col_offset": 27, - "filename": "src/tests/integration/test_sqlite_parity.py", - "issue_confidence": "MEDIUM", - "issue_severity": "MEDIUM", - "issue_text": "Possible SQL injection vector through string-based query construction.", - "line_number": 1055, - "line_range": [ - 1055 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b608_hardcoded_sql_expressions.html", - "test_id": "B608", - "test_name": "hardcoded_sql_expressions" - }, - { - "code": "1093 \n1094 tableName = \"TestParseDeclTypes\" + str(random.randint(0, 99999))\n1095 try:\n", - "col_offset": 47, - "filename": "src/tests/integration/test_sqlite_parity.py", - "issue_confidence": "HIGH", - "issue_severity": "LOW", - "issue_text": "Standard pseudo-random generators are not suitable for security/cryptographic purposes.", - "line_number": 1094, - "line_range": [ - 1094 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/blacklists/blacklist_calls.html#b311-random", - "test_id": "B311", - "test_name": "blacklist" - }, - { - "code": "1099 cursor.execute(f\"INSERT INTO {tableName}(p) VALUES (?)\", (p,))\n1100 cursor.execute(f\"SELECT p FROM {tableName}\")\n1101 \n", - "col_offset": 27, - "filename": "src/tests/integration/test_sqlite_parity.py", - "issue_confidence": "MEDIUM", - "issue_severity": "MEDIUM", - "issue_text": "Possible SQL injection vector through string-based query construction.", - "line_number": 1100, - "line_range": [ - 1100 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b608_hardcoded_sql_expressions.html", - "test_id": "B608", - "test_name": "hardcoded_sql_expressions" - }, - { - "code": "1179 \n1180 tableName = \"TestParseColnames\" + str(random.randint(0, 99999))\n1181 try:\n", - "col_offset": 46, - "filename": "src/tests/integration/test_sqlite_parity.py", - "issue_confidence": "HIGH", - "issue_severity": "LOW", - "issue_text": "Standard pseudo-random generators are not suitable for security/cryptographic purposes.", - "line_number": 1180, - "line_range": [ - 1180 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/blacklists/blacklist_calls.html#b311-random", - "test_id": "B311", - "test_name": "blacklist" - }, - { - "code": "1185 cursor.execute(f\"INSERT INTO {tableName}(p) VALUES (?)\", (str(p),))\n1186 cursor.execute(f'SELECT p, p \"lat lng [coordinate]\" FROM {tableName}')\n1187 \n", - "col_offset": 27, - "filename": "src/tests/integration/test_sqlite_parity.py", - "issue_confidence": "MEDIUM", - "issue_severity": "MEDIUM", - "issue_text": "Possible SQL injection vector through string-based query construction.", - "line_number": 1186, - "line_range": [ - 1186 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b608_hardcoded_sql_expressions.html", - "test_id": "B608", - "test_name": "hardcoded_sql_expressions" - }, - { - "code": "18 rowset = client.exec_query(\n19 f\"USE DATABASE {dbname}; SELECT * FROM contacts\", connection\n20 )\n", - "col_offset": 16, - "filename": "src/tests/integration/test_upload.py", - "issue_confidence": "LOW", - "issue_severity": "MEDIUM", - "issue_text": "Possible SQL injection vector through string-based query construction.", - "line_number": 19, - "line_range": [ - 19 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b608_hardcoded_sql_expressions.html", - "test_id": "B608", - "test_name": "hardcoded_sql_expressions" } ] } diff --git a/bandit.yaml b/bandit.yaml index b99641c..0aba3ea 100644 --- a/bandit.yaml +++ b/bandit.yaml @@ -1,2 +1,3 @@ # https://bndit.readthedocs.io/en/latest/config.html skips: ['B101'] +exclude_dirs: ['src/tests'] diff --git a/requirements-dev.txt b/requirements-dev.txt index 7ae6b68..1933763 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -12,7 +12,7 @@ pylint==2.13.9 flake8==5.0.4 isort==5.10.1 autoflake==1.4 -pre-commit==2.17.0 +pre-commit==4.2.0 bandit==1.7.1 # We can use the most recent compatible version because # this package is only used for testing compatibility diff --git a/src/sqlitecloud/datatypes.py b/src/sqlitecloud/datatypes.py index a215381..482f4ec 100644 --- a/src/sqlitecloud/datatypes.py +++ b/src/sqlitecloud/datatypes.py @@ -99,6 +99,7 @@ def __init__( dbname: Optional[str] = "", port: int = SQLITECLOUD_DEFAULT.PORT.value, apikey: Optional[str] = "", + token: Optional[str] = "", ) -> None: # User name is required unless connectionstring is provided self.username = username @@ -106,8 +107,10 @@ def __init__( self.password = password # Password is hashed self.password_hashed = False - # API key instead of username and password + # API key self.apikey = apikey + # Access Token + self.token = token # Name of database to open self.dbname = dbname # Like mynode.sqlitecloud.io @@ -205,13 +208,19 @@ def _parse_connection_string(self, connection_string) -> None: elif hasattr(self.account, opt): setattr(self.account, opt, value) - # apikey or username/password is accepted - if not self.account.apikey: - self.account.username = ( - parse.unquote(params.username) if params.username else "" - ) - self.account.password = ( - parse.unquote(params.password) if params.password else "" + if params.username and params.password: + self.account.username = parse.unquote(params.username) + self.account.password = parse.unquote(params.password) + + # either you use an apikey, token or username and password + if ( + bool(self.account.apikey) + + bool(self.account.token) + + bool(self.account.username or self.account.password) + > 1 + ): + raise SQLiteCloudException( + "Choose between apikey, token or username/password" ) path = params.path @@ -224,9 +233,7 @@ def _parse_connection_string(self, connection_string) -> None: int(params.port) if params.port else SQLITECLOUD_DEFAULT.PORT.value ) except Exception as e: - raise SQLiteCloudException( - f"Invalid connection string {connection_string}" - ) from e + raise SQLiteCloudException("Invalid connection string") from e class SQLiteCloudNumber: diff --git a/src/sqlitecloud/dbapi2.py b/src/sqlitecloud/dbapi2.py index 87ad6bc..6de84c7 100644 --- a/src/sqlitecloud/dbapi2.py +++ b/src/sqlitecloud/dbapi2.py @@ -832,8 +832,11 @@ def _named_to_question_mark_parameters( SCSP protocol does not support named placeholders yet. """ - pattern = r":(\w+)" + # Python variable names: start with letter or underscore, followed by letters, digits, or underscores + pattern = r":([a-zA-Z_][a-zA-Z0-9_]*)" matches = re.findall(pattern, sql) + # filter out duplicates + matches = list(dict.fromkeys(matches)) params_list = () for match in matches: diff --git a/src/sqlitecloud/driver.py b/src/sqlitecloud/driver.py index 67fb448..2e3c8dc 100644 --- a/src/sqlitecloud/driver.py +++ b/src/sqlitecloud/driver.py @@ -436,8 +436,9 @@ def _internal_config_apply( if config.account.apikey: command += f"AUTH APIKEY {config.account.apikey};" - - if config.account.username and config.account.password: + elif config.account.token: + command += f"AUTH TOKEN {config.account.token};" + elif config.account.username and config.account.password: option = "HASH" if config.account.password_hashed else "PASSWORD" command += f"AUTH USER {config.account.username} {option} {config.account.password};" diff --git a/src/tests/integration/test_client.py b/src/tests/integration/test_client.py index 70a514c..52a5745 100644 --- a/src/tests/integration/test_client.py +++ b/src/tests/integration/test_client.py @@ -42,7 +42,21 @@ def test_connection_with_credentials(self): def test_connection_with_apikey(self): account = SQLiteCloudAccount() - account.username = os.getenv("SQLITE_API_KEY") + account.apikey = os.getenv("SQLITE_API_KEY") + account.dbname = os.getenv("SQLITE_DB") + account.hostname = os.getenv("SQLITE_HOST") + account.port = int(os.getenv("SQLITE_PORT")) + + client = SQLiteCloudClient(cloud_account=account) + conn = client.open_connection() + assert isinstance(conn, SQLiteCloudConnect) + + client.disconnect(conn) + + def test_connection_with_access_token(self): + account = SQLiteCloudAccount() + account.token = os.getenv("SQLITE_ACCESS_TOKEN") + account.dbname = os.getenv("SQLITE_DB") account.hostname = os.getenv("SQLITE_HOST") account.port = int(os.getenv("SQLITE_PORT")) @@ -201,6 +215,13 @@ def test_integer(self, sqlitecloud_connection): assert SQLITECLOUD_RESULT_TYPE.RESULT_INTEGER == result.tag assert 123456 == result.get_result() + def test_integer_64_bit(self, sqlitecloud_connection): + connection, client = sqlitecloud_connection + result = client.exec_query("TEST INT64", connection) + + assert SQLITECLOUD_RESULT_TYPE.RESULT_INTEGER == result.tag + assert 9223372036854775807 == result.get_result() + def test_float(self, sqlitecloud_connection): connection, client = sqlitecloud_connection result = client.exec_query("TEST FLOAT", connection) diff --git a/src/tests/integration/test_driver.py b/src/tests/integration/test_driver.py index ece2755..029b11b 100644 --- a/src/tests/integration/test_driver.py +++ b/src/tests/integration/test_driver.py @@ -1,3 +1,4 @@ +import hashlib import random import tempfile import uuid @@ -113,3 +114,28 @@ def test_prepare_statement_update(self, sqlitecloud_connection): assert result.changes == 1 assert result.total_changes == 2 # insert + update assert result.finalized == 1 + + def test_prepare_statement_insert_blob(self, sqlitecloud_connection): + driver = Driver() + + connection, _ = sqlitecloud_connection + + hash_data = hashlib.sha256(b"my blob data").digest() + + driver.execute_statement( + "CREATE TABLE IF NOT EXISTS blobs (id INTEGER PRIMARY KEY, hash BLOB NOT NULL);", + [], + connection, + ) + insert_result = driver.execute_statement( + "INSERT INTO blobs (hash) VALUES (?);", (hash_data,), connection + ) + result = driver.execute_statement( + "SELECT id, hash FROM blobs WHERE id = ? and hash = ?;", + (insert_result.rowid, hash_data), + connection, + ) + + assert result.nrows == 1 + assert result.get_value(0, 1) == hash_data + assert result.get_value(0, 0) == insert_result.rowid diff --git a/src/tests/integration/test_sqlite_parity.py b/src/tests/integration/test_sqlite_parity.py index 7459bcb..eb3b007 100644 --- a/src/tests/integration/test_sqlite_parity.py +++ b/src/tests/integration/test_sqlite_parity.py @@ -187,16 +187,29 @@ def test_execute_with_named_param_style(self, connection, request): def test_executemany_with_named_param_style(self, connection, request): connection = request.getfixturevalue(connection) - select_query = "INSERT INTO customers (FirstName, Email, LastName) VALUES (:name, :email, :name)" + random_str1 = str(uuid.uuid4()) + random_str2 = str(uuid.uuid4()) + insert_query = "INSERT INTO customers (FirstName, Email, LastName) VALUES (:name, :email, :name)" params = [ - {"name": "pippo", "email": "pippo@disney.com"}, - {"name": "pluto", "email": "pluto@disney.com"}, + {"name": random_str1, "email": "pippo@disney.com"}, + {"name": random_str2, "email": "pluto@disney.com"}, ] - connection.executemany(select_query, params) + connection.executemany(insert_query, params) assert connection.total_changes == 2 + select_query = "SELECT FirstName, Email, LastName FROM customers WHERE FirstName = ? OR FirstName = ?" + rows = connection.execute(select_query, (random_str1, random_str2)).fetchmany(2) + + assert len(rows) == 2 + assert rows[0][0] == random_str1 + assert rows[0][1] == "pippo@disney.com" + assert rows[0][2] == random_str1 + assert rows[1][0] == random_str2 + assert rows[1][1] == "pluto@disney.com" + assert rows[1][2] == random_str2 + def test_insert_result(self, sqlitecloud_dbapi2_connection, sqlite3_connection): sqlitecloud_connection = sqlitecloud_dbapi2_connection @@ -513,7 +526,7 @@ def test_autocommit_mode_enabled_by_default( ] try: - for (connection, control_connection) in connections: + for connection, control_connection in connections: connection.execute( "INSERT INTO albums (Title, ArtistId) VALUES (? , 1);", (f"Test {seed}",), @@ -543,7 +556,7 @@ def test_explicit_transaction_to_commit( ] try: - for (connection, control_connection) in connections: + for connection, control_connection in connections: cursor1 = connection.execute("BEGIN;") cursor1.execute( "INSERT INTO albums (Title, ArtistId) VALUES (?, 1);", diff --git a/src/tests/unit/test_client.py b/src/tests/unit/test_client.py index 7a561cc..e5eb7dd 100644 --- a/src/tests/unit/test_client.py +++ b/src/tests/unit/test_client.py @@ -3,7 +3,7 @@ class TestClient: def test_parse_connection_string_with_apikey(self): - connection_string = "sqlitecloud://user:pass@host.com:8860/dbname?apikey=abc123&timeout=10&compression=true" + connection_string = "sqlitecloud://host.com:8860/dbname?apikey=abc123&timeout=10&compression=true" client = SQLiteCloudClient(connection_str=connection_string) assert not client.config.account.username diff --git a/src/tests/unit/test_driver.py b/src/tests/unit/test_driver.py index 8e09d70..9e3fc48 100644 --- a/src/tests/unit/test_driver.py +++ b/src/tests/unit/test_driver.py @@ -179,3 +179,21 @@ def test_internal_serialize_command(self, data, zero_string, expected): serialized = driver._internal_serialize_command(data, zero_string=zero_string) assert serialized == expected + + def test_expect_command_auth_token(self, mocker: MockerFixture): + driver = Driver() + + config = SQLiteCloudConfig() + config.account = SQLiteCloudAccount() + config.account.token = "abc123" + + mocker.patch.object(driver, "_internal_connect", return_value=None) + run_command_mock = mocker.patch.object(driver, "_internal_run_command") + + driver.connect("myhost", 8860, config) + + expected_buffer = b"AUTH TOKEN abc123;" + + run_command_mock.assert_called_once() + assert expected_buffer in run_command_mock.call_args[0][1] + assert b"AUTH APIKEY" not in run_command_mock.call_args[0][1] diff --git a/src/tests/unit/test_types.py b/src/tests/unit/test_types.py index ff4cb4c..c26cfcd 100644 --- a/src/tests/unit/test_types.py +++ b/src/tests/unit/test_types.py @@ -1,6 +1,7 @@ import pytest from sqlitecloud.datatypes import SQLiteCloudConfig +from sqlitecloud.exceptions import SQLiteCloudException class TestSQLiteCloudConfig: @@ -17,3 +18,21 @@ def test_parse_connection_string_with_nonlinarizable(self, param: str, value: an config = SQLiteCloudConfig(connection_string) assert config.non_linearizable + + def test_parse_connection_string_error_with_both_user_pass_and_apikey(self): + connection_string = "sqlitecloud://user:password@host:1234/database?apikey=xxx" + + with pytest.raises(SQLiteCloudException, match="Invalid connection string"): + SQLiteCloudConfig(connection_string) + + def test_parse_connection_string_error_with_both_user_pass_and_token(self): + connection_string = "sqlitecloud://user:password@host:1234/database?token=xxx" + + with pytest.raises(SQLiteCloudException, match="Invalid connection string"): + SQLiteCloudConfig(connection_string) + + def test_parse_connection_string_error_with_both_apikey_and_token(self): + connection_string = "sqlitecloud://host:1234/database?apikey=xxx&token=yyy" + + with pytest.raises(SQLiteCloudException, match="Invalid connection string"): + SQLiteCloudConfig(connection_string) From 2f45e47b6052f38f79515c996744524bbaf50d5f Mon Sep 17 00:00:00 2001 From: Daniele Briggi Date: Thu, 22 May 2025 07:58:34 +0000 Subject: [PATCH 2/2] chore(venv): setup latest devcontainer --- .devcontainer/{py3.11 => py3.13}/devcontainer.json | 4 ++-- .github/workflows/test.yaml | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) rename .devcontainer/{py3.11 => py3.13}/devcontainer.json (93%) diff --git a/.devcontainer/py3.11/devcontainer.json b/.devcontainer/py3.13/devcontainer.json similarity index 93% rename from .devcontainer/py3.11/devcontainer.json rename to .devcontainer/py3.13/devcontainer.json index 54298b9..1ee24fd 100644 --- a/.devcontainer/py3.11/devcontainer.json +++ b/.devcontainer/py3.13/devcontainer.json @@ -1,9 +1,9 @@ // For format details, see https://aka.ms/devcontainer.json. For config options, see the // README at: https://github.com/devcontainers/templates/tree/main/src/python { - "name": "Python 3.11", + "name": "Python 3.13", // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile - "image": "mcr.microsoft.com/devcontainers/python:3.11", + "image": "mcr.microsoft.com/devcontainers/python:3.13", "features": { "ghcr.io/warrenbuckley/codespace-features/sqlite:1": {} }, diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 18c10ad..44e5246 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -36,6 +36,7 @@ jobs: SQLITE_USER: ${{ secrets.SQLITE_USER }} SQLITE_PASSWORD: ${{ secrets.SQLITE_PASSWORD }} SQLITE_API_KEY: ${{ secrets.SQLITE_API_KEY }} + SQLITE_ACCESS_TOKEN: ${{ secrets.SQLITE_ACCESS_TOKEN }} SQLITE_HOST: ${{ vars.SQLITE_HOST }} SQLITE_DB: ${{ vars.SQLITE_DB }} SQLITE_PORT: ${{ vars.SQLITE_PORT }}