diff --git a/.github/workflows/api-build-lint-push-containers.yml b/.github/workflows/api-build-lint-push-containers.yml
index 1a463ff7bc..62db7e4172 100644
--- a/.github/workflows/api-build-lint-push-containers.yml
+++ b/.github/workflows/api-build-lint-push-containers.yml
@@ -63,6 +63,12 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
+ - name: Set short git commit SHA
+ id: vars
+ run: |
+ shortSha=$(git rev-parse --short ${{ github.sha }})
+ echo "SHORT_SHA=${shortSha}" >> $GITHUB_ENV
+
- name: Login to DockerHub
uses: docker/login-action@v3
with:
@@ -82,6 +88,7 @@ jobs:
push: true
tags: |
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ env.LATEST_TAG }}
+ ${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ env.SHORT_SHA }}
cache-from: type=gha
cache-to: type=gha,mode=max
diff --git a/.github/workflows/find-secrets.yml b/.github/workflows/find-secrets.yml
index acf5f33217..6c6a634d09 100644
--- a/.github/workflows/find-secrets.yml
+++ b/.github/workflows/find-secrets.yml
@@ -11,7 +11,7 @@ jobs:
with:
fetch-depth: 0
- name: TruffleHog OSS
- uses: trufflesecurity/trufflehog@v3.88.6
+ uses: trufflesecurity/trufflehog@v3.88.9
with:
path: ./
base: ${{ github.event.repository.default_branch }}
diff --git a/.github/workflows/ui-build-lint-push-containers.yml b/.github/workflows/ui-build-lint-push-containers.yml
index a34c2379e6..31a66012db 100644
--- a/.github/workflows/ui-build-lint-push-containers.yml
+++ b/.github/workflows/ui-build-lint-push-containers.yml
@@ -63,6 +63,12 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
+ - name: Set short git commit SHA
+ id: vars
+ run: |
+ shortSha=$(git rev-parse --short ${{ github.sha }})
+ echo "SHORT_SHA=${shortSha}" >> $GITHUB_ENV
+
- name: Login to DockerHub
uses: docker/login-action@v3
with:
@@ -82,6 +88,7 @@ jobs:
push: true
tags: |
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ env.LATEST_TAG }}
+ ${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ env.SHORT_SHA }}
cache-from: type=gha
cache-to: type=gha,mode=max
diff --git a/api/.env.example b/api/.env.example
index 4b0894b3ba..192317fa02 100644
--- a/api/.env.example
+++ b/api/.env.example
@@ -39,3 +39,12 @@ POSTGRES_DB=prowler_db
VALKEY_HOST=[localhost|valkey]
VALKEY_PORT=6379
VALKEY_DB=0
+
+# Social login credentials
+DJANGO_GOOGLE_OAUTH_CLIENT_ID=""
+DJANGO_GOOGLE_OAUTH_CLIENT_SECRET=""
+DJANGO_GOOGLE_OAUTH_CALLBACK_URL=""
+
+DJANGO_GITHUB_OAUTH_CLIENT_ID=""
+DJANGO_GITHUB_OAUTH_CLIENT_SECRET=""
+DJANGO_GITHUB_OAUTH_CALLBACK_URL=""
diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md
index 2a18f822a0..d50142c134 100644
--- a/api/CHANGELOG.md
+++ b/api/CHANGELOG.md
@@ -4,8 +4,10 @@ All notable changes to the **Prowler API** are documented in this file.
---
-## [Unreleased]
+## [v1.5.0] (Prowler UNRELEASED)
+### Added
+- Social login integration with Google and GitHub [(#6906)](https://github.com/prowler-cloud/prowler/pull/6906)
---
diff --git a/api/poetry.lock b/api/poetry.lock
index 491b06933a..c05ae59d8c 100644
--- a/api/poetry.lock
+++ b/api/poetry.lock
@@ -13,98 +13,103 @@ files = [
[[package]]
name = "aiohappyeyeballs"
-version = "2.4.4"
+version = "2.4.6"
description = "Happy Eyeballs for asyncio"
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
files = [
- {file = "aiohappyeyeballs-2.4.4-py3-none-any.whl", hash = "sha256:a980909d50efcd44795c4afeca523296716d50cd756ddca6af8c65b996e27de8"},
- {file = "aiohappyeyeballs-2.4.4.tar.gz", hash = "sha256:5fdd7d87889c63183afc18ce9271f9b0a7d32c2303e394468dd45d514a757745"},
+ {file = "aiohappyeyeballs-2.4.6-py3-none-any.whl", hash = "sha256:147ec992cf873d74f5062644332c539fcd42956dc69453fe5204195e560517e1"},
+ {file = "aiohappyeyeballs-2.4.6.tar.gz", hash = "sha256:9b05052f9042985d32ecbe4b59a77ae19c006a78f1344d7fdad69d28ded3d0b0"},
]
[[package]]
name = "aiohttp"
-version = "3.11.11"
+version = "3.11.12"
description = "Async http client/server framework (asyncio)"
optional = false
python-versions = ">=3.9"
files = [
- {file = "aiohttp-3.11.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a60804bff28662cbcf340a4d61598891f12eea3a66af48ecfdc975ceec21e3c8"},
- {file = "aiohttp-3.11.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b4fa1cb5f270fb3eab079536b764ad740bb749ce69a94d4ec30ceee1b5940d5"},
- {file = "aiohttp-3.11.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:731468f555656767cda219ab42e033355fe48c85fbe3ba83a349631541715ba2"},
- {file = "aiohttp-3.11.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb23d8bb86282b342481cad4370ea0853a39e4a32a0042bb52ca6bdde132df43"},
- {file = "aiohttp-3.11.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f047569d655f81cb70ea5be942ee5d4421b6219c3f05d131f64088c73bb0917f"},
- {file = "aiohttp-3.11.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd7659baae9ccf94ae5fe8bfaa2c7bc2e94d24611528395ce88d009107e00c6d"},
- {file = "aiohttp-3.11.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af01e42ad87ae24932138f154105e88da13ce7d202a6de93fafdafb2883a00ef"},
- {file = "aiohttp-3.11.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5854be2f3e5a729800bac57a8d76af464e160f19676ab6aea74bde18ad19d438"},
- {file = "aiohttp-3.11.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6526e5fb4e14f4bbf30411216780c9967c20c5a55f2f51d3abd6de68320cc2f3"},
- {file = "aiohttp-3.11.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:85992ee30a31835fc482468637b3e5bd085fa8fe9392ba0bdcbdc1ef5e9e3c55"},
- {file = "aiohttp-3.11.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:88a12ad8ccf325a8a5ed80e6d7c3bdc247d66175afedbe104ee2aaca72960d8e"},
- {file = "aiohttp-3.11.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0a6d3fbf2232e3a08c41eca81ae4f1dff3d8f1a30bae415ebe0af2d2458b8a33"},
- {file = "aiohttp-3.11.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:84a585799c58b795573c7fa9b84c455adf3e1d72f19a2bf498b54a95ae0d194c"},
- {file = "aiohttp-3.11.11-cp310-cp310-win32.whl", hash = "sha256:bfde76a8f430cf5c5584553adf9926534352251d379dcb266ad2b93c54a29745"},
- {file = "aiohttp-3.11.11-cp310-cp310-win_amd64.whl", hash = "sha256:0fd82b8e9c383af11d2b26f27a478640b6b83d669440c0a71481f7c865a51da9"},
- {file = "aiohttp-3.11.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ba74ec819177af1ef7f59063c6d35a214a8fde6f987f7661f4f0eecc468a8f76"},
- {file = "aiohttp-3.11.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4af57160800b7a815f3fe0eba9b46bf28aafc195555f1824555fa2cfab6c1538"},
- {file = "aiohttp-3.11.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ffa336210cf9cd8ed117011085817d00abe4c08f99968deef0013ea283547204"},
- {file = "aiohttp-3.11.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81b8fe282183e4a3c7a1b72f5ade1094ed1c6345a8f153506d114af5bf8accd9"},
- {file = "aiohttp-3.11.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3af41686ccec6a0f2bdc66686dc0f403c41ac2089f80e2214a0f82d001052c03"},
- {file = "aiohttp-3.11.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70d1f9dde0e5dd9e292a6d4d00058737052b01f3532f69c0c65818dac26dc287"},
- {file = "aiohttp-3.11.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:249cc6912405917344192b9f9ea5cd5b139d49e0d2f5c7f70bdfaf6b4dbf3a2e"},
- {file = "aiohttp-3.11.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0eb98d90b6690827dcc84c246811feeb4e1eea683c0eac6caed7549be9c84665"},
- {file = "aiohttp-3.11.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec82bf1fda6cecce7f7b915f9196601a1bd1a3079796b76d16ae4cce6d0ef89b"},
- {file = "aiohttp-3.11.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9fd46ce0845cfe28f108888b3ab17abff84ff695e01e73657eec3f96d72eef34"},
- {file = "aiohttp-3.11.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bd176afcf8f5d2aed50c3647d4925d0db0579d96f75a31e77cbaf67d8a87742d"},
- {file = "aiohttp-3.11.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ec2aa89305006fba9ffb98970db6c8221541be7bee4c1d027421d6f6df7d1ce2"},
- {file = "aiohttp-3.11.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:92cde43018a2e17d48bb09c79e4d4cb0e236de5063ce897a5e40ac7cb4878773"},
- {file = "aiohttp-3.11.11-cp311-cp311-win32.whl", hash = "sha256:aba807f9569455cba566882c8938f1a549f205ee43c27b126e5450dc9f83cc62"},
- {file = "aiohttp-3.11.11-cp311-cp311-win_amd64.whl", hash = "sha256:ae545f31489548c87b0cced5755cfe5a5308d00407000e72c4fa30b19c3220ac"},
- {file = "aiohttp-3.11.11-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e595c591a48bbc295ebf47cb91aebf9bd32f3ff76749ecf282ea7f9f6bb73886"},
- {file = "aiohttp-3.11.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3ea1b59dc06396b0b424740a10a0a63974c725b1c64736ff788a3689d36c02d2"},
- {file = "aiohttp-3.11.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8811f3f098a78ffa16e0ea36dffd577eb031aea797cbdba81be039a4169e242c"},
- {file = "aiohttp-3.11.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7227b87a355ce1f4bf83bfae4399b1f5bb42e0259cb9405824bd03d2f4336a"},
- {file = "aiohttp-3.11.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d40f9da8cabbf295d3a9dae1295c69975b86d941bc20f0a087f0477fa0a66231"},
- {file = "aiohttp-3.11.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffb3dc385f6bb1568aa974fe65da84723210e5d9707e360e9ecb51f59406cd2e"},
- {file = "aiohttp-3.11.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8f5f7515f3552d899c61202d99dcb17d6e3b0de777900405611cd747cecd1b8"},
- {file = "aiohttp-3.11.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3499c7ffbfd9c6a3d8d6a2b01c26639da7e43d47c7b4f788016226b1e711caa8"},
- {file = "aiohttp-3.11.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8e2bf8029dbf0810c7bfbc3e594b51c4cc9101fbffb583a3923aea184724203c"},
- {file = "aiohttp-3.11.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b6212a60e5c482ef90f2d788835387070a88d52cf6241d3916733c9176d39eab"},
- {file = "aiohttp-3.11.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d119fafe7b634dbfa25a8c597718e69a930e4847f0b88e172744be24515140da"},
- {file = "aiohttp-3.11.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:6fba278063559acc730abf49845d0e9a9e1ba74f85f0ee6efd5803f08b285853"},
- {file = "aiohttp-3.11.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:92fc484e34b733704ad77210c7957679c5c3877bd1e6b6d74b185e9320cc716e"},
- {file = "aiohttp-3.11.11-cp312-cp312-win32.whl", hash = "sha256:9f5b3c1ed63c8fa937a920b6c1bec78b74ee09593b3f5b979ab2ae5ef60d7600"},
- {file = "aiohttp-3.11.11-cp312-cp312-win_amd64.whl", hash = "sha256:1e69966ea6ef0c14ee53ef7a3d68b564cc408121ea56c0caa2dc918c1b2f553d"},
- {file = "aiohttp-3.11.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:541d823548ab69d13d23730a06f97460f4238ad2e5ed966aaf850d7c369782d9"},
- {file = "aiohttp-3.11.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:929f3ed33743a49ab127c58c3e0a827de0664bfcda566108989a14068f820194"},
- {file = "aiohttp-3.11.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0882c2820fd0132240edbb4a51eb8ceb6eef8181db9ad5291ab3332e0d71df5f"},
- {file = "aiohttp-3.11.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b63de12e44935d5aca7ed7ed98a255a11e5cb47f83a9fded7a5e41c40277d104"},
- {file = "aiohttp-3.11.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa54f8ef31d23c506910c21163f22b124facb573bff73930735cf9fe38bf7dff"},
- {file = "aiohttp-3.11.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a344d5dc18074e3872777b62f5f7d584ae4344cd6006c17ba12103759d407af3"},
- {file = "aiohttp-3.11.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7fb429ab1aafa1f48578eb315ca45bd46e9c37de11fe45c7f5f4138091e2f1"},
- {file = "aiohttp-3.11.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c341c7d868750e31961d6d8e60ff040fb9d3d3a46d77fd85e1ab8e76c3e9a5c4"},
- {file = "aiohttp-3.11.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ed9ee95614a71e87f1a70bc81603f6c6760128b140bc4030abe6abaa988f1c3d"},
- {file = "aiohttp-3.11.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:de8d38f1c2810fa2a4f1d995a2e9c70bb8737b18da04ac2afbf3971f65781d87"},
- {file = "aiohttp-3.11.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a9b7371665d4f00deb8f32208c7c5e652059b0fda41cf6dbcac6114a041f1cc2"},
- {file = "aiohttp-3.11.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:620598717fce1b3bd14dd09947ea53e1ad510317c85dda2c9c65b622edc96b12"},
- {file = "aiohttp-3.11.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bf8d9bfee991d8acc72d060d53860f356e07a50f0e0d09a8dfedea1c554dd0d5"},
- {file = "aiohttp-3.11.11-cp313-cp313-win32.whl", hash = "sha256:9d73ee3725b7a737ad86c2eac5c57a4a97793d9f442599bea5ec67ac9f4bdc3d"},
- {file = "aiohttp-3.11.11-cp313-cp313-win_amd64.whl", hash = "sha256:c7a06301c2fb096bdb0bd25fe2011531c1453b9f2c163c8031600ec73af1cc99"},
- {file = "aiohttp-3.11.11-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3e23419d832d969f659c208557de4a123e30a10d26e1e14b73431d3c13444c2e"},
- {file = "aiohttp-3.11.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:21fef42317cf02e05d3b09c028712e1d73a9606f02467fd803f7c1f39cc59add"},
- {file = "aiohttp-3.11.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1f21bb8d0235fc10c09ce1d11ffbd40fc50d3f08a89e4cf3a0c503dc2562247a"},
- {file = "aiohttp-3.11.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1642eceeaa5ab6c9b6dfeaaa626ae314d808188ab23ae196a34c9d97efb68350"},
- {file = "aiohttp-3.11.11-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2170816e34e10f2fd120f603e951630f8a112e1be3b60963a1f159f5699059a6"},
- {file = "aiohttp-3.11.11-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8be8508d110d93061197fd2d6a74f7401f73b6d12f8822bbcd6d74f2b55d71b1"},
- {file = "aiohttp-3.11.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4eed954b161e6b9b65f6be446ed448ed3921763cc432053ceb606f89d793927e"},
- {file = "aiohttp-3.11.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6c9af134da4bc9b3bd3e6a70072509f295d10ee60c697826225b60b9959acdd"},
- {file = "aiohttp-3.11.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:44167fc6a763d534a6908bdb2592269b4bf30a03239bcb1654781adf5e49caf1"},
- {file = "aiohttp-3.11.11-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:479b8c6ebd12aedfe64563b85920525d05d394b85f166b7873c8bde6da612f9c"},
- {file = "aiohttp-3.11.11-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:10b4ff0ad793d98605958089fabfa350e8e62bd5d40aa65cdc69d6785859f94e"},
- {file = "aiohttp-3.11.11-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:b540bd67cfb54e6f0865ceccd9979687210d7ed1a1cc8c01f8e67e2f1e883d28"},
- {file = "aiohttp-3.11.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1dac54e8ce2ed83b1f6b1a54005c87dfed139cf3f777fdc8afc76e7841101226"},
- {file = "aiohttp-3.11.11-cp39-cp39-win32.whl", hash = "sha256:568c1236b2fde93b7720f95a890741854c1200fba4a3471ff48b2934d2d93fd3"},
- {file = "aiohttp-3.11.11-cp39-cp39-win_amd64.whl", hash = "sha256:943a8b052e54dfd6439fd7989f67fc6a7f2138d0a2cf0a7de5f18aa4fe7eb3b1"},
- {file = "aiohttp-3.11.11.tar.gz", hash = "sha256:bb49c7f1e6ebf3821a42d81d494f538107610c3a705987f53068546b0e90303e"},
+ {file = "aiohttp-3.11.12-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:aa8a8caca81c0a3e765f19c6953416c58e2f4cc1b84829af01dd1c771bb2f91f"},
+ {file = "aiohttp-3.11.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:84ede78acde96ca57f6cf8ccb8a13fbaf569f6011b9a52f870c662d4dc8cd854"},
+ {file = "aiohttp-3.11.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:584096938a001378484aa4ee54e05dc79c7b9dd933e271c744a97b3b6f644957"},
+ {file = "aiohttp-3.11.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:392432a2dde22b86f70dd4a0e9671a349446c93965f261dbaecfaf28813e5c42"},
+ {file = "aiohttp-3.11.12-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:88d385b8e7f3a870146bf5ea31786ef7463e99eb59e31db56e2315535d811f55"},
+ {file = "aiohttp-3.11.12-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b10a47e5390c4b30a0d58ee12581003be52eedd506862ab7f97da7a66805befb"},
+ {file = "aiohttp-3.11.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b5263dcede17b6b0c41ef0c3ccce847d82a7da98709e75cf7efde3e9e3b5cae"},
+ {file = "aiohttp-3.11.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50c5c7b8aa5443304c55c262c5693b108c35a3b61ef961f1e782dd52a2f559c7"},
+ {file = "aiohttp-3.11.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d1c031a7572f62f66f1257db37ddab4cb98bfaf9b9434a3b4840bf3560f5e788"},
+ {file = "aiohttp-3.11.12-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:7e44eba534381dd2687be50cbd5f2daded21575242ecfdaf86bbeecbc38dae8e"},
+ {file = "aiohttp-3.11.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:145a73850926018ec1681e734cedcf2716d6a8697d90da11284043b745c286d5"},
+ {file = "aiohttp-3.11.12-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:2c311e2f63e42c1bf86361d11e2c4a59f25d9e7aabdbdf53dc38b885c5435cdb"},
+ {file = "aiohttp-3.11.12-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ea756b5a7bac046d202a9a3889b9a92219f885481d78cd318db85b15cc0b7bcf"},
+ {file = "aiohttp-3.11.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:526c900397f3bbc2db9cb360ce9c35134c908961cdd0ac25b1ae6ffcaa2507ff"},
+ {file = "aiohttp-3.11.12-cp310-cp310-win32.whl", hash = "sha256:b8d3bb96c147b39c02d3db086899679f31958c5d81c494ef0fc9ef5bb1359b3d"},
+ {file = "aiohttp-3.11.12-cp310-cp310-win_amd64.whl", hash = "sha256:7fe3d65279bfbee8de0fb4f8c17fc4e893eed2dba21b2f680e930cc2b09075c5"},
+ {file = "aiohttp-3.11.12-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:87a2e00bf17da098d90d4145375f1d985a81605267e7f9377ff94e55c5d769eb"},
+ {file = "aiohttp-3.11.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b34508f1cd928ce915ed09682d11307ba4b37d0708d1f28e5774c07a7674cac9"},
+ {file = "aiohttp-3.11.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:936d8a4f0f7081327014742cd51d320296b56aa6d324461a13724ab05f4b2933"},
+ {file = "aiohttp-3.11.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de1378f72def7dfb5dbd73d86c19eda0ea7b0a6873910cc37d57e80f10d64e1"},
+ {file = "aiohttp-3.11.12-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9d45dbb3aaec05cf01525ee1a7ac72de46a8c425cb75c003acd29f76b1ffe94"},
+ {file = "aiohttp-3.11.12-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:930ffa1925393381e1e0a9b82137fa7b34c92a019b521cf9f41263976666a0d6"},
+ {file = "aiohttp-3.11.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8340def6737118f5429a5df4e88f440746b791f8f1c4ce4ad8a595f42c980bd5"},
+ {file = "aiohttp-3.11.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4016e383f91f2814e48ed61e6bda7d24c4d7f2402c75dd28f7e1027ae44ea204"},
+ {file = "aiohttp-3.11.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3c0600bcc1adfaaac321422d615939ef300df81e165f6522ad096b73439c0f58"},
+ {file = "aiohttp-3.11.12-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:0450ada317a65383b7cce9576096150fdb97396dcfe559109b403c7242faffef"},
+ {file = "aiohttp-3.11.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:850ff6155371fd802a280f8d369d4e15d69434651b844bde566ce97ee2277420"},
+ {file = "aiohttp-3.11.12-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8fd12d0f989c6099e7b0f30dc6e0d1e05499f3337461f0b2b0dadea6c64b89df"},
+ {file = "aiohttp-3.11.12-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:76719dd521c20a58a6c256d058547b3a9595d1d885b830013366e27011ffe804"},
+ {file = "aiohttp-3.11.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:97fe431f2ed646a3b56142fc81d238abcbaff08548d6912acb0b19a0cadc146b"},
+ {file = "aiohttp-3.11.12-cp311-cp311-win32.whl", hash = "sha256:e10c440d142fa8b32cfdb194caf60ceeceb3e49807072e0dc3a8887ea80e8c16"},
+ {file = "aiohttp-3.11.12-cp311-cp311-win_amd64.whl", hash = "sha256:246067ba0cf5560cf42e775069c5d80a8989d14a7ded21af529a4e10e3e0f0e6"},
+ {file = "aiohttp-3.11.12-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e392804a38353900c3fd8b7cacbea5132888f7129f8e241915e90b85f00e3250"},
+ {file = "aiohttp-3.11.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8fa1510b96c08aaad49303ab11f8803787c99222288f310a62f493faf883ede1"},
+ {file = "aiohttp-3.11.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dc065a4285307607df3f3686363e7f8bdd0d8ab35f12226362a847731516e42c"},
+ {file = "aiohttp-3.11.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddb31f8474695cd61fc9455c644fc1606c164b93bff2490390d90464b4655df"},
+ {file = "aiohttp-3.11.12-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9dec0000d2d8621d8015c293e24589d46fa218637d820894cb7356c77eca3259"},
+ {file = "aiohttp-3.11.12-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3552fe98e90fdf5918c04769f338a87fa4f00f3b28830ea9b78b1bdc6140e0d"},
+ {file = "aiohttp-3.11.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dfe7f984f28a8ae94ff3a7953cd9678550dbd2a1f9bda5dd9c5ae627744c78e"},
+ {file = "aiohttp-3.11.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a481a574af914b6e84624412666cbfbe531a05667ca197804ecc19c97b8ab1b0"},
+ {file = "aiohttp-3.11.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1987770fb4887560363b0e1a9b75aa303e447433c41284d3af2840a2f226d6e0"},
+ {file = "aiohttp-3.11.12-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a4ac6a0f0f6402854adca4e3259a623f5c82ec3f0c049374133bcb243132baf9"},
+ {file = "aiohttp-3.11.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c96a43822f1f9f69cc5c3706af33239489a6294be486a0447fb71380070d4d5f"},
+ {file = "aiohttp-3.11.12-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a5e69046f83c0d3cb8f0d5bd9b8838271b1bc898e01562a04398e160953e8eb9"},
+ {file = "aiohttp-3.11.12-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:68d54234c8d76d8ef74744f9f9fc6324f1508129e23da8883771cdbb5818cbef"},
+ {file = "aiohttp-3.11.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c9fd9dcf9c91affe71654ef77426f5cf8489305e1c66ed4816f5a21874b094b9"},
+ {file = "aiohttp-3.11.12-cp312-cp312-win32.whl", hash = "sha256:0ed49efcd0dc1611378beadbd97beb5d9ca8fe48579fc04a6ed0844072261b6a"},
+ {file = "aiohttp-3.11.12-cp312-cp312-win_amd64.whl", hash = "sha256:54775858c7f2f214476773ce785a19ee81d1294a6bedc5cc17225355aab74802"},
+ {file = "aiohttp-3.11.12-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:413ad794dccb19453e2b97c2375f2ca3cdf34dc50d18cc2693bd5aed7d16f4b9"},
+ {file = "aiohttp-3.11.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4a93d28ed4b4b39e6f46fd240896c29b686b75e39cc6992692e3922ff6982b4c"},
+ {file = "aiohttp-3.11.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d589264dbba3b16e8951b6f145d1e6b883094075283dafcab4cdd564a9e353a0"},
+ {file = "aiohttp-3.11.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5148ca8955affdfeb864aca158ecae11030e952b25b3ae15d4e2b5ba299bad2"},
+ {file = "aiohttp-3.11.12-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:525410e0790aab036492eeea913858989c4cb070ff373ec3bc322d700bdf47c1"},
+ {file = "aiohttp-3.11.12-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bd8695be2c80b665ae3f05cb584093a1e59c35ecb7d794d1edd96e8cc9201d7"},
+ {file = "aiohttp-3.11.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0203433121484b32646a5f5ea93ae86f3d9559d7243f07e8c0eab5ff8e3f70e"},
+ {file = "aiohttp-3.11.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40cd36749a1035c34ba8d8aaf221b91ca3d111532e5ccb5fa8c3703ab1b967ed"},
+ {file = "aiohttp-3.11.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a7442662afebbf7b4c6d28cb7aab9e9ce3a5df055fc4116cc7228192ad6cb484"},
+ {file = "aiohttp-3.11.12-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:8a2fb742ef378284a50766e985804bd6adb5adb5aa781100b09befdbfa757b65"},
+ {file = "aiohttp-3.11.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2cee3b117a8d13ab98b38d5b6bdcd040cfb4181068d05ce0c474ec9db5f3c5bb"},
+ {file = "aiohttp-3.11.12-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f6a19bcab7fbd8f8649d6595624856635159a6527861b9cdc3447af288a00c00"},
+ {file = "aiohttp-3.11.12-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e4cecdb52aaa9994fbed6b81d4568427b6002f0a91c322697a4bfcc2b2363f5a"},
+ {file = "aiohttp-3.11.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:30f546358dfa0953db92ba620101fefc81574f87b2346556b90b5f3ef16e55ce"},
+ {file = "aiohttp-3.11.12-cp313-cp313-win32.whl", hash = "sha256:ce1bb21fc7d753b5f8a5d5a4bae99566386b15e716ebdb410154c16c91494d7f"},
+ {file = "aiohttp-3.11.12-cp313-cp313-win_amd64.whl", hash = "sha256:f7914ab70d2ee8ab91c13e5402122edbc77821c66d2758abb53aabe87f013287"},
+ {file = "aiohttp-3.11.12-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7c3623053b85b4296cd3925eeb725e386644fd5bc67250b3bb08b0f144803e7b"},
+ {file = "aiohttp-3.11.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:67453e603cea8e85ed566b2700efa1f6916aefbc0c9fcb2e86aaffc08ec38e78"},
+ {file = "aiohttp-3.11.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6130459189e61baac5a88c10019b21e1f0c6d00ebc770e9ce269475650ff7f73"},
+ {file = "aiohttp-3.11.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9060addfa4ff753b09392efe41e6af06ea5dd257829199747b9f15bfad819460"},
+ {file = "aiohttp-3.11.12-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34245498eeb9ae54c687a07ad7f160053911b5745e186afe2d0c0f2898a1ab8a"},
+ {file = "aiohttp-3.11.12-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8dc0fba9a74b471c45ca1a3cb6e6913ebfae416678d90529d188886278e7f3f6"},
+ {file = "aiohttp-3.11.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a478aa11b328983c4444dacb947d4513cb371cd323f3845e53caeda6be5589d5"},
+ {file = "aiohttp-3.11.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c160a04283c8c6f55b5bf6d4cad59bb9c5b9c9cd08903841b25f1f7109ef1259"},
+ {file = "aiohttp-3.11.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:edb69b9589324bdc40961cdf0657815df674f1743a8d5ad9ab56a99e4833cfdd"},
+ {file = "aiohttp-3.11.12-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4ee84c2a22a809c4f868153b178fe59e71423e1f3d6a8cd416134bb231fbf6d3"},
+ {file = "aiohttp-3.11.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:bf4480a5438f80e0f1539e15a7eb8b5f97a26fe087e9828e2c0ec2be119a9f72"},
+ {file = "aiohttp-3.11.12-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:e6b2732ef3bafc759f653a98881b5b9cdef0716d98f013d376ee8dfd7285abf1"},
+ {file = "aiohttp-3.11.12-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f752e80606b132140883bb262a457c475d219d7163d996dc9072434ffb0784c4"},
+ {file = "aiohttp-3.11.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ab3247d58b393bda5b1c8f31c9edece7162fc13265334217785518dd770792b8"},
+ {file = "aiohttp-3.11.12-cp39-cp39-win32.whl", hash = "sha256:0d5176f310a7fe6f65608213cc74f4228e4f4ce9fd10bcb2bb6da8fc66991462"},
+ {file = "aiohttp-3.11.12-cp39-cp39-win_amd64.whl", hash = "sha256:74bd573dde27e58c760d9ca8615c41a57e719bff315c9adb6f2a4281a28e8798"},
+ {file = "aiohttp-3.11.12.tar.gz", hash = "sha256:7603ca26d75b1b86160ce1bbe2787a0b706e592af5b2504e12caa88a217767b0"},
]
[package.dependencies]
@@ -809,13 +814,13 @@ zstd = ["zstandard (==0.22.0)"]
[[package]]
name = "certifi"
-version = "2024.12.14"
+version = "2025.1.31"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
python-versions = ">=3.6"
files = [
- {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"},
- {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"},
+ {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"},
+ {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"},
]
[[package]]
@@ -1368,6 +1373,24 @@ files = [
graph = ["objgraph (>=1.7.2)"]
profile = ["gprof2dot (>=2022.7.29)"]
+[[package]]
+name = "dj-rest-auth"
+version = "7.0.1"
+description = "Authentication and Registration in Django Rest Framework"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "dj-rest-auth-7.0.1.tar.gz", hash = "sha256:3f8c744cbcf05355ff4bcbef0c8a63645da38e29a0fdef3c3332d4aced52fb90"},
+]
+
+[package.dependencies]
+Django = ">=4.2,<6.0"
+django-allauth = {version = ">=64.0.0", extras = ["socialaccount"], optional = true, markers = "extra == \"with-social\""}
+djangorestframework = ">=3.13.0"
+
+[package.extras]
+with-social = ["django-allauth[socialaccount] (>=64.0.0)"]
+
[[package]]
name = "django"
version = "5.1.5"
@@ -1388,6 +1411,30 @@ tzdata = {version = "*", markers = "sys_platform == \"win32\""}
argon2 = ["argon2-cffi (>=19.1.0)"]
bcrypt = ["bcrypt"]
+[[package]]
+name = "django-allauth"
+version = "65.4.1"
+description = "Integrated set of Django applications addressing authentication, registration, account management as well as 3rd party (social) account authentication."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "django_allauth-65.4.1.tar.gz", hash = "sha256:60b32aef7dbbcc213319aa4fd8f570e985266ea1162ae6ef7a26a24efca85c8c"},
+]
+
+[package.dependencies]
+asgiref = ">=3.8.1"
+Django = ">=4.2.16"
+pyjwt = {version = ">=1.7", extras = ["crypto"], optional = true, markers = "extra == \"socialaccount\""}
+requests = {version = ">=2.0.0", optional = true, markers = "extra == \"socialaccount\""}
+requests-oauthlib = {version = ">=0.3.0", optional = true, markers = "extra == \"socialaccount\""}
+
+[package.extras]
+mfa = ["fido2 (>=1.1.2)", "qrcode (>=7.0.0)"]
+openid = ["python3-openid (>=3.0.8)"]
+saml = ["python3-saml (>=1.15.0,<2.0.0)"]
+socialaccount = ["pyjwt[crypto] (>=1.7)", "requests (>=2.0.0)", "requests-oauthlib (>=0.3.0)"]
+steam = ["python3-openid (>=3.0.8)"]
+
[[package]]
name = "django-celery-beat"
version = "2.7.0"
@@ -1943,13 +1990,13 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"]
[[package]]
name = "google-api-python-client"
-version = "2.159.0"
+version = "2.160.0"
description = "Google API Client Library for Python"
optional = false
python-versions = ">=3.7"
files = [
- {file = "google_api_python_client-2.159.0-py2.py3-none-any.whl", hash = "sha256:baef0bb631a60a0bd7c0bf12a5499e3a40cd4388484de7ee55c1950bf820a0cf"},
- {file = "google_api_python_client-2.159.0.tar.gz", hash = "sha256:55197f430f25c907394b44fa078545ffef89d33fd4dca501b7db9f0d8e224bd6"},
+ {file = "google_api_python_client-2.160.0-py2.py3-none-any.whl", hash = "sha256:63d61fb3e4cf3fb31a70a87f45567c22f6dfe87bbfa27252317e3e2c42900db4"},
+ {file = "google_api_python_client-2.160.0.tar.gz", hash = "sha256:a8ccafaecfa42d15d5b5c3134ced8de08380019717fc9fb1ed510ca58eca3b7e"},
]
[package.dependencies]
@@ -2073,18 +2120,18 @@ files = [
[[package]]
name = "h2"
-version = "4.1.0"
-description = "HTTP/2 State-Machine based protocol implementation"
+version = "4.2.0"
+description = "Pure-Python HTTP/2 protocol implementation"
optional = false
-python-versions = ">=3.6.1"
+python-versions = ">=3.9"
files = [
- {file = "h2-4.1.0-py3-none-any.whl", hash = "sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d"},
- {file = "h2-4.1.0.tar.gz", hash = "sha256:a83aca08fbe7aacb79fec788c9c0bac936343560ed9ec18b82a13a12c28d2abb"},
+ {file = "h2-4.2.0-py3-none-any.whl", hash = "sha256:479a53ad425bb29af087f3458a61d30780bc818e4ebcf01f0b536ba916462ed0"},
+ {file = "h2-4.2.0.tar.gz", hash = "sha256:c8a52129695e88b1a0578d8d2cc6842bbd79128ac685463b887ee278126ad01f"},
]
[package.dependencies]
-hpack = ">=4.0,<5"
-hyperframe = ">=6.0,<7"
+hpack = ">=4.1,<5"
+hyperframe = ">=6.1,<7"
[[package]]
name = "hpack"
@@ -2361,13 +2408,13 @@ zookeeper = ["kazoo (>=2.8.0)"]
[[package]]
name = "kubernetes"
-version = "31.0.0"
+version = "32.0.0"
description = "Kubernetes python client"
optional = false
python-versions = ">=3.6"
files = [
- {file = "kubernetes-31.0.0-py2.py3-none-any.whl", hash = "sha256:bf141e2d380c8520eada8b351f4e319ffee9636328c137aa432bc486ca1200e1"},
- {file = "kubernetes-31.0.0.tar.gz", hash = "sha256:28945de906c8c259c1ebe62703b56a03b714049372196f854105afe4e6d014c0"},
+ {file = "kubernetes-32.0.0-py2.py3-none-any.whl", hash = "sha256:60fd8c29e8e43d9c553ca4811895a687426717deba9c0a66fb2dcc3f5ef96692"},
+ {file = "kubernetes-32.0.0.tar.gz", hash = "sha256:319fa840345a482001ac5d6062222daeb66ec4d1bcb3087402aed685adf0aecb"},
]
[package.dependencies]
@@ -2482,13 +2529,13 @@ files = [
[[package]]
name = "marshmallow"
-version = "3.26.0"
+version = "3.26.1"
description = "A lightweight library for converting complex datatypes to and from native Python datatypes."
optional = false
python-versions = ">=3.9"
files = [
- {file = "marshmallow-3.26.0-py3-none-any.whl", hash = "sha256:1287bca04e6a5f4094822ac153c03da5e214a0a60bcd557b140f3e66991b8ca1"},
- {file = "marshmallow-3.26.0.tar.gz", hash = "sha256:eb36762a1cc76d7abf831e18a3a1b26d3d481bbc74581b8e532a3d3a8115e1cb"},
+ {file = "marshmallow-3.26.1-py3-none-any.whl", hash = "sha256:3350409f20a70a7e4e11a27661187b77cdcaeb20abca41c1454fe33636bea09c"},
+ {file = "marshmallow-3.26.1.tar.gz", hash = "sha256:e6d8affb6cb61d39d26402096dc0aee12d5a26d490a121f118d2e81dc0719dc6"},
]
[package.dependencies]
@@ -2523,13 +2570,13 @@ files = [
[[package]]
name = "microsoft-kiota-abstractions"
-version = "1.6.8"
+version = "1.9.2"
description = "Core abstractions for kiota generated libraries in Python"
optional = false
-python-versions = "<4.0,>=3.8"
+python-versions = "<4.0,>=3.9"
files = [
- {file = "microsoft_kiota_abstractions-1.6.8-py3-none-any.whl", hash = "sha256:12819dee24d5aaa31e99683d938f65e50cbc446de087df244cd26c3326ec4e15"},
- {file = "microsoft_kiota_abstractions-1.6.8.tar.gz", hash = "sha256:7070affabfa7182841646a0c8491cbb240af366aff2b9132f0caa45c4837dd78"},
+ {file = "microsoft_kiota_abstractions-1.9.2-py3-none-any.whl", hash = "sha256:a8853d272a84da59d6a2fe11a76c28e9c55bdab268a345ba48e918cb6822b607"},
+ {file = "microsoft_kiota_abstractions-1.9.2.tar.gz", hash = "sha256:29cdafe8d0672f23099556e0b120dca6231c752cca9393e1e0092fa9ca594572"},
]
[package.dependencies]
@@ -2539,99 +2586,94 @@ std-uritemplate = ">=2.0.0"
[[package]]
name = "microsoft-kiota-authentication-azure"
-version = "1.6.8"
+version = "1.9.2"
description = "Core abstractions for kiota generated libraries in Python"
optional = false
-python-versions = "<4.0,>=3.8"
+python-versions = "<4.0,>=3.9"
files = [
- {file = "microsoft_kiota_authentication_azure-1.6.8-py3-none-any.whl", hash = "sha256:50455789b7133e27fbccec839d93e40d2637d18593a93921ae1338880c5b5b3b"},
- {file = "microsoft_kiota_authentication_azure-1.6.8.tar.gz", hash = "sha256:fef23f43cd4d3b9ef839c8b3d1f675ec4a1120c150f963d8c4551c5e19ac3b36"},
+ {file = "microsoft_kiota_authentication_azure-1.9.2-py3-none-any.whl", hash = "sha256:56840f8b15df8aedfd143fb2deb7cc7fae4ac0bafb1a50546b7313a7b3ab4ca0"},
+ {file = "microsoft_kiota_authentication_azure-1.9.2.tar.gz", hash = "sha256:171045f522a93d9340fbddc4cabb218f14f1d9d289e82e535b3d9291986c3d5a"},
]
[package.dependencies]
aiohttp = ">=3.8.0"
azure-core = ">=1.21.1"
-microsoft-kiota-abstractions = ">=1.6.8,<1.7.0"
+microsoft-kiota-abstractions = ">=1.9.2,<1.10.0"
opentelemetry-api = ">=1.27.0"
opentelemetry-sdk = ">=1.27.0"
[[package]]
name = "microsoft-kiota-http"
-version = "1.6.8"
+version = "1.9.2"
description = "Core abstractions for kiota generated libraries in Python"
optional = false
-python-versions = "<4.0,>=3.8"
+python-versions = "<4.0,>=3.9"
files = [
- {file = "microsoft_kiota_http-1.6.8-py3-none-any.whl", hash = "sha256:7ff76a308351d885453185d6a6538c47a64ebdc7661cce46a904e89e2ceb9a1d"},
- {file = "microsoft_kiota_http-1.6.8.tar.gz", hash = "sha256:67242690b79a30c0cadf823675249269e4bc020283e3d65b33af7d771df64df8"},
+ {file = "microsoft_kiota_http-1.9.2-py3-none-any.whl", hash = "sha256:3a2d930a70d0184d9f4848473f929ee892462cae1acfaf33b2d193f1828c76c2"},
+ {file = "microsoft_kiota_http-1.9.2.tar.gz", hash = "sha256:2ba3d04a3d1d5d600736eebc1e33533d54d87799ac4fbb92c9cce4a97809af61"},
]
[package.dependencies]
-httpx = {version = ">=0.28", extras = ["http2"]}
-microsoft-kiota-abstractions = ">=1.6.8,<1.7.0"
+httpx = {version = ">=0.25,<1.0.0", extras = ["http2"]}
+microsoft-kiota-abstractions = ">=1.9.2,<1.10.0"
opentelemetry-api = ">=1.27.0"
opentelemetry-sdk = ">=1.27.0"
-urllib3 = ">=2.2.2,<3.0.0"
[[package]]
name = "microsoft-kiota-serialization-form"
-version = "1.6.8"
+version = "1.9.2"
description = "Core abstractions for kiota generated libraries in Python"
optional = false
-python-versions = "<4.0,>=3.8"
+python-versions = "<4.0,>=3.9"
files = [
- {file = "microsoft_kiota_serialization_form-1.6.8-py3-none-any.whl", hash = "sha256:ca7dd19e173aa87c68b38c5056cc0921570c8c86f3ba5511d1616cf97e2f0f67"},
- {file = "microsoft_kiota_serialization_form-1.6.8.tar.gz", hash = "sha256:bb9eb98b3abf596b4bfe208014dff948361ff48a757316ac58e19c31ab8d640a"},
+ {file = "microsoft_kiota_serialization_form-1.9.2-py3-none-any.whl", hash = "sha256:7b997efb2c8750b1d4fbc00878ba2a3e6e1df3fcefc8815226c90fcc9c54f218"},
+ {file = "microsoft_kiota_serialization_form-1.9.2.tar.gz", hash = "sha256:badfbe65d8ec3369bd58b01022d13ef590edf14babeef94188efe3f4ec24fe41"},
]
[package.dependencies]
-microsoft-kiota-abstractions = ">=1.6.8,<1.7.0"
-pendulum = ">=3.0.0b1"
+microsoft-kiota-abstractions = ">=1.9.2,<1.10.0"
[[package]]
name = "microsoft-kiota-serialization-json"
-version = "1.6.8"
+version = "1.9.2"
description = "Core abstractions for kiota generated libraries in Python"
optional = false
-python-versions = "<4.0,>=3.8"
+python-versions = "<4.0,>=3.9"
files = [
- {file = "microsoft_kiota_serialization_json-1.6.8-py3-none-any.whl", hash = "sha256:2734c2ad64cc089441279e4962f6fedf41af040730f6eab1533890cd5377aff5"},
- {file = "microsoft_kiota_serialization_json-1.6.8.tar.gz", hash = "sha256:89e2dd0eb4eaaa6ab74fa89ab5d84c5a53464e73b85eb7085f0aa4560a2b8183"},
+ {file = "microsoft_kiota_serialization_json-1.9.2-py3-none-any.whl", hash = "sha256:8f4ecf485607fff3df5ce8fa9b9c957bc7f4bff1658b183703e180af753098e3"},
+ {file = "microsoft_kiota_serialization_json-1.9.2.tar.gz", hash = "sha256:19f7beb69c67b2cb77ca96f77824ee78a693929e20237bb5476ea54f69118bf1"},
]
[package.dependencies]
-microsoft-kiota-abstractions = ">=1.6.8,<1.7.0"
-pendulum = ">=3.0.0b1"
+microsoft-kiota-abstractions = ">=1.9.2,<1.10.0"
[[package]]
name = "microsoft-kiota-serialization-multipart"
-version = "1.6.8"
+version = "1.9.2"
description = "Core abstractions for kiota generated libraries in Python"
optional = false
-python-versions = "<4.0,>=3.8"
+python-versions = "<4.0,>=3.9"
files = [
- {file = "microsoft_kiota_serialization_multipart-1.6.8-py3-none-any.whl", hash = "sha256:1ecdd15dd1f78aed031d7d1828b6fbc00c633542d863c23f96fdd0a61bfb189a"},
- {file = "microsoft_kiota_serialization_multipart-1.6.8.tar.gz", hash = "sha256:3d95c6d7186588af7a1d3aa852ce42077f80487b8b3c60e36fe109a8b4918c03"},
+ {file = "microsoft_kiota_serialization_multipart-1.9.2-py3-none-any.whl", hash = "sha256:641ad374046f1c7adff90d110bdc68d77418adb1e479a716f4ffea3647f0ead6"},
+ {file = "microsoft_kiota_serialization_multipart-1.9.2.tar.gz", hash = "sha256:b1851409205668d83f5c7a35a8b6fca974b341985b4a92841e95aaec93b7ca0a"},
]
[package.dependencies]
-microsoft-kiota-abstractions = ">=1.6.8,<1.7.0"
-pendulum = ">=3.0.0b1"
+microsoft-kiota-abstractions = ">=1.9.2,<1.10.0"
[[package]]
name = "microsoft-kiota-serialization-text"
-version = "1.6.8"
+version = "1.9.2"
description = "Core abstractions for kiota generated libraries in Python"
optional = false
-python-versions = "<4.0,>=3.8"
+python-versions = "<4.0,>=3.9"
files = [
- {file = "microsoft_kiota_serialization_text-1.6.8-py3-none-any.whl", hash = "sha256:4e5e287a614d362f864b5061dca0861c3f70b8792ec72967d1bff23944da1e80"},
- {file = "microsoft_kiota_serialization_text-1.6.8.tar.gz", hash = "sha256:687d4858337eaf4f351b12ed1c6c934d869560f54ee3855bfdde589660e07208"},
+ {file = "microsoft_kiota_serialization_text-1.9.2-py3-none-any.whl", hash = "sha256:6e63129ea29eb9b976f4ed56fc6595d204e29fc309958b639299e9f9f4e5edb4"},
+ {file = "microsoft_kiota_serialization_text-1.9.2.tar.gz", hash = "sha256:4289508ebac0cefdc4fa21c545051769a9409913972355ccda9116b647f978f2"},
]
[package.dependencies]
-microsoft-kiota-abstractions = ">=1.6.8,<1.7.0"
-python-dateutil = "2.9.0.post0"
+microsoft-kiota-abstractions = ">=1.9.2,<1.10.0"
[[package]]
name = "msal"
@@ -2669,20 +2711,20 @@ portalocker = ">=1.4,<3"
[[package]]
name = "msgraph-core"
-version = "1.2.0"
+version = "1.3.1"
description = "Core component of the Microsoft Graph Python SDK"
optional = false
python-versions = ">=3.9"
files = [
- {file = "msgraph_core-1.2.0-py3-none-any.whl", hash = "sha256:4ce14bbe743c0f2dd8b53c7fcd338fc14081e0df6b14021f0d0e4bb63cd5a840"},
- {file = "msgraph_core-1.2.0.tar.gz", hash = "sha256:a4e42f692e664c60d63359e610bbf990f57b42d8080417261ff7042bbd59c98b"},
+ {file = "msgraph_core-1.3.1-py3-none-any.whl", hash = "sha256:a6e3c19c36d43d00edfb49225647a357f8189351c226c3b7c020c5dfaa24171b"},
+ {file = "msgraph_core-1.3.1.tar.gz", hash = "sha256:91c721b4c741d0e9a536e3c43e1cbd2254928b4d207e4f994e9cc7cb7a25bd74"},
]
[package.dependencies]
httpx = {version = ">=0.23.0", extras = ["http2"]}
-microsoft-kiota-abstractions = ">=1.0.0,<2.0.0"
-microsoft-kiota-authentication-azure = ">=1.0.0,<2.0.0"
-microsoft-kiota-http = ">=1.0.0,<2.0.0"
+microsoft-kiota-abstractions = ">=1.8.0,<2.0.0"
+microsoft-kiota-authentication-azure = ">=1.8.0,<2.0.0"
+microsoft-kiota-http = ">=1.8.0,<2.0.0"
[package.extras]
dev = ["bumpver", "isort", "mypy", "pylint", "pytest", "yapf"]
@@ -2891,6 +2933,32 @@ files = [
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
]
+[[package]]
+name = "narwhals"
+version = "1.25.2"
+description = "Extremely lightweight compatibility layer between dataframe libraries"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "narwhals-1.25.2-py3-none-any.whl", hash = "sha256:e645f7fc1f8c0a3563a6cdcd0191586cdf88470ad90f0818abba7ceb6c181b00"},
+ {file = "narwhals-1.25.2.tar.gz", hash = "sha256:37594746fc06fe4a588967a34a2974b1f3a7ad6ff1571b6e31ac5e58c9591000"},
+]
+
+[package.extras]
+core = ["duckdb", "pandas", "polars", "pyarrow", "pyarrow-stubs"]
+cudf = ["cudf (>=24.10.0)"]
+dask = ["dask[dataframe] (>=2024.8)"]
+dev = ["covdefaults", "hypothesis", "pre-commit", "pytest", "pytest-cov", "pytest-env", "pytest-randomly", "typing-extensions"]
+docs = ["black", "duckdb", "jinja2", "markdown-exec[ansi]", "mkdocs", "mkdocs-autorefs", "mkdocs-material", "mkdocstrings[python]", "pandas", "polars (>=1.0.0)", "pyarrow"]
+duckdb = ["duckdb (>=1.0)"]
+extra = ["scikit-learn"]
+ibis = ["ibis-framework (>=6.0.0)", "packaging", "pyarrow-hotfix", "rich"]
+modin = ["modin"]
+pandas = ["pandas (>=0.25.3)"]
+polars = ["polars (>=0.20.3)"]
+pyarrow = ["pyarrow (>=11.0.0)"]
+pyspark = ["pyspark (>=3.5.0)"]
+
[[package]]
name = "nest-asyncio"
version = "1.6.0"
@@ -2974,13 +3042,13 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
[[package]]
name = "opentelemetry-api"
-version = "1.29.0"
+version = "1.30.0"
description = "OpenTelemetry Python API"
optional = false
python-versions = ">=3.8"
files = [
- {file = "opentelemetry_api-1.29.0-py3-none-any.whl", hash = "sha256:5fcd94c4141cc49c736271f3e1efb777bebe9cc535759c54c936cca4f1b312b8"},
- {file = "opentelemetry_api-1.29.0.tar.gz", hash = "sha256:d04a6cf78aad09614f52964ecb38021e248f5714dc32c2e0d8fd99517b4d69cf"},
+ {file = "opentelemetry_api-1.30.0-py3-none-any.whl", hash = "sha256:d5f5284890d73fdf47f843dda3210edf37a38d66f44f2b5aedc1e89ed455dc09"},
+ {file = "opentelemetry_api-1.30.0.tar.gz", hash = "sha256:375893400c1435bf623f7dfb3bcd44825fe6b56c34d0667c542ea8257b1a1240"},
]
[package.dependencies]
@@ -2989,34 +3057,34 @@ importlib-metadata = ">=6.0,<=8.5.0"
[[package]]
name = "opentelemetry-sdk"
-version = "1.29.0"
+version = "1.30.0"
description = "OpenTelemetry Python SDK"
optional = false
python-versions = ">=3.8"
files = [
- {file = "opentelemetry_sdk-1.29.0-py3-none-any.whl", hash = "sha256:173be3b5d3f8f7d671f20ea37056710217959e774e2749d984355d1f9391a30a"},
- {file = "opentelemetry_sdk-1.29.0.tar.gz", hash = "sha256:b0787ce6aade6ab84315302e72bd7a7f2f014b0fb1b7c3295b88afe014ed0643"},
+ {file = "opentelemetry_sdk-1.30.0-py3-none-any.whl", hash = "sha256:14fe7afc090caad881addb6926cec967129bd9260c4d33ae6a217359f6b61091"},
+ {file = "opentelemetry_sdk-1.30.0.tar.gz", hash = "sha256:c9287a9e4a7614b9946e933a67168450b9ab35f08797eb9bc77d998fa480fa18"},
]
[package.dependencies]
-opentelemetry-api = "1.29.0"
-opentelemetry-semantic-conventions = "0.50b0"
+opentelemetry-api = "1.30.0"
+opentelemetry-semantic-conventions = "0.51b0"
typing-extensions = ">=3.7.4"
[[package]]
name = "opentelemetry-semantic-conventions"
-version = "0.50b0"
+version = "0.51b0"
description = "OpenTelemetry Semantic Conventions"
optional = false
python-versions = ">=3.8"
files = [
- {file = "opentelemetry_semantic_conventions-0.50b0-py3-none-any.whl", hash = "sha256:e87efba8fdb67fb38113efea6a349531e75ed7ffc01562f65b802fcecb5e115e"},
- {file = "opentelemetry_semantic_conventions-0.50b0.tar.gz", hash = "sha256:02dc6dbcb62f082de9b877ff19a3f1ffaa3c306300fa53bfac761c4567c83d38"},
+ {file = "opentelemetry_semantic_conventions-0.51b0-py3-none-any.whl", hash = "sha256:fdc777359418e8d06c86012c3dc92c88a6453ba662e941593adb062e48c2eeae"},
+ {file = "opentelemetry_semantic_conventions-0.51b0.tar.gz", hash = "sha256:3fabf47f35d1fd9aebcdca7e6802d86bd5ebc3bc3408b7e3248dde6e87a18c47"},
]
[package.dependencies]
deprecated = ">=1.2.6"
-opentelemetry-api = "1.29.0"
+opentelemetry-api = "1.30.0"
[[package]]
name = "packaging"
@@ -3116,113 +3184,17 @@ xml = ["lxml (>=4.9.2)"]
[[package]]
name = "pbr"
-version = "6.1.0"
+version = "6.1.1"
description = "Python Build Reasonableness"
optional = false
python-versions = ">=2.6"
files = [
- {file = "pbr-6.1.0-py2.py3-none-any.whl", hash = "sha256:a776ae228892d8013649c0aeccbb3d5f99ee15e005a4cbb7e61d55a067b28a2a"},
- {file = "pbr-6.1.0.tar.gz", hash = "sha256:788183e382e3d1d7707db08978239965e8b9e4e5ed42669bf4758186734d5f24"},
-]
-
-[[package]]
-name = "pendulum"
-version = "3.0.0"
-description = "Python datetimes made easy"
-optional = false
-python-versions = ">=3.8"
-files = [
- {file = "pendulum-3.0.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2cf9e53ef11668e07f73190c805dbdf07a1939c3298b78d5a9203a86775d1bfd"},
- {file = "pendulum-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fb551b9b5e6059377889d2d878d940fd0bbb80ae4810543db18e6f77b02c5ef6"},
- {file = "pendulum-3.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c58227ac260d5b01fc1025176d7b31858c9f62595737f350d22124a9a3ad82d"},
- {file = "pendulum-3.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60fb6f415fea93a11c52578eaa10594568a6716602be8430b167eb0d730f3332"},
- {file = "pendulum-3.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b69f6b4dbcb86f2c2fe696ba991e67347bcf87fe601362a1aba6431454b46bde"},
- {file = "pendulum-3.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:138afa9c373ee450ede206db5a5e9004fd3011b3c6bbe1e57015395cd076a09f"},
- {file = "pendulum-3.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:83d9031f39c6da9677164241fd0d37fbfc9dc8ade7043b5d6d62f56e81af8ad2"},
- {file = "pendulum-3.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0c2308af4033fa534f089595bcd40a95a39988ce4059ccd3dc6acb9ef14ca44a"},
- {file = "pendulum-3.0.0-cp310-none-win_amd64.whl", hash = "sha256:9a59637cdb8462bdf2dbcb9d389518c0263799189d773ad5c11db6b13064fa79"},
- {file = "pendulum-3.0.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3725245c0352c95d6ca297193192020d1b0c0f83d5ee6bb09964edc2b5a2d508"},
- {file = "pendulum-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6c035f03a3e565ed132927e2c1b691de0dbf4eb53b02a5a3c5a97e1a64e17bec"},
- {file = "pendulum-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597e66e63cbd68dd6d58ac46cb7a92363d2088d37ccde2dae4332ef23e95cd00"},
- {file = "pendulum-3.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99a0f8172e19f3f0c0e4ace0ad1595134d5243cf75985dc2233e8f9e8de263ca"},
- {file = "pendulum-3.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:77d8839e20f54706aed425bec82a83b4aec74db07f26acd039905d1237a5e1d4"},
- {file = "pendulum-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afde30e8146292b059020fbc8b6f8fd4a60ae7c5e6f0afef937bbb24880bdf01"},
- {file = "pendulum-3.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:660434a6fcf6303c4efd36713ca9212c753140107ee169a3fc6c49c4711c2a05"},
- {file = "pendulum-3.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dee9e5a48c6999dc1106eb7eea3e3a50e98a50651b72c08a87ee2154e544b33e"},
- {file = "pendulum-3.0.0-cp311-none-win_amd64.whl", hash = "sha256:d4cdecde90aec2d67cebe4042fd2a87a4441cc02152ed7ed8fb3ebb110b94ec4"},
- {file = "pendulum-3.0.0-cp311-none-win_arm64.whl", hash = "sha256:773c3bc4ddda2dda9f1b9d51fe06762f9200f3293d75c4660c19b2614b991d83"},
- {file = "pendulum-3.0.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:409e64e41418c49f973d43a28afe5df1df4f1dd87c41c7c90f1a63f61ae0f1f7"},
- {file = "pendulum-3.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a38ad2121c5ec7c4c190c7334e789c3b4624798859156b138fcc4d92295835dc"},
- {file = "pendulum-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fde4d0b2024b9785f66b7f30ed59281bd60d63d9213cda0eb0910ead777f6d37"},
- {file = "pendulum-3.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b2c5675769fb6d4c11238132962939b960fcb365436b6d623c5864287faa319"},
- {file = "pendulum-3.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8af95e03e066826f0f4c65811cbee1b3123d4a45a1c3a2b4fc23c4b0dff893b5"},
- {file = "pendulum-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2165a8f33cb15e06c67070b8afc87a62b85c5a273e3aaa6bc9d15c93a4920d6f"},
- {file = "pendulum-3.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ad5e65b874b5e56bd942546ea7ba9dd1d6a25121db1c517700f1c9de91b28518"},
- {file = "pendulum-3.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:17fe4b2c844bbf5f0ece69cfd959fa02957c61317b2161763950d88fed8e13b9"},
- {file = "pendulum-3.0.0-cp312-none-win_amd64.whl", hash = "sha256:78f8f4e7efe5066aca24a7a57511b9c2119f5c2b5eb81c46ff9222ce11e0a7a5"},
- {file = "pendulum-3.0.0-cp312-none-win_arm64.whl", hash = "sha256:28f49d8d1e32aae9c284a90b6bb3873eee15ec6e1d9042edd611b22a94ac462f"},
- {file = "pendulum-3.0.0-cp37-cp37m-macosx_10_12_x86_64.whl", hash = "sha256:d4e2512f4e1a4670284a153b214db9719eb5d14ac55ada5b76cbdb8c5c00399d"},
- {file = "pendulum-3.0.0-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:3d897eb50883cc58d9b92f6405245f84b9286cd2de6e8694cb9ea5cb15195a32"},
- {file = "pendulum-3.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e169cc2ca419517f397811bbe4589cf3cd13fca6dc38bb352ba15ea90739ebb"},
- {file = "pendulum-3.0.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f17c3084a4524ebefd9255513692f7e7360e23c8853dc6f10c64cc184e1217ab"},
- {file = "pendulum-3.0.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:826d6e258052715f64d05ae0fc9040c0151e6a87aae7c109ba9a0ed930ce4000"},
- {file = "pendulum-3.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2aae97087872ef152a0c40e06100b3665d8cb86b59bc8471ca7c26132fccd0f"},
- {file = "pendulum-3.0.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ac65eeec2250d03106b5e81284ad47f0d417ca299a45e89ccc69e36130ca8bc7"},
- {file = "pendulum-3.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a5346d08f3f4a6e9e672187faa179c7bf9227897081d7121866358af369f44f9"},
- {file = "pendulum-3.0.0-cp37-none-win_amd64.whl", hash = "sha256:235d64e87946d8f95c796af34818c76e0f88c94d624c268693c85b723b698aa9"},
- {file = "pendulum-3.0.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:6a881d9c2a7f85bc9adafcfe671df5207f51f5715ae61f5d838b77a1356e8b7b"},
- {file = "pendulum-3.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d7762d2076b9b1cb718a6631ad6c16c23fc3fac76cbb8c454e81e80be98daa34"},
- {file = "pendulum-3.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e8e36a8130819d97a479a0e7bf379b66b3b1b520e5dc46bd7eb14634338df8c"},
- {file = "pendulum-3.0.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7dc843253ac373358ffc0711960e2dd5b94ab67530a3e204d85c6e8cb2c5fa10"},
- {file = "pendulum-3.0.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a78ad3635d609ceb1e97d6aedef6a6a6f93433ddb2312888e668365908c7120"},
- {file = "pendulum-3.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b30a137e9e0d1f751e60e67d11fc67781a572db76b2296f7b4d44554761049d6"},
- {file = "pendulum-3.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c95984037987f4a457bb760455d9ca80467be792236b69d0084f228a8ada0162"},
- {file = "pendulum-3.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d29c6e578fe0f893766c0d286adbf0b3c726a4e2341eba0917ec79c50274ec16"},
- {file = "pendulum-3.0.0-cp38-none-win_amd64.whl", hash = "sha256:deaba8e16dbfcb3d7a6b5fabdd5a38b7c982809567479987b9c89572df62e027"},
- {file = "pendulum-3.0.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b11aceea5b20b4b5382962b321dbc354af0defe35daa84e9ff3aae3c230df694"},
- {file = "pendulum-3.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a90d4d504e82ad236afac9adca4d6a19e4865f717034fc69bafb112c320dcc8f"},
- {file = "pendulum-3.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:825799c6b66e3734227756fa746cc34b3549c48693325b8b9f823cb7d21b19ac"},
- {file = "pendulum-3.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad769e98dc07972e24afe0cff8d365cb6f0ebc7e65620aa1976fcfbcadc4c6f3"},
- {file = "pendulum-3.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6fc26907eb5fb8cc6188cc620bc2075a6c534d981a2f045daa5f79dfe50d512"},
- {file = "pendulum-3.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c717eab1b6d898c00a3e0fa7781d615b5c5136bbd40abe82be100bb06df7a56"},
- {file = "pendulum-3.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3ddd1d66d1a714ce43acfe337190be055cdc221d911fc886d5a3aae28e14b76d"},
- {file = "pendulum-3.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:822172853d7a9cf6da95d7b66a16c7160cb99ae6df55d44373888181d7a06edc"},
- {file = "pendulum-3.0.0-cp39-none-win_amd64.whl", hash = "sha256:840de1b49cf1ec54c225a2a6f4f0784d50bd47f68e41dc005b7f67c7d5b5f3ae"},
- {file = "pendulum-3.0.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3b1f74d1e6ffe5d01d6023870e2ce5c2191486928823196f8575dcc786e107b1"},
- {file = "pendulum-3.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:729e9f93756a2cdfa77d0fc82068346e9731c7e884097160603872686e570f07"},
- {file = "pendulum-3.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e586acc0b450cd21cbf0db6bae386237011b75260a3adceddc4be15334689a9a"},
- {file = "pendulum-3.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22e7944ffc1f0099a79ff468ee9630c73f8c7835cd76fdb57ef7320e6a409df4"},
- {file = "pendulum-3.0.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:fa30af36bd8e50686846bdace37cf6707bdd044e5cb6e1109acbad3277232e04"},
- {file = "pendulum-3.0.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:440215347b11914ae707981b9a57ab9c7b6983ab0babde07063c6ee75c0dc6e7"},
- {file = "pendulum-3.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:314c4038dc5e6a52991570f50edb2f08c339debdf8cea68ac355b32c4174e820"},
- {file = "pendulum-3.0.0-pp37-pypy37_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5acb1d386337415f74f4d1955c4ce8d0201978c162927d07df8eb0692b2d8533"},
- {file = "pendulum-3.0.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a789e12fbdefaffb7b8ac67f9d8f22ba17a3050ceaaa635cd1cc4645773a4b1e"},
- {file = "pendulum-3.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:860aa9b8a888e5913bd70d819306749e5eb488e6b99cd6c47beb701b22bdecf5"},
- {file = "pendulum-3.0.0-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:5ebc65ea033ef0281368217fbf59f5cb05b338ac4dd23d60959c7afcd79a60a0"},
- {file = "pendulum-3.0.0-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:d9fef18ab0386ef6a9ac7bad7e43ded42c83ff7ad412f950633854f90d59afa8"},
- {file = "pendulum-3.0.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1c134ba2f0571d0b68b83f6972e2307a55a5a849e7dac8505c715c531d2a8795"},
- {file = "pendulum-3.0.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:385680812e7e18af200bb9b4a49777418c32422d05ad5a8eb85144c4a285907b"},
- {file = "pendulum-3.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eec91cd87c59fb32ec49eb722f375bd58f4be790cae11c1b70fac3ee4f00da0"},
- {file = "pendulum-3.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4386bffeca23c4b69ad50a36211f75b35a4deb6210bdca112ac3043deb7e494a"},
- {file = "pendulum-3.0.0-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:dfbcf1661d7146d7698da4b86e7f04814221081e9fe154183e34f4c5f5fa3bf8"},
- {file = "pendulum-3.0.0-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:04a1094a5aa1daa34a6b57c865b25f691848c61583fb22722a4df5699f6bf74c"},
- {file = "pendulum-3.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5b0ec85b9045bd49dd3a3493a5e7ddfd31c36a2a60da387c419fa04abcaecb23"},
- {file = "pendulum-3.0.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0a15b90129765b705eb2039062a6daf4d22c4e28d1a54fa260892e8c3ae6e157"},
- {file = "pendulum-3.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:bb8f6d7acd67a67d6fedd361ad2958ff0539445ef51cbe8cd288db4306503cd0"},
- {file = "pendulum-3.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd69b15374bef7e4b4440612915315cc42e8575fcda2a3d7586a0d88192d0c88"},
- {file = "pendulum-3.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc00f8110db6898360c53c812872662e077eaf9c75515d53ecc65d886eec209a"},
- {file = "pendulum-3.0.0-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:83a44e8b40655d0ba565a5c3d1365d27e3e6778ae2a05b69124db9e471255c4a"},
- {file = "pendulum-3.0.0-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:1a3604e9fbc06b788041b2a8b78f75c243021e0f512447806a6d37ee5214905d"},
- {file = "pendulum-3.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:92c307ae7accebd06cbae4729f0ba9fa724df5f7d91a0964b1b972a22baa482b"},
- {file = "pendulum-3.0.0.tar.gz", hash = "sha256:5d034998dea404ec31fae27af6b22cff1708f830a1ed7353be4d1019bb9f584e"},
+ {file = "pbr-6.1.1-py2.py3-none-any.whl", hash = "sha256:38d4daea5d9fa63b3f626131b9d34947fd0c8be9b05a29276870580050a25a76"},
+ {file = "pbr-6.1.1.tar.gz", hash = "sha256:93ea72ce6989eb2eed99d0f75721474f69ad88128afdef5ac377eb797c4bf76b"},
]
[package.dependencies]
-python-dateutil = ">=2.6"
-tzdata = ">=2020.1"
-
-[package.extras]
-test = ["time-machine (>=2.6.0)"]
+setuptools = "*"
[[package]]
name = "platformdirs"
@@ -3242,18 +3214,21 @@ type = ["mypy (>=1.11.2)"]
[[package]]
name = "plotly"
-version = "5.24.1"
+version = "6.0.0"
description = "An open-source, interactive data visualization library for Python"
optional = false
python-versions = ">=3.8"
files = [
- {file = "plotly-5.24.1-py3-none-any.whl", hash = "sha256:f67073a1e637eb0dc3e46324d9d51e2fe76e9727c892dde64ddf1e1b51f29089"},
- {file = "plotly-5.24.1.tar.gz", hash = "sha256:dbc8ac8339d248a4bcc36e08a5659bacfe1b079390b8953533f4eb22169b4bae"},
+ {file = "plotly-6.0.0-py3-none-any.whl", hash = "sha256:f708871c3a9349a68791ff943a5781b1ec04de7769ea69068adcd9202e57653a"},
+ {file = "plotly-6.0.0.tar.gz", hash = "sha256:c4aad38b8c3d65e4a5e7dd308b084143b9025c2cc9d5317fc1f1d30958db87d3"},
]
[package.dependencies]
+narwhals = ">=1.15.1"
packaging = "*"
-tenacity = ">=6.2.0"
+
+[package.extras]
+express = ["numpy"]
[[package]]
name = "pluggy"
@@ -3470,18 +3445,18 @@ cryptography = "43.0.1"
dash = "2.18.2"
dash-bootstrap-components = "1.6.0"
detect-secrets = "1.5.0"
-google-api-python-client = "2.159.0"
+google-api-python-client = "2.160.0"
google-auth-httplib2 = ">=0.1,<0.3"
jsonschema = "4.23.0"
-kubernetes = "31.0.0"
-microsoft-kiota-abstractions = "1.6.8"
+kubernetes = "32.0.0"
+microsoft-kiota-abstractions = "1.9.2"
msgraph-sdk = "1.18.0"
numpy = "2.0.2"
pandas = "2.2.3"
-py-ocsf-models = "0.2.0"
+py-ocsf-models = "0.3.0"
pydantic = "1.10.18"
python-dateutil = "^2.9.0.post0"
-pytz = "2024.2"
+pytz = "2025.1"
schema = "0.7.7"
shodan = "1.31.0"
slack-sdk = "3.34.0"
@@ -3492,7 +3467,7 @@ tzlocal = "5.2"
type = "git"
url = "https://github.com/prowler-cloud/prowler.git"
reference = "master"
-resolved_reference = "47bc2ed2dc472fc24ecab35970205448ab3b090d"
+resolved_reference = "7469377079bb88487a741625dcd431aa5375e793"
[[package]]
name = "psutil"
@@ -3606,13 +3581,13 @@ files = [
[[package]]
name = "py-ocsf-models"
-version = "0.2.0"
+version = "0.3.0"
description = "This is a Python implementation of the OCSF models. The models are used to represent the data of the OCSF Schema defined in https://schema.ocsf.io/."
optional = false
python-versions = "<3.13,>=3.9"
files = [
- {file = "py_ocsf_models-0.2.0-py3-none-any.whl", hash = "sha256:ac75fd21077694b343ebaad3479194db113c274879b114277560ff287d5cd7b5"},
- {file = "py_ocsf_models-0.2.0.tar.gz", hash = "sha256:3e12648d05329e6776a0e6b1ffea87a3eb60aa7d8cb2c4afd69e5724f443ce03"},
+ {file = "py_ocsf_models-0.3.0-py3-none-any.whl", hash = "sha256:3d31e379be5e4271f7faf62dee9c36798559a1f7f98dff142c0e4cfdb35e291c"},
+ {file = "py_ocsf_models-0.3.0.tar.gz", hash = "sha256:ad46b7d9761b74010f06a894df2d9541989252b7ff738cd5c7edbf4283df2279"},
]
[package.dependencies]
@@ -4040,13 +4015,13 @@ files = [
[[package]]
name = "pytz"
-version = "2024.2"
+version = "2025.1"
description = "World timezone definitions, modern and historical"
optional = false
python-versions = "*"
files = [
- {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"},
- {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"},
+ {file = "pytz-2025.1-py2.py3-none-any.whl", hash = "sha256:89dd22dca55b46eac6eda23b2d72721bf1bdfef212645d81513ef5d03038de57"},
+ {file = "pytz-2025.1.tar.gz", hash = "sha256:c2db42be2a2518b28e65f9207c4d05e6ff547d1efa4086469ef855e4ab70178e"},
]
[[package]]
@@ -4661,13 +4636,13 @@ doc = ["sphinx"]
[[package]]
name = "std-uritemplate"
-version = "2.0.1"
+version = "2.0.3"
description = "std-uritemplate implementation for Python"
optional = false
python-versions = "<4.0,>=3.8"
files = [
- {file = "std_uritemplate-2.0.1-py3-none-any.whl", hash = "sha256:6405d13f9ff3b8f70e1fa4eaefa373049659e1a61870c4b651441821a3fc984d"},
- {file = "std_uritemplate-2.0.1.tar.gz", hash = "sha256:f4684ae050167e237ed5819423139893e0b5b7f08dc6b7467bba3fdd64608be8"},
+ {file = "std_uritemplate-2.0.3-py3-none-any.whl", hash = "sha256:434df26453bf68c6077879fed6609b2c39e2fc73080e74cd157269d5f8abdb3e"},
+ {file = "std_uritemplate-2.0.3.tar.gz", hash = "sha256:ad4cb1d671bcf4a3608b3598c687be4b0929867c53a2d69c105989da6a5a2d4c"},
]
[[package]]
@@ -4996,13 +4971,13 @@ files = [
[[package]]
name = "xlsxwriter"
-version = "3.2.1"
+version = "3.2.2"
description = "A Python module for creating Excel XLSX files."
optional = false
python-versions = ">=3.6"
files = [
- {file = "XlsxWriter-3.2.1-py3-none-any.whl", hash = "sha256:7e8f7c60b7a1660ef791d46ab5de78469cb978b991ca841af61f5832d2f9f4fe"},
- {file = "XlsxWriter-3.2.1.tar.gz", hash = "sha256:97618759cb264fb6a93397f660cca156ffa9561743b1823dafb60dc4474e1902"},
+ {file = "XlsxWriter-3.2.2-py3-none-any.whl", hash = "sha256:272ce861e7fa5e82a4a6ebc24511f2cb952fde3461f6c6e1a1e81d3272db1471"},
+ {file = "xlsxwriter-3.2.2.tar.gz", hash = "sha256:befc7f92578a85fed261639fb6cde1fd51b79c5e854040847dde59d4317077dc"},
]
[[package]]
@@ -5123,4 +5098,4 @@ type = ["pytest-mypy"]
[metadata]
lock-version = "2.0"
python-versions = ">=3.11,<3.13"
-content-hash = "6465edb36efd1fa6db06d4103fea8046951acc3f4f8b357facaaa34ae2bc74bd"
+content-hash = "d762c984b70da32828fc0b2c8b9d4849a9342b8a9ac7060c47d6e82071b740f1"
diff --git a/api/pyproject.toml b/api/pyproject.toml
index 2cccb9ddb3..2a219106cc 100644
--- a/api/pyproject.toml
+++ b/api/pyproject.toml
@@ -8,10 +8,11 @@ description = "Prowler's API (Django/DRF)"
license = "Apache-2.0"
name = "prowler-api"
package-mode = false
-version = "1.4.0"
+version = "1.5.0"
[tool.poetry.dependencies]
celery = {extras = ["pytest"], version = "^5.4.0"}
+dj-rest-auth = {extras = ["with_social", "jwt"], version = "7.0.1"}
django = "5.1.5"
django-celery-beat = "^2.7.0"
django-celery-results = "^2.5.1"
diff --git a/api/src/backend/api/adapters.py b/api/src/backend/api/adapters.py
new file mode 100644
index 0000000000..9a8cb2f044
--- /dev/null
+++ b/api/src/backend/api/adapters.py
@@ -0,0 +1,57 @@
+from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
+from django.db import transaction
+
+from api.db_router import MainRouter
+from api.db_utils import rls_transaction
+from api.models import Membership, Role, Tenant, User, UserRoleRelationship
+
+
+class ProwlerSocialAccountAdapter(DefaultSocialAccountAdapter):
+ @staticmethod
+ def get_user_by_email(email: str):
+ try:
+ return User.objects.get(email=email)
+ except User.DoesNotExist:
+ return None
+
+ def pre_social_login(self, request, sociallogin):
+ # Link existing accounts with the same email address
+ email = sociallogin.account.extra_data.get("email")
+ if email:
+ existing_user = self.get_user_by_email(email)
+ if existing_user:
+ sociallogin.connect(request, existing_user)
+
+ def save_user(self, request, sociallogin, form=None):
+ """
+ Called after the user data is fully populated from the provider
+ and is about to be saved to the DB for the first time.
+ """
+ with transaction.atomic(using=MainRouter.admin_db):
+ user = super().save_user(request, sociallogin, form)
+ user.save(using=MainRouter.admin_db)
+
+ tenant = Tenant.objects.using(MainRouter.admin_db).create(
+ name=f"{user.email.split('@')[0]} default tenant"
+ )
+ with rls_transaction(str(tenant.id)):
+ Membership.objects.using(MainRouter.admin_db).create(
+ user=user, tenant=tenant, role=Membership.RoleChoices.OWNER
+ )
+ role = Role.objects.using(MainRouter.admin_db).create(
+ name="admin",
+ tenant_id=tenant.id,
+ manage_users=True,
+ manage_account=True,
+ manage_billing=True,
+ manage_providers=True,
+ manage_integrations=True,
+ manage_scans=True,
+ unlimited_visibility=True,
+ )
+ UserRoleRelationship.objects.using(MainRouter.admin_db).create(
+ user=user,
+ role=role,
+ tenant_id=tenant.id,
+ )
+ return user
diff --git a/api/src/backend/api/db_router.py b/api/src/backend/api/db_router.py
index 939672f88e..dc34c1191a 100644
--- a/api/src/backend/api/db_router.py
+++ b/api/src/backend/api/db_router.py
@@ -1,22 +1,29 @@
+ALLOWED_APPS = ("django", "socialaccount", "account", "authtoken", "silk")
+
+
class MainRouter:
default_db = "default"
admin_db = "admin"
def db_for_read(self, model, **hints): # noqa: F841
model_table_name = model._meta.db_table
- if model_table_name.startswith("django_") or model_table_name.startswith(
- "silk_"
+ if model_table_name.startswith("django_") or any(
+ model_table_name.startswith(f"{app}_") for app in ALLOWED_APPS
):
return self.admin_db
return None
def db_for_write(self, model, **hints): # noqa: F841
model_table_name = model._meta.db_table
- if model_table_name.startswith("django_") or model_table_name.startswith(
- "silk_"
- ):
+ if any(model_table_name.startswith(f"{app}_") for app in ALLOWED_APPS):
return self.admin_db
return None
def allow_migrate(self, db, app_label, model_name=None, **hints): # noqa: F841
return db == self.admin_db
+
+ def allow_relation(self, obj1, obj2, **hints): # noqa: F841
+ # Allow relations if both objects are in either "default" or "admin" db connectors
+ if {obj1._state.db, obj2._state.db} <= {self.default_db, self.admin_db}:
+ return True
+ return None
diff --git a/api/src/backend/api/specs/v1.yaml b/api/src/backend/api/specs/v1.yaml
index 8efce29fe7..adf8d333fb 100644
--- a/api/src/backend/api/specs/v1.yaml
+++ b/api/src/backend/api/specs/v1.yaml
@@ -1,7 +1,7 @@
openapi: 3.0.3
info:
title: Prowler API
- version: 1.4.0
+ version: 1.5.0
description: |-
Prowler API specification.
@@ -5029,6 +5029,35 @@ paths:
schema:
$ref: '#/components/schemas/TokenRefreshResponse'
description: ''
+ /api/v1/tokens/switch:
+ post:
+ operationId: tokens_switch_create
+ description: Switch tenant by providing a valid tenant ID. The authenticated
+ user must belong to the tenant.
+ summary: Switch tenant using a valid tenant ID
+ tags:
+ - Token
+ requestBody:
+ content:
+ application/vnd.api+json:
+ schema:
+ $ref: '#/components/schemas/TokenSwitchTenantRequest'
+ application/x-www-form-urlencoded:
+ schema:
+ $ref: '#/components/schemas/TokenSwitchTenantRequest'
+ multipart/form-data:
+ schema:
+ $ref: '#/components/schemas/TokenSwitchTenantRequest'
+ required: true
+ security:
+ - jwtAuth: []
+ responses:
+ '200':
+ content:
+ application/vnd.api+json:
+ schema:
+ $ref: '#/components/schemas/TokenSwitchTenantResponse'
+ description: ''
/api/v1/users:
get:
operationId: users_list
@@ -10080,6 +10109,81 @@ components:
$ref: '#/components/schemas/Token'
required:
- data
+ TokenSwitchTenant:
+ type: object
+ required:
+ - type
+ additionalProperties: false
+ properties:
+ type:
+ allOf:
+ - $ref: '#/components/schemas/TokenSwitchTenantTypeEnum'
+ description: The [type](https://jsonapi.org/format/#document-resource-object-identification)
+ member is used to describe resource objects that share common attributes
+ and relationships.
+ attributes:
+ type: object
+ properties:
+ tenant_id:
+ type: string
+ format: uuid
+ writeOnly: true
+ description: The tenant ID for which to request a new token.
+ access:
+ type: string
+ readOnly: true
+ refresh:
+ type: string
+ readOnly: true
+ required:
+ - tenant_id
+ TokenSwitchTenantRequest:
+ type: object
+ properties:
+ data:
+ type: object
+ required:
+ - type
+ additionalProperties: false
+ properties:
+ type:
+ type: string
+ description: The [type](https://jsonapi.org/format/#document-resource-object-identification)
+ member is used to describe resource objects that share common attributes
+ and relationships.
+ enum:
+ - tokens-switch-tenant
+ attributes:
+ type: object
+ properties:
+ tenant_id:
+ type: string
+ format: uuid
+ writeOnly: true
+ description: The tenant ID for which to request a new token.
+ access:
+ type: string
+ readOnly: true
+ minLength: 1
+ refresh:
+ type: string
+ readOnly: true
+ minLength: 1
+ required:
+ - tenant_id
+ required:
+ - data
+ TokenSwitchTenantResponse:
+ type: object
+ properties:
+ data:
+ $ref: '#/components/schemas/TokenSwitchTenant'
+ required:
+ - data
+ TokenSwitchTenantTypeEnum:
+ type: string
+ enum:
+ - tokens-switch-tenant
TokenTypeEnum:
type: string
enum:
diff --git a/api/src/backend/api/tests/integration/test_authentication.py b/api/src/backend/api/tests/integration/test_authentication.py
index 85dbbad315..f41e8207c4 100644
--- a/api/src/backend/api/tests/integration/test_authentication.py
+++ b/api/src/backend/api/tests/integration/test_authentication.py
@@ -3,6 +3,8 @@ from conftest import TEST_PASSWORD, get_api_tokens, get_authorization_header
from django.urls import reverse
from rest_framework.test import APIClient
+from api.models import Membership, User
+
@pytest.mark.django_db
def test_basic_authentication():
@@ -177,3 +179,122 @@ def test_user_me_when_inviting_users(create_test_user, tenants_fixture, roles_fi
user2_me = client.get(reverse("user-me"), headers=user2_headers)
assert user2_me.status_code == 200
assert user2_me.json()["data"]["attributes"]["email"] == user2_email
+
+
+@pytest.mark.django_db
+class TestTokenSwitchTenant:
+ def test_switch_tenant_with_valid_token(self, tenants_fixture, providers_fixture):
+ client = APIClient()
+
+ test_user = "test_email@prowler.com"
+ test_password = "test_password"
+
+ # Check that we can create a new user without any kind of authentication
+ user_creation_response = client.post(
+ reverse("user-list"),
+ data={
+ "data": {
+ "type": "users",
+ "attributes": {
+ "name": "test",
+ "email": test_user,
+ "password": test_password,
+ },
+ }
+ },
+ format="vnd.api+json",
+ )
+ assert user_creation_response.status_code == 201
+
+ # Create a new relationship between this user and another tenant
+ tenant_id = tenants_fixture[0].id
+ user_instance = User.objects.get(email=test_user)
+ Membership.objects.create(user=user_instance, tenant_id=tenant_id)
+
+ # Check that using our new user's credentials we can authenticate and get the providers
+ access_token, _ = get_api_tokens(client, test_user, test_password)
+ auth_headers = get_authorization_header(access_token)
+
+ user_me_response = client.get(
+ reverse("user-me"),
+ headers=auth_headers,
+ )
+ assert user_me_response.status_code == 200
+ # Assert this user belongs to two tenants
+ assert (
+ user_me_response.json()["data"]["relationships"]["memberships"]["meta"][
+ "count"
+ ]
+ == 2
+ )
+
+ provider_response = client.get(
+ reverse("provider-list"),
+ headers=auth_headers,
+ )
+ assert provider_response.status_code == 200
+ # Empty response since there are no providers in this tenant
+ assert not provider_response.json()["data"]
+
+ switch_tenant_response = client.post(
+ reverse("token-switch"),
+ data={
+ "data": {
+ "type": "tokens-switch-tenant",
+ "attributes": {"tenant_id": tenant_id},
+ }
+ },
+ headers=auth_headers,
+ )
+ assert switch_tenant_response.status_code == 200
+ new_access_token = switch_tenant_response.json()["data"]["attributes"]["access"]
+ new_auth_headers = get_authorization_header(new_access_token)
+
+ provider_response = client.get(
+ reverse("provider-list"),
+ headers=new_auth_headers,
+ )
+ assert provider_response.status_code == 200
+ # Now it must be data because we switched to another tenant with providers
+ assert provider_response.json()["data"]
+
+ def test_switch_tenant_with_invalid_token(self, create_test_user, tenants_fixture):
+ client = APIClient()
+
+ access_token, refresh_token = get_api_tokens(
+ client, create_test_user.email, TEST_PASSWORD
+ )
+ auth_headers = get_authorization_header(access_token)
+
+ invalid_token_response = client.post(
+ reverse("token-switch"),
+ data={
+ "data": {
+ "type": "tokens-switch-tenant",
+ "attributes": {"tenant_id": "invalid_tenant_id"},
+ }
+ },
+ headers=auth_headers,
+ )
+ assert invalid_token_response.status_code == 400
+ assert invalid_token_response.json()["errors"][0]["code"] == "invalid"
+ assert (
+ invalid_token_response.json()["errors"][0]["detail"]
+ == "Must be a valid UUID."
+ )
+
+ invalid_tenant_response = client.post(
+ reverse("token-switch"),
+ data={
+ "data": {
+ "type": "tokens-switch-tenant",
+ "attributes": {"tenant_id": tenants_fixture[-1].id},
+ }
+ },
+ headers=auth_headers,
+ )
+ assert invalid_tenant_response.status_code == 400
+ assert invalid_tenant_response.json()["errors"][0]["code"] == "invalid"
+ assert invalid_tenant_response.json()["errors"][0]["detail"] == (
+ "Tenant does not exist or user is not a " "member."
+ )
diff --git a/api/src/backend/api/utils.py b/api/src/backend/api/utils.py
index 1f60669039..9dd86daeaf 100644
--- a/api/src/backend/api/utils.py
+++ b/api/src/backend/api/utils.py
@@ -1,15 +1,25 @@
from datetime import datetime, timezone
+from allauth.socialaccount.providers.oauth2.client import OAuth2Client
+from rest_framework.exceptions import NotFound, ValidationError
+
+from api.db_router import MainRouter
+from api.exceptions import InvitationTokenExpiredException
+from api.models import Invitation, Provider
from prowler.providers.aws.aws_provider import AwsProvider
from prowler.providers.azure.azure_provider import AzureProvider
from prowler.providers.common.models import Connection
from prowler.providers.gcp.gcp_provider import GcpProvider
from prowler.providers.kubernetes.kubernetes_provider import KubernetesProvider
-from rest_framework.exceptions import ValidationError, NotFound
-from api.db_router import MainRouter
-from api.exceptions import InvitationTokenExpiredException
-from api.models import Provider, Invitation
+
+class CustomOAuth2Client(OAuth2Client):
+ def __init__(self, client_id, secret, *args, **kwargs):
+ # Remove any duplicate "scope_delimiter" from kwargs
+ # Bug present in dj-rest-auth after version v7.0.1
+ # https://github.com/iMerica/dj-rest-auth/issues/673
+ kwargs.pop("scope_delimiter", None)
+ super().__init__(client_id, secret, *args, **kwargs)
def merge_dicts(default_dict: dict, replacement_dict: dict) -> dict:
diff --git a/api/src/backend/api/v1/serializers.py b/api/src/backend/api/v1/serializers.py
index 5a6f3ed671..5705d30c32 100644
--- a/api/src/backend/api/v1/serializers.py
+++ b/api/src/backend/api/v1/serializers.py
@@ -38,7 +38,65 @@ from api.rls import Tenant
# Tokens
-class TokenSerializer(TokenObtainPairSerializer):
+def generate_tokens(user: User, tenant_id: str) -> dict:
+ try:
+ refresh = RefreshToken.for_user(user)
+ except InvalidKeyError:
+ # Handle invalid key error
+ raise ValidationError(
+ {
+ "detail": "Token generation failed due to invalid key configuration. Provide valid "
+ "DJANGO_TOKEN_SIGNING_KEY and DJANGO_TOKEN_VERIFYING_KEY in the environment."
+ }
+ )
+ except Exception as e:
+ raise ValidationError({"detail": str(e)})
+
+ # Post-process the tokens
+ # Set the tenant_id
+ refresh["tenant_id"] = tenant_id
+
+ # Set the nbf (not before) claim to the iat (issued at) claim. At this moment, simplejwt does not provide a
+ # way to set the nbf claim
+ refresh.payload["nbf"] = refresh["iat"]
+
+ # Get the access token
+ access = refresh.access_token
+
+ if settings.SIMPLE_JWT["UPDATE_LAST_LOGIN"]:
+ update_last_login(None, user)
+
+ return {"access": str(access), "refresh": str(refresh)}
+
+
+class BaseTokenSerializer(TokenObtainPairSerializer):
+ def custom_validate(self, attrs, social: bool = False):
+ email = attrs.get("email")
+ password = attrs.get("password")
+ tenant_id = str(attrs.get("tenant_id", ""))
+
+ # Authenticate user
+ user = (
+ User.objects.get(email=email)
+ if social
+ else authenticate(username=email, password=password)
+ )
+ if user is None:
+ raise ValidationError("Invalid credentials")
+
+ if tenant_id:
+ if not user.is_member_of_tenant(tenant_id):
+ raise ValidationError("Tenant does not exist or user is not a member.")
+ else:
+ first_membership = user.memberships.order_by("date_joined").first()
+ if first_membership is None:
+ raise ValidationError("User has no memberships.")
+ tenant_id = str(first_membership.tenant_id)
+
+ return generate_tokens(user, tenant_id)
+
+
+class TokenSerializer(BaseTokenSerializer):
email = serializers.EmailField(write_only=True)
password = serializers.CharField(write_only=True)
tenant_id = serializers.UUIDField(
@@ -56,53 +114,25 @@ class TokenSerializer(TokenObtainPairSerializer):
resource_name = "tokens"
def validate(self, attrs):
- email = attrs.get("email")
- password = attrs.get("password")
- tenant_id = str(attrs.get("tenant_id", ""))
+ return super().custom_validate(attrs)
- # Authenticate user
- user = authenticate(username=email, password=password)
- if user is None:
- raise ValidationError("Invalid credentials")
- if tenant_id:
- if not user.is_member_of_tenant(tenant_id):
- raise ValidationError("Tenant does not exist or user is not a member.")
- else:
- first_membership = user.memberships.order_by("date_joined").first()
- if first_membership is None:
- raise ValidationError("User has no memberships.")
- tenant_id = str(first_membership.tenant_id)
+class TokenSocialLoginSerializer(BaseTokenSerializer):
+ email = serializers.EmailField(write_only=True)
- # Generate tokens
- try:
- refresh = RefreshToken.for_user(user)
- except InvalidKeyError:
- # Handle invalid key error
- raise ValidationError(
- {
- "detail": "Token generation failed due to invalid key configuration. Provide valid "
- "DJANGO_TOKEN_SIGNING_KEY and DJANGO_TOKEN_VERIFYING_KEY in the environment."
- }
- )
- except Exception as e:
- raise ValidationError({"detail": str(e)})
+ # Output tokens
+ refresh = serializers.CharField(read_only=True)
+ access = serializers.CharField(read_only=True)
- # Post-process the tokens
- # Set the tenant_id
- refresh["tenant_id"] = tenant_id
+ class JSONAPIMeta:
+ resource_name = "tokens"
- # Set the nbf (not before) claim to the iat (issued at) claim. At this moment, simplejwt does not provide a
- # way to set the nbf claim
- refresh.payload["nbf"] = refresh["iat"]
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.fields.pop("password", None)
- # Get the access token
- access = refresh.access_token
-
- if settings.SIMPLE_JWT["UPDATE_LAST_LOGIN"]:
- update_last_login(None, user)
-
- return {"access": str(access), "refresh": str(refresh)}
+ def validate(self, attrs):
+ return super().custom_validate(attrs, social=True)
# TODO: Check if we can change the parent class to TokenRefreshSerializer from rest_framework_simplejwt.serializers
@@ -140,6 +170,30 @@ class TokenRefreshSerializer(serializers.Serializer):
raise ValidationError({"refresh": "Invalid or expired token"})
+class TokenSwitchTenantSerializer(serializers.Serializer):
+ tenant_id = serializers.UUIDField(
+ write_only=True, help_text="The tenant ID for which to request a new token."
+ )
+ access = serializers.CharField(read_only=True)
+ refresh = serializers.CharField(read_only=True)
+
+ class JSONAPIMeta:
+ resource_name = "tokens-switch-tenant"
+
+ def validate(self, attrs):
+ request = self.context["request"]
+ user = request.user
+
+ if not user.is_authenticated:
+ raise ValidationError("Invalid or expired token.")
+
+ tenant_id = str(attrs.get("tenant_id"))
+ if not user.is_member_of_tenant(tenant_id):
+ raise ValidationError("Tenant does not exist or user is not a member.")
+
+ return generate_tokens(user, tenant_id)
+
+
# Base
diff --git a/api/src/backend/api/v1/urls.py b/api/src/backend/api/v1/urls.py
index 4ff587d913..f26e0cb8f3 100644
--- a/api/src/backend/api/v1/urls.py
+++ b/api/src/backend/api/v1/urls.py
@@ -6,7 +6,10 @@ from api.v1.views import (
ComplianceOverviewViewSet,
CustomTokenObtainView,
CustomTokenRefreshView,
+ CustomTokenSwitchTenantView,
FindingViewSet,
+ GithubSocialLoginView,
+ GoogleSocialLoginView,
InvitationAcceptViewSet,
InvitationViewSet,
MembershipViewSet,
@@ -56,6 +59,7 @@ users_router.register(r"memberships", MembershipViewSet, basename="user-membersh
urlpatterns = [
path("tokens", CustomTokenObtainView.as_view(), name="token-obtain"),
path("tokens/refresh", CustomTokenRefreshView.as_view(), name="token-refresh"),
+ path("tokens/switch", CustomTokenSwitchTenantView.as_view(), name="token-switch"),
path(
"providers/secrets",
ProviderSecretViewSet.as_view({"get": "list", "post": "create"}),
@@ -106,6 +110,8 @@ urlpatterns = [
),
name="provider_group-providers-relationship",
),
+ path("tokens/google", GoogleSocialLoginView.as_view(), name="token-google"),
+ path("tokens/github", GithubSocialLoginView.as_view(), name="token-github"),
path("", include(router.urls)),
path("", include(tenants_router.urls)),
path("", include(users_router.urls)),
diff --git a/api/src/backend/api/v1/views.py b/api/src/backend/api/v1/views.py
index 973c800be5..4d5cc6d59f 100644
--- a/api/src/backend/api/v1/views.py
+++ b/api/src/backend/api/v1/views.py
@@ -5,6 +5,14 @@ import boto3
from botocore.exceptions import ClientError, NoCredentialsError, ParamValidationError
from celery.result import AsyncResult
from config.env import env
+from allauth.socialaccount.providers.github.views import GitHubOAuth2Adapter
+from allauth.socialaccount.providers.google.views import GoogleOAuth2Adapter
+from celery.result import AsyncResult
+from config.settings.social_login import (
+ GITHUB_OAUTH_CALLBACK_URL,
+ GOOGLE_OAUTH_CALLBACK_URL,
+)
+from dj_rest_auth.registration.views import SocialLoginView
from django.conf import settings as django_settings
from django.contrib.postgres.aggregates import ArrayAgg
from django.contrib.postgres.search import SearchQuery
@@ -88,7 +96,7 @@ from api.pagination import ComplianceOverviewPagination
from api.rbac.permissions import Permissions, get_providers, get_role
from api.rls import Tenant
from tasks.jobs.export import get_s3_client
-from api.utils import validate_invitation
+from api.utils import CustomOAuth2Client, validate_invitation
from api.uuid_utils import datetime_to_uuid7
from api.v1.serializers import (
ComplianceOverviewFullSerializer,
@@ -129,6 +137,8 @@ from api.v1.serializers import (
TenantSerializer,
TokenRefreshSerializer,
TokenSerializer,
+ TokenSocialLoginSerializer,
+ TokenSwitchTenantSerializer,
UserCreateSerializer,
UserRoleRelationshipSerializer,
UserSerializer,
@@ -195,13 +205,43 @@ class CustomTokenRefreshView(GenericAPIView):
)
+@extend_schema(
+ tags=["Token"],
+ summary="Switch tenant using a valid tenant ID",
+ description="Switch tenant by providing a valid tenant ID. The authenticated user must belong to the tenant.",
+)
+class CustomTokenSwitchTenantView(GenericAPIView):
+ permission_classes = [permissions.IsAuthenticated]
+ resource_name = "tokens-switch-tenant"
+ serializer_class = TokenSwitchTenantSerializer
+ http_method_names = ["post"]
+
+ def post(self, request):
+ serializer = TokenSwitchTenantSerializer(
+ data=request.data, context={"request": request}
+ )
+
+ try:
+ serializer.is_valid(raise_exception=True)
+ except TokenError as e:
+ raise InvalidToken(e.args[0])
+
+ return Response(
+ data={
+ "type": "tokens-switch-tenant",
+ "attributes": serializer.validated_data,
+ },
+ status=status.HTTP_200_OK,
+ )
+
+
@extend_schema(exclude=True)
class SchemaView(SpectacularAPIView):
serializer_class = None
def get(self, request, *args, **kwargs):
spectacular_settings.TITLE = "Prowler API"
- spectacular_settings.VERSION = "1.4.0"
+ spectacular_settings.VERSION = "1.5.0"
spectacular_settings.DESCRIPTION = (
"Prowler API specification.\n\nThis file is auto-generated."
)
@@ -261,6 +301,58 @@ class SchemaView(SpectacularAPIView):
return super().get(request, *args, **kwargs)
+@extend_schema(exclude=True)
+class GoogleSocialLoginView(SocialLoginView):
+ adapter_class = GoogleOAuth2Adapter
+ client_class = CustomOAuth2Client
+ callback_url = GOOGLE_OAUTH_CALLBACK_URL
+
+ def get_response(self):
+ original_response = super().get_response()
+
+ if self.user and self.user.is_authenticated:
+ serializer = TokenSocialLoginSerializer(data={"email": self.user.email})
+ try:
+ serializer.is_valid(raise_exception=True)
+ except TokenError as e:
+ raise InvalidToken(e.args[0])
+ return Response(
+ data={
+ "type": "google-social-tokens",
+ "attributes": serializer.validated_data,
+ },
+ status=status.HTTP_200_OK,
+ )
+ return original_response
+
+
+@extend_schema(exclude=True)
+class GithubSocialLoginView(SocialLoginView):
+ adapter_class = GitHubOAuth2Adapter
+ client_class = CustomOAuth2Client
+ callback_url = GITHUB_OAUTH_CALLBACK_URL
+
+ def get_response(self):
+ original_response = super().get_response()
+
+ if self.user and self.user.is_authenticated:
+ serializer = TokenSocialLoginSerializer(data={"email": self.user.email})
+
+ try:
+ serializer.is_valid(raise_exception=True)
+ except TokenError as e:
+ raise InvalidToken(e.args[0])
+
+ return Response(
+ data={
+ "type": "github-social-tokens",
+ "attributes": serializer.validated_data,
+ },
+ status=status.HTTP_200_OK,
+ )
+ return original_response
+
+
@extend_schema_view(
list=extend_schema(
tags=["User"],
diff --git a/api/src/backend/config/django/base.py b/api/src/backend/config/django/base.py
index 95a1854ef6..8641a09af1 100644
--- a/api/src/backend/config/django/base.py
+++ b/api/src/backend/config/django/base.py
@@ -4,6 +4,7 @@ from config.custom_logging import LOGGING # noqa
from config.env import BASE_DIR, env # noqa
from config.settings.celery import * # noqa
from config.settings.partitions import * # noqa
+from config.settings.social_login import * # noqa
SECRET_KEY = env("SECRET_KEY", default="secret")
DEBUG = env.bool("DJANGO_DEBUG", default=False)
@@ -29,6 +30,13 @@ INSTALLED_APPS = [
"django_celery_results",
"django_celery_beat",
"rest_framework_simplejwt.token_blacklist",
+ "allauth",
+ "allauth.account",
+ "allauth.socialaccount",
+ "allauth.socialaccount.providers.google",
+ "allauth.socialaccount.providers.github",
+ "dj_rest_auth.registration",
+ "rest_framework.authtoken",
]
MIDDLEWARE = [
@@ -42,8 +50,11 @@ MIDDLEWARE = [
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"api.middleware.APILoggingMiddleware",
+ "allauth.account.middleware.AccountMiddleware",
]
+SITE_ID = 1
+
CORS_ALLOWED_ORIGINS = ["http://localhost", "http://127.0.0.1"]
ROOT_URLCONF = "config.urls"
diff --git a/api/src/backend/config/settings/social_login.py b/api/src/backend/config/settings/social_login.py
new file mode 100644
index 0000000000..035a166279
--- /dev/null
+++ b/api/src/backend/config/settings/social_login.py
@@ -0,0 +1,53 @@
+from config.env import env
+
+# Google Oauth settings
+GOOGLE_OAUTH_CLIENT_ID = env("DJANGO_GOOGLE_OAUTH_CLIENT_ID", default="")
+GOOGLE_OAUTH_CLIENT_SECRET = env("DJANGO_GOOGLE_OAUTH_CLIENT_SECRET", default="")
+GOOGLE_OAUTH_CALLBACK_URL = env("DJANGO_GOOGLE_OAUTH_CALLBACK_URL", default="")
+
+GITHUB_OAUTH_CLIENT_ID = env("DJANGO_GITHUB_OAUTH_CLIENT_ID", default="")
+GITHUB_OAUTH_CLIENT_SECRET = env("DJANGO_GITHUB_OAUTH_CLIENT_SECRET", default="")
+GITHUB_OAUTH_CALLBACK_URL = env("DJANGO_GITHUB_OAUTH_CALLBACK_URL", default="")
+
+# Allauth settings
+ACCOUNT_LOGIN_METHODS = {"email"} # Use Email / Password authentication
+ACCOUNT_USERNAME_REQUIRED = False
+ACCOUNT_EMAIL_REQUIRED = True
+ACCOUNT_EMAIL_VERIFICATION = "none" # Do not require email confirmation
+ACCOUNT_USER_MODEL_USERNAME_FIELD = None
+REST_AUTH = {
+ "TOKEN_MODEL": None,
+ "REST_USE_JWT": True,
+}
+# django-allauth (social)
+# Authenticate if local account with this email address already exists
+SOCIALACCOUNT_EMAIL_AUTHENTICATION = True
+# Connect local account and social account if local account with that email address already exists
+SOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECT = True
+SOCIALACCOUNT_ADAPTER = "api.adapters.ProwlerSocialAccountAdapter"
+SOCIALACCOUNT_PROVIDERS = {
+ "google": {
+ "APP": {
+ "client_id": GOOGLE_OAUTH_CLIENT_ID,
+ "secret": GOOGLE_OAUTH_CLIENT_SECRET,
+ "key": "",
+ },
+ "SCOPE": [
+ "email",
+ "profile",
+ ],
+ "AUTH_PARAMS": {
+ "access_type": "online",
+ },
+ },
+ "github": {
+ "APP": {
+ "client_id": GITHUB_OAUTH_CLIENT_ID,
+ "secret": GITHUB_OAUTH_CLIENT_SECRET,
+ },
+ "SCOPE": [
+ "user",
+ "read:org",
+ ],
+ },
+}
diff --git a/docs/tutorials/kubernetes/in-cluster.md b/docs/tutorials/kubernetes/in-cluster.md
index ae733db84a..9b16b21f09 100644
--- a/docs/tutorials/kubernetes/in-cluster.md
+++ b/docs/tutorials/kubernetes/in-cluster.md
@@ -2,6 +2,7 @@
For in-cluster execution, you can use the supplied yaml files inside `/kubernetes`:
+* [prowler-sa.yaml](https://github.com/prowler-cloud/prowler/blob/master/kubernetes/prowler-sa.yaml)
* [job.yaml](https://github.com/prowler-cloud/prowler/blob/master/kubernetes/job.yaml)
* [prowler-role.yaml](https://github.com/prowler-cloud/prowler/blob/master/kubernetes/prowler-role.yaml)
* [prowler-rolebinding.yaml](https://github.com/prowler-cloud/prowler/blob/master/kubernetes/prowler-rolebinding.yaml)
diff --git a/docs/tutorials/prowler-app.md b/docs/tutorials/prowler-app.md
index d4d3d2f890..cd94504579 100644
--- a/docs/tutorials/prowler-app.md
+++ b/docs/tutorials/prowler-app.md
@@ -100,9 +100,11 @@ By default, the `kubeconfig` file is located at `~/.kube/config`.
???+ note
- If you are adding an **Amazon EKS** cluster, follow these additional steps to ensure proper authentication:
+ If you are adding an **EKS**, **GKE**, **AKS** or external cluster, follow these additional steps to ensure proper authentication:
- 1. Apply the necessary Kubernetes resources to your EKS cluster (you can find the files in the [`kubernetes` directory of the Prowler repository](https://github.com/prowler-cloud/prowler/tree/master/kubernetes)):
+ ** Make sure your cluster allow traffic from the Prowler Cloud IP address `52.48.254.174/32` **
+
+ 1. Apply the necessary Kubernetes resources to your EKS, GKE, AKS or external cluster (you can find the files in the [`kubernetes` directory of the Prowler repository](https://github.com/prowler-cloud/prowler/tree/master/kubernetes)):
```console
kubectl apply -f kubernetes/prowler-sa.yaml
kubectl apply -f kubernetes/prowler-role.yaml
@@ -119,11 +121,11 @@ By default, the `kubeconfig` file is located at `~/.kube/config`.
3. Update your `kubeconfig` to use the ServiceAccount token:
```console
kubectl config set-credentials prowler-sa --token=
- kubectl config set-context --user=prowler-sa
+ kubectl config set-context --user=prowler-sa
```
- Replace with the generated token and with your EKS cluster ARN.
+ Replace with the generated token and with your KubeConfig Context Name of your EKS, GKE or AKS cluster.
- 4. Now you can add the modified `kubeconfig` as the credentials of the AWS EKS Cluster in Prowler Cloud. Then simply test the connection.
+ 4. Now you can add the modified `kubeconfig` in Prowler Cloud. Then simply test the connection.
---
diff --git a/poetry.lock b/poetry.lock
index 1697f1d1fd..80468d7c88 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1693,13 +1693,13 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"]
[[package]]
name = "google-api-python-client"
-version = "2.160.0"
+version = "2.161.0"
description = "Google API Client Library for Python"
optional = false
python-versions = ">=3.7"
files = [
- {file = "google_api_python_client-2.160.0-py2.py3-none-any.whl", hash = "sha256:63d61fb3e4cf3fb31a70a87f45567c22f6dfe87bbfa27252317e3e2c42900db4"},
- {file = "google_api_python_client-2.160.0.tar.gz", hash = "sha256:a8ccafaecfa42d15d5b5c3134ced8de08380019717fc9fb1ed510ca58eca3b7e"},
+ {file = "google_api_python_client-2.161.0-py2.py3-none-any.whl", hash = "sha256:9476a5a4f200bae368140453df40f9cda36be53fa7d0e9a9aac4cdb859a26448"},
+ {file = "google_api_python_client-2.161.0.tar.gz", hash = "sha256:324c0cce73e9ea0a0d2afd5937e01b7c2d6a4d7e2579cdb6c384f9699d6c9f37"},
]
[package.dependencies]
@@ -2537,13 +2537,13 @@ dev = ["click", "codecov", "mkdocs-gen-files", "mkdocs-git-authors-plugin", "mkd
[[package]]
name = "mkdocs-material"
-version = "9.6.3"
+version = "9.6.4"
description = "Documentation that simply works"
optional = false
python-versions = ">=3.8"
files = [
- {file = "mkdocs_material-9.6.3-py3-none-any.whl", hash = "sha256:1125622067e26940806701219303b27c0933e04533560725d97ec26fd16a39cf"},
- {file = "mkdocs_material-9.6.3.tar.gz", hash = "sha256:c87f7d1c39ce6326da5e10e232aed51bae46252e646755900f4b0fc9192fa832"},
+ {file = "mkdocs_material-9.6.4-py3-none-any.whl", hash = "sha256:414e8376551def6d644b8e6f77226022868532a792eb2c9accf52199009f568f"},
+ {file = "mkdocs_material-9.6.4.tar.gz", hash = "sha256:4d1d35e1c1d3e15294cb7fa5d02e0abaee70d408f75027dc7be6e30fb32e6867"},
]
[package.dependencies]
@@ -5091,4 +5091,4 @@ type = ["pytest-mypy"]
[metadata]
lock-version = "2.0"
python-versions = ">=3.9,<3.13"
-content-hash = "7182befee2c0494d066d4715935ee4aaf6a7dfe24e6cfea429be90fad03c4024"
+content-hash = "f20ee317a5d1cb29b16bf7bb0e8a7c6532358042c5164ca99c2cc4763641eb73"
diff --git a/prowler/compliance/gcp/cis_3.0_gcp.json b/prowler/compliance/gcp/cis_3.0_gcp.json
index 6ae75a260e..a0e860e81d 100644
--- a/prowler/compliance/gcp/cis_3.0_gcp.json
+++ b/prowler/compliance/gcp/cis_3.0_gcp.json
@@ -222,7 +222,7 @@
"Id": "1.11",
"Description": "Ensure That Separation of Duties Is Enforced While Assigning KMS Related Roles to Users",
"Checks": [
- "iam_role_kms_enforce_separation_of_duties”"
+ "iam_role_kms_enforce_separation_of_duties"
],
"Attributes": [
{
@@ -394,7 +394,7 @@
"Id": "2.2",
"Description": "Ensure That Sinks Are Configured for All Log Entries",
"Checks": [
- "cloudstorage_bucket_log_retention_policy_lock”"
+ "cloudstorage_bucket_log_retention_policy_lock"
],
"Attributes": [
{
diff --git a/prowler/providers/aws/aws_regions_by_service.json b/prowler/providers/aws/aws_regions_by_service.json
index 4e05963af0..29f4541927 100644
--- a/prowler/providers/aws/aws_regions_by_service.json
+++ b/prowler/providers/aws/aws_regions_by_service.json
@@ -9737,6 +9737,7 @@
"ap-southeast-3",
"ap-southeast-4",
"ap-southeast-5",
+ "ap-southeast-7",
"ca-central-1",
"ca-west-1",
"eu-central-1",
@@ -9750,6 +9751,7 @@
"il-central-1",
"me-central-1",
"me-south-1",
+ "mx-central-1",
"sa-east-1",
"us-east-1",
"us-east-2",
diff --git a/prowler/providers/aws/services/awslambda/awslambda_function_not_publicly_accessible/awslambda_function_not_publicly_accessible.py b/prowler/providers/aws/services/awslambda/awslambda_function_not_publicly_accessible/awslambda_function_not_publicly_accessible.py
index 86c3cbfec8..43bad4a686 100644
--- a/prowler/providers/aws/services/awslambda/awslambda_function_not_publicly_accessible/awslambda_function_not_publicly_accessible.py
+++ b/prowler/providers/aws/services/awslambda/awslambda_function_not_publicly_accessible/awslambda_function_not_publicly_accessible.py
@@ -7,6 +7,8 @@ class awslambda_function_not_publicly_accessible(Check):
def execute(self):
findings = []
for function in awslambda_client.functions.values():
+ if function.policy is None:
+ continue
report = Check_Report_AWS(metadata=self.metadata(), resource=function)
report.status = "PASS"
diff --git a/prowler/providers/aws/services/cloudfront/cloudfront_distributions_s3_origin_non_existent_bucket/cloudfront_distributions_s3_origin_non_existent_bucket.py b/prowler/providers/aws/services/cloudfront/cloudfront_distributions_s3_origin_non_existent_bucket/cloudfront_distributions_s3_origin_non_existent_bucket.py
index a9da703239..ea932ba027 100644
--- a/prowler/providers/aws/services/cloudfront/cloudfront_distributions_s3_origin_non_existent_bucket/cloudfront_distributions_s3_origin_non_existent_bucket.py
+++ b/prowler/providers/aws/services/cloudfront/cloudfront_distributions_s3_origin_non_existent_bucket/cloudfront_distributions_s3_origin_non_existent_bucket.py
@@ -16,7 +16,7 @@ class cloudfront_distributions_s3_origin_non_existent_bucket(Check):
for origin in distribution.origins:
if origin.s3_origin_config:
- bucket_name = origin.domain_name.split(".")[0]
+ bucket_name = origin.domain_name.split(".s3")[0]
if not s3_client._head_bucket(bucket_name):
non_existent_buckets.append(bucket_name)
diff --git a/prowler/providers/aws/services/codebuild/codebuild_service.py b/prowler/providers/aws/services/codebuild/codebuild_service.py
index 1ed4226586..a1e00790bb 100644
--- a/prowler/providers/aws/services/codebuild/codebuild_service.py
+++ b/prowler/providers/aws/services/codebuild/codebuild_service.py
@@ -84,12 +84,13 @@ class Codebuild(AWSService):
if project_info["source"]["type"] != "NO_SOURCE":
project.source = Source(
type=project_info["source"]["type"],
- location=project_info["source"]["location"],
+ location=project_info["source"].get("location", ""),
)
project.secondary_sources = []
for secondary_source in project_info.get("secondarySources", []):
source_obj = Source(
- type=secondary_source["type"], location=secondary_source["location"]
+ type=secondary_source["type"],
+ location=secondary_source.get("location", ""),
)
project.secondary_sources.append(source_obj)
environment = project_info.get("environment", {})
diff --git a/prowler/providers/aws/services/dynamodb/dynamodb_table_cross_account_access/dynamodb_table_cross_account_access.py b/prowler/providers/aws/services/dynamodb/dynamodb_table_cross_account_access/dynamodb_table_cross_account_access.py
index da161a1c0b..e3c1e817ca 100644
--- a/prowler/providers/aws/services/dynamodb/dynamodb_table_cross_account_access/dynamodb_table_cross_account_access.py
+++ b/prowler/providers/aws/services/dynamodb/dynamodb_table_cross_account_access/dynamodb_table_cross_account_access.py
@@ -7,6 +7,8 @@ class dynamodb_table_cross_account_access(Check):
def execute(self):
findings = []
for table in dynamodb_client.tables.values():
+ if table.policy is None:
+ continue
report = Check_Report_AWS(metadata=self.metadata(), resource=table)
report.status = "PASS"
report.status_extended = (
diff --git a/prowler/providers/aws/services/ecr/ecr_repositories_not_publicly_accessible/ecr_repositories_not_publicly_accessible.py b/prowler/providers/aws/services/ecr/ecr_repositories_not_publicly_accessible/ecr_repositories_not_publicly_accessible.py
index aa332926a5..484e7a1631 100644
--- a/prowler/providers/aws/services/ecr/ecr_repositories_not_publicly_accessible/ecr_repositories_not_publicly_accessible.py
+++ b/prowler/providers/aws/services/ecr/ecr_repositories_not_publicly_accessible/ecr_repositories_not_publicly_accessible.py
@@ -8,6 +8,8 @@ class ecr_repositories_not_publicly_accessible(Check):
findings = []
for registry in ecr_client.registries.values():
for repository in registry.repositories:
+ if repository.policy is None:
+ continue
report = Check_Report_AWS(metadata=self.metadata(), resource=repository)
report.status = "PASS"
report.status_extended = (
diff --git a/prowler/providers/aws/services/ecr/ecr_service.py b/prowler/providers/aws/services/ecr/ecr_service.py
index 9cd6846d6d..c892ad1fca 100644
--- a/prowler/providers/aws/services/ecr/ecr_service.py
+++ b/prowler/providers/aws/services/ecr/ecr_service.py
@@ -87,6 +87,7 @@ class ECR(AWSService):
logger.warning(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
+ repository.policy = {}
except Exception as error:
if "RepositoryPolicyNotFoundException" not in str(error):
diff --git a/prowler/providers/aws/services/efs/efs_not_publicly_accessible/efs_not_publicly_accessible.py b/prowler/providers/aws/services/efs/efs_not_publicly_accessible/efs_not_publicly_accessible.py
index 2db08c396d..bf12756b20 100644
--- a/prowler/providers/aws/services/efs/efs_not_publicly_accessible/efs_not_publicly_accessible.py
+++ b/prowler/providers/aws/services/efs/efs_not_publicly_accessible/efs_not_publicly_accessible.py
@@ -7,6 +7,8 @@ class efs_not_publicly_accessible(Check):
def execute(self):
findings = []
for fs in efs_client.filesystems.values():
+ if fs.policy is None:
+ continue
report = Check_Report_AWS(metadata=self.metadata(), resource=fs)
report.status = "PASS"
report.status_extended = f"EFS {fs.id} has a policy which does not allow access to any client within the VPC."
diff --git a/prowler/providers/aws/services/efs/efs_service.py b/prowler/providers/aws/services/efs/efs_service.py
index 51570de3c9..38b7f03073 100644
--- a/prowler/providers/aws/services/efs/efs_service.py
+++ b/prowler/providers/aws/services/efs/efs_service.py
@@ -164,7 +164,7 @@ class FileSystem(BaseModel):
id: str
arn: str
region: str
- policy: Optional[dict] = {}
+ policy: Optional[dict]
backup_policy: Optional[str] = "DISABLED"
encrypted: bool
availability_zone_id: Optional[str]
diff --git a/prowler/providers/aws/services/eventbridge/eventbridge_bus_cross_account_access/eventbridge_bus_cross_account_access.py b/prowler/providers/aws/services/eventbridge/eventbridge_bus_cross_account_access/eventbridge_bus_cross_account_access.py
index d78b46fc52..5fee9d0775 100644
--- a/prowler/providers/aws/services/eventbridge/eventbridge_bus_cross_account_access/eventbridge_bus_cross_account_access.py
+++ b/prowler/providers/aws/services/eventbridge/eventbridge_bus_cross_account_access/eventbridge_bus_cross_account_access.py
@@ -9,6 +9,8 @@ class eventbridge_bus_cross_account_access(Check):
def execute(self):
findings = []
for bus in eventbridge_client.buses.values():
+ if bus.policy is None:
+ continue
report = Check_Report_AWS(metadata=self.metadata(), resource=bus)
report.status = "PASS"
report.status_extended = (
diff --git a/prowler/providers/aws/services/eventbridge/eventbridge_bus_exposed/eventbridge_bus_exposed.py b/prowler/providers/aws/services/eventbridge/eventbridge_bus_exposed/eventbridge_bus_exposed.py
index d0803c1805..53797202a6 100644
--- a/prowler/providers/aws/services/eventbridge/eventbridge_bus_exposed/eventbridge_bus_exposed.py
+++ b/prowler/providers/aws/services/eventbridge/eventbridge_bus_exposed/eventbridge_bus_exposed.py
@@ -9,6 +9,8 @@ class eventbridge_bus_exposed(Check):
def execute(self):
findings = []
for bus in eventbridge_client.buses.values():
+ if bus.policy is None:
+ continue
report = Check_Report_AWS(metadata=self.metadata(), resource=bus)
report.status = "PASS"
report.status_extended = (
diff --git a/prowler/providers/aws/services/eventbridge/eventbridge_schema_registry_cross_account_access/eventbridge_schema_registry_cross_account_access.py b/prowler/providers/aws/services/eventbridge/eventbridge_schema_registry_cross_account_access/eventbridge_schema_registry_cross_account_access.py
index 910cc5580c..c3a2a29377 100644
--- a/prowler/providers/aws/services/eventbridge/eventbridge_schema_registry_cross_account_access/eventbridge_schema_registry_cross_account_access.py
+++ b/prowler/providers/aws/services/eventbridge/eventbridge_schema_registry_cross_account_access/eventbridge_schema_registry_cross_account_access.py
@@ -7,6 +7,8 @@ class eventbridge_schema_registry_cross_account_access(Check):
def execute(self):
findings = []
for registry in schema_client.registries.values():
+ if registry.policy is None:
+ continue
report = Check_Report_AWS(metadata=self.metadata(), resource=registry)
report.status = "PASS"
report.status_extended = f"EventBridge schema registry {registry.name} does not allow cross-account access."
diff --git a/prowler/providers/aws/services/glue/glue_data_catalogs_not_publicly_accessible/glue_data_catalogs_not_publicly_accessible.py b/prowler/providers/aws/services/glue/glue_data_catalogs_not_publicly_accessible/glue_data_catalogs_not_publicly_accessible.py
index 6ad4645320..1c6be44aae 100644
--- a/prowler/providers/aws/services/glue/glue_data_catalogs_not_publicly_accessible/glue_data_catalogs_not_publicly_accessible.py
+++ b/prowler/providers/aws/services/glue/glue_data_catalogs_not_publicly_accessible/glue_data_catalogs_not_publicly_accessible.py
@@ -7,6 +7,8 @@ class glue_data_catalogs_not_publicly_accessible(Check):
def execute(self):
findings = []
for data_catalog in glue_client.data_catalogs.values():
+ if data_catalog.policy is None:
+ continue
report = Check_Report_AWS(metadata=self.metadata(), resource=data_catalog)
report.resource_id = glue_client.audited_account
report.resource_arn = glue_client._get_data_catalog_arn_template(
diff --git a/prowler/providers/aws/services/opensearch/opensearch_service_domains_not_publicly_accessible/opensearch_service_domains_not_publicly_accessible.py b/prowler/providers/aws/services/opensearch/opensearch_service_domains_not_publicly_accessible/opensearch_service_domains_not_publicly_accessible.py
index 2ffd9c40fb..19a890620b 100644
--- a/prowler/providers/aws/services/opensearch/opensearch_service_domains_not_publicly_accessible/opensearch_service_domains_not_publicly_accessible.py
+++ b/prowler/providers/aws/services/opensearch/opensearch_service_domains_not_publicly_accessible/opensearch_service_domains_not_publicly_accessible.py
@@ -17,7 +17,7 @@ class opensearch_service_domains_not_publicly_accessible(Check):
if domain.vpc_id:
report.status_extended = f"Opensearch domain {domain.name} is in a VPC, then it is not publicly accessible."
- elif domain.access_policy and is_policy_public(
+ elif domain.access_policy is not None and is_policy_public(
domain.access_policy, opensearch_client.audited_account
):
report.status = "FAIL"
diff --git a/prowler/providers/aws/services/s3/s3_bucket_cross_account_access/s3_bucket_cross_account_access.py b/prowler/providers/aws/services/s3/s3_bucket_cross_account_access/s3_bucket_cross_account_access.py
index a98483c767..3178a08aa1 100644
--- a/prowler/providers/aws/services/s3/s3_bucket_cross_account_access/s3_bucket_cross_account_access.py
+++ b/prowler/providers/aws/services/s3/s3_bucket_cross_account_access/s3_bucket_cross_account_access.py
@@ -7,6 +7,8 @@ class s3_bucket_cross_account_access(Check):
def execute(self):
findings = []
for bucket in s3_client.buckets.values():
+ if bucket.policy is None:
+ continue
report = Check_Report_AWS(metadata=self.metadata(), resource=bucket)
report.status = "PASS"
report.status_extended = f"S3 Bucket {bucket.name} has a bucket policy but it does not allow cross account access."
diff --git a/prowler/providers/aws/services/s3/s3_bucket_policy_public_write_access/s3_bucket_policy_public_write_access.py b/prowler/providers/aws/services/s3/s3_bucket_policy_public_write_access/s3_bucket_policy_public_write_access.py
index 4fef8cf330..5ab17358dc 100644
--- a/prowler/providers/aws/services/s3/s3_bucket_policy_public_write_access/s3_bucket_policy_public_write_access.py
+++ b/prowler/providers/aws/services/s3/s3_bucket_policy_public_write_access/s3_bucket_policy_public_write_access.py
@@ -8,6 +8,8 @@ class s3_bucket_policy_public_write_access(Check):
def execute(self):
findings = []
for bucket in s3_client.buckets.values():
+ if bucket.policy is None:
+ continue
report = Check_Report_AWS(metadata=self.metadata(), resource=bucket)
# Check if bucket policy allow public write access
if not bucket.policy:
diff --git a/prowler/providers/aws/services/s3/s3_bucket_public_access/s3_bucket_public_access.py b/prowler/providers/aws/services/s3/s3_bucket_public_access/s3_bucket_public_access.py
index 8a77958863..77b83f03cd 100644
--- a/prowler/providers/aws/services/s3/s3_bucket_public_access/s3_bucket_public_access.py
+++ b/prowler/providers/aws/services/s3/s3_bucket_public_access/s3_bucket_public_access.py
@@ -45,7 +45,9 @@ class s3_bucket_public_access(Check):
report.status_extended = f"S3 Bucket {bucket.name} has public access due to bucket ACL."
# 4. Check bucket policy
- if is_policy_public(bucket.policy, s3_client.audited_account):
+ if bucket.policy is not None and is_policy_public(
+ bucket.policy, s3_client.audited_account
+ ):
report.status = "FAIL"
report.status_extended = f"S3 Bucket {bucket.name} has public access due to bucket policy."
findings.append(report)
diff --git a/prowler/providers/aws/services/s3/s3_service.py b/prowler/providers/aws/services/s3/s3_service.py
index 655555ce3e..3894e1001d 100644
--- a/prowler/providers/aws/services/s3/s3_service.py
+++ b/prowler/providers/aws/services/s3/s3_service.py
@@ -496,6 +496,7 @@ class S3(AWSService):
)
return False
else:
+ # Bucket exists but we don't have access to it
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
@@ -681,7 +682,7 @@ class Bucket(BaseModel):
logging: bool = False
public_access_block: Optional[PublicAccessBlock]
acl_grantees: List[ACL_Grantee] = Field(default_factory=list)
- policy: Dict = Field(default_factory=dict)
+ policy: Optional[dict]
encryption: Optional[str]
region: str
logging_target_bucket: Optional[str]
diff --git a/prowler/providers/aws/services/secretsmanager/secretsmanager_not_publicly_accessible/secretsmanager_not_publicly_accessible.py b/prowler/providers/aws/services/secretsmanager/secretsmanager_not_publicly_accessible/secretsmanager_not_publicly_accessible.py
index 8c8601d158..abc82ab3c7 100644
--- a/prowler/providers/aws/services/secretsmanager/secretsmanager_not_publicly_accessible/secretsmanager_not_publicly_accessible.py
+++ b/prowler/providers/aws/services/secretsmanager/secretsmanager_not_publicly_accessible/secretsmanager_not_publicly_accessible.py
@@ -9,6 +9,8 @@ class secretsmanager_not_publicly_accessible(Check):
def execute(self):
findings = []
for secret in secretsmanager_client.secrets.values():
+ if secret.policy is None:
+ continue
report = Check_Report_AWS(metadata=self.metadata(), resource=secret)
report.status = "PASS"
report.status_extended = (
diff --git a/prowler/providers/aws/services/ses/ses_identity_not_publicly_accessible/ses_identity_not_publicly_accessible.py b/prowler/providers/aws/services/ses/ses_identity_not_publicly_accessible/ses_identity_not_publicly_accessible.py
index 0238de01a7..f668b05d06 100644
--- a/prowler/providers/aws/services/ses/ses_identity_not_publicly_accessible/ses_identity_not_publicly_accessible.py
+++ b/prowler/providers/aws/services/ses/ses_identity_not_publicly_accessible/ses_identity_not_publicly_accessible.py
@@ -7,6 +7,8 @@ class ses_identity_not_publicly_accessible(Check):
def execute(self):
findings = []
for identity in ses_client.email_identities.values():
+ if identity.policy is None:
+ continue
report = Check_Report_AWS(metadata=self.metadata(), resource=identity)
report.status = "PASS"
report.status_extended = (
diff --git a/prowler/providers/aws/services/sns/sns_service.py b/prowler/providers/aws/services/sns/sns_service.py
index a2f2d3be29..2eb93ee792 100644
--- a/prowler/providers/aws/services/sns/sns_service.py
+++ b/prowler/providers/aws/services/sns/sns_service.py
@@ -92,12 +92,12 @@ class SNS(AWSService):
)
subscriptions: list[Subscription] = [
Subscription(
- id=sub["SubscriptionArn"].split(":")[-1],
+ id=(parts := sub["SubscriptionArn"].split(":"))[-1],
arn=sub["SubscriptionArn"],
owner=sub["Owner"],
protocol=sub["Protocol"],
endpoint=sub["Endpoint"],
- region=sub["SubscriptionArn"].split(":")[3],
+ region=parts[3] if len(parts) > 3 else "unknown",
)
for sub in response["Subscriptions"]
]
diff --git a/prowler/providers/gcp/services/cloudsql/cloudsql_instance_ssl_connections/cloudsql_instance_ssl_connections.py b/prowler/providers/gcp/services/cloudsql/cloudsql_instance_ssl_connections/cloudsql_instance_ssl_connections.py
index 4882defaa6..f2badd878a 100644
--- a/prowler/providers/gcp/services/cloudsql/cloudsql_instance_ssl_connections/cloudsql_instance_ssl_connections.py
+++ b/prowler/providers/gcp/services/cloudsql/cloudsql_instance_ssl_connections/cloudsql_instance_ssl_connections.py
@@ -11,12 +11,13 @@ class cloudsql_instance_ssl_connections(Check):
report.status_extended = (
f"Database Instance {instance.name} requires SSL connections."
)
- if (
- not instance.require_ssl
- or instance.ssl_mode == "ALLOW_UNENCRYPTED_AND_ENCRYPTED"
+
+ if (instance.ssl_mode == "ALLOW_UNENCRYPTED_AND_ENCRYPTED") or (
+ instance.ssl_mode == "SSL_MODE_UNSPECIFIED" and not instance.require_ssl
):
report.status = "FAIL"
report.status_extended = f"Database Instance {instance.name} does not require SSL connections."
+
findings.append(report)
return findings
diff --git a/prowler/providers/gcp/services/dns/dns_service.py b/prowler/providers/gcp/services/dns/dns_service.py
index 5783dbf137..dc01c5cf95 100644
--- a/prowler/providers/gcp/services/dns/dns_service.py
+++ b/prowler/providers/gcp/services/dns/dns_service.py
@@ -24,11 +24,13 @@ class DNS(GCPService):
ManagedZone(
name=managed_zone["name"],
id=managed_zone["id"],
- dnssec=managed_zone.get("dnssecConfig", {})["state"]
+ dnssec=managed_zone.get("dnssecConfig", {}).get(
+ "state", ""
+ )
== "on",
- key_specs=managed_zone.get("dnssecConfig", {})[
- "defaultKeySpecs"
- ],
+ key_specs=managed_zone.get("dnssecConfig", {}).get(
+ "defaultKeySpecs", []
+ ),
project_id=project_id,
)
)
diff --git a/pyproject.toml b/pyproject.toml
index 05f233d132..f311432c63 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -55,7 +55,7 @@ cryptography = "43.0.1"
dash = "2.18.2"
dash-bootstrap-components = "1.6.0"
detect-secrets = "1.5.0"
-google-api-python-client = "2.160.0"
+google-api-python-client = "2.161.0"
google-auth-httplib2 = ">=0.1,<0.3"
jsonschema = "4.23.0"
kubernetes = "32.0.0"
@@ -100,7 +100,7 @@ optional = true
[tool.poetry.group.docs.dependencies]
mkdocs = "1.6.1"
mkdocs-git-revision-date-localized-plugin = "1.3.0"
-mkdocs-material = "9.6.3"
+mkdocs-material = "9.6.4"
mkdocs-material-extensions = "1.3.1"
[tool.poetry.scripts]
diff --git a/tests/providers/aws/services/dynamodb/dynamodb_table_cross_account_access/dynamodb_table_cross_account_access_test.py b/tests/providers/aws/services/dynamodb/dynamodb_table_cross_account_access/dynamodb_table_cross_account_access_test.py
index 4071394a6d..b29cd086bb 100644
--- a/tests/providers/aws/services/dynamodb/dynamodb_table_cross_account_access/dynamodb_table_cross_account_access_test.py
+++ b/tests/providers/aws/services/dynamodb/dynamodb_table_cross_account_access/dynamodb_table_cross_account_access_test.py
@@ -104,12 +104,15 @@ class Test_dynamodb_table_cross_account_access:
def test_no_tables(self):
dynamodb_client = mock.MagicMock
dynamodb_client.tables = {}
- with mock.patch(
- "prowler.providers.aws.services.dynamodb.dynamodb_service.DynamoDB",
- new=dynamodb_client,
- ), mock.patch(
- "prowler.providers.aws.services.dynamodb.dynamodb_client.dynamodb_client",
- new=dynamodb_client,
+ with (
+ mock.patch(
+ "prowler.providers.aws.services.dynamodb.dynamodb_service.DynamoDB",
+ new=dynamodb_client,
+ ),
+ mock.patch(
+ "prowler.providers.aws.services.dynamodb.dynamodb_client.dynamodb_client",
+ new=dynamodb_client,
+ ),
):
from prowler.providers.aws.services.dynamodb.dynamodb_table_cross_account_access.dynamodb_table_cross_account_access import (
dynamodb_table_cross_account_access,
@@ -131,15 +134,19 @@ class Test_dynamodb_table_cross_account_access:
arn=arn,
name=test_table_name,
region=AWS_REGION_EU_WEST_1,
+ policy={},
)
}
- with mock.patch(
- "prowler.providers.aws.services.dynamodb.dynamodb_service.DynamoDB",
- new=dynamodb_client,
- ), mock.patch(
- "prowler.providers.aws.services.dynamodb.dynamodb_client.dynamodb_client",
- new=dynamodb_client,
+ with (
+ mock.patch(
+ "prowler.providers.aws.services.dynamodb.dynamodb_service.DynamoDB",
+ new=dynamodb_client,
+ ),
+ mock.patch(
+ "prowler.providers.aws.services.dynamodb.dynamodb_client.dynamodb_client",
+ new=dynamodb_client,
+ ),
):
from prowler.providers.aws.services.dynamodb.dynamodb_table_cross_account_access.dynamodb_table_cross_account_access import (
dynamodb_table_cross_account_access,
@@ -172,12 +179,15 @@ class Test_dynamodb_table_cross_account_access:
policy=test_restricted_policy,
)
}
- with mock.patch(
- "prowler.providers.aws.services.dynamodb.dynamodb_service.DynamoDB",
- new=dynamodb_client,
- ), mock.patch(
- "prowler.providers.aws.services.dynamodb.dynamodb_client.dynamodb_client",
- new=dynamodb_client,
+ with (
+ mock.patch(
+ "prowler.providers.aws.services.dynamodb.dynamodb_service.DynamoDB",
+ new=dynamodb_client,
+ ),
+ mock.patch(
+ "prowler.providers.aws.services.dynamodb.dynamodb_client.dynamodb_client",
+ new=dynamodb_client,
+ ),
):
from prowler.providers.aws.services.dynamodb.dynamodb_table_cross_account_access.dynamodb_table_cross_account_access import (
dynamodb_table_cross_account_access,
@@ -211,12 +221,15 @@ class Test_dynamodb_table_cross_account_access:
)
}
- with mock.patch(
- "prowler.providers.aws.services.dynamodb.dynamodb_service.DynamoDB",
- new=dynamodb_client,
- ), mock.patch(
- "prowler.providers.aws.services.dynamodb.dynamodb_client.dynamodb_client",
- new=dynamodb_client,
+ with (
+ mock.patch(
+ "prowler.providers.aws.services.dynamodb.dynamodb_service.DynamoDB",
+ new=dynamodb_client,
+ ),
+ mock.patch(
+ "prowler.providers.aws.services.dynamodb.dynamodb_client.dynamodb_client",
+ new=dynamodb_client,
+ ),
):
from prowler.providers.aws.services.dynamodb.dynamodb_table_cross_account_access.dynamodb_table_cross_account_access import (
dynamodb_table_cross_account_access,
@@ -251,12 +264,15 @@ class Test_dynamodb_table_cross_account_access:
)
}
- with mock.patch(
- "prowler.providers.aws.services.dynamodb.dynamodb_service.DynamoDB",
- new=dynamodb_client,
- ), mock.patch(
- "prowler.providers.aws.services.dynamodb.dynamodb_client.dynamodb_client",
- new=dynamodb_client,
+ with (
+ mock.patch(
+ "prowler.providers.aws.services.dynamodb.dynamodb_service.DynamoDB",
+ new=dynamodb_client,
+ ),
+ mock.patch(
+ "prowler.providers.aws.services.dynamodb.dynamodb_client.dynamodb_client",
+ new=dynamodb_client,
+ ),
):
from prowler.providers.aws.services.dynamodb.dynamodb_table_cross_account_access.dynamodb_table_cross_account_access import (
dynamodb_table_cross_account_access,
@@ -291,12 +307,15 @@ class Test_dynamodb_table_cross_account_access:
)
}
- with mock.patch(
- "prowler.providers.aws.services.dynamodb.dynamodb_service.DynamoDB",
- new=dynamodb_client,
- ), mock.patch(
- "prowler.providers.aws.services.dynamodb.dynamodb_client.dynamodb_client",
- new=dynamodb_client,
+ with (
+ mock.patch(
+ "prowler.providers.aws.services.dynamodb.dynamodb_service.DynamoDB",
+ new=dynamodb_client,
+ ),
+ mock.patch(
+ "prowler.providers.aws.services.dynamodb.dynamodb_client.dynamodb_client",
+ new=dynamodb_client,
+ ),
):
from prowler.providers.aws.services.dynamodb.dynamodb_table_cross_account_access.dynamodb_table_cross_account_access import (
dynamodb_table_cross_account_access,
@@ -330,12 +349,15 @@ class Test_dynamodb_table_cross_account_access:
)
}
- with mock.patch(
- "prowler.providers.aws.services.dynamodb.dynamodb_service.DynamoDB",
- new=dynamodb_client,
- ), mock.patch(
- "prowler.providers.aws.services.dynamodb.dynamodb_client.dynamodb_client",
- new=dynamodb_client,
+ with (
+ mock.patch(
+ "prowler.providers.aws.services.dynamodb.dynamodb_service.DynamoDB",
+ new=dynamodb_client,
+ ),
+ mock.patch(
+ "prowler.providers.aws.services.dynamodb.dynamodb_client.dynamodb_client",
+ new=dynamodb_client,
+ ),
):
from prowler.providers.aws.services.dynamodb.dynamodb_table_cross_account_access.dynamodb_table_cross_account_access import (
dynamodb_table_cross_account_access,
@@ -369,12 +391,15 @@ class Test_dynamodb_table_cross_account_access:
)
}
- with mock.patch(
- "prowler.providers.aws.services.dynamodb.dynamodb_service.DynamoDB",
- new=dynamodb_client,
- ), mock.patch(
- "prowler.providers.aws.services.dynamodb.dynamodb_client.dynamodb_client",
- new=dynamodb_client,
+ with (
+ mock.patch(
+ "prowler.providers.aws.services.dynamodb.dynamodb_service.DynamoDB",
+ new=dynamodb_client,
+ ),
+ mock.patch(
+ "prowler.providers.aws.services.dynamodb.dynamodb_client.dynamodb_client",
+ new=dynamodb_client,
+ ),
):
from prowler.providers.aws.services.dynamodb.dynamodb_table_cross_account_access.dynamodb_table_cross_account_access import (
dynamodb_table_cross_account_access,
diff --git a/tests/providers/aws/services/ecr/ecr_repositories_not_publicly_accessible/ecr_repositories_not_publicly_accessible_test.py b/tests/providers/aws/services/ecr/ecr_repositories_not_publicly_accessible/ecr_repositories_not_publicly_accessible_test.py
index 9904aec2ca..82c31cd48d 100644
--- a/tests/providers/aws/services/ecr/ecr_repositories_not_publicly_accessible/ecr_repositories_not_publicly_accessible_test.py
+++ b/tests/providers/aws/services/ecr/ecr_repositories_not_publicly_accessible/ecr_repositories_not_publicly_accessible_test.py
@@ -43,12 +43,15 @@ class Test_ecr_repositories_not_publicly_accessible:
ecr_client = mock.MagicMock
ecr_client.registries = {}
- with mock.patch(
- "prowler.providers.common.provider.Provider.get_global_provider",
- return_value=set_mocked_aws_provider(),
- ), mock.patch(
- "prowler.providers.aws.services.ecr.ecr_repositories_not_publicly_accessible.ecr_repositories_not_publicly_accessible.ecr_client",
- ecr_client,
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_aws_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.aws.services.ecr.ecr_repositories_not_publicly_accessible.ecr_repositories_not_publicly_accessible.ecr_client",
+ ecr_client,
+ ),
):
from prowler.providers.aws.services.ecr.ecr_repositories_not_publicly_accessible.ecr_repositories_not_publicly_accessible import (
ecr_repositories_not_publicly_accessible,
@@ -70,12 +73,15 @@ class Test_ecr_repositories_not_publicly_accessible:
rules=[],
)
- with mock.patch(
- "prowler.providers.common.provider.Provider.get_global_provider",
- return_value=set_mocked_aws_provider(),
- ), mock.patch(
- "prowler.providers.aws.services.ecr.ecr_repositories_not_publicly_accessible.ecr_repositories_not_publicly_accessible.ecr_client",
- ecr_client,
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_aws_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.aws.services.ecr.ecr_repositories_not_publicly_accessible.ecr_repositories_not_publicly_accessible.ecr_client",
+ ecr_client,
+ ),
):
from prowler.providers.aws.services.ecr.ecr_repositories_not_publicly_accessible.ecr_repositories_not_publicly_accessible import (
ecr_repositories_not_publicly_accessible,
@@ -109,12 +115,15 @@ class Test_ecr_repositories_not_publicly_accessible:
rules=[],
)
- with mock.patch(
- "prowler.providers.common.provider.Provider.get_global_provider",
- return_value=set_mocked_aws_provider(),
- ), mock.patch(
- "prowler.providers.aws.services.ecr.ecr_repositories_not_publicly_accessible.ecr_repositories_not_publicly_accessible.ecr_client",
- ecr_client,
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_aws_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.aws.services.ecr.ecr_repositories_not_publicly_accessible.ecr_repositories_not_publicly_accessible.ecr_client",
+ ecr_client,
+ ),
):
from prowler.providers.aws.services.ecr.ecr_repositories_not_publicly_accessible.ecr_repositories_not_publicly_accessible import (
ecr_repositories_not_publicly_accessible,
@@ -145,7 +154,7 @@ class Test_ecr_repositories_not_publicly_accessible:
arn=repository_arn,
region=AWS_REGION_EU_WEST_1,
scan_on_push=True,
- policy=None,
+ policy={},
images_details=None,
lifecycle_policy=None,
)
@@ -153,12 +162,15 @@ class Test_ecr_repositories_not_publicly_accessible:
rules=[],
)
- with mock.patch(
- "prowler.providers.common.provider.Provider.get_global_provider",
- return_value=set_mocked_aws_provider(),
- ), mock.patch(
- "prowler.providers.aws.services.ecr.ecr_repositories_not_publicly_accessible.ecr_repositories_not_publicly_accessible.ecr_client",
- ecr_client,
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_aws_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.aws.services.ecr.ecr_repositories_not_publicly_accessible.ecr_repositories_not_publicly_accessible.ecr_client",
+ ecr_client,
+ ),
):
from prowler.providers.aws.services.ecr.ecr_repositories_not_publicly_accessible.ecr_repositories_not_publicly_accessible import (
ecr_repositories_not_publicly_accessible,
@@ -199,12 +211,15 @@ class Test_ecr_repositories_not_publicly_accessible:
rules=[],
)
- with mock.patch(
- "prowler.providers.common.provider.Provider.get_global_provider",
- return_value=set_mocked_aws_provider(),
- ), mock.patch(
- "prowler.providers.aws.services.ecr.ecr_repositories_not_publicly_accessible.ecr_repositories_not_publicly_accessible.ecr_client",
- ecr_client,
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_aws_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.aws.services.ecr.ecr_repositories_not_publicly_accessible.ecr_repositories_not_publicly_accessible.ecr_client",
+ ecr_client,
+ ),
):
from prowler.providers.aws.services.ecr.ecr_repositories_not_publicly_accessible.ecr_repositories_not_publicly_accessible import (
ecr_repositories_not_publicly_accessible,
diff --git a/tests/providers/gcp/services/cloudsql/cloudsql_instance_ssl_connections/cloudsql_instance_ssl_connections_test.py b/tests/providers/gcp/services/cloudsql/cloudsql_instance_ssl_connections/cloudsql_instance_ssl_connections_test.py
index 83712c3a8d..8b398aee00 100644
--- a/tests/providers/gcp/services/cloudsql/cloudsql_instance_ssl_connections/cloudsql_instance_ssl_connections_test.py
+++ b/tests/providers/gcp/services/cloudsql/cloudsql_instance_ssl_connections/cloudsql_instance_ssl_connections_test.py
@@ -74,6 +74,52 @@ class Test_cloudsql_instance_ssl_connections:
assert result[0].location == GCP_EU1_LOCATION
assert result[0].project_id == GCP_PROJECT_ID
+ def test_cloudsql_instance_ssl_connections_disabled_and_ssl_mode_encrypted(self):
+ cloudsql_client = mock.MagicMock()
+
+ with mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_gcp_provider(),
+ ), mock.patch(
+ "prowler.providers.gcp.services.cloudsql.cloudsql_instance_ssl_connections.cloudsql_instance_ssl_connections.cloudsql_client",
+ new=cloudsql_client,
+ ):
+ from prowler.providers.gcp.services.cloudsql.cloudsql_instance_ssl_connections.cloudsql_instance_ssl_connections import (
+ cloudsql_instance_ssl_connections,
+ )
+ from prowler.providers.gcp.services.cloudsql.cloudsql_service import (
+ Instance,
+ )
+
+ cloudsql_client.instances = [
+ Instance(
+ name="instance1",
+ version="POSTGRES_15",
+ ip_addresses=[],
+ region=GCP_EU1_LOCATION,
+ public_ip=False,
+ require_ssl=False,
+ ssl_mode="ENCRYPTED_ONLY",
+ automated_backups=True,
+ authorized_networks=[],
+ flags=[],
+ project_id=GCP_PROJECT_ID,
+ )
+ ]
+
+ check = cloudsql_instance_ssl_connections()
+ result = check.execute()
+ assert len(result) == 1
+ assert result[0].status == "PASS"
+ assert (
+ result[0].status_extended
+ == "Database Instance instance1 requires SSL connections."
+ )
+ assert result[0].resource_id == "instance1"
+ assert result[0].resource_name == "instance1"
+ assert result[0].location == GCP_EU1_LOCATION
+ assert result[0].project_id == GCP_PROJECT_ID
+
def test_cloudsql_instance_ssl_connections_enabled_and_ssl_mode_not_encrypted(self):
cloudsql_client = mock.MagicMock()
diff --git a/ui/components/ui/sidebar/sidebar-items.tsx b/ui/components/ui/sidebar/sidebar-items.tsx
index 3123e81291..ce4c2b1e89 100644
--- a/ui/components/ui/sidebar/sidebar-items.tsx
+++ b/ui/components/ui/sidebar/sidebar-items.tsx
@@ -152,37 +152,37 @@ export const sectionItems: SidebarItem[] = [
items: [
{
key: "cloud-misconfigurations",
- href: "/findings?filter[status__in]=FAIL&sort=severity",
+ href: "/findings?filter[status__in]=FAIL&sort=severity,-inserted_at",
icon: "solar:danger-triangle-linear",
title: "Cloud Misconfigurations",
},
{
key: "iam-issues",
- href: "/findings?filter[status__in]=FAIL&filter[severity__in]=critical%2Chigh%2Cmedium&filter[provider_type__in]=aws%2Cazure%2Cgcp%2Ckubernetes&filter[service__in]=iam%2Crbac",
+ href: "/findings?filter[status__in]=FAIL&filter[severity__in]=critical%2Chigh%2Cmedium&filter[provider_type__in]=aws%2Cazure%2Cgcp%2Ckubernetes&filter[service__in]=iam%2Crbac&sort=-inserted_at",
icon: "solar:shield-user-linear",
title: "IAM Issues",
},
{
key: "aws-findings",
- href: "/findings?filter[status__in]=FAIL&filter[severity__in]=critical%2Chigh%2Cmedium&filter[provider_type__in]=aws&sort=severity",
+ href: "/findings?filter[status__in]=FAIL&filter[severity__in]=critical%2Chigh%2Cmedium&filter[provider_type__in]=aws&sort=severity,-inserted_at",
icon: "ri:amazon-line",
title: "AWS Top Failed Findings",
},
{
key: "azure-findings",
- href: "/findings?filter[status__in]=FAIL&filter[severity__in]=critical%2Chigh%2Cmedium&filter[provider_type__in]=azure&sort=severity",
+ href: "/findings?filter[status__in]=FAIL&filter[severity__in]=critical%2Chigh%2Cmedium&filter[provider_type__in]=azure&sort=severity,-inserted_at",
icon: "ri:microsoft-line",
title: "Azure Top Failed Findings",
},
{
key: "gcp-findings",
- href: "/findings?filter[status__in]=FAIL&filter[severity__in]=critical%2Chigh%2Cmedium&filter[provider_type__in]=gcp&sort=severity",
+ href: "/findings?filter[status__in]=FAIL&filter[severity__in]=critical%2Chigh%2Cmedium&filter[provider_type__in]=gcp&sort=severity,-inserted_at",
icon: "ri:google-line",
title: "GCP Top Failed Findings",
},
{
key: "kubernetes-findings",
- href: "/findings?filter[status__in]=FAIL&filter[severity__in]=critical%2Chigh%2Cmedium&filter[provider_type__in]=kubernetes&sort=severity",
+ href: "/findings?filter[status__in]=FAIL&filter[severity__in]=critical%2Chigh%2Cmedium&filter[provider_type__in]=kubernetes&sort=severity,-inserted_at",
icon: "ri:steering-2-line",
title: "Kubernetes Top Failed Findings",
},