Compare commits
	
		
			234 Commits
		
	
	
		
			version/20
			...
			analytics
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 435ba598bb | |||
| 582511abcc | |||
| 80ea1dae81 | |||
| c6b3b877ee | |||
| 2909f2787a | |||
| 49a10efd6b | |||
| aca20fc3af | |||
| c80d881400 | |||
| 27380e9903 | |||
| ab4a125613 | |||
| 4fed92127d | |||
| 4abef6d7b9 | |||
| ffcd146b2a | |||
| 6d1c48bb8b | |||
| ba6ddcb655 | |||
| d5581fde17 | |||
| 33b3e9bbf9 | |||
| 421873b15f | |||
| d351edc3d4 | |||
| 2ce1c3f724 | |||
| 09ad69b0be | |||
| b144d0cf05 | |||
| f386856aa6 | |||
| 849951e747 | |||
| 9b7aa99713 | |||
| a46189ff21 | |||
| 2ce6ede0f0 | |||
| 7a5850b1e8 | |||
| f9cc835eba | |||
| f5ed14d9a8 | |||
| 393584cd71 | |||
| 9c42df7191 | |||
| bad0203e94 | |||
| ad3820c11c | |||
| 20f555ebb6 | |||
| c39808f398 | |||
| 113c87baef | |||
| a96639ba06 | |||
| 32a61f7cc9 | |||
| 31175862fe | |||
| 11fef40eeb | |||
| e80d76cca3 | |||
| 7f075eb103 | |||
| 70b5a214f2 | |||
| c4d6388b1e | |||
| b27bf1ec56 | |||
| 37d0de767e | |||
| 5dd9bd1e44 | |||
| 4e56dda1b8 | |||
| e130f16809 | |||
| cdf5acd9ec | |||
| b4ec306f93 | |||
| bb56c4fa49 | |||
| 6b94fecf7b | |||
| 5822653155 | |||
| f0ec7511cf | |||
| e4f711dab1 | |||
| 1892112b6f | |||
| e3aa35970e | |||
| a343848c7b | |||
| 02fba59985 | |||
| fd7d276d9a | |||
| e87c946403 | |||
| 1182009dac | |||
| 02d8f82a5e | |||
| ebf39139cb | |||
| 480c0284b5 | |||
| 00547bcf3f | |||
| 0a91f2fe3f | |||
| 48ea4a9fa3 | |||
| 5b192c51f9 | |||
| 3644ccbe02 | |||
| 0adcb0d081 | |||
| d5b717efce | |||
| 8661b764c1 | |||
| 4a54a127c1 | |||
| cbf90b7422 | |||
| f4dfa241b7 | |||
| 41b310f62d | |||
| aa1277f700 | |||
| 171d0f55cb | |||
| 18a43455f1 | |||
| 0e41df42b7 | |||
| a3113d3b26 | |||
| edef110dca | |||
| 0bcc778dda | |||
| c76283ffae | |||
| 408fa926b3 | |||
| dbff42a338 | |||
| 20552dc899 | |||
| 65166e8a2a | |||
| 38236ed95d | |||
| 8958b225b4 | |||
| 0145b74103 | |||
| 220f540c50 | |||
| 3ab9747ebe | |||
| 52d2514461 | |||
| c12958aebd | |||
| 7b66850d8e | |||
| 4281b6be2e | |||
| 9df63af6f8 | |||
| 8a31acbe91 | |||
| f207ce64f8 | |||
| 8a9d4c1763 | |||
| c6383981ef | |||
| a0cf9b246d | |||
| 5b98368555 | |||
| f29d38116c | |||
| 2bc1607c0f | |||
| d5a087c0ef | |||
| b4df730990 | |||
| af8e33edcf | |||
| f9f93d50c7 | |||
| 8af8061ca5 | |||
| af5a0d3311 | |||
| 3308c0001a | |||
| 52cd4a909c | |||
| ed38372a63 | |||
| 81f22dbce8 | |||
| 0e9b8b3a95 | |||
| 8e902f9d09 | |||
| 8c19385803 | |||
| ad21b41053 | |||
| e99b119c88 | |||
| 1ba1acd82b | |||
| de33eae5f1 | |||
| 9d8ba2825b | |||
| f3b9fca890 | |||
| b0e5d59c31 | |||
| 378daba74e | |||
| e1b969446d | |||
| 88928d7f72 | |||
| b8ae028d4d | |||
| c90ecf4ebe | |||
| c3b6d8ea70 | |||
| ebc258efe0 | |||
| 1eb69ceb38 | |||
| dee7e12669 | |||
| 205cc084ac | |||
| 5be49a8e80 | |||
| ab76155e7a | |||
| a5fc85c2d8 | |||
| 2bc8e9bae6 | |||
| 6dabd83a99 | |||
| f790cd689c | |||
| 3696faf299 | |||
| fcc5c57589 | |||
| 710809a9ef | |||
| 266e5a2cbd | |||
| ae079ea490 | |||
| 6123f6bf1e | |||
| 984466f77f | |||
| 31f5e19c21 | |||
| 4670f12ba7 | |||
| ea74ad4090 | |||
| a8b33b25e8 | |||
| 8886532ed6 | |||
| 4b63e11d29 | |||
| 2cceec66fa | |||
| 30fcdbf05f | |||
| 31ad841aed | |||
| c4f149dd46 | |||
| 89245123dc | |||
| 55d9da18d5 | |||
| 4b80d860b6 | |||
| 90f5c4c757 | |||
| 67c90d7ec5 | |||
| eed3ffd0ee | |||
| 7ff42c34fb | |||
| b2873fad10 | |||
| 441b6ca6b0 | |||
| 5f261bed96 | |||
| 4d51671f88 | |||
| 1c570b2502 | |||
| 78e4370b98 | |||
| 9a26111f1c | |||
| 02ae099bdf | |||
| 05fe4e5e7b | |||
| 24c289fdd1 | |||
| cd8de3e526 | |||
| 243bd03785 | |||
| e02cf0b3bd | |||
| 3018735579 | |||
| 6a956d149a | |||
| 3fd1bc6673 | |||
| 6bc4877702 | |||
| 9592b42501 | |||
| 517a5bc689 | |||
| a19e350ca6 | |||
| 88577145fb | |||
| bc93df1e29 | |||
| 9d5eb54504 | |||
| d95c433027 | |||
| 7416c90efb | |||
| 34a073b0f7 | |||
| 86eb112a03 | |||
| e48d001ea6 | |||
| 2070372f03 | |||
| bea4679192 | |||
| 32d1488a56 | |||
| 1003c79d8c | |||
| a05ed7e237 | |||
| 087d4f6a48 | |||
| 141cfe75d8 | |||
| a3a13d265b | |||
| 9c45ec1918 | |||
| 59fa449abe | |||
| f83c84b04d | |||
| 835a4097eb | |||
| da43df44b2 | |||
| d03b14aac4 | |||
| 62d990e91b | |||
| 6c8cfc9ef7 | |||
| e93d8b1646 | |||
| 6faa250574 | |||
| c8e4b187b8 | |||
| 98acca896a | |||
| 7dc4b70ee1 | |||
| 8f55d3fc07 | |||
| 17fb90e0af | |||
| 870ed99097 | |||
| 1c5af88ea9 | |||
| 461856d067 | |||
| cdbf448769 | |||
| 0fcac0e165 | |||
| 40e857fdb3 | |||
| 7d86593d05 | |||
| c95ce0a5d4 | |||
| a7b31ce6de | |||
| b5f4303fbd | |||
| 29f04ea801 | |||
| 7141702c9e | |||
| 0f30d135b6 | |||
| 73f326b21b | 
| @ -1,5 +1,5 @@ | ||||
| [bumpversion] | ||||
| current_version = 2024.8.0-rc2 | ||||
| current_version = 2024.8.2 | ||||
| tag = True | ||||
| commit = True | ||||
| parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<rc_t>[a-zA-Z-]+)(?P<rc_n>[1-9]\\d*))? | ||||
|  | ||||
| @ -29,9 +29,9 @@ outputs: | ||||
|   imageTags: | ||||
|     description: "Docker image tags" | ||||
|     value: ${{ steps.ev.outputs.imageTags }} | ||||
|   imageNames: | ||||
|     description: "Docker image names" | ||||
|     value: ${{ steps.ev.outputs.imageNames }} | ||||
|   attestImageNames: | ||||
|     description: "Docker image names used for attestation" | ||||
|     value: ${{ steps.ev.outputs.attestImageNames }} | ||||
|   imageMainTag: | ||||
|     description: "Docker image main tag" | ||||
|     value: ${{ steps.ev.outputs.imageMainTag }} | ||||
|  | ||||
| @ -51,15 +51,24 @@ else: | ||||
|         ] | ||||
|  | ||||
| image_main_tag = image_tags[0].split(":")[-1] | ||||
| image_tags_rendered = ",".join(image_tags) | ||||
| image_names_rendered = ",".join(set(name.split(":")[0] for name in image_tags)) | ||||
|  | ||||
|  | ||||
| def get_attest_image_names(image_with_tags: list[str]): | ||||
|     """Attestation only for GHCR""" | ||||
|     image_tags = [] | ||||
|     for image_name in set(name.split(":")[0] for name in image_with_tags): | ||||
|         if not image_name.startswith("ghcr.io"): | ||||
|             continue | ||||
|         image_tags.append(image_name) | ||||
|     return ",".join(set(image_tags)) | ||||
|  | ||||
|  | ||||
| with open(os.environ["GITHUB_OUTPUT"], "a+", encoding="utf-8") as _output: | ||||
|     print(f"shouldBuild={should_build}", file=_output) | ||||
|     print(f"sha={sha}", file=_output) | ||||
|     print(f"version={version}", file=_output) | ||||
|     print(f"prerelease={prerelease}", file=_output) | ||||
|     print(f"imageTags={image_tags_rendered}", file=_output) | ||||
|     print(f"imageNames={image_names_rendered}", file=_output) | ||||
|     print(f"imageTags={','.join(image_tags)}", file=_output) | ||||
|     print(f"attestImageNames={get_attest_image_names(image_tags)}", file=_output) | ||||
|     print(f"imageMainTag={image_main_tag}", file=_output) | ||||
|     print(f"imageMainName={image_tags[0]}", file=_output) | ||||
|  | ||||
							
								
								
									
										6
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							| @ -44,9 +44,11 @@ updates: | ||||
|           - "babel-*" | ||||
|       eslint: | ||||
|         patterns: | ||||
|           - "@eslint/*" | ||||
|           - "@typescript-eslint/*" | ||||
|           - "eslint" | ||||
|           - "eslint-*" | ||||
|           - "eslint" | ||||
|           - "typescript-eslint" | ||||
|       storybook: | ||||
|         patterns: | ||||
|           - "@storybook/*" | ||||
| @ -54,10 +56,12 @@ updates: | ||||
|       esbuild: | ||||
|         patterns: | ||||
|           - "@esbuild/*" | ||||
|           - "esbuild*" | ||||
|       rollup: | ||||
|         patterns: | ||||
|           - "@rollup/*" | ||||
|           - "rollup-*" | ||||
|           - "rollup*" | ||||
|       swc: | ||||
|         patterns: | ||||
|           - "@swc/*" | ||||
|  | ||||
							
								
								
									
										2
									
								
								.github/workflows/api-ts-publish.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/api-ts-publish.yml
									
									
									
									
										vendored
									
									
								
							| @ -40,7 +40,7 @@ jobs: | ||||
|         run: | | ||||
|           export VERSION=`node -e 'console.log(require("../gen-ts-api/package.json").version)'` | ||||
|           npm i @goauthentik/api@$VERSION | ||||
|       - uses: peter-evans/create-pull-request@v6 | ||||
|       - uses: peter-evans/create-pull-request@v7 | ||||
|         id: cpr | ||||
|         with: | ||||
|           token: ${{ steps.generate_token.outputs.token }} | ||||
|  | ||||
							
								
								
									
										20
									
								
								.github/workflows/ci-main.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								.github/workflows/ci-main.yml
									
									
									
									
										vendored
									
									
								
							| @ -120,6 +120,12 @@ jobs: | ||||
|         with: | ||||
|           flags: unit | ||||
|           token: ${{ secrets.CODECOV_TOKEN }} | ||||
|       - if: ${{ !cancelled() }} | ||||
|         uses: codecov/test-results-action@v1 | ||||
|         with: | ||||
|           flags: unit | ||||
|           file: unittest.xml | ||||
|           token: ${{ secrets.CODECOV_TOKEN }} | ||||
|   test-integration: | ||||
|     runs-on: ubuntu-latest | ||||
|     timeout-minutes: 30 | ||||
| @ -138,6 +144,12 @@ jobs: | ||||
|         with: | ||||
|           flags: integration | ||||
|           token: ${{ secrets.CODECOV_TOKEN }} | ||||
|       - if: ${{ !cancelled() }} | ||||
|         uses: codecov/test-results-action@v1 | ||||
|         with: | ||||
|           flags: integration | ||||
|           file: unittest.xml | ||||
|           token: ${{ secrets.CODECOV_TOKEN }} | ||||
|   test-e2e: | ||||
|     name: test-e2e (${{ matrix.job.name }}) | ||||
|     runs-on: ubuntu-latest | ||||
| @ -190,6 +202,12 @@ jobs: | ||||
|         with: | ||||
|           flags: e2e | ||||
|           token: ${{ secrets.CODECOV_TOKEN }} | ||||
|       - if: ${{ !cancelled() }} | ||||
|         uses: codecov/test-results-action@v1 | ||||
|         with: | ||||
|           flags: e2e | ||||
|           file: unittest.xml | ||||
|           token: ${{ secrets.CODECOV_TOKEN }} | ||||
|   ci-core-mark: | ||||
|     needs: | ||||
|       - lint | ||||
| @ -261,7 +279,7 @@ jobs: | ||||
|         id: attest | ||||
|         if: ${{ steps.ev.outputs.shouldBuild == 'true' }} | ||||
|         with: | ||||
|           subject-name: ${{ steps.ev.outputs.imageNames }} | ||||
|           subject-name: ${{ steps.ev.outputs.attestImageNames }} | ||||
|           subject-digest: ${{ steps.push.outputs.digest }} | ||||
|           push-to-registry: true | ||||
|   pr-comment: | ||||
|  | ||||
							
								
								
									
										2
									
								
								.github/workflows/ci-outpost.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci-outpost.yml
									
									
									
									
										vendored
									
									
								
							| @ -115,7 +115,7 @@ jobs: | ||||
|         id: attest | ||||
|         if: ${{ steps.ev.outputs.shouldBuild == 'true' }} | ||||
|         with: | ||||
|           subject-name: ${{ steps.ev.outputs.imageNames }} | ||||
|           subject-name: ${{ steps.ev.outputs.attestImageNames }} | ||||
|           subject-digest: ${{ steps.push.outputs.digest }} | ||||
|           push-to-registry: true | ||||
|   build-binary: | ||||
|  | ||||
							
								
								
									
										1
									
								
								.github/workflows/ci-web.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/ci-web.yml
									
									
									
									
										vendored
									
									
								
							| @ -45,7 +45,6 @@ jobs: | ||||
|       - working-directory: ${{ matrix.project }}/ | ||||
|         run: | | ||||
|           npm ci | ||||
|           ${{ matrix.extra_setup }} | ||||
|       - name: Generate API | ||||
|         run: make gen-client-ts | ||||
|       - name: Lint | ||||
|  | ||||
| @ -24,7 +24,7 @@ jobs: | ||||
|       - name: Setup authentik env | ||||
|         uses: ./.github/actions/setup | ||||
|       - run: poetry run ak update_webauthn_mds | ||||
|       - uses: peter-evans/create-pull-request@v6 | ||||
|       - uses: peter-evans/create-pull-request@v7 | ||||
|         id: cpr | ||||
|         with: | ||||
|           token: ${{ steps.generate_token.outputs.token }} | ||||
|  | ||||
							
								
								
									
										2
									
								
								.github/workflows/image-compress.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/image-compress.yml
									
									
									
									
										vendored
									
									
								
							| @ -42,7 +42,7 @@ jobs: | ||||
|         with: | ||||
|           githubToken: ${{ steps.generate_token.outputs.token }} | ||||
|           compressOnly: ${{ github.event_name != 'pull_request' }} | ||||
|       - uses: peter-evans/create-pull-request@v6 | ||||
|       - uses: peter-evans/create-pull-request@v7 | ||||
|         if: "${{ github.event_name != 'pull_request' && steps.compress.outputs.markdown != '' }}" | ||||
|         id: cpr | ||||
|         with: | ||||
|  | ||||
							
								
								
									
										4
									
								
								.github/workflows/release-publish.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/release-publish.yml
									
									
									
									
										vendored
									
									
								
							| @ -58,7 +58,7 @@ jobs: | ||||
|       - uses: actions/attest-build-provenance@v1 | ||||
|         id: attest | ||||
|         with: | ||||
|           subject-name: ${{ steps.ev.outputs.imageNames }} | ||||
|           subject-name: ${{ steps.ev.outputs.attestImageNames }} | ||||
|           subject-digest: ${{ steps.push.outputs.digest }} | ||||
|           push-to-registry: true | ||||
|   build-outpost: | ||||
| @ -122,7 +122,7 @@ jobs: | ||||
|       - uses: actions/attest-build-provenance@v1 | ||||
|         id: attest | ||||
|         with: | ||||
|           subject-name: ${{ steps.ev.outputs.imageNames }} | ||||
|           subject-name: ${{ steps.ev.outputs.attestImageNames }} | ||||
|           subject-digest: ${{ steps.push.outputs.digest }} | ||||
|           push-to-registry: true | ||||
|   build-outpost-binary: | ||||
|  | ||||
| @ -32,7 +32,7 @@ jobs: | ||||
|           poetry run ak compilemessages | ||||
|           make web-check-compile | ||||
|       - name: Create Pull Request | ||||
|         uses: peter-evans/create-pull-request@v6 | ||||
|         uses: peter-evans/create-pull-request@v7 | ||||
|         with: | ||||
|           token: ${{ steps.generate_token.outputs.token }} | ||||
|           branch: extract-compile-backend-translation | ||||
|  | ||||
| @ -94,7 +94,7 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \ | ||||
|     /bin/sh -c "/usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0" | ||||
|  | ||||
| # Stage 5: Python dependencies | ||||
| FROM ghcr.io/goauthentik/fips-python:3.12.5-slim-bookworm-fips-full AS python-deps | ||||
| FROM ghcr.io/goauthentik/fips-python:3.12.6-slim-bookworm-fips-full AS python-deps | ||||
|  | ||||
| ARG TARGETARCH | ||||
| ARG TARGETVARIANT | ||||
| @ -124,7 +124,7 @@ RUN --mount=type=bind,target=./pyproject.toml,src=./pyproject.toml \ | ||||
|     pip install --force-reinstall /wheels/*" | ||||
|  | ||||
| # Stage 6: Run | ||||
| FROM ghcr.io/goauthentik/fips-python:3.12.5-slim-bookworm-fips-full AS final-image | ||||
| FROM ghcr.io/goauthentik/fips-python:3.12.6-slim-bookworm-fips-full AS final-image | ||||
|  | ||||
| ARG VERSION | ||||
| ARG GIT_BUILD_HASH | ||||
|  | ||||
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							| @ -205,7 +205,7 @@ gen: gen-build gen-client-ts | ||||
| web-build: web-install  ## Build the Authentik UI | ||||
| 	cd web && npm run build | ||||
|  | ||||
| web: web-lint-fix web-lint web-check-compile web-test  ## Automatically fix formatting issues in the Authentik UI source code, lint the code, and compile it | ||||
| web: web-lint-fix web-lint web-check-compile  ## Automatically fix formatting issues in the Authentik UI source code, lint the code, and compile it | ||||
|  | ||||
| web-install:  ## Install the necessary libraries to build the Authentik UI | ||||
| 	cd web && npm ci | ||||
|  | ||||
| @ -20,8 +20,8 @@ Even if the issue is not a CVE, we still greatly appreciate your help in hardeni | ||||
|  | ||||
| | Version  | Supported | | ||||
| | -------- | --------- | | ||||
| | 2024.4.x | ✅        | | ||||
| | 2024.6.x | ✅        | | ||||
| | 2024.8.x | ✅        | | ||||
|  | ||||
| ## Reporting a Vulnerability | ||||
|  | ||||
|  | ||||
| @ -2,7 +2,7 @@ | ||||
|  | ||||
| from os import environ | ||||
|  | ||||
| __version__ = "2024.8.0" | ||||
| __version__ = "2024.8.2" | ||||
| ENV_GIT_HASH_KEY = "GIT_BUILD_HASH" | ||||
|  | ||||
|  | ||||
|  | ||||
							
								
								
									
										20
									
								
								authentik/admin/analytics.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								authentik/admin/analytics.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | ||||
| """authentik admin analytics""" | ||||
|  | ||||
| from typing import Any | ||||
|  | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
|  | ||||
| from authentik.root.celery import CELERY_APP | ||||
|  | ||||
|  | ||||
| def get_analytics_description() -> dict[str, str]: | ||||
|     return { | ||||
|         "worker_count": _("Number of running workers"), | ||||
|     } | ||||
|  | ||||
|  | ||||
| def get_analytics_data() -> dict[str, Any]: | ||||
|     worker_count = len(CELERY_APP.control.ping(timeout=0.5)) | ||||
|     return { | ||||
|         "worker_count": worker_count, | ||||
|     } | ||||
| @ -1,10 +1,8 @@ | ||||
| """authentik admin tasks""" | ||||
|  | ||||
| import re | ||||
|  | ||||
| from django.core.cache import cache | ||||
| from django.core.validators import URLValidator | ||||
| from django.db import DatabaseError, InternalError, ProgrammingError | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from packaging.version import parse | ||||
| from requests import RequestException | ||||
| from structlog.stdlib import get_logger | ||||
| @ -21,8 +19,6 @@ LOGGER = get_logger() | ||||
| VERSION_NULL = "0.0.0" | ||||
| VERSION_CACHE_KEY = "authentik_latest_version" | ||||
| VERSION_CACHE_TIMEOUT = 8 * 60 * 60  # 8 hours | ||||
| # Chop of the first ^ because we want to search the entire string | ||||
| URL_FINDER = URLValidator.regex.pattern[1:] | ||||
| LOCAL_VERSION = parse(__version__) | ||||
|  | ||||
|  | ||||
| @ -78,10 +74,16 @@ def update_latest_version(self: SystemTask): | ||||
|                 context__new_version=upstream_version, | ||||
|             ).exists(): | ||||
|                 return | ||||
|             event_dict = {"new_version": upstream_version} | ||||
|             if match := re.search(URL_FINDER, data.get("stable", {}).get("changelog", "")): | ||||
|                 event_dict["message"] = f"Changelog: {match.group()}" | ||||
|             Event.new(EventAction.UPDATE_AVAILABLE, **event_dict).save() | ||||
|             Event.new( | ||||
|                 EventAction.UPDATE_AVAILABLE, | ||||
|                 message=_( | ||||
|                     "New version {version} available!".format( | ||||
|                         version=upstream_version, | ||||
|                     ) | ||||
|                 ), | ||||
|                 new_version=upstream_version, | ||||
|                 changelog=data.get("stable", {}).get("changelog_url"), | ||||
|             ).save() | ||||
|     except (RequestException, IndexError) as exc: | ||||
|         cache.set(VERSION_CACHE_KEY, VERSION_NULL, VERSION_CACHE_TIMEOUT) | ||||
|         self.set_error(exc) | ||||
|  | ||||
| @ -17,6 +17,7 @@ RESPONSE_VALID = { | ||||
|     "stable": { | ||||
|         "version": "99999999.9999999", | ||||
|         "changelog": "See https://goauthentik.io/test", | ||||
|         "changelog_url": "https://goauthentik.io/test", | ||||
|         "reason": "bugfix", | ||||
|     }, | ||||
| } | ||||
| @ -35,7 +36,7 @@ class TestAdminTasks(TestCase): | ||||
|                 Event.objects.filter( | ||||
|                     action=EventAction.UPDATE_AVAILABLE, | ||||
|                     context__new_version="99999999.9999999", | ||||
|                     context__message="Changelog: https://goauthentik.io/test", | ||||
|                     context__message="New version 99999999.9999999 available!", | ||||
|                 ).exists() | ||||
|             ) | ||||
|             # test that a consecutive check doesn't create a duplicate event | ||||
| @ -45,7 +46,7 @@ class TestAdminTasks(TestCase): | ||||
|                     Event.objects.filter( | ||||
|                         action=EventAction.UPDATE_AVAILABLE, | ||||
|                         context__new_version="99999999.9999999", | ||||
|                         context__message="Changelog: https://goauthentik.io/test", | ||||
|                         context__message="New version 99999999.9999999 available!", | ||||
|                     ) | ||||
|                 ), | ||||
|                 1, | ||||
|  | ||||
							
								
								
									
										0
									
								
								authentik/analytics/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								authentik/analytics/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										54
									
								
								authentik/analytics/api.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								authentik/analytics/api.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,54 @@ | ||||
| """authentik analytics api""" | ||||
|  | ||||
| from drf_spectacular.utils import extend_schema, inline_serializer | ||||
| from rest_framework.fields import CharField, DictField | ||||
| from rest_framework.request import Request | ||||
| from rest_framework.response import Response | ||||
| from rest_framework.viewsets import ViewSet | ||||
|  | ||||
| from authentik.analytics.utils import get_analytics_data, get_analytics_description | ||||
| from authentik.core.api.utils import PassiveSerializer | ||||
| from authentik.rbac.permissions import HasPermission | ||||
|  | ||||
|  | ||||
| class AnalyticsDescriptionSerializer(PassiveSerializer): | ||||
|     label = CharField() | ||||
|     desc = CharField() | ||||
|  | ||||
|  | ||||
| class AnalyticsDescriptionViewSet(ViewSet): | ||||
|     """Read-only view of analytics descriptions""" | ||||
|  | ||||
|     permission_classes = [HasPermission("authentik_rbac.view_system_settings")] | ||||
|  | ||||
|     @extend_schema(responses={200: AnalyticsDescriptionSerializer}) | ||||
|     def list(self, request: Request) -> Response: | ||||
|         """Read-only view of analytics descriptions""" | ||||
|         data = [] | ||||
|         for label, desc in get_analytics_description().items(): | ||||
|             data.append({"label": label, "desc": desc}) | ||||
|         return Response(AnalyticsDescriptionSerializer(data, many=True).data) | ||||
|  | ||||
|  | ||||
| class AnalyticsDataViewSet(ViewSet): | ||||
|     """Read-only view of analytics descriptions""" | ||||
|  | ||||
|     permission_classes = [HasPermission("authentik_rbac.edit_system_settings")] | ||||
|  | ||||
|     @extend_schema( | ||||
|         responses={ | ||||
|             200: inline_serializer( | ||||
|                 name="AnalyticsData", | ||||
|                 fields={ | ||||
|                     "data": DictField(), | ||||
|                 }, | ||||
|             ) | ||||
|         } | ||||
|     ) | ||||
|     def list(self, request: Request) -> Response: | ||||
|         """Read-only view of analytics descriptions""" | ||||
|         return Response( | ||||
|             { | ||||
|                 "data": get_analytics_data(force=True), | ||||
|             } | ||||
|         ) | ||||
							
								
								
									
										12
									
								
								authentik/analytics/apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								authentik/analytics/apps.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| """authentik analytics app config""" | ||||
|  | ||||
| from authentik.blueprints.apps import ManagedAppConfig | ||||
|  | ||||
|  | ||||
| class AuthentikAdminConfig(ManagedAppConfig): | ||||
|     """authentik analytics app config""" | ||||
|  | ||||
|     name = "authentik.analytics" | ||||
|     label = "authentik_analytics" | ||||
|     verbose_name = "authentik Analytics" | ||||
|     default = True | ||||
							
								
								
									
										19
									
								
								authentik/analytics/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								authentik/analytics/models.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | ||||
| """authentik analytics mixins""" | ||||
|  | ||||
| from typing import Any | ||||
|  | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
|  | ||||
|  | ||||
| class AnalyticsMixin: | ||||
|     @classmethod | ||||
|     def get_analytics_description(cls) -> dict[str, str]: | ||||
|         object_name = _(cls._meta.verbose_name) | ||||
|         count_desc = _("Number of {object_name} objects".format_map({"object_name": object_name})) | ||||
|         return { | ||||
|             "count": count_desc, | ||||
|         } | ||||
|  | ||||
|     @classmethod | ||||
|     def get_analytics_data(cls) -> dict[str, Any]: | ||||
|         return {"count": cls.objects.all().count()} | ||||
							
								
								
									
										17
									
								
								authentik/analytics/settings.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								authentik/analytics/settings.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| """authentik admin settings""" | ||||
|  | ||||
| from celery.schedules import crontab | ||||
|  | ||||
| from authentik.lib.utils.time import fqdn_rand | ||||
|  | ||||
| CELERY_BEAT_SCHEDULE = { | ||||
|     "analytics_send": { | ||||
|         "task": "authentik.analytics.tasks.send_analytics", | ||||
|         "schedule": crontab( | ||||
|             minute=fqdn_rand("analytics_send"), | ||||
|             hour=fqdn_rand("analytics_send", stop=24), | ||||
|             day_of_week=fqdn_rand("analytics_send", 7), | ||||
|         ), | ||||
|         "options": {"queue": "authentik_scheduled"}, | ||||
|     } | ||||
| } | ||||
							
								
								
									
										45
									
								
								authentik/analytics/tasks.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								authentik/analytics/tasks.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,45 @@ | ||||
| """authentik admin tasks""" | ||||
|  | ||||
| import orjson | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from requests import RequestException | ||||
| from structlog.stdlib import get_logger | ||||
|  | ||||
| from authentik.analytics.utils import get_analytics_data | ||||
| from authentik.events.models import Event, EventAction | ||||
| from authentik.events.system_tasks import SystemTask, TaskStatus, prefill_task | ||||
| from authentik.lib.utils.http import get_http_session | ||||
| from authentik.root.celery import CELERY_APP | ||||
| from authentik.tenants.models import Tenant | ||||
|  | ||||
| LOGGER = get_logger() | ||||
|  | ||||
|  | ||||
| @CELERY_APP.task(bind=True, base=SystemTask) | ||||
| @prefill_task | ||||
| def send_analytics(self: SystemTask): | ||||
|     """Send analytics""" | ||||
|     for tenant in Tenant.objects.filter(ready=True): | ||||
|         data = get_analytics_data(current_tenant=tenant) | ||||
|         if not tenant.analytics_enabled or not data: | ||||
|             self.set_status(TaskStatus.WARNING, "Analytics disabled. Nothing was sent.") | ||||
|             return | ||||
|         try: | ||||
|             response = get_http_session().post( | ||||
|                 "https://customers.goauthentik.io/api/analytics/post/", json=data | ||||
|             ) | ||||
|             response.raise_for_status() | ||||
|             self.set_status( | ||||
|                 TaskStatus.SUCCESSFUL, | ||||
|                 "Successfully sent analytics", | ||||
|                 orjson.dumps( | ||||
|                     data, option=orjson.OPT_INDENT_2 | orjson.OPT_NON_STR_KEYS | orjson.OPT_UTC_Z | ||||
|                 ).decode(), | ||||
|             ) | ||||
|             Event.new( | ||||
|                 EventAction.ANALYTICS_SENT, | ||||
|                 message=_("Analytics sent"), | ||||
|                 analytics_data=data, | ||||
|             ).save() | ||||
|         except (RequestException, IndexError) as exc: | ||||
|             self.set_error(exc) | ||||
							
								
								
									
										76
									
								
								authentik/analytics/tests.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								authentik/analytics/tests.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,76 @@ | ||||
| """authentik analytics tests""" | ||||
|  | ||||
| from json import loads | ||||
| from requests_mock import Mocker | ||||
|  | ||||
| from django.test import TestCase | ||||
| from django.urls import reverse | ||||
|  | ||||
| from authentik import __version__ | ||||
| from authentik.analytics.tasks import send_analytics | ||||
| from authentik.analytics.utils import get_analytics_apps_data, get_analytics_apps_description, get_analytics_data, get_analytics_description, get_analytics_models_data, get_analytics_models_description | ||||
| from authentik.core.models import Group, User | ||||
| from authentik.events.models import Event, EventAction | ||||
| from authentik.lib.generators import generate_id | ||||
| from authentik.tenants.utils import get_current_tenant | ||||
|  | ||||
|  | ||||
| class TestAnalytics(TestCase): | ||||
|     """test analytics api""" | ||||
|  | ||||
|     def setUp(self) -> None: | ||||
|         super().setUp() | ||||
|         self.user = User.objects.create(username=generate_id()) | ||||
|         self.group = Group.objects.create(name=generate_id(), is_superuser=True) | ||||
|         self.group.users.add(self.user) | ||||
|         self.client.force_login(self.user) | ||||
|         self.tenant = get_current_tenant() | ||||
|  | ||||
|     def test_description_api(self): | ||||
|         """Test Version API""" | ||||
|         response = self.client.get(reverse("authentik_api:analytics-description-list")) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         loads(response.content) | ||||
|  | ||||
|     def test_data_api(self): | ||||
|         """Test Version API""" | ||||
|         response = self.client.get(reverse("authentik_api:analytics-data-list")) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         body = loads(response.content) | ||||
|         self.assertEqual(body["data"]["version"], __version__) | ||||
|  | ||||
|     def test_sending_enabled(self): | ||||
|         """Test analytics sending""" | ||||
|         self.tenant.analytics_enabled = True | ||||
|         self.tenant.save() | ||||
|         with Mocker() as mocker: | ||||
|             mocker.post("https://customers.goauthentik.io/api/analytics/post/", status_code=200) | ||||
|             send_analytics.delay().get() | ||||
|             self.assertTrue( | ||||
|                 Event.objects.filter( | ||||
|                     action=EventAction.ANALYTICS_SENT | ||||
|                 ).exists() | ||||
|             ) | ||||
|  | ||||
|     def test_sending_disabled(self): | ||||
|         """Test analytics sending""" | ||||
|         self.tenant.analytics_enabled = False | ||||
|         self.tenant.save() | ||||
|         send_analytics.delay().get() | ||||
|         self.assertFalse( | ||||
|             Event.objects.filter( | ||||
|                 action=EventAction.ANALYTICS_SENT | ||||
|             ).exists() | ||||
|         ) | ||||
|  | ||||
|     def test_description_data_match_apps(self): | ||||
|         """Test description and data keys match""" | ||||
|         description = get_analytics_apps_description() | ||||
|         data = get_analytics_apps_data() | ||||
|         self.assertEqual(data.keys(), description.keys()) | ||||
|  | ||||
|     def test_description_data_match_models(self): | ||||
|         """Test description and data keys match""" | ||||
|         description = get_analytics_models_description() | ||||
|         data = get_analytics_models_data() | ||||
|         self.assertEqual(data.keys(), description.keys()) | ||||
							
								
								
									
										8
									
								
								authentik/analytics/urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								authentik/analytics/urls.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| """API URLs""" | ||||
|  | ||||
| from authentik.analytics.api import AnalyticsDataViewSet, AnalyticsDescriptionViewSet | ||||
|  | ||||
| api_urlpatterns = [ | ||||
|     ("analytics/description", AnalyticsDescriptionViewSet, "analytics-description"), | ||||
|     ("analytics/data", AnalyticsDataViewSet, "analytics-data"), | ||||
| ] | ||||
							
								
								
									
										112
									
								
								authentik/analytics/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								authentik/analytics/utils.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,112 @@ | ||||
| """authentik analytics utils""" | ||||
|  | ||||
| from hashlib import sha256 | ||||
| from importlib import import_module | ||||
| from typing import Any | ||||
|  | ||||
| from structlog import get_logger | ||||
|  | ||||
| from authentik import get_full_version | ||||
| from authentik.analytics.models import AnalyticsMixin | ||||
| from authentik.lib.utils.reflection import get_apps | ||||
| from authentik.root.install_id import get_install_id | ||||
| from authentik.tenants.models import Tenant | ||||
| from authentik.tenants.utils import get_current_tenant | ||||
|  | ||||
| LOGGER = get_logger() | ||||
|  | ||||
|  | ||||
| def get_analytics_apps() -> dict: | ||||
|     modules = {} | ||||
|     for _authentik_app in get_apps(): | ||||
|         try: | ||||
|             module = import_module(f"{_authentik_app.name}.analytics") | ||||
|         except ModuleNotFoundError: | ||||
|             continue | ||||
|         except ImportError as exc: | ||||
|             LOGGER.warning( | ||||
|                 "Could not import app's analytics", app_name=_authentik_app.name, exc=exc | ||||
|             ) | ||||
|             continue | ||||
|         if not hasattr(module, "get_analytics_description") or not hasattr( | ||||
|             module, "get_analytics_data" | ||||
|         ): | ||||
|             LOGGER.debug( | ||||
|                 "App does not define API URLs", | ||||
|                 app_name=_authentik_app.name, | ||||
|             ) | ||||
|             continue | ||||
|         modules[_authentik_app.label] = module | ||||
|     return modules | ||||
|  | ||||
|  | ||||
| def get_analytics_apps_description() -> dict[str, str]: | ||||
|     result = {} | ||||
|     for app_label, module in get_analytics_apps().items(): | ||||
|         for k, v in module.get_analytics_description().items(): | ||||
|             result[f"{app_label}/app/{k}"] = v | ||||
|     return result | ||||
|  | ||||
|  | ||||
| def get_analytics_apps_data() -> dict[str, Any]: | ||||
|     result = {} | ||||
|     for app_label, module in get_analytics_apps().items(): | ||||
|         for k, v in module.get_analytics_data().items(): | ||||
|             result[f"{app_label}/app/{k}"] = v | ||||
|     return result | ||||
|  | ||||
|  | ||||
| def get_analytics_models() -> list[AnalyticsMixin]: | ||||
|     def get_subclasses(cls): | ||||
|         for subclass in cls.__subclasses__(): | ||||
|             if subclass.__subclasses__(): | ||||
|                 yield from get_subclasses(subclass) | ||||
|             elif not subclass._meta.abstract: | ||||
|                 yield subclass | ||||
|  | ||||
|     return list(get_subclasses(AnalyticsMixin)) | ||||
|  | ||||
|  | ||||
| def get_analytics_models_description() -> dict[str, str]: | ||||
|     result = {} | ||||
|     for model in get_analytics_models(): | ||||
|         for k, v in model.get_analytics_description().items(): | ||||
|             result[f"{model._meta.app_label}/models/{model._meta.object_name}/{k}"] = v | ||||
|     return result | ||||
|  | ||||
|  | ||||
| def get_analytics_models_data() -> dict[str, Any]: | ||||
|     result = {} | ||||
|     for model in get_analytics_models(): | ||||
|         for k, v in model.get_analytics_data().items(): | ||||
|             result[f"{model._meta.app_label}/models/{model._meta.object_name}/{k}"] = v | ||||
|     return result | ||||
|  | ||||
|  | ||||
| def get_analytics_description() -> dict[str, str]: | ||||
|     return { | ||||
|         **get_analytics_apps_description(), | ||||
|         **get_analytics_models_description(), | ||||
|     } | ||||
|  | ||||
|  | ||||
| def get_analytics_data(current_tenant: Tenant | None = None, force: bool = False) -> dict[str, Any]: | ||||
|     current_tenant = current_tenant or get_current_tenant() | ||||
|     if not current_tenant.analytics_enabled and not force: | ||||
|         return {} | ||||
|     data = { | ||||
|         **get_analytics_apps_data(), | ||||
|         **get_analytics_models_data(), | ||||
|     } | ||||
|     to_remove = [] | ||||
|     for key in data.keys(): | ||||
|         if key not in current_tenant.analytics_sources: | ||||
|             to_remove.append(key) | ||||
|     for key in to_remove: | ||||
|         del data[key] | ||||
|     return { | ||||
|         **data, | ||||
|         "install_id_hash": sha256(get_install_id().encode()).hexdigest(), | ||||
|         "tenant_hash": sha256(current_tenant.tenant_uuid.bytes).hexdigest(), | ||||
|         "version": get_full_version(), | ||||
|     } | ||||
| @ -30,8 +30,10 @@ from authentik.core.api.utils import ( | ||||
|     PassiveSerializer, | ||||
| ) | ||||
| from authentik.core.expression.evaluator import PropertyMappingEvaluator | ||||
| from authentik.core.expression.exceptions import PropertyMappingExpressionException | ||||
| from authentik.core.models import Group, PropertyMapping, User | ||||
| from authentik.events.utils import sanitize_item | ||||
| from authentik.lib.utils.errors import exception_to_string | ||||
| from authentik.policies.api.exec import PolicyTestSerializer | ||||
| from authentik.rbac.decorators import permission_required | ||||
|  | ||||
| @ -162,12 +164,15 @@ class PropertyMappingViewSet( | ||||
|  | ||||
|         response_data = {"successful": True, "result": ""} | ||||
|         try: | ||||
|             result = mapping.evaluate(**context) | ||||
|             result = mapping.evaluate(dry_run=True, **context) | ||||
|             response_data["result"] = dumps( | ||||
|                 sanitize_item(result), indent=(4 if format_result else None) | ||||
|             ) | ||||
|         except PropertyMappingExpressionException as exc: | ||||
|             response_data["result"] = exception_to_string(exc.exc) | ||||
|             response_data["successful"] = False | ||||
|         except Exception as exc: | ||||
|             response_data["result"] = str(exc) | ||||
|             response_data["result"] = exception_to_string(exc) | ||||
|             response_data["successful"] = False | ||||
|         response = PropertyMappingTestResultSerializer(response_data) | ||||
|         return Response(response.data) | ||||
|  | ||||
| @ -678,10 +678,10 @@ class UserViewSet(UsedByMixin, ModelViewSet): | ||||
|         if not request.tenant.impersonation: | ||||
|             LOGGER.debug("User attempted to impersonate", user=request.user) | ||||
|             return Response(status=401) | ||||
|         if not request.user.has_perm("impersonate"): | ||||
|         user_to_be = self.get_object() | ||||
|         if not request.user.has_perm("impersonate", user_to_be): | ||||
|             LOGGER.debug("User attempted to impersonate without permissions", user=request.user) | ||||
|             return Response(status=401) | ||||
|         user_to_be = self.get_object() | ||||
|         if user_to_be.pk == self.request.user.pk: | ||||
|             LOGGER.debug("User attempted to impersonate themselves", user=request.user) | ||||
|             return Response(status=401) | ||||
|  | ||||
| @ -9,10 +9,11 @@ class Command(TenantCommand): | ||||
|  | ||||
|     def add_arguments(self, parser): | ||||
|         parser.add_argument("--type", type=str, required=True) | ||||
|         parser.add_argument("--all", action="store_true") | ||||
|         parser.add_argument("usernames", nargs="+", type=str) | ||||
|         parser.add_argument("--all", action="store_true", default=False) | ||||
|         parser.add_argument("usernames", nargs="*", type=str) | ||||
|  | ||||
|     def handle_per_tenant(self, **options): | ||||
|         print(options) | ||||
|         new_type = UserTypes(options["type"]) | ||||
|         qs = ( | ||||
|             User.objects.exclude_anonymous() | ||||
| @ -22,6 +23,9 @@ class Command(TenantCommand): | ||||
|         if options["usernames"] and options["all"]: | ||||
|             self.stderr.write("--all and usernames specified, only one can be specified") | ||||
|             return | ||||
|         if not options["usernames"] and not options["all"]: | ||||
|             self.stderr.write("--all or usernames must be specified") | ||||
|             return | ||||
|         if options["usernames"] and not options["all"]: | ||||
|             qs = qs.filter(username__in=options["usernames"]) | ||||
|         updated = qs.update(type=new_type) | ||||
|  | ||||
| @ -23,6 +23,7 @@ from model_utils.managers import InheritanceManager | ||||
| from rest_framework.serializers import Serializer | ||||
| from structlog.stdlib import get_logger | ||||
|  | ||||
| from authentik.analytics.models import AnalyticsMixin | ||||
| from authentik.blueprints.models import ManagedModel | ||||
| from authentik.core.expression.exceptions import PropertyMappingExpressionException | ||||
| from authentik.core.types import UILoginButton, UserSettingSerializer | ||||
| @ -168,7 +169,7 @@ class GroupQuerySet(CTEQuerySet): | ||||
|         return cte.join(Group, group_uuid=cte.col.group_uuid).with_cte(cte) | ||||
|  | ||||
|  | ||||
| class Group(SerializerModel, AttributesMixin): | ||||
| class Group(SerializerModel, AttributesMixin, AnalyticsMixin): | ||||
|     """Group model which supports a basic hierarchy and has attributes""" | ||||
|  | ||||
|     group_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4) | ||||
| @ -258,7 +259,7 @@ class UserManager(DjangoUserManager): | ||||
|         return self.get_queryset().exclude_anonymous() | ||||
|  | ||||
|  | ||||
| class User(SerializerModel, GuardianUserMixin, AttributesMixin, AbstractUser): | ||||
| class User(SerializerModel, GuardianUserMixin, AttributesMixin, AbstractUser, AnalyticsMixin): | ||||
|     """authentik User model, based on django's contrib auth user model.""" | ||||
|  | ||||
|     uuid = models.UUIDField(default=uuid4, editable=False, unique=True) | ||||
| @ -376,7 +377,7 @@ class User(SerializerModel, GuardianUserMixin, AttributesMixin, AbstractUser): | ||||
|         return get_avatar(self) | ||||
|  | ||||
|  | ||||
| class Provider(SerializerModel): | ||||
| class Provider(SerializerModel, AnalyticsMixin): | ||||
|     """Application-independent Provider instance. For example SAML2 Remote, OAuth2 Application""" | ||||
|  | ||||
|     name = models.TextField(unique=True) | ||||
| @ -466,13 +467,11 @@ class ApplicationQuerySet(QuerySet): | ||||
|     def with_provider(self) -> "QuerySet[Application]": | ||||
|         qs = self.select_related("provider") | ||||
|         for subclass in Provider.objects.get_queryset()._get_subclasses_recurse(Provider): | ||||
|             if LOOKUP_SEP in subclass: | ||||
|                 continue | ||||
|             qs = qs.select_related(f"provider__{subclass}") | ||||
|         return qs | ||||
|  | ||||
|  | ||||
| class Application(SerializerModel, PolicyBindingModel): | ||||
| class Application(SerializerModel, PolicyBindingModel, AnalyticsMixin): | ||||
|     """Every Application which uses authentik for authentication/identification/authorization | ||||
|     needs an Application record. Other authentication types can subclass this Model to | ||||
|     add custom fields and other properties""" | ||||
| @ -545,15 +544,24 @@ class Application(SerializerModel, PolicyBindingModel): | ||||
|         if not self.provider: | ||||
|             return None | ||||
|  | ||||
|         for subclass in Provider.objects.get_queryset()._get_subclasses_recurse(Provider): | ||||
|             # We don't care about recursion, skip nested models | ||||
|             if LOOKUP_SEP in subclass: | ||||
|                 continue | ||||
|         candidates = [] | ||||
|         base_class = Provider | ||||
|         for subclass in base_class.objects.get_queryset()._get_subclasses_recurse(base_class): | ||||
|             parent = self.provider | ||||
|             for level in subclass.split(LOOKUP_SEP): | ||||
|                 try: | ||||
|                 return getattr(self.provider, subclass) | ||||
|                     parent = getattr(parent, level) | ||||
|                 except AttributeError: | ||||
|                 pass | ||||
|                     break | ||||
|             if parent in candidates: | ||||
|                 continue | ||||
|             idx = subclass.count(LOOKUP_SEP) | ||||
|             if type(parent) is not base_class: | ||||
|                 idx += 1 | ||||
|             candidates.insert(idx, parent) | ||||
|         if not candidates: | ||||
|             return None | ||||
|         return candidates[-1] | ||||
|  | ||||
|     def __str__(self): | ||||
|         return str(self.name) | ||||
| @ -596,7 +604,7 @@ class SourceGroupMatchingModes(models.TextChoices): | ||||
|     ) | ||||
|  | ||||
|  | ||||
| class Source(ManagedModel, SerializerModel, PolicyBindingModel): | ||||
| class Source(ManagedModel, SerializerModel, PolicyBindingModel, AnalyticsMixin): | ||||
|     """Base Authentication source, i.e. an OAuth Provider, SAML Remote or LDAP Server""" | ||||
|  | ||||
|     name = models.TextField(help_text=_("Source's display Name.")) | ||||
| @ -728,7 +736,7 @@ class Source(ManagedModel, SerializerModel, PolicyBindingModel): | ||||
|         ] | ||||
|  | ||||
|  | ||||
| class UserSourceConnection(SerializerModel, CreatedUpdatedModel): | ||||
| class UserSourceConnection(SerializerModel, CreatedUpdatedModel, AnalyticsMixin): | ||||
|     """Connection between User and Source.""" | ||||
|  | ||||
|     user = models.ForeignKey(User, on_delete=models.CASCADE) | ||||
| @ -748,7 +756,7 @@ class UserSourceConnection(SerializerModel, CreatedUpdatedModel): | ||||
|         unique_together = (("user", "source"),) | ||||
|  | ||||
|  | ||||
| class GroupSourceConnection(SerializerModel, CreatedUpdatedModel): | ||||
| class GroupSourceConnection(SerializerModel, CreatedUpdatedModel, AnalyticsMixin): | ||||
|     """Connection between Group and Source.""" | ||||
|  | ||||
|     group = models.ForeignKey(Group, on_delete=models.CASCADE) | ||||
| @ -872,7 +880,7 @@ class Token(SerializerModel, ManagedModel, ExpiringModel): | ||||
|         ).save() | ||||
|  | ||||
|  | ||||
| class PropertyMapping(SerializerModel, ManagedModel): | ||||
| class PropertyMapping(SerializerModel, ManagedModel, AnalyticsMixin): | ||||
|     """User-defined key -> x mapping which can be used by providers to expose extra data.""" | ||||
|  | ||||
|     pm_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4) | ||||
| @ -901,7 +909,7 @@ class PropertyMapping(SerializerModel, ManagedModel): | ||||
|         except ControlFlowException as exc: | ||||
|             raise exc | ||||
|         except Exception as exc: | ||||
|             raise PropertyMappingExpressionException(self, exc) from exc | ||||
|             raise PropertyMappingExpressionException(exc, self) from exc | ||||
|  | ||||
|     def __str__(self): | ||||
|         return f"Property Mapping {self.name}" | ||||
|  | ||||
| @ -9,9 +9,12 @@ from rest_framework.test import APITestCase | ||||
|  | ||||
| from authentik.core.models import Application | ||||
| from authentik.core.tests.utils import create_test_admin_user, create_test_flow | ||||
| from authentik.lib.generators import generate_id | ||||
| from authentik.policies.dummy.models import DummyPolicy | ||||
| from authentik.policies.models import PolicyBinding | ||||
| from authentik.providers.oauth2.models import OAuth2Provider | ||||
| from authentik.providers.proxy.models import ProxyProvider | ||||
| from authentik.providers.saml.models import SAMLProvider | ||||
|  | ||||
|  | ||||
| class TestApplicationsAPI(APITestCase): | ||||
| @ -222,3 +225,31 @@ class TestApplicationsAPI(APITestCase): | ||||
|                 ], | ||||
|             }, | ||||
|         ) | ||||
|  | ||||
|     def test_get_provider(self): | ||||
|         """Ensure that proxy providers (at the time of writing that is the only provider | ||||
|         that inherits from another proxy type (OAuth) instead of inheriting from the root | ||||
|         provider class) is correctly looked up and selected from the database""" | ||||
|         slug = generate_id() | ||||
|         provider = ProxyProvider.objects.create(name=generate_id()) | ||||
|         Application.objects.create( | ||||
|             name=generate_id(), | ||||
|             slug=slug, | ||||
|             provider=provider, | ||||
|         ) | ||||
|         self.assertEqual(Application.objects.get(slug=slug).get_provider(), provider) | ||||
|         self.assertEqual( | ||||
|             Application.objects.with_provider().get(slug=slug).get_provider(), provider | ||||
|         ) | ||||
|  | ||||
|         slug = generate_id() | ||||
|         provider = SAMLProvider.objects.create(name=generate_id()) | ||||
|         Application.objects.create( | ||||
|             name=generate_id(), | ||||
|             slug=slug, | ||||
|             provider=provider, | ||||
|         ) | ||||
|         self.assertEqual(Application.objects.get(slug=slug).get_provider(), provider) | ||||
|         self.assertEqual( | ||||
|             Application.objects.with_provider().get(slug=slug).get_provider(), provider | ||||
|         ) | ||||
|  | ||||
| @ -3,10 +3,10 @@ | ||||
| from json import loads | ||||
|  | ||||
| from django.urls import reverse | ||||
| from guardian.shortcuts import assign_perm | ||||
| from rest_framework.test import APITestCase | ||||
|  | ||||
| from authentik.core.models import User | ||||
| from authentik.core.tests.utils import create_test_admin_user | ||||
| from authentik.core.tests.utils import create_test_admin_user, create_test_user | ||||
| from authentik.tenants.utils import get_current_tenant | ||||
|  | ||||
|  | ||||
| @ -15,7 +15,7 @@ class TestImpersonation(APITestCase): | ||||
|  | ||||
|     def setUp(self) -> None: | ||||
|         super().setUp() | ||||
|         self.other_user = User.objects.create(username="to-impersonate") | ||||
|         self.other_user = create_test_user() | ||||
|         self.user = create_test_admin_user() | ||||
|  | ||||
|     def test_impersonate_simple(self): | ||||
| @ -44,6 +44,26 @@ class TestImpersonation(APITestCase): | ||||
|         self.assertEqual(response_body["user"]["username"], self.user.username) | ||||
|         self.assertNotIn("original", response_body) | ||||
|  | ||||
|     def test_impersonate_scoped(self): | ||||
|         """Test impersonation with scoped permissions""" | ||||
|         new_user = create_test_user() | ||||
|         assign_perm("authentik_core.impersonate", new_user, self.other_user) | ||||
|         assign_perm("authentik_core.view_user", new_user, self.other_user) | ||||
|         self.client.force_login(new_user) | ||||
|  | ||||
|         response = self.client.post( | ||||
|             reverse( | ||||
|                 "authentik_api:user-impersonate", | ||||
|                 kwargs={"pk": self.other_user.pk}, | ||||
|             ) | ||||
|         ) | ||||
|         self.assertEqual(response.status_code, 201) | ||||
|  | ||||
|         response = self.client.get(reverse("authentik_api:user-me")) | ||||
|         response_body = loads(response.content.decode()) | ||||
|         self.assertEqual(response_body["user"]["username"], self.other_user.username) | ||||
|         self.assertEqual(response_body["original"]["username"], new_user.username) | ||||
|  | ||||
|     def test_impersonate_denied(self): | ||||
|         """test impersonation without permissions""" | ||||
|         self.client.force_login(self.other_user) | ||||
|  | ||||
| @ -18,7 +18,7 @@ from authentik.core.api.used_by import UsedByMixin | ||||
| from authentik.core.api.utils import ModelSerializer, PassiveSerializer | ||||
| from authentik.core.models import User, UserTypes | ||||
| from authentik.enterprise.license import LicenseKey, LicenseSummarySerializer | ||||
| from authentik.enterprise.models import License, LicenseUsageStatus | ||||
| from authentik.enterprise.models import License | ||||
| from authentik.rbac.decorators import permission_required | ||||
| from authentik.tenants.utils import get_unique_identifier | ||||
|  | ||||
| @ -29,7 +29,7 @@ class EnterpriseRequiredMixin: | ||||
|  | ||||
|     def validate(self, attrs: dict) -> dict: | ||||
|         """Check that a valid license exists""" | ||||
|         if LicenseKey.cached_summary().status != LicenseUsageStatus.UNLICENSED: | ||||
|         if not LicenseKey.cached_summary().status.is_valid: | ||||
|             raise ValidationError(_("Enterprise is required to create/update this object.")) | ||||
|         return super().validate(attrs) | ||||
|  | ||||
|  | ||||
| @ -117,10 +117,13 @@ class LicenseKey: | ||||
|                     our_cert.public_key(), | ||||
|                     algorithms=["ES512"], | ||||
|                     audience=get_license_aud(), | ||||
|                     options={"verify_exp": check_expiry}, | ||||
|                     options={"verify_exp": check_expiry, "verify_signature": check_expiry}, | ||||
|                 ), | ||||
|             ) | ||||
|         except PyJWTError: | ||||
|             unverified = decode(jwt, options={"verify_signature": False}) | ||||
|             if unverified["aud"] != get_license_aud(): | ||||
|                 raise ValidationError("Invalid Install ID in license") from None | ||||
|             raise ValidationError("Unable to verify license") from None | ||||
|         return body | ||||
|  | ||||
| @ -134,7 +137,7 @@ class LicenseKey: | ||||
|             exp_ts = int(mktime(lic.expiry.timetuple())) | ||||
|             if total.exp == 0: | ||||
|                 total.exp = exp_ts | ||||
|             total.exp = min(total.exp, exp_ts) | ||||
|             total.exp = max(total.exp, exp_ts) | ||||
|             total.license_flags.extend(lic.status.license_flags) | ||||
|         return total | ||||
|  | ||||
|  | ||||
| @ -3,7 +3,7 @@ | ||||
| from datetime import datetime | ||||
|  | ||||
| from django.core.cache import cache | ||||
| from django.db.models.signals import post_save, pre_save | ||||
| from django.db.models.signals import post_delete, post_save, pre_save | ||||
| from django.dispatch import receiver | ||||
| from django.utils.timezone import get_current_timezone | ||||
|  | ||||
| @ -27,3 +27,9 @@ def post_save_license(sender: type[License], instance: License, **_): | ||||
|     """Trigger license usage calculation when license is saved""" | ||||
|     cache.delete(CACHE_KEY_ENTERPRISE_LICENSE) | ||||
|     enterprise_update_usage.delay() | ||||
|  | ||||
|  | ||||
| @receiver(post_delete, sender=License) | ||||
| def post_delete_license(sender: type[License], instance: License, **_): | ||||
|     """Clear license cache when license is deleted""" | ||||
|     cache.delete(CACHE_KEY_ENTERPRISE_LICENSE) | ||||
|  | ||||
| @ -69,8 +69,5 @@ class NotificationViewSet( | ||||
|     @action(detail=False, methods=["post"]) | ||||
|     def mark_all_seen(self, request: Request) -> Response: | ||||
|         """Mark all the user's notifications as seen""" | ||||
|         notifications = Notification.objects.filter(user=request.user) | ||||
|         for notification in notifications: | ||||
|             notification.seen = True | ||||
|         Notification.objects.bulk_update(notifications, ["seen"]) | ||||
|         Notification.objects.filter(user=request.user, seen=False).update(seen=True) | ||||
|         return Response({}, status=204) | ||||
|  | ||||
							
								
								
									
										49
									
								
								authentik/events/migrations/0008_alter_event_action.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								authentik/events/migrations/0008_alter_event_action.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,49 @@ | ||||
| # Generated by Django 5.0.9 on 2024-09-25 11:06 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ("authentik_events", "0007_event_authentik_e_action_9a9dd9_idx_and_more"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AlterField( | ||||
|             model_name="event", | ||||
|             name="action", | ||||
|             field=models.TextField( | ||||
|                 choices=[ | ||||
|                     ("login", "Login"), | ||||
|                     ("login_failed", "Login Failed"), | ||||
|                     ("logout", "Logout"), | ||||
|                     ("user_write", "User Write"), | ||||
|                     ("suspicious_request", "Suspicious Request"), | ||||
|                     ("password_set", "Password Set"), | ||||
|                     ("secret_view", "Secret View"), | ||||
|                     ("secret_rotate", "Secret Rotate"), | ||||
|                     ("invitation_used", "Invite Used"), | ||||
|                     ("authorize_application", "Authorize Application"), | ||||
|                     ("source_linked", "Source Linked"), | ||||
|                     ("impersonation_started", "Impersonation Started"), | ||||
|                     ("impersonation_ended", "Impersonation Ended"), | ||||
|                     ("flow_execution", "Flow Execution"), | ||||
|                     ("policy_execution", "Policy Execution"), | ||||
|                     ("policy_exception", "Policy Exception"), | ||||
|                     ("property_mapping_exception", "Property Mapping Exception"), | ||||
|                     ("system_task_execution", "System Task Execution"), | ||||
|                     ("system_task_exception", "System Task Exception"), | ||||
|                     ("system_exception", "System Exception"), | ||||
|                     ("configuration_error", "Configuration Error"), | ||||
|                     ("model_created", "Model Created"), | ||||
|                     ("model_updated", "Model Updated"), | ||||
|                     ("model_deleted", "Model Deleted"), | ||||
|                     ("email_sent", "Email Sent"), | ||||
|                     ("analytics_sent", "Analytics Sent"), | ||||
|                     ("update_available", "Update Available"), | ||||
|                     ("custom_", "Custom Prefix"), | ||||
|                 ] | ||||
|             ), | ||||
|         ), | ||||
|     ] | ||||
| @ -49,6 +49,7 @@ from authentik.policies.models import PolicyBindingModel | ||||
| from authentik.root.middleware import ClientIPMiddleware | ||||
| from authentik.stages.email.utils import TemplateEmailMessage | ||||
| from authentik.tenants.models import Tenant | ||||
| from authentik.tenants.utils import get_current_tenant | ||||
|  | ||||
| LOGGER = get_logger() | ||||
| DISCORD_FIELD_LIMIT = 25 | ||||
| @ -58,6 +59,10 @@ NOTIFICATION_SUMMARY_LENGTH = 75 | ||||
| def default_event_duration(): | ||||
|     """Default duration an Event is saved. | ||||
|     This is used as a fallback when no brand is available""" | ||||
|     try: | ||||
|         tenant = get_current_tenant() | ||||
|         return now() + timedelta_from_string(tenant.event_retention) | ||||
|     except Tenant.DoesNotExist: | ||||
|         return now() + timedelta(days=365) | ||||
|  | ||||
|  | ||||
| @ -114,6 +119,7 @@ class EventAction(models.TextChoices): | ||||
|     MODEL_DELETED = "model_deleted" | ||||
|     EMAIL_SENT = "email_sent" | ||||
|  | ||||
|     ANALYTICS_SENT = "analytics_sent" | ||||
|     UPDATE_AVAILABLE = "update_available" | ||||
|  | ||||
|     CUSTOM_PREFIX = "custom_" | ||||
| @ -245,12 +251,6 @@ class Event(SerializerModel, ExpiringModel): | ||||
|             if QS_QUERY in self.context["http_request"]["args"]: | ||||
|                 wrapped = self.context["http_request"]["args"][QS_QUERY] | ||||
|                 self.context["http_request"]["args"] = cleanse_dict(QueryDict(wrapped)) | ||||
|         if hasattr(request, "tenant"): | ||||
|             tenant: Tenant = request.tenant | ||||
|             # Because self.created only gets set on save, we can't use it's value here | ||||
|             # hence we set self.created to now and then use it | ||||
|             self.created = now() | ||||
|             self.expires = self.created + timedelta_from_string(tenant.event_retention) | ||||
|         if hasattr(request, "brand"): | ||||
|             brand: Brand = request.brand | ||||
|             self.brand = sanitize_dict(model_to_dict(brand)) | ||||
|  | ||||
| @ -13,7 +13,7 @@ from authentik.events.apps import SYSTEM_TASK_STATUS | ||||
| from authentik.events.models import Event, EventAction, SystemTask | ||||
| from authentik.events.tasks import event_notification_handler, gdpr_cleanup | ||||
| from authentik.flows.models import Stage | ||||
| from authentik.flows.planner import PLAN_CONTEXT_SOURCE, FlowPlan | ||||
| from authentik.flows.planner import PLAN_CONTEXT_OUTPOST, PLAN_CONTEXT_SOURCE, FlowPlan | ||||
| from authentik.flows.views.executor import SESSION_KEY_PLAN | ||||
| from authentik.root.monitoring import monitoring_set | ||||
| from authentik.stages.invitation.models import Invitation | ||||
| @ -38,6 +38,9 @@ def on_user_logged_in(sender, request: HttpRequest, user: User, **_): | ||||
|             # Save the login method used | ||||
|             kwargs[PLAN_CONTEXT_METHOD] = flow_plan.context[PLAN_CONTEXT_METHOD] | ||||
|             kwargs[PLAN_CONTEXT_METHOD_ARGS] = flow_plan.context.get(PLAN_CONTEXT_METHOD_ARGS, {}) | ||||
|         if PLAN_CONTEXT_OUTPOST in flow_plan.context: | ||||
|             # Save outpost context | ||||
|             kwargs[PLAN_CONTEXT_OUTPOST] = flow_plan.context[PLAN_CONTEXT_OUTPOST] | ||||
|     event = Event.new(EventAction.LOGIN, **kwargs).from_http(request, user=user) | ||||
|     request.session[SESSION_LOGIN_EVENT] = event | ||||
|  | ||||
|  | ||||
| @ -6,6 +6,7 @@ from django.db.models import Model | ||||
| from django.test import TestCase | ||||
|  | ||||
| from authentik.core.models import default_token_key | ||||
| from authentik.events.models import default_event_duration | ||||
| from authentik.lib.utils.reflection import get_apps | ||||
|  | ||||
|  | ||||
| @ -20,7 +21,7 @@ def model_tester_factory(test_model: type[Model]) -> Callable: | ||||
|         allowed = 0 | ||||
|         # Token-like objects need to lookup the current tenant to get the default token length | ||||
|         for field in test_model._meta.fields: | ||||
|             if field.default == default_token_key: | ||||
|             if field.default in [default_token_key, default_event_duration]: | ||||
|                 allowed += 1 | ||||
|         with self.assertNumQueries(allowed): | ||||
|             str(test_model()) | ||||
|  | ||||
| @ -2,7 +2,8 @@ | ||||
|  | ||||
| from unittest.mock import MagicMock, patch | ||||
|  | ||||
| from django.test import TestCase | ||||
| from django.urls import reverse | ||||
| from rest_framework.test import APITestCase | ||||
|  | ||||
| from authentik.core.models import Group, User | ||||
| from authentik.events.models import ( | ||||
| @ -10,6 +11,7 @@ from authentik.events.models import ( | ||||
|     EventAction, | ||||
|     Notification, | ||||
|     NotificationRule, | ||||
|     NotificationSeverity, | ||||
|     NotificationTransport, | ||||
|     NotificationWebhookMapping, | ||||
|     TransportMode, | ||||
| @ -20,7 +22,7 @@ from authentik.policies.exceptions import PolicyException | ||||
| from authentik.policies.models import PolicyBinding | ||||
|  | ||||
|  | ||||
| class TestEventsNotifications(TestCase): | ||||
| class TestEventsNotifications(APITestCase): | ||||
|     """Test Event Notifications""" | ||||
|  | ||||
|     def setUp(self) -> None: | ||||
| @ -131,3 +133,15 @@ class TestEventsNotifications(TestCase): | ||||
|         Notification.objects.all().delete() | ||||
|         Event.new(EventAction.CUSTOM_PREFIX).save() | ||||
|         self.assertEqual(Notification.objects.first().body, "foo") | ||||
|  | ||||
|     def test_api_mark_all_seen(self): | ||||
|         """Test mark_all_seen""" | ||||
|         self.client.force_login(self.user) | ||||
|  | ||||
|         Notification.objects.create( | ||||
|             severity=NotificationSeverity.NOTICE, body="foo", user=self.user, seen=False | ||||
|         ) | ||||
|  | ||||
|         response = self.client.post(reverse("authentik_api:notification-mark-all-seen")) | ||||
|         self.assertEqual(response.status_code, 204) | ||||
|         self.assertFalse(Notification.objects.filter(body="foo", seen=False).exists()) | ||||
|  | ||||
| @ -23,6 +23,7 @@ from authentik.flows.models import ( | ||||
|     in_memory_stage, | ||||
| ) | ||||
| from authentik.lib.config import CONFIG | ||||
| from authentik.outposts.models import Outpost | ||||
| from authentik.policies.engine import PolicyEngine | ||||
| from authentik.root.middleware import ClientIPMiddleware | ||||
|  | ||||
| @ -32,6 +33,7 @@ PLAN_CONTEXT_SSO = "is_sso" | ||||
| PLAN_CONTEXT_REDIRECT = "redirect" | ||||
| PLAN_CONTEXT_APPLICATION = "application" | ||||
| PLAN_CONTEXT_SOURCE = "source" | ||||
| PLAN_CONTEXT_OUTPOST = "outpost" | ||||
| # Is set by the Flow Planner when a FlowToken was used, and the currently active flow plan | ||||
| # was restored. | ||||
| PLAN_CONTEXT_IS_RESTORED = "is_restored" | ||||
| @ -143,10 +145,23 @@ class FlowPlanner: | ||||
|             and not request.user.is_superuser | ||||
|         ): | ||||
|             raise FlowNonApplicableException() | ||||
|         if self.flow.authentication == FlowAuthenticationRequirement.REQUIRE_OUTPOST: | ||||
|         outpost_user = ClientIPMiddleware.get_outpost_user(request) | ||||
|         if self.flow.authentication == FlowAuthenticationRequirement.REQUIRE_OUTPOST: | ||||
|             if not outpost_user: | ||||
|                 raise FlowNonApplicableException() | ||||
|         if outpost_user: | ||||
|             outpost = Outpost.objects.filter( | ||||
|                 # TODO: Since Outpost and user are not directly connected, we have to look up a user | ||||
|                 # like this. This should ideally by in authentik/outposts/models.py | ||||
|                 pk=outpost_user.username.replace("ak-outpost-", "") | ||||
|             ).first() | ||||
|             if outpost: | ||||
|                 return { | ||||
|                     PLAN_CONTEXT_OUTPOST: { | ||||
|                         "instance": outpost, | ||||
|                     } | ||||
|                 } | ||||
|         return {} | ||||
|  | ||||
|     def plan(self, request: HttpRequest, default_context: dict[str, Any] | None = None) -> FlowPlan: | ||||
|         """Check each of the flows' policies, check policies for each stage with PolicyBinding | ||||
| @ -159,11 +174,12 @@ class FlowPlanner: | ||||
|             self._logger.debug( | ||||
|                 "f(plan): starting planning process", | ||||
|             ) | ||||
|             context = default_context or {} | ||||
|             # Bit of a workaround here, if there is a pending user set in the default context | ||||
|             # we use that user for our cache key | ||||
|             # to make sure they don't get the generic response | ||||
|             if default_context and PLAN_CONTEXT_PENDING_USER in default_context: | ||||
|                 user = default_context[PLAN_CONTEXT_PENDING_USER] | ||||
|             if context and PLAN_CONTEXT_PENDING_USER in context: | ||||
|                 user = context[PLAN_CONTEXT_PENDING_USER] | ||||
|             else: | ||||
|                 user = request.user | ||||
|                 # We only need to check the flow authentication if it's planned without a user | ||||
| @ -171,14 +187,13 @@ class FlowPlanner: | ||||
|                 # or if a flow is restarted due to `invalid_response_action` being set to | ||||
|                 # `restart_with_context`, which can only happen if the user was already authorized | ||||
|                 # to use the flow | ||||
|                 self._check_authentication(request) | ||||
|                 context.update(self._check_authentication(request)) | ||||
|             # First off, check the flow's direct policy bindings | ||||
|             # to make sure the user even has access to the flow | ||||
|             engine = PolicyEngine(self.flow, user, request) | ||||
|             engine.use_cache = self.use_cache | ||||
|             if default_context: | ||||
|                 span.set_data("default_context", cleanse_dict(default_context)) | ||||
|                 engine.request.context.update(default_context) | ||||
|             span.set_data("context", cleanse_dict(context)) | ||||
|             engine.request.context.update(context) | ||||
|             engine.build() | ||||
|             result = engine.result | ||||
|             if not result.passing: | ||||
| @ -195,12 +210,12 @@ class FlowPlanner: | ||||
|                         key=cached_plan_key, | ||||
|                     ) | ||||
|                     # Reset the context as this isn't factored into caching | ||||
|                     cached_plan.context = default_context or {} | ||||
|                     cached_plan.context = context | ||||
|                     return cached_plan | ||||
|             self._logger.debug( | ||||
|                 "f(plan): building plan", | ||||
|             ) | ||||
|             plan = self._build_plan(user, request, default_context) | ||||
|             plan = self._build_plan(user, request, context) | ||||
|             if self.use_cache: | ||||
|                 cache.set(cache_key(self.flow, user), plan, CACHE_TIMEOUT) | ||||
|             if not plan.bindings and not self.allow_empty_flows: | ||||
|  | ||||
| @ -2,7 +2,6 @@ | ||||
|  | ||||
| import re | ||||
| import socket | ||||
| from collections.abc import Iterable | ||||
| from ipaddress import ip_address, ip_network | ||||
| from textwrap import indent | ||||
| from types import CodeType | ||||
| @ -28,6 +27,12 @@ from authentik.stages.authenticator import devices_for_user | ||||
|  | ||||
| LOGGER = get_logger() | ||||
|  | ||||
| ARG_SANITIZE = re.compile(r"[:.-]") | ||||
|  | ||||
|  | ||||
| def sanitize_arg(arg_name: str) -> str: | ||||
|     return re.sub(ARG_SANITIZE, "_", arg_name) | ||||
|  | ||||
|  | ||||
| class BaseEvaluator: | ||||
|     """Validate and evaluate python-based expressions""" | ||||
| @ -177,9 +182,9 @@ class BaseEvaluator: | ||||
|         proc = PolicyProcess(PolicyBinding(policy=policy), request=req, connection=None) | ||||
|         return proc.profiling_wrapper() | ||||
|  | ||||
|     def wrap_expression(self, expression: str, params: Iterable[str]) -> str: | ||||
|     def wrap_expression(self, expression: str) -> str: | ||||
|         """Wrap expression in a function, call it, and save the result as `result`""" | ||||
|         handler_signature = ",".join(params) | ||||
|         handler_signature = ",".join(sanitize_arg(x) for x in self._context.keys()) | ||||
|         full_expression = "" | ||||
|         full_expression += f"def handler({handler_signature}):\n" | ||||
|         full_expression += indent(expression, "    ") | ||||
| @ -188,8 +193,8 @@ class BaseEvaluator: | ||||
|  | ||||
|     def compile(self, expression: str) -> CodeType: | ||||
|         """Parse expression. Raises SyntaxError or ValueError if the syntax is incorrect.""" | ||||
|         param_keys = self._context.keys() | ||||
|         return compile(self.wrap_expression(expression, param_keys), self._filename, "exec") | ||||
|         expression = self.wrap_expression(expression) | ||||
|         return compile(expression, self._filename, "exec") | ||||
|  | ||||
|     def evaluate(self, expression_source: str) -> Any: | ||||
|         """Parse and evaluate expression. If the syntax is incorrect, a SyntaxError is raised. | ||||
| @ -205,7 +210,7 @@ class BaseEvaluator: | ||||
|                 self.handle_error(exc, expression_source) | ||||
|                 raise exc | ||||
|             try: | ||||
|                 _locals = self._context | ||||
|                 _locals = {sanitize_arg(x): y for x, y in self._context.items()} | ||||
|                 # Yes this is an exec, yes it is potentially bad. Since we limit what variables are | ||||
|                 # available here, and these policies can only be edited by admins, this is a risk | ||||
|                 # we're willing to take. | ||||
|  | ||||
| @ -0,0 +1,52 @@ | ||||
| # Generated by Django 5.0.9 on 2024-09-25 11:06 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ("authentik_policies_event_matcher", "0023_alter_eventmatcherpolicy_action_and_more"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AlterField( | ||||
|             model_name="eventmatcherpolicy", | ||||
|             name="action", | ||||
|             field=models.TextField( | ||||
|                 choices=[ | ||||
|                     ("login", "Login"), | ||||
|                     ("login_failed", "Login Failed"), | ||||
|                     ("logout", "Logout"), | ||||
|                     ("user_write", "User Write"), | ||||
|                     ("suspicious_request", "Suspicious Request"), | ||||
|                     ("password_set", "Password Set"), | ||||
|                     ("secret_view", "Secret View"), | ||||
|                     ("secret_rotate", "Secret Rotate"), | ||||
|                     ("invitation_used", "Invite Used"), | ||||
|                     ("authorize_application", "Authorize Application"), | ||||
|                     ("source_linked", "Source Linked"), | ||||
|                     ("impersonation_started", "Impersonation Started"), | ||||
|                     ("impersonation_ended", "Impersonation Ended"), | ||||
|                     ("flow_execution", "Flow Execution"), | ||||
|                     ("policy_execution", "Policy Execution"), | ||||
|                     ("policy_exception", "Policy Exception"), | ||||
|                     ("property_mapping_exception", "Property Mapping Exception"), | ||||
|                     ("system_task_execution", "System Task Execution"), | ||||
|                     ("system_task_exception", "System Task Exception"), | ||||
|                     ("system_exception", "System Exception"), | ||||
|                     ("configuration_error", "Configuration Error"), | ||||
|                     ("model_created", "Model Created"), | ||||
|                     ("model_updated", "Model Updated"), | ||||
|                     ("model_deleted", "Model Deleted"), | ||||
|                     ("email_sent", "Email Sent"), | ||||
|                     ("analytics_sent", "Analytics Sent"), | ||||
|                     ("update_available", "Update Available"), | ||||
|                     ("custom_", "Custom Prefix"), | ||||
|                 ], | ||||
|                 default=None, | ||||
|                 help_text="Match created events with this action type. When left empty, all action types will be matched.", | ||||
|                 null=True, | ||||
|             ), | ||||
|         ), | ||||
|     ] | ||||
| @ -4,13 +4,13 @@ from django.apps.registry import Apps | ||||
| from django.db.backends.base.schema import BaseDatabaseSchemaEditor | ||||
|  | ||||
| from django.db import migrations | ||||
| from django.contrib.auth.management import create_permissions | ||||
|  | ||||
|  | ||||
| def migrate_search_group(apps: Apps, schema_editor: BaseDatabaseSchemaEditor): | ||||
|     from guardian.shortcuts import assign_perm | ||||
|     from authentik.core.models import User | ||||
|     from django.apps import apps as real_apps | ||||
|     from django.contrib.auth.management import create_permissions | ||||
|     from guardian.shortcuts import UserObjectPermission | ||||
|  | ||||
|     db_alias = schema_editor.connection.alias | ||||
|  | ||||
| @ -20,14 +20,25 @@ def migrate_search_group(apps: Apps, schema_editor: BaseDatabaseSchemaEditor): | ||||
|     create_permissions(real_apps.get_app_config("authentik_providers_ldap"), using=db_alias) | ||||
|  | ||||
|     LDAPProvider = apps.get_model("authentik_providers_ldap", "ldapprovider") | ||||
|     Permission = apps.get_model("auth", "Permission") | ||||
|     UserObjectPermission = apps.get_model("guardian", "UserObjectPermission") | ||||
|     ContentType = apps.get_model("contenttypes", "ContentType") | ||||
|  | ||||
|     new_prem = Permission.objects.using(db_alias).get(codename="search_full_directory") | ||||
|     ct = ContentType.objects.using(db_alias).get( | ||||
|         app_label="authentik_providers_ldap", | ||||
|         model="ldapprovider", | ||||
|     ) | ||||
|  | ||||
|     for provider in LDAPProvider.objects.using(db_alias).all(): | ||||
|         for user_pk in ( | ||||
|             provider.search_group.users.using(db_alias).all().values_list("pk", flat=True) | ||||
|         ): | ||||
|             # We need the correct user model instance to assign the permission | ||||
|             assign_perm( | ||||
|                 "search_full_directory", User.objects.using(db_alias).get(pk=user_pk), provider | ||||
|         if not provider.search_group: | ||||
|             continue | ||||
|         for user in provider.search_group.users.using(db_alias).all(): | ||||
|             UserObjectPermission.objects.using(db_alias).create( | ||||
|                 user=user, | ||||
|                 permission=new_prem, | ||||
|                 object_pk=provider.pk, | ||||
|                 content_type=ct, | ||||
|             ) | ||||
|  | ||||
|  | ||||
| @ -35,6 +46,7 @@ class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ("authentik_providers_ldap", "0003_ldapprovider_mfa_support_and_more"), | ||||
|         ("guardian", "0002_generic_permissions_index"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|  | ||||
| @ -28,7 +28,7 @@ class ProxyDockerController(DockerController): | ||||
|         labels = super()._get_labels() | ||||
|         labels["traefik.enable"] = "true" | ||||
|         labels[f"traefik.http.routers.{traefik_name}-router.rule"] = ( | ||||
|             f"({' || '.join([f'Host(`{host}`)' for host in hosts])})" | ||||
|             f"({' || '.join([f'Host({host})' for host in hosts])})" | ||||
|             f" && PathPrefix(`/outpost.goauthentik.io`)" | ||||
|         ) | ||||
|         labels[f"traefik.http.routers.{traefik_name}-router.tls"] = "true" | ||||
|  | ||||
| @ -164,7 +164,7 @@ class SAMLProvider(Provider): | ||||
|     ) | ||||
|  | ||||
|     sign_assertion = models.BooleanField(default=True) | ||||
|     sign_response = models.BooleanField(default=True) | ||||
|     sign_response = models.BooleanField(default=False) | ||||
|  | ||||
|     @property | ||||
|     def launch_url(self) -> str | None: | ||||
|  | ||||
| @ -87,7 +87,11 @@ def task_error_hook(task_id: str, exception: Exception, traceback, *args, **kwar | ||||
|  | ||||
| def _get_startup_tasks_default_tenant() -> list[Callable]: | ||||
|     """Get all tasks to be run on startup for the default tenant""" | ||||
|     return [] | ||||
|     from authentik.outposts.tasks import outpost_connection_discovery | ||||
|  | ||||
|     return [ | ||||
|         outpost_connection_discovery, | ||||
|     ] | ||||
|  | ||||
|  | ||||
| def _get_startup_tasks_all_tenants() -> list[Callable]: | ||||
|  | ||||
| @ -221,9 +221,9 @@ class ClientIPMiddleware: | ||||
|             ) | ||||
|             return None | ||||
|         # Update sentry scope to include correct IP | ||||
|         user = Scope.get_isolation_scope()._user or {} | ||||
|         user["ip_address"] = delegated_ip | ||||
|         Scope.get_isolation_scope().set_user(user) | ||||
|         sentry_user = Scope.get_isolation_scope()._user or {} | ||||
|         sentry_user["ip_address"] = delegated_ip | ||||
|         Scope.get_isolation_scope().set_user(sentry_user) | ||||
|         # Set the outpost service account on the request | ||||
|         setattr(request, self.request_attr_outpost_user, user) | ||||
|         return delegated_ip | ||||
|  | ||||
| @ -70,6 +70,7 @@ TENANT_APPS = [ | ||||
|     "django.contrib.contenttypes", | ||||
|     "django.contrib.sessions", | ||||
|     "authentik.admin", | ||||
|     "authentik.analytics", | ||||
|     "authentik.api", | ||||
|     "authentik.crypto", | ||||
|     "authentik.flows", | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| """authentik storage backends""" | ||||
|  | ||||
| import os | ||||
| from urllib.parse import parse_qsl, urlsplit | ||||
|  | ||||
| from django.conf import settings | ||||
| from django.core.exceptions import SuspiciousOperation | ||||
| @ -110,3 +111,34 @@ class S3Storage(BaseS3Storage): | ||||
|         if self.querystring_auth: | ||||
|             return url | ||||
|         return self._strip_signing_parameters(url) | ||||
|  | ||||
|     def _strip_signing_parameters(self, url): | ||||
|         # Boto3 does not currently support generating URLs that are unsigned. Instead | ||||
|         # we take the signed URLs and strip any querystring params related to signing | ||||
|         # and expiration. | ||||
|         # Note that this may end up with URLs that are still invalid, especially if | ||||
|         # params are passed in that only work with signed URLs, e.g. response header | ||||
|         # params. | ||||
|         # The code attempts to strip all query parameters that match names of known | ||||
|         # parameters from v2 and v4 signatures, regardless of the actual signature | ||||
|         # version used. | ||||
|         split_url = urlsplit(url) | ||||
|         qs = parse_qsl(split_url.query, keep_blank_values=True) | ||||
|         blacklist = { | ||||
|             "x-amz-algorithm", | ||||
|             "x-amz-credential", | ||||
|             "x-amz-date", | ||||
|             "x-amz-expires", | ||||
|             "x-amz-signedheaders", | ||||
|             "x-amz-signature", | ||||
|             "x-amz-security-token", | ||||
|             "awsaccesskeyid", | ||||
|             "expires", | ||||
|             "signature", | ||||
|         } | ||||
|         filtered_qs = ((key, val) for key, val in qs if key.lower() not in blacklist) | ||||
|         # Note: Parameters that did not have a value in the original query string will | ||||
|         # have an '=' sign appended to it, e.g ?foo&bar becomes ?foo=&bar= | ||||
|         joined_qs = ("=".join(keyval) for keyval in filtered_qs) | ||||
|         split_url = split_url._replace(query="&".join(joined_qs)) | ||||
|         return split_url.geturl() | ||||
|  | ||||
| @ -38,7 +38,11 @@ class GroupLDAPSynchronizer(BaseLDAPSynchronizer): | ||||
|             search_base=self.base_dn_groups, | ||||
|             search_filter=self._source.group_object_filter, | ||||
|             search_scope=SUBTREE, | ||||
|             attributes=[ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES], | ||||
|             attributes=[ | ||||
|                 ALL_ATTRIBUTES, | ||||
|                 ALL_OPERATIONAL_ATTRIBUTES, | ||||
|                 self._source.object_uniqueness_field, | ||||
|             ], | ||||
|             **kwargs, | ||||
|         ) | ||||
|  | ||||
| @ -53,9 +57,9 @@ class GroupLDAPSynchronizer(BaseLDAPSynchronizer): | ||||
|                 continue | ||||
|             attributes = group.get("attributes", {}) | ||||
|             group_dn = flatten(flatten(group.get("entryDN", group.get("dn")))) | ||||
|             if self._source.object_uniqueness_field not in attributes: | ||||
|             if not attributes.get(self._source.object_uniqueness_field): | ||||
|                 self.message( | ||||
|                     f"Cannot find uniqueness field in attributes: '{group_dn}'", | ||||
|                     f"Uniqueness field not found/not set in attributes: '{group_dn}'", | ||||
|                     attributes=attributes.keys(), | ||||
|                     dn=group_dn, | ||||
|                 ) | ||||
|  | ||||
| @ -40,7 +40,11 @@ class UserLDAPSynchronizer(BaseLDAPSynchronizer): | ||||
|             search_base=self.base_dn_users, | ||||
|             search_filter=self._source.user_object_filter, | ||||
|             search_scope=SUBTREE, | ||||
|             attributes=[ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES], | ||||
|             attributes=[ | ||||
|                 ALL_ATTRIBUTES, | ||||
|                 ALL_OPERATIONAL_ATTRIBUTES, | ||||
|                 self._source.object_uniqueness_field, | ||||
|             ], | ||||
|             **kwargs, | ||||
|         ) | ||||
|  | ||||
| @ -55,9 +59,9 @@ class UserLDAPSynchronizer(BaseLDAPSynchronizer): | ||||
|                 continue | ||||
|             attributes = user.get("attributes", {}) | ||||
|             user_dn = flatten(user.get("entryDN", user.get("dn"))) | ||||
|             if self._source.object_uniqueness_field not in attributes: | ||||
|             if not attributes.get(self._source.object_uniqueness_field): | ||||
|                 self.message( | ||||
|                     f"Cannot find uniqueness field in attributes: '{user_dn}'", | ||||
|                     f"Uniqueness field not found/not set in attributes: '{user_dn}'", | ||||
|                     attributes=attributes.keys(), | ||||
|                     dn=user_dn, | ||||
|                 ) | ||||
|  | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @ -1,9 +1,12 @@ | ||||
| """Serializer for tenants models""" | ||||
|  | ||||
| from django_tenants.utils import get_public_schema_name | ||||
| from rest_framework.fields import SerializerMethodField | ||||
| from rest_framework.generics import RetrieveUpdateAPIView | ||||
| from rest_framework.permissions import SAFE_METHODS | ||||
|  | ||||
| from authentik.analytics.api import AnalyticsDescriptionSerializer | ||||
| from authentik.analytics.utils import get_analytics_description | ||||
| from authentik.core.api.utils import ModelSerializer | ||||
| from authentik.rbac.permissions import HasPermission | ||||
| from authentik.tenants.models import Tenant | ||||
| @ -12,6 +15,8 @@ from authentik.tenants.models import Tenant | ||||
| class SettingsSerializer(ModelSerializer): | ||||
|     """Settings Serializer""" | ||||
|  | ||||
|     analytics_sources_obj = SerializerMethodField() | ||||
|  | ||||
|     class Meta: | ||||
|         model = Tenant | ||||
|         fields = [ | ||||
| @ -25,8 +30,19 @@ class SettingsSerializer(ModelSerializer): | ||||
|             "impersonation", | ||||
|             "default_token_duration", | ||||
|             "default_token_length", | ||||
|             "default_token_length", | ||||
|             "analytics_enabled", | ||||
|             "analytics_sources", | ||||
|             "analytics_sources_obj", | ||||
|         ] | ||||
|  | ||||
|     def get_analytics_sources_obj(self, obj: Tenant) -> list[AnalyticsDescriptionSerializer]: | ||||
|         result = [] | ||||
|         for label, desc in get_analytics_description().items(): | ||||
|             if label in obj.analytics_sources: | ||||
|                 result.append((label, desc)) | ||||
|         return result | ||||
|  | ||||
|  | ||||
| class SettingsView(RetrieveUpdateAPIView): | ||||
|     """Settings view""" | ||||
|  | ||||
| @ -0,0 +1,26 @@ | ||||
| # Generated by Django 5.0.9 on 2024-09-24 15:36 | ||||
|  | ||||
| import django.contrib.postgres.fields | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ("authentik_tenants", "0003_alter_tenant_default_token_duration"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name="tenant", | ||||
|             name="analytics_enabled", | ||||
|             field=models.BooleanField(default=False), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name="tenant", | ||||
|             name="analytics_sources", | ||||
|             field=django.contrib.postgres.fields.ArrayField( | ||||
|                 base_field=models.TextField(), blank=True, default=list, size=None | ||||
|             ), | ||||
|         ), | ||||
|     ] | ||||
| @ -4,6 +4,7 @@ import re | ||||
| from uuid import uuid4 | ||||
|  | ||||
| from django.apps import apps | ||||
| from django.contrib.postgres.fields import ArrayField | ||||
| from django.core.exceptions import ValidationError | ||||
| from django.core.validators import MinValueValidator | ||||
| from django.db import models | ||||
| @ -96,6 +97,9 @@ class Tenant(TenantMixin, SerializerModel): | ||||
|         validators=[MinValueValidator(1)], | ||||
|     ) | ||||
|  | ||||
|     analytics_enabled = models.BooleanField(default=False) | ||||
|     analytics_sources = ArrayField(models.TextField(), blank=True, default=list) | ||||
|  | ||||
|     def save(self, *args, **kwargs): | ||||
|         if self.schema_name == "template": | ||||
|             raise IntegrityError("Cannot create schema named template") | ||||
|  | ||||
| @ -2,7 +2,7 @@ | ||||
|     "$schema": "http://json-schema.org/draft-07/schema", | ||||
|     "$id": "https://goauthentik.io/blueprints/schema.json", | ||||
|     "type": "object", | ||||
|     "title": "authentik 2024.8.0 Blueprint schema", | ||||
|     "title": "authentik 2024.8.2 Blueprint schema", | ||||
|     "required": [ | ||||
|         "version", | ||||
|         "entries" | ||||
| @ -4227,6 +4227,7 @@ | ||||
|                         "model_updated", | ||||
|                         "model_deleted", | ||||
|                         "email_sent", | ||||
|                         "analytics_sent", | ||||
|                         "update_available", | ||||
|                         "custom_" | ||||
|                     ], | ||||
| @ -4251,6 +4252,7 @@ | ||||
|                         null, | ||||
|                         "authentik.tenants", | ||||
|                         "authentik.admin", | ||||
|                         "authentik.analytics", | ||||
|                         "authentik.api", | ||||
|                         "authentik.crypto", | ||||
|                         "authentik.flows", | ||||
| @ -13116,6 +13118,7 @@ | ||||
|                         "model_updated", | ||||
|                         "model_deleted", | ||||
|                         "email_sent", | ||||
|                         "analytics_sent", | ||||
|                         "update_available", | ||||
|                         "custom_" | ||||
|                     ], | ||||
| @ -13277,6 +13280,7 @@ | ||||
|                                 "model_updated", | ||||
|                                 "model_deleted", | ||||
|                                 "email_sent", | ||||
|                                 "analytics_sent", | ||||
|                                 "update_available", | ||||
|                                 "custom_" | ||||
|                             ], | ||||
|  | ||||
| @ -31,7 +31,7 @@ services: | ||||
|     volumes: | ||||
|       - redis:/data | ||||
|   server: | ||||
|     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.8.0} | ||||
|     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.8.2} | ||||
|     restart: unless-stopped | ||||
|     command: server | ||||
|     environment: | ||||
| @ -52,7 +52,7 @@ services: | ||||
|       - postgresql | ||||
|       - redis | ||||
|   worker: | ||||
|     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.8.0} | ||||
|     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.8.2} | ||||
|     restart: unless-stopped | ||||
|     command: worker | ||||
|     environment: | ||||
|  | ||||
							
								
								
									
										10
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								go.mod
									
									
									
									
									
								
							| @ -7,7 +7,7 @@ toolchain go1.23.0 | ||||
| require ( | ||||
| 	beryju.io/ldap v0.1.0 | ||||
| 	github.com/coreos/go-oidc/v3 v3.11.0 | ||||
| 	github.com/getsentry/sentry-go v0.28.1 | ||||
| 	github.com/getsentry/sentry-go v0.29.0 | ||||
| 	github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1 | ||||
| 	github.com/go-ldap/ldap/v3 v3.4.8 | ||||
| 	github.com/go-openapi/runtime v0.28.0 | ||||
| @ -18,20 +18,20 @@ require ( | ||||
| 	github.com/gorilla/securecookie v1.1.2 | ||||
| 	github.com/gorilla/sessions v1.4.0 | ||||
| 	github.com/gorilla/websocket v1.5.3 | ||||
| 	github.com/jellydator/ttlcache/v3 v3.2.1 | ||||
| 	github.com/jellydator/ttlcache/v3 v3.3.0 | ||||
| 	github.com/mitchellh/mapstructure v1.5.0 | ||||
| 	github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 | ||||
| 	github.com/pires/go-proxyproto v0.7.0 | ||||
| 	github.com/prometheus/client_golang v1.20.2 | ||||
| 	github.com/prometheus/client_golang v1.20.4 | ||||
| 	github.com/redis/go-redis/v9 v9.6.1 | ||||
| 	github.com/sethvargo/go-envconfig v1.1.0 | ||||
| 	github.com/sirupsen/logrus v1.9.3 | ||||
| 	github.com/spf13/cobra v1.8.1 | ||||
| 	github.com/stretchr/testify v1.9.0 | ||||
| 	github.com/wwt/guac v1.3.2 | ||||
| 	goauthentik.io/api/v3 v3.2024064.1 | ||||
| 	goauthentik.io/api/v3 v3.2024082.1 | ||||
| 	golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab | ||||
| 	golang.org/x/oauth2 v0.22.0 | ||||
| 	golang.org/x/oauth2 v0.23.0 | ||||
| 	golang.org/x/sync v0.8.0 | ||||
| 	gopkg.in/yaml.v2 v2.4.0 | ||||
| 	layeh.com/radius v0.0.0-20210819152912-ad72663a72ab | ||||
|  | ||||
							
								
								
									
										20
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								go.sum
									
									
									
									
									
								
							| @ -69,8 +69,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m | ||||
| github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= | ||||
| github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= | ||||
| github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= | ||||
| github.com/getsentry/sentry-go v0.28.1 h1:zzaSm/vHmGllRM6Tpx1492r0YDzauArdBfkJRtY6P5k= | ||||
| github.com/getsentry/sentry-go v0.28.1/go.mod h1:1fQZ+7l7eeJ3wYi82q5Hg8GqAPgefRq+FP/QhafYVgg= | ||||
| github.com/getsentry/sentry-go v0.29.0 h1:YtWluuCFg9OfcqnaujpY918N/AhCCwarIDWOYSBAjCA= | ||||
| github.com/getsentry/sentry-go v0.29.0/go.mod h1:jhPesDAL0Q0W2+2YEuVOvdWmVtdsr1+jtBrlDEVWwLY= | ||||
| github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= | ||||
| github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= | ||||
| github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= | ||||
| @ -200,8 +200,8 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6 | ||||
| github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= | ||||
| github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= | ||||
| github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= | ||||
| github.com/jellydator/ttlcache/v3 v3.2.1 h1:eS8ljnYY7BllYGkXw/TfczWZrXUu/CH7SIkC6ugn9Js= | ||||
| github.com/jellydator/ttlcache/v3 v3.2.1/go.mod h1:bj2/e0l4jRnQdrnSTaGTsh4GSXvMjQcy41i7th0GVGw= | ||||
| github.com/jellydator/ttlcache/v3 v3.3.0 h1:BdoC9cE81qXfrxeb9eoJi9dWrdhSuwXMAnHTbnBm4Wc= | ||||
| github.com/jellydator/ttlcache/v3 v3.3.0/go.mod h1:bj2/e0l4jRnQdrnSTaGTsh4GSXvMjQcy41i7th0GVGw= | ||||
| github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= | ||||
| github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= | ||||
| github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= | ||||
| @ -239,8 +239,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= | ||||
| github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||
| github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||||
| github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||
| github.com/prometheus/client_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg= | ||||
| github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= | ||||
| github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= | ||||
| github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= | ||||
| github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | ||||
| github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= | ||||
| github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= | ||||
| @ -299,8 +299,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y | ||||
| go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= | ||||
| go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= | ||||
| go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= | ||||
| goauthentik.io/api/v3 v3.2024064.1 h1:vxquklgDGD+nGFhWRAsQ7ezQKg17MRq6bzEk25fbsb4= | ||||
| goauthentik.io/api/v3 v3.2024064.1/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw= | ||||
| goauthentik.io/api/v3 v3.2024082.1 h1:V/3tq3rGK8Fse6xqnVQ8epzzytjXRI93y+jNHen2zMQ= | ||||
| goauthentik.io/api/v3 v3.2024082.1/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw= | ||||
| golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||||
| golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||
| golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||
| @ -388,8 +388,8 @@ golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4Iltr | ||||
| golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||||
| golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||||
| golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= | ||||
| golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= | ||||
| golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= | ||||
| golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= | ||||
| golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= | ||||
| golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
|  | ||||
| @ -29,4 +29,4 @@ func UserAgent() string { | ||||
| 	return fmt.Sprintf("authentik@%s", FullVersion()) | ||||
| } | ||||
|  | ||||
| const VERSION = "2024.8.0" | ||||
| const VERSION = "2024.8.2" | ||||
|  | ||||
| @ -35,10 +35,11 @@ func Paginator[Tobj any, Treq any, Tres PaginatorResponse[Tobj]]( | ||||
| 	req PaginatorRequest[Treq, Tres], | ||||
| 	opts PaginatorOptions, | ||||
| ) ([]Tobj, error) { | ||||
| 	var bfreq, cfreq interface{} | ||||
| 	fetchOffset := func(page int32) (Tres, error) { | ||||
| 		req.Page(page) | ||||
| 		req.PageSize(int32(opts.PageSize)) | ||||
| 		res, _, err := req.Execute() | ||||
| 		bfreq = req.Page(page) | ||||
| 		cfreq = bfreq.(PaginatorRequest[Treq, Tres]).PageSize(int32(opts.PageSize)) | ||||
| 		res, _, err := cfreq.(PaginatorRequest[Treq, Tres]).Execute() | ||||
| 		if err != nil { | ||||
| 			opts.Logger.WithError(err).WithField("page", page).Warning("failed to fetch page") | ||||
| 		} | ||||
|  | ||||
							
								
								
									
										26
									
								
								internal/outpost/ak/api_utils_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								internal/outpost/ak/api_utils_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | ||||
| package ak | ||||
|  | ||||
| // func Test_PaginatorCompile(t *testing.T) { | ||||
| // 	req := api.ApiCoreUsersListRequest{} | ||||
| // 	Paginator(req, PaginatorOptions{ | ||||
| // 		PageSize: 100, | ||||
| // 	}) | ||||
| // } | ||||
|  | ||||
| // func Test_PaginatorCompileExplicit(t *testing.T) { | ||||
| // 	req := api.ApiCoreUsersListRequest{} | ||||
| // 	Paginator[ | ||||
| // 		api.User, | ||||
| // 		api.ApiCoreUsersListRequest, | ||||
| // 		*api.PaginatedUserList, | ||||
| // 	](req, PaginatorOptions{ | ||||
| // 		PageSize: 100, | ||||
| // 	}) | ||||
| // } | ||||
|  | ||||
| // func Test_PaginatorCompileOther(t *testing.T) { | ||||
| // 	req := api.ApiOutpostsProxyListRequest{} | ||||
| // 	Paginator(req, PaginatorOptions{ | ||||
| // 		PageSize: 100, | ||||
| // 	}) | ||||
| // } | ||||
| @ -96,7 +96,7 @@ func (db *DirectBinder) Bind(username string, req *bind.Request) (ldap.LDAPResul | ||||
| 		return ldap.LDAPResultOperationsError, nil | ||||
| 	} | ||||
| 	flags.UserPk = userInfo.User.Pk | ||||
| 	flags.CanSearch = access.HasSearchPermission != nil | ||||
| 	flags.CanSearch = access.GetHasSearchPermission() | ||||
| 	db.si.SetFlags(req.BindDN, &flags) | ||||
| 	if flags.CanSearch { | ||||
| 		req.Log().Debug("Allowed access to search") | ||||
|  | ||||
| @ -65,8 +65,11 @@ type Server interface { | ||||
| 	CryptoStore() *ak.CryptoStore | ||||
| } | ||||
|  | ||||
| func NewApplication(p api.ProxyOutpostConfig, c *http.Client, server Server) (*Application, error) { | ||||
| func init() { | ||||
| 	gob.Register(Claims{}) | ||||
| } | ||||
|  | ||||
| func NewApplication(p api.ProxyOutpostConfig, c *http.Client, server Server, oldApp *Application) (*Application, error) { | ||||
| 	muxLogger := log.WithField("logger", "authentik.outpost.proxyv2.application").WithField("name", p.Name) | ||||
|  | ||||
| 	externalHost, err := url.Parse(p.ExternalHost) | ||||
| @ -137,7 +140,15 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, server Server) (*A | ||||
| 		isEmbedded:           isEmbedded, | ||||
| 	} | ||||
| 	go a.authHeaderCache.Start() | ||||
| 	a.sessions = a.getStore(p, externalHost) | ||||
| 	if oldApp != nil && oldApp.sessions != nil { | ||||
| 		a.sessions = oldApp.sessions | ||||
| 	} else { | ||||
| 		sess, err := a.getStore(p, externalHost) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		a.sessions = sess | ||||
| 	} | ||||
| 	mux.Use(web.NewLoggingHandler(muxLogger, func(l *log.Entry, r *http.Request) *log.Entry { | ||||
| 		c := a.getClaimsFromSession(r) | ||||
| 		if c == nil { | ||||
| @ -193,7 +204,17 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, server Server) (*A | ||||
| 	}) | ||||
|  | ||||
| 	mux.HandleFunc("/outpost.goauthentik.io/start", func(w http.ResponseWriter, r *http.Request) { | ||||
| 		a.handleAuthStart(w, r, "") | ||||
| 		fwd := "" | ||||
| 		// This should only really be hit for nginx forward_auth | ||||
| 		// as for that the auth start redirect URL is generated by the | ||||
| 		// reverse proxy, and as such we won't have a request we just | ||||
| 		// denied to reference for final URL | ||||
| 		rd, ok := a.checkRedirectParam(r) | ||||
| 		if ok { | ||||
| 			a.log.WithField("rd", rd).Trace("Setting redirect") | ||||
| 			fwd = rd | ||||
| 		} | ||||
| 		a.handleAuthStart(w, r, fwd) | ||||
| 	}) | ||||
| 	mux.HandleFunc("/outpost.goauthentik.io/callback", a.handleAuthCallback) | ||||
| 	mux.HandleFunc("/outpost.goauthentik.io/sign_out", a.handleSignOut) | ||||
| @ -225,9 +246,8 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, server Server) (*A | ||||
| 				// TODO: maybe create event for this? | ||||
| 				a.log.WithError(err).Warning("failed to compile SkipPathRegex") | ||||
| 				continue | ||||
| 			} else { | ||||
| 				a.UnauthenticatedRegex = append(a.UnauthenticatedRegex, re) | ||||
| 			} | ||||
| 			a.UnauthenticatedRegex = append(a.UnauthenticatedRegex, re) | ||||
| 		} | ||||
| 	} | ||||
| 	return a, nil | ||||
|  | ||||
| @ -15,36 +15,6 @@ const ( | ||||
| 	LogoutSignature   = "X-authentik-logout" | ||||
| ) | ||||
|  | ||||
| func (a *Application) checkRedirectParam(r *http.Request) (string, bool) { | ||||
| 	rd := r.URL.Query().Get(redirectParam) | ||||
| 	if rd == "" { | ||||
| 		return "", false | ||||
| 	} | ||||
| 	u, err := url.Parse(rd) | ||||
| 	if err != nil { | ||||
| 		a.log.WithError(err).Warning("Failed to parse redirect URL") | ||||
| 		return "", false | ||||
| 	} | ||||
| 	// Check to make sure we only redirect to allowed places | ||||
| 	if a.Mode() == api.PROXYMODE_PROXY || a.Mode() == api.PROXYMODE_FORWARD_SINGLE { | ||||
| 		ext, err := url.Parse(a.proxyConfig.ExternalHost) | ||||
| 		if err != nil { | ||||
| 			return "", false | ||||
| 		} | ||||
| 		ext.Scheme = "" | ||||
| 		if !strings.Contains(u.String(), ext.String()) { | ||||
| 			a.log.WithField("url", u.String()).WithField("ext", ext.String()).Warning("redirect URI did not contain external host") | ||||
| 			return "", false | ||||
| 		} | ||||
| 	} else { | ||||
| 		if !strings.HasSuffix(u.Host, *a.proxyConfig.CookieDomain) { | ||||
| 			a.log.WithField("host", u.Host).WithField("dom", *a.proxyConfig.CookieDomain).Warning("redirect URI Host was not included in cookie domain") | ||||
| 			return "", false | ||||
| 		} | ||||
| 	} | ||||
| 	return u.String(), true | ||||
| } | ||||
|  | ||||
| func (a *Application) handleAuthStart(rw http.ResponseWriter, r *http.Request, fwd string) { | ||||
| 	state, err := a.createState(r, fwd) | ||||
| 	if err != nil { | ||||
|  | ||||
| @ -5,10 +5,13 @@ import ( | ||||
| 	"encoding/base64" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/golang-jwt/jwt/v5" | ||||
| 	"github.com/gorilla/securecookie" | ||||
| 	"github.com/mitchellh/mapstructure" | ||||
| 	"goauthentik.io/api/v3" | ||||
| ) | ||||
|  | ||||
| type OAuthState struct { | ||||
| @ -27,6 +30,44 @@ func (oas *OAuthState) GetAudience() (jwt.ClaimStrings, error)       { return ni | ||||
|  | ||||
| var base32RawStdEncoding = base32.StdEncoding.WithPadding(base32.NoPadding) | ||||
|  | ||||
| // Validate that the given redirect parameter (?rd=...) is valid and can be used | ||||
| // For proxy/forward_single this checks that if the `rd` param has a Hostname (and is a full URL) | ||||
| // the hostname matches what's configured, or no hostname must be given | ||||
| // For forward_domain this checks if the domain of the URL in `rd` ends with the configured domain | ||||
| func (a *Application) checkRedirectParam(r *http.Request) (string, bool) { | ||||
| 	rd := r.URL.Query().Get(redirectParam) | ||||
| 	if rd == "" { | ||||
| 		return "", false | ||||
| 	} | ||||
| 	u, err := url.Parse(rd) | ||||
| 	if err != nil { | ||||
| 		a.log.WithError(err).Warning("Failed to parse redirect URL") | ||||
| 		return "", false | ||||
| 	} | ||||
| 	// Check to make sure we only redirect to allowed places | ||||
| 	if a.Mode() == api.PROXYMODE_PROXY || a.Mode() == api.PROXYMODE_FORWARD_SINGLE { | ||||
| 		ext, err := url.Parse(a.proxyConfig.ExternalHost) | ||||
| 		if err != nil { | ||||
| 			return "", false | ||||
| 		} | ||||
| 		// Either hostname needs to match the configured domain, or host name must be empty for just a path | ||||
| 		if u.Host == "" { | ||||
| 			u.Host = ext.Host | ||||
| 			u.Scheme = ext.Scheme | ||||
| 		} | ||||
| 		if u.Host != ext.Host { | ||||
| 			a.log.WithField("url", u.String()).WithField("ext", ext.String()).Warning("redirect URI did not contain external host") | ||||
| 			return "", false | ||||
| 		} | ||||
| 	} else { | ||||
| 		if !strings.HasSuffix(u.Host, *a.proxyConfig.CookieDomain) { | ||||
| 			a.log.WithField("host", u.Host).WithField("dom", *a.proxyConfig.CookieDomain).Warning("redirect URI Host was not included in cookie domain") | ||||
| 			return "", false | ||||
| 		} | ||||
| 	} | ||||
| 	return u.String(), true | ||||
| } | ||||
|  | ||||
| func (a *Application) createState(r *http.Request, fwd string) (string, error) { | ||||
| 	s, _ := a.sessions.Get(r, a.SessionName()) | ||||
| 	if s.ID == "" { | ||||
| @ -39,17 +80,6 @@ func (a *Application) createState(r *http.Request, fwd string) (string, error) { | ||||
| 		SessionID: s.ID, | ||||
| 		Redirect:  fwd, | ||||
| 	} | ||||
| 	if fwd == "" { | ||||
| 		// This should only really be hit for nginx forward_auth | ||||
| 		// as for that the auth start redirect URL is generated by the | ||||
| 		// reverse proxy, and as such we won't have a request we just | ||||
| 		// denied to reference for final URL | ||||
| 		rd, ok := a.checkRedirectParam(r) | ||||
| 		if ok { | ||||
| 			a.log.WithField("rd", rd).Trace("Setting redirect") | ||||
| 			st.Redirect = rd | ||||
| 		} | ||||
| 	} | ||||
| 	token := jwt.NewWithClaims(jwt.SigningMethodHS256, st) | ||||
| 	tokenString, err := token.SignedString([]byte(a.proxyConfig.GetCookieSecret())) | ||||
| 	if err != nil { | ||||
|  | ||||
| @ -8,25 +8,45 @@ import ( | ||||
| 	"goauthentik.io/api/v3" | ||||
| ) | ||||
|  | ||||
| func TestCheckRedirectParam(t *testing.T) { | ||||
| func TestCheckRedirectParam_None(t *testing.T) { | ||||
| 	a := newTestApplication() | ||||
| 	// Test no rd param | ||||
| 	req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/start", nil) | ||||
|  | ||||
| 	rd, ok := a.checkRedirectParam(req) | ||||
|  | ||||
| 	assert.Equal(t, false, ok) | ||||
| 	assert.Equal(t, "", rd) | ||||
| } | ||||
|  | ||||
| 	req, _ = http.NewRequest("GET", "/outpost.goauthentik.io/auth/start?rd=https://google.com", nil) | ||||
| func TestCheckRedirectParam_Invalid(t *testing.T) { | ||||
| 	a := newTestApplication() | ||||
| 	// Test invalid rd param | ||||
| 	req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/start?rd=https://google.com", nil) | ||||
|  | ||||
| 	rd, ok = a.checkRedirectParam(req) | ||||
| 	rd, ok := a.checkRedirectParam(req) | ||||
|  | ||||
| 	assert.Equal(t, false, ok) | ||||
| 	assert.Equal(t, "", rd) | ||||
| } | ||||
|  | ||||
| 	req, _ = http.NewRequest("GET", "/outpost.goauthentik.io/auth/start?rd=https://ext.t.goauthentik.io/test?foo", nil) | ||||
| func TestCheckRedirectParam_ValidFull(t *testing.T) { | ||||
| 	a := newTestApplication() | ||||
| 	// Test valid full rd param | ||||
| 	req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/start?rd=https://ext.t.goauthentik.io/test?foo", nil) | ||||
|  | ||||
| 	rd, ok = a.checkRedirectParam(req) | ||||
| 	rd, ok := a.checkRedirectParam(req) | ||||
|  | ||||
| 	assert.Equal(t, true, ok) | ||||
| 	assert.Equal(t, "https://ext.t.goauthentik.io/test?foo", rd) | ||||
| } | ||||
|  | ||||
| func TestCheckRedirectParam_ValidPartial(t *testing.T) { | ||||
| 	a := newTestApplication() | ||||
| 	// Test valid partial rd param | ||||
| 	req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/start?rd=/test?foo", nil) | ||||
|  | ||||
| 	rd, ok := a.checkRedirectParam(req) | ||||
|  | ||||
| 	assert.Equal(t, true, ok) | ||||
| 	assert.Equal(t, "https://ext.t.goauthentik.io/test?foo", rd) | ||||
|  | ||||
| @ -26,7 +26,7 @@ import ( | ||||
|  | ||||
| const RedisKeyPrefix = "authentik_proxy_session_" | ||||
|  | ||||
| func (a *Application) getStore(p api.ProxyOutpostConfig, externalHost *url.URL) sessions.Store { | ||||
| func (a *Application) getStore(p api.ProxyOutpostConfig, externalHost *url.URL) (sessions.Store, error) { | ||||
| 	maxAge := 0 | ||||
| 	if p.AccessTokenValidity.IsSet() { | ||||
| 		t := p.AccessTokenValidity.Get() | ||||
| @ -73,7 +73,7 @@ func (a *Application) getStore(p api.ProxyOutpostConfig, externalHost *url.URL) | ||||
| 		// New default RedisStore | ||||
| 		rs, err := redisstore.NewRedisStore(context.Background(), client) | ||||
| 		if err != nil { | ||||
| 			a.log.WithError(err).Panic("failed to connect to redis") | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		rs.KeyPrefix(RedisKeyPrefix) | ||||
| @ -87,7 +87,7 @@ func (a *Application) getStore(p api.ProxyOutpostConfig, externalHost *url.URL) | ||||
| 		}) | ||||
|  | ||||
| 		a.log.Trace("using redis session backend") | ||||
| 		return rs | ||||
| 		return rs, nil | ||||
| 	} | ||||
| 	dir := os.TempDir() | ||||
| 	cs := sessions.NewFilesystemStore(dir) | ||||
| @ -106,7 +106,7 @@ func (a *Application) getStore(p api.ProxyOutpostConfig, externalHost *url.URL) | ||||
| 	cs.Options.MaxAge = maxAge | ||||
| 	cs.Options.Path = "/" | ||||
| 	a.log.WithField("dir", dir).Trace("using filesystem session backend") | ||||
| 	return cs | ||||
| 	return cs, nil | ||||
| } | ||||
|  | ||||
| func (a *Application) SessionName() string { | ||||
|  | ||||
| @ -66,6 +66,7 @@ func newTestApplication() *Application { | ||||
| 		}, | ||||
| 		http.DefaultClient, | ||||
| 		ts, | ||||
| 		nil, | ||||
| 	) | ||||
| 	ts.apps = append(ts.apps, a) | ||||
| 	return a | ||||
|  | ||||
| @ -4,6 +4,7 @@ import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
|  | ||||
| 	"github.com/getsentry/sentry-go" | ||||
| 	"goauthentik.io/internal/constants" | ||||
| @ -37,16 +38,21 @@ func (ps *ProxyServer) Refresh() error { | ||||
| 				), | ||||
| 			), | ||||
| 		} | ||||
| 		a, err := application.NewApplication(provider, hc, ps) | ||||
| 		existing, ok := ps.apps[a.Host] | ||||
| 		externalHost, err := url.Parse(provider.ExternalHost) | ||||
| 		if err != nil { | ||||
| 			ps.log.WithError(err).Warning("failed to parse URL, skipping provider") | ||||
| 			continue | ||||
| 		} | ||||
| 		existing, ok := ps.apps[externalHost.Host] | ||||
| 		a, err := application.NewApplication(provider, hc, ps, existing) | ||||
| 		if ok { | ||||
| 			existing.Stop() | ||||
| 		} | ||||
| 		if err != nil { | ||||
| 			ps.log.WithError(err).Warning("failed to setup application") | ||||
| 		} else { | ||||
| 			apps[a.Host] = a | ||||
| 			continue | ||||
| 		} | ||||
| 		apps[externalHost.Host] = a | ||||
| 	} | ||||
| 	ps.apps = apps | ||||
| 	ps.log.Debug("Swapped maps") | ||||
|  | ||||
| @ -8,7 +8,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PACKAGE VERSION\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2024-08-18 00:08+0000\n" | ||||
| "POT-Creation-Date: 2024-09-08 00:09+0000\n" | ||||
| "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | ||||
| "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | ||||
| "Language-Team: LANGUAGE <LL@li.org>\n" | ||||
| @ -18,6 +18,11 @@ msgstr "" | ||||
| "Content-Transfer-Encoding: 8bit\n" | ||||
| "Plural-Forms: nplurals=2; plural=(n != 1);\n" | ||||
|  | ||||
| #: authentik/admin/tasks.py | ||||
| #, python-brace-format | ||||
| msgid "New version {version} available!" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/api/schema.py | ||||
| msgid "Generic API Error" | ||||
| msgstr "" | ||||
|  | ||||
										
											Binary file not shown.
										
									
								
							| @ -19,7 +19,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PACKAGE VERSION\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2024-08-12 13:45+0000\n" | ||||
| "POT-Creation-Date: 2024-09-08 00:09+0000\n" | ||||
| "PO-Revision-Date: 2022-09-26 16:47+0000\n" | ||||
| "Last-Translator: Marc Schmitt, 2024\n" | ||||
| "Language-Team: French (https://app.transifex.com/authentik/teams/119923/fr/)\n" | ||||
| @ -29,6 +29,11 @@ msgstr "" | ||||
| "Language: fr\n" | ||||
| "Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" | ||||
|  | ||||
| #: authentik/admin/tasks.py | ||||
| #, python-brace-format | ||||
| msgid "New version {version} available!" | ||||
| msgstr "Une nouvelle version {version} est disponible !" | ||||
|  | ||||
| #: authentik/api/schema.py | ||||
| msgid "Generic API Error" | ||||
| msgstr "Erreur d'API Générique" | ||||
| @ -1342,14 +1347,6 @@ msgstr "Impossible de résoudre l'application" | ||||
| msgid "DN under which objects are accessible." | ||||
| msgstr "DN sous lequel les objets sont accessibles." | ||||
|  | ||||
| #: authentik/providers/ldap/models.py | ||||
| msgid "" | ||||
| "Users in this group can do search queries. If not set, every user can " | ||||
| "execute search queries." | ||||
| msgstr "" | ||||
| "Les utilisateurs dans ce groupe peuvent faire des requêtes de recherche. Si " | ||||
| "laissé vide, tous les utilisateurs peuvent faire des requêtes de recherche." | ||||
|  | ||||
| #: authentik/providers/ldap/models.py | ||||
| msgid "" | ||||
| "The start for uidNumbers, this number is added to the user.pk to make sure " | ||||
| @ -1396,6 +1393,10 @@ msgstr "Fournisseur LDAP" | ||||
| msgid "LDAP Providers" | ||||
| msgstr "Fournisseurs LDAP" | ||||
|  | ||||
| #: authentik/providers/ldap/models.py | ||||
| msgid "Search full LDAP directory" | ||||
| msgstr "Rechercher dans l'annuaire LDAP complet" | ||||
|  | ||||
| #: authentik/providers/oauth2/id_token.py | ||||
| msgid "Based on the Hashed User ID" | ||||
| msgstr "Basé sur le hash de l'ID utilisateur" | ||||
| @ -1796,6 +1797,14 @@ msgstr "Mappage de propriété fournisseur Radius" | ||||
| msgid "Radius Provider Property Mappings" | ||||
| msgstr "Mappages de propriété fournisseur Radius" | ||||
|  | ||||
| #: authentik/providers/saml/api/providers.py | ||||
| msgid "" | ||||
| "With a signing keypair selected, at least one of 'Sign assertion' and 'Sign " | ||||
| "Response' must be selected." | ||||
| msgstr "" | ||||
| "Quand une clé de signature est sélectionnée, au moins l'un de « Signer les " | ||||
| "assertions » et « Signer les réponses » doit être sélectionné." | ||||
|  | ||||
| #: authentik/providers/saml/api/providers.py | ||||
| msgid "Invalid XML Syntax" | ||||
| msgstr "Syntaxe XML Invalide" | ||||
| @ -1944,6 +1953,20 @@ msgstr "" | ||||
| msgid "Signing Keypair" | ||||
| msgstr "Paire de clés de Signature" | ||||
|  | ||||
| #: authentik/providers/saml/models.py authentik/sources/saml/models.py | ||||
| msgid "" | ||||
| "When selected, incoming assertions are encrypted by the IdP using the public" | ||||
| " key of the encryption keypair. The assertion is decrypted by the SP using " | ||||
| "the the private key." | ||||
| msgstr "" | ||||
| "Si activé, les assertions entrantes seront chiffrées par l'IdP avec la clé " | ||||
| "publique de la paire de clé de chiffrement. L'assertion est déchiffrée par " | ||||
| "le SP en utilisant la clé privée." | ||||
|  | ||||
| #: authentik/providers/saml/models.py authentik/sources/saml/models.py | ||||
| msgid "Encryption Keypair" | ||||
| msgstr "Paire de clés de chiffrement" | ||||
|  | ||||
| #: authentik/providers/saml/models.py | ||||
| msgid "Default relay_state value for IDP-initiated logins" | ||||
| msgstr "Valeur par défaut de relay_state des connexions initiées par l'IdP" | ||||
| @ -2466,20 +2489,6 @@ msgstr "" | ||||
| "Paire de clés utilisées pour signer les réponses sortantes allant vers le " | ||||
| "fournisseur d'identité." | ||||
|  | ||||
| #: authentik/sources/saml/models.py | ||||
| msgid "" | ||||
| "When selected, incoming assertions are encrypted by the IdP using the public" | ||||
| " key of the encryption keypair. The assertion is decrypted by the SP using " | ||||
| "the the private key." | ||||
| msgstr "" | ||||
| "Si activé, les assertions entrantes seront chiffrées par l'IdP avec la clé " | ||||
| "publique de la paire de clé de chiffrement. L'assertion est déchiffrée par " | ||||
| "le SP en utilisant la clé privée." | ||||
|  | ||||
| #: authentik/sources/saml/models.py | ||||
| msgid "Encryption Keypair" | ||||
| msgstr "Paire de clés de chiffrement" | ||||
|  | ||||
| #: authentik/sources/saml/models.py | ||||
| msgid "SAML Source" | ||||
| msgstr "Source SAML" | ||||
|  | ||||
| @ -16,7 +16,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PACKAGE VERSION\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2024-08-15 00:09+0000\n" | ||||
| "POT-Creation-Date: 2024-08-18 00:08+0000\n" | ||||
| "PO-Revision-Date: 2022-09-26 16:47+0000\n" | ||||
| "Last-Translator: Anton Babenko, 2024\n" | ||||
| "Language-Team: Russian (https://app.transifex.com/authentik/teams/119923/ru/)\n" | ||||
| @ -739,7 +739,7 @@ msgstr "Правило Уведомления" | ||||
|  | ||||
| #: authentik/events/models.py | ||||
| msgid "Notification Rules" | ||||
| msgstr "Правило Уведомлений" | ||||
| msgstr "Правила уведомлений" | ||||
|  | ||||
| #: authentik/events/models.py | ||||
| msgid "Webhook Mapping" | ||||
| @ -1771,6 +1771,14 @@ msgstr "Сопоставление свойства Radius провайдера" | ||||
| msgid "Radius Provider Property Mappings" | ||||
| msgstr "Сопоставление свойств Radius провайдера" | ||||
|  | ||||
| #: authentik/providers/saml/api/providers.py | ||||
| msgid "" | ||||
| "With a signing keypair selected, at least one of 'Sign assertion' and 'Sign " | ||||
| "Response' must be selected." | ||||
| msgstr "" | ||||
| "При выборе пары ключей для подписи необходимо выбрать как минимум один из " | ||||
| "вариантов: 'Подписывать утверждение' или 'Подписывать ответ'." | ||||
|  | ||||
| #: authentik/providers/saml/api/providers.py | ||||
| msgid "Invalid XML Syntax" | ||||
| msgstr "Некорректный синтаксис XML" | ||||
| @ -1918,6 +1926,21 @@ msgstr "" | ||||
| msgid "Signing Keypair" | ||||
| msgstr "Пара ключей для подписи" | ||||
|  | ||||
| #: authentik/providers/saml/models.py authentik/sources/saml/models.py | ||||
| msgid "" | ||||
| "When selected, incoming assertions are encrypted by the IdP using the public" | ||||
| " key of the encryption keypair. The assertion is decrypted by the SP using " | ||||
| "the the private key." | ||||
| msgstr "" | ||||
| "При выборе этого варианта, входящие утверждения шифруются поставщиком " | ||||
| "идентификации (IdP) с использованием открытого ключа из пары ключей " | ||||
| "шифрования. Утверждение расшифровывается поставщиком услуг (SP) с " | ||||
| "использованием закрытого ключа." | ||||
|  | ||||
| #: authentik/providers/saml/models.py authentik/sources/saml/models.py | ||||
| msgid "Encryption Keypair" | ||||
| msgstr "Пара ключей шифрования" | ||||
|  | ||||
| #: authentik/providers/saml/models.py | ||||
| msgid "Default relay_state value for IDP-initiated logins" | ||||
| msgstr "Значение relay_state по умолчанию для логинов, инициированных IDP" | ||||
| @ -2446,21 +2469,6 @@ msgstr "" | ||||
| "Пара ключей, используемая для подписи исходящих ответов, направляемых " | ||||
| "провайдеру идентификационных данных." | ||||
|  | ||||
| #: authentik/sources/saml/models.py | ||||
| msgid "" | ||||
| "When selected, incoming assertions are encrypted by the IdP using the public" | ||||
| " key of the encryption keypair. The assertion is decrypted by the SP using " | ||||
| "the the private key." | ||||
| msgstr "" | ||||
| "При выборе этого варианта, входящие утверждения шифруются поставщиком " | ||||
| "идентификации (IdP) с использованием открытого ключа из пары ключей " | ||||
| "шифрования. Утверждение расшифровывается поставщиком услуг (SP) с " | ||||
| "использованием закрытого ключа." | ||||
|  | ||||
| #: authentik/sources/saml/models.py | ||||
| msgid "Encryption Keypair" | ||||
| msgstr "Пара ключей шифрования" | ||||
|  | ||||
| #: authentik/sources/saml/models.py | ||||
| msgid "SAML Source" | ||||
| msgstr "Источник SAML" | ||||
|  | ||||
										
											Binary file not shown.
										
									
								
							| @ -15,7 +15,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PACKAGE VERSION\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2024-08-18 00:08+0000\n" | ||||
| "POT-Creation-Date: 2024-09-08 00:09+0000\n" | ||||
| "PO-Revision-Date: 2022-09-26 16:47+0000\n" | ||||
| "Last-Translator: deluxghost, 2024\n" | ||||
| "Language-Team: Chinese Simplified (https://app.transifex.com/authentik/teams/119923/zh-Hans/)\n" | ||||
| @ -25,6 +25,11 @@ msgstr "" | ||||
| "Language: zh-Hans\n" | ||||
| "Plural-Forms: nplurals=1; plural=0;\n" | ||||
|  | ||||
| #: authentik/admin/tasks.py | ||||
| #, python-brace-format | ||||
| msgid "New version {version} available!" | ||||
| msgstr "新版本 {version} 可用!" | ||||
|  | ||||
| #: authentik/api/schema.py | ||||
| msgid "Generic API Error" | ||||
| msgstr "通用 API 错误" | ||||
|  | ||||
										
											Binary file not shown.
										
									
								
							| @ -14,7 +14,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PACKAGE VERSION\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2024-08-18 00:08+0000\n" | ||||
| "POT-Creation-Date: 2024-09-08 00:09+0000\n" | ||||
| "PO-Revision-Date: 2022-09-26 16:47+0000\n" | ||||
| "Last-Translator: deluxghost, 2024\n" | ||||
| "Language-Team: Chinese (China) (https://app.transifex.com/authentik/teams/119923/zh_CN/)\n" | ||||
| @ -24,6 +24,11 @@ msgstr "" | ||||
| "Language: zh_CN\n" | ||||
| "Plural-Forms: nplurals=1; plural=0;\n" | ||||
|  | ||||
| #: authentik/admin/tasks.py | ||||
| #, python-brace-format | ||||
| msgid "New version {version} available!" | ||||
| msgstr "新版本 {version} 可用!" | ||||
|  | ||||
| #: authentik/api/schema.py | ||||
| msgid "Generic API Error" | ||||
| msgstr "通用 API 错误" | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| { | ||||
|     "name": "@goauthentik/authentik", | ||||
|     "version": "2024.8.0", | ||||
|     "version": "2024.8.2", | ||||
|     "private": true | ||||
| } | ||||
|  | ||||
							
								
								
									
										575
									
								
								poetry.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										575
									
								
								poetry.lock
									
									
									
										generated
									
									
									
								
							| @ -366,13 +366,13 @@ typing-extensions = ">=4.0.0" | ||||
|  | ||||
| [[package]] | ||||
| name = "bandit" | ||||
| version = "1.7.9" | ||||
| version = "1.7.10" | ||||
| description = "Security oriented static analyser for python code." | ||||
| optional = false | ||||
| python-versions = ">=3.8" | ||||
| files = [ | ||||
|     {file = "bandit-1.7.9-py3-none-any.whl", hash = "sha256:52077cb339000f337fb25f7e045995c4ad01511e716e5daac37014b9752de8ec"}, | ||||
|     {file = "bandit-1.7.9.tar.gz", hash = "sha256:7c395a436743018f7be0a4cbb0a4ea9b902b6d87264ddecf8cfdc73b4f78ff61"}, | ||||
|     {file = "bandit-1.7.10-py3-none-any.whl", hash = "sha256:665721d7bebbb4485a339c55161ac0eedde27d51e638000d91c8c2d68343ad02"}, | ||||
|     {file = "bandit-1.7.10.tar.gz", hash = "sha256:59ed5caf5d92b6ada4bf65bc6437feea4a9da1093384445fed4d472acc6cff7b"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| @ -1053,38 +1053,38 @@ toml = ["tomli"] | ||||
|  | ||||
| [[package]] | ||||
| name = "cryptography" | ||||
| version = "43.0.0" | ||||
| version = "43.0.1" | ||||
| description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." | ||||
| optional = false | ||||
| python-versions = ">=3.7" | ||||
| files = [ | ||||
|     {file = "cryptography-43.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:64c3f16e2a4fc51c0d06af28441881f98c5d91009b8caaff40cf3548089e9c74"}, | ||||
|     {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3dcdedae5c7710b9f97ac6bba7e1052b95c7083c9d0e9df96e02a1932e777895"}, | ||||
|     {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d9a1eca329405219b605fac09ecfc09ac09e595d6def650a437523fcd08dd22"}, | ||||
|     {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ea9e57f8ea880eeea38ab5abf9fbe39f923544d7884228ec67d666abd60f5a47"}, | ||||
|     {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9a8d6802e0825767476f62aafed40532bd435e8a5f7d23bd8b4f5fd04cc80ecf"}, | ||||
|     {file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:cc70b4b581f28d0a254d006f26949245e3657d40d8857066c2ae22a61222ef55"}, | ||||
|     {file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4a997df8c1c2aae1e1e5ac49c2e4f610ad037fc5a3aadc7b64e39dea42249431"}, | ||||
|     {file = "cryptography-43.0.0-cp37-abi3-win32.whl", hash = "sha256:6e2b11c55d260d03a8cf29ac9b5e0608d35f08077d8c087be96287f43af3ccdc"}, | ||||
|     {file = "cryptography-43.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:31e44a986ceccec3d0498e16f3d27b2ee5fdf69ce2ab89b52eaad1d2f33d8778"}, | ||||
|     {file = "cryptography-43.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:7b3f5fe74a5ca32d4d0f302ffe6680fcc5c28f8ef0dc0ae8f40c0f3a1b4fca66"}, | ||||
|     {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac1955ce000cb29ab40def14fd1bbfa7af2017cca696ee696925615cafd0dce5"}, | ||||
|     {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:299d3da8e00b7e2b54bb02ef58d73cd5f55fb31f33ebbf33bd00d9aa6807df7e"}, | ||||
|     {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ee0c405832ade84d4de74b9029bedb7b31200600fa524d218fc29bfa371e97f5"}, | ||||
|     {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb013933d4c127349b3948aa8aaf2f12c0353ad0eccd715ca789c8a0f671646f"}, | ||||
|     {file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fdcb265de28585de5b859ae13e3846a8e805268a823a12a4da2597f1f5afc9f0"}, | ||||
|     {file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2905ccf93a8a2a416f3ec01b1a7911c3fe4073ef35640e7ee5296754e30b762b"}, | ||||
|     {file = "cryptography-43.0.0-cp39-abi3-win32.whl", hash = "sha256:47ca71115e545954e6c1d207dd13461ab81f4eccfcb1345eac874828b5e3eaaf"}, | ||||
|     {file = "cryptography-43.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:0663585d02f76929792470451a5ba64424acc3cd5227b03921dab0e2f27b1709"}, | ||||
|     {file = "cryptography-43.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2c6d112bf61c5ef44042c253e4859b3cbbb50df2f78fa8fae6747a7814484a70"}, | ||||
|     {file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:844b6d608374e7d08f4f6e6f9f7b951f9256db41421917dfb2d003dde4cd6b66"}, | ||||
|     {file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:51956cf8730665e2bdf8ddb8da0056f699c1a5715648c1b0144670c1ba00b48f"}, | ||||
|     {file = "cryptography-43.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:aae4d918f6b180a8ab8bf6511a419473d107df4dbb4225c7b48c5c9602c38c7f"}, | ||||
|     {file = "cryptography-43.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:232ce02943a579095a339ac4b390fbbe97f5b5d5d107f8a08260ea2768be8cc2"}, | ||||
|     {file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5bcb8a5620008a8034d39bce21dc3e23735dfdb6a33a06974739bfa04f853947"}, | ||||
|     {file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:08a24a7070b2b6804c1940ff0f910ff728932a9d0e80e7814234269f9d46d069"}, | ||||
|     {file = "cryptography-43.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e9c5266c432a1e23738d178e51c2c7a5e2ddf790f248be939448c0ba2021f9d1"}, | ||||
|     {file = "cryptography-43.0.0.tar.gz", hash = "sha256:b88075ada2d51aa9f18283532c9f60e72170041bba88d7f37e49cbb10275299e"}, | ||||
|     {file = "cryptography-43.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d"}, | ||||
|     {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062"}, | ||||
|     {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962"}, | ||||
|     {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277"}, | ||||
|     {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a"}, | ||||
|     {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042"}, | ||||
|     {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494"}, | ||||
|     {file = "cryptography-43.0.1-cp37-abi3-win32.whl", hash = "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2"}, | ||||
|     {file = "cryptography-43.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d"}, | ||||
|     {file = "cryptography-43.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d"}, | ||||
|     {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806"}, | ||||
|     {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85"}, | ||||
|     {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c"}, | ||||
|     {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1"}, | ||||
|     {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa"}, | ||||
|     {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4"}, | ||||
|     {file = "cryptography-43.0.1-cp39-abi3-win32.whl", hash = "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47"}, | ||||
|     {file = "cryptography-43.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb"}, | ||||
|     {file = "cryptography-43.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ea25acb556320250756e53f9e20a4177515f012c9eaea17eb7587a8c4d8ae034"}, | ||||
|     {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c1332724be35d23a854994ff0b66530119500b6053d0bd3363265f7e5e77288d"}, | ||||
|     {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289"}, | ||||
|     {file = "cryptography-43.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5b43d1ea6b378b54a1dc99dd8a2b5be47658fe9a7ce0a58ff0b55f4b43ef2b84"}, | ||||
|     {file = "cryptography-43.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:88cce104c36870d70c49c7c8fd22885875d950d9ee6ab54df2745f83ba0dc365"}, | ||||
|     {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9d3cdb25fa98afdd3d0892d132b8d7139e2c087da1712041f6b762e4f807cc96"}, | ||||
|     {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e710bf40870f4db63c3d7d929aa9e09e4e7ee219e703f949ec4073b4294f6172"}, | ||||
|     {file = "cryptography-43.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7c05650fe8023c5ed0d46793d4b7d7e6cd9c04e68eabe5b0aeea836e37bdcec2"}, | ||||
|     {file = "cryptography-43.0.1.tar.gz", hash = "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| @ -1097,7 +1097,7 @@ nox = ["nox"] | ||||
| pep8test = ["check-sdist", "click", "mypy", "ruff"] | ||||
| sdist = ["build"] | ||||
| ssh = ["bcrypt (>=3.1.5)"] | ||||
| test = ["certifi", "cryptography-vectors (==43.0.0)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] | ||||
| test = ["certifi", "cryptography-vectors (==43.0.1)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] | ||||
| test-randomorder = ["pytest-randomly"] | ||||
|  | ||||
| [[package]] | ||||
| @ -1165,15 +1165,18 @@ files = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "deepmerge" | ||||
| version = "1.1.1" | ||||
| description = "a toolset to deeply merge python dictionaries." | ||||
| version = "2.0" | ||||
| description = "A toolset for deeply merging Python dictionaries." | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| python-versions = ">=3.8" | ||||
| files = [ | ||||
|     {file = "deepmerge-1.1.1-py3-none-any.whl", hash = "sha256:7219dad9763f15be9dcd4bcb53e00f48e4eed6f5ed8f15824223eb934bb35977"}, | ||||
|     {file = "deepmerge-1.1.1.tar.gz", hash = "sha256:53a489dc9449636e480a784359ae2aab3191748c920649551c8e378622f0eca4"}, | ||||
|     {file = "deepmerge-2.0-py3-none-any.whl", hash = "sha256:6de9ce507115cff0bed95ff0ce9ecc31088ef50cbdf09bc90a09349a318b3d00"}, | ||||
|     {file = "deepmerge-2.0.tar.gz", hash = "sha256:5c3d86081fbebd04dd5de03626a0607b809a98fb6ccba5770b62466fe940ff20"}, | ||||
| ] | ||||
|  | ||||
| [package.extras] | ||||
| dev = ["black", "build", "mypy", "pytest", "pyupgrade", "twine", "validate-pyproject[all]"] | ||||
|  | ||||
| [[package]] | ||||
| name = "defusedxml" | ||||
| version = "0.7.1" | ||||
| @ -1204,13 +1207,13 @@ dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] | ||||
|  | ||||
| [[package]] | ||||
| name = "django" | ||||
| version = "5.0.8" | ||||
| version = "5.0.9" | ||||
| description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." | ||||
| optional = false | ||||
| python-versions = ">=3.10" | ||||
| files = [ | ||||
|     {file = "Django-5.0.8-py3-none-any.whl", hash = "sha256:333a7988f7ca4bc14d360d3d8f6b793704517761ae3813b95432043daec22a45"}, | ||||
|     {file = "Django-5.0.8.tar.gz", hash = "sha256:ebe859c9da6fead9c9ee6dbfa4943b04f41342f4cea2c4d8c978ef0d10694f2b"}, | ||||
|     {file = "Django-5.0.9-py3-none-any.whl", hash = "sha256:f219576ba53be4e83f485130a7283f0efde06a9f2e3a7c3c5180327549f078fa"}, | ||||
|     {file = "Django-5.0.9.tar.gz", hash = "sha256:6333870d342329b60174da3a60dbd302e533f3b0bb0971516750e974a99b5a39"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| @ -1284,13 +1287,13 @@ Django = ">=2.2" | ||||
|  | ||||
| [[package]] | ||||
| name = "django-model-utils" | ||||
| version = "4.5.1" | ||||
| version = "5.0.0" | ||||
| description = "Django model mixins and utilities" | ||||
| optional = false | ||||
| python-versions = ">=3.8" | ||||
| files = [ | ||||
|     {file = "django_model_utils-4.5.1-py3-none-any.whl", hash = "sha256:f1141fc71796242edeffed5ad53a8cc57f00d345eb5a3a63e3f69401cd562ee2"}, | ||||
|     {file = "django_model_utils-4.5.1.tar.gz", hash = "sha256:1220f22d9a467d53a1e0f4cda4857df0b2f757edf9a29955c42461988caa648a"}, | ||||
|     {file = "django_model_utils-5.0.0-py3-none-any.whl", hash = "sha256:fec78e6c323d565a221f7c4edc703f4567d7bb1caeafe1acd16a80c5ff82056b"}, | ||||
|     {file = "django_model_utils-5.0.0.tar.gz", hash = "sha256:041cdd6230d2fbf6cd943e1969318bce762272077f4ecd333ab2263924b4e5eb"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| @ -1312,13 +1315,13 @@ django = ">=3" | ||||
|  | ||||
| [[package]] | ||||
| name = "django-pglock" | ||||
| version = "1.6.0" | ||||
| version = "1.6.2" | ||||
| description = "Postgres locking routines and lock table access." | ||||
| optional = false | ||||
| python-versions = "<4,>=3.8.0" | ||||
| files = [ | ||||
|     {file = "django_pglock-1.6.0-py3-none-any.whl", hash = "sha256:41c98d0bd3738d11e6eaefcc3e5146028f118a593ac58c13d663b751170f01de"}, | ||||
|     {file = "django_pglock-1.6.0.tar.gz", hash = "sha256:724450ecc9886f39af599c477d84ad086545a5373215ef7a670cd25faca25a61"}, | ||||
|     {file = "django_pglock-1.6.2-py3-none-any.whl", hash = "sha256:abdb92531bf8cb36471dc9eb33ed163b06bd3323140d132ed4f308b9e5505f50"}, | ||||
|     {file = "django_pglock-1.6.2.tar.gz", hash = "sha256:05db998cab21556d4a307eac4b5db8e50f874f42b1a581560b3c54610fee6a1b"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| @ -1563,6 +1566,16 @@ files = [ | ||||
| setuptools = "*" | ||||
| six = "*" | ||||
|  | ||||
| [[package]] | ||||
| name = "durationpy" | ||||
| version = "0.7" | ||||
| description = "Module for converting between datetime.timedelta and Go's Duration strings." | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| files = [ | ||||
|     {file = "durationpy-0.7.tar.gz", hash = "sha256:8447c43df4f1a0b434e70c15a38d77f5c9bd17284bfc1ff1d430f233d5083732"}, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "email-validator" | ||||
| version = "2.2.0" | ||||
| @ -1758,13 +1771,13 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] | ||||
|  | ||||
| [[package]] | ||||
| name = "google-api-python-client" | ||||
| version = "2.142.0" | ||||
| version = "2.146.0" | ||||
| description = "Google API Client Library for Python" | ||||
| optional = false | ||||
| python-versions = ">=3.7" | ||||
| files = [ | ||||
|     {file = "google_api_python_client-2.142.0-py2.py3-none-any.whl", hash = "sha256:266799082bb8301f423ec204dffbffb470b502abbf29efd1f83e644d36eb5a8f"}, | ||||
|     {file = "google_api_python_client-2.142.0.tar.gz", hash = "sha256:a1101ac9e24356557ca22f07ff48b7f61fa5d4b4e7feeef3bda16e5dcb86350e"}, | ||||
|     {file = "google_api_python_client-2.146.0-py2.py3-none-any.whl", hash = "sha256:b1e62c9889c5ef6022f11d30d7ef23dc55100300f0e8aaf8aa09e8e92540acad"}, | ||||
|     {file = "google_api_python_client-2.146.0.tar.gz", hash = "sha256:41f671be10fa077ee5143ee9f0903c14006d39dc644564f4e044ae96b380bf68"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| @ -2047,13 +2060,13 @@ files = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "importlib-metadata" | ||||
| version = "8.0.0" | ||||
| version = "8.4.0" | ||||
| description = "Read metadata from Python packages" | ||||
| optional = false | ||||
| python-versions = ">=3.8" | ||||
| files = [ | ||||
|     {file = "importlib_metadata-8.0.0-py3-none-any.whl", hash = "sha256:15584cf2b1bf449d98ff8a6ff1abef57bf20f3ac6454f431736cd3e660921b2f"}, | ||||
|     {file = "importlib_metadata-8.0.0.tar.gz", hash = "sha256:188bd24e4c346d3f0a933f275c2fec67050326a856b9a359881d7c2a697e8812"}, | ||||
|     {file = "importlib_metadata-8.4.0-py3-none-any.whl", hash = "sha256:66f342cc6ac9818fc6ff340576acd24d65ba0b3efabb2b4ac08b598965a4a2f1"}, | ||||
|     {file = "importlib_metadata-8.4.0.tar.gz", hash = "sha256:9a547d3bc3608b025f93d403fdd1aae741c24fbb8314df4b155675742ce303c5"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| @ -2240,17 +2253,18 @@ zookeeper = ["kazoo (>=2.8.0)"] | ||||
|  | ||||
| [[package]] | ||||
| name = "kubernetes" | ||||
| version = "30.1.0" | ||||
| version = "31.0.0" | ||||
| description = "Kubernetes python client" | ||||
| optional = false | ||||
| python-versions = ">=3.6" | ||||
| files = [ | ||||
|     {file = "kubernetes-30.1.0-py2.py3-none-any.whl", hash = "sha256:e212e8b7579031dd2e512168b617373bc1e03888d41ac4e04039240a292d478d"}, | ||||
|     {file = "kubernetes-30.1.0.tar.gz", hash = "sha256:41e4c77af9f28e7a6c314e3bd06a8c6229ddd787cad684e0ab9f69b498e98ebc"}, | ||||
|     {file = "kubernetes-31.0.0-py2.py3-none-any.whl", hash = "sha256:bf141e2d380c8520eada8b351f4e319ffee9636328c137aa432bc486ca1200e1"}, | ||||
|     {file = "kubernetes-31.0.0.tar.gz", hash = "sha256:28945de906c8c259c1ebe62703b56a03b714049372196f854105afe4e6d014c0"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| certifi = ">=14.05.14" | ||||
| durationpy = ">=0.7" | ||||
| google-auth = ">=1.0.1" | ||||
| oauthlib = ">=3.2.2" | ||||
| python-dateutil = ">=2.5.3" | ||||
| @ -2845,13 +2859,13 @@ dev = ["bumpver", "isort", "mypy", "pylint", "pytest", "yapf"] | ||||
|  | ||||
| [[package]] | ||||
| name = "msgraph-sdk" | ||||
| version = "1.5.4" | ||||
| version = "1.8.0" | ||||
| description = "The Microsoft Graph Python SDK" | ||||
| optional = false | ||||
| python-versions = ">=3.8" | ||||
| files = [ | ||||
|     {file = "msgraph_sdk-1.5.4-py3-none-any.whl", hash = "sha256:9ea349f30cc4a03edb587e26554c7a4839a38c2ef30d4b5396882fd2be82dcac"}, | ||||
|     {file = "msgraph_sdk-1.5.4.tar.gz", hash = "sha256:b0e146328d136d1db175938d8fc901f3bb32acf3ea6fe93c0dc7c5a0abc45e39"}, | ||||
|     {file = "msgraph_sdk-1.8.0-py3-none-any.whl", hash = "sha256:22a8e4a63f989865228f66a54501bef8105909c7156fe0a079ca9b5296339cc2"}, | ||||
|     {file = "msgraph_sdk-1.8.0.tar.gz", hash = "sha256:1ac84bd47ea288a84f46f6c6d0c89d164ee3453b917615632652344538098314"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| @ -3025,49 +3039,49 @@ resolved_reference = "20d69d9cc50a0fef31605b46f06da0c94f1ec3cf" | ||||
|  | ||||
| [[package]] | ||||
| name = "opentelemetry-api" | ||||
| version = "1.26.0" | ||||
| version = "1.27.0" | ||||
| description = "OpenTelemetry Python API" | ||||
| optional = false | ||||
| python-versions = ">=3.8" | ||||
| files = [ | ||||
|     {file = "opentelemetry_api-1.26.0-py3-none-any.whl", hash = "sha256:7d7ea33adf2ceda2dd680b18b1677e4152000b37ca76e679da71ff103b943064"}, | ||||
|     {file = "opentelemetry_api-1.26.0.tar.gz", hash = "sha256:2bd639e4bed5b18486fef0b5a520aaffde5a18fc225e808a1ac4df363f43a1ce"}, | ||||
|     {file = "opentelemetry_api-1.27.0-py3-none-any.whl", hash = "sha256:953d5871815e7c30c81b56d910c707588000fff7a3ca1c73e6531911d53065e7"}, | ||||
|     {file = "opentelemetry_api-1.27.0.tar.gz", hash = "sha256:ed673583eaa5f81b5ce5e86ef7cdaf622f88ef65f0b9aab40b843dcae5bef342"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| deprecated = ">=1.2.6" | ||||
| importlib-metadata = ">=6.0,<=8.0.0" | ||||
| importlib-metadata = ">=6.0,<=8.4.0" | ||||
|  | ||||
| [[package]] | ||||
| name = "opentelemetry-sdk" | ||||
| version = "1.26.0" | ||||
| version = "1.27.0" | ||||
| description = "OpenTelemetry Python SDK" | ||||
| optional = false | ||||
| python-versions = ">=3.8" | ||||
| files = [ | ||||
|     {file = "opentelemetry_sdk-1.26.0-py3-none-any.whl", hash = "sha256:feb5056a84a88670c041ea0ded9921fca559efec03905dddeb3885525e0af897"}, | ||||
|     {file = "opentelemetry_sdk-1.26.0.tar.gz", hash = "sha256:c90d2868f8805619535c05562d699e2f4fb1f00dbd55a86dcefca4da6fa02f85"}, | ||||
|     {file = "opentelemetry_sdk-1.27.0-py3-none-any.whl", hash = "sha256:365f5e32f920faf0fd9e14fdfd92c086e317eaa5f860edba9cdc17a380d9197d"}, | ||||
|     {file = "opentelemetry_sdk-1.27.0.tar.gz", hash = "sha256:d525017dea0ccce9ba4e0245100ec46ecdc043f2d7b8315d56b19aff0904fa6f"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| opentelemetry-api = "1.26.0" | ||||
| opentelemetry-semantic-conventions = "0.47b0" | ||||
| opentelemetry-api = "1.27.0" | ||||
| opentelemetry-semantic-conventions = "0.48b0" | ||||
| typing-extensions = ">=3.7.4" | ||||
|  | ||||
| [[package]] | ||||
| name = "opentelemetry-semantic-conventions" | ||||
| version = "0.47b0" | ||||
| version = "0.48b0" | ||||
| description = "OpenTelemetry Semantic Conventions" | ||||
| optional = false | ||||
| python-versions = ">=3.8" | ||||
| files = [ | ||||
|     {file = "opentelemetry_semantic_conventions-0.47b0-py3-none-any.whl", hash = "sha256:4ff9d595b85a59c1c1413f02bba320ce7ea6bf9e2ead2b0913c4395c7bbc1063"}, | ||||
|     {file = "opentelemetry_semantic_conventions-0.47b0.tar.gz", hash = "sha256:a8d57999bbe3495ffd4d510de26a97dadc1dace53e0275001b2c1b2f67992a7e"}, | ||||
|     {file = "opentelemetry_semantic_conventions-0.48b0-py3-none-any.whl", hash = "sha256:a0de9f45c413a8669788a38569c7e0a11ce6ce97861a628cca785deecdc32a1f"}, | ||||
|     {file = "opentelemetry_semantic_conventions-0.48b0.tar.gz", hash = "sha256:12d74983783b6878162208be57c9effcb89dc88691c64992d70bb89dc00daa1a"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| deprecated = ">=1.2.6" | ||||
| opentelemetry-api = "1.26.0" | ||||
| opentelemetry-api = "1.27.0" | ||||
|  | ||||
| [[package]] | ||||
| name = "orjson" | ||||
| @ -3158,13 +3172,13 @@ files = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "paramiko" | ||||
| version = "3.4.1" | ||||
| version = "3.5.0" | ||||
| description = "SSH2 protocol library" | ||||
| optional = false | ||||
| python-versions = ">=3.6" | ||||
| files = [ | ||||
|     {file = "paramiko-3.4.1-py3-none-any.whl", hash = "sha256:8e49fd2f82f84acf7ffd57c64311aa2b30e575370dc23bdb375b10262f7eac32"}, | ||||
|     {file = "paramiko-3.4.1.tar.gz", hash = "sha256:8b15302870af7f6652f2e038975c1d2973f06046cb5d7d65355668b3ecbece0c"}, | ||||
|     {file = "paramiko-3.5.0-py3-none-any.whl", hash = "sha256:1fedf06b085359051cd7d0d270cebe19e755a8a921cc2ddbfa647fb0cd7d68f9"}, | ||||
|     {file = "paramiko-3.5.0.tar.gz", hash = "sha256:ad11e540da4f55cedda52931f1a3f812a8238a7af7f62a60de538cd80bb28124"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| @ -3201,13 +3215,13 @@ files = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "pdoc" | ||||
| version = "14.6.0" | ||||
| version = "14.7.0" | ||||
| description = "API Documentation for Python Projects" | ||||
| optional = false | ||||
| python-versions = ">=3.8" | ||||
| files = [ | ||||
|     {file = "pdoc-14.6.0-py3-none-any.whl", hash = "sha256:36c42c546a317d8e3e8c0b39645f24161374de0c7066ccaae76628d721e49ba5"}, | ||||
|     {file = "pdoc-14.6.0.tar.gz", hash = "sha256:6e98a24c5e0ca5d188397969cf82581836eaef13f172fc3820047bfe15c61c9a"}, | ||||
|     {file = "pdoc-14.7.0-py3-none-any.whl", hash = "sha256:72377a907efc6b2c5b3c56b717ef34f11d93621dced3b663f3aede0b844c0ad2"}, | ||||
|     {file = "pdoc-14.7.0.tar.gz", hash = "sha256:2d28af9c0acc39180744ad0543e4bbc3223ecba0d1302db315ec521c51f71f93"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| @ -3434,36 +3448,36 @@ files = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "psycopg" | ||||
| version = "3.2.1" | ||||
| version = "3.2.2" | ||||
| description = "PostgreSQL database adapter for Python" | ||||
| optional = false | ||||
| python-versions = ">=3.8" | ||||
| files = [ | ||||
|     {file = "psycopg-3.2.1-py3-none-any.whl", hash = "sha256:ece385fb413a37db332f97c49208b36cf030ff02b199d7635ed2fbd378724175"}, | ||||
|     {file = "psycopg-3.2.1.tar.gz", hash = "sha256:dc8da6dc8729dacacda3cc2f17d2c9397a70a66cf0d2b69c91065d60d5f00cb7"}, | ||||
|     {file = "psycopg-3.2.2-py3-none-any.whl", hash = "sha256:babf565d459d8f72fb65da5e211dd0b58a52c51e4e1fa9cadecff42d6b7619b2"}, | ||||
|     {file = "psycopg-3.2.2.tar.gz", hash = "sha256:8bad2e497ce22d556dac1464738cb948f8d6bab450d965cf1d8a8effd52412e0"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| psycopg-c = {version = "3.2.1", optional = true, markers = "implementation_name != \"pypy\" and extra == \"c\""} | ||||
| typing-extensions = ">=4.4" | ||||
| psycopg-c = {version = "3.2.2", optional = true, markers = "implementation_name != \"pypy\" and extra == \"c\""} | ||||
| typing-extensions = {version = ">=4.6", markers = "python_version < \"3.13\""} | ||||
| tzdata = {version = "*", markers = "sys_platform == \"win32\""} | ||||
|  | ||||
| [package.extras] | ||||
| binary = ["psycopg-binary (==3.2.1)"] | ||||
| c = ["psycopg-c (==3.2.1)"] | ||||
| dev = ["ast-comments (>=1.1.2)", "black (>=24.1.0)", "codespell (>=2.2)", "dnspython (>=2.1)", "flake8 (>=4.0)", "mypy (>=1.6)", "types-setuptools (>=57.4)", "wheel (>=0.37)"] | ||||
| binary = ["psycopg-binary (==3.2.2)"] | ||||
| c = ["psycopg-c (==3.2.2)"] | ||||
| dev = ["ast-comments (>=1.1.2)", "black (>=24.1.0)", "codespell (>=2.2)", "dnspython (>=2.1)", "flake8 (>=4.0)", "mypy (>=1.11)", "types-setuptools (>=57.4)", "wheel (>=0.37)"] | ||||
| docs = ["Sphinx (>=5.0)", "furo (==2022.6.21)", "sphinx-autobuild (>=2021.3.14)", "sphinx-autodoc-typehints (>=1.12)"] | ||||
| pool = ["psycopg-pool"] | ||||
| test = ["anyio (>=4.0)", "mypy (>=1.6)", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"] | ||||
| test = ["anyio (>=4.0)", "mypy (>=1.11)", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"] | ||||
|  | ||||
| [[package]] | ||||
| name = "psycopg-c" | ||||
| version = "3.2.1" | ||||
| version = "3.2.2" | ||||
| description = "PostgreSQL database adapter for Python -- C optimisation distribution" | ||||
| optional = false | ||||
| python-versions = ">=3.8" | ||||
| files = [ | ||||
|     {file = "psycopg_c-3.2.1.tar.gz", hash = "sha256:2d09943cc8a855c42c1e23b4298957b7ce8f27bf3683258c52fd139f601f7cda"}, | ||||
|     {file = "psycopg_c-3.2.2.tar.gz", hash = "sha256:de8cac75bc6640ef0f54ad9187b81e07c430206a83c566b73d4cca41ecccb7c8"}, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -3504,120 +3518,121 @@ files = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "pydantic" | ||||
| version = "2.8.2" | ||||
| version = "2.9.2" | ||||
| description = "Data validation using Python type hints" | ||||
| optional = false | ||||
| python-versions = ">=3.8" | ||||
| files = [ | ||||
|     {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, | ||||
|     {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, | ||||
|     {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"}, | ||||
|     {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| annotated-types = ">=0.4.0" | ||||
| annotated-types = ">=0.6.0" | ||||
| email-validator = {version = ">=2.0.0", optional = true, markers = "extra == \"email\""} | ||||
| pydantic-core = "2.20.1" | ||||
| pydantic-core = "2.23.4" | ||||
| typing-extensions = {version = ">=4.6.1", markers = "python_version < \"3.13\""} | ||||
|  | ||||
| [package.extras] | ||||
| email = ["email-validator (>=2.0.0)"] | ||||
| timezone = ["tzdata"] | ||||
|  | ||||
| [[package]] | ||||
| name = "pydantic-core" | ||||
| version = "2.20.1" | ||||
| version = "2.23.4" | ||||
| description = "Core functionality for Pydantic validation and serialization" | ||||
| optional = false | ||||
| python-versions = ">=3.8" | ||||
| files = [ | ||||
|     {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, | ||||
|     {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, | ||||
|     {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"}, | ||||
|     {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"}, | ||||
|     {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"}, | ||||
|     {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"}, | ||||
|     {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"}, | ||||
|     {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"}, | ||||
|     {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"}, | ||||
|     {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"}, | ||||
|     {file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"}, | ||||
|     {file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"}, | ||||
|     {file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"}, | ||||
|     {file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"}, | ||||
|     {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"}, | ||||
|     {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"}, | ||||
|     {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"}, | ||||
|     {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"}, | ||||
|     {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"}, | ||||
|     {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"}, | ||||
|     {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"}, | ||||
|     {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"}, | ||||
|     {file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"}, | ||||
|     {file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"}, | ||||
|     {file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"}, | ||||
|     {file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"}, | ||||
|     {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"}, | ||||
|     {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"}, | ||||
|     {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"}, | ||||
|     {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"}, | ||||
|     {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"}, | ||||
|     {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"}, | ||||
|     {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"}, | ||||
|     {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"}, | ||||
|     {file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"}, | ||||
|     {file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"}, | ||||
|     {file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"}, | ||||
|     {file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"}, | ||||
|     {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"}, | ||||
|     {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"}, | ||||
|     {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"}, | ||||
|     {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"}, | ||||
|     {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"}, | ||||
|     {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"}, | ||||
|     {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"}, | ||||
|     {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"}, | ||||
|     {file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"}, | ||||
|     {file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"}, | ||||
|     {file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"}, | ||||
|     {file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"}, | ||||
|     {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"}, | ||||
|     {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"}, | ||||
|     {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"}, | ||||
|     {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"}, | ||||
|     {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"}, | ||||
|     {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"}, | ||||
|     {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"}, | ||||
|     {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"}, | ||||
|     {file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"}, | ||||
|     {file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"}, | ||||
|     {file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"}, | ||||
|     {file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"}, | ||||
|     {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"}, | ||||
|     {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"}, | ||||
|     {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"}, | ||||
|     {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"}, | ||||
|     {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"}, | ||||
|     {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"}, | ||||
|     {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"}, | ||||
|     {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"}, | ||||
|     {file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"}, | ||||
|     {file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"}, | ||||
|     {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"}, | ||||
|     {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"}, | ||||
|     {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"}, | ||||
|     {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"}, | ||||
|     {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"}, | ||||
|     {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"}, | ||||
|     {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"}, | ||||
|     {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"}, | ||||
|     {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"}, | ||||
|     {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"}, | ||||
|     {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"}, | ||||
|     {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"}, | ||||
|     {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"}, | ||||
|     {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"}, | ||||
|     {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"}, | ||||
|     {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"}, | ||||
|     {file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"}, | ||||
|     {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"}, | ||||
|     {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"}, | ||||
|     {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"}, | ||||
|     {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"}, | ||||
|     {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"}, | ||||
|     {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"}, | ||||
|     {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"}, | ||||
|     {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"}, | ||||
|     {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"}, | ||||
|     {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"}, | ||||
|     {file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"}, | ||||
|     {file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"}, | ||||
|     {file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"}, | ||||
|     {file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"}, | ||||
|     {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"}, | ||||
|     {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"}, | ||||
|     {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"}, | ||||
|     {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"}, | ||||
|     {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"}, | ||||
|     {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"}, | ||||
|     {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"}, | ||||
|     {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"}, | ||||
|     {file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"}, | ||||
|     {file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"}, | ||||
|     {file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"}, | ||||
|     {file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"}, | ||||
|     {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"}, | ||||
|     {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"}, | ||||
|     {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"}, | ||||
|     {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"}, | ||||
|     {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"}, | ||||
|     {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"}, | ||||
|     {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"}, | ||||
|     {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"}, | ||||
|     {file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"}, | ||||
|     {file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"}, | ||||
|     {file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"}, | ||||
|     {file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"}, | ||||
|     {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"}, | ||||
|     {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"}, | ||||
|     {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"}, | ||||
|     {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"}, | ||||
|     {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"}, | ||||
|     {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"}, | ||||
|     {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"}, | ||||
|     {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"}, | ||||
|     {file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"}, | ||||
|     {file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"}, | ||||
|     {file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"}, | ||||
|     {file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"}, | ||||
|     {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"}, | ||||
|     {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"}, | ||||
|     {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"}, | ||||
|     {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"}, | ||||
|     {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"}, | ||||
|     {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"}, | ||||
|     {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"}, | ||||
|     {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"}, | ||||
|     {file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"}, | ||||
|     {file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"}, | ||||
|     {file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"}, | ||||
|     {file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"}, | ||||
|     {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"}, | ||||
|     {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"}, | ||||
|     {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"}, | ||||
|     {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"}, | ||||
|     {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"}, | ||||
|     {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"}, | ||||
|     {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"}, | ||||
|     {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"}, | ||||
|     {file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"}, | ||||
|     {file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"}, | ||||
|     {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"}, | ||||
|     {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"}, | ||||
|     {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"}, | ||||
|     {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"}, | ||||
|     {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"}, | ||||
|     {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"}, | ||||
|     {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"}, | ||||
|     {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"}, | ||||
|     {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"}, | ||||
|     {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"}, | ||||
|     {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"}, | ||||
|     {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"}, | ||||
|     {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"}, | ||||
|     {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"}, | ||||
|     {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"}, | ||||
|     {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"}, | ||||
|     {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| @ -3761,13 +3776,13 @@ files = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "pytest" | ||||
| version = "8.3.2" | ||||
| version = "8.3.3" | ||||
| description = "pytest: simple powerful testing with Python" | ||||
| optional = false | ||||
| python-versions = ">=3.8" | ||||
| files = [ | ||||
|     {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, | ||||
|     {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, | ||||
|     {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, | ||||
|     {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| @ -3781,13 +3796,13 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments | ||||
|  | ||||
| [[package]] | ||||
| name = "pytest-django" | ||||
| version = "4.8.0" | ||||
| version = "4.9.0" | ||||
| description = "A Django plugin for pytest." | ||||
| optional = false | ||||
| python-versions = ">=3.8" | ||||
| files = [ | ||||
|     {file = "pytest-django-4.8.0.tar.gz", hash = "sha256:5d054fe011c56f3b10f978f41a8efb2e5adfc7e680ef36fb571ada1f24779d90"}, | ||||
|     {file = "pytest_django-4.8.0-py3-none-any.whl", hash = "sha256:ca1ddd1e0e4c227cf9e3e40a6afc6d106b3e70868fd2ac5798a22501271cd0c7"}, | ||||
|     {file = "pytest_django-4.9.0-py3-none-any.whl", hash = "sha256:1d83692cb39188682dbb419ff0393867e9904094a549a7d38a3154d5731b2b99"}, | ||||
|     {file = "pytest_django-4.9.0.tar.gz", hash = "sha256:8bf7bc358c9ae6f6fc51b6cebb190fe20212196e6807121f11bd6a3b03428314"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| @ -4195,29 +4210,29 @@ pyasn1 = ">=0.1.3" | ||||
|  | ||||
| [[package]] | ||||
| name = "ruff" | ||||
| version = "0.6.2" | ||||
| version = "0.6.7" | ||||
| description = "An extremely fast Python linter and code formatter, written in Rust." | ||||
| optional = false | ||||
| python-versions = ">=3.7" | ||||
| files = [ | ||||
|     {file = "ruff-0.6.2-py3-none-linux_armv6l.whl", hash = "sha256:5c8cbc6252deb3ea840ad6a20b0f8583caab0c5ef4f9cca21adc5a92b8f79f3c"}, | ||||
|     {file = "ruff-0.6.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:17002fe241e76544448a8e1e6118abecbe8cd10cf68fde635dad480dba594570"}, | ||||
|     {file = "ruff-0.6.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3dbeac76ed13456f8158b8f4fe087bf87882e645c8e8b606dd17b0b66c2c1158"}, | ||||
|     {file = "ruff-0.6.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:094600ee88cda325988d3f54e3588c46de5c18dae09d683ace278b11f9d4d534"}, | ||||
|     {file = "ruff-0.6.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:316d418fe258c036ba05fbf7dfc1f7d3d4096db63431546163b472285668132b"}, | ||||
|     {file = "ruff-0.6.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d72b8b3abf8a2d51b7b9944a41307d2f442558ccb3859bbd87e6ae9be1694a5d"}, | ||||
|     {file = "ruff-0.6.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:2aed7e243be68487aa8982e91c6e260982d00da3f38955873aecd5a9204b1d66"}, | ||||
|     {file = "ruff-0.6.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d371f7fc9cec83497fe7cf5eaf5b76e22a8efce463de5f775a1826197feb9df8"}, | ||||
|     {file = "ruff-0.6.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8f310d63af08f583363dfb844ba8f9417b558199c58a5999215082036d795a1"}, | ||||
|     {file = "ruff-0.6.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7db6880c53c56addb8638fe444818183385ec85eeada1d48fc5abe045301b2f1"}, | ||||
|     {file = "ruff-0.6.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1175d39faadd9a50718f478d23bfc1d4da5743f1ab56af81a2b6caf0a2394f23"}, | ||||
|     {file = "ruff-0.6.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5b939f9c86d51635fe486585389f54582f0d65b8238e08c327c1534844b3bb9a"}, | ||||
|     {file = "ruff-0.6.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d0d62ca91219f906caf9b187dea50d17353f15ec9bb15aae4a606cd697b49b4c"}, | ||||
|     {file = "ruff-0.6.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7438a7288f9d67ed3c8ce4d059e67f7ed65e9fe3aa2ab6f5b4b3610e57e3cb56"}, | ||||
|     {file = "ruff-0.6.2-py3-none-win32.whl", hash = "sha256:279d5f7d86696df5f9549b56b9b6a7f6c72961b619022b5b7999b15db392a4da"}, | ||||
|     {file = "ruff-0.6.2-py3-none-win_amd64.whl", hash = "sha256:d9f3469c7dd43cd22eb1c3fc16926fb8258d50cb1b216658a07be95dd117b0f2"}, | ||||
|     {file = "ruff-0.6.2-py3-none-win_arm64.whl", hash = "sha256:f28fcd2cd0e02bdf739297516d5643a945cc7caf09bd9bcb4d932540a5ea4fa9"}, | ||||
|     {file = "ruff-0.6.2.tar.gz", hash = "sha256:239ee6beb9e91feb8e0ec384204a763f36cb53fb895a1a364618c6abb076b3be"}, | ||||
|     {file = "ruff-0.6.7-py3-none-linux_armv6l.whl", hash = "sha256:08277b217534bfdcc2e1377f7f933e1c7957453e8a79764d004e44c40db923f2"}, | ||||
|     {file = "ruff-0.6.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c6707a32e03b791f4448dc0dce24b636cbcdee4dd5607adc24e5ee73fd86c00a"}, | ||||
|     {file = "ruff-0.6.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:533d66b7774ef224e7cf91506a7dafcc9e8ec7c059263ec46629e54e7b1f90ab"}, | ||||
|     {file = "ruff-0.6.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17a86aac6f915932d259f7bec79173e356165518859f94649d8c50b81ff087e9"}, | ||||
|     {file = "ruff-0.6.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b3f8822defd260ae2460ea3832b24d37d203c3577f48b055590a426a722d50ef"}, | ||||
|     {file = "ruff-0.6.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9ba4efe5c6dbbb58be58dd83feedb83b5e95c00091bf09987b4baf510fee5c99"}, | ||||
|     {file = "ruff-0.6.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:525201b77f94d2b54868f0cbe5edc018e64c22563da6c5c2e5c107a4e85c1c0d"}, | ||||
|     {file = "ruff-0.6.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8854450839f339e1049fdbe15d875384242b8e85d5c6947bb2faad33c651020b"}, | ||||
|     {file = "ruff-0.6.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f0b62056246234d59cbf2ea66e84812dc9ec4540518e37553513392c171cb18"}, | ||||
|     {file = "ruff-0.6.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b1462fa56c832dc0cea5b4041cfc9c97813505d11cce74ebc6d1aae068de36b"}, | ||||
|     {file = "ruff-0.6.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:02b083770e4cdb1495ed313f5694c62808e71764ec6ee5db84eedd82fd32d8f5"}, | ||||
|     {file = "ruff-0.6.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0c05fd37013de36dfa883a3854fae57b3113aaa8abf5dea79202675991d48624"}, | ||||
|     {file = "ruff-0.6.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f49c9caa28d9bbfac4a637ae10327b3db00f47d038f3fbb2195c4d682e925b14"}, | ||||
|     {file = "ruff-0.6.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a0e1655868164e114ba43a908fd2d64a271a23660195017c17691fb6355d59bb"}, | ||||
|     {file = "ruff-0.6.7-py3-none-win32.whl", hash = "sha256:a939ca435b49f6966a7dd64b765c9df16f1faed0ca3b6f16acdf7731969deb35"}, | ||||
|     {file = "ruff-0.6.7-py3-none-win_amd64.whl", hash = "sha256:590445eec5653f36248584579c06252ad2e110a5d1f32db5420de35fb0e1c977"}, | ||||
|     {file = "ruff-0.6.7-py3-none-win_arm64.whl", hash = "sha256:b28f0d5e2f771c1fe3c7a45d3f53916fc74a480698c4b5731f0bea61e52137c8"}, | ||||
|     {file = "ruff-0.6.7.tar.gz", hash = "sha256:44e52129d82266fa59b587e2cd74def5637b730a69c4542525dfdecfaae38bd5"}, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -4256,13 +4271,13 @@ django-query = ["django (>=3.2)"] | ||||
|  | ||||
| [[package]] | ||||
| name = "selenium" | ||||
| version = "4.23.1" | ||||
| version = "4.25.0" | ||||
| description = "Official Python bindings for Selenium WebDriver" | ||||
| optional = false | ||||
| python-versions = ">=3.8" | ||||
| files = [ | ||||
|     {file = "selenium-4.23.1-py3-none-any.whl", hash = "sha256:3a8d9f23dc636bd3840dd56f00c2739e32ec0c1e34a821dd553e15babef24477"}, | ||||
|     {file = "selenium-4.23.1.tar.gz", hash = "sha256:128d099e66284437e7128d2279176ec7a06e6ec7426e167f5d34987166bd8f46"}, | ||||
|     {file = "selenium-4.25.0-py3-none-any.whl", hash = "sha256:3798d2d12b4a570bc5790163ba57fef10b2afee958bf1d80f2a3cf07c4141f33"}, | ||||
|     {file = "selenium-4.25.0.tar.gz", hash = "sha256:95d08d3b82fb353f3c474895154516604c7f0e6a9a565ae6498ef36c9bac6921"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| @ -4275,13 +4290,13 @@ websocket-client = ">=1.8,<2.0" | ||||
|  | ||||
| [[package]] | ||||
| name = "sentry-sdk" | ||||
| version = "2.13.0" | ||||
| version = "2.14.0" | ||||
| description = "Python client for Sentry (https://sentry.io)" | ||||
| optional = false | ||||
| python-versions = ">=3.6" | ||||
| files = [ | ||||
|     {file = "sentry_sdk-2.13.0-py2.py3-none-any.whl", hash = "sha256:6beede8fc2ab4043da7f69d95534e320944690680dd9a963178a49de71d726c6"}, | ||||
|     {file = "sentry_sdk-2.13.0.tar.gz", hash = "sha256:8d4a576f7a98eb2fdb40e13106e41f330e5c79d72a68be1316e7852cf4995260"}, | ||||
|     {file = "sentry_sdk-2.14.0-py2.py3-none-any.whl", hash = "sha256:b8bc3dc51d06590df1291b7519b85c75e2ced4f28d9ea655b6d54033503b5bf4"}, | ||||
|     {file = "sentry_sdk-2.14.0.tar.gz", hash = "sha256:1e0e2eaf6dad918c7d1e0edac868a7bf20017b177f242cefe2a6bcd47955961d"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| @ -4652,13 +4667,13 @@ wsproto = ">=0.14" | ||||
|  | ||||
| [[package]] | ||||
| name = "twilio" | ||||
| version = "9.2.3" | ||||
| version = "9.3.1" | ||||
| description = "Twilio API client and TwiML generator" | ||||
| optional = false | ||||
| python-versions = ">=3.7.0" | ||||
| files = [ | ||||
|     {file = "twilio-9.2.3-py2.py3-none-any.whl", hash = "sha256:76bfc39aa8d854510907cb7f9465814dfdea9e91ec199bb44f0785f05746f4cc"}, | ||||
|     {file = "twilio-9.2.3.tar.gz", hash = "sha256:da2255b5f3753cb3bf647fc6c50edbdb367ebc3cde6802806f6f863058a65f75"}, | ||||
|     {file = "twilio-9.3.1-py2.py3-none-any.whl", hash = "sha256:48c714e5a1340a3d9001d6c9ccd107434c340ff94a5bc1bfb2473d3dc33f5051"}, | ||||
|     {file = "twilio-9.3.1.tar.gz", hash = "sha256:bd754371353855438a8cf0c38f54580d474afe9655af2d81edb6dbabd8d785c4"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| @ -4669,13 +4684,13 @@ requests = ">=2.0.0" | ||||
|  | ||||
| [[package]] | ||||
| name = "twisted" | ||||
| version = "24.3.0" | ||||
| version = "24.7.0" | ||||
| description = "An asynchronous networking framework written in Python" | ||||
| optional = false | ||||
| python-versions = ">=3.8.0" | ||||
| files = [ | ||||
|     {file = "twisted-24.3.0-py3-none-any.whl", hash = "sha256:039f2e6a49ab5108abd94de187fa92377abe5985c7a72d68d0ad266ba19eae63"}, | ||||
|     {file = "twisted-24.3.0.tar.gz", hash = "sha256:6b38b6ece7296b5e122c9eb17da2eeab3d98a198f50ca9efd00fb03e5b4fd4ae"}, | ||||
|     {file = "twisted-24.7.0-py3-none-any.whl", hash = "sha256:734832ef98108136e222b5230075b1079dad8a3fc5637319615619a7725b0c81"}, | ||||
|     {file = "twisted-24.7.0.tar.gz", hash = "sha256:5a60147f044187a127ec7da96d170d49bcce50c6fd36f594e60f4587eff4d394"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| @ -4684,55 +4699,26 @@ automat = ">=0.8.0" | ||||
| constantly = ">=15.1" | ||||
| hyperlink = ">=17.1.1" | ||||
| idna = {version = ">=2.4", optional = true, markers = "extra == \"tls\""} | ||||
| incremental = ">=22.10.0" | ||||
| incremental = ">=24.7.0" | ||||
| pyopenssl = {version = ">=21.0.0", optional = true, markers = "extra == \"tls\""} | ||||
| service-identity = {version = ">=18.1.0", optional = true, markers = "extra == \"tls\""} | ||||
| twisted-iocpsupport = {version = ">=1.0.2,<2", markers = "platform_system == \"Windows\""} | ||||
| typing-extensions = ">=4.2.0" | ||||
| zope-interface = ">=5" | ||||
|  | ||||
| [package.extras] | ||||
| all-non-platform = ["twisted[conch,http2,serial,test,tls]", "twisted[conch,http2,serial,test,tls]"] | ||||
| all-non-platform = ["appdirs (>=1.4.0)", "appdirs (>=1.4.0)", "bcrypt (>=3.1.3)", "bcrypt (>=3.1.3)", "cryptography (>=3.3)", "cryptography (>=3.3)", "cython-test-exception-raiser (>=1.0.2,<2)", "cython-test-exception-raiser (>=1.0.2,<2)", "h2 (>=3.0,<5.0)", "h2 (>=3.0,<5.0)", "hypothesis (>=6.56)", "hypothesis (>=6.56)", "idna (>=2.4)", "idna (>=2.4)", "priority (>=1.1.0,<2.0)", "priority (>=1.1.0,<2.0)", "pyhamcrest (>=2)", "pyhamcrest (>=2)", "pyopenssl (>=21.0.0)", "pyopenssl (>=21.0.0)", "pyserial (>=3.0)", "pyserial (>=3.0)", "pywin32 (!=226)", "pywin32 (!=226)", "service-identity (>=18.1.0)", "service-identity (>=18.1.0)"] | ||||
| conch = ["appdirs (>=1.4.0)", "bcrypt (>=3.1.3)", "cryptography (>=3.3)"] | ||||
| dev = ["coverage (>=6b1,<7)", "pyflakes (>=2.2,<3.0)", "python-subunit (>=1.4,<2.0)", "twisted[dev-release]", "twistedchecker (>=0.7,<1.0)"] | ||||
| dev = ["coverage (>=7.5,<8.0)", "cython-test-exception-raiser (>=1.0.2,<2)", "hypothesis (>=6.56)", "pydoctor (>=23.9.0,<23.10.0)", "pyflakes (>=2.2,<3.0)", "pyhamcrest (>=2)", "python-subunit (>=1.4,<2.0)", "sphinx (>=6,<7)", "sphinx-rtd-theme (>=1.3,<2.0)", "towncrier (>=23.6,<24.0)", "twistedchecker (>=0.7,<1.0)"] | ||||
| dev-release = ["pydoctor (>=23.9.0,<23.10.0)", "pydoctor (>=23.9.0,<23.10.0)", "sphinx (>=6,<7)", "sphinx (>=6,<7)", "sphinx-rtd-theme (>=1.3,<2.0)", "sphinx-rtd-theme (>=1.3,<2.0)", "towncrier (>=23.6,<24.0)", "towncrier (>=23.6,<24.0)"] | ||||
| gtk-platform = ["pygobject", "pygobject", "twisted[all-non-platform]", "twisted[all-non-platform]"] | ||||
| gtk-platform = ["appdirs (>=1.4.0)", "appdirs (>=1.4.0)", "bcrypt (>=3.1.3)", "bcrypt (>=3.1.3)", "cryptography (>=3.3)", "cryptography (>=3.3)", "cython-test-exception-raiser (>=1.0.2,<2)", "cython-test-exception-raiser (>=1.0.2,<2)", "h2 (>=3.0,<5.0)", "h2 (>=3.0,<5.0)", "hypothesis (>=6.56)", "hypothesis (>=6.56)", "idna (>=2.4)", "idna (>=2.4)", "priority (>=1.1.0,<2.0)", "priority (>=1.1.0,<2.0)", "pygobject", "pygobject", "pyhamcrest (>=2)", "pyhamcrest (>=2)", "pyopenssl (>=21.0.0)", "pyopenssl (>=21.0.0)", "pyserial (>=3.0)", "pyserial (>=3.0)", "pywin32 (!=226)", "pywin32 (!=226)", "service-identity (>=18.1.0)", "service-identity (>=18.1.0)"] | ||||
| http2 = ["h2 (>=3.0,<5.0)", "priority (>=1.1.0,<2.0)"] | ||||
| macos-platform = ["pyobjc-core", "pyobjc-core", "pyobjc-framework-cfnetwork", "pyobjc-framework-cfnetwork", "pyobjc-framework-cocoa", "pyobjc-framework-cocoa", "twisted[all-non-platform]", "twisted[all-non-platform]"] | ||||
| mypy = ["mypy (>=1.8,<2.0)", "mypy-zope (>=1.0.3,<1.1.0)", "twisted[all-non-platform,dev]", "types-pyopenssl", "types-setuptools"] | ||||
| osx-platform = ["twisted[macos-platform]", "twisted[macos-platform]"] | ||||
| macos-platform = ["appdirs (>=1.4.0)", "appdirs (>=1.4.0)", "bcrypt (>=3.1.3)", "bcrypt (>=3.1.3)", "cryptography (>=3.3)", "cryptography (>=3.3)", "cython-test-exception-raiser (>=1.0.2,<2)", "cython-test-exception-raiser (>=1.0.2,<2)", "h2 (>=3.0,<5.0)", "h2 (>=3.0,<5.0)", "hypothesis (>=6.56)", "hypothesis (>=6.56)", "idna (>=2.4)", "idna (>=2.4)", "priority (>=1.1.0,<2.0)", "priority (>=1.1.0,<2.0)", "pyhamcrest (>=2)", "pyhamcrest (>=2)", "pyobjc-core", "pyobjc-core", "pyobjc-framework-cfnetwork", "pyobjc-framework-cfnetwork", "pyobjc-framework-cocoa", "pyobjc-framework-cocoa", "pyopenssl (>=21.0.0)", "pyopenssl (>=21.0.0)", "pyserial (>=3.0)", "pyserial (>=3.0)", "pywin32 (!=226)", "pywin32 (!=226)", "service-identity (>=18.1.0)", "service-identity (>=18.1.0)"] | ||||
| mypy = ["appdirs (>=1.4.0)", "bcrypt (>=3.1.3)", "coverage (>=7.5,<8.0)", "cryptography (>=3.3)", "cython-test-exception-raiser (>=1.0.2,<2)", "h2 (>=3.0,<5.0)", "hypothesis (>=6.56)", "idna (>=2.4)", "mypy (>=1.8,<2.0)", "mypy-zope (>=1.0.3,<1.1.0)", "priority (>=1.1.0,<2.0)", "pydoctor (>=23.9.0,<23.10.0)", "pyflakes (>=2.2,<3.0)", "pyhamcrest (>=2)", "pyopenssl (>=21.0.0)", "pyserial (>=3.0)", "python-subunit (>=1.4,<2.0)", "pywin32 (!=226)", "service-identity (>=18.1.0)", "sphinx (>=6,<7)", "sphinx-rtd-theme (>=1.3,<2.0)", "towncrier (>=23.6,<24.0)", "twistedchecker (>=0.7,<1.0)", "types-pyopenssl", "types-setuptools"] | ||||
| osx-platform = ["appdirs (>=1.4.0)", "appdirs (>=1.4.0)", "bcrypt (>=3.1.3)", "bcrypt (>=3.1.3)", "cryptography (>=3.3)", "cryptography (>=3.3)", "cython-test-exception-raiser (>=1.0.2,<2)", "cython-test-exception-raiser (>=1.0.2,<2)", "h2 (>=3.0,<5.0)", "h2 (>=3.0,<5.0)", "hypothesis (>=6.56)", "hypothesis (>=6.56)", "idna (>=2.4)", "idna (>=2.4)", "priority (>=1.1.0,<2.0)", "priority (>=1.1.0,<2.0)", "pyhamcrest (>=2)", "pyhamcrest (>=2)", "pyobjc-core", "pyobjc-core", "pyobjc-framework-cfnetwork", "pyobjc-framework-cfnetwork", "pyobjc-framework-cocoa", "pyobjc-framework-cocoa", "pyopenssl (>=21.0.0)", "pyopenssl (>=21.0.0)", "pyserial (>=3.0)", "pyserial (>=3.0)", "pywin32 (!=226)", "pywin32 (!=226)", "service-identity (>=18.1.0)", "service-identity (>=18.1.0)"] | ||||
| serial = ["pyserial (>=3.0)", "pywin32 (!=226)"] | ||||
| test = ["cython-test-exception-raiser (>=1.0.2,<2)", "hypothesis (>=6.56)", "pyhamcrest (>=2)"] | ||||
| tls = ["idna (>=2.4)", "pyopenssl (>=21.0.0)", "service-identity (>=18.1.0)"] | ||||
| windows-platform = ["pywin32 (!=226)", "pywin32 (!=226)", "twisted[all-non-platform]", "twisted[all-non-platform]"] | ||||
|  | ||||
| [[package]] | ||||
| name = "twisted-iocpsupport" | ||||
| version = "1.0.4" | ||||
| description = "An extension for use in the twisted I/O Completion Ports reactor." | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| files = [ | ||||
|     {file = "twisted-iocpsupport-1.0.4.tar.gz", hash = "sha256:858096c0d15e33f15ac157f455d8f86f2f2cdd223963e58c0f682a3af8362d89"}, | ||||
|     {file = "twisted_iocpsupport-1.0.4-cp310-cp310-win32.whl", hash = "sha256:afa2b630797f9ed2f27f3d9f55e3f72b4244911e45a8c82756f44babbf0b243e"}, | ||||
|     {file = "twisted_iocpsupport-1.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:0058c963c8957bcd3deda62122e89953c9de1e867a274facc9b15dde1a9f31e8"}, | ||||
|     {file = "twisted_iocpsupport-1.0.4-cp311-cp311-win32.whl", hash = "sha256:196f7c7ccad4ba4d1783b1c4e1d1b22d93c04275cd780bf7498d16c77319ad6e"}, | ||||
|     {file = "twisted_iocpsupport-1.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:4e5f97bcbabdd79cbaa969b63439b89801ea560f11d42b0a387634275c633623"}, | ||||
|     {file = "twisted_iocpsupport-1.0.4-cp312-cp312-win32.whl", hash = "sha256:6081bd7c2f4fcf9b383dcdb3b3385d75a26a7c9d2be25b6950c3d8ea652d2d2d"}, | ||||
|     {file = "twisted_iocpsupport-1.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:76f7e67cec1f1d097d1f4ed7de41be3d74546e1a4ede0c7d56e775c4dce5dfb0"}, | ||||
|     {file = "twisted_iocpsupport-1.0.4-cp36-cp36m-win32.whl", hash = "sha256:3d306fc4d88a6bcf61ce9d572c738b918578121bfd72891625fab314549024b5"}, | ||||
|     {file = "twisted_iocpsupport-1.0.4-cp36-cp36m-win_amd64.whl", hash = "sha256:391ac4d6002a80e15f35adc4ad6056f4fe1c17ceb0d1f98ba01b0f4f917adfd7"}, | ||||
|     {file = "twisted_iocpsupport-1.0.4-cp37-cp37m-win32.whl", hash = "sha256:0c1b5cf37f0b2d96cc3c9bc86fff16613b9f5d0ca565c96cf1f1fb8cfca4b81c"}, | ||||
|     {file = "twisted_iocpsupport-1.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:3c5dc11d72519e55f727320e3cee535feedfaee09c0f0765ed1ca7badff1ab3c"}, | ||||
|     {file = "twisted_iocpsupport-1.0.4-cp38-cp38-win32.whl", hash = "sha256:cc86c2ef598c15d824a243c2541c29459881c67fc3c0adb6efe2242f8f0ec3af"}, | ||||
|     {file = "twisted_iocpsupport-1.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:c27985e949b9b1a1fb4c20c71d315c10ea0f93fdf3ccdd4a8c158b5926edd8c8"}, | ||||
|     {file = "twisted_iocpsupport-1.0.4-cp39-cp39-win32.whl", hash = "sha256:e311dfcb470696e3c077249615893cada598e62fa7c4e4ca090167bd2b7d331f"}, | ||||
|     {file = "twisted_iocpsupport-1.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:4574eef1f3bb81501fb02f911298af3c02fe8179c31a33b361dd49180c3e644d"}, | ||||
|     {file = "twisted_iocpsupport-1.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:872747a3b64e2909aee59c803ccd0bceb9b75bf27915520ebd32d69687040fa2"}, | ||||
|     {file = "twisted_iocpsupport-1.0.4-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:c2712b778bacf1db434e3e065adfed3db300754186a29aecac1efae9ef4bcaff"}, | ||||
|     {file = "twisted_iocpsupport-1.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7c66fa0aa4236b27b3c61cb488662d85dae746a6d1c7b0d91cf7aae118445adf"}, | ||||
|     {file = "twisted_iocpsupport-1.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:300437af17396a945a58dcfffd77863303a8b6d9e65c6e81f1d2eed55b50d444"}, | ||||
| ] | ||||
| windows-platform = ["appdirs (>=1.4.0)", "appdirs (>=1.4.0)", "bcrypt (>=3.1.3)", "bcrypt (>=3.1.3)", "cryptography (>=3.3)", "cryptography (>=3.3)", "cython-test-exception-raiser (>=1.0.2,<2)", "cython-test-exception-raiser (>=1.0.2,<2)", "h2 (>=3.0,<5.0)", "h2 (>=3.0,<5.0)", "hypothesis (>=6.56)", "hypothesis (>=6.56)", "idna (>=2.4)", "idna (>=2.4)", "priority (>=1.1.0,<2.0)", "priority (>=1.1.0,<2.0)", "pyhamcrest (>=2)", "pyhamcrest (>=2)", "pyopenssl (>=21.0.0)", "pyopenssl (>=21.0.0)", "pyserial (>=3.0)", "pyserial (>=3.0)", "pywin32 (!=226)", "pywin32 (!=226)", "pywin32 (!=226)", "pywin32 (!=226)", "service-identity (>=18.1.0)", "service-identity (>=18.1.0)", "twisted-iocpsupport (>=1.0.2)", "twisted-iocpsupport (>=1.0.2)"] | ||||
|  | ||||
| [[package]] | ||||
| name = "txaio" | ||||
| @ -4796,13 +4782,13 @@ files = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "urllib3" | ||||
| version = "2.2.2" | ||||
| version = "2.2.3" | ||||
| description = "HTTP library with thread-safe connection pooling, file post, and more." | ||||
| optional = false | ||||
| python-versions = ">=3.8" | ||||
| files = [ | ||||
|     {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, | ||||
|     {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, | ||||
|     {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, | ||||
|     {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| @ -4896,46 +4882,41 @@ files = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "watchdog" | ||||
| version = "4.0.2" | ||||
| version = "5.0.2" | ||||
| description = "Filesystem events monitoring" | ||||
| optional = false | ||||
| python-versions = ">=3.8" | ||||
| python-versions = ">=3.9" | ||||
| files = [ | ||||
|     {file = "watchdog-4.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ede7f010f2239b97cc79e6cb3c249e72962404ae3865860855d5cbe708b0fd22"}, | ||||
|     {file = "watchdog-4.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a2cffa171445b0efa0726c561eca9a27d00a1f2b83846dbd5a4f639c4f8ca8e1"}, | ||||
|     {file = "watchdog-4.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c50f148b31b03fbadd6d0b5980e38b558046b127dc483e5e4505fcef250f9503"}, | ||||
|     {file = "watchdog-4.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7c7d4bf585ad501c5f6c980e7be9c4f15604c7cc150e942d82083b31a7548930"}, | ||||
|     {file = "watchdog-4.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:914285126ad0b6eb2258bbbcb7b288d9dfd655ae88fa28945be05a7b475a800b"}, | ||||
|     {file = "watchdog-4.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:984306dc4720da5498b16fc037b36ac443816125a3705dfde4fd90652d8028ef"}, | ||||
|     {file = "watchdog-4.0.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1cdcfd8142f604630deef34722d695fb455d04ab7cfe9963055df1fc69e6727a"}, | ||||
|     {file = "watchdog-4.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d7ab624ff2f663f98cd03c8b7eedc09375a911794dfea6bf2a359fcc266bff29"}, | ||||
|     {file = "watchdog-4.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:132937547a716027bd5714383dfc40dc66c26769f1ce8a72a859d6a48f371f3a"}, | ||||
|     {file = "watchdog-4.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cd67c7df93eb58f360c43802acc945fa8da70c675b6fa37a241e17ca698ca49b"}, | ||||
|     {file = "watchdog-4.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcfd02377be80ef3b6bc4ce481ef3959640458d6feaae0bd43dd90a43da90a7d"}, | ||||
|     {file = "watchdog-4.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:980b71510f59c884d684b3663d46e7a14b457c9611c481e5cef08f4dd022eed7"}, | ||||
|     {file = "watchdog-4.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:aa160781cafff2719b663c8a506156e9289d111d80f3387cf3af49cedee1f040"}, | ||||
|     {file = "watchdog-4.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f6ee8dedd255087bc7fe82adf046f0b75479b989185fb0bdf9a98b612170eac7"}, | ||||
|     {file = "watchdog-4.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0b4359067d30d5b864e09c8597b112fe0a0a59321a0f331498b013fb097406b4"}, | ||||
|     {file = "watchdog-4.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:770eef5372f146997638d737c9a3c597a3b41037cfbc5c41538fc27c09c3a3f9"}, | ||||
|     {file = "watchdog-4.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eeea812f38536a0aa859972d50c76e37f4456474b02bd93674d1947cf1e39578"}, | ||||
|     {file = "watchdog-4.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b2c45f6e1e57ebb4687690c05bc3a2c1fb6ab260550c4290b8abb1335e0fd08b"}, | ||||
|     {file = "watchdog-4.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:10b6683df70d340ac3279eff0b2766813f00f35a1d37515d2c99959ada8f05fa"}, | ||||
|     {file = "watchdog-4.0.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f7c739888c20f99824f7aa9d31ac8a97353e22d0c0e54703a547a218f6637eb3"}, | ||||
|     {file = "watchdog-4.0.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c100d09ac72a8a08ddbf0629ddfa0b8ee41740f9051429baa8e31bb903ad7508"}, | ||||
|     {file = "watchdog-4.0.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:f5315a8c8dd6dd9425b974515081fc0aadca1d1d61e078d2246509fd756141ee"}, | ||||
|     {file = "watchdog-4.0.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2d468028a77b42cc685ed694a7a550a8d1771bb05193ba7b24006b8241a571a1"}, | ||||
|     {file = "watchdog-4.0.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f15edcae3830ff20e55d1f4e743e92970c847bcddc8b7509bcd172aa04de506e"}, | ||||
|     {file = "watchdog-4.0.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:936acba76d636f70db8f3c66e76aa6cb5136a936fc2a5088b9ce1c7a3508fc83"}, | ||||
|     {file = "watchdog-4.0.2-py3-none-manylinux2014_armv7l.whl", hash = "sha256:e252f8ca942a870f38cf785aef420285431311652d871409a64e2a0a52a2174c"}, | ||||
|     {file = "watchdog-4.0.2-py3-none-manylinux2014_i686.whl", hash = "sha256:0e83619a2d5d436a7e58a1aea957a3c1ccbf9782c43c0b4fed80580e5e4acd1a"}, | ||||
|     {file = "watchdog-4.0.2-py3-none-manylinux2014_ppc64.whl", hash = "sha256:88456d65f207b39f1981bf772e473799fcdc10801062c36fd5ad9f9d1d463a73"}, | ||||
|     {file = "watchdog-4.0.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:32be97f3b75693a93c683787a87a0dc8db98bb84701539954eef991fb35f5fbc"}, | ||||
|     {file = "watchdog-4.0.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:c82253cfc9be68e3e49282831afad2c1f6593af80c0daf1287f6a92657986757"}, | ||||
|     {file = "watchdog-4.0.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c0b14488bd336c5b1845cee83d3e631a1f8b4e9c5091ec539406e4a324f882d8"}, | ||||
|     {file = "watchdog-4.0.2-py3-none-win32.whl", hash = "sha256:0d8a7e523ef03757a5aa29f591437d64d0d894635f8a50f370fe37f913ce4e19"}, | ||||
|     {file = "watchdog-4.0.2-py3-none-win_amd64.whl", hash = "sha256:c344453ef3bf875a535b0488e3ad28e341adbd5a9ffb0f7d62cefacc8824ef2b"}, | ||||
|     {file = "watchdog-4.0.2-py3-none-win_ia64.whl", hash = "sha256:baececaa8edff42cd16558a639a9b0ddf425f93d892e8392a56bf904f5eff22c"}, | ||||
|     {file = "watchdog-4.0.2.tar.gz", hash = "sha256:b4dfbb6c49221be4535623ea4474a4d6ee0a9cef4a80b20c28db4d858b64e270"}, | ||||
|     {file = "watchdog-5.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d961f4123bb3c447d9fcdcb67e1530c366f10ab3a0c7d1c0c9943050936d4877"}, | ||||
|     {file = "watchdog-5.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72990192cb63872c47d5e5fefe230a401b87fd59d257ee577d61c9e5564c62e5"}, | ||||
|     {file = "watchdog-5.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6bec703ad90b35a848e05e1b40bf0050da7ca28ead7ac4be724ae5ac2653a1a0"}, | ||||
|     {file = "watchdog-5.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dae7a1879918f6544201d33666909b040a46421054a50e0f773e0d870ed7438d"}, | ||||
|     {file = "watchdog-5.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c4a440f725f3b99133de610bfec93d570b13826f89616377715b9cd60424db6e"}, | ||||
|     {file = "watchdog-5.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8b2918c19e0d48f5f20df458c84692e2a054f02d9df25e6c3c930063eca64c1"}, | ||||
|     {file = "watchdog-5.0.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:aa9cd6e24126d4afb3752a3e70fce39f92d0e1a58a236ddf6ee823ff7dba28ee"}, | ||||
|     {file = "watchdog-5.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f627c5bf5759fdd90195b0c0431f99cff4867d212a67b384442c51136a098ed7"}, | ||||
|     {file = "watchdog-5.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d7594a6d32cda2b49df3fd9abf9b37c8d2f3eab5df45c24056b4a671ac661619"}, | ||||
|     {file = "watchdog-5.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba32efcccfe2c58f4d01115440d1672b4eb26cdd6fc5b5818f1fb41f7c3e1889"}, | ||||
|     {file = "watchdog-5.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:963f7c4c91e3f51c998eeff1b3fb24a52a8a34da4f956e470f4b068bb47b78ee"}, | ||||
|     {file = "watchdog-5.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8c47150aa12f775e22efff1eee9f0f6beee542a7aa1a985c271b1997d340184f"}, | ||||
|     {file = "watchdog-5.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:14dd4ed023d79d1f670aa659f449bcd2733c33a35c8ffd88689d9d243885198b"}, | ||||
|     {file = "watchdog-5.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b84bff0391ad4abe25c2740c7aec0e3de316fdf7764007f41e248422a7760a7f"}, | ||||
|     {file = "watchdog-5.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e8d5ff39f0a9968952cce548e8e08f849141a4fcc1290b1c17c032ba697b9d7"}, | ||||
|     {file = "watchdog-5.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fb223456db6e5f7bd9bbd5cd969f05aae82ae21acc00643b60d81c770abd402b"}, | ||||
|     {file = "watchdog-5.0.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9814adb768c23727a27792c77812cf4e2fd9853cd280eafa2bcfa62a99e8bd6e"}, | ||||
|     {file = "watchdog-5.0.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:901ee48c23f70193d1a7bc2d9ee297df66081dd5f46f0ca011be4f70dec80dab"}, | ||||
|     {file = "watchdog-5.0.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:638bcca3d5b1885c6ec47be67bf712b00a9ab3d4b22ec0881f4889ad870bc7e8"}, | ||||
|     {file = "watchdog-5.0.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:5597c051587f8757798216f2485e85eac583c3b343e9aa09127a3a6f82c65ee8"}, | ||||
|     {file = "watchdog-5.0.2-py3-none-manylinux2014_armv7l.whl", hash = "sha256:53ed1bf71fcb8475dd0ef4912ab139c294c87b903724b6f4a8bd98e026862e6d"}, | ||||
|     {file = "watchdog-5.0.2-py3-none-manylinux2014_i686.whl", hash = "sha256:29e4a2607bd407d9552c502d38b45a05ec26a8e40cc7e94db9bb48f861fa5abc"}, | ||||
|     {file = "watchdog-5.0.2-py3-none-manylinux2014_ppc64.whl", hash = "sha256:b6dc8f1d770a8280997e4beae7b9a75a33b268c59e033e72c8a10990097e5fde"}, | ||||
|     {file = "watchdog-5.0.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:d2ab34adc9bf1489452965cdb16a924e97d4452fcf88a50b21859068b50b5c3b"}, | ||||
|     {file = "watchdog-5.0.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:7d1aa7e4bb0f0c65a1a91ba37c10e19dabf7eaaa282c5787e51371f090748f4b"}, | ||||
|     {file = "watchdog-5.0.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:726eef8f8c634ac6584f86c9c53353a010d9f311f6c15a034f3800a7a891d941"}, | ||||
|     {file = "watchdog-5.0.2-py3-none-win32.whl", hash = "sha256:bda40c57115684d0216556671875e008279dea2dc00fcd3dde126ac8e0d7a2fb"}, | ||||
|     {file = "watchdog-5.0.2-py3-none-win_amd64.whl", hash = "sha256:d010be060c996db725fbce7e3ef14687cdcc76f4ca0e4339a68cc4532c382a73"}, | ||||
|     {file = "watchdog-5.0.2-py3-none-win_ia64.whl", hash = "sha256:3960136b2b619510569b90f0cd96408591d6c251a75c97690f4553ca88889769"}, | ||||
|     {file = "watchdog-5.0.2.tar.gz", hash = "sha256:dcebf7e475001d2cdeb020be630dc5b687e9acdd60d16fea6bb4508e7b94cf76"}, | ||||
| ] | ||||
|  | ||||
| [package.extras] | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| [tool.poetry] | ||||
| name = "authentik" | ||||
| version = "2024.8.0" | ||||
| version = "2024.8.2" | ||||
| description = "" | ||||
| authors = ["authentik Team <hello@goauthentik.io>"] | ||||
|  | ||||
|  | ||||
							
								
								
									
										113
									
								
								schema.yml
									
									
									
									
									
								
							
							
						
						
									
										113
									
								
								schema.yml
									
									
									
									
									
								
							| @ -1,7 +1,7 @@ | ||||
| openapi: 3.0.3 | ||||
| info: | ||||
|   title: authentik | ||||
|   version: 2024.8.0 | ||||
|   version: 2024.8.2 | ||||
|   description: Making authentication simple. | ||||
|   contact: | ||||
|     email: hello@goauthentik.io | ||||
| @ -290,6 +290,64 @@ paths: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/GenericError' | ||||
|           description: '' | ||||
|   /analytics/data/: | ||||
|     get: | ||||
|       operationId: analytics_data_list | ||||
|       description: Read-only view of analytics descriptions | ||||
|       tags: | ||||
|       - analytics | ||||
|       security: | ||||
|       - authentik: [] | ||||
|       responses: | ||||
|         '200': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 type: array | ||||
|                 items: | ||||
|                   $ref: '#/components/schemas/AnalyticsData' | ||||
|           description: '' | ||||
|         '400': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/ValidationError' | ||||
|           description: '' | ||||
|         '403': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/GenericError' | ||||
|           description: '' | ||||
|   /analytics/description/: | ||||
|     get: | ||||
|       operationId: analytics_description_list | ||||
|       description: Read-only view of analytics descriptions | ||||
|       tags: | ||||
|       - analytics | ||||
|       security: | ||||
|       - authentik: [] | ||||
|       responses: | ||||
|         '200': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 type: array | ||||
|                 items: | ||||
|                   $ref: '#/components/schemas/AnalyticsDescription' | ||||
|           description: '' | ||||
|         '400': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/ValidationError' | ||||
|           description: '' | ||||
|         '403': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/GenericError' | ||||
|           description: '' | ||||
|   /authenticators/admin/all/: | ||||
|     get: | ||||
|       operationId: authenticators_admin_all_list | ||||
| @ -11502,6 +11560,7 @@ paths: | ||||
|           type: string | ||||
|           nullable: true | ||||
|           enum: | ||||
|           - analytics_sent | ||||
|           - authorize_application | ||||
|           - configuration_error | ||||
|           - custom_ | ||||
| @ -35758,6 +35817,25 @@ components: | ||||
|       - rsa | ||||
|       - ecdsa | ||||
|       type: string | ||||
|     AnalyticsData: | ||||
|       type: object | ||||
|       properties: | ||||
|         data: | ||||
|           type: object | ||||
|           additionalProperties: {} | ||||
|       required: | ||||
|       - data | ||||
|     AnalyticsDescription: | ||||
|       type: object | ||||
|       description: Base serializer class which doesn't implement create/update methods | ||||
|       properties: | ||||
|         label: | ||||
|           type: string | ||||
|         desc: | ||||
|           type: string | ||||
|       required: | ||||
|       - desc | ||||
|       - label | ||||
|     App: | ||||
|       type: object | ||||
|       description: Serialize Application info | ||||
| @ -35773,6 +35851,7 @@ components: | ||||
|       enum: | ||||
|       - authentik.tenants | ||||
|       - authentik.admin | ||||
|       - authentik.analytics | ||||
|       - authentik.api | ||||
|       - authentik.crypto | ||||
|       - authentik.flows | ||||
| @ -38960,6 +39039,7 @@ components: | ||||
|       - model_updated | ||||
|       - model_deleted | ||||
|       - email_sent | ||||
|       - analytics_sent | ||||
|       - update_available | ||||
|       - custom_ | ||||
|       type: string | ||||
| @ -47361,6 +47441,13 @@ components: | ||||
|           maximum: 2147483647 | ||||
|           minimum: 1 | ||||
|           description: Default token length | ||||
|         analytics_enabled: | ||||
|           type: boolean | ||||
|         analytics_sources: | ||||
|           type: array | ||||
|           items: | ||||
|             type: string | ||||
|             minLength: 1 | ||||
|     PatchedSourceStageRequest: | ||||
|       type: object | ||||
|       description: SourceStage Serializer | ||||
| @ -51001,6 +51088,23 @@ components: | ||||
|           maximum: 2147483647 | ||||
|           minimum: 1 | ||||
|           description: Default token length | ||||
|         analytics_enabled: | ||||
|           type: boolean | ||||
|         analytics_sources: | ||||
|           type: array | ||||
|           items: | ||||
|             type: string | ||||
|         analytics_sources_obj: | ||||
|           type: array | ||||
|           items: | ||||
|             type: array | ||||
|             items: | ||||
|               type: string | ||||
|             minLength: 2 | ||||
|             maxLength: 2 | ||||
|           readOnly: true | ||||
|       required: | ||||
|       - analytics_sources_obj | ||||
|     SettingsRequest: | ||||
|       type: object | ||||
|       description: Settings Serializer | ||||
| @ -51041,6 +51145,13 @@ components: | ||||
|           maximum: 2147483647 | ||||
|           minimum: 1 | ||||
|           description: Default token length | ||||
|         analytics_enabled: | ||||
|           type: boolean | ||||
|         analytics_sources: | ||||
|           type: array | ||||
|           items: | ||||
|             type: string | ||||
|             minLength: 1 | ||||
|     SeverityEnum: | ||||
|       enum: | ||||
|       - notice | ||||
|  | ||||
| @ -11,6 +11,7 @@ from ldap3.core.exceptions import LDAPInvalidCredentialsResult | ||||
|  | ||||
| from authentik.blueprints.tests import apply_blueprint, reconcile_app | ||||
| from authentik.core.models import Application, User | ||||
| from authentik.core.tests.utils import create_test_user | ||||
| from authentik.events.models import Event, EventAction | ||||
| from authentik.flows.models import Flow | ||||
| from authentik.lib.generators import generate_id | ||||
| @ -331,6 +332,83 @@ class TestProviderLDAP(SeleniumTestCase): | ||||
|         ] | ||||
|         self.assert_list_dict_equal(expected, response) | ||||
|  | ||||
|     @retry() | ||||
|     @apply_blueprint( | ||||
|         "default/flow-default-authentication-flow.yaml", | ||||
|         "default/flow-default-invalidation-flow.yaml", | ||||
|     ) | ||||
|     @reconcile_app("authentik_tenants") | ||||
|     @reconcile_app("authentik_outposts") | ||||
|     def test_ldap_bind_search_no_perms(self): | ||||
|         """Test simple bind + search""" | ||||
|         user = create_test_user() | ||||
|         self._prepare() | ||||
|         server = Server("ldap://localhost:3389", get_info=ALL) | ||||
|         _connection = Connection( | ||||
|             server, | ||||
|             raise_exceptions=True, | ||||
|             user=f"cn={user.username},ou=users,dc=ldap,dc=goauthentik,dc=io", | ||||
|             password=user.username, | ||||
|         ) | ||||
|         _connection.bind() | ||||
|         self.assertTrue( | ||||
|             Event.objects.filter( | ||||
|                 action=EventAction.LOGIN, | ||||
|                 user={ | ||||
|                     "pk": user.pk, | ||||
|                     "email": user.email, | ||||
|                     "username": user.username, | ||||
|                 }, | ||||
|             ) | ||||
|         ) | ||||
|  | ||||
|         _connection.search( | ||||
|             "ou=Users,DC=ldaP,dc=goauthentik,dc=io", | ||||
|             "(objectClass=user)", | ||||
|             search_scope=SUBTREE, | ||||
|             attributes=[ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES], | ||||
|         ) | ||||
|         response: list = _connection.response | ||||
|         # Remove raw_attributes to make checking easier | ||||
|         for obj in response: | ||||
|             del obj["raw_attributes"] | ||||
|             del obj["raw_dn"] | ||||
|             obj["attributes"] = dict(obj["attributes"]) | ||||
|         expected = [ | ||||
|             { | ||||
|                 "dn": f"cn={user.username},ou=users,dc=ldap,dc=goauthentik,dc=io", | ||||
|                 "attributes": { | ||||
|                     "cn": user.username, | ||||
|                     "sAMAccountName": user.username, | ||||
|                     "uid": user.uid, | ||||
|                     "name": user.name, | ||||
|                     "displayName": user.name, | ||||
|                     "sn": user.name, | ||||
|                     "mail": user.email, | ||||
|                     "objectClass": [ | ||||
|                         "top", | ||||
|                         "person", | ||||
|                         "organizationalPerson", | ||||
|                         "inetOrgPerson", | ||||
|                         "user", | ||||
|                         "posixAccount", | ||||
|                         "goauthentik.io/ldap/user", | ||||
|                     ], | ||||
|                     "uidNumber": 2000 + user.pk, | ||||
|                     "gidNumber": 2000 + user.pk, | ||||
|                     "memberOf": [ | ||||
|                         f"cn={group.name},ou=groups,dc=ldap,dc=goauthentik,dc=io" | ||||
|                         for group in user.ak_groups.all() | ||||
|                     ], | ||||
|                     "homeDirectory": f"/home/{user.username}", | ||||
|                     "ak-active": True, | ||||
|                     "ak-superuser": False, | ||||
|                 }, | ||||
|                 "type": "searchResEntry", | ||||
|             }, | ||||
|         ] | ||||
|         self.assert_list_dict_equal(expected, response) | ||||
|  | ||||
|     def assert_list_dict_equal(self, expected: list[dict], actual: list[dict], match_key="dn"): | ||||
|         """Assert a list of dictionaries is identical, ignoring the ordering of items""" | ||||
|         self.assertEqual(len(expected), len(actual)) | ||||
|  | ||||
| @ -1,4 +0,0 @@ | ||||
| # don't ever lint node_modules | ||||
| node_modules | ||||
| # don't lint nyc coverage output | ||||
| coverage | ||||
| @ -1,29 +0,0 @@ | ||||
| { | ||||
|     "env": { | ||||
|         "browser": true, | ||||
|         "es2021": true | ||||
|     }, | ||||
|     "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], | ||||
|     "parser": "@typescript-eslint/parser", | ||||
|     "parserOptions": { | ||||
|         "ecmaVersion": 12, | ||||
|         "sourceType": "module" | ||||
|     }, | ||||
|     "plugins": ["@typescript-eslint"], | ||||
|     "rules": { | ||||
|         "indent": "off", | ||||
|         "linebreak-style": ["error", "unix"], | ||||
|         "quotes": ["error", "double", { "avoidEscape": true }], | ||||
|         "semi": ["error", "always"], | ||||
|         "@typescript-eslint/ban-ts-comment": "off", | ||||
|         "no-unused-vars": "off", | ||||
|         "@typescript-eslint/no-unused-vars": [ | ||||
|             "error", | ||||
|             { | ||||
|                 "argsIgnorePattern": "^_", | ||||
|                 "varsIgnorePattern": "^_", | ||||
|                 "caughtErrorsIgnorePattern": "^_" | ||||
|             } | ||||
|         ] | ||||
|     } | ||||
| } | ||||
| @ -1,26 +0,0 @@ | ||||
| { | ||||
|     "env": { | ||||
|         "browser": true, | ||||
|         "es2021": true | ||||
|     }, | ||||
|     "extends": [ | ||||
|         "eslint:recommended", | ||||
|         "plugin:@typescript-eslint/recommended", | ||||
|         "plugin:sonarjs/recommended" | ||||
|     ], | ||||
|     "parser": "@typescript-eslint/parser", | ||||
|     "parserOptions": { | ||||
|         "ecmaVersion": 12, | ||||
|         "sourceType": "module" | ||||
|     }, | ||||
|     "plugins": ["@typescript-eslint", "sonarjs"], | ||||
|     "rules": { | ||||
|         "indent": "off", | ||||
|         "linebreak-style": ["error", "unix"], | ||||
|         "quotes": ["error", "double", { "avoidEscape": true }], | ||||
|         "semi": ["error", "always"], | ||||
|         "@typescript-eslint/ban-ts-comment": "off", | ||||
|         "sonarjs/cognitive-complexity": ["error", 9], | ||||
|         "sonarjs/no-nested-template-literals": "off" | ||||
|     } | ||||
| } | ||||
							
								
								
									
										84
									
								
								tests/wdio/eslint.config.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								tests/wdio/eslint.config.mjs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,84 @@ | ||||
| import eslint from "@eslint/js"; | ||||
| import tsparser from "@typescript-eslint/parser"; | ||||
| import litconf from "eslint-plugin-lit"; | ||||
| import wcconf from "eslint-plugin-wc"; | ||||
| import globals from "globals"; | ||||
| import tseslint from "typescript-eslint"; | ||||
|  | ||||
| export default [ | ||||
|     // You would not believe how much this change has frustrated users: ["if an ignores key is used | ||||
|     // without any other keys in the configuration object, then the patterns act as global | ||||
|     // ignores"](https://eslint.org/docs/latest/use/configure/ignore) | ||||
|     { | ||||
|         ignores: [ | ||||
|             "dist/", | ||||
|             // don't lint the cache | ||||
|             ".wireit/", | ||||
|             // let packages have their own configurations | ||||
|             "packages/", | ||||
|             // don't ever lint node_modules | ||||
|             "node_modules/", | ||||
|             ".storybook/*", | ||||
|             // don't lint build output (make sure it's set to your correct build folder name) | ||||
|             // don't lint nyc coverage output | ||||
|             "coverage/", | ||||
|             "src/locale-codes.ts", | ||||
|             "storybook-static/", | ||||
|             "src/locales/", | ||||
|         ], | ||||
|     }, | ||||
|     eslint.configs.recommended, | ||||
|     wcconf.configs["flat/recommended"], | ||||
|     litconf.configs["flat/recommended"], | ||||
|     ...tseslint.configs.recommended, | ||||
|     { | ||||
|         languageOptions: { | ||||
|             parser: tsparser, | ||||
|             parserOptions: { | ||||
|                 ecmaVersion: 12, | ||||
|                 sourceType: "module", | ||||
|             }, | ||||
|         }, | ||||
|         files: ["src/**"], | ||||
|         rules: { | ||||
|             "no-unused-vars": "off", | ||||
|             "no-console": ["error", { allow: ["debug", "warn", "error"] }], | ||||
|             "@typescript-eslint/ban-ts-comment": "off", | ||||
|             "@typescript-eslint/no-unused-vars": [ | ||||
|                 "error", | ||||
|                 { | ||||
|                     argsIgnorePattern: "^_", | ||||
|                     varsIgnorePattern: "^_", | ||||
|                     caughtErrorsIgnorePattern: "^_", | ||||
|                 }, | ||||
|             ], | ||||
|         }, | ||||
|     }, | ||||
|     { | ||||
|         languageOptions: { | ||||
|             parser: tsparser, | ||||
|             parserOptions: { | ||||
|                 ecmaVersion: 12, | ||||
|                 sourceType: "module", | ||||
|             }, | ||||
|             globals: { | ||||
|                 ...globals.nodeBuiltin, | ||||
|             }, | ||||
|         }, | ||||
|         files: ["scripts/*.mjs", "*.ts", "*.mjs"], | ||||
|         rules: { | ||||
|             "no-unused-vars": "off", | ||||
|             // We WANT our scripts to output to the console! | ||||
|             "no-console": "off", | ||||
|             "@typescript-eslint/ban-ts-comment": "off", | ||||
|             "@typescript-eslint/no-unused-vars": [ | ||||
|                 "error", | ||||
|                 { | ||||
|                     argsIgnorePattern: "^_", | ||||
|                     varsIgnorePattern: "^_", | ||||
|                     caughtErrorsIgnorePattern: "^_", | ||||
|                 }, | ||||
|             ], | ||||
|         }, | ||||
|     }, | ||||
| ]; | ||||
							
								
								
									
										10691
									
								
								tests/wdio/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										10691
									
								
								tests/wdio/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -1,37 +1,44 @@ | ||||
| { | ||||
|     "name": "@goauthentik/web-tests", | ||||
|     "private": true, | ||||
|     "type": "module", | ||||
|     "dependencies": { | ||||
|         "chromedriver": "^129.0.0", | ||||
|         "lockfile-lint": "^4.14.0", | ||||
|         "syncpack": "^13.0.0" | ||||
|     }, | ||||
|     "devDependencies": { | ||||
|         "@eslint/js": "^9.11.1", | ||||
|         "@trivago/prettier-plugin-sort-imports": "^4.3.0", | ||||
|         "@typescript-eslint/eslint-plugin": "^7.17.0", | ||||
|         "@typescript-eslint/parser": "^7.17.0", | ||||
|         "@types/mocha": "^10.0.8", | ||||
|         "@typescript-eslint/eslint-plugin": "^8.7.0", | ||||
|         "@typescript-eslint/parser": "^8.7.0", | ||||
|         "@wdio/cli": "^9.0.3", | ||||
|         "@wdio/local-runner": "^9.0.1", | ||||
|         "@wdio/mocha-framework": "^8.40.2", | ||||
|         "@wdio/spec-reporter": "^8.39.0", | ||||
|         "eslint": "^8.57.0", | ||||
|         "eslint-config-google": "^0.14.0", | ||||
|         "eslint-plugin-sonarjs": "^1.0.3", | ||||
|         "@wdio/mocha-framework": "^9.0.8", | ||||
|         "@wdio/spec-reporter": "^9.0.8", | ||||
|         "eslint-plugin-lit": "^1.14.0", | ||||
|         "eslint-plugin-sonarjs": "^2.0.2", | ||||
|         "eslint-plugin-wc": "^2.1.0", | ||||
|         "eslint": "^9.11.1", | ||||
|         "npm-run-all": "^4.1.5", | ||||
|         "prettier": "^3.3.3", | ||||
|         "ts-node": "^10.9.2", | ||||
|         "typescript": "^5.5.4", | ||||
|         "typescript-eslint": "^8.7.0", | ||||
|         "typescript": "^5.6.2", | ||||
|         "wdio-wait-for": "^3.0.11" | ||||
|     }, | ||||
|     "scripts": { | ||||
|         "wdio": "wdio run ./wdio.conf.ts", | ||||
|         "lint:precommit": "eslint --max-warnings 0 --config ./.eslintrc.precommit.json $(git status --porcelain . | grep '^[AM?][M?]' | cut -d'/' -f3- | grep -E '\\.(ts|js|tsx|jsx)$')", | ||||
|         "lint": "eslint . --max-warnings 0 --fix", | ||||
|         "lint:spelling": "codespell -D - -D $(git rev-parse --show-toplevel 2> /dev/null)/.github/codespell-dictionary.txt -I $(git rev-parse --show-toplevel 2> /dev/null)/.github/codespell-words.txt ./test -s", | ||||
|         "precommit": "run-s lint:precommit lint:spelling prettier", | ||||
|         "prettier-check": "prettier --check .", | ||||
|         "prettier": "prettier --write ." | ||||
|     }, | ||||
|     "engines": { | ||||
|         "node": ">=20" | ||||
|     }, | ||||
|     "dependencies": { | ||||
|         "chromedriver": "^128.0.0" | ||||
|     } | ||||
|     "private": true, | ||||
|     "scripts": { | ||||
|         "lint": "eslint . --max-warnings 0 --fix", | ||||
|         "lint:lockfile": "lockfile-lint --path package.json --type npm --allowed-hosts npm --validate-https", | ||||
|         "lint:package": "syncpack format -i '    '", | ||||
|         "lint:precommit": "eslint --max-warnings 0 --config ./.eslintrc.precommit.json $(git status --porcelain . | grep '^[AM?][M?]' | cut -d'/' -f3- | grep -E '\\.(ts|js|tsx|jsx)$')", | ||||
|         "lint:spelling": "codespell -D - -D $(git rev-parse --show-toplevel 2> /dev/null)/.github/codespell-dictionary.txt -I $(git rev-parse --show-toplevel 2> /dev/null)/.github/codespell-words.txt ./test -s", | ||||
|         "precommit": "run-s lint:precommit lint:spelling prettier", | ||||
|         "prettier": "prettier --write .", | ||||
|         "prettier-check": "prettier --check .", | ||||
|         "wdio": "wdio run ./wdio.conf.ts" | ||||
|     }, | ||||
|     "type": "module" | ||||
| } | ||||
|  | ||||
| @ -1,25 +1,11 @@ | ||||
| import Page from "../pageobjects/page.js"; | ||||
| import { browser } from "@wdio/globals"; | ||||
|  | ||||
| const CLICK_TIME_DELAY = 250; | ||||
|  | ||||
| export default class AdminPage extends Page { | ||||
|     public get pageHeader() { | ||||
|         return $('>>>ak-page-header slot[name="header"]'); | ||||
|     public async pageHeader() { | ||||
|         return await $("ak-page-header").$('slot[name="header"]'); | ||||
|     } | ||||
|  | ||||
|     async openApplicationsListPage() { | ||||
|         await this.open("if/admin/#/core/applications"); | ||||
|     } | ||||
|  | ||||
|     public open(path: string) { | ||||
|         return browser.url(`http://localhost:9000/${path}`); | ||||
|     } | ||||
|  | ||||
|     public pause(selector?: string) { | ||||
|         if (selector) { | ||||
|             return $(selector).waitForDisplayed(); | ||||
|         } | ||||
|         return browser.pause(CLICK_TIME_DELAY); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -27,24 +27,24 @@ class ApplicationWizardView extends AdminPage { | ||||
|     radius = RadiusForm; | ||||
|     app = ApplicationForm; | ||||
|  | ||||
|     get wizardTitle() { | ||||
|         return $(">>>ak-wizard-frame .pf-c-wizard__header h1.pf-c-title"); | ||||
|     async wizardTitle() { | ||||
|         return await $("ak-wizard-frame").$(".pf-c-wizard__title"); | ||||
|     } | ||||
|  | ||||
|     get providerList() { | ||||
|         return $(">>>ak-application-wizard-authentication-method-choice"); | ||||
|     async providerList() { | ||||
|         return await $("ak-application-wizard-authentication-method-choice"); | ||||
|     } | ||||
|  | ||||
|     get nextButton() { | ||||
|         return $(">>>ak-wizard-frame footer button.pf-m-primary"); | ||||
|     async nextButton() { | ||||
|         return await $("ak-wizard-frame").$("footer button.pf-m-primary"); | ||||
|     } | ||||
|  | ||||
|     async getProviderType(type: string) { | ||||
|         return await this.providerList.$(`>>>input[value="${type}"]`); | ||||
|         return await this.providerList().$(`input[value="${type}"]`); | ||||
|     } | ||||
|  | ||||
|     get successMessage() { | ||||
|         return $('>>>[data-commit-state="success"]'); | ||||
|     async successMessage() { | ||||
|         return await $('[data-commit-state="success"]'); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -65,8 +65,10 @@ const providerValues: Pair[] = [ | ||||
| providerValues.forEach(([value, name]: Pair) => { | ||||
|     Object.defineProperties(ApplicationWizardView.prototype, { | ||||
|         [name]: { | ||||
|             get: function () { | ||||
|                 return this.providerList.$(`>>>input[value="${value}"]`); | ||||
|             get: async function () { | ||||
|                 return await ( | ||||
|                     await this.providerList() | ||||
|                 ).$(`div[data-ouid-component-name="${value}"]`); | ||||
|             }, | ||||
|         }, | ||||
|     }); | ||||
|  | ||||
| @ -9,8 +9,8 @@ class ApplicationsListPage extends AdminPage { | ||||
|      * define selectors using getter methods | ||||
|      */ | ||||
|  | ||||
|     get startWizardButton() { | ||||
|         return $('>>>ak-wizard-frame button[slot="trigger"]'); | ||||
|     async startWizardButton() { | ||||
|         return await $("ak-application-wizard").$('button[slot="trigger"]'); | ||||
|     } | ||||
|  | ||||
|     async open() { | ||||
|  | ||||
| @ -2,16 +2,16 @@ import Page from "../page.js"; | ||||
| import { $ } from "@wdio/globals"; | ||||
|  | ||||
| export class ApplicationForm extends Page { | ||||
|     get name() { | ||||
|         return $('>>>ak-form-element-horizontal input[name="name"]'); | ||||
|     async name() { | ||||
|         return await $('ak-text-input[name="name"]').$("input"); | ||||
|     } | ||||
|  | ||||
|     get uiSettings() { | ||||
|         return $('>>>ak-form-group button[aria-label="UI Settings"]'); | ||||
|     async uiSettings() { | ||||
|         return await $("ak-form-group").$('button[aria-label="UI Settings"]'); | ||||
|     } | ||||
|  | ||||
|     get launchUrl() { | ||||
|         return $('>>>input[name="metaLaunchUrl"]'); | ||||
|     async launchUrl() { | ||||
|         return await $('input[name="metaLaunchUrl"]'); | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| @ -4,14 +4,14 @@ import { $ } from "@wdio/globals"; | ||||
| export class ForwardProxyForm extends Page { | ||||
|     async setAuthorizationFlow(selector: string) { | ||||
|         await this.searchSelect( | ||||
|             '>>>ak-flow-search[name="authorizationFlow"] input[type="text"]', | ||||
|             'ak-flow-search[name="authorizationFlow"]', | ||||
|             "authorizationFlow", | ||||
|             `button*=${selector}`, | ||||
|             selector, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     get externalHost() { | ||||
|         return $('>>>input[name="externalHost"]'); | ||||
|         return $('input[name="externalHost"]'); | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| @ -1,11 +1,11 @@ | ||||
| import Page from "../page.js"; | ||||
|  | ||||
| export class LdapForm extends Page { | ||||
|     async setBindFlow(selector: string) { | ||||
|     async setBindFlow() { | ||||
|         await this.searchSelect( | ||||
|             '>>>ak-branded-flow-search[name="authorizationFlow"] input[type="text"]', | ||||
|             'ak-search-select-view[name="authorizationFlow"]', | ||||
|             "authorizationFlow", | ||||
|             `button*=${selector}`, | ||||
|             "default-authentication-flow", | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|  | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	