Compare commits

..

9 Commits

Author SHA1 Message Date
a407d903ab idk
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-09-03 19:51:11 +02:00
7e6f36b0b4 initial flow support
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-09-03 19:39:54 +02:00
8dd7b0569c ws support
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-09-03 19:39:26 +02:00
616be78e10 fallback for error reporting
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-09-03 19:39:15 +02:00
7125db1fbd cleanup
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-09-03 19:39:04 +02:00
8c7c7c3fee separate cors middleware
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-09-03 19:11:09 +02:00
bbbd00db22 make flows kinda work with bearer token
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-09-03 19:11:09 +02:00
6d60c5f7c7 terrible cors for testing
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-09-03 19:11:09 +02:00
accf25a626 web/sdk: embed authentik settings into anything
the web part

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-09-03 19:11:09 +02:00
294 changed files with 7962 additions and 22134 deletions

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 2024.8.2
current_version = 2024.8.0
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*))?

View File

@ -44,11 +44,9 @@ updates:
- "babel-*"
eslint:
patterns:
- "@eslint/*"
- "@typescript-eslint/*"
- "eslint-*"
- "eslint"
- "typescript-eslint"
- "eslint-*"
storybook:
patterns:
- "@storybook/*"
@ -56,12 +54,10 @@ updates:
esbuild:
patterns:
- "@esbuild/*"
- "esbuild*"
rollup:
patterns:
- "@rollup/*"
- "rollup-*"
- "rollup*"
swc:
patterns:
- "@swc/*"

View File

@ -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@v7
- uses: peter-evans/create-pull-request@v6
id: cpr
with:
token: ${{ steps.generate_token.outputs.token }}

View File

@ -120,12 +120,6 @@ 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
@ -144,12 +138,6 @@ 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
@ -202,12 +190,6 @@ 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

View File

@ -45,6 +45,7 @@ jobs:
- working-directory: ${{ matrix.project }}/
run: |
npm ci
${{ matrix.extra_setup }}
- name: Generate API
run: make gen-client-ts
- name: Lint

View File

@ -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@v7
- uses: peter-evans/create-pull-request@v6
id: cpr
with:
token: ${{ steps.generate_token.outputs.token }}

View File

@ -42,7 +42,7 @@ jobs:
with:
githubToken: ${{ steps.generate_token.outputs.token }}
compressOnly: ${{ github.event_name != 'pull_request' }}
- uses: peter-evans/create-pull-request@v7
- uses: peter-evans/create-pull-request@v6
if: "${{ github.event_name != 'pull_request' && steps.compress.outputs.markdown != '' }}"
id: cpr
with:

View File

@ -32,7 +32,7 @@ jobs:
poetry run ak compilemessages
make web-check-compile
- name: Create Pull Request
uses: peter-evans/create-pull-request@v7
uses: peter-evans/create-pull-request@v6
with:
token: ${{ steps.generate_token.outputs.token }}
branch: extract-compile-backend-translation

View File

@ -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.6-slim-bookworm-fips-full AS python-deps
FROM ghcr.io/goauthentik/fips-python:3.12.5-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.6-slim-bookworm-fips-full AS final-image
FROM ghcr.io/goauthentik/fips-python:3.12.5-slim-bookworm-fips-full AS final-image
ARG VERSION
ARG GIT_BUILD_HASH

View File

@ -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 ## 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 web-test ## 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

View File

@ -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

View File

@ -2,7 +2,7 @@
from os import environ
__version__ = "2024.8.2"
__version__ = "2024.8.0"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"

View File

@ -1,20 +0,0 @@
"""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,
}

View File

@ -1,8 +1,10 @@
"""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
@ -19,6 +21,8 @@ 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__)
@ -74,16 +78,10 @@ def update_latest_version(self: SystemTask):
context__new_version=upstream_version,
).exists():
return
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()
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()
except (RequestException, IndexError) as exc:
cache.set(VERSION_CACHE_KEY, VERSION_NULL, VERSION_CACHE_TIMEOUT)
self.set_error(exc)

View File

@ -17,7 +17,6 @@ RESPONSE_VALID = {
"stable": {
"version": "99999999.9999999",
"changelog": "See https://goauthentik.io/test",
"changelog_url": "https://goauthentik.io/test",
"reason": "bugfix",
},
}
@ -36,7 +35,7 @@ class TestAdminTasks(TestCase):
Event.objects.filter(
action=EventAction.UPDATE_AVAILABLE,
context__new_version="99999999.9999999",
context__message="New version 99999999.9999999 available!",
context__message="Changelog: https://goauthentik.io/test",
).exists()
)
# test that a consecutive check doesn't create a duplicate event
@ -46,7 +45,7 @@ class TestAdminTasks(TestCase):
Event.objects.filter(
action=EventAction.UPDATE_AVAILABLE,
context__new_version="99999999.9999999",
context__message="New version 99999999.9999999 available!",
context__message="Changelog: https://goauthentik.io/test",
)
),
1,

View File

@ -1,54 +0,0 @@
"""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),
}
)

View File

@ -1,12 +0,0 @@
"""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

View File

@ -1,19 +0,0 @@
"""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()}

View File

@ -1,17 +0,0 @@
"""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"},
}
}

View File

@ -1,45 +0,0 @@
"""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)

View File

@ -1,76 +0,0 @@
"""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())

View File

@ -1,8 +0,0 @@
"""API URLs"""
from authentik.analytics.api import AnalyticsDataViewSet, AnalyticsDescriptionViewSet
api_urlpatterns = [
("analytics/description", AnalyticsDescriptionViewSet, "analytics-description"),
("analytics/data", AnalyticsDataViewSet, "analytics-data"),
]

View File

@ -1,112 +0,0 @@
"""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(),
}

View File

@ -25,3 +25,31 @@ class BrandMiddleware:
if locale != "":
activate(locale)
return self.get_response(request)
class BrandCORSAPIMiddleware:
"""CORS for API requests depending on Brand"""
get_response: Callable[[HttpRequest], HttpResponse]
def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]):
self.get_response = get_response
def set_headers(self, request: HttpRequest, response: HttpResponse):
response["Access-Control-Allow-Origin"] = "http://localhost:8080"
response["Access-Control-Allow-Credentials"] = "true"
def __call__(self, request: HttpRequest) -> HttpResponse:
if request.method == "OPTIONS":
response = HttpResponse(
status=200,
)
self.set_headers(request, response)
response["Access-Control-Allow-Headers"] = (
"authorization,sentry-trace,x-authentik-csrf,content-type"
)
response["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS"
return response
response = self.get_response(request)
self.set_headers(request, response)
return response

View File

@ -30,10 +30,8 @@ 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
@ -164,15 +162,12 @@ class PropertyMappingViewSet(
response_data = {"successful": True, "result": ""}
try:
result = mapping.evaluate(dry_run=True, **context)
result = mapping.evaluate(**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"] = exception_to_string(exc)
response_data["result"] = str(exc)
response_data["successful"] = False
response = PropertyMappingTestResultSerializer(response_data)
return Response(response.data)

View File

@ -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)
user_to_be = self.get_object()
if not request.user.has_perm("impersonate", user_to_be):
if not request.user.has_perm("impersonate"):
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)

View File

@ -23,7 +23,6 @@ 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
@ -169,7 +168,7 @@ class GroupQuerySet(CTEQuerySet):
return cte.join(Group, group_uuid=cte.col.group_uuid).with_cte(cte)
class Group(SerializerModel, AttributesMixin, AnalyticsMixin):
class Group(SerializerModel, AttributesMixin):
"""Group model which supports a basic hierarchy and has attributes"""
group_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
@ -259,7 +258,7 @@ class UserManager(DjangoUserManager):
return self.get_queryset().exclude_anonymous()
class User(SerializerModel, GuardianUserMixin, AttributesMixin, AbstractUser, AnalyticsMixin):
class User(SerializerModel, GuardianUserMixin, AttributesMixin, AbstractUser):
"""authentik User model, based on django's contrib auth user model."""
uuid = models.UUIDField(default=uuid4, editable=False, unique=True)
@ -377,7 +376,7 @@ class User(SerializerModel, GuardianUserMixin, AttributesMixin, AbstractUser, An
return get_avatar(self)
class Provider(SerializerModel, AnalyticsMixin):
class Provider(SerializerModel):
"""Application-independent Provider instance. For example SAML2 Remote, OAuth2 Application"""
name = models.TextField(unique=True)
@ -467,11 +466,13 @@ 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, AnalyticsMixin):
class Application(SerializerModel, PolicyBindingModel):
"""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"""
@ -544,24 +545,15 @@ class Application(SerializerModel, PolicyBindingModel, AnalyticsMixin):
if not self.provider:
return None
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:
parent = getattr(parent, level)
except AttributeError:
break
if parent in candidates:
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
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]
try:
return getattr(self.provider, subclass)
except AttributeError:
pass
return None
def __str__(self):
return str(self.name)
@ -604,7 +596,7 @@ class SourceGroupMatchingModes(models.TextChoices):
)
class Source(ManagedModel, SerializerModel, PolicyBindingModel, AnalyticsMixin):
class Source(ManagedModel, SerializerModel, PolicyBindingModel):
"""Base Authentication source, i.e. an OAuth Provider, SAML Remote or LDAP Server"""
name = models.TextField(help_text=_("Source's display Name."))
@ -736,7 +728,7 @@ class Source(ManagedModel, SerializerModel, PolicyBindingModel, AnalyticsMixin):
]
class UserSourceConnection(SerializerModel, CreatedUpdatedModel, AnalyticsMixin):
class UserSourceConnection(SerializerModel, CreatedUpdatedModel):
"""Connection between User and Source."""
user = models.ForeignKey(User, on_delete=models.CASCADE)
@ -756,7 +748,7 @@ class UserSourceConnection(SerializerModel, CreatedUpdatedModel, AnalyticsMixin)
unique_together = (("user", "source"),)
class GroupSourceConnection(SerializerModel, CreatedUpdatedModel, AnalyticsMixin):
class GroupSourceConnection(SerializerModel, CreatedUpdatedModel):
"""Connection between Group and Source."""
group = models.ForeignKey(Group, on_delete=models.CASCADE)
@ -880,7 +872,7 @@ class Token(SerializerModel, ManagedModel, ExpiringModel):
).save()
class PropertyMapping(SerializerModel, ManagedModel, AnalyticsMixin):
class PropertyMapping(SerializerModel, ManagedModel):
"""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)
@ -909,7 +901,7 @@ class PropertyMapping(SerializerModel, ManagedModel, AnalyticsMixin):
except ControlFlowException as exc:
raise exc
except Exception as exc:
raise PropertyMappingExpressionException(exc, self) from exc
raise PropertyMappingExpressionException(self, exc) from exc
def __str__(self):
return f"Property Mapping {self.name}"

View File

@ -13,6 +13,7 @@
<link rel="shortcut icon" href="{{ brand.branding_favicon }}">
{% block head_before %}
{% endblock %}
<link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly-base.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/custom.css' %}" data-inject>
{% versioned_script "dist/poly-%v.js" %}

View File

@ -9,12 +9,9 @@ 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):
@ -225,31 +222,3 @@ 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
)

View File

@ -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.tests.utils import create_test_admin_user, create_test_user
from authentik.core.models import User
from authentik.core.tests.utils import create_test_admin_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 = create_test_user()
self.other_user = User.objects.create(username="to-impersonate")
self.user = create_test_admin_user()
def test_impersonate_simple(self):
@ -44,26 +44,6 @@ 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)

View File

@ -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
from authentik.enterprise.models import License, LicenseUsageStatus
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 not LicenseKey.cached_summary().status.is_valid:
if LicenseKey.cached_summary().status != LicenseUsageStatus.UNLICENSED:
raise ValidationError(_("Enterprise is required to create/update this object."))
return super().validate(attrs)

View File

@ -121,9 +121,6 @@ class LicenseKey:
),
)
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

View File

@ -3,7 +3,7 @@
from datetime import datetime
from django.core.cache import cache
from django.db.models.signals import post_delete, post_save, pre_save
from django.db.models.signals import post_save, pre_save
from django.dispatch import receiver
from django.utils.timezone import get_current_timezone
@ -27,9 +27,3 @@ 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)

View File

@ -69,5 +69,8 @@ class NotificationViewSet(
@action(detail=False, methods=["post"])
def mark_all_seen(self, request: Request) -> Response:
"""Mark all the user's notifications as seen"""
Notification.objects.filter(user=request.user, seen=False).update(seen=True)
notifications = Notification.objects.filter(user=request.user)
for notification in notifications:
notification.seen = True
Notification.objects.bulk_update(notifications, ["seen"])
return Response({}, status=204)

View File

@ -1,49 +0,0 @@
# 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"),
]
),
),
]

View File

@ -49,7 +49,6 @@ 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
@ -59,11 +58,7 @@ 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)
return now() + timedelta(days=365)
def default_brand():
@ -119,7 +114,6 @@ class EventAction(models.TextChoices):
MODEL_DELETED = "model_deleted"
EMAIL_SENT = "email_sent"
ANALYTICS_SENT = "analytics_sent"
UPDATE_AVAILABLE = "update_available"
CUSTOM_PREFIX = "custom_"
@ -251,6 +245,12 @@ 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))

View File

@ -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_OUTPOST, PLAN_CONTEXT_SOURCE, FlowPlan
from authentik.flows.planner import 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,9 +38,6 @@ 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

View File

@ -6,7 +6,6 @@ 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
@ -21,7 +20,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 in [default_token_key, default_event_duration]:
if field.default == default_token_key:
allowed += 1
with self.assertNumQueries(allowed):
str(test_model())

View File

@ -2,8 +2,7 @@
from unittest.mock import MagicMock, patch
from django.urls import reverse
from rest_framework.test import APITestCase
from django.test import TestCase
from authentik.core.models import Group, User
from authentik.events.models import (
@ -11,7 +10,6 @@ from authentik.events.models import (
EventAction,
Notification,
NotificationRule,
NotificationSeverity,
NotificationTransport,
NotificationWebhookMapping,
TransportMode,
@ -22,7 +20,7 @@ from authentik.policies.exceptions import PolicyException
from authentik.policies.models import PolicyBinding
class TestEventsNotifications(APITestCase):
class TestEventsNotifications(TestCase):
"""Test Event Notifications"""
def setUp(self) -> None:
@ -133,15 +131,3 @@ class TestEventsNotifications(APITestCase):
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())

View File

@ -23,7 +23,6 @@ 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
@ -33,7 +32,6 @@ 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"
@ -145,23 +143,10 @@ class FlowPlanner:
and not request.user.is_superuser
):
raise FlowNonApplicableException()
outpost_user = ClientIPMiddleware.get_outpost_user(request)
if self.flow.authentication == FlowAuthenticationRequirement.REQUIRE_OUTPOST:
outpost_user = ClientIPMiddleware.get_outpost_user(request)
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
@ -174,12 +159,11 @@ 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 context and PLAN_CONTEXT_PENDING_USER in context:
user = context[PLAN_CONTEXT_PENDING_USER]
if default_context and PLAN_CONTEXT_PENDING_USER in default_context:
user = default_context[PLAN_CONTEXT_PENDING_USER]
else:
user = request.user
# We only need to check the flow authentication if it's planned without a user
@ -187,13 +171,14 @@ 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
context.update(self._check_authentication(request))
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
span.set_data("context", cleanse_dict(context))
engine.request.context.update(context)
if default_context:
span.set_data("default_context", cleanse_dict(default_context))
engine.request.context.update(default_context)
engine.build()
result = engine.result
if not result.passing:
@ -210,12 +195,12 @@ class FlowPlanner:
key=cached_plan_key,
)
# Reset the context as this isn't factored into caching
cached_plan.context = context
cached_plan.context = default_context or {}
return cached_plan
self._logger.debug(
"f(plan): building plan",
)
plan = self._build_plan(user, request, context)
plan = self._build_plan(user, request, default_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:

View File

@ -16,12 +16,14 @@ from django.views.decorators.clickjacking import xframe_options_sameorigin
from django.views.generic import View
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, PolymorphicProxySerializer, extend_schema
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.permissions import AllowAny
from rest_framework.views import APIView
from sentry_sdk import capture_exception, start_span
from sentry_sdk.api import set_tag
from structlog.stdlib import BoundLogger, get_logger
from authentik.api.authentication import bearer_auth, get_authorization_header
from authentik.brands.models import Brand
from authentik.core.models import Application
from authentik.events.models import Event, EventAction, cleanse_dict
@ -116,6 +118,14 @@ class FlowExecutorView(APIView):
super().setup(request, flow_slug=flow_slug)
self.flow = get_object_or_404(Flow.objects.select_related(), slug=flow_slug)
self._logger = get_logger().bind(flow_slug=flow_slug)
# Usually flows are authenticated by session, we don't really use rest_framework's
# authentication method.
try:
user = bearer_auth(get_authorization_header(request))
if user:
request.user = user
except AuthenticationFailed:
pass
set_tag("authentik.flow", self.flow.slug)
def handle_invalid_flow(self, exc: FlowNonApplicableException) -> HttpResponse:

View File

@ -2,6 +2,7 @@
import re
import socket
from collections.abc import Iterable
from ipaddress import ip_address, ip_network
from textwrap import indent
from types import CodeType
@ -27,12 +28,6 @@ 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"""
@ -182,9 +177,9 @@ class BaseEvaluator:
proc = PolicyProcess(PolicyBinding(policy=policy), request=req, connection=None)
return proc.profiling_wrapper()
def wrap_expression(self, expression: str) -> str:
def wrap_expression(self, expression: str, params: Iterable[str]) -> str:
"""Wrap expression in a function, call it, and save the result as `result`"""
handler_signature = ",".join(sanitize_arg(x) for x in self._context.keys())
handler_signature = ",".join(params)
full_expression = ""
full_expression += f"def handler({handler_signature}):\n"
full_expression += indent(expression, " ")
@ -193,8 +188,8 @@ class BaseEvaluator:
def compile(self, expression: str) -> CodeType:
"""Parse expression. Raises SyntaxError or ValueError if the syntax is incorrect."""
expression = self.wrap_expression(expression)
return compile(expression, self._filename, "exec")
param_keys = self._context.keys()
return compile(self.wrap_expression(expression, param_keys), self._filename, "exec")
def evaluate(self, expression_source: str) -> Any:
"""Parse and evaluate expression. If the syntax is incorrect, a SyntaxError is raised.
@ -210,7 +205,7 @@ class BaseEvaluator:
self.handle_error(exc, expression_source)
raise exc
try:
_locals = {sanitize_arg(x): y for x, y in self._context.items()}
_locals = self._context
# 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.

View File

@ -1,52 +0,0 @@
# 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,
),
),
]

View File

@ -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,25 +20,16 @@ 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():
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,
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
)
@ -46,7 +37,6 @@ class Migration(migrations.Migration):
dependencies = [
("authentik_providers_ldap", "0003_ldapprovider_mfa_support_and_more"),
("guardian", "0002_generic_permissions_index"),
]
operations = [

View File

@ -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"

View File

@ -87,11 +87,7 @@ 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"""
from authentik.outposts.tasks import outpost_connection_discovery
return [
outpost_connection_discovery,
]
return []
def _get_startup_tasks_all_tenants() -> list[Callable]:

View File

@ -221,9 +221,9 @@ class ClientIPMiddleware:
)
return None
# Update sentry scope to include correct IP
sentry_user = Scope.get_isolation_scope()._user or {}
sentry_user["ip_address"] = delegated_ip
Scope.get_isolation_scope().set_user(sentry_user)
user = Scope.get_isolation_scope()._user or {}
user["ip_address"] = delegated_ip
Scope.get_isolation_scope().set_user(user)
# Set the outpost service account on the request
setattr(request, self.request_attr_outpost_user, user)
return delegated_ip

View File

@ -70,7 +70,6 @@ TENANT_APPS = [
"django.contrib.contenttypes",
"django.contrib.sessions",
"authentik.admin",
"authentik.analytics",
"authentik.api",
"authentik.crypto",
"authentik.flows",
@ -249,6 +248,7 @@ MIDDLEWARE = [
"django.contrib.auth.middleware.AuthenticationMiddleware",
"authentik.core.middleware.RequestIDMiddleware",
"authentik.brands.middleware.BrandMiddleware",
"authentik.brands.middleware.BrandCORSAPIMiddleware",
"authentik.events.middleware.AuditMiddleware",
"django.middleware.security.SecurityMiddleware",
"django.middleware.common.CommonMiddleware",

View File

@ -1,7 +1,6 @@
"""authentik storage backends"""
import os
from urllib.parse import parse_qsl, urlsplit
from django.conf import settings
from django.core.exceptions import SuspiciousOperation
@ -111,34 +110,3 @@ 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()

View File

@ -38,11 +38,7 @@ 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,
self._source.object_uniqueness_field,
],
attributes=[ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES],
**kwargs,
)
@ -57,9 +53,9 @@ class GroupLDAPSynchronizer(BaseLDAPSynchronizer):
continue
attributes = group.get("attributes", {})
group_dn = flatten(flatten(group.get("entryDN", group.get("dn"))))
if not attributes.get(self._source.object_uniqueness_field):
if self._source.object_uniqueness_field not in attributes:
self.message(
f"Uniqueness field not found/not set in attributes: '{group_dn}'",
f"Cannot find uniqueness field in attributes: '{group_dn}'",
attributes=attributes.keys(),
dn=group_dn,
)

View File

@ -40,11 +40,7 @@ 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,
self._source.object_uniqueness_field,
],
attributes=[ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES],
**kwargs,
)
@ -59,9 +55,9 @@ class UserLDAPSynchronizer(BaseLDAPSynchronizer):
continue
attributes = user.get("attributes", {})
user_dn = flatten(user.get("entryDN", user.get("dn")))
if not attributes.get(self._source.object_uniqueness_field):
if self._source.object_uniqueness_field not in attributes:
self.message(
f"Uniqueness field not found/not set in attributes: '{user_dn}'",
f"Cannot find uniqueness field in attributes: '{user_dn}'",
attributes=attributes.keys(),
dn=user_dn,
)

File diff suppressed because one or more lines are too long

View File

@ -1,12 +1,9 @@
"""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
@ -15,8 +12,6 @@ from authentik.tenants.models import Tenant
class SettingsSerializer(ModelSerializer):
"""Settings Serializer"""
analytics_sources_obj = SerializerMethodField()
class Meta:
model = Tenant
fields = [
@ -30,19 +25,8 @@ 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"""

View File

@ -1,26 +0,0 @@
# 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
),
),
]

View File

@ -4,7 +4,6 @@ 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
@ -97,9 +96,6 @@ 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")

View File

@ -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.2 Blueprint schema",
"title": "authentik 2024.8.0 Blueprint schema",
"required": [
"version",
"entries"
@ -4227,7 +4227,6 @@
"model_updated",
"model_deleted",
"email_sent",
"analytics_sent",
"update_available",
"custom_"
],
@ -4252,7 +4251,6 @@
null,
"authentik.tenants",
"authentik.admin",
"authentik.analytics",
"authentik.api",
"authentik.crypto",
"authentik.flows",
@ -13118,7 +13116,6 @@
"model_updated",
"model_deleted",
"email_sent",
"analytics_sent",
"update_available",
"custom_"
],
@ -13280,7 +13277,6 @@
"model_updated",
"model_deleted",
"email_sent",
"analytics_sent",
"update_available",
"custom_"
],

View File

@ -31,7 +31,7 @@ services:
volumes:
- redis:/data
server:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.8.2}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.8.0}
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.2}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.8.0}
restart: unless-stopped
command: worker
environment:

8
go.mod
View File

@ -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.29.0
github.com/getsentry/sentry-go v0.28.1
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
@ -22,16 +22,16 @@ require (
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.4
github.com/prometheus/client_golang v1.20.2
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.2024082.1
goauthentik.io/api/v3 v3.2024064.1
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
golang.org/x/oauth2 v0.23.0
golang.org/x/oauth2 v0.22.0
golang.org/x/sync v0.8.0
gopkg.in/yaml.v2 v2.4.0
layeh.com/radius v0.0.0-20210819152912-ad72663a72ab

16
go.sum
View File

@ -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.29.0 h1:YtWluuCFg9OfcqnaujpY918N/AhCCwarIDWOYSBAjCA=
github.com/getsentry/sentry-go v0.29.0/go.mod h1:jhPesDAL0Q0W2+2YEuVOvdWmVtdsr1+jtBrlDEVWwLY=
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/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=
@ -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.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI=
github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
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_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.2024082.1 h1:V/3tq3rGK8Fse6xqnVQ8epzzytjXRI93y+jNHen2zMQ=
goauthentik.io/api/v3 v3.2024082.1/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
goauthentik.io/api/v3 v3.2024064.1 h1:vxquklgDGD+nGFhWRAsQ7ezQKg17MRq6bzEk25fbsb4=
goauthentik.io/api/v3 v3.2024064.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.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
golang.org/x/oauth2 v0.22.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=

View File

@ -29,4 +29,4 @@ func UserAgent() string {
return fmt.Sprintf("authentik@%s", FullVersion())
}
const VERSION = "2024.8.2"
const VERSION = "2024.8.0"

View File

@ -35,11 +35,10 @@ 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) {
bfreq = req.Page(page)
cfreq = bfreq.(PaginatorRequest[Treq, Tres]).PageSize(int32(opts.PageSize))
res, _, err := cfreq.(PaginatorRequest[Treq, Tres]).Execute()
req.Page(page)
req.PageSize(int32(opts.PageSize))
res, _, err := req.Execute()
if err != nil {
opts.Logger.WithError(err).WithField("page", page).Warning("failed to fetch page")
}

View File

@ -1,26 +0,0 @@
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,
// })
// }

View File

@ -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.GetHasSearchPermission()
flags.CanSearch = access.HasSearchPermission != nil
db.si.SetFlags(req.BindDN, &flags)
if flags.CanSearch {
req.Log().Debug("Allowed access to search")

View File

@ -65,11 +65,8 @@ type Server interface {
CryptoStore() *ak.CryptoStore
}
func init() {
func NewApplication(p api.ProxyOutpostConfig, c *http.Client, server Server) (*Application, error) {
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)
@ -140,15 +137,7 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, server Server, old
isEmbedded: isEmbedded,
}
go a.authHeaderCache.Start()
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
}
a.sessions = a.getStore(p, externalHost)
mux.Use(web.NewLoggingHandler(muxLogger, func(l *log.Entry, r *http.Request) *log.Entry {
c := a.getClaimsFromSession(r)
if c == nil {
@ -204,17 +193,7 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, server Server, old
})
mux.HandleFunc("/outpost.goauthentik.io/start", func(w http.ResponseWriter, r *http.Request) {
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)
a.handleAuthStart(w, r, "")
})
mux.HandleFunc("/outpost.goauthentik.io/callback", a.handleAuthCallback)
mux.HandleFunc("/outpost.goauthentik.io/sign_out", a.handleSignOut)
@ -246,8 +225,9 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, server Server, old
// 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

View File

@ -15,6 +15,36 @@ 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 {

View File

@ -5,13 +5,10 @@ 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 {
@ -30,44 +27,6 @@ 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 == "" {
@ -80,6 +39,17 @@ 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 {

View File

@ -8,45 +8,25 @@ import (
"goauthentik.io/api/v3"
)
func TestCheckRedirectParam_None(t *testing.T) {
func TestCheckRedirectParam(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)
}
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)
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)
}
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)
req, _ = http.NewRequest("GET", "/outpost.goauthentik.io/auth/start?rd=https://ext.t.goauthentik.io/test?foo", nil)
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)
rd, ok = a.checkRedirectParam(req)
assert.Equal(t, true, ok)
assert.Equal(t, "https://ext.t.goauthentik.io/test?foo", rd)

View File

@ -26,7 +26,7 @@ import (
const RedisKeyPrefix = "authentik_proxy_session_"
func (a *Application) getStore(p api.ProxyOutpostConfig, externalHost *url.URL) (sessions.Store, error) {
func (a *Application) getStore(p api.ProxyOutpostConfig, externalHost *url.URL) sessions.Store {
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 {
return nil, err
a.log.WithError(err).Panic("failed to connect to redis")
}
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, nil
return rs
}
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, nil
return cs
}
func (a *Application) SessionName() string {

View File

@ -66,7 +66,6 @@ func newTestApplication() *Application {
},
http.DefaultClient,
ts,
nil,
)
ts.apps = append(ts.apps, a)
return a

View File

@ -4,7 +4,6 @@ import (
"context"
"fmt"
"net/http"
"net/url"
"github.com/getsentry/sentry-go"
"goauthentik.io/internal/constants"
@ -38,21 +37,16 @@ func (ps *ProxyServer) Refresh() error {
),
),
}
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)
a, err := application.NewApplication(provider, hc, ps)
existing, ok := ps.apps[a.Host]
if ok {
existing.Stop()
}
if err != nil {
ps.log.WithError(err).Warning("failed to setup application")
continue
} else {
apps[a.Host] = a
}
apps[externalHost.Host] = a
}
ps.apps = apps
ps.log.Debug("Swapped maps")

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-08 00:09+0000\n"
"POT-Creation-Date: 2024-08-18 00:08+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,11 +18,6 @@ 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.

View File

@ -19,7 +19,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-08 00:09+0000\n"
"POT-Creation-Date: 2024-08-12 13:45+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,11 +29,6 @@ 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"
@ -1347,6 +1342,14 @@ 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 "
@ -1393,10 +1396,6 @@ 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"
@ -1797,14 +1796,6 @@ 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"
@ -1953,20 +1944,6 @@ 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"
@ -2489,6 +2466,20 @@ 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"

Binary file not shown.

View File

@ -15,7 +15,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-08 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: deluxghost, 2024\n"
"Language-Team: Chinese Simplified (https://app.transifex.com/authentik/teams/119923/zh-Hans/)\n"
@ -25,11 +25,6 @@ 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.

View File

@ -14,7 +14,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-08 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: deluxghost, 2024\n"
"Language-Team: Chinese (China) (https://app.transifex.com/authentik/teams/119923/zh_CN/)\n"
@ -24,11 +24,6 @@ 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 错误"

View File

@ -1,5 +1,5 @@
{
"name": "@goauthentik/authentik",
"version": "2024.8.2",
"version": "2024.8.0",
"private": true
}

466
poetry.lock generated
View File

@ -366,13 +366,13 @@ typing-extensions = ">=4.0.0"
[[package]]
name = "bandit"
version = "1.7.10"
version = "1.7.9"
description = "Security oriented static analyser for python code."
optional = false
python-versions = ">=3.8"
files = [
{file = "bandit-1.7.10-py3-none-any.whl", hash = "sha256:665721d7bebbb4485a339c55161ac0eedde27d51e638000d91c8c2d68343ad02"},
{file = "bandit-1.7.10.tar.gz", hash = "sha256:59ed5caf5d92b6ada4bf65bc6437feea4a9da1093384445fed4d472acc6cff7b"},
{file = "bandit-1.7.9-py3-none-any.whl", hash = "sha256:52077cb339000f337fb25f7e045995c4ad01511e716e5daac37014b9752de8ec"},
{file = "bandit-1.7.9.tar.gz", hash = "sha256:7c395a436743018f7be0a4cbb0a4ea9b902b6d87264ddecf8cfdc73b4f78ff61"},
]
[package.dependencies]
@ -1053,38 +1053,38 @@ toml = ["tomli"]
[[package]]
name = "cryptography"
version = "43.0.1"
version = "43.0.0"
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.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"},
{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"},
]
[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.1)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"]
test = ["certifi", "cryptography-vectors (==43.0.0)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"]
test-randomorder = ["pytest-randomly"]
[[package]]
@ -1207,13 +1207,13 @@ dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"]
[[package]]
name = "django"
version = "5.0.9"
version = "5.0.8"
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.9-py3-none-any.whl", hash = "sha256:f219576ba53be4e83f485130a7283f0efde06a9f2e3a7c3c5180327549f078fa"},
{file = "Django-5.0.9.tar.gz", hash = "sha256:6333870d342329b60174da3a60dbd302e533f3b0bb0971516750e974a99b5a39"},
{file = "Django-5.0.8-py3-none-any.whl", hash = "sha256:333a7988f7ca4bc14d360d3d8f6b793704517761ae3813b95432043daec22a45"},
{file = "Django-5.0.8.tar.gz", hash = "sha256:ebe859c9da6fead9c9ee6dbfa4943b04f41342f4cea2c4d8c978ef0d10694f2b"},
]
[package.dependencies]
@ -1287,13 +1287,13 @@ Django = ">=2.2"
[[package]]
name = "django-model-utils"
version = "5.0.0"
version = "4.5.1"
description = "Django model mixins and utilities"
optional = false
python-versions = ">=3.8"
files = [
{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"},
{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"},
]
[package.dependencies]
@ -1315,13 +1315,13 @@ django = ">=3"
[[package]]
name = "django-pglock"
version = "1.6.2"
version = "1.6.0"
description = "Postgres locking routines and lock table access."
optional = false
python-versions = "<4,>=3.8.0"
files = [
{file = "django_pglock-1.6.2-py3-none-any.whl", hash = "sha256:abdb92531bf8cb36471dc9eb33ed163b06bd3323140d132ed4f308b9e5505f50"},
{file = "django_pglock-1.6.2.tar.gz", hash = "sha256:05db998cab21556d4a307eac4b5db8e50f874f42b1a581560b3c54610fee6a1b"},
{file = "django_pglock-1.6.0-py3-none-any.whl", hash = "sha256:41c98d0bd3738d11e6eaefcc3e5146028f118a593ac58c13d663b751170f01de"},
{file = "django_pglock-1.6.0.tar.gz", hash = "sha256:724450ecc9886f39af599c477d84ad086545a5373215ef7a670cd25faca25a61"},
]
[package.dependencies]
@ -1566,16 +1566,6 @@ 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"
@ -1771,13 +1761,13 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"]
[[package]]
name = "google-api-python-client"
version = "2.146.0"
version = "2.143.0"
description = "Google API Client Library for Python"
optional = false
python-versions = ">=3.7"
files = [
{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"},
{file = "google_api_python_client-2.143.0-py2.py3-none-any.whl", hash = "sha256:d5654134522b9b574b82234e96f7e0aeeabcbf33643fbabcd449ef0068e3a476"},
{file = "google_api_python_client-2.143.0.tar.gz", hash = "sha256:6a75441f9078e6e2fcdf4946a153fda1e2cc81b5e9c8d6e8c0750c85c7f8a566"},
]
[package.dependencies]
@ -2253,18 +2243,17 @@ zookeeper = ["kazoo (>=2.8.0)"]
[[package]]
name = "kubernetes"
version = "31.0.0"
version = "30.1.0"
description = "Kubernetes python client"
optional = false
python-versions = ">=3.6"
files = [
{file = "kubernetes-31.0.0-py2.py3-none-any.whl", hash = "sha256:bf141e2d380c8520eada8b351f4e319ffee9636328c137aa432bc486ca1200e1"},
{file = "kubernetes-31.0.0.tar.gz", hash = "sha256:28945de906c8c259c1ebe62703b56a03b714049372196f854105afe4e6d014c0"},
{file = "kubernetes-30.1.0-py2.py3-none-any.whl", hash = "sha256:e212e8b7579031dd2e512168b617373bc1e03888d41ac4e04039240a292d478d"},
{file = "kubernetes-30.1.0.tar.gz", hash = "sha256:41e4c77af9f28e7a6c314e3bd06a8c6229ddd787cad684e0ab9f69b498e98ebc"},
]
[package.dependencies]
certifi = ">=14.05.14"
durationpy = ">=0.7"
google-auth = ">=1.0.1"
oauthlib = ">=3.2.2"
python-dateutil = ">=2.5.3"
@ -2859,13 +2848,13 @@ dev = ["bumpver", "isort", "mypy", "pylint", "pytest", "yapf"]
[[package]]
name = "msgraph-sdk"
version = "1.8.0"
version = "1.5.4"
description = "The Microsoft Graph Python SDK"
optional = false
python-versions = ">=3.8"
files = [
{file = "msgraph_sdk-1.8.0-py3-none-any.whl", hash = "sha256:22a8e4a63f989865228f66a54501bef8105909c7156fe0a079ca9b5296339cc2"},
{file = "msgraph_sdk-1.8.0.tar.gz", hash = "sha256:1ac84bd47ea288a84f46f6c6d0c89d164ee3453b917615632652344538098314"},
{file = "msgraph_sdk-1.5.4-py3-none-any.whl", hash = "sha256:9ea349f30cc4a03edb587e26554c7a4839a38c2ef30d4b5396882fd2be82dcac"},
{file = "msgraph_sdk-1.5.4.tar.gz", hash = "sha256:b0e146328d136d1db175938d8fc901f3bb32acf3ea6fe93c0dc7c5a0abc45e39"},
]
[package.dependencies]
@ -3172,13 +3161,13 @@ files = [
[[package]]
name = "paramiko"
version = "3.5.0"
version = "3.4.1"
description = "SSH2 protocol library"
optional = false
python-versions = ">=3.6"
files = [
{file = "paramiko-3.5.0-py3-none-any.whl", hash = "sha256:1fedf06b085359051cd7d0d270cebe19e755a8a921cc2ddbfa647fb0cd7d68f9"},
{file = "paramiko-3.5.0.tar.gz", hash = "sha256:ad11e540da4f55cedda52931f1a3f812a8238a7af7f62a60de538cd80bb28124"},
{file = "paramiko-3.4.1-py3-none-any.whl", hash = "sha256:8e49fd2f82f84acf7ffd57c64311aa2b30e575370dc23bdb375b10262f7eac32"},
{file = "paramiko-3.4.1.tar.gz", hash = "sha256:8b15302870af7f6652f2e038975c1d2973f06046cb5d7d65355668b3ecbece0c"},
]
[package.dependencies]
@ -3215,13 +3204,13 @@ files = [
[[package]]
name = "pdoc"
version = "14.7.0"
version = "14.6.1"
description = "API Documentation for Python Projects"
optional = false
python-versions = ">=3.8"
files = [
{file = "pdoc-14.7.0-py3-none-any.whl", hash = "sha256:72377a907efc6b2c5b3c56b717ef34f11d93621dced3b663f3aede0b844c0ad2"},
{file = "pdoc-14.7.0.tar.gz", hash = "sha256:2d28af9c0acc39180744ad0543e4bbc3223ecba0d1302db315ec521c51f71f93"},
{file = "pdoc-14.6.1-py3-none-any.whl", hash = "sha256:efbed433655264392c60551615a3d42b8f21e492373419756d20234c667b54bc"},
{file = "pdoc-14.6.1.tar.gz", hash = "sha256:ee598f30d5c55dd4702086dabc412a26022acc35aa88aa382cda8ac655fead98"},
]
[package.dependencies]
@ -3448,36 +3437,36 @@ files = [
[[package]]
name = "psycopg"
version = "3.2.2"
version = "3.2.1"
description = "PostgreSQL database adapter for Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "psycopg-3.2.2-py3-none-any.whl", hash = "sha256:babf565d459d8f72fb65da5e211dd0b58a52c51e4e1fa9cadecff42d6b7619b2"},
{file = "psycopg-3.2.2.tar.gz", hash = "sha256:8bad2e497ce22d556dac1464738cb948f8d6bab450d965cf1d8a8effd52412e0"},
{file = "psycopg-3.2.1-py3-none-any.whl", hash = "sha256:ece385fb413a37db332f97c49208b36cf030ff02b199d7635ed2fbd378724175"},
{file = "psycopg-3.2.1.tar.gz", hash = "sha256:dc8da6dc8729dacacda3cc2f17d2c9397a70a66cf0d2b69c91065d60d5f00cb7"},
]
[package.dependencies]
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\""}
psycopg-c = {version = "3.2.1", optional = true, markers = "implementation_name != \"pypy\" and extra == \"c\""}
typing-extensions = ">=4.4"
tzdata = {version = "*", markers = "sys_platform == \"win32\""}
[package.extras]
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)"]
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)"]
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.11)", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"]
test = ["anyio (>=4.0)", "mypy (>=1.6)", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"]
[[package]]
name = "psycopg-c"
version = "3.2.2"
version = "3.2.1"
description = "PostgreSQL database adapter for Python -- C optimisation distribution"
optional = false
python-versions = ">=3.8"
files = [
{file = "psycopg_c-3.2.2.tar.gz", hash = "sha256:de8cac75bc6640ef0f54ad9187b81e07c430206a83c566b73d4cca41ecccb7c8"},
{file = "psycopg_c-3.2.1.tar.gz", hash = "sha256:2d09943cc8a855c42c1e23b4298957b7ce8f27bf3683258c52fd139f601f7cda"},
]
[[package]]
@ -3518,121 +3507,120 @@ files = [
[[package]]
name = "pydantic"
version = "2.9.2"
version = "2.8.2"
description = "Data validation using Python type hints"
optional = false
python-versions = ">=3.8"
files = [
{file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"},
{file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"},
{file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"},
{file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"},
]
[package.dependencies]
annotated-types = ">=0.6.0"
annotated-types = ">=0.4.0"
email-validator = {version = ">=2.0.0", optional = true, markers = "extra == \"email\""}
pydantic-core = "2.23.4"
pydantic-core = "2.20.1"
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.23.4"
version = "2.20.1"
description = "Core functionality for Pydantic validation and serialization"
optional = false
python-versions = ">=3.8"
files = [
{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"},
{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"},
]
[package.dependencies]
@ -3776,13 +3764,13 @@ files = [
[[package]]
name = "pytest"
version = "8.3.3"
version = "8.3.2"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"},
{file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"},
{file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"},
{file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"},
]
[package.dependencies]
@ -4210,29 +4198,29 @@ pyasn1 = ">=0.1.3"
[[package]]
name = "ruff"
version = "0.6.7"
version = "0.6.3"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
{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"},
{file = "ruff-0.6.3-py3-none-linux_armv6l.whl", hash = "sha256:97f58fda4e309382ad30ede7f30e2791d70dd29ea17f41970119f55bdb7a45c3"},
{file = "ruff-0.6.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3b061e49b5cf3a297b4d1c27ac5587954ccb4ff601160d3d6b2f70b1622194dc"},
{file = "ruff-0.6.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:34e2824a13bb8c668c71c1760a6ac7d795ccbd8d38ff4a0d8471fdb15de910b1"},
{file = "ruff-0.6.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bddfbb8d63c460f4b4128b6a506e7052bad4d6f3ff607ebbb41b0aa19c2770d1"},
{file = "ruff-0.6.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ced3eeb44df75353e08ab3b6a9e113b5f3f996bea48d4f7c027bc528ba87b672"},
{file = "ruff-0.6.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47021dff5445d549be954eb275156dfd7c37222acc1e8014311badcb9b4ec8c1"},
{file = "ruff-0.6.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7d7bd20dc07cebd68cc8bc7b3f5ada6d637f42d947c85264f94b0d1cd9d87384"},
{file = "ruff-0.6.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:500f166d03fc6d0e61c8e40a3ff853fa8a43d938f5d14c183c612df1b0d6c58a"},
{file = "ruff-0.6.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:42844ff678f9b976366b262fa2d1d1a3fe76f6e145bd92c84e27d172e3c34500"},
{file = "ruff-0.6.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70452a10eb2d66549de8e75f89ae82462159855e983ddff91bc0bce6511d0470"},
{file = "ruff-0.6.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:65a533235ed55f767d1fc62193a21cbf9e3329cf26d427b800fdeacfb77d296f"},
{file = "ruff-0.6.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d2e2c23cef30dc3cbe9cc5d04f2899e7f5e478c40d2e0a633513ad081f7361b5"},
{file = "ruff-0.6.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d8a136aa7d228975a6aee3dd8bea9b28e2b43e9444aa678fb62aeb1956ff2351"},
{file = "ruff-0.6.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f92fe93bc72e262b7b3f2bba9879897e2d58a989b4714ba6a5a7273e842ad2f8"},
{file = "ruff-0.6.3-py3-none-win32.whl", hash = "sha256:7a62d3b5b0d7f9143d94893f8ba43aa5a5c51a0ffc4a401aa97a81ed76930521"},
{file = "ruff-0.6.3-py3-none-win_amd64.whl", hash = "sha256:746af39356fee2b89aada06c7376e1aa274a23493d7016059c3a72e3b296befb"},
{file = "ruff-0.6.3-py3-none-win_arm64.whl", hash = "sha256:14a9528a8b70ccc7a847637c29e56fd1f9183a9db743bbc5b8e0c4ad60592a82"},
{file = "ruff-0.6.3.tar.gz", hash = "sha256:183b99e9edd1ef63be34a3b51fee0a9f4ab95add123dbf89a71f7b1f0c991983"},
]
[[package]]
@ -4271,13 +4259,13 @@ django-query = ["django (>=3.2)"]
[[package]]
name = "selenium"
version = "4.25.0"
version = "4.24.0"
description = "Official Python bindings for Selenium WebDriver"
optional = false
python-versions = ">=3.8"
files = [
{file = "selenium-4.25.0-py3-none-any.whl", hash = "sha256:3798d2d12b4a570bc5790163ba57fef10b2afee958bf1d80f2a3cf07c4141f33"},
{file = "selenium-4.25.0.tar.gz", hash = "sha256:95d08d3b82fb353f3c474895154516604c7f0e6a9a565ae6498ef36c9bac6921"},
{file = "selenium-4.24.0-py3-none-any.whl", hash = "sha256:42c23f60753d5415b261b236cecbd69bd4eb5271e1563915f546b443cb6b71c6"},
{file = "selenium-4.24.0.tar.gz", hash = "sha256:88281e5b5b90fe231868905d5ea745b9ee5e30db280b33498cc73fb0fa06d571"},
]
[package.dependencies]
@ -4290,13 +4278,13 @@ websocket-client = ">=1.8,<2.0"
[[package]]
name = "sentry-sdk"
version = "2.14.0"
version = "2.13.0"
description = "Python client for Sentry (https://sentry.io)"
optional = false
python-versions = ">=3.6"
files = [
{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"},
{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"},
]
[package.dependencies]
@ -4667,13 +4655,13 @@ wsproto = ">=0.14"
[[package]]
name = "twilio"
version = "9.3.1"
version = "9.2.4"
description = "Twilio API client and TwiML generator"
optional = false
python-versions = ">=3.7.0"
files = [
{file = "twilio-9.3.1-py2.py3-none-any.whl", hash = "sha256:48c714e5a1340a3d9001d6c9ccd107434c340ff94a5bc1bfb2473d3dc33f5051"},
{file = "twilio-9.3.1.tar.gz", hash = "sha256:bd754371353855438a8cf0c38f54580d474afe9655af2d81edb6dbabd8d785c4"},
{file = "twilio-9.2.4-py2.py3-none-any.whl", hash = "sha256:490da2518c0da370d738d436f9086b2463902707a811cd306ec8dcc8ce831758"},
{file = "twilio-9.2.4.tar.gz", hash = "sha256:454b7d075c6bee3b64c81c39151be1f9105c695df6dbb0021b0c43e2930263e7"},
]
[package.dependencies]
@ -4782,13 +4770,13 @@ files = [
[[package]]
name = "urllib3"
version = "2.2.3"
version = "2.2.2"
description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = false
python-versions = ">=3.8"
files = [
{file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"},
{file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"},
{file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"},
{file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"},
]
[package.dependencies]
@ -4882,41 +4870,41 @@ files = [
[[package]]
name = "watchdog"
version = "5.0.2"
version = "5.0.1"
description = "Filesystem events monitoring"
optional = false
python-versions = ">=3.9"
files = [
{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"},
{file = "watchdog-5.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a6b8c6c82ada78479a0df568d27d69aa07105aba9301ac66d1ae162645f4ba34"},
{file = "watchdog-5.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1e8ca9b7f5f03d2f0556a43db1e9adf1e5af6adf52e0890f781324514b67a612"},
{file = "watchdog-5.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c92812a358eabebe92b12b9290d16dc95c8003654658f6b2676c9a2103a73ceb"},
{file = "watchdog-5.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a03a6ccb846ead406a25a0b702d0a6b88fdfa77becaf907cfcfce7737ebbda1f"},
{file = "watchdog-5.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39f0de161a822402f0f00c68b82349a4d71c9814e749148ca2b083a25606dbf9"},
{file = "watchdog-5.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5541a8765c4090decb4dba55d3dceb57724748a717ceaba8dc4f213edb0026e0"},
{file = "watchdog-5.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e321f1561adea30e447130882efe451af519646178d04189d6ba91a8cd7d88a5"},
{file = "watchdog-5.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c4ae0b3e95455fa9d959aa3b253c87845ad454ef188a4bf5a69cab287c131216"},
{file = "watchdog-5.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b2d56425dfa0c1e6f8a510f21d3d54ef7fe50bbc29638943c2cb1394b7b49156"},
{file = "watchdog-5.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:70e30116849f4ec52240eb1fad83d27e525eae179bfe1c09b3bf120163d731b6"},
{file = "watchdog-5.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f66df2c152edf5a2fe472bb2f8a5d562165bcf6cf9686cee5d75e524c21ca895"},
{file = "watchdog-5.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6bb68d9adb9c45f0dc1c2b12f4fb6eab0463a8f9741e371e4ede6769064e0785"},
{file = "watchdog-5.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6fbb4dd5ace074a2969825fde10034b35b31efcb6973defb22eb945b1d3acc37"},
{file = "watchdog-5.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:753c6a4c1eea9d3b96cd58159b49103e66cb288216a414ab9ad234ccc7642ec2"},
{file = "watchdog-5.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:20a28c8b0b3edf4ea2b27fb3527fc0a348e983f22a4317d316bb561524391932"},
{file = "watchdog-5.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a1cd7c919940b15f253db8279a579fb81e4e4e434b39b11a1cb7f54fe3fa46a6"},
{file = "watchdog-5.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:a791dfc050ed24b82f7f100ae794192594fe863a7e9bdafcdfa5c6e405a981e5"},
{file = "watchdog-5.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8ba1472b5fa7c644e49641f70d7ccc567f70b54d776defa5d6f755dc2edc3fbb"},
{file = "watchdog-5.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b21e6601efe8453514c2fc21aca57fb5413c3d8b157bfe520b05b57b1788a167"},
{file = "watchdog-5.0.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:763c6f82bb65504b47d4aea268462b2fb662676676356e04787f332a11f03eb0"},
{file = "watchdog-5.0.1-py3-none-manylinux2014_armv7l.whl", hash = "sha256:664917cd513538728875a42d5654584b533da88cf06680452c98e73b45466968"},
{file = "watchdog-5.0.1-py3-none-manylinux2014_i686.whl", hash = "sha256:39e828c4270452b966bc9d814911a3c7e24c62d726d2a3245f5841664ff56b5e"},
{file = "watchdog-5.0.1-py3-none-manylinux2014_ppc64.whl", hash = "sha256:59ec6111f3750772badae3403ef17263489ed6f27ac01ec50c0244b2afa258fb"},
{file = "watchdog-5.0.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:f3006361dba2005552cc8aa49c44d16a10e0a1939bb3286e888a14f722122808"},
{file = "watchdog-5.0.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:72dbdffe4aa0c36c59f4a5190bceeb7fdfdf849ab98a562b3a783a64cc6dacdd"},
{file = "watchdog-5.0.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c93aa24899cb4e8a51492c7ccc420bea45ced502fe9ef2e83f9ab1107e5a13b5"},
{file = "watchdog-5.0.1-py3-none-win32.whl", hash = "sha256:2b8cd627b76194e725ed6f48d9524b1ad93a51a0dc3bd0225c56023716245091"},
{file = "watchdog-5.0.1-py3-none-win_amd64.whl", hash = "sha256:4eaebff2f938f5325788cef26521891b2d8ecc8e7852aa123a9b458815f93875"},
{file = "watchdog-5.0.1-py3-none-win_ia64.whl", hash = "sha256:9b1b32f89f95162f09aea6e15d9384f6e0490152f10d7ed241f8a85cddc50658"},
{file = "watchdog-5.0.1.tar.gz", hash = "sha256:f0180e84e6493ef7c82e051334e8c9b00ffd89fa9de5e0613d3c267f6ccf2d38"},
]
[package.extras]

View File

@ -1,6 +1,6 @@
[tool.poetry]
name = "authentik"
version = "2024.8.2"
version = "2024.8.0"
description = ""
authors = ["authentik Team <hello@goauthentik.io>"]

View File

@ -1,7 +1,7 @@
openapi: 3.0.3
info:
title: authentik
version: 2024.8.2
version: 2024.8.0
description: Making authentication simple.
contact:
email: hello@goauthentik.io
@ -290,64 +290,6 @@ 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
@ -11560,7 +11502,6 @@ paths:
type: string
nullable: true
enum:
- analytics_sent
- authorize_application
- configuration_error
- custom_
@ -35817,25 +35758,6 @@ 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
@ -35851,7 +35773,6 @@ components:
enum:
- authentik.tenants
- authentik.admin
- authentik.analytics
- authentik.api
- authentik.crypto
- authentik.flows
@ -39039,7 +38960,6 @@ components:
- model_updated
- model_deleted
- email_sent
- analytics_sent
- update_available
- custom_
type: string
@ -47441,13 +47361,6 @@ 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
@ -51088,23 +51001,6 @@ 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
@ -51145,13 +51041,6 @@ 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

View File

@ -11,7 +11,6 @@ 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
@ -332,83 +331,6 @@ 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))

4
tests/wdio/.eslintignore Normal file
View File

@ -0,0 +1,4 @@
# don't ever lint node_modules
node_modules
# don't lint nyc coverage output
coverage

29
tests/wdio/.eslintrc.json Normal file
View File

@ -0,0 +1,29 @@
{
"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": "^_"
}
]
}
}

View File

@ -0,0 +1,26 @@
{
"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"
}
}

View File

@ -1,84 +0,0 @@
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: "^_",
},
],
},
},
];

File diff suppressed because it is too large Load Diff

View File

@ -1,28 +1,26 @@
{
"name": "@goauthentik/web-tests",
"dependencies": {
"chromedriver": "^129.0.0",
"chromedriver": "^128.0.1",
"lockfile-lint": "^4.14.0",
"syncpack": "^13.0.0"
},
"devDependencies": {
"@eslint/js": "^9.11.1",
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/mocha": "^10.0.8",
"@typescript-eslint/eslint-plugin": "^8.7.0",
"@typescript-eslint/parser": "^8.7.0",
"@types/mocha": "^10.0.7",
"@typescript-eslint/eslint-plugin": "^7.17.0",
"@typescript-eslint/parser": "^7.17.0",
"@wdio/cli": "^9.0.3",
"@wdio/local-runner": "^9.0.1",
"@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",
"@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",
"npm-run-all": "^4.1.5",
"prettier": "^3.3.3",
"typescript-eslint": "^8.7.0",
"typescript": "^5.6.2",
"ts-node": "^10.9.2",
"typescript": "^5.5.4",
"wdio-wait-for": "^3.0.11"
},
"engines": {

View File

@ -1,7 +1,7 @@
import Page from "../page.js";
export class LdapForm extends Page {
async setBindFlow() {
async setBindFlow(_selector: string) {
await this.searchSelect(
'ak-search-select-view[name="authorizationFlow"]',
"authorizationFlow",

View File

@ -1,11 +1,19 @@
export const config: WebdriverIO.Config = {
import type { Options } from "@wdio/types";
export const config: Options.Testrunner = {
//
// ====================
// Runner Configuration
// ====================
// WebdriverIO supports running e2e tests as well as unit and component tests.
runner: "local",
tsConfigPath: "./tsconfig.json",
autoCompileOpts: {
autoCompile: true,
tsNodeOpts: {
project: "./tsconfig.json",
transpileOnly: true,
},
},
//
// ==================

View File

@ -0,0 +1,30 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:lit/recommended",
"plugin:custom-elements/recommended",
"plugin:storybook/recommended",
"plugin:sonarjs/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": ["@typescript-eslint", "lit", "custom-elements", "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": ["warn", 9],
"sonarjs/no-duplicate-string": "off",
"sonarjs/no-nested-template-literals": "off"
}
}

View File

@ -41,6 +41,7 @@ const definitions = {
const otherFiles = [
["node_modules/@patternfly/patternfly/patternfly.min.css", "."],
["node_modules/@patternfly/patternfly/patternfly-base.css", "."],
["node_modules/@patternfly/patternfly/assets/**", ".", "node_modules/@patternfly/patternfly/"],
["src/custom.css", "."],
["src/common/styles/**", "."],
@ -79,6 +80,12 @@ const interfaces = [
["polyfill/poly.ts", "."],
];
const extraTargets = [
["sdk/index.ts", "sdk", { entryNames: "[dir]/[name]" }],
["sdk/user-settings.ts", "sdk/user-settings", { entryNames: "[dir]/[name]" }],
["sdk/flow.ts", "sdk/flow", { entryNames: "[dir]/[name]" }],
];
const baseArgs = {
bundle: true,
write: true,
@ -101,7 +108,11 @@ function getVersion() {
return version;
}
async function buildOneSource(source, dest) {
function getAllTargets() {
return [...interfaces, ...extraTargets];
}
async function buildSingleTarget(source, dest, options) {
const DIST = path.join(__dirname, "./dist", dest);
console.log(`[${new Date(Date.now()).toISOString()}] Starting build for target ${source}`);
@ -112,6 +123,7 @@ async function buildOneSource(source, dest) {
entryPoints: [`./src/${source}`],
entryNames: `[dir]/[name]-${getVersion()}`,
outdir: DIST,
...options,
});
const end = Date.now();
console.log(
@ -124,8 +136,10 @@ async function buildOneSource(source, dest) {
}
}
async function buildAuthentik(interfaces) {
await Promise.allSettled(interfaces.map(([source, dest]) => buildOneSource(source, dest)));
async function buildTargets(targets) {
await Promise.allSettled(
targets.map(([source, dest, options]) => buildSingleTarget(source, dest, options)),
);
}
let timeoutId = null;
@ -135,7 +149,7 @@ function debouncedBuild() {
}
timeoutId = setTimeout(() => {
console.clear();
buildAuthentik(interfaces);
buildTargets(getAllTargets());
}, 250);
}
@ -143,7 +157,7 @@ if (process.argv.length > 2 && (process.argv[2] === "-h" || process.argv[2] ===
console.log(`Build the authentikUI
options:
-w, --watch: Build all ${interfaces.length} interfaces
-w, --watch: Build all ${getAllTargets().length} interfaces
-p, --proxy: Build only the polyfills and the loading application
-h, --help: This help message
`);
@ -163,11 +177,11 @@ if (process.argv.length > 2 && (process.argv[2] === "-w" || process.argv[2] ===
});
} else if (process.argv.length > 2 && (process.argv[2] === "-p" || process.argv[2] === "--proxy")) {
// There's no watch-for-proxy, sorry.
await buildAuthentik(
await buildTargets(
interfaces.filter(([_, dest]) => ["standalone/loading", "."].includes(dest)),
);
process.exit(0);
} else {
// And the fallback: just build it.
await buildAuthentik(interfaces);
await buildTargets(interfaces);
}

15543
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -8,10 +8,10 @@
"@codemirror/lang-xml": "^6.1.0",
"@codemirror/legacy-modes": "^6.4.1",
"@codemirror/theme-one-dark": "^6.1.2",
"@floating-ui/dom": "^1.6.11",
"@floating-ui/dom": "^1.6.9",
"@formatjs/intl-listformat": "^7.5.7",
"@fortawesome/fontawesome-free": "^6.6.0",
"@goauthentik/api": "^2024.8.2-1726491792",
"@goauthentik/api": "^2024.8.0-1725367323",
"@lit/context": "^1.1.2",
"@lit/localize": "^0.12.2",
"@lit/reactive-element": "^2.0.4",
@ -19,7 +19,7 @@
"@open-wc/lit-helpers": "^0.7.0",
"@patternfly/elements": "^4.0.1",
"@patternfly/patternfly": "^4.224.2",
"@sentry/browser": "^8.31.0",
"@sentry/browser": "^8.27.0",
"@webcomponents/webcomponentsjs": "^2.8.0",
"base64-js": "^1.5.1",
"chart.js": "^4.4.4",
@ -32,13 +32,13 @@
"guacamole-common-js": "^1.5.0",
"lit": "^3.2.0",
"md-front-matter": "^1.0.4",
"mermaid": "^11.2.1",
"rapidoc": "^9.3.6",
"mermaid": "^11.1.0",
"rapidoc": "^9.3.4",
"showdown": "^2.1.0",
"style-mod": "^4.1.2",
"ts-pattern": "^5.3.1",
"webcomponent-qr-code": "^1.2.0",
"yaml": "^2.5.1"
"yaml": "^2.5.0"
},
"devDependencies": {
"@babel/core": "^7.25.2",
@ -49,50 +49,51 @@
"@babel/plugin-transform-runtime": "^7.25.4",
"@babel/preset-env": "^7.25.4",
"@babel/preset-typescript": "^7.24.7",
"@changesets/cli": "^2.27.8",
"@changesets/cli": "^2.27.5",
"@custom-elements-manifest/analyzer": "^0.10.2",
"@eslint/js": "^9.11.1",
"@eslint/js": "^9.9.1",
"@genesiscommunitysuccess/custom-elements-lsp": "^5.0.3",
"@hcaptcha/types": "^1.0.4",
"@jeysal/storybook-addon-css-user-preferences": "^0.2.0",
"@lit/localize-tools": "^0.8.0",
"@rollup/plugin-replace": "^6.0.1",
"@spotlightjs/spotlight": "^2.4.1",
"@storybook/addon-essentials": "^8.3.3",
"@storybook/addon-links": "^8.3.3",
"@rollup/plugin-replace": "^5.0.7",
"@spotlightjs/spotlight": "^2.3.2",
"@storybook/addon-essentials": "^8.2.9",
"@storybook/addon-links": "^8.2.9",
"@storybook/api": "^7.6.17",
"@storybook/blocks": "^8.0.8",
"@storybook/manager-api": "^8.3.3",
"@storybook/web-components": "^8.3.3",
"@storybook/web-components-vite": "^8.3.3",
"@storybook/manager-api": "^8.2.9",
"@storybook/web-components": "^8.2.9",
"@storybook/web-components-vite": "^8.2.9",
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/chart.js": "^2.9.41",
"@types/codemirror": "5.60.15",
"@types/eslint__js": "^8.42.3",
"@types/grecaptcha": "^3.0.9",
"@types/guacamole-common-js": "1.5.2",
"@types/node": "^22.6.1",
"@types/node": "^22.5.0",
"@types/showdown": "^2.0.6",
"@typescript-eslint/eslint-plugin": "^8.7.0",
"@typescript-eslint/parser": "^8.7.0",
"@wdio/browser-runner": "^9.0.9",
"@wdio/cli": "^9.0.9",
"@wdio/mocha-framework": "^9.0.8",
"@wdio/spec-reporter": "^9.0.8",
"@typescript-eslint/eslint-plugin": "^8.0.1",
"@typescript-eslint/parser": "^8.0.1",
"@wdio/browser-runner": "^8.40.2",
"@wdio/cli": "^8.40.2",
"@wdio/mocha-framework": "^8.40.2",
"@wdio/spec-reporter": "^8.36.1",
"babel-plugin-macros": "^3.1.0",
"babel-plugin-tsconfig-paths": "^1.0.3",
"chokidar": "^4.0.1",
"chokidar": "^3.6.0",
"cross-env": "^7.0.3",
"esbuild": "^0.24.0",
"eslint": "^9.11.1",
"esbuild": "^0.23.1",
"eslint": "^9.8.0",
"eslint-plugin-lit": "^1.14.0",
"eslint-plugin-sonarjs": "^2.0.2",
"eslint-plugin-sonarjs": "^1.0.4",
"eslint-plugin-wc": "^2.1.0",
"github-slugger": "^2.0.0",
"glob": "^11.0.0",
"globals": "^15.9.0",
"knip": "^5.30.5",
"knip": "^5.27.4",
"lit-analyzer": "^2.0.3",
"lockfile-lint": "^4.14.0",
"npm-run-all": "^4.1.5",
"prettier": "^3.3.3",
"pseudolocale": "^2.1.0",
@ -107,23 +108,23 @@
"ts-node": "^10.9.2",
"tslib": "^2.7.0",
"turnstile-types": "^1.2.2",
"typescript": "^5.6.2",
"typescript-eslint": "^8.7.0",
"typescript": "^5.5.4",
"typescript-eslint": "^8.4.0",
"vite-tsconfig-paths": "^5.0.1",
"wdio-wait-for": "^3.0.11",
"wireit": "^0.14.9"
"wireit": "^0.14.8"
},
"engines": {
"node": ">=20"
},
"license": "MIT",
"optionalDependencies": {
"@esbuild/darwin-arm64": "^0.24.0",
"@esbuild/darwin-arm64": "^0.23.0",
"@esbuild/linux-amd64": "^0.18.11",
"@esbuild/linux-arm64": "^0.24.0",
"@rollup/rollup-darwin-arm64": "4.22.4",
"@rollup/rollup-linux-arm64-gnu": "4.22.4",
"@rollup/rollup-linux-x64-gnu": "4.22.4"
"@esbuild/linux-arm64": "^0.23.0",
"@rollup/rollup-darwin-arm64": "4.21.2",
"@rollup/rollup-linux-arm64-gnu": "4.21.2",
"@rollup/rollup-linux-x64-gnu": "4.21.2"
},
"private": true,
"scripts": {
@ -261,9 +262,7 @@
]
},
"lint:lockfile": {
"__comment": "The lockfile-lint package does not have an option to ensure resolved hashes are set everywhere",
"shell": true,
"command": "[ -z \"$(jq -r '.packages | to_entries[] | select((.key | startswith(\"node_modules\")) and (.value | has(\"resolved\") | not)) | .key' < package-lock.json)\" ]"
"command": "lockfile-lint --path package.json --type npm --allowed-hosts npm --validate-https"
},
"lint:lockfiles": {
"dependencies": [

View File

@ -10,22 +10,22 @@
"weakmap-polyfill": "^2.0.4"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^28.0.0",
"@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-swc": "^0.4.0",
"@rollup/plugin-commonjs": "^26.0.1",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-swc": "^0.3.1",
"@swc/cli": "^0.4.0",
"@swc/core": "^1.7.26",
"@swc/core": "^1.7.23",
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/jquery": "^3.5.30",
"lockfile-lint": "^4.14.0",
"prettier": "^3.3.2",
"rollup": "^4.22.4",
"rollup": "^4.21.2",
"rollup-plugin-copy": "^3.5.0",
"wireit": "^0.14.9"
"wireit": "^0.14.8"
},
"license": "MIT",
"optionalDependencies": {
"@swc/core": "^1.7.26",
"@swc/core": "^1.7.23",
"@swc/core-darwin-arm64": "^1.6.13",
"@swc/core-darwin-x64": "^1.6.13",
"@swc/core-linux-arm-gnueabihf": "^1.6.13",

2637
web/sfe/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,28 +0,0 @@
{
"name": "@goauthentik/web-sfe",
"version": "0.0.0",
"private": true,
"license": "MIT",
"dependencies": {
"@goauthentik/api": "^2024.6.3-1724414734",
"base64-js": "^1.5.1",
"bootstrap": "^4.6.1",
"formdata-polyfill": "^4.0.10",
"jquery": "^3.7.1",
"weakmap-polyfill": "^2.0.4"
},
"scripts": {
"build": "rollup -c rollup.config.js --bundleConfigAsCjs",
"watch": "rollup -w -c rollup.config.js --bundleConfigAsCjs"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^28.0.0",
"@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-swc": "^0.4.0",
"@swc/cli": "^0.4.0",
"@swc/core": "^1.7.26",
"@types/jquery": "^3.5.30",
"rollup": "^4.22.4",
"rollup-plugin-copy": "^3.5.0"
}
}

View File

@ -5,8 +5,6 @@ import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input";
import "@goauthentik/elements/CodeMirror";
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
import { DataProvision, DualSelectPair } from "@goauthentik/elements/ak-dual-select/types";
import { Form } from "@goauthentik/elements/forms/Form";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
@ -21,17 +19,7 @@ import { ifDefined } from "lit/directives/if-defined.js";
import PFList from "@patternfly/patternfly/components/List/list.css";
import {
AdminApi,
AnalyticsApi,
AnalyticsDescription,
Settings,
SettingsRequest,
} from "@goauthentik/api";
function analyticsSourceToPair(analyticsSource: AnalyticsDescription): DualSelectPair {
return [analyticsSource.label, analyticsSource.desc];
}
import { AdminApi, Settings, SettingsRequest } from "@goauthentik/api";
@customElement("ak-admin-settings-form")
export class AdminSettingsForm extends Form<SettingsRequest> {
@ -223,42 +211,6 @@ export class AdminSettingsForm extends Form<SettingsRequest> {
value="${first(this._settings?.defaultTokenLength, 60)}"
help=${msg("Default length of generated tokens")}
></ak-number-input>
<ak-form-group ?expanded=${false}>
<span slot="header"> ${msg("Analytics")} </span>
<div slot="body" class="pf-c-form">
<ak-switch-input
name="analyticsEnabled"
label=${msg("Enable analytics")}
?checked="${this._settings?.analyticsEnabled}"
help=${msg("Enable sending analytics to the authentik team")}
>
</ak-switch-input>
<ak-form-element-horizontal
label=${msg("Analytics sources")}
name="analyticsSources"
>
<ak-dual-select-provider
.provider=${async (
_page: number,
_search?: string,
): Promise<DataProvision> => {
return new AnalyticsApi(DEFAULT_CONFIG)
.analyticsDescriptionList()
.then((results) => {
return {
options: results.map(analyticsSourceToPair),
};
});
}}
.selected=${this._settings?.analyticsSourcesObj ?? []}
available-label="${msg("Available sources")}"
selected-label="${msg("Selected sources")}"
>
</ak-dual-select-provider>
<p class="pf-c-form__helper-text">${msg("Choose what data to send")}</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
`;
}
}

View File

@ -1,6 +1,5 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EVENT_REFRESH } from "@goauthentik/common/constants";
import { parseAPIError } from "@goauthentik/common/errors";
import "@goauthentik/components/ak-radio-input";
import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input";
@ -25,6 +24,7 @@ import {
type TransactionApplicationRequest,
type TransactionApplicationResponse,
ValidationError,
ValidationErrorFromJSON,
} from "@goauthentik/api";
import BasePanel from "../BasePanel";
@ -59,7 +59,7 @@ const runningState: State = {
};
const errorState: State = {
state: "error",
label: msg("authentik was unable to save this application:"),
label: msg("Authentik was unable to save this application:"),
icon: ["fa-times-circle", "pf-m-danger"],
};
@ -133,7 +133,9 @@ export class ApplicationWizardCommitApplication extends BasePanel {
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.catch(async (resolution: any) => {
const errors = await parseAPIError(resolution);
const errors = (this.errors = ValidationErrorFromJSON(
await resolution.response.json(),
));
this.dispatchWizardUpdate({
update: {
...this.wizard,

Some files were not shown because too many files have changed in this diff Show More