sql-data-guard 0.0.40__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.

Potentially problematic release.


This version of sql-data-guard might be problematic. Click here for more details.

Files changed (27) hide show
  1. sql_data_guard-0.0.40/LICENSE.md +9 -0
  2. sql_data_guard-0.0.40/PKG-INFO +153 -0
  3. sql_data_guard-0.0.40/README.md +129 -0
  4. sql_data_guard-0.0.40/pyproject.toml +30 -0
  5. sql_data_guard-0.0.40/setup.cfg +4 -0
  6. sql_data_guard-0.0.40/src/sql_data_guard/__init__.py +1 -0
  7. sql_data_guard-0.0.40/src/sql_data_guard/rest/__init__.py +1 -0
  8. sql_data_guard-0.0.40/src/sql_data_guard/rest/sql_data_guard_rest.py +39 -0
  9. sql_data_guard-0.0.40/src/sql_data_guard/restriction_validation.py +82 -0
  10. sql_data_guard-0.0.40/src/sql_data_guard/restriction_verification.py +146 -0
  11. sql_data_guard-0.0.40/src/sql_data_guard/sql_data_guard.py +273 -0
  12. sql_data_guard-0.0.40/src/sql_data_guard/verification_context.py +63 -0
  13. sql_data_guard-0.0.40/src/sql_data_guard/verification_utils.py +18 -0
  14. sql_data_guard-0.0.40/src/sql_data_guard.egg-info/PKG-INFO +153 -0
  15. sql_data_guard-0.0.40/src/sql_data_guard.egg-info/SOURCES.txt +25 -0
  16. sql_data_guard-0.0.40/src/sql_data_guard.egg-info/dependency_links.txt +1 -0
  17. sql_data_guard-0.0.40/src/sql_data_guard.egg-info/requires.txt +1 -0
  18. sql_data_guard-0.0.40/src/sql_data_guard.egg-info/top_level.txt +1 -0
  19. sql_data_guard-0.0.40/test/test_duckdb_unit.py +235 -0
  20. sql_data_guard-0.0.40/test/test_rest_api_unit.py +76 -0
  21. sql_data_guard-0.0.40/test/test_sql_guard_curr_unit.py +678 -0
  22. sql_data_guard-0.0.40/test/test_sql_guard_joins_unit.py +300 -0
  23. sql_data_guard-0.0.40/test/test_sql_guard_llm.py +150 -0
  24. sql_data_guard-0.0.40/test/test_sql_guard_unit.py +401 -0
  25. sql_data_guard-0.0.40/test/test_sql_guard_updates_unit.py +770 -0
  26. sql_data_guard-0.0.40/test/test_sql_guard_validation_unit.py +176 -0
  27. sql_data_guard-0.0.40/test/test_utils.py +156 -0
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Imperva
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,153 @@
1
+ Metadata-Version: 2.4
2
+ Name: sql-data-guard
3
+ Version: 0.0.40
4
+ Summary: Safety Layer for LLM Database Interactions
5
+ Author-email: Imperva - Threat Reseach Infra <ww.dis.imperva.threat-research-infra@thalesgroup.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 Imperva
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15
+ Project-URL: Homepage, https://github.com/ThalesGroup/sql-data-guard
16
+ Project-URL: Issues, https://github.com/ThalesGroup/sql-data-guard/issues
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Operating System :: OS Independent
19
+ Requires-Python: >=3.8
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE.md
22
+ Requires-Dist: sqlglot
23
+ Dynamic: license-file
24
+
25
+
26
+ # sql-data-guard: Safety Layer for LLM Database Interactions
27
+
28
+ <div style="text-align: center;">
29
+ <img alt="SQL Data Guard logo" src="https://github.com/ThalesGroup/sql-data-guard/blob/main/sql-data-guard-logo.png" width="300"/>
30
+ </div>
31
+
32
+ SQL is the go-to language for performing queries on databases, and for a good reason - it’s well known, easy to use and pretty simple. However, it seems that it’s as easy to use as it is to exploit, and SQL injection is still one of the most targeted vulnerabilities - especially nowadays with the proliferation of “natural language queries” harnessing Large Language Models (LLMs) power to generate and run SQL queries.
33
+
34
+
35
+ To help solve this problem, we developed sql-data-guard, an open-source project designed to verify that SQL queries access only the data they are allowed to. It takes a query and a restriction configuration, and returns whether the query is allowed to run or not. Additionally, it can modify the query to ensure it complies with the restrictions. sql-data-guard has also a built-in module for detection of malicious payloads, allowing it to report on and remove malicious expressions before query execution.
36
+
37
+
38
+ sql-data-guard is particularly useful when constructing SQL queries with LLMs, as such queries can’t run as prepared statements. Prepared statements secure a query’s structure, but LLM-generated queries are dynamic and lack this fixed form, increasing SQL injection risk. sql-data-guard mitigates this by inspecting and validating the query's content.
39
+
40
+
41
+ By verifying and modifying queries before they are executed, sql-data-guard helps prevent unauthorized data access and accidental data exposure. Adding sql-data-guard to your application can prevent or minimize data breaches and the impact of SQL injection attacks, ensuring that only permitted data is accessed.
42
+
43
+
44
+ Connecting LLMs to SQL databases without strict controls can risk accidental data exposure, as models may generate SQL queries that access sensitive information. OWASP highlights cases of poor sandboxing leading to unauthorized disclosures, emphasizing the need for clear access controls and prompt validation. Businesses should adopt rigorous access restrictions, regular audits, and robust API security, especially to comply with privacy laws and regulations like GDPR and CCPA, which penalize unauthorized data exposure.
45
+
46
+ ## Why Use sql-data-guard?
47
+
48
+ Consider using sql-guard if your application constructs SQL queries, and you need to ensure that only permitted data is accessed. This is particularly beneficial if:
49
+ - Your application generates complex SQL queries.
50
+ - Your application employs LLM (Large Language Models) to create SQL queries, making it difficult to fully control the queries.
51
+ - Different application users and roles should have different permissions, and you need to correlate an application user or role with fine-grained data access permission.
52
+ - In multi-tenant applications, you need to ensure that each tenant can access only their data, which requires row-level security and often cannot be done using the database permissions model.
53
+
54
+ sql-guard does not replace the database permissions model. Instead, it adds an extra layer of security, which is crucial when implementing fine-grained, column-level, and row-level security is challenging or impossible.
55
+ Data restrictions are often complex and cannot be expressed by the database permissions model. For instance, you may need to restrict access to specific columns or rows based on intricate business logic, which many database implementations do not support. Instead of relying on the database to enforce these restrictions, sql-guard helps you overcome vendor-specific limitations by verifying and modifying queries before they are executed.
56
+
57
+ ## How It Works
58
+
59
+ 1. **Input**: sql-data-guard takes an SQL query and a restriction configuration as input.
60
+ 2. **Verification**: It verifies whether the query complies with the restrictions specified in the configuration.
61
+ 3. **Modification**: If the query does not comply, sql-data-guard can modify the query to ensure it meets the restrictions.
62
+ 4. **Output**: It returns whether the query is allowed to run or not, and if necessary, the modified query.
63
+
64
+ sql-data-guard is designed to be easy to integrate into your application. It provides a simple API that you can call to verify and modify SQL queries before they are executed. You can integrate it using REST API or directly in your application code.
65
+
66
+ ## Example
67
+
68
+ Below you can find a Python snippet with allowed data access configuration, and usage of sql-data-guard. sql-data-guard finds a restricted column and an “always-true” possible injection and removes them both. It also adds a missing data restriction:
69
+
70
+ ```python
71
+ from sql_data_guard import verify_sql
72
+
73
+ config = {
74
+ "tables": [
75
+ {
76
+ "name": "orders",
77
+ "columns": ["id", "product_name", "account_id"],
78
+ "restrictions": [{"column": "account_id", "value": 123}]
79
+ }
80
+ ]
81
+ }
82
+
83
+ query = "SELECT id, name FROM orders WHERE 1 = 1"
84
+ result = verify_sql(query, config)
85
+ print(result)
86
+ ```
87
+ Output:
88
+ ```json
89
+ {
90
+ "allowed": false,
91
+ "errors": ["Column name not allowed. Column removed from SELECT clause",
92
+ "Always-True expression is not allowed", "Missing restriction for table: orders column: account_id value: 123"],
93
+ "fixed": "SELECT id, product_name, account_id FROM orders WHERE account_id = 123"
94
+ }
95
+ ```
96
+ Here is a table with more examples of SQL queries and their corresponding JSON outputs:
97
+
98
+ | SQL Query | JSON Output |
99
+ |---------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
100
+ | SELECT id, product_name FROM orders WHERE account_id = 123 | { "allowed": true, "errors": [], "fixed": null } |
101
+ | SELECT id FROM orders WHERE account_id = 456 | { "allowed": false, "errors": ["Missing restriction for table: orders column: account_id value: 123"], "fixed": "SELECT id FROM orders WHERE account_id = 456 AND account_id = 123" } |
102
+ | SELECT id, col FROM orders WHERE account_id = 123 | { "allowed": false, "errors": ["Column col is not allowed. Column removed from SELECT clause"], "fixed": "SELECT id FROM orders WHERE account_id = 123" } ``` |
103
+ | SELECT id FROM orders WHERE account_id = 123 OR 1 = 1 | { "allowed": false, "errors": ["Always-True expression is not allowed"], "fixed": "SELECT id FROM orders WHERE account_id = 123" } |
104
+ |SELECT * FROM orders WHERE account_id = 123| {"allowed": false, "errors": ["SELECT * is not allowed"], "fixed": "SELECT id, product_name, account_id FROM orders WHERE account_id = 123"} |
105
+ ```
106
+
107
+
108
+ This table provides a variety of SQL queries and their corresponding JSON outputs, demonstrating how `sql-data-guard` handles different scenarios.
109
+
110
+ ## Installation
111
+ To install sql-data-guard, use pip:
112
+
113
+ ```bash
114
+ pip install sql-data-guard
115
+ ```
116
+
117
+ ## Docker Repository
118
+
119
+ sql-data-guard is also available as a Docker image, which can be used to run the application in a containerized environment. This is particularly useful for deployment in cloud environments or for maintaining consistency across different development setups.
120
+
121
+ ### Running the Docker Container
122
+
123
+ To run the sql-data-guard Docker container, use the following command:
124
+
125
+ ```bash
126
+ docker run -d --name sql-data-guard -p 5000:5000 ghcr.io/thalesgroup/sql-data-guard
127
+
128
+ ### Calling the Docker Container Using REST API
129
+
130
+ Once the `sql-data-guard` Docker container is running, you can interact with it using its REST API. Below is an example of how to verify an SQL query using `curl`:
131
+
132
+ ```bash
133
+ curl -X POST http://localhost:5000/verify-sql \
134
+ -H "Content-Type: application/json" \
135
+ -d '{
136
+ "sql": "SELECT * FROM orders WHERE account_id = 123",
137
+ "config": {
138
+ "tables": [
139
+ {
140
+ "table_name": "orders",
141
+ "columns": ["id", "product_name", "account_id"],
142
+ "restrictions": [{"column": "account_id", "value": 123}]
143
+ }
144
+ ]
145
+ }
146
+ }'
147
+ ```
148
+
149
+ ## Contributing
150
+ We welcome contributions! Please see our [CONTRIBUTING.md](CONTRIBUTING.md) for more details.
151
+
152
+ ## License
153
+ This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,129 @@
1
+
2
+ # sql-data-guard: Safety Layer for LLM Database Interactions
3
+
4
+ <div style="text-align: center;">
5
+ <img alt="SQL Data Guard logo" src="https://github.com/ThalesGroup/sql-data-guard/blob/main/sql-data-guard-logo.png" width="300"/>
6
+ </div>
7
+
8
+ SQL is the go-to language for performing queries on databases, and for a good reason - it’s well known, easy to use and pretty simple. However, it seems that it’s as easy to use as it is to exploit, and SQL injection is still one of the most targeted vulnerabilities - especially nowadays with the proliferation of “natural language queries” harnessing Large Language Models (LLMs) power to generate and run SQL queries.
9
+
10
+
11
+ To help solve this problem, we developed sql-data-guard, an open-source project designed to verify that SQL queries access only the data they are allowed to. It takes a query and a restriction configuration, and returns whether the query is allowed to run or not. Additionally, it can modify the query to ensure it complies with the restrictions. sql-data-guard has also a built-in module for detection of malicious payloads, allowing it to report on and remove malicious expressions before query execution.
12
+
13
+
14
+ sql-data-guard is particularly useful when constructing SQL queries with LLMs, as such queries can’t run as prepared statements. Prepared statements secure a query’s structure, but LLM-generated queries are dynamic and lack this fixed form, increasing SQL injection risk. sql-data-guard mitigates this by inspecting and validating the query's content.
15
+
16
+
17
+ By verifying and modifying queries before they are executed, sql-data-guard helps prevent unauthorized data access and accidental data exposure. Adding sql-data-guard to your application can prevent or minimize data breaches and the impact of SQL injection attacks, ensuring that only permitted data is accessed.
18
+
19
+
20
+ Connecting LLMs to SQL databases without strict controls can risk accidental data exposure, as models may generate SQL queries that access sensitive information. OWASP highlights cases of poor sandboxing leading to unauthorized disclosures, emphasizing the need for clear access controls and prompt validation. Businesses should adopt rigorous access restrictions, regular audits, and robust API security, especially to comply with privacy laws and regulations like GDPR and CCPA, which penalize unauthorized data exposure.
21
+
22
+ ## Why Use sql-data-guard?
23
+
24
+ Consider using sql-guard if your application constructs SQL queries, and you need to ensure that only permitted data is accessed. This is particularly beneficial if:
25
+ - Your application generates complex SQL queries.
26
+ - Your application employs LLM (Large Language Models) to create SQL queries, making it difficult to fully control the queries.
27
+ - Different application users and roles should have different permissions, and you need to correlate an application user or role with fine-grained data access permission.
28
+ - In multi-tenant applications, you need to ensure that each tenant can access only their data, which requires row-level security and often cannot be done using the database permissions model.
29
+
30
+ sql-guard does not replace the database permissions model. Instead, it adds an extra layer of security, which is crucial when implementing fine-grained, column-level, and row-level security is challenging or impossible.
31
+ Data restrictions are often complex and cannot be expressed by the database permissions model. For instance, you may need to restrict access to specific columns or rows based on intricate business logic, which many database implementations do not support. Instead of relying on the database to enforce these restrictions, sql-guard helps you overcome vendor-specific limitations by verifying and modifying queries before they are executed.
32
+
33
+ ## How It Works
34
+
35
+ 1. **Input**: sql-data-guard takes an SQL query and a restriction configuration as input.
36
+ 2. **Verification**: It verifies whether the query complies with the restrictions specified in the configuration.
37
+ 3. **Modification**: If the query does not comply, sql-data-guard can modify the query to ensure it meets the restrictions.
38
+ 4. **Output**: It returns whether the query is allowed to run or not, and if necessary, the modified query.
39
+
40
+ sql-data-guard is designed to be easy to integrate into your application. It provides a simple API that you can call to verify and modify SQL queries before they are executed. You can integrate it using REST API or directly in your application code.
41
+
42
+ ## Example
43
+
44
+ Below you can find a Python snippet with allowed data access configuration, and usage of sql-data-guard. sql-data-guard finds a restricted column and an “always-true” possible injection and removes them both. It also adds a missing data restriction:
45
+
46
+ ```python
47
+ from sql_data_guard import verify_sql
48
+
49
+ config = {
50
+ "tables": [
51
+ {
52
+ "name": "orders",
53
+ "columns": ["id", "product_name", "account_id"],
54
+ "restrictions": [{"column": "account_id", "value": 123}]
55
+ }
56
+ ]
57
+ }
58
+
59
+ query = "SELECT id, name FROM orders WHERE 1 = 1"
60
+ result = verify_sql(query, config)
61
+ print(result)
62
+ ```
63
+ Output:
64
+ ```json
65
+ {
66
+ "allowed": false,
67
+ "errors": ["Column name not allowed. Column removed from SELECT clause",
68
+ "Always-True expression is not allowed", "Missing restriction for table: orders column: account_id value: 123"],
69
+ "fixed": "SELECT id, product_name, account_id FROM orders WHERE account_id = 123"
70
+ }
71
+ ```
72
+ Here is a table with more examples of SQL queries and their corresponding JSON outputs:
73
+
74
+ | SQL Query | JSON Output |
75
+ |---------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
76
+ | SELECT id, product_name FROM orders WHERE account_id = 123 | { "allowed": true, "errors": [], "fixed": null } |
77
+ | SELECT id FROM orders WHERE account_id = 456 | { "allowed": false, "errors": ["Missing restriction for table: orders column: account_id value: 123"], "fixed": "SELECT id FROM orders WHERE account_id = 456 AND account_id = 123" } |
78
+ | SELECT id, col FROM orders WHERE account_id = 123 | { "allowed": false, "errors": ["Column col is not allowed. Column removed from SELECT clause"], "fixed": "SELECT id FROM orders WHERE account_id = 123" } ``` |
79
+ | SELECT id FROM orders WHERE account_id = 123 OR 1 = 1 | { "allowed": false, "errors": ["Always-True expression is not allowed"], "fixed": "SELECT id FROM orders WHERE account_id = 123" } |
80
+ |SELECT * FROM orders WHERE account_id = 123| {"allowed": false, "errors": ["SELECT * is not allowed"], "fixed": "SELECT id, product_name, account_id FROM orders WHERE account_id = 123"} |
81
+ ```
82
+
83
+
84
+ This table provides a variety of SQL queries and their corresponding JSON outputs, demonstrating how `sql-data-guard` handles different scenarios.
85
+
86
+ ## Installation
87
+ To install sql-data-guard, use pip:
88
+
89
+ ```bash
90
+ pip install sql-data-guard
91
+ ```
92
+
93
+ ## Docker Repository
94
+
95
+ sql-data-guard is also available as a Docker image, which can be used to run the application in a containerized environment. This is particularly useful for deployment in cloud environments or for maintaining consistency across different development setups.
96
+
97
+ ### Running the Docker Container
98
+
99
+ To run the sql-data-guard Docker container, use the following command:
100
+
101
+ ```bash
102
+ docker run -d --name sql-data-guard -p 5000:5000 ghcr.io/thalesgroup/sql-data-guard
103
+
104
+ ### Calling the Docker Container Using REST API
105
+
106
+ Once the `sql-data-guard` Docker container is running, you can interact with it using its REST API. Below is an example of how to verify an SQL query using `curl`:
107
+
108
+ ```bash
109
+ curl -X POST http://localhost:5000/verify-sql \
110
+ -H "Content-Type: application/json" \
111
+ -d '{
112
+ "sql": "SELECT * FROM orders WHERE account_id = 123",
113
+ "config": {
114
+ "tables": [
115
+ {
116
+ "table_name": "orders",
117
+ "columns": ["id", "product_name", "account_id"],
118
+ "restrictions": [{"column": "account_id", "value": 123}]
119
+ }
120
+ ]
121
+ }
122
+ }'
123
+ ```
124
+
125
+ ## Contributing
126
+ We welcome contributions! Please see our [CONTRIBUTING.md](CONTRIBUTING.md) for more details.
127
+
128
+ ## License
129
+ This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,30 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [tool.setuptools]
6
+ include-package-data = false
7
+ package-dir = {"" = "src"}
8
+ [tool.pip]
9
+ extra-index-url = ["https://pypi.org/simple"]
10
+ [project]
11
+ name = "sql-data-guard"
12
+ version = "0.0.40"
13
+ dependencies = [
14
+ "sqlglot"
15
+ ]
16
+ authors = [
17
+ { name="Imperva - Threat Reseach Infra", email="ww.dis.imperva.threat-research-infra@thalesgroup.com" },
18
+ ]
19
+ description = "Safety Layer for LLM Database Interactions"
20
+ readme = "README.md"
21
+ requires-python = ">=3.8"
22
+ classifiers = [
23
+ "Programming Language :: Python :: 3",
24
+ "Operating System :: OS Independent",
25
+ ]
26
+ license = {file = "LICENSE.md"}
27
+
28
+ [project.urls]
29
+ Homepage = "https://github.com/ThalesGroup/sql-data-guard"
30
+ Issues = "https://github.com/ThalesGroup/sql-data-guard/issues"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1 @@
1
+ from .sql_data_guard import verify_sql
@@ -0,0 +1 @@
1
+ from sql_data_guard.rest.sql_data_guard_rest import app
@@ -0,0 +1,39 @@
1
+ import logging
2
+ import os
3
+ from logging.config import fileConfig
4
+
5
+ from flask import Flask, jsonify, request
6
+
7
+ from sql_data_guard import verify_sql
8
+
9
+ app = Flask(__name__)
10
+
11
+
12
+ @app.route("/verify-sql", methods=["POST"])
13
+ def _verify_sql():
14
+ if not request.is_json:
15
+ return jsonify({"error": "Request must be JSON"}), 400
16
+ data = request.get_json()
17
+ if "sql" not in data:
18
+ return jsonify({"error": "Missing 'sql' in request"}), 400
19
+ sql = data["sql"]
20
+ if "config" not in data:
21
+ return jsonify({"error": "Missing 'config' in request"}), 400
22
+ config = data["config"]
23
+ dialect = data.get("dialect")
24
+ result = verify_sql(sql, config, dialect)
25
+ result["errors"] = list(result["errors"])
26
+ return jsonify(result)
27
+
28
+
29
+ def _init_logging():
30
+ fileConfig(os.path.join(os.path.dirname(os.path.abspath(__file__)), "logging.conf"))
31
+ logging.info("Logging initialized")
32
+
33
+
34
+ if __name__ == "__main__":
35
+ _init_logging()
36
+ logging.getLogger("werkzeug").setLevel("WARNING")
37
+ port = os.environ.get("APP_PORT", 5000)
38
+ logging.info(f"Going to start the app. Port: {port}")
39
+ app.run(host="0.0.0.0", port=port)
@@ -0,0 +1,82 @@
1
+ class UnsupportedRestrictionError(Exception):
2
+ pass
3
+
4
+
5
+ def validate_restrictions(config: dict):
6
+ """
7
+ Validates the restrictions in the configuration to ensure only supported operations are used.
8
+
9
+ Args:
10
+ config (dict): The configuration containing the restrictions to validate.
11
+
12
+ Raises:
13
+ UnsupportedRestrictionError: If an unsupported restriction operation is found.
14
+ ValueError: If there are no tables in the configuration.
15
+ """
16
+ supported_operations = [
17
+ "=",
18
+ ">",
19
+ "<",
20
+ ">=",
21
+ "<=",
22
+ "!=",
23
+ "BETWEEN",
24
+ "IN",
25
+ ] # Allowed operations
26
+ # Ensure 'tables' exists in config and is not empty
27
+ tables = config.get("tables", [])
28
+ # Check if tables are empty
29
+ if not tables:
30
+ raise ValueError("Configuration must contain at least one table.")
31
+
32
+ for table in tables:
33
+ # Ensure that 'table_name' exists in each table
34
+ if "table_name" not in table:
35
+ raise ValueError("Each table must have a 'table_name' key.")
36
+ # Ensure that 'columns' exists and is not empty in each table
37
+ if "columns" not in table or not table["columns"]:
38
+ raise ValueError(
39
+ "Each table must have a 'columns' key with valid column definitions."
40
+ )
41
+
42
+ restrictions = table.get("restrictions", [])
43
+ if not restrictions:
44
+ continue # Skip if no restrictions are provided
45
+
46
+ for restriction in restrictions:
47
+ operation = restriction.get("operation")
48
+ if operation == "BETWEEN":
49
+ values = restriction.get("values")
50
+ if not (
51
+ isinstance(values, list)
52
+ and len(values) == 2
53
+ and all(isinstance(v, (int, float)) for v in values)
54
+ and values[0] < values[1]
55
+ ):
56
+ raise ValueError(
57
+ f"Invalid 'BETWEEN' format. Expected list of two numeric values where min < max. Received: {values}"
58
+ )
59
+
60
+ elif operation == "IN":
61
+ values = restriction.get("values")
62
+ if not (
63
+ isinstance(values, list)
64
+ and len(values) == 2
65
+ and all(isinstance(v, (int, float)) for v in values)
66
+ ):
67
+ raise ValueError(
68
+ f"Invalid 'IN' format. Expected list of two numeric values. Received: {values}"
69
+ )
70
+
71
+ elif operation == ">=":
72
+ # You may want to ensure the value provided is numeric for >=
73
+ value = restriction.get("value")
74
+ if not isinstance(value, (int, float)):
75
+ raise ValueError(
76
+ f"Invalid restriction value type for column '{restriction['column']}' in table '{table['table_name']}'. Expected a numeric value."
77
+ )
78
+
79
+ elif operation and operation.lower() not in supported_operations:
80
+ raise UnsupportedRestrictionError(
81
+ f"Invalid restriction: 'operation={operation}' is not supported."
82
+ )
@@ -0,0 +1,146 @@
1
+ from typing import List
2
+
3
+ import sqlglot
4
+ import sqlglot.expressions as expr
5
+
6
+ from sql_data_guard.verification_context import VerificationContext
7
+ from sql_data_guard.verification_utils import split_to_expressions
8
+
9
+
10
+ def verify_restrictions(
11
+ select_statement: expr.Query,
12
+ context: VerificationContext,
13
+ from_tables: List[expr.Table],
14
+ ):
15
+ where_clause = select_statement.find(expr.Where)
16
+ if where_clause is None:
17
+ where_clause = select_statement.find(expr.Where)
18
+ and_exps = []
19
+ else:
20
+ and_exps = list(split_to_expressions(where_clause.this, expr.And))
21
+ for c_t in context.config["tables"]:
22
+ for from_t in [t for t in from_tables if t.name == c_t["table_name"]]:
23
+ for idx, r in enumerate(c_t.get("restrictions", [])):
24
+ found = False
25
+ for sub_exp in and_exps:
26
+ if _verify_restriction(r, from_t, sub_exp):
27
+ found = True
28
+ break
29
+ if not found:
30
+ if from_t.alias:
31
+ t_prefix = f"{from_t.alias}."
32
+ elif len([t for t in from_tables if t.name == from_t.name]) > 1:
33
+ t_prefix = f"{from_t.name}."
34
+ else:
35
+ t_prefix = ""
36
+
37
+ context.add_error(
38
+ f"Missing restriction for table: {c_t['table_name']} column: {t_prefix}{r['column']} value: {r.get('values', r.get('value'))}",
39
+ True,
40
+ 0.5,
41
+ )
42
+ new_condition = _create_new_condition(context, r, t_prefix)
43
+ if where_clause is None:
44
+ where_clause = expr.Where(this=new_condition)
45
+ select_statement.set("where", where_clause)
46
+ else:
47
+ where_clause = where_clause.replace(
48
+ expr.Where(
49
+ this=expr.And(
50
+ this=expr.paren(where_clause.this),
51
+ expression=new_condition,
52
+ )
53
+ )
54
+ )
55
+
56
+
57
+ def _create_new_condition(
58
+ context: VerificationContext, restriction: dict, table_prefix: str
59
+ ) -> expr.Expression:
60
+ """
61
+ Used to create a restriction condition for a given restriction.
62
+
63
+ Args:
64
+ context: verification context
65
+ restriction: restriction to create condition for
66
+ table_prefix: table prefix to use in the condition
67
+
68
+ Returns: condition expression
69
+
70
+ """
71
+ if restriction.get("operation") == "BETWEEN":
72
+ operator = "BETWEEN"
73
+ operand = f"{_format_value(restriction["values"][0])} AND {_format_value(restriction["values"][1])}"
74
+ elif restriction.get("operation") == "IN":
75
+ operator = "IN"
76
+ values = restriction.get("values", [restriction.get("value")])
77
+ operand = f"({', '.join(map(str, values))})"
78
+ else:
79
+ operator = "="
80
+ operand = (
81
+ _format_value(restriction["value"])
82
+ if "value" in restriction
83
+ else str(restriction["values"])[1:-1]
84
+ )
85
+ new_condition = sqlglot.parse_one(
86
+ f"{table_prefix}{restriction['column']} {operator} {operand}",
87
+ dialect=context.dialect,
88
+ )
89
+ return new_condition
90
+
91
+
92
+ def _format_value(value):
93
+ if isinstance(value, str):
94
+ return f"'{value}'"
95
+ else:
96
+ return value
97
+
98
+
99
+ def _verify_restriction(
100
+ restriction: dict, from_table: expr.Table, exp: expr.Expression
101
+ ) -> bool:
102
+ """
103
+ Verifies if a given restriction is satisfied within an SQL expression.
104
+
105
+ Args:
106
+ restriction (dict): The restriction to verify, containing 'column' and 'value' keys.
107
+ from_table (Table): The table reference to check the restriction against.
108
+ exp (list): The SQL expression to check against the restriction.
109
+
110
+ Returns:
111
+ bool: True if the restriction is satisfied, False otherwise.
112
+ """
113
+ if isinstance(exp, expr.Not):
114
+ return False
115
+ if isinstance(exp, expr.Paren):
116
+ return _verify_restriction(restriction, from_table, exp.this)
117
+ if not isinstance(exp.this, expr.Column):
118
+ return False
119
+ if not exp.this.name == restriction["column"]:
120
+ return False
121
+ if exp.this.table and from_table.alias and exp.this.table != from_table.alias:
122
+ return False
123
+ if exp.this.table and not from_table.alias and exp.this.table != from_table.name:
124
+ return False
125
+ if isinstance(exp, expr.EQ) and isinstance(exp.right, expr.Condition):
126
+ if isinstance(exp.right, expr.Boolean):
127
+ return exp.right.this == restriction["value"]
128
+ else:
129
+ values = _get_restriction_values(restriction)
130
+ return exp.right.this in values
131
+ if isinstance(exp, expr.Between):
132
+ sql_values = [v.this for v in exp.expressions]
133
+ values = _get_restriction_values(restriction)
134
+ return any(v in values for v in sql_values)
135
+ if isinstance(exp, expr.In):
136
+ values = [v.this for v in exp.expressions]
137
+ return False
138
+ return False
139
+
140
+
141
+ def _get_restriction_values(restriction: dict) -> List[str]:
142
+ if "values" in restriction:
143
+ values = [str(v) for v in restriction["values"]]
144
+ else:
145
+ values = [str(restriction["value"])]
146
+ return values