pywebguard 1.0.0__tar.gz → 1.0.2__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.
- {pywebguard-1.0.0 → pywebguard-1.0.2}/PKG-INFO +34 -50
- {pywebguard-1.0.0 → pywebguard-1.0.2}/README.md +32 -2
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard.egg-info/PKG-INFO +34 -50
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard.egg-info/SOURCES.txt +1 -21
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard.egg-info/requires.txt +3 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/setup.py +1 -1
- pywebguard-1.0.0/tests/__init__.py +0 -1
- pywebguard-1.0.0/tests/conftest.py +0 -211
- pywebguard-1.0.0/tests/test_cli.py +0 -330
- pywebguard-1.0.0/tests/test_core.py +0 -304
- pywebguard-1.0.0/tests/test_filters.py +0 -260
- pywebguard-1.0.0/tests/test_frameworks.py +0 -130
- pywebguard-1.0.0/tests/test_frameworks_fastapi.py +0 -318
- pywebguard-1.0.0/tests/test_frameworks_flask.py +0 -179
- pywebguard-1.0.0/tests/test_limiters.py +0 -354
- pywebguard-1.0.0/tests/test_security.py +0 -330
- pywebguard-1.0.0/tests/test_storage.py +0 -206
- pywebguard-1.0.0/tests/test_storage_memory.py +0 -64
- pywebguard-1.0.0/tests/test_storage_mongodb.py +0 -265
- pywebguard-1.0.0/tests/test_storage_postgresql.py +0 -347
- pywebguard-1.0.0/tests/test_storage_redis.py +0 -145
- pywebguard-1.0.0/tests/test_storage_sqlite.py +0 -132
- pywebguard-1.0.0/tests/test_storage_tinydb.py +0 -110
- pywebguard-1.0.0/tests/test_user_agent.py +0 -182
- pywebguard-1.0.0/tests/test_utils_ip.py +0 -102
- pywebguard-1.0.0/tests/test_utils_request.py +0 -90
- {pywebguard-1.0.0 → pywebguard-1.0.2}/LICENSE +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/__init__.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/cli.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/core/__init__.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/core/base.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/core/config.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/filters/__init__.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/filters/base.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/filters/ip_filter.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/filters/user_agent.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/frameworks/__init__.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/frameworks/_fastapi.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/frameworks/_flask.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/limiters/__init__.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/limiters/base.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/limiters/rate_limit.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/logging/__init__.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/logging/backends/__init__.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/logging/backends/_elasticsearch.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/logging/backends/_meilisearch.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/logging/backends/_mongodb.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/logging/base.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/logging/logger.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/security/__init__.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/security/base.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/security/cors.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/security/penetration.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/storage/__init__.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/storage/_mongodb.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/storage/_postgresql.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/storage/_redis.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/storage/_sqlite.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/storage/_tinydb.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/storage/base.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/storage/memory.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/utils/__init__.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/utils/ip.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard/utils/request.py +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard.egg-info/dependency_links.txt +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard.egg-info/entry_points.txt +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/pywebguard.egg-info/top_level.txt +0 -0
- {pywebguard-1.0.0 → pywebguard-1.0.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pywebguard
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: A comprehensive security library for Python web applications
|
|
5
5
|
Home-page: https://github.com/py-daily/pywebguard
|
|
6
6
|
Author: py-daily
|
|
@@ -22,68 +22,52 @@ Classifier: Topic :: Internet :: WWW/HTTP
|
|
|
22
22
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
23
|
Requires-Python: >=3.8
|
|
24
24
|
Description-Content-Type: text/markdown
|
|
25
|
-
License-File: LICENSE
|
|
26
|
-
Requires-Dist: pydantic>=2.0.0
|
|
27
|
-
Requires-Dist: ipaddress>=1.0.23
|
|
28
|
-
Requires-Dist: python-dotenv>=0.19.0
|
|
29
25
|
Provides-Extra: fastapi
|
|
30
|
-
Requires-Dist: fastapi>=0.115.12; extra == "fastapi"
|
|
31
|
-
Requires-Dist: starlette>=0.14.0; extra == "fastapi"
|
|
32
|
-
Requires-Dist: uvicorn>=0.34.2; extra == "fastapi"
|
|
33
26
|
Provides-Extra: flask
|
|
34
|
-
Requires-Dist: flask>=3.1.1; extra == "flask"
|
|
35
27
|
Provides-Extra: redis
|
|
36
|
-
Requires-Dist: redis>=6.1.0; extra == "redis"
|
|
37
|
-
Requires-Dist: aioredis>=2.0.1; extra == "redis"
|
|
38
28
|
Provides-Extra: sqlite
|
|
39
|
-
Requires-Dist: aiosqlite>=0.21.0; extra == "sqlite"
|
|
40
29
|
Provides-Extra: tinydb
|
|
41
|
-
Requires-Dist: tinydb>=4.8.2; extra == "tinydb"
|
|
42
30
|
Provides-Extra: mongodb
|
|
43
|
-
Requires-Dist: pymongo>=4.13.0; extra == "mongodb"
|
|
44
31
|
Provides-Extra: postgresql
|
|
45
|
-
Requires-Dist: asyncpg>=0.30.0; extra == "postgresql"
|
|
46
|
-
Requires-Dist: psycopg2-binary>=2.9.10; extra == "postgresql"
|
|
47
32
|
Provides-Extra: elasticsearch
|
|
48
|
-
Requires-Dist: elasticsearch>=9.0.1; extra == "elasticsearch"
|
|
49
33
|
Provides-Extra: all-storage
|
|
50
|
-
Requires-Dist: redis>=6.1.0; extra == "all-storage"
|
|
51
|
-
Requires-Dist: aioredis>=2.0.1; extra == "all-storage"
|
|
52
|
-
Requires-Dist: aiosqlite>=0.21.0; extra == "all-storage"
|
|
53
|
-
Requires-Dist: tinydb>=4.8.2; extra == "all-storage"
|
|
54
|
-
Requires-Dist: pymongo>=4.13.0; extra == "all-storage"
|
|
55
|
-
Requires-Dist: asyncpg>=0.30.0; extra == "all-storage"
|
|
56
|
-
Requires-Dist: psycopg2-binary>=2.9.10; extra == "all-storage"
|
|
57
|
-
Requires-Dist: elasticsearch>=9.0.1; extra == "all-storage"
|
|
58
34
|
Provides-Extra: all-frameworks
|
|
59
|
-
Requires-Dist: fastapi>=0.115.12; extra == "all-frameworks"
|
|
60
|
-
Requires-Dist: starlette>=0.14.0; extra == "all-frameworks"
|
|
61
|
-
Requires-Dist: uvicorn>=0.34.2; extra == "all-frameworks"
|
|
62
|
-
Requires-Dist: flask>=3.1.1; extra == "all-frameworks"
|
|
63
35
|
Provides-Extra: all
|
|
64
|
-
Requires-Dist: fastapi>=0.115.12; extra == "all"
|
|
65
|
-
Requires-Dist: starlette>=0.14.0; extra == "all"
|
|
66
|
-
Requires-Dist: uvicorn>=0.34.2; extra == "all"
|
|
67
|
-
Requires-Dist: flask>=3.1.1; extra == "all"
|
|
68
|
-
Requires-Dist: redis>=6.1.0; extra == "all"
|
|
69
|
-
Requires-Dist: aioredis>=2.0.1; extra == "all"
|
|
70
|
-
Requires-Dist: aiosqlite>=0.21.0; extra == "all"
|
|
71
|
-
Requires-Dist: tinydb>=4.8.2; extra == "all"
|
|
72
|
-
Requires-Dist: pymongo>=4.13.0; extra == "all"
|
|
73
|
-
Requires-Dist: asyncpg>=0.30.0; extra == "all"
|
|
74
|
-
Requires-Dist: psycopg2-binary>=2.9.10; extra == "all"
|
|
75
|
-
Requires-Dist: elasticsearch>=9.0.1; extra == "all"
|
|
76
36
|
Provides-Extra: dev
|
|
77
|
-
|
|
78
|
-
Requires-Dist: pytest-cov>=6.1.1; extra == "dev"
|
|
79
|
-
Requires-Dist: pytest-asyncio>=0.26.0; extra == "dev"
|
|
80
|
-
Requires-Dist: black>=25.1.0; extra == "dev"
|
|
81
|
-
Requires-Dist: isort>=6.0.1; extra == "dev"
|
|
82
|
-
Requires-Dist: mypy>=1.15.0; extra == "dev"
|
|
83
|
-
Requires-Dist: flake8>=7.2.0; extra == "dev"
|
|
37
|
+
License-File: LICENSE
|
|
84
38
|
|
|
85
39
|
|
|
40
|
+
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
|
41
|
+
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
|
42
|
+
**Table of Contents** *generated with [DocToc](https://github.com/ktechhub/doctoc)*
|
|
43
|
+
|
|
44
|
+
<!---toc start-->
|
|
45
|
+
|
|
46
|
+
* [PyWebGuard](#pywebguard)
|
|
47
|
+
* [Key Features](#key-features)
|
|
48
|
+
* [Quick Start](#quick-start)
|
|
49
|
+
* [Define route-specific rate limits](#define-route-specific-rate-limits)
|
|
50
|
+
* [Initialize storage (async in-memory for this example)](#initialize-storage-async-in-memory-for-this-example)
|
|
51
|
+
* [Uncomment to use Redis storage instead or import any of the storage supports](#uncomment-to-use-redis-storage-instead-or-import-any-of-the-storage-supports)
|
|
52
|
+
* [storage = AsyncRedisStorage(url="redis://localhost:6379")](#storage--asyncredisstorageurlredislocalhost6379)
|
|
53
|
+
* [Add PyWebGuard middleware](#add-pywebguard-middleware)
|
|
54
|
+
* [Example of a path that might trigger penetration detection](#example-of-a-path-that-might-trigger-penetration-detection)
|
|
55
|
+
* [Helper endpoint to check remaining rate limits](#helper-endpoint-to-check-remaining-rate-limits)
|
|
56
|
+
* [Endpoint to check if an IP is banned](#endpoint-to-check-if-an-ip-is-banned)
|
|
57
|
+
* [Admin endpoint to get metrics](#admin-endpoint-to-get-metrics)
|
|
58
|
+
* [Admin endpoint to ban an IP](#admin-endpoint-to-ban-an-ip)
|
|
59
|
+
* [Admin endpoint to unban an IP](#admin-endpoint-to-unban-an-ip)
|
|
60
|
+
* [Documentation](#documentation)
|
|
61
|
+
* [Contributors](#contributors)
|
|
62
|
+
* [License](#license)
|
|
63
|
+
|
|
64
|
+
<!---toc end-->
|
|
65
|
+
|
|
66
|
+
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
|
86
67
|
# PyWebGuard
|
|
68
|
+
[](https://badge.fury.io/py/pywebguard)
|
|
69
|
+
[](https://pypi.org/project/pywebguard/)
|
|
70
|
+
[](https://github.com/py-daily/pywebguard/blob/main/LICENSE)
|
|
87
71
|
|
|
88
72
|
A comprehensive security library for Python web applications, providing middleware for IP filtering, rate limiting, and other security features with both synchronous and asynchronous support.
|
|
89
73
|
|
|
@@ -412,7 +396,6 @@ if __name__ == "__main__":
|
|
|
412
396
|
|
|
413
397
|
|
|
414
398
|
## Documentation
|
|
415
|
-
|
|
416
399
|
- [Installation Guide](docs/installation.md)
|
|
417
400
|
- [CLI Usage](docs/cli.md)
|
|
418
401
|
- [Core Features](docs/core/)
|
|
@@ -422,9 +405,10 @@ if __name__ == "__main__":
|
|
|
422
405
|
## Contributors
|
|
423
406
|
|
|
424
407
|
<a href="https://github.com/py-daily/pywebguard/graphs/contributors">
|
|
425
|
-
<img src="https://contrib.rocks/image?repo=py-daily/pywebguard" width="
|
|
408
|
+
<img src="https://contrib.rocks/image?repo=py-daily/pywebguard" width="80" />
|
|
426
409
|
</a>
|
|
427
410
|
|
|
411
|
+
|
|
428
412
|
## License
|
|
429
413
|
|
|
430
414
|
This project is licensed under the [MIT License](LICENSE).
|
|
@@ -1,4 +1,34 @@
|
|
|
1
|
+
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
|
2
|
+
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
|
3
|
+
**Table of Contents** *generated with [DocToc](https://github.com/ktechhub/doctoc)*
|
|
4
|
+
|
|
5
|
+
<!---toc start-->
|
|
6
|
+
|
|
7
|
+
* [PyWebGuard](#pywebguard)
|
|
8
|
+
* [Key Features](#key-features)
|
|
9
|
+
* [Quick Start](#quick-start)
|
|
10
|
+
* [Define route-specific rate limits](#define-route-specific-rate-limits)
|
|
11
|
+
* [Initialize storage (async in-memory for this example)](#initialize-storage-async-in-memory-for-this-example)
|
|
12
|
+
* [Uncomment to use Redis storage instead or import any of the storage supports](#uncomment-to-use-redis-storage-instead-or-import-any-of-the-storage-supports)
|
|
13
|
+
* [storage = AsyncRedisStorage(url="redis://localhost:6379")](#storage--asyncredisstorageurlredislocalhost6379)
|
|
14
|
+
* [Add PyWebGuard middleware](#add-pywebguard-middleware)
|
|
15
|
+
* [Example of a path that might trigger penetration detection](#example-of-a-path-that-might-trigger-penetration-detection)
|
|
16
|
+
* [Helper endpoint to check remaining rate limits](#helper-endpoint-to-check-remaining-rate-limits)
|
|
17
|
+
* [Endpoint to check if an IP is banned](#endpoint-to-check-if-an-ip-is-banned)
|
|
18
|
+
* [Admin endpoint to get metrics](#admin-endpoint-to-get-metrics)
|
|
19
|
+
* [Admin endpoint to ban an IP](#admin-endpoint-to-ban-an-ip)
|
|
20
|
+
* [Admin endpoint to unban an IP](#admin-endpoint-to-unban-an-ip)
|
|
21
|
+
* [Documentation](#documentation)
|
|
22
|
+
* [Contributors](#contributors)
|
|
23
|
+
* [License](#license)
|
|
24
|
+
|
|
25
|
+
<!---toc end-->
|
|
26
|
+
|
|
27
|
+
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
|
1
28
|
# PyWebGuard
|
|
29
|
+
[](https://badge.fury.io/py/pywebguard)
|
|
30
|
+
[](https://pypi.org/project/pywebguard/)
|
|
31
|
+
[](https://github.com/py-daily/pywebguard/blob/main/LICENSE)
|
|
2
32
|
|
|
3
33
|
A comprehensive security library for Python web applications, providing middleware for IP filtering, rate limiting, and other security features with both synchronous and asynchronous support.
|
|
4
34
|
|
|
@@ -327,7 +357,6 @@ if __name__ == "__main__":
|
|
|
327
357
|
|
|
328
358
|
|
|
329
359
|
## Documentation
|
|
330
|
-
|
|
331
360
|
- [Installation Guide](docs/installation.md)
|
|
332
361
|
- [CLI Usage](docs/cli.md)
|
|
333
362
|
- [Core Features](docs/core/)
|
|
@@ -337,9 +366,10 @@ if __name__ == "__main__":
|
|
|
337
366
|
## Contributors
|
|
338
367
|
|
|
339
368
|
<a href="https://github.com/py-daily/pywebguard/graphs/contributors">
|
|
340
|
-
<img src="https://contrib.rocks/image?repo=py-daily/pywebguard" width="
|
|
369
|
+
<img src="https://contrib.rocks/image?repo=py-daily/pywebguard" width="80" />
|
|
341
370
|
</a>
|
|
342
371
|
|
|
372
|
+
|
|
343
373
|
## License
|
|
344
374
|
|
|
345
375
|
This project is licensed under the [MIT License](LICENSE).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pywebguard
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: A comprehensive security library for Python web applications
|
|
5
5
|
Home-page: https://github.com/py-daily/pywebguard
|
|
6
6
|
Author: py-daily
|
|
@@ -22,68 +22,52 @@ Classifier: Topic :: Internet :: WWW/HTTP
|
|
|
22
22
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
23
|
Requires-Python: >=3.8
|
|
24
24
|
Description-Content-Type: text/markdown
|
|
25
|
-
License-File: LICENSE
|
|
26
|
-
Requires-Dist: pydantic>=2.0.0
|
|
27
|
-
Requires-Dist: ipaddress>=1.0.23
|
|
28
|
-
Requires-Dist: python-dotenv>=0.19.0
|
|
29
25
|
Provides-Extra: fastapi
|
|
30
|
-
Requires-Dist: fastapi>=0.115.12; extra == "fastapi"
|
|
31
|
-
Requires-Dist: starlette>=0.14.0; extra == "fastapi"
|
|
32
|
-
Requires-Dist: uvicorn>=0.34.2; extra == "fastapi"
|
|
33
26
|
Provides-Extra: flask
|
|
34
|
-
Requires-Dist: flask>=3.1.1; extra == "flask"
|
|
35
27
|
Provides-Extra: redis
|
|
36
|
-
Requires-Dist: redis>=6.1.0; extra == "redis"
|
|
37
|
-
Requires-Dist: aioredis>=2.0.1; extra == "redis"
|
|
38
28
|
Provides-Extra: sqlite
|
|
39
|
-
Requires-Dist: aiosqlite>=0.21.0; extra == "sqlite"
|
|
40
29
|
Provides-Extra: tinydb
|
|
41
|
-
Requires-Dist: tinydb>=4.8.2; extra == "tinydb"
|
|
42
30
|
Provides-Extra: mongodb
|
|
43
|
-
Requires-Dist: pymongo>=4.13.0; extra == "mongodb"
|
|
44
31
|
Provides-Extra: postgresql
|
|
45
|
-
Requires-Dist: asyncpg>=0.30.0; extra == "postgresql"
|
|
46
|
-
Requires-Dist: psycopg2-binary>=2.9.10; extra == "postgresql"
|
|
47
32
|
Provides-Extra: elasticsearch
|
|
48
|
-
Requires-Dist: elasticsearch>=9.0.1; extra == "elasticsearch"
|
|
49
33
|
Provides-Extra: all-storage
|
|
50
|
-
Requires-Dist: redis>=6.1.0; extra == "all-storage"
|
|
51
|
-
Requires-Dist: aioredis>=2.0.1; extra == "all-storage"
|
|
52
|
-
Requires-Dist: aiosqlite>=0.21.0; extra == "all-storage"
|
|
53
|
-
Requires-Dist: tinydb>=4.8.2; extra == "all-storage"
|
|
54
|
-
Requires-Dist: pymongo>=4.13.0; extra == "all-storage"
|
|
55
|
-
Requires-Dist: asyncpg>=0.30.0; extra == "all-storage"
|
|
56
|
-
Requires-Dist: psycopg2-binary>=2.9.10; extra == "all-storage"
|
|
57
|
-
Requires-Dist: elasticsearch>=9.0.1; extra == "all-storage"
|
|
58
34
|
Provides-Extra: all-frameworks
|
|
59
|
-
Requires-Dist: fastapi>=0.115.12; extra == "all-frameworks"
|
|
60
|
-
Requires-Dist: starlette>=0.14.0; extra == "all-frameworks"
|
|
61
|
-
Requires-Dist: uvicorn>=0.34.2; extra == "all-frameworks"
|
|
62
|
-
Requires-Dist: flask>=3.1.1; extra == "all-frameworks"
|
|
63
35
|
Provides-Extra: all
|
|
64
|
-
Requires-Dist: fastapi>=0.115.12; extra == "all"
|
|
65
|
-
Requires-Dist: starlette>=0.14.0; extra == "all"
|
|
66
|
-
Requires-Dist: uvicorn>=0.34.2; extra == "all"
|
|
67
|
-
Requires-Dist: flask>=3.1.1; extra == "all"
|
|
68
|
-
Requires-Dist: redis>=6.1.0; extra == "all"
|
|
69
|
-
Requires-Dist: aioredis>=2.0.1; extra == "all"
|
|
70
|
-
Requires-Dist: aiosqlite>=0.21.0; extra == "all"
|
|
71
|
-
Requires-Dist: tinydb>=4.8.2; extra == "all"
|
|
72
|
-
Requires-Dist: pymongo>=4.13.0; extra == "all"
|
|
73
|
-
Requires-Dist: asyncpg>=0.30.0; extra == "all"
|
|
74
|
-
Requires-Dist: psycopg2-binary>=2.9.10; extra == "all"
|
|
75
|
-
Requires-Dist: elasticsearch>=9.0.1; extra == "all"
|
|
76
36
|
Provides-Extra: dev
|
|
77
|
-
|
|
78
|
-
Requires-Dist: pytest-cov>=6.1.1; extra == "dev"
|
|
79
|
-
Requires-Dist: pytest-asyncio>=0.26.0; extra == "dev"
|
|
80
|
-
Requires-Dist: black>=25.1.0; extra == "dev"
|
|
81
|
-
Requires-Dist: isort>=6.0.1; extra == "dev"
|
|
82
|
-
Requires-Dist: mypy>=1.15.0; extra == "dev"
|
|
83
|
-
Requires-Dist: flake8>=7.2.0; extra == "dev"
|
|
37
|
+
License-File: LICENSE
|
|
84
38
|
|
|
85
39
|
|
|
40
|
+
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
|
41
|
+
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
|
42
|
+
**Table of Contents** *generated with [DocToc](https://github.com/ktechhub/doctoc)*
|
|
43
|
+
|
|
44
|
+
<!---toc start-->
|
|
45
|
+
|
|
46
|
+
* [PyWebGuard](#pywebguard)
|
|
47
|
+
* [Key Features](#key-features)
|
|
48
|
+
* [Quick Start](#quick-start)
|
|
49
|
+
* [Define route-specific rate limits](#define-route-specific-rate-limits)
|
|
50
|
+
* [Initialize storage (async in-memory for this example)](#initialize-storage-async-in-memory-for-this-example)
|
|
51
|
+
* [Uncomment to use Redis storage instead or import any of the storage supports](#uncomment-to-use-redis-storage-instead-or-import-any-of-the-storage-supports)
|
|
52
|
+
* [storage = AsyncRedisStorage(url="redis://localhost:6379")](#storage--asyncredisstorageurlredislocalhost6379)
|
|
53
|
+
* [Add PyWebGuard middleware](#add-pywebguard-middleware)
|
|
54
|
+
* [Example of a path that might trigger penetration detection](#example-of-a-path-that-might-trigger-penetration-detection)
|
|
55
|
+
* [Helper endpoint to check remaining rate limits](#helper-endpoint-to-check-remaining-rate-limits)
|
|
56
|
+
* [Endpoint to check if an IP is banned](#endpoint-to-check-if-an-ip-is-banned)
|
|
57
|
+
* [Admin endpoint to get metrics](#admin-endpoint-to-get-metrics)
|
|
58
|
+
* [Admin endpoint to ban an IP](#admin-endpoint-to-ban-an-ip)
|
|
59
|
+
* [Admin endpoint to unban an IP](#admin-endpoint-to-unban-an-ip)
|
|
60
|
+
* [Documentation](#documentation)
|
|
61
|
+
* [Contributors](#contributors)
|
|
62
|
+
* [License](#license)
|
|
63
|
+
|
|
64
|
+
<!---toc end-->
|
|
65
|
+
|
|
66
|
+
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
|
86
67
|
# PyWebGuard
|
|
68
|
+
[](https://badge.fury.io/py/pywebguard)
|
|
69
|
+
[](https://pypi.org/project/pywebguard/)
|
|
70
|
+
[](https://github.com/py-daily/pywebguard/blob/main/LICENSE)
|
|
87
71
|
|
|
88
72
|
A comprehensive security library for Python web applications, providing middleware for IP filtering, rate limiting, and other security features with both synchronous and asynchronous support.
|
|
89
73
|
|
|
@@ -412,7 +396,6 @@ if __name__ == "__main__":
|
|
|
412
396
|
|
|
413
397
|
|
|
414
398
|
## Documentation
|
|
415
|
-
|
|
416
399
|
- [Installation Guide](docs/installation.md)
|
|
417
400
|
- [CLI Usage](docs/cli.md)
|
|
418
401
|
- [Core Features](docs/core/)
|
|
@@ -422,9 +405,10 @@ if __name__ == "__main__":
|
|
|
422
405
|
## Contributors
|
|
423
406
|
|
|
424
407
|
<a href="https://github.com/py-daily/pywebguard/graphs/contributors">
|
|
425
|
-
<img src="https://contrib.rocks/image?repo=py-daily/pywebguard" width="
|
|
408
|
+
<img src="https://contrib.rocks/image?repo=py-daily/pywebguard" width="80" />
|
|
426
409
|
</a>
|
|
427
410
|
|
|
411
|
+
|
|
428
412
|
## License
|
|
429
413
|
|
|
430
414
|
This project is licensed under the [MIT License](LICENSE).
|
|
@@ -43,24 +43,4 @@ pywebguard/storage/base.py
|
|
|
43
43
|
pywebguard/storage/memory.py
|
|
44
44
|
pywebguard/utils/__init__.py
|
|
45
45
|
pywebguard/utils/ip.py
|
|
46
|
-
pywebguard/utils/request.py
|
|
47
|
-
tests/__init__.py
|
|
48
|
-
tests/conftest.py
|
|
49
|
-
tests/test_cli.py
|
|
50
|
-
tests/test_core.py
|
|
51
|
-
tests/test_filters.py
|
|
52
|
-
tests/test_frameworks.py
|
|
53
|
-
tests/test_frameworks_fastapi.py
|
|
54
|
-
tests/test_frameworks_flask.py
|
|
55
|
-
tests/test_limiters.py
|
|
56
|
-
tests/test_security.py
|
|
57
|
-
tests/test_storage.py
|
|
58
|
-
tests/test_storage_memory.py
|
|
59
|
-
tests/test_storage_mongodb.py
|
|
60
|
-
tests/test_storage_postgresql.py
|
|
61
|
-
tests/test_storage_redis.py
|
|
62
|
-
tests/test_storage_sqlite.py
|
|
63
|
-
tests/test_storage_tinydb.py
|
|
64
|
-
tests/test_user_agent.py
|
|
65
|
-
tests/test_utils_ip.py
|
|
66
|
-
tests/test_utils_request.py
|
|
46
|
+
pywebguard/utils/request.py
|
|
@@ -4,6 +4,7 @@ python-dotenv>=0.19.0
|
|
|
4
4
|
|
|
5
5
|
[all]
|
|
6
6
|
fastapi>=0.115.12
|
|
7
|
+
httpx>=0.28.1
|
|
7
8
|
starlette>=0.14.0
|
|
8
9
|
uvicorn>=0.34.2
|
|
9
10
|
flask>=3.1.1
|
|
@@ -18,6 +19,7 @@ elasticsearch>=9.0.1
|
|
|
18
19
|
|
|
19
20
|
[all-frameworks]
|
|
20
21
|
fastapi>=0.115.12
|
|
22
|
+
httpx>=0.28.1
|
|
21
23
|
starlette>=0.14.0
|
|
22
24
|
uvicorn>=0.34.2
|
|
23
25
|
flask>=3.1.1
|
|
@@ -46,6 +48,7 @@ elasticsearch>=9.0.1
|
|
|
46
48
|
|
|
47
49
|
[fastapi]
|
|
48
50
|
fastapi>=0.115.12
|
|
51
|
+
httpx>=0.28.1
|
|
49
52
|
starlette>=0.14.0
|
|
50
53
|
uvicorn>=0.34.2
|
|
51
54
|
|
|
@@ -13,7 +13,7 @@ with io.open(path.join(here, "README.md"), encoding="utf-8") as f:
|
|
|
13
13
|
long_description = "\n" + f.read()
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
fastapi = ["fastapi>=0.115.12", "starlette>=0.14.0", "uvicorn>=0.34.2"]
|
|
16
|
+
fastapi = ["fastapi>=0.115.12", "httpx>=0.28.1", "starlette>=0.14.0", "uvicorn>=0.34.2"]
|
|
17
17
|
flask = ["flask>=3.1.1"]
|
|
18
18
|
# Storage backends
|
|
19
19
|
redis = ["redis>=6.1.0", "aioredis>=2.0.1"]
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"""Test package for PyWebGuard."""
|
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
"""Common test fixtures for PyWebGuard tests."""
|
|
2
|
-
|
|
3
|
-
import pytest
|
|
4
|
-
import time
|
|
5
|
-
from typing import Dict, Any, Optional, Generator, Callable
|
|
6
|
-
|
|
7
|
-
from pywebguard.core.config import (
|
|
8
|
-
GuardConfig,
|
|
9
|
-
IPFilterConfig,
|
|
10
|
-
RateLimitConfig,
|
|
11
|
-
UserAgentConfig,
|
|
12
|
-
)
|
|
13
|
-
from pywebguard.core.base import Guard, AsyncGuard
|
|
14
|
-
from pywebguard.storage.memory import MemoryStorage, AsyncMemoryStorage
|
|
15
|
-
from pywebguard.storage.base import BaseStorage, AsyncBaseStorage
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
# Mock request and response classes for testing
|
|
19
|
-
class MockRequest:
|
|
20
|
-
"""Mock request object for testing."""
|
|
21
|
-
|
|
22
|
-
def __init__(
|
|
23
|
-
self,
|
|
24
|
-
remote_addr: str = "127.0.0.1",
|
|
25
|
-
user_agent: str = "Mozilla/5.0",
|
|
26
|
-
method: str = "GET",
|
|
27
|
-
path: str = "/",
|
|
28
|
-
query_string: str = "",
|
|
29
|
-
headers: Optional[Dict[str, str]] = None,
|
|
30
|
-
):
|
|
31
|
-
self.remote_addr = remote_addr
|
|
32
|
-
self.user_agent = user_agent
|
|
33
|
-
self.method = method
|
|
34
|
-
self.path = path
|
|
35
|
-
self.query_string = query_string
|
|
36
|
-
self.headers = headers or {}
|
|
37
|
-
|
|
38
|
-
# For FastAPI compatibility
|
|
39
|
-
class Client:
|
|
40
|
-
def __init__(self, host: str):
|
|
41
|
-
self.host = host
|
|
42
|
-
|
|
43
|
-
self.client = Client(remote_addr)
|
|
44
|
-
|
|
45
|
-
# For URL object in FastAPI
|
|
46
|
-
class URL:
|
|
47
|
-
def __init__(self, path: str):
|
|
48
|
-
self.path = path
|
|
49
|
-
|
|
50
|
-
self.url = URL(path)
|
|
51
|
-
|
|
52
|
-
# For query params in FastAPI
|
|
53
|
-
self.query_params = {}
|
|
54
|
-
if query_string:
|
|
55
|
-
for param in query_string.split("&"):
|
|
56
|
-
if "=" in param:
|
|
57
|
-
key, value = param.split("=", 1)
|
|
58
|
-
self.query_params[key] = value
|
|
59
|
-
else:
|
|
60
|
-
self.query_params[param] = ""
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
class MockResponse:
|
|
64
|
-
"""Mock response object for testing."""
|
|
65
|
-
|
|
66
|
-
def __init__(
|
|
67
|
-
self,
|
|
68
|
-
status_code: int = 200,
|
|
69
|
-
headers: Optional[Dict[str, str]] = None,
|
|
70
|
-
):
|
|
71
|
-
self.status_code = status_code
|
|
72
|
-
self.headers = headers or {}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
@pytest.fixture
|
|
76
|
-
def basic_config() -> GuardConfig:
|
|
77
|
-
"""Create a basic GuardConfig for testing."""
|
|
78
|
-
return GuardConfig(
|
|
79
|
-
ip_filter=IPFilterConfig(
|
|
80
|
-
enabled=True,
|
|
81
|
-
whitelist=["127.0.0.1"],
|
|
82
|
-
blacklist=["10.0.0.1"],
|
|
83
|
-
),
|
|
84
|
-
rate_limit=RateLimitConfig(
|
|
85
|
-
enabled=True,
|
|
86
|
-
requests_per_minute=60,
|
|
87
|
-
burst_size=10,
|
|
88
|
-
),
|
|
89
|
-
user_agent=UserAgentConfig(
|
|
90
|
-
enabled=True,
|
|
91
|
-
allowed_agents=["Werkzeug/3.1.3"],
|
|
92
|
-
),
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
@pytest.fixture
|
|
97
|
-
def memory_storage() -> MemoryStorage:
|
|
98
|
-
"""Create a memory storage instance for testing."""
|
|
99
|
-
return MemoryStorage()
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
@pytest.fixture
|
|
103
|
-
def async_memory_storage() -> AsyncMemoryStorage:
|
|
104
|
-
"""Create an async memory storage instance for testing."""
|
|
105
|
-
return AsyncMemoryStorage()
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
@pytest.fixture
|
|
109
|
-
def guard(basic_config: GuardConfig, memory_storage: MemoryStorage) -> Guard:
|
|
110
|
-
"""Create a Guard instance with test configuration."""
|
|
111
|
-
return Guard(config=basic_config, storage=memory_storage)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
@pytest.fixture
|
|
115
|
-
def async_guard(
|
|
116
|
-
basic_config: GuardConfig, async_memory_storage: AsyncMemoryStorage
|
|
117
|
-
) -> AsyncGuard:
|
|
118
|
-
"""Create an AsyncGuard instance with test configuration."""
|
|
119
|
-
return AsyncGuard(config=basic_config, storage=async_memory_storage)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
@pytest.fixture
|
|
123
|
-
def mock_request() -> MockRequest:
|
|
124
|
-
"""Create a mock request for testing."""
|
|
125
|
-
return MockRequest()
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
@pytest.fixture
|
|
129
|
-
def mock_response() -> MockResponse:
|
|
130
|
-
"""Create a mock response for testing."""
|
|
131
|
-
return MockResponse()
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
@pytest.fixture
|
|
135
|
-
def mock_blocked_ip_request() -> MockRequest:
|
|
136
|
-
"""Create a mock request with a blocked IP."""
|
|
137
|
-
return MockRequest(remote_addr="10.0.0.1")
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
@pytest.fixture
|
|
141
|
-
def mock_blocked_ua_request() -> MockRequest:
|
|
142
|
-
"""Create a mock request with a blocked user agent."""
|
|
143
|
-
return MockRequest(user_agent="curl/7.64.1")
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
@pytest.fixture
|
|
147
|
-
def rate_limited_guard(
|
|
148
|
-
basic_config: GuardConfig, memory_storage: MemoryStorage
|
|
149
|
-
) -> Guard:
|
|
150
|
-
"""Create a Guard instance with a very low rate limit for testing."""
|
|
151
|
-
config = GuardConfig(
|
|
152
|
-
ip_filter=basic_config.ip_filter,
|
|
153
|
-
rate_limit=RateLimitConfig(
|
|
154
|
-
enabled=True,
|
|
155
|
-
requests_per_minute=1, # Very low rate limit for testing
|
|
156
|
-
burst_size=0, # Strict: no burst allowed
|
|
157
|
-
),
|
|
158
|
-
)
|
|
159
|
-
return Guard(config=config, storage=memory_storage)
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
@pytest.fixture
|
|
163
|
-
def async_rate_limited_guard(
|
|
164
|
-
basic_config: GuardConfig, async_memory_storage: AsyncMemoryStorage
|
|
165
|
-
) -> AsyncGuard:
|
|
166
|
-
"""Create an AsyncGuard instance with a very low rate limit for testing."""
|
|
167
|
-
config = GuardConfig(
|
|
168
|
-
ip_filter=basic_config.ip_filter,
|
|
169
|
-
rate_limit=RateLimitConfig(
|
|
170
|
-
enabled=True,
|
|
171
|
-
requests_per_minute=1, # Very low rate limit for testing
|
|
172
|
-
burst_size=0, # Strict: no burst allowed
|
|
173
|
-
),
|
|
174
|
-
)
|
|
175
|
-
return AsyncGuard(config=config, storage=async_memory_storage)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
@pytest.fixture
|
|
179
|
-
def route_rate_limited_guard(
|
|
180
|
-
basic_config: GuardConfig, memory_storage: MemoryStorage
|
|
181
|
-
) -> Guard:
|
|
182
|
-
"""Create a Guard instance with route-specific rate limits for testing."""
|
|
183
|
-
guard = Guard(config=basic_config, storage=memory_storage)
|
|
184
|
-
|
|
185
|
-
# Add route-specific rate limits
|
|
186
|
-
guard.add_route_rate_limit(
|
|
187
|
-
"/api/limited",
|
|
188
|
-
{
|
|
189
|
-
"requests_per_minute": 1,
|
|
190
|
-
"burst_size": 0, # Strict: no burst allowed
|
|
191
|
-
},
|
|
192
|
-
)
|
|
193
|
-
return guard
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
@pytest.fixture
|
|
197
|
-
def async_route_rate_limited_guard(
|
|
198
|
-
basic_config: GuardConfig, async_memory_storage: AsyncMemoryStorage
|
|
199
|
-
) -> AsyncGuard:
|
|
200
|
-
"""Create an AsyncGuard instance with route-specific rate limits for testing."""
|
|
201
|
-
guard = AsyncGuard(config=basic_config, storage=async_memory_storage)
|
|
202
|
-
|
|
203
|
-
# Add route-specific rate limits
|
|
204
|
-
guard.add_route_rate_limit(
|
|
205
|
-
"/api/limited",
|
|
206
|
-
{
|
|
207
|
-
"requests_per_minute": 1,
|
|
208
|
-
"burst_size": 0, # Strict: no burst allowed
|
|
209
|
-
},
|
|
210
|
-
)
|
|
211
|
-
return guard
|