Compare commits
39 Commits
restricted
...
benchmarks
Author | SHA1 | Date | |
---|---|---|---|
cde4e395e9 | |||
d19c692f81 | |||
d5d2be5672 | |||
8597db59f5 | |||
74fb9492bc | |||
defbafb55e | |||
e2ed7391bc | |||
8dcd0dcaa9 | |||
18eee1b722 | |||
d0f6c815c3 | |||
b13eba3b0a | |||
77fe4e9fe2 | |||
71fe8b4fb3 | |||
b14cb832b2 | |||
24b5296d88 | |||
41b7e50bc6 | |||
6b750d7c59 | |||
d268c28934 | |||
688404b6a5 | |||
cbd2425a5f | |||
877c264d59 | |||
2575b540fa | |||
0e0b76a62e | |||
6d625fd1d7 | |||
bd0630e300 | |||
ffb7d44024 | |||
7589b11f98 | |||
ad21dfa2bc | |||
95692f5a7c | |||
1f4ed1defa | |||
334b183465 | |||
1f789dd4c5 | |||
057e5747c9 | |||
8717a3aaab | |||
527173236a | |||
3e6eb6f248 | |||
6babf0f1c4 | |||
ca7cc30504 | |||
a7cb808cad |
@ -1,5 +1,5 @@
|
||||
[bumpversion]
|
||||
current_version = 2024.4.1
|
||||
current_version = 2024.2.2
|
||||
tag = True
|
||||
commit = True
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<rc_t>[a-zA-Z-]+)(?P<rc_n>[1-9]\\d*))?
|
||||
@ -21,8 +21,6 @@ optional_value = final
|
||||
|
||||
[bumpversion:file:schema.yml]
|
||||
|
||||
[bumpversion:file:blueprints/schema.json]
|
||||
|
||||
[bumpversion:file:authentik/__init__.py]
|
||||
|
||||
[bumpversion:file:internal/constants/constants.go]
|
||||
|
@ -54,9 +54,9 @@ image_main_tag = image_tags[0]
|
||||
image_tags_rendered = ",".join(image_tags)
|
||||
|
||||
with open(os.environ["GITHUB_OUTPUT"], "a+", encoding="utf-8") as _output:
|
||||
print(f"shouldBuild={should_build}", file=_output)
|
||||
print(f"sha={sha}", file=_output)
|
||||
print(f"version={version}", file=_output)
|
||||
print(f"prerelease={prerelease}", file=_output)
|
||||
print(f"imageTags={image_tags_rendered}", file=_output)
|
||||
print(f"imageMainTag={image_main_tag}", file=_output)
|
||||
print("shouldBuild=%s" % should_build, file=_output)
|
||||
print("sha=%s" % sha, file=_output)
|
||||
print("version=%s" % version, file=_output)
|
||||
print("prerelease=%s" % prerelease, file=_output)
|
||||
print("imageTags=%s" % image_tags_rendered, file=_output)
|
||||
print("imageMainTag=%s" % image_main_tag, file=_output)
|
||||
|
2
.github/workflows/ci-main.yml
vendored
2
.github/workflows/ci-main.yml
vendored
@ -130,7 +130,7 @@ jobs:
|
||||
- name: Setup authentik env
|
||||
uses: ./.github/actions/setup
|
||||
- name: Create k8s Kind Cluster
|
||||
uses: helm/kind-action@v1.10.0
|
||||
uses: helm/kind-action@v1.9.0
|
||||
- name: run integration
|
||||
run: |
|
||||
poetry run coverage run manage.py test tests/integration
|
||||
|
2
.github/workflows/ci-outpost.yml
vendored
2
.github/workflows/ci-outpost.yml
vendored
@ -29,7 +29,7 @@ jobs:
|
||||
- name: Generate API
|
||||
run: make gen-client-go
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v5
|
||||
uses: golangci/golangci-lint-action@v4
|
||||
with:
|
||||
version: v1.54.2
|
||||
args: --timeout 5000s --verbose
|
||||
|
8
.github/workflows/ci-web.yml
vendored
8
.github/workflows/ci-web.yml
vendored
@ -34,13 +34,6 @@ jobs:
|
||||
- name: Eslint
|
||||
working-directory: ${{ matrix.project }}/
|
||||
run: npm run lint
|
||||
lint-lockfile:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- working-directory: web/
|
||||
run: |
|
||||
[ -z "$(jq -r '.packages | to_entries[] | select((.key | startswith("node_modules")) and (.value | has("resolved") | not)) | .key' < package-lock.json)" ]
|
||||
lint-build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@ -102,7 +95,6 @@ jobs:
|
||||
run: npm run lit-analyse
|
||||
ci-web-mark:
|
||||
needs:
|
||||
- lint-lockfile
|
||||
- lint-eslint
|
||||
- lint-prettier
|
||||
- lint-lit-analyse
|
||||
|
8
.github/workflows/ci-website.yml
vendored
8
.github/workflows/ci-website.yml
vendored
@ -12,13 +12,6 @@ on:
|
||||
- version-*
|
||||
|
||||
jobs:
|
||||
lint-lockfile:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- working-directory: website/
|
||||
run: |
|
||||
[ -z "$(jq -r '.packages | to_entries[] | select((.key | startswith("node_modules")) and (.value | has("resolved") | not)) | .key' < package-lock.json)" ]
|
||||
lint-prettier:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@ -69,7 +62,6 @@ jobs:
|
||||
run: npm run ${{ matrix.job }}
|
||||
ci-website-mark:
|
||||
needs:
|
||||
- lint-lockfile
|
||||
- lint-prettier
|
||||
- test
|
||||
- build
|
||||
|
7
.gitignore
vendored
7
.gitignore
vendored
@ -209,3 +209,10 @@ source_docs/
|
||||
|
||||
### Golang ###
|
||||
/vendor/
|
||||
|
||||
### Benchmark ###
|
||||
tests/benchmark/k6
|
||||
tests/benchmark/prometheus
|
||||
tests/benchmark/**/*.json
|
||||
tests/benchmark/**/*.ndjson
|
||||
tests/benchmark/**/*.html
|
||||
|
17
Makefile
17
Makefile
@ -278,3 +278,20 @@ ci-bandit: ci--meta-debug
|
||||
|
||||
ci-pending-migrations: ci--meta-debug
|
||||
ak makemigrations --check
|
||||
|
||||
#########################
|
||||
## Benchmark
|
||||
#########################
|
||||
|
||||
benchmark-fixtures-create:
|
||||
tests/benchmark/fixtures.py create
|
||||
|
||||
benchmark-run:
|
||||
docker compose -f tests/benchmark/docker-compose.yml up -d
|
||||
sleep 5
|
||||
tests/benchmark/run.sh
|
||||
|
||||
benchmark-fixtures-delete:
|
||||
tests/benchmark/fixtures.py delete
|
||||
|
||||
benchmark: benchmark-fixtures-create benchmark-run benchmark-fixtures-delete
|
||||
|
20
SECURITY.md
20
SECURITY.md
@ -18,10 +18,10 @@ Even if the issue is not a CVE, we still greatly appreciate your help in hardeni
|
||||
|
||||
(.x being the latest patch release for each version)
|
||||
|
||||
| Version | Supported |
|
||||
| --------- | --------- |
|
||||
| 2023.10.x | ✅ |
|
||||
| 2024.2.x | ✅ |
|
||||
| Version | Supported |
|
||||
| --- | --- |
|
||||
| 2023.6.x | ✅ |
|
||||
| 2023.8.x | ✅ |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
@ -31,12 +31,12 @@ To report a vulnerability, send an email to [security@goauthentik.io](mailto:se
|
||||
|
||||
authentik reserves the right to reclassify CVSS as necessary. To determine severity, we will use the CVSS calculator from NVD (https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator). The calculated CVSS score will then be translated into one of the following categories:
|
||||
|
||||
| Score | Severity |
|
||||
| ---------- | -------- |
|
||||
| 0.0 | None |
|
||||
| 0.1 – 3.9 | Low |
|
||||
| 4.0 – 6.9 | Medium |
|
||||
| 7.0 – 8.9 | High |
|
||||
| Score | Severity |
|
||||
| --- | --- |
|
||||
| 0.0 | None |
|
||||
| 0.1 – 3.9 | Low |
|
||||
| 4.0 – 6.9 | Medium |
|
||||
| 7.0 – 8.9 | High |
|
||||
| 9.0 – 10.0 | Critical |
|
||||
|
||||
## Disclosure process
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
from os import environ
|
||||
|
||||
__version__ = "2024.4.1"
|
||||
__version__ = "2024.2.2"
|
||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||
|
||||
|
||||
|
@ -73,11 +73,6 @@ def auth_user_lookup(raw_header: bytes) -> User | None:
|
||||
if user:
|
||||
CTX_AUTH_VIA.set("secret_key")
|
||||
return user
|
||||
# then try to auth via expression JWT
|
||||
user = token_expression_jwt(auth_credentials)
|
||||
if user:
|
||||
CTX_AUTH_VIA.set("expression_jwt")
|
||||
return user
|
||||
raise AuthenticationFailed("Token invalid/expired")
|
||||
|
||||
|
||||
@ -95,13 +90,6 @@ def token_secret_key(value: str) -> User | None:
|
||||
return outpost.user
|
||||
|
||||
|
||||
def token_expression_jwt(value: str) -> User | None:
|
||||
"""Authenticate API call made by Expressions"""
|
||||
from authentik.lib.expression.evaluator import authenticate_token
|
||||
|
||||
return authenticate_token(value)
|
||||
|
||||
|
||||
class TokenAuthentication(BaseAuthentication):
|
||||
"""Token-based authentication using HTTP Bearer authentication"""
|
||||
|
||||
|
@ -1,21 +0,0 @@
|
||||
# Generated by Django 5.0.4 on 2024-04-18 18:56
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_brands", "0005_tenantuuid_to_branduuid"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddIndex(
|
||||
model_name="brand",
|
||||
index=models.Index(fields=["domain"], name="authentik_b_domain_b9b24a_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="brand",
|
||||
index=models.Index(fields=["default"], name="authentik_b_default_3ccf12_idx"),
|
||||
),
|
||||
]
|
@ -84,7 +84,3 @@ class Brand(SerializerModel):
|
||||
class Meta:
|
||||
verbose_name = _("Brand")
|
||||
verbose_name_plural = _("Brands")
|
||||
indexes = [
|
||||
models.Index(fields=["domain"]),
|
||||
models.Index(fields=["default"]),
|
||||
]
|
||||
|
@ -154,18 +154,12 @@ class GroupViewSet(UsedByMixin, ModelViewSet):
|
||||
|
||||
pk = IntegerField(required=True)
|
||||
|
||||
queryset = Group.objects.none()
|
||||
queryset = Group.objects.all().select_related("parent").prefetch_related("users")
|
||||
serializer_class = GroupSerializer
|
||||
search_fields = ["name", "is_superuser"]
|
||||
filterset_class = GroupFilter
|
||||
ordering = ["name"]
|
||||
|
||||
def get_queryset(self):
|
||||
base_qs = Group.objects.all().select_related("parent").prefetch_related("roles")
|
||||
if self.serializer_class(context={"request": self.request})._should_include_users:
|
||||
base_qs = base_qs.prefetch_related("users")
|
||||
return base_qs
|
||||
|
||||
@extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter("include_users", bool, default=True),
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
from typing import Any
|
||||
|
||||
from django.utils.timezone import now
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from drf_spectacular.utils import OpenApiResponse, extend_schema, inline_serializer
|
||||
from guardian.shortcuts import assign_perm, get_anonymous_user
|
||||
@ -28,6 +27,7 @@ from authentik.core.models import (
|
||||
TokenIntents,
|
||||
User,
|
||||
default_token_duration,
|
||||
token_expires_from_timedelta,
|
||||
)
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.events.utils import model_to_dict
|
||||
@ -68,17 +68,15 @@ class TokenSerializer(ManagedSerializer, ModelSerializer):
|
||||
max_token_lifetime_dt = default_token_duration()
|
||||
if max_token_lifetime is not None:
|
||||
try:
|
||||
max_token_lifetime_dt = now() + timedelta_from_string(max_token_lifetime)
|
||||
max_token_lifetime_dt = timedelta_from_string(max_token_lifetime)
|
||||
except ValueError:
|
||||
pass
|
||||
max_token_lifetime_dt = default_token_duration()
|
||||
|
||||
if "expires" in attrs and attrs.get("expires") > max_token_lifetime_dt:
|
||||
if "expires" in attrs and attrs.get("expires") > token_expires_from_timedelta(
|
||||
max_token_lifetime_dt
|
||||
):
|
||||
raise ValidationError(
|
||||
{
|
||||
"expires": (
|
||||
f"Token expires exceeds maximum lifetime ({max_token_lifetime_dt} UTC)."
|
||||
)
|
||||
}
|
||||
{"expires": f"Token expires exceeds maximum lifetime ({max_token_lifetime})."}
|
||||
)
|
||||
elif attrs.get("intent") == TokenIntents.INTENT_API:
|
||||
# For API tokens, expires cannot be overridden
|
||||
|
@ -407,11 +407,8 @@ class UserViewSet(UsedByMixin, ModelViewSet):
|
||||
search_fields = ["username", "name", "is_active", "email", "uuid"]
|
||||
filterset_class = UsersFilter
|
||||
|
||||
def get_queryset(self):
|
||||
base_qs = User.objects.all().exclude_anonymous()
|
||||
if self.serializer_class(context={"request": self.request})._should_include_groups:
|
||||
base_qs = base_qs.prefetch_related("ak_groups")
|
||||
return base_qs
|
||||
def get_queryset(self): # pragma: no cover
|
||||
return User.objects.all().exclude_anonymous().prefetch_related("ak_groups")
|
||||
|
||||
@extend_schema(
|
||||
parameters=[
|
||||
|
@ -36,7 +36,7 @@ class PropertyMappingEvaluator(BaseEvaluator):
|
||||
_filename = model.name
|
||||
else:
|
||||
_filename = str(model)
|
||||
super().__init__(None, filename=_filename)
|
||||
super().__init__(filename=_filename)
|
||||
req = PolicyRequest(user=User())
|
||||
req.obj = model
|
||||
if user:
|
||||
|
@ -1,6 +1,6 @@
|
||||
"""authentik core models"""
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from hashlib import sha256
|
||||
from typing import Any, Optional, Self
|
||||
from uuid import uuid4
|
||||
@ -54,6 +54,9 @@ options.DEFAULT_NAMES = options.DEFAULT_NAMES + (
|
||||
# used_by API that allows models to specify if they shadow an object
|
||||
# for example the proxy provider which is built on top of an oauth provider
|
||||
"authentik_used_by_shadows",
|
||||
# List fields for which changes are not logged (due to them having dedicated objects)
|
||||
# for example user's password and last_login
|
||||
"authentik_signals_ignored_fields",
|
||||
)
|
||||
|
||||
|
||||
@ -68,6 +71,11 @@ def default_token_duration() -> datetime:
|
||||
return now() + timedelta_from_string(token_duration)
|
||||
|
||||
|
||||
def token_expires_from_timedelta(dt: timedelta) -> datetime:
|
||||
"""Return a `datetime.datetime` object with the duration of the Token"""
|
||||
return now() + dt
|
||||
|
||||
|
||||
def default_token_key() -> str:
|
||||
"""Default token key"""
|
||||
current_tenant = get_current_tenant()
|
||||
@ -327,6 +335,14 @@ class User(SerializerModel, GuardianUserMixin, AbstractUser):
|
||||
models.Index(fields=["path"]),
|
||||
models.Index(fields=["type"]),
|
||||
]
|
||||
authentik_signals_ignored_fields = [
|
||||
# Logged by the events `password_set`
|
||||
# the `password_set` action/signal doesn't currently convey which user
|
||||
# initiated the password change, so for now we'll log two actions
|
||||
# ("password", "password_change_date"),
|
||||
# Logged by `login`
|
||||
("last_login",),
|
||||
]
|
||||
|
||||
|
||||
class Provider(SerializerModel):
|
||||
@ -632,7 +648,7 @@ class UserSourceConnection(SerializerModel, CreatedUpdatedModel):
|
||||
raise NotImplementedError
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"User-source connection (user={self.user_id}, source={self.source_id})"
|
||||
return f"User-source connection (user={self.user.username}, source={self.source.slug})"
|
||||
|
||||
class Meta:
|
||||
unique_together = (("user", "source"),)
|
||||
|
@ -5,7 +5,7 @@ from guardian.shortcuts import assign_perm
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.models import Group, User
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_user
|
||||
from authentik.core.tests.utils import create_test_user
|
||||
from authentik.lib.generators import generate_id
|
||||
|
||||
|
||||
@ -16,13 +16,6 @@ class TestGroupsAPI(APITestCase):
|
||||
self.login_user = create_test_user()
|
||||
self.user = User.objects.create(username="test-user")
|
||||
|
||||
def test_list_with_users(self):
|
||||
"""Test listing with users"""
|
||||
admin = create_test_admin_user()
|
||||
self.client.force_login(admin)
|
||||
response = self.client.get(reverse("authentik_api:group-list"), {"include_users": "true"})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_add_user(self):
|
||||
"""Test add_user"""
|
||||
group = Group.objects.create(name=generate_id())
|
||||
|
@ -66,11 +66,14 @@ class TestPropertyMappings(TestCase):
|
||||
expression="return request.http_request.path",
|
||||
)
|
||||
http_request = self.factory.get("/")
|
||||
tmpl = f"""
|
||||
res = ak_call_policy('{expr.name}')
|
||||
tmpl = (
|
||||
"""
|
||||
res = ak_call_policy('%s')
|
||||
result = [request.http_request.path, res.raw_result]
|
||||
return result
|
||||
"""
|
||||
% expr.name
|
||||
)
|
||||
evaluator = PropertyMapping(expression=tmpl, name=generate_id())
|
||||
res = evaluator.evaluate(self.user, http_request)
|
||||
self.assertEqual(res, ["/", "/"])
|
||||
|
@ -41,12 +41,6 @@ class TestUsersAPI(APITestCase):
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_list_with_groups(self):
|
||||
"""Test listing with groups"""
|
||||
self.client.force_login(self.admin)
|
||||
response = self.client.get(reverse("authentik_api:user-list"), {"include_groups": "true"})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_metrics(self):
|
||||
"""Test user's metrics"""
|
||||
self.client.force_login(self.admin)
|
||||
|
@ -8,6 +8,7 @@ from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.models import User
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.tenants.utils import get_current_tenant
|
||||
|
||||
|
||||
@ -24,6 +25,7 @@ class TestUsersAvatars(APITestCase):
|
||||
tenant.avatars = mode
|
||||
tenant.save()
|
||||
|
||||
@CONFIG.patch("avatars", "none")
|
||||
def test_avatars_none(self):
|
||||
"""Test avatars none"""
|
||||
self.set_avatar_mode("none")
|
||||
|
@ -102,4 +102,9 @@ class EnterpriseAuditMiddleware(AuditMiddleware):
|
||||
new_state = self.serialize_simple(instance)
|
||||
diff = self.diff(prev_state, new_state)
|
||||
thread_kwargs["diff"] = diff
|
||||
if not created:
|
||||
ignored_field_sets = getattr(instance._meta, "authentik_signals_ignored_fields", [])
|
||||
for field_set in ignored_field_sets:
|
||||
if set(diff.keys()) == set(field_set):
|
||||
return None
|
||||
return super().post_save_handler(request, sender, instance, created, thread_kwargs, **_)
|
||||
|
@ -201,7 +201,10 @@ class ConnectionToken(ExpiringModel):
|
||||
return settings
|
||||
|
||||
def __str__(self):
|
||||
return f"RAC Connection token {self.session_id} to {self.provider_id}/{self.endpoint_id}"
|
||||
return (
|
||||
f"RAC Connection token {self.session.user} to "
|
||||
f"{self.endpoint.provider.name}/{self.endpoint.name}"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("RAC Connection token")
|
||||
|
@ -116,12 +116,12 @@ class AuditMiddleware:
|
||||
return user
|
||||
user = getattr(request, "user", self.anonymous_user)
|
||||
if not user.is_authenticated:
|
||||
self._ensure_fallback_user()
|
||||
return self.anonymous_user
|
||||
return user
|
||||
|
||||
def connect(self, request: HttpRequest):
|
||||
"""Connect signal for automatic logging"""
|
||||
self._ensure_fallback_user()
|
||||
if not hasattr(request, "request_id"):
|
||||
return
|
||||
post_save.connect(
|
||||
|
@ -556,7 +556,7 @@ class Notification(SerializerModel):
|
||||
if len(self.body) > NOTIFICATION_SUMMARY_LENGTH
|
||||
else self.body
|
||||
)
|
||||
return f"Notification for user {self.user_id}: {body_trunc}"
|
||||
return f"Notification for user {self.user}: {body_trunc}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Notification")
|
||||
|
@ -1,35 +0,0 @@
|
||||
"""authentik event models tests"""
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
from django.db.models import Model
|
||||
from django.test import TestCase
|
||||
|
||||
from authentik.core.models import default_token_key
|
||||
from authentik.lib.utils.reflection import get_apps
|
||||
|
||||
|
||||
class TestModels(TestCase):
|
||||
"""Test Models"""
|
||||
|
||||
|
||||
def model_tester_factory(test_model: type[Model]) -> Callable:
|
||||
"""Test models' __str__ and __repr__"""
|
||||
|
||||
def tester(self: TestModels):
|
||||
allowed = 0
|
||||
# Token-like objects need to lookup the current tenant to get the default token length
|
||||
for field in test_model._meta.fields:
|
||||
if field.default == default_token_key:
|
||||
allowed += 1
|
||||
with self.assertNumQueries(allowed):
|
||||
str(test_model())
|
||||
with self.assertNumQueries(allowed):
|
||||
repr(test_model())
|
||||
|
||||
return tester
|
||||
|
||||
|
||||
for app in get_apps():
|
||||
for model in app.get_models():
|
||||
setattr(TestModels, f"test_{app.label}_{model.__name__}", model_tester_factory(model))
|
@ -278,7 +278,7 @@ class FlowViewSet(UsedByMixin, ModelViewSet):
|
||||
},
|
||||
)
|
||||
@action(detail=True, pagination_class=None, filter_backends=[])
|
||||
def execute(self, request: Request, slug: str):
|
||||
def execute(self, request: Request, _slug: str):
|
||||
"""Execute flow for current user"""
|
||||
# Because we pre-plan the flow here, and not in the planner, we need to manually clear
|
||||
# the history of the inspector
|
||||
|
@ -6,7 +6,6 @@ from rest_framework.test import APITestCase
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
from authentik.flows.api.stages import StageSerializer, StageViewSet
|
||||
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding, Stage
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.policies.dummy.models import DummyPolicy
|
||||
from authentik.policies.models import PolicyBinding
|
||||
from authentik.stages.dummy.models import DummyStage
|
||||
@ -102,21 +101,3 @@ class TestFlowsAPI(APITestCase):
|
||||
reverse("authentik_api:stage-types"),
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_execute(self):
|
||||
"""Test execute endpoint"""
|
||||
user = create_test_admin_user()
|
||||
self.client.force_login(user)
|
||||
|
||||
flow = Flow.objects.create(
|
||||
name=generate_id(),
|
||||
slug=generate_id(),
|
||||
designation=FlowDesignation.AUTHENTICATION,
|
||||
)
|
||||
FlowStageBinding.objects.create(
|
||||
target=flow, stage=DummyStage.objects.create(name=generate_id()), order=0
|
||||
)
|
||||
response = self.client.get(
|
||||
reverse("authentik_api:flow-execute", kwargs={"slug": flow.slug})
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
@ -14,7 +14,7 @@ from pathlib import Path
|
||||
from sys import argv, stderr
|
||||
from time import time
|
||||
from typing import Any
|
||||
from urllib.parse import quote_plus, urlparse
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import yaml
|
||||
from django.conf import ImproperlyConfigured
|
||||
@ -331,26 +331,6 @@ class ConfigLoader:
|
||||
CONFIG = ConfigLoader()
|
||||
|
||||
|
||||
def redis_url(db: int) -> str:
|
||||
"""Helper to create a Redis URL for a specific database"""
|
||||
_redis_protocol_prefix = "redis://"
|
||||
_redis_tls_requirements = ""
|
||||
if CONFIG.get_bool("redis.tls", False):
|
||||
_redis_protocol_prefix = "rediss://"
|
||||
_redis_tls_requirements = f"?ssl_cert_reqs={CONFIG.get('redis.tls_reqs')}"
|
||||
if _redis_ca := CONFIG.get("redis.tls_ca_cert", None):
|
||||
_redis_tls_requirements += f"&ssl_ca_certs={_redis_ca}"
|
||||
_redis_url = (
|
||||
f"{_redis_protocol_prefix}"
|
||||
f"{quote_plus(CONFIG.get('redis.username'))}:"
|
||||
f"{quote_plus(CONFIG.get('redis.password'))}@"
|
||||
f"{quote_plus(CONFIG.get('redis.host'))}:"
|
||||
f"{CONFIG.get_int('redis.port')}"
|
||||
f"/{db}{_redis_tls_requirements}"
|
||||
)
|
||||
return _redis_url
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(argv) < 2: # noqa: PLR2004
|
||||
print(dumps(CONFIG.raw, indent=4, cls=AttrEncoder))
|
||||
|
@ -35,7 +35,6 @@ redis:
|
||||
password: ""
|
||||
tls: false
|
||||
tls_reqs: "none"
|
||||
tls_ca_cert: null
|
||||
|
||||
# broker:
|
||||
# url: ""
|
||||
@ -59,8 +58,6 @@ remote_debug: false
|
||||
|
||||
log_level: info
|
||||
|
||||
session_storage: cache
|
||||
|
||||
error_reporting:
|
||||
enabled: false
|
||||
sentry_dsn: https://151ba72610234c4c97c5bcff4e1cffd8@authentik.error-reporting.a7k.io/4504163677503489
|
||||
@ -95,9 +92,6 @@ outposts:
|
||||
discover: true
|
||||
disable_embedded_outpost: false
|
||||
|
||||
expressions:
|
||||
global_runtime: python # or python_restricted
|
||||
|
||||
ldap:
|
||||
task_timeout_hours: 2
|
||||
page_size: 50
|
||||
|
@ -3,117 +3,27 @@
|
||||
import re
|
||||
import socket
|
||||
from collections.abc import Iterable
|
||||
from datetime import timedelta
|
||||
from functools import lru_cache
|
||||
from ipaddress import ip_address, ip_network
|
||||
from pathlib import Path
|
||||
from tempfile import gettempdir
|
||||
from textwrap import indent
|
||||
from typing import Any
|
||||
|
||||
from authentik_client.api.admin_api import AdminApi
|
||||
from authentik_client.api.authenticators_api import AuthenticatorsApi
|
||||
from authentik_client.api.core_api import CoreApi
|
||||
from authentik_client.api.crypto_api import CryptoApi
|
||||
from authentik_client.api.enterprise_api import EnterpriseApi
|
||||
from authentik_client.api.events_api import EventsApi
|
||||
from authentik_client.api.flows_api import FlowsApi
|
||||
from authentik_client.api.managed_api import ManagedApi
|
||||
from authentik_client.api.oauth2_api import Oauth2Api
|
||||
from authentik_client.api.outposts_api import OutpostsApi
|
||||
from authentik_client.api.policies_api import PoliciesApi
|
||||
from authentik_client.api.propertymappings_api import PropertymappingsApi
|
||||
from authentik_client.api.providers_api import ProvidersApi
|
||||
from authentik_client.api.rac_api import RacApi
|
||||
from authentik_client.api.rbac_api import RbacApi
|
||||
from authentik_client.api.root_api import RootApi
|
||||
from authentik_client.api.schema_api import SchemaApi
|
||||
from authentik_client.api.sources_api import SourcesApi
|
||||
from authentik_client.api.stages_api import StagesApi
|
||||
from authentik_client.api.tenants_api import TenantsApi
|
||||
from authentik_client.api_client import ApiClient
|
||||
from authentik_client.configuration import Configuration
|
||||
from cachetools import TLRUCache, cached
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import FieldError
|
||||
from django.utils.timezone import now
|
||||
from guardian.shortcuts import get_anonymous_user
|
||||
from jwt import PyJWTError, decode, encode
|
||||
from rest_framework.serializers import ValidationError
|
||||
from RestrictedPython import compile_restricted, limited_builtins, safe_builtins, utility_builtins
|
||||
from sentry_sdk.hub import Hub
|
||||
from sentry_sdk.tracing import Span
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.core.models import User
|
||||
from authentik.events.models import Event
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.lib.generators import generate_key
|
||||
from authentik.lib.utils.errors import exception_to_string
|
||||
from authentik.lib.utils.http import authentik_user_agent, get_http_session
|
||||
from authentik.lib.utils.reflection import get_apps
|
||||
from authentik.lib.utils.http import get_http_session
|
||||
from authentik.policies.models import Policy, PolicyBinding
|
||||
from authentik.policies.process import PolicyProcess
|
||||
from authentik.policies.types import PolicyRequest, PolicyResult
|
||||
from authentik.stages.authenticator import devices_for_user
|
||||
|
||||
LOGGER = get_logger()
|
||||
_tmp = Path(gettempdir())
|
||||
token_path = _tmp / "authentik-evaluator-token"
|
||||
|
||||
API_CLIENTS = {
|
||||
"AdminApi": AdminApi,
|
||||
"AuthenticatorsApi": AuthenticatorsApi,
|
||||
"CoreApi": CoreApi,
|
||||
"CryptoApi": CryptoApi,
|
||||
"EnterpriseApi": EnterpriseApi,
|
||||
"EventsApi": EventsApi,
|
||||
"FlowsApi": FlowsApi,
|
||||
"ManagedApi": ManagedApi,
|
||||
"Oauth2Api": Oauth2Api,
|
||||
"OutpostsApi": OutpostsApi,
|
||||
"PoliciesApi": PoliciesApi,
|
||||
"PropertymappingsApi": PropertymappingsApi,
|
||||
"ProvidersApi": ProvidersApi,
|
||||
"RacApi": RacApi,
|
||||
"RbacApi": RbacApi,
|
||||
"RootApi": RootApi,
|
||||
"SchemaApi": SchemaApi,
|
||||
"SourcesApi": SourcesApi,
|
||||
"StagesApi": StagesApi,
|
||||
"TenantsApi": TenantsApi,
|
||||
}
|
||||
|
||||
JWT_AUD = "goauthentik.io/api/expression"
|
||||
|
||||
_SAFE_MODULES = frozenset(("authentik_client",))
|
||||
|
||||
|
||||
def _safe_import(name, *args, **kwargs):
|
||||
if name not in _SAFE_MODULES:
|
||||
raise Exception(f"Don't you even think about {name!r}")
|
||||
return __import__(name, *args, **kwargs)
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_api_token_secret():
|
||||
if token_path.exists():
|
||||
with open(token_path) as _token_file:
|
||||
return _token_file.read()
|
||||
key = generate_key()
|
||||
with open(_tmp / "authentik-evaluator-token", "w") as _token_file:
|
||||
_token_file.write(key)
|
||||
return key
|
||||
|
||||
|
||||
def authenticate_token(raw_value: str):
|
||||
"""Authenticate API call from evaluator token"""
|
||||
try:
|
||||
jwt = decode(raw_value, get_api_token_secret(), ["HS256"], audience=JWT_AUD)
|
||||
return User.objects.filter(pk=jwt["sub"]).first()
|
||||
except PyJWTError as exc:
|
||||
LOGGER.debug("failed to auth", exc=exc)
|
||||
return None
|
||||
|
||||
|
||||
class BaseEvaluator:
|
||||
@ -127,14 +37,8 @@ class BaseEvaluator:
|
||||
# Filename used for exec
|
||||
_filename: str
|
||||
|
||||
_user: User
|
||||
|
||||
# Timeout in seconds, used for the expiration of the API key
|
||||
timeout = 30
|
||||
|
||||
def __init__(self, user: User, filename: str | None = None):
|
||||
def __init__(self, filename: str | None = None):
|
||||
self._filename = filename if filename else "BaseEvaluator"
|
||||
self._user = user
|
||||
# update website/docs/expressions/_objects.md
|
||||
# update website/docs/expressions/_functions.md
|
||||
self._globals = {
|
||||
@ -153,44 +57,8 @@ class BaseEvaluator:
|
||||
"resolve_dns": BaseEvaluator.expr_resolve_dns,
|
||||
"reverse_dns": BaseEvaluator.expr_reverse_dns,
|
||||
}
|
||||
for app in get_apps():
|
||||
# Load models from each app
|
||||
for model in app.get_models():
|
||||
self._globals[model.__name__] = model
|
||||
self._globals.update(API_CLIENTS)
|
||||
self._context = {}
|
||||
|
||||
def get_token(self) -> str:
|
||||
"""Generate API token to be used by the API Client"""
|
||||
_now = now()
|
||||
if not self._user:
|
||||
self._user = get_anonymous_user()
|
||||
return encode(
|
||||
{
|
||||
"aud": JWT_AUD,
|
||||
"iss": f"goauthentik.io/expression/{self._filename}",
|
||||
"sub": str(self._user.pk),
|
||||
"iat": int(_now.timestamp()),
|
||||
"exp": int((_now + timedelta(seconds=self.timeout)).timestamp()),
|
||||
},
|
||||
get_api_token_secret(),
|
||||
)
|
||||
|
||||
def get_api_client(self):
|
||||
token = self.get_token()
|
||||
config = Configuration(
|
||||
f"unix://{str(_tmp.joinpath('authentik-core.sock'))}/api/v3",
|
||||
api_key={
|
||||
"authentik": token,
|
||||
},
|
||||
api_key_prefix={"authentik": "Bearer"},
|
||||
)
|
||||
if settings.DEBUG:
|
||||
config.host = "http://localhost:8000/api/v3"
|
||||
client = ApiClient(config)
|
||||
client.user_agent = authentik_user_agent()
|
||||
return client
|
||||
|
||||
@cached(cache=TLRUCache(maxsize=32, ttu=lambda key, value, now: now + 180))
|
||||
@staticmethod
|
||||
def expr_resolve_dns(host: str, ip_version: int | None = None) -> list[str]:
|
||||
@ -317,16 +185,7 @@ class BaseEvaluator:
|
||||
def compile(self, expression: str) -> Any:
|
||||
"""Parse expression. Raises SyntaxError or ValueError if the syntax is incorrect."""
|
||||
param_keys = self._context.keys()
|
||||
compiler = (
|
||||
compile_restricted
|
||||
if CONFIG.get("expressions.global_runtime") == "python_restricted"
|
||||
else compile
|
||||
)
|
||||
return compiler(
|
||||
self.wrap_expression(expression, param_keys),
|
||||
self._filename,
|
||||
"exec",
|
||||
)
|
||||
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.
|
||||
@ -342,17 +201,7 @@ class BaseEvaluator:
|
||||
self.handle_error(exc, expression_source)
|
||||
raise exc
|
||||
try:
|
||||
if CONFIG.get("expressions.global_runtime") == "python_restricted":
|
||||
self._globals["__builtins__"] = {
|
||||
**safe_builtins,
|
||||
**limited_builtins,
|
||||
**utility_builtins,
|
||||
"__import__": _safe_import,
|
||||
}
|
||||
_locals = self._context
|
||||
# We need to create the API Client later so that the token is valid
|
||||
# from when the execution starts
|
||||
self._globals["api"] = self.get_api_client()
|
||||
# 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.
|
||||
@ -360,7 +209,6 @@ class BaseEvaluator:
|
||||
exec(ast_obj, self._globals, _locals) # nosec # noqa
|
||||
result = _locals["result"]
|
||||
except Exception as exc:
|
||||
print(exception_to_string(exc))
|
||||
# So, this is a bit questionable. Essentially, we are edit the stacktrace
|
||||
# so the user only sees information relevant to them
|
||||
# and none of our surrounding error handling
|
||||
|
@ -1,7 +1,6 @@
|
||||
"""Test Evaluator base functions"""
|
||||
|
||||
from django.test import TestCase
|
||||
from guardian.shortcuts import get_anonymous_user
|
||||
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
from authentik.events.models import Event
|
||||
@ -34,7 +33,7 @@ class TestEvaluator(TestCase):
|
||||
|
||||
def test_expr_event_create(self):
|
||||
"""Test expr_event_create"""
|
||||
evaluator = BaseEvaluator(get_anonymous_user(), generate_id())
|
||||
evaluator = BaseEvaluator(generate_id())
|
||||
evaluator._context = {
|
||||
"foo": "bar",
|
||||
}
|
||||
|
@ -14,13 +14,12 @@ class ExpressionPolicySerializer(PolicySerializer):
|
||||
def validate_expression(self, expr: str) -> str:
|
||||
"""validate the syntax of the expression"""
|
||||
name = "temp-policy" if not self.instance else self.instance.name
|
||||
request = self.context.get("request")
|
||||
PolicyEvaluator(request.user if request else None, name).validate(expr)
|
||||
PolicyEvaluator(name).validate(expr)
|
||||
return expr
|
||||
|
||||
class Meta:
|
||||
model = ExpressionPolicy
|
||||
fields = PolicySerializer.Meta.fields + ["expression", "execution_user"]
|
||||
fields = PolicySerializer.Meta.fields + ["expression"]
|
||||
|
||||
|
||||
class ExpressionPolicyViewSet(UsedByMixin, ModelViewSet):
|
||||
|
@ -1,12 +1,11 @@
|
||||
"""Authentik policy_expression app config"""
|
||||
|
||||
from authentik.blueprints.apps import ManagedAppConfig
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AuthentikPolicyExpressionConfig(ManagedAppConfig):
|
||||
class AuthentikPolicyExpressionConfig(AppConfig):
|
||||
"""Authentik policy_expression app config"""
|
||||
|
||||
name = "authentik.policies.expression"
|
||||
label = "authentik_policies_expression"
|
||||
verbose_name = "authentik Policies.Expression"
|
||||
default = True
|
||||
|
@ -6,7 +6,6 @@ from typing import TYPE_CHECKING, Optional
|
||||
from django.http import HttpRequest
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.core.models import User
|
||||
from authentik.flows.planner import PLAN_CONTEXT_SSO
|
||||
from authentik.lib.expression.evaluator import BaseEvaluator
|
||||
from authentik.policies.exceptions import PolicyException
|
||||
@ -25,8 +24,8 @@ class PolicyEvaluator(BaseEvaluator):
|
||||
|
||||
policy: Optional["ExpressionPolicy"] = None
|
||||
|
||||
def __init__(self, user: User, policy_name: str | None = None):
|
||||
super().__init__(user, policy_name or "PolicyEvaluator")
|
||||
def __init__(self, policy_name: str | None = None):
|
||||
super().__init__(policy_name or "PolicyEvaluator")
|
||||
self._messages = []
|
||||
# update website/docs/expressions/_objects.md
|
||||
# update website/docs/expressions/_functions.md
|
||||
@ -45,8 +44,6 @@ class PolicyEvaluator(BaseEvaluator):
|
||||
if request.http_request:
|
||||
self.set_http_request(request.http_request)
|
||||
self._context["request"] = request
|
||||
if not self._user:
|
||||
self._user = request.user
|
||||
self._context["context"] = request.context
|
||||
|
||||
def set_http_request(self, request: HttpRequest):
|
||||
|
@ -1,26 +0,0 @@
|
||||
# Generated by Django 5.0.3 on 2024-03-20 12:14
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_policies_expression", "0004_expressionpolicy_authentik_p_policy__fb6feb_idx"),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="expressionpolicy",
|
||||
name="execution_user",
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_DEFAULT,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
]
|
@ -12,9 +12,6 @@ from authentik.policies.types import PolicyRequest, PolicyResult
|
||||
class ExpressionPolicy(Policy):
|
||||
"""Execute arbitrary Python code to implement custom checks and validation."""
|
||||
|
||||
execution_user = models.ForeignKey(
|
||||
"authentik_core.User", default=None, null=True, on_delete=models.SET_DEFAULT
|
||||
)
|
||||
expression = models.TextField()
|
||||
|
||||
@property
|
||||
@ -29,11 +26,17 @@ class ExpressionPolicy(Policy):
|
||||
|
||||
def passes(self, request: PolicyRequest) -> PolicyResult:
|
||||
"""Evaluate and render expression. Returns PolicyResult(false) on error."""
|
||||
evaluator = PolicyEvaluator(self.execution_user, self.name)
|
||||
evaluator = PolicyEvaluator(self.name)
|
||||
evaluator.policy = self
|
||||
evaluator.set_policy_request(request)
|
||||
return evaluator.evaluate(self.expression)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
evaluator = PolicyEvaluator(self.name)
|
||||
evaluator.policy = self
|
||||
evaluator.validate(self.expression)
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
class Meta(Policy.PolicyMeta):
|
||||
verbose_name = _("Expression Policy")
|
||||
verbose_name_plural = _("Expression Policies")
|
||||
|
@ -1,13 +0,0 @@
|
||||
from django.db.models.signals import pre_save
|
||||
from django.dispatch import receiver
|
||||
|
||||
from authentik.policies.expression.evaluator import PolicyEvaluator
|
||||
from authentik.policies.expression.models import ExpressionPolicy
|
||||
|
||||
|
||||
@receiver(pre_save, sender=ExpressionPolicy)
|
||||
def pre_save_expression_policy(sender: type[ExpressionPolicy], instance: ExpressionPolicy, **_):
|
||||
"""Ensure policy is valid before saving"""
|
||||
evaluator = PolicyEvaluator(instance.execution_user, instance.name)
|
||||
evaluator.policy = instance
|
||||
evaluator.validate(instance.expression)
|
@ -41,14 +41,14 @@ class TestEvaluator(TestCase):
|
||||
def test_valid(self):
|
||||
"""test simple value expression"""
|
||||
template = "return True"
|
||||
evaluator = PolicyEvaluator(self.request.user, "test")
|
||||
evaluator = PolicyEvaluator("test")
|
||||
evaluator.set_policy_request(self.request)
|
||||
self.assertEqual(evaluator.evaluate(template).passing, True)
|
||||
|
||||
def test_messages(self):
|
||||
"""test expression with message return"""
|
||||
template = 'ak_message("some message");return False'
|
||||
evaluator = PolicyEvaluator(self.request.user, "test")
|
||||
evaluator = PolicyEvaluator("test")
|
||||
evaluator.set_policy_request(self.request)
|
||||
result = evaluator.evaluate(template)
|
||||
self.assertEqual(result.passing, False)
|
||||
@ -57,7 +57,7 @@ class TestEvaluator(TestCase):
|
||||
def test_invalid_syntax(self):
|
||||
"""test invalid syntax"""
|
||||
template = ";"
|
||||
evaluator = PolicyEvaluator(self.request.user, "test")
|
||||
evaluator = PolicyEvaluator("test")
|
||||
evaluator.set_policy_request(self.request)
|
||||
with self.assertRaises(PolicyException):
|
||||
evaluator.evaluate(template)
|
||||
@ -65,14 +65,14 @@ class TestEvaluator(TestCase):
|
||||
def test_validate(self):
|
||||
"""test validate"""
|
||||
template = "True"
|
||||
evaluator = PolicyEvaluator(self.request.user, "test")
|
||||
evaluator = PolicyEvaluator("test")
|
||||
result = evaluator.validate(template)
|
||||
self.assertEqual(result, True)
|
||||
|
||||
def test_validate_invalid(self):
|
||||
"""test validate"""
|
||||
template = ";"
|
||||
evaluator = PolicyEvaluator(self.request.user, "test")
|
||||
evaluator = PolicyEvaluator("test")
|
||||
with self.assertRaises(ValidationError):
|
||||
evaluator.validate(template)
|
||||
|
||||
@ -83,7 +83,7 @@ class TestEvaluator(TestCase):
|
||||
execution_logging=True,
|
||||
expression="ak_message(request.http_request.path)\nreturn True",
|
||||
)
|
||||
evaluator = PolicyEvaluator(self.request.user, "test")
|
||||
evaluator = PolicyEvaluator("test")
|
||||
evaluator.set_policy_request(self.request)
|
||||
proc = PolicyProcess(PolicyBinding(policy=expr), request=self.request, connection=None)
|
||||
res = proc.profiling_wrapper()
|
||||
@ -96,13 +96,16 @@ class TestEvaluator(TestCase):
|
||||
execution_logging=True,
|
||||
expression="ak_message(request.http_request.path)\nreturn True",
|
||||
)
|
||||
tmpl = f"""
|
||||
tmpl = (
|
||||
"""
|
||||
ak_message(request.http_request.path)
|
||||
res = ak_call_policy('{expr.name}')
|
||||
res = ak_call_policy('%s')
|
||||
ak_message(request.http_request.path)
|
||||
for msg in res.messages:
|
||||
ak_message(msg)
|
||||
"""
|
||||
% expr.name
|
||||
)
|
||||
evaluator = PolicyEvaluator("test")
|
||||
evaluator.set_policy_request(self.request)
|
||||
res = evaluator.evaluate(tmpl)
|
||||
|
@ -326,7 +326,7 @@ class AuthorizationCode(SerializerModel, ExpiringModel, BaseGrantModel):
|
||||
verbose_name_plural = _("Authorization Codes")
|
||||
|
||||
def __str__(self):
|
||||
return f"Authorization code for {self.provider_id} for user {self.user_id}"
|
||||
return f"Authorization code for {self.provider} for user {self.user}"
|
||||
|
||||
@property
|
||||
def serializer(self) -> Serializer:
|
||||
@ -356,7 +356,7 @@ class AccessToken(SerializerModel, ExpiringModel, BaseGrantModel):
|
||||
verbose_name_plural = _("OAuth2 Access Tokens")
|
||||
|
||||
def __str__(self):
|
||||
return f"Access Token for {self.provider_id} for user {self.user_id}"
|
||||
return f"Access Token for {self.provider} for user {self.user}"
|
||||
|
||||
@property
|
||||
def id_token(self) -> IDToken:
|
||||
@ -399,7 +399,7 @@ class RefreshToken(SerializerModel, ExpiringModel, BaseGrantModel):
|
||||
verbose_name_plural = _("OAuth2 Refresh Tokens")
|
||||
|
||||
def __str__(self):
|
||||
return f"Refresh Token for {self.provider_id} for user {self.user_id}"
|
||||
return f"Refresh Token for {self.provider} for user {self.user}"
|
||||
|
||||
@property
|
||||
def id_token(self) -> IDToken:
|
||||
@ -443,4 +443,4 @@ class DeviceToken(ExpiringModel):
|
||||
verbose_name_plural = _("Device Tokens")
|
||||
|
||||
def __str__(self):
|
||||
return f"Device Token for {self.provider_id}"
|
||||
return f"Device Token for {self.provider}"
|
||||
|
@ -105,7 +105,7 @@ class SCIMUser(models.Model):
|
||||
unique_together = (("id", "user", "provider"),)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"SCIM User {self.user_id} to {self.provider_id}"
|
||||
return f"SCIM User {self.user.username} to {self.provider.name}"
|
||||
|
||||
|
||||
class SCIMGroup(models.Model):
|
||||
@ -119,4 +119,4 @@ class SCIMGroup(models.Model):
|
||||
unique_together = (("id", "group", "provider"),)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"SCIM Group {self.group_id} to {self.provider_id}"
|
||||
return f"SCIM Group {self.group.name} to {self.provider.name}"
|
||||
|
@ -5,13 +5,13 @@ import os
|
||||
from collections import OrderedDict
|
||||
from hashlib import sha512
|
||||
from pathlib import Path
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
from celery.schedules import crontab
|
||||
from django.conf import ImproperlyConfigured
|
||||
from sentry_sdk import set_tag
|
||||
|
||||
from authentik import ENV_GIT_HASH_KEY, __version__
|
||||
from authentik.lib.config import CONFIG, redis_url
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.lib.logging import get_logger_config, structlog_configure
|
||||
from authentik.lib.sentry import sentry_init
|
||||
from authentik.lib.utils.reflection import get_env
|
||||
@ -195,15 +195,25 @@ REST_FRAMEWORK = {
|
||||
},
|
||||
}
|
||||
|
||||
_redis_protocol_prefix = "redis://"
|
||||
_redis_celery_tls_requirements = ""
|
||||
if CONFIG.get_bool("redis.tls", False):
|
||||
_redis_protocol_prefix = "rediss://"
|
||||
_redis_celery_tls_requirements = f"?ssl_cert_reqs={CONFIG.get('redis.tls_reqs')}"
|
||||
_redis_url = (
|
||||
f"{_redis_protocol_prefix}"
|
||||
f"{quote_plus(CONFIG.get('redis.username'))}:"
|
||||
f"{quote_plus(CONFIG.get('redis.password'))}@"
|
||||
f"{quote_plus(CONFIG.get('redis.host'))}:"
|
||||
f"{CONFIG.get_int('redis.port')}"
|
||||
)
|
||||
|
||||
CACHES = {
|
||||
"default": {
|
||||
"BACKEND": "django_redis.cache.RedisCache",
|
||||
"LOCATION": CONFIG.get("cache.url") or redis_url(CONFIG.get("redis.db")),
|
||||
"LOCATION": CONFIG.get("cache.url") or f"{_redis_url}/{CONFIG.get('redis.db')}",
|
||||
"TIMEOUT": CONFIG.get_int("cache.timeout", 300),
|
||||
"OPTIONS": {
|
||||
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
||||
},
|
||||
"OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient"},
|
||||
"KEY_PREFIX": "authentik_cache",
|
||||
"KEY_FUNCTION": "django_tenants.cache.make_key",
|
||||
"REVERSE_KEY_FUNCTION": "django_tenants.cache.reverse_key",
|
||||
@ -212,15 +222,7 @@ CACHES = {
|
||||
DJANGO_REDIS_SCAN_ITERSIZE = 1000
|
||||
DJANGO_REDIS_IGNORE_EXCEPTIONS = True
|
||||
DJANGO_REDIS_LOG_IGNORED_EXCEPTIONS = True
|
||||
match CONFIG.get("session_storage", "cache"):
|
||||
case "cache":
|
||||
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
|
||||
case "db":
|
||||
SESSION_ENGINE = "django.contrib.sessions.backends.db"
|
||||
case _:
|
||||
raise ImproperlyConfigured(
|
||||
"Invalid session_storage setting, allowed values are db and cache"
|
||||
)
|
||||
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
|
||||
SESSION_SERIALIZER = "authentik.root.sessions.pickle.PickleSerializer"
|
||||
SESSION_CACHE_ALIAS = "default"
|
||||
# Configured via custom SessionMiddleware
|
||||
@ -274,7 +276,7 @@ CHANNEL_LAYERS = {
|
||||
"default": {
|
||||
"BACKEND": "channels_redis.pubsub.RedisPubSubChannelLayer",
|
||||
"CONFIG": {
|
||||
"hosts": [CONFIG.get("channel.url") or redis_url(CONFIG.get("redis.db"))],
|
||||
"hosts": [CONFIG.get("channel.url", f"{_redis_url}/{CONFIG.get('redis.db')}")],
|
||||
"prefix": "authentik_channels_",
|
||||
},
|
||||
},
|
||||
@ -374,9 +376,11 @@ CELERY = {
|
||||
"beat_scheduler": "authentik.tenants.scheduler:TenantAwarePersistentScheduler",
|
||||
"task_create_missing_queues": True,
|
||||
"task_default_queue": "authentik",
|
||||
"broker_url": CONFIG.get("broker.url") or redis_url(CONFIG.get("redis.db")),
|
||||
"result_backend": CONFIG.get("result_backend.url") or redis_url(CONFIG.get("redis.db")),
|
||||
"broker_url": CONFIG.get("broker.url")
|
||||
or f"{_redis_url}/{CONFIG.get('redis.db')}{_redis_celery_tls_requirements}",
|
||||
"broker_transport_options": CONFIG.get_dict_from_b64_json("broker.transport_options"),
|
||||
"result_backend": CONFIG.get("result_backend.url")
|
||||
or f"{_redis_url}/{CONFIG.get('redis.db')}{_redis_celery_tls_requirements}",
|
||||
}
|
||||
|
||||
# Sentry integration
|
||||
|
@ -76,7 +76,7 @@ class S3Storage(BaseS3Storage):
|
||||
|
||||
return safe_join(self.location, connection.schema_name, name)
|
||||
except ValueError:
|
||||
raise SuspiciousOperation(f"Attempted access to '{name}' denied.") from None
|
||||
raise SuspiciousOperation("Attempted access to '%s' denied." % name) from None
|
||||
|
||||
# This is a fix for https://github.com/jschneier/django-storages/pull/839
|
||||
def url(self, name, parameters=None, expire=None, http_method=None):
|
||||
|
@ -34,7 +34,7 @@ def mock_ad_connection(password: str) -> Connection:
|
||||
"objectSid": "unique-test-group",
|
||||
"objectClass": "group",
|
||||
"distinguishedName": "cn=group1,ou=groups,dc=goauthentik,dc=io",
|
||||
"member": ["cn=user,ou=users,dc=goauthentik,dc=io"],
|
||||
"member": ["cn=user0,ou=users,dc=goauthentik,dc=io"],
|
||||
},
|
||||
)
|
||||
# Group without SID
|
||||
@ -47,7 +47,7 @@ def mock_ad_connection(password: str) -> Connection:
|
||||
},
|
||||
)
|
||||
connection.strategy.add_entry(
|
||||
"cn=user0,ou=foo,ou=users,dc=goauthentik,dc=io",
|
||||
"cn=user0,ou=users,dc=goauthentik,dc=io",
|
||||
{
|
||||
"userPassword": password,
|
||||
"sAMAccountName": "user0_sn",
|
||||
|
@ -55,7 +55,7 @@ class LDAPSyncTests(TestCase):
|
||||
)
|
||||
connection.assert_called_with(
|
||||
connection_kwargs={
|
||||
"user": "cn=user0,ou=foo,ou=users,dc=goauthentik,dc=io",
|
||||
"user": "cn=user0,ou=users,dc=goauthentik,dc=io",
|
||||
"password": LDAP_PASSWORD,
|
||||
}
|
||||
)
|
||||
|
@ -80,7 +80,7 @@ class OAuth2Client(BaseOAuthClient):
|
||||
access_token_url = self.source.source_type.access_token_url or ""
|
||||
if self.source.source_type.urls_customizable and self.source.access_token_url:
|
||||
access_token_url = self.source.access_token_url
|
||||
response = self.do_request(
|
||||
response = self.session.request(
|
||||
"post", access_token_url, data=args, headers=self._default_headers, **request_kwargs
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
@ -1,37 +0,0 @@
|
||||
"""Apple Type tests"""
|
||||
|
||||
from django.test import RequestFactory, TestCase
|
||||
from guardian.shortcuts import get_anonymous_user
|
||||
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.lib.tests.utils import dummy_get_response
|
||||
from authentik.root.middleware import SessionMiddleware
|
||||
from authentik.sources.oauth.models import OAuthSource
|
||||
from authentik.sources.oauth.types.registry import registry
|
||||
|
||||
|
||||
class TestTypeApple(TestCase):
|
||||
"""OAuth Source tests"""
|
||||
|
||||
def setUp(self):
|
||||
self.source = OAuthSource.objects.create(
|
||||
name="test",
|
||||
slug="test",
|
||||
provider_type="apple",
|
||||
authorization_url="",
|
||||
profile_url="",
|
||||
consumer_key=generate_id(),
|
||||
)
|
||||
self.factory = RequestFactory()
|
||||
|
||||
def test_login_challenge(self):
|
||||
"""Test login_challenge"""
|
||||
request = self.factory.get("/")
|
||||
request.user = get_anonymous_user()
|
||||
|
||||
middleware = SessionMiddleware(dummy_get_response)
|
||||
middleware.process_request(request)
|
||||
request.session.save()
|
||||
oauth_type = registry.find_type("apple")
|
||||
challenge = oauth_type().login_challenge(self.source, request)
|
||||
self.assertTrue(challenge.is_valid(raise_exception=True))
|
@ -125,7 +125,7 @@ class AppleType(SourceType):
|
||||
)
|
||||
args = apple_client.get_redirect_args()
|
||||
return AppleLoginChallenge(
|
||||
data={
|
||||
instance={
|
||||
"client_id": apple_client.get_client_id(),
|
||||
"scope": "name email",
|
||||
"redirect_uri": args["redirect_uri"],
|
||||
|
@ -66,7 +66,7 @@ class PlexSource(Source):
|
||||
icon = static("authentik/sources/plex.svg")
|
||||
return UILoginButton(
|
||||
challenge=PlexAuthenticationChallenge(
|
||||
data={
|
||||
{
|
||||
"type": ChallengeTypes.NATIVE.value,
|
||||
"component": "ak-source-plex",
|
||||
"client_id": self.client_id,
|
||||
|
@ -40,11 +40,6 @@ class TestPlexSource(TestCase):
|
||||
slug="test",
|
||||
)
|
||||
|
||||
def test_login_challenge(self):
|
||||
"""Test login_challenge"""
|
||||
ui_login_button = self.source.ui_login_button(None)
|
||||
self.assertTrue(ui_login_button.challenge.is_valid(raise_exception=True))
|
||||
|
||||
def test_get_user_info(self):
|
||||
"""Test get_user_info"""
|
||||
token = generate_key()
|
||||
|
@ -7,6 +7,7 @@ from rest_framework.viewsets import ModelViewSet
|
||||
from authentik.core.api.sources import SourceSerializer
|
||||
from authentik.core.api.tokens import TokenSerializer
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.models import Token, TokenIntents, User, UserTypes
|
||||
from authentik.sources.scim.models import SCIMSource
|
||||
|
||||
|
||||
@ -26,6 +27,25 @@ class SCIMSourceSerializer(SourceSerializer):
|
||||
return relative_url
|
||||
return self.context["request"].build_absolute_uri(relative_url)
|
||||
|
||||
def create(self, validated_data):
|
||||
instance: SCIMSource = super().create(validated_data)
|
||||
identifier = f"ak-source-scim-{instance.pk}"
|
||||
user = User.objects.create(
|
||||
username=identifier,
|
||||
name=f"SCIM Source {instance.name} Service-Account",
|
||||
type=UserTypes.SERVICE_ACCOUNT,
|
||||
)
|
||||
token = Token.objects.create(
|
||||
user=user,
|
||||
identifier=identifier,
|
||||
intent=TokenIntents.INTENT_API,
|
||||
expiring=False,
|
||||
managed=f"goauthentik.io/sources/scim/{instance.pk}",
|
||||
)
|
||||
instance.token = token
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
class Meta:
|
||||
|
||||
model = SCIMSource
|
||||
|
@ -1,13 +1,12 @@
|
||||
"""Authentik SCIM app config"""
|
||||
|
||||
from authentik.blueprints.apps import ManagedAppConfig
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AuthentikSourceSCIMConfig(ManagedAppConfig):
|
||||
class AuthentikSourceSCIMConfig(AppConfig):
|
||||
"""authentik SCIM Source app config"""
|
||||
|
||||
name = "authentik.sources.scim"
|
||||
label = "authentik_sources_scim"
|
||||
verbose_name = "authentik Sources.SCIM"
|
||||
mountpoint = "source/scim/"
|
||||
default = True
|
||||
|
@ -1,7 +1,5 @@
|
||||
"""SCIM Source"""
|
||||
|
||||
from uuid import uuid4
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework.serializers import BaseSerializer
|
||||
@ -16,12 +14,6 @@ class SCIMSource(Source):
|
||||
|
||||
token = models.ForeignKey(Token, on_delete=models.CASCADE, null=True, default=None)
|
||||
|
||||
@property
|
||||
def service_account_identifier(self) -> str:
|
||||
if not self.pk:
|
||||
self.pk = uuid4()
|
||||
return f"ak-source-scim-{self.pk}"
|
||||
|
||||
@property
|
||||
def component(self) -> str:
|
||||
"""Return component used to edit this object"""
|
||||
@ -60,7 +52,7 @@ class SCIMSourceUser(SerializerModel):
|
||||
unique_together = (("id", "user", "source"),)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"SCIM User {self.user_id} to {self.source_id}"
|
||||
return f"SCIM User {self.user.username} to {self.source.name}"
|
||||
|
||||
|
||||
class SCIMSourceGroup(SerializerModel):
|
||||
@ -81,4 +73,4 @@ class SCIMSourceGroup(SerializerModel):
|
||||
unique_together = (("id", "group", "source"),)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"SCIM Group {self.group_id} to {self.source_id}"
|
||||
return f"SCIM Group {self.group.name} to {self.source.name}"
|
||||
|
@ -1,41 +0,0 @@
|
||||
from django.db.models import Model
|
||||
from django.db.models.signals import pre_delete, pre_save
|
||||
from django.dispatch import receiver
|
||||
|
||||
from authentik.core.models import USER_PATH_SYSTEM_PREFIX, Token, TokenIntents, User, UserTypes
|
||||
from authentik.sources.scim.models import SCIMSource
|
||||
|
||||
USER_PATH_SOURCE_SCIM = USER_PATH_SYSTEM_PREFIX + "/sources/scim"
|
||||
|
||||
|
||||
@receiver(pre_save, sender=SCIMSource)
|
||||
def scim_source_pre_save(sender: type[Model], instance: SCIMSource, **_):
|
||||
"""Create service account before source is saved"""
|
||||
# .service_account_identifier will auto-assign a primary key uuid to the source
|
||||
# if none is set yet, just so we can get the identifier before we save
|
||||
identifier = instance.service_account_identifier
|
||||
user = User.objects.create(
|
||||
username=identifier,
|
||||
name=f"SCIM Source {instance.name} Service-Account",
|
||||
type=UserTypes.INTERNAL_SERVICE_ACCOUNT,
|
||||
path=USER_PATH_SOURCE_SCIM,
|
||||
)
|
||||
token = Token.objects.create(
|
||||
user=user,
|
||||
identifier=identifier,
|
||||
intent=TokenIntents.INTENT_API,
|
||||
expiring=False,
|
||||
managed=f"goauthentik.io/sources/scim/{instance.pk}",
|
||||
)
|
||||
instance.token = token
|
||||
|
||||
|
||||
@receiver(pre_delete, sender=SCIMSource)
|
||||
def scim_source_pre_delete(sender: type[Model], instance: SCIMSource, **_):
|
||||
"""Delete SCIM Source service account before deleting source"""
|
||||
Token.objects.filter(
|
||||
identifier=instance.service_account_identifier, intent=TokenIntents.INTENT_API
|
||||
).delete()
|
||||
User.objects.filter(
|
||||
username=instance.service_account_identifier, type=UserTypes.INTERNAL_SERVICE_ACCOUNT
|
||||
).delete()
|
@ -14,13 +14,27 @@ class TestSCIMAuth(APITestCase):
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.user = create_test_admin_user()
|
||||
self.token = Token.objects.create(
|
||||
user=self.user,
|
||||
identifier=generate_id(),
|
||||
intent=TokenIntents.INTENT_API,
|
||||
)
|
||||
self.token2 = Token.objects.create(
|
||||
user=self.user,
|
||||
identifier=generate_id(),
|
||||
intent=TokenIntents.INTENT_API,
|
||||
)
|
||||
self.token3 = Token.objects.create(
|
||||
user=self.user,
|
||||
identifier=generate_id(),
|
||||
intent=TokenIntents.INTENT_API,
|
||||
)
|
||||
self.source = SCIMSource.objects.create(name=generate_id(), slug=generate_id())
|
||||
self.source2 = SCIMSource.objects.create(name=generate_id(), slug=generate_id())
|
||||
self.source = SCIMSource.objects.create(
|
||||
name=generate_id(), slug=generate_id(), token=self.token
|
||||
)
|
||||
self.source2 = SCIMSource.objects.create(
|
||||
name=generate_id(), slug=generate_id(), token=self.token2
|
||||
)
|
||||
|
||||
def test_auth_ok(self):
|
||||
"""Test successful auth"""
|
||||
@ -31,7 +45,7 @@ class TestSCIMAuth(APITestCase):
|
||||
"source_slug": self.source.slug,
|
||||
},
|
||||
),
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.token.key}",
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
@ -57,7 +71,7 @@ class TestSCIMAuth(APITestCase):
|
||||
"source_slug": self.source.slug,
|
||||
},
|
||||
),
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.source2.token.key}",
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.token2.key}",
|
||||
)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
# Token for no source
|
||||
|
@ -3,6 +3,8 @@
|
||||
from django.urls import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.models import Token, TokenIntents
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.sources.scim.models import SCIMSource
|
||||
|
||||
@ -11,9 +13,14 @@ class TestSCIMResourceTypes(APITestCase):
|
||||
"""Test SCIM ResourceTypes view"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.user = create_test_admin_user()
|
||||
self.token = Token.objects.create(
|
||||
user=self.user,
|
||||
identifier=generate_id(),
|
||||
intent=TokenIntents.INTENT_API,
|
||||
)
|
||||
self.source = SCIMSource.objects.create(
|
||||
name=generate_id(),
|
||||
slug=generate_id(),
|
||||
name=generate_id(), slug=generate_id(), token=self.token
|
||||
)
|
||||
|
||||
def test_resource_type(self):
|
||||
@ -25,7 +32,7 @@ class TestSCIMResourceTypes(APITestCase):
|
||||
"source_slug": self.source.slug,
|
||||
},
|
||||
),
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.token.key}",
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
@ -39,7 +46,7 @@ class TestSCIMResourceTypes(APITestCase):
|
||||
"resource_type": "ServiceProviderConfig",
|
||||
},
|
||||
),
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.token.key}",
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
@ -53,6 +60,6 @@ class TestSCIMResourceTypes(APITestCase):
|
||||
"resource_type": "foo",
|
||||
},
|
||||
),
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.token.key}",
|
||||
)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
@ -3,6 +3,8 @@
|
||||
from django.urls import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.models import Token, TokenIntents
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.sources.scim.models import SCIMSource
|
||||
|
||||
@ -11,7 +13,15 @@ class TestSCIMSchemas(APITestCase):
|
||||
"""Test SCIM Schema view"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.source = SCIMSource.objects.create(name=generate_id(), slug=generate_id())
|
||||
self.user = create_test_admin_user()
|
||||
self.token = Token.objects.create(
|
||||
user=self.user,
|
||||
identifier=generate_id(),
|
||||
intent=TokenIntents.INTENT_API,
|
||||
)
|
||||
self.source = SCIMSource.objects.create(
|
||||
name=generate_id(), slug=generate_id(), token=self.token
|
||||
)
|
||||
|
||||
def test_schema(self):
|
||||
"""Test full schema view"""
|
||||
@ -22,7 +32,7 @@ class TestSCIMSchemas(APITestCase):
|
||||
"source_slug": self.source.slug,
|
||||
},
|
||||
),
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.token.key}",
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
@ -36,7 +46,7 @@ class TestSCIMSchemas(APITestCase):
|
||||
"schema_uri": "urn:ietf:params:scim:schemas:core:2.0:Meta",
|
||||
},
|
||||
),
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.token.key}",
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
@ -50,6 +60,6 @@ class TestSCIMSchemas(APITestCase):
|
||||
"schema_uri": "foo",
|
||||
},
|
||||
),
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.token.key}",
|
||||
)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
@ -3,6 +3,8 @@
|
||||
from django.urls import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.models import Token, TokenIntents
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.sources.scim.models import SCIMSource
|
||||
|
||||
@ -11,9 +13,14 @@ class TestSCIMServiceProviderConfig(APITestCase):
|
||||
"""Test SCIM ServiceProviderConfig view"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.user = create_test_admin_user()
|
||||
self.token = Token.objects.create(
|
||||
user=self.user,
|
||||
identifier=generate_id(),
|
||||
intent=TokenIntents.INTENT_API,
|
||||
)
|
||||
self.source = SCIMSource.objects.create(
|
||||
name=generate_id(),
|
||||
slug=generate_id(),
|
||||
name=generate_id(), slug=generate_id(), token=self.token
|
||||
)
|
||||
|
||||
def test_config(self):
|
||||
@ -25,6 +32,6 @@ class TestSCIMServiceProviderConfig(APITestCase):
|
||||
"source_slug": self.source.slug,
|
||||
},
|
||||
),
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.token.key}",
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
@ -1,27 +0,0 @@
|
||||
"""Test SCIM Source creation"""
|
||||
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.models import Token, User
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.sources.scim.models import SCIMSource
|
||||
|
||||
|
||||
class TestSCIMSignals(APITestCase):
|
||||
"""Test SCIM Signals view"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.uid = generate_id()
|
||||
|
||||
def test_create(self) -> None:
|
||||
source = SCIMSource.objects.create(name=self.uid, slug=self.uid)
|
||||
self.assertIsNotNone(source.token)
|
||||
self.assertIsNotNone(source.token.user)
|
||||
|
||||
def test_delete(self):
|
||||
self.test_create()
|
||||
source = SCIMSource.objects.filter(slug=self.uid).first()
|
||||
identifier = source.service_account_identifier
|
||||
source.delete()
|
||||
self.assertFalse(User.objects.filter(username=identifier).exists())
|
||||
self.assertFalse(Token.objects.filter(identifier=identifier).exists())
|
@ -6,8 +6,8 @@ from uuid import uuid4
|
||||
from django.urls import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.tests.utils import create_test_user
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.core.models import Token, TokenIntents
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.providers.scim.clients.schema import User as SCIMUserSchema
|
||||
from authentik.sources.scim.models import SCIMSource, SCIMSourceUser
|
||||
@ -18,7 +18,15 @@ class TestSCIMUsers(APITestCase):
|
||||
"""Test SCIM User view"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.source = SCIMSource.objects.create(name=generate_id(), slug=generate_id())
|
||||
self.user = create_test_admin_user()
|
||||
self.token = Token.objects.create(
|
||||
user=self.user,
|
||||
identifier=generate_id(),
|
||||
intent=TokenIntents.INTENT_API,
|
||||
)
|
||||
self.source = SCIMSource.objects.create(
|
||||
name=generate_id(), slug=generate_id(), token=self.token
|
||||
)
|
||||
|
||||
def test_user_list(self):
|
||||
"""Test full user list"""
|
||||
@ -29,16 +37,15 @@ class TestSCIMUsers(APITestCase):
|
||||
"source_slug": self.source.slug,
|
||||
},
|
||||
),
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.token.key}",
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_user_list_single(self):
|
||||
"""Test full user list (single user)"""
|
||||
user = create_test_user()
|
||||
SCIMSourceUser.objects.create(
|
||||
source=self.source,
|
||||
user=user,
|
||||
user=self.user,
|
||||
id=str(uuid4()),
|
||||
)
|
||||
response = self.client.get(
|
||||
@ -46,17 +53,16 @@ class TestSCIMUsers(APITestCase):
|
||||
"authentik_sources_scim:v2-users",
|
||||
kwargs={
|
||||
"source_slug": self.source.slug,
|
||||
"user_id": str(user.uuid),
|
||||
"user_id": str(self.user.uuid),
|
||||
},
|
||||
),
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.token.key}",
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
SCIMUserSchema.model_validate_json(response.content, strict=True)
|
||||
|
||||
def test_user_create(self):
|
||||
"""Test user create"""
|
||||
user = create_test_user()
|
||||
ext_id = generate_id()
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
@ -72,18 +78,13 @@ class TestSCIMUsers(APITestCase):
|
||||
"emails": [
|
||||
{
|
||||
"primary": True,
|
||||
"value": user.email,
|
||||
"value": self.user.email,
|
||||
}
|
||||
],
|
||||
}
|
||||
),
|
||||
content_type=SCIM_CONTENT_TYPE,
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.token.key}",
|
||||
)
|
||||
self.assertEqual(response.status_code, 201)
|
||||
self.assertTrue(SCIMSourceUser.objects.filter(source=self.source, id=ext_id).exists())
|
||||
self.assertTrue(
|
||||
Event.objects.filter(
|
||||
action=EventAction.MODEL_CREATED, user__username=self.source.token.user.username
|
||||
).exists()
|
||||
)
|
||||
|
@ -96,7 +96,7 @@ class DuoDevice(SerializerModel, Device):
|
||||
return DuoDeviceSerializer
|
||||
|
||||
def __str__(self):
|
||||
return str(self.name) or str(self.user_id)
|
||||
return str(self.name) or str(self.user)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Duo Device")
|
||||
|
@ -221,7 +221,7 @@ class SMSDevice(SerializerModel, SideChannelDevice):
|
||||
return valid
|
||||
|
||||
def __str__(self):
|
||||
return str(self.name) or str(self.user_id)
|
||||
return str(self.name) or str(self.user)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("SMS Device")
|
||||
|
@ -20,10 +20,7 @@ class WebAuthnDeviceSerializer(ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = WebAuthnDevice
|
||||
fields = ["pk", "name", "created_on", "device_type", "aaguid"]
|
||||
extra_kwargs = {
|
||||
"aaguid": {"read_only": True},
|
||||
}
|
||||
fields = ["pk", "name", "created_on", "device_type"]
|
||||
|
||||
|
||||
class WebAuthnDeviceViewSet(
|
||||
|
@ -1,168 +0,0 @@
|
||||
# Generated by Django 5.0.4 on 2024-04-18 11:29
|
||||
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [
|
||||
("authentik_stages_authenticator_webauthn", "0001_initial"),
|
||||
("authentik_stages_authenticator_webauthn", "0002_default_setup_flow"),
|
||||
("authentik_stages_authenticator_webauthn", "0003_webauthndevice_confirmed"),
|
||||
("authentik_stages_authenticator_webauthn", "0004_auto_20210304_1850"),
|
||||
(
|
||||
"authentik_stages_authenticator_webauthn",
|
||||
"0005_authenticatewebauthnstage_user_verification",
|
||||
),
|
||||
(
|
||||
"authentik_stages_authenticator_webauthn",
|
||||
"0006_authenticatewebauthnstage_authenticator_attachment_and_more",
|
||||
),
|
||||
(
|
||||
"authentik_stages_authenticator_webauthn",
|
||||
"0007_rename_last_used_on_webauthndevice_last_t",
|
||||
),
|
||||
("authentik_stages_authenticator_webauthn", "0008_alter_webauthndevice_credential_id"),
|
||||
("authentik_stages_authenticator_webauthn", "0009_authenticatewebauthnstage_friendly_name"),
|
||||
(
|
||||
"authentik_stages_authenticator_webauthn",
|
||||
"0010_webauthndevicetype_authenticatorwebauthnstage_and_more",
|
||||
),
|
||||
("authentik_stages_authenticator_webauthn", "0011_webauthndevice_aaguid"),
|
||||
]
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("authentik_flows", "0016_auto_20201202_1307"),
|
||||
("authentik_flows", "0027_auto_20231028_1424"),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="WebAuthnDeviceType",
|
||||
fields=[
|
||||
("aaguid", models.UUIDField(primary_key=True, serialize=False, unique=True)),
|
||||
("description", models.TextField()),
|
||||
("icon", models.TextField(null=True)),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "WebAuthn Device type",
|
||||
"verbose_name_plural": "WebAuthn Device types",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="AuthenticatorWebAuthnStage",
|
||||
fields=[
|
||||
(
|
||||
"stage_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="authentik_flows.stage",
|
||||
),
|
||||
),
|
||||
(
|
||||
"configure_flow",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Flow used by an authenticated user to configure this Stage. If empty, user will not be able to configure this stage.",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="authentik_flows.flow",
|
||||
),
|
||||
),
|
||||
(
|
||||
"user_verification",
|
||||
models.TextField(
|
||||
choices=[
|
||||
("required", "Required"),
|
||||
("preferred", "Preferred"),
|
||||
("discouraged", "Discouraged"),
|
||||
],
|
||||
default="preferred",
|
||||
),
|
||||
),
|
||||
(
|
||||
"authenticator_attachment",
|
||||
models.TextField(
|
||||
choices=[("platform", "Platform"), ("cross-platform", "Cross Platform")],
|
||||
default=None,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
"resident_key_requirement",
|
||||
models.TextField(
|
||||
choices=[
|
||||
("discouraged", "Discouraged"),
|
||||
("preferred", "Preferred"),
|
||||
("required", "Required"),
|
||||
],
|
||||
default="preferred",
|
||||
),
|
||||
),
|
||||
("friendly_name", models.TextField(null=True)),
|
||||
(
|
||||
"device_type_restrictions",
|
||||
models.ManyToManyField(
|
||||
blank=True, to="authentik_stages_authenticator_webauthn.webauthndevicetype"
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "WebAuthn Authenticator Setup Stage",
|
||||
"verbose_name_plural": "WebAuthn Authenticator Setup Stages",
|
||||
},
|
||||
bases=("authentik_flows.stage", models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="WebAuthnDevice",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||
),
|
||||
),
|
||||
("name", models.TextField(max_length=200)),
|
||||
("credential_id", models.TextField(unique=True)),
|
||||
("public_key", models.TextField()),
|
||||
("sign_count", models.IntegerField(default=0)),
|
||||
("rp_id", models.CharField(max_length=253)),
|
||||
("created_on", models.DateTimeField(auto_now_add=True)),
|
||||
("last_t", models.DateTimeField(default=django.utils.timezone.now)),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
|
||||
),
|
||||
),
|
||||
(
|
||||
"confirmed",
|
||||
models.BooleanField(default=True, help_text="Is this device ready for use?"),
|
||||
),
|
||||
(
|
||||
"device_type",
|
||||
models.ForeignKey(
|
||||
default=None,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_DEFAULT,
|
||||
to="authentik_stages_authenticator_webauthn.webauthndevicetype",
|
||||
),
|
||||
),
|
||||
("aaguid", models.TextField(default="00000000-0000-0000-0000-000000000000")),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "WebAuthn Device",
|
||||
"verbose_name_plural": "WebAuthn Devices",
|
||||
},
|
||||
),
|
||||
]
|
@ -1,21 +0,0 @@
|
||||
# Generated by Django 5.0.4 on 2024-04-18 11:27
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
(
|
||||
"authentik_stages_authenticator_webauthn",
|
||||
"0010_webauthndevicetype_authenticatorwebauthnstage_and_more",
|
||||
),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="webauthndevice",
|
||||
name="aaguid",
|
||||
field=models.TextField(default="00000000-0000-0000-0000-000000000000"),
|
||||
),
|
||||
]
|
@ -132,7 +132,6 @@ class WebAuthnDevice(SerializerModel, Device):
|
||||
created_on = models.DateTimeField(auto_now_add=True)
|
||||
last_t = models.DateTimeField(default=now)
|
||||
|
||||
aaguid = models.TextField(default=UNKNOWN_DEVICE_TYPE_AAGUID)
|
||||
device_type = models.ForeignKey(
|
||||
"WebAuthnDeviceType", on_delete=models.SET_DEFAULT, null=True, default=None
|
||||
)
|
||||
@ -155,7 +154,7 @@ class WebAuthnDevice(SerializerModel, Device):
|
||||
return WebAuthnDeviceSerializer
|
||||
|
||||
def __str__(self):
|
||||
return str(self.name) or str(self.user_id)
|
||||
return str(self.name) or str(self.user)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("WebAuthn Device")
|
||||
|
@ -126,6 +126,10 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView):
|
||||
if authenticator_attachment:
|
||||
authenticator_attachment = AuthenticatorAttachment(str(authenticator_attachment))
|
||||
|
||||
attestation = AttestationConveyancePreference.DIRECT
|
||||
if stage.device_type_restrictions.exists():
|
||||
attestation = AttestationConveyancePreference.ENTERPRISE
|
||||
|
||||
registration_options: PublicKeyCredentialCreationOptions = generate_registration_options(
|
||||
rp_id=get_rp_id(self.request),
|
||||
rp_name=self.request.brand.branding_title,
|
||||
@ -137,7 +141,7 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView):
|
||||
user_verification=UserVerificationRequirement(str(stage.user_verification)),
|
||||
authenticator_attachment=authenticator_attachment,
|
||||
),
|
||||
attestation=AttestationConveyancePreference.DIRECT,
|
||||
attestation=attestation,
|
||||
)
|
||||
|
||||
self.request.session[SESSION_KEY_WEBAUTHN_CHALLENGE] = registration_options.challenge
|
||||
@ -176,7 +180,6 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView):
|
||||
sign_count=webauthn_credential.sign_count,
|
||||
rp_id=get_rp_id(self.request),
|
||||
device_type=device_type,
|
||||
aaguid=webauthn_credential.aaguid,
|
||||
)
|
||||
else:
|
||||
return self.executor.stage_invalid("Device with Credential ID already exists.")
|
||||
|
@ -65,7 +65,7 @@ class UserConsent(SerializerModel, ExpiringModel):
|
||||
return UserConsentSerializer
|
||||
|
||||
def __str__(self):
|
||||
return f"User Consent {self.application_id} by {self.user_id}"
|
||||
return f"User Consent {self.application} by {self.user}"
|
||||
|
||||
class Meta:
|
||||
unique_together = (("user", "application", "permissions"),)
|
||||
|
@ -79,7 +79,7 @@ class Invitation(SerializerModel, ExpiringModel):
|
||||
return InvitationSerializer
|
||||
|
||||
def __str__(self):
|
||||
return f"Invitation {str(self.invite_uuid)} created by {self.created_by_id}"
|
||||
return f"Invitation {str(self.invite_uuid)} created by {self.created_by}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Invitation")
|
||||
|
@ -150,26 +150,22 @@ class PromptChallengeResponse(ChallengeResponse):
|
||||
return attrs
|
||||
|
||||
|
||||
def username_field_validator_factory() -> Callable[[PromptChallengeResponse, str], Any]:
|
||||
def username_field_validator_factory() -> Callable[[PromptChallenge, str], Any]:
|
||||
"""Return a `clean_` method for `field`. Clean method checks if username is taken already."""
|
||||
|
||||
def username_field_validator(self: PromptChallengeResponse, value: str) -> Any:
|
||||
def username_field_validator(_: PromptChallenge, value: str) -> Any:
|
||||
"""Check for duplicate usernames"""
|
||||
pending_user = self.stage.get_pending_user()
|
||||
query = User.objects.all()
|
||||
if pending_user.pk:
|
||||
query = query.exclude(username=pending_user.username)
|
||||
if query.filter(username=value).exists():
|
||||
if User.objects.filter(username=value).exists():
|
||||
raise ValidationError("Username is already taken.")
|
||||
return value
|
||||
|
||||
return username_field_validator
|
||||
|
||||
|
||||
def password_single_validator_factory() -> Callable[[PromptChallengeResponse, str], Any]:
|
||||
def password_single_validator_factory() -> Callable[[PromptChallenge, str], Any]:
|
||||
"""Return a `clean_` method for `field`. Clean method checks if username is taken already."""
|
||||
|
||||
def password_single_clean(self: PromptChallengeResponse, value: str) -> Any:
|
||||
def password_single_clean(self: PromptChallenge, value: str) -> Any:
|
||||
"""Send password validation signals for e.g. LDAP Source"""
|
||||
password_validate.send(sender=self, password=value, plan_context=self.plan.context)
|
||||
return value
|
||||
|
@ -9,7 +9,6 @@ from django.utils.translation import gettext as _
|
||||
from rest_framework.fields import BooleanField, CharField
|
||||
|
||||
from authentik.core.models import AuthenticatedSession, User
|
||||
from authentik.events.middleware import audit_ignore
|
||||
from authentik.flows.challenge import ChallengeResponse, ChallengeTypes, WithUserInfoChallenge
|
||||
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, PLAN_CONTEXT_SOURCE
|
||||
from authentik.flows.stage import ChallengeStageView
|
||||
@ -96,14 +95,11 @@ class UserLoginStageView(ChallengeStageView):
|
||||
self.logger.warning("User is not active, login will not work.")
|
||||
delta = self.set_session_duration(remember)
|
||||
self.set_session_ip()
|
||||
# the `user_logged_in` signal will update the user to write the `last_login` field
|
||||
# which we don't want to log as we already have a dedicated login event
|
||||
with audit_ignore():
|
||||
login(
|
||||
self.request,
|
||||
user,
|
||||
backend=backend,
|
||||
)
|
||||
login(
|
||||
self.request,
|
||||
user,
|
||||
backend=backend,
|
||||
)
|
||||
self.logger.debug(
|
||||
"Logged in",
|
||||
backend=backend,
|
||||
|
@ -23,7 +23,7 @@ LOGGER = get_logger()
|
||||
|
||||
VALID_SCHEMA_NAME = re.compile(r"^t_[a-z0-9]{1,61}$")
|
||||
|
||||
DEFAULT_TOKEN_DURATION = "days=1" # nosec
|
||||
DEFAULT_TOKEN_DURATION = "minutes=30" # nosec
|
||||
DEFAULT_TOKEN_LENGTH = 60
|
||||
|
||||
|
||||
|
@ -16,7 +16,7 @@ entries:
|
||||
placeholder: Username
|
||||
placeholder_expression: false
|
||||
required: true
|
||||
type: username
|
||||
type: text
|
||||
field_key: username
|
||||
label: Username
|
||||
identifiers:
|
||||
|
@ -85,19 +85,30 @@ entries:
|
||||
model: authentik_stages_prompt.prompt
|
||||
- attrs:
|
||||
expression: |
|
||||
from authentik.core.models import (
|
||||
USER_ATTRIBUTE_CHANGE_EMAIL,
|
||||
USER_ATTRIBUTE_CHANGE_NAME,
|
||||
USER_ATTRIBUTE_CHANGE_USERNAME
|
||||
)
|
||||
prompt_data = request.context.get("prompt_data")
|
||||
user_group_attributes = request.user.group_attributes(request.http_request)
|
||||
if not user_group_attributes.get(USER_ATTRIBUTE_CHANGE_EMAIL, request.http_request.tenant.default_user_change_email):
|
||||
|
||||
if not request.user.group_attributes(request.http_request).get(
|
||||
USER_ATTRIBUTE_CHANGE_EMAIL, request.http_request.tenant.default_user_change_email
|
||||
):
|
||||
if prompt_data.get("email") != request.user.email:
|
||||
ak_message("Not allowed to change email address.")
|
||||
return False
|
||||
|
||||
if not user_group_attributes.get(USER_ATTRIBUTE_CHANGE_NAME, request.http_request.tenant.default_user_change_name):
|
||||
if not request.user.group_attributes(request.http_request).get(
|
||||
USER_ATTRIBUTE_CHANGE_NAME, request.http_request.tenant.default_user_change_name
|
||||
):
|
||||
if prompt_data.get("name") != request.user.name:
|
||||
ak_message("Not allowed to change name.")
|
||||
return False
|
||||
|
||||
if not user_group_attributes.get(USER_ATTRIBUTE_CHANGE_USERNAME, request.http_request.tenant.default_user_change_username):
|
||||
if not request.user.group_attributes(request.http_request).get(
|
||||
USER_ATTRIBUTE_CHANGE_USERNAME, request.http_request.tenant.default_user_change_username
|
||||
):
|
||||
if prompt_data.get("username") != request.user.username:
|
||||
ak_message("Not allowed to change username.")
|
||||
return False
|
||||
|
@ -89,9 +89,10 @@ entries:
|
||||
expression: |
|
||||
# This policy ensures that the setup flow can only be
|
||||
# used one time
|
||||
FlowsApi(api).flows_instances_partial_update("initial-setup", {
|
||||
"authentication": "REQUIRE_SUPERUSER"
|
||||
})
|
||||
from authentik.flows.models import Flow, FlowAuthenticationRequirement
|
||||
Flow.objects.filter(slug="initial-setup").update(
|
||||
authentication=FlowAuthenticationRequirement.REQUIRE_SUPERUSER,
|
||||
)
|
||||
return True
|
||||
id: policy-default-oobe-flow-set-authentication
|
||||
identifiers:
|
||||
|
@ -2,7 +2,7 @@
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "https://goauthentik.io/blueprints/schema.json",
|
||||
"type": "object",
|
||||
"title": "authentik 2024.4.1 Blueprint schema",
|
||||
"title": "authentik 2024.2.2 Blueprint schema",
|
||||
"required": [
|
||||
"version",
|
||||
"entries"
|
||||
@ -3477,10 +3477,6 @@
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"title": "Expression"
|
||||
},
|
||||
"execution_user": {
|
||||
"type": "integer",
|
||||
"title": "Execution user"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
|
@ -11,6 +11,7 @@ entries:
|
||||
name: "authentik default LDAP Mapping: DN to User Path"
|
||||
object_field: "path"
|
||||
expression: |
|
||||
dn = ldap.get("distinguishedName")
|
||||
path_elements = []
|
||||
for pair in dn.split(","):
|
||||
attr, _, value = pair.partition("=")
|
||||
|
@ -32,7 +32,7 @@ services:
|
||||
volumes:
|
||||
- redis:/data
|
||||
server:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.4.1}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.2.2}
|
||||
restart: unless-stopped
|
||||
command: server
|
||||
environment:
|
||||
@ -53,7 +53,7 @@ services:
|
||||
- postgresql
|
||||
- redis
|
||||
worker:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.4.1}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.2.2}
|
||||
restart: unless-stopped
|
||||
command: worker
|
||||
environment:
|
||||
|
10
go.mod
10
go.mod
@ -1,13 +1,15 @@
|
||||
module goauthentik.io
|
||||
|
||||
go 1.22.2
|
||||
go 1.22
|
||||
|
||||
toolchain go1.22.0
|
||||
|
||||
require (
|
||||
beryju.io/ldap v0.1.0
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible
|
||||
github.com/getsentry/sentry-go v0.27.0
|
||||
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
|
||||
github.com/go-ldap/ldap/v3 v3.4.8
|
||||
github.com/go-ldap/ldap/v3 v3.4.7
|
||||
github.com/go-openapi/runtime v0.28.0
|
||||
github.com/go-openapi/strfmt v0.23.0
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
@ -28,7 +30,7 @@ require (
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/wwt/guac v1.3.2
|
||||
goauthentik.io/api/v3 v3.2024041.1
|
||||
goauthentik.io/api/v3 v3.2024022.11
|
||||
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
||||
golang.org/x/oauth2 v0.19.0
|
||||
golang.org/x/sync v0.7.0
|
||||
@ -73,7 +75,7 @@ require (
|
||||
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||
golang.org/x/crypto v0.21.0 // indirect
|
||||
golang.org/x/net v0.23.0 // indirect
|
||||
golang.org/x/net v0.22.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
|
11
go.sum
11
go.sum
@ -84,8 +84,8 @@ github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27 h1:O6yi4xa9b2D
|
||||
github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27/go.mod h1:AYvN8omj7nKLmbcXS2dyABYU6JB1Lz1bHmkkq1kf4I4=
|
||||
github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a h1:v6zMvHuY9yue4+QkG/HQ/W67wvtQmWJ4SDo9aK/GIno=
|
||||
github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a/go.mod h1:I79BieaU4fxrw4LMXby6q5OS9XnoR9UIKLOzDFjUmuw=
|
||||
github.com/go-ldap/ldap/v3 v3.4.8 h1:loKJyspcRezt2Q3ZRMq2p/0v8iOurlmeXDPw6fikSvQ=
|
||||
github.com/go-ldap/ldap/v3 v3.4.8/go.mod h1:qS3Sjlu76eHfHGpUdWkAXQTw4beih+cHsco2jXlIXrk=
|
||||
github.com/go-ldap/ldap/v3 v3.4.7 h1:3Hbd7mIB1qjd3Ra59fI3JYea/t5kykFu2CVHBca9koE=
|
||||
github.com/go-ldap/ldap/v3 v3.4.7/go.mod h1:qS3Sjlu76eHfHGpUdWkAXQTw4beih+cHsco2jXlIXrk=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
@ -294,8 +294,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.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
||||
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
||||
goauthentik.io/api/v3 v3.2024041.1 h1:oYj6DYqmZJd6/wyknBZLnLa+4+ShT4ry7HQn0W8VXxY=
|
||||
goauthentik.io/api/v3 v3.2024041.1/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
||||
goauthentik.io/api/v3 v3.2024022.11 h1:MlsaBwyMM9NtDvZcoaWvuNznPHXA0a5olnDLyr24REA=
|
||||
goauthentik.io/api/v3 v3.2024022.11/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=
|
||||
@ -373,9 +373,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
|
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -25,14 +25,13 @@ type Config struct {
|
||||
}
|
||||
|
||||
type RedisConfig struct {
|
||||
Host string `yaml:"host" env:"HOST, overwrite"`
|
||||
Port int `yaml:"port" env:"PORT, overwrite"`
|
||||
DB int `yaml:"db" env:"DB, overwrite"`
|
||||
Username string `yaml:"username" env:"USERNAME, overwrite"`
|
||||
Password string `yaml:"password" env:"PASSWORD, overwrite"`
|
||||
TLS bool `yaml:"tls" env:"TLS, overwrite"`
|
||||
TLSReqs string `yaml:"tls_reqs" env:"TLS_REQS, overwrite"`
|
||||
TLSCaCert *string `yaml:"tls_ca_certs" env:"TLS_CA_CERT, overwrite"`
|
||||
Host string `yaml:"host" env:"HOST, overwrite"`
|
||||
Port int `yaml:"port" env:"PORT, overwrite"`
|
||||
DB int `yaml:"db" env:"DB, overwrite"`
|
||||
Username string `yaml:"username" env:"USERNAME, overwrite"`
|
||||
Password string `yaml:"password" env:"PASSWORD, overwrite"`
|
||||
TLS bool `yaml:"tls" env:"TLS, overwrite"`
|
||||
TLSReqs string `yaml:"tls_reqs" env:"TLS_REQS, overwrite"`
|
||||
}
|
||||
|
||||
type ListenConfig struct {
|
||||
|
@ -29,4 +29,4 @@ func UserAgent() string {
|
||||
return fmt.Sprintf("authentik@%s", FullVersion())
|
||||
}
|
||||
|
||||
const VERSION = "2024.4.1"
|
||||
const VERSION = "2024.2.2"
|
||||
|
@ -2,8 +2,6 @@ package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
@ -21,7 +19,6 @@ import (
|
||||
"goauthentik.io/internal/outpost/proxyv2/codecs"
|
||||
"goauthentik.io/internal/outpost/proxyv2/constants"
|
||||
"goauthentik.io/internal/outpost/proxyv2/redisstore"
|
||||
"goauthentik.io/internal/utils"
|
||||
)
|
||||
|
||||
const RedisKeyPrefix = "authentik_proxy_session_"
|
||||
@ -34,40 +31,11 @@ func (a *Application) getStore(p api.ProxyOutpostConfig, externalHost *url.URL)
|
||||
maxAge = int(*t) + 1
|
||||
}
|
||||
if a.isEmbedded {
|
||||
var tls *tls.Config
|
||||
if config.Get().Redis.TLS {
|
||||
tls = utils.GetTLSConfig()
|
||||
switch strings.ToLower(config.Get().Redis.TLSReqs) {
|
||||
case "none":
|
||||
case "false":
|
||||
tls.InsecureSkipVerify = true
|
||||
case "required":
|
||||
break
|
||||
}
|
||||
ca := config.Get().Redis.TLSCaCert
|
||||
if ca != nil {
|
||||
// Get the SystemCertPool, continue with an empty pool on error
|
||||
rootCAs, _ := x509.SystemCertPool()
|
||||
if rootCAs == nil {
|
||||
rootCAs = x509.NewCertPool()
|
||||
}
|
||||
certs, err := os.ReadFile(*ca)
|
||||
if err != nil {
|
||||
a.log.WithError(err).Fatalf("Failed to append %s to RootCAs", *ca)
|
||||
}
|
||||
// Append our cert to the system pool
|
||||
if ok := rootCAs.AppendCertsFromPEM(certs); !ok {
|
||||
a.log.Println("No certs appended, using system certs only")
|
||||
}
|
||||
tls.RootCAs = rootCAs
|
||||
}
|
||||
}
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: fmt.Sprintf("%s:%d", config.Get().Redis.Host, config.Get().Redis.Port),
|
||||
Username: config.Get().Redis.Username,
|
||||
Password: config.Get().Redis.Password,
|
||||
DB: config.Get().Redis.DB,
|
||||
TLSConfig: tls,
|
||||
Addr: fmt.Sprintf("%s:%d", config.Get().Redis.Host, config.Get().Redis.Port),
|
||||
Username: config.Get().Redis.Username,
|
||||
Password: config.Get().Redis.Password,
|
||||
DB: config.Get().Redis.DB,
|
||||
})
|
||||
|
||||
// New default RedisStore
|
||||
|
@ -54,7 +54,7 @@ function cleanup {
|
||||
}
|
||||
|
||||
function prepare_debug {
|
||||
VIRTUAL_ENV=/ak-root/venv poetry install --no-ansi --no-interaction
|
||||
poetry install --no-ansi --no-interaction
|
||||
touch /unittest.xml
|
||||
chown authentik:authentik /unittest.xml
|
||||
}
|
||||
|
@ -3,12 +3,13 @@
|
||||
import authentik. This is done by the dockerfile."""
|
||||
from sys import exit as sysexit
|
||||
from time import sleep
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
from psycopg import OperationalError, connect
|
||||
from redis import Redis
|
||||
from redis.exceptions import RedisError
|
||||
|
||||
from authentik.lib.config import CONFIG, redis_url
|
||||
from authentik.lib.config import CONFIG
|
||||
|
||||
|
||||
def check_postgres():
|
||||
@ -34,15 +35,24 @@ def check_postgres():
|
||||
|
||||
|
||||
def check_redis():
|
||||
url = CONFIG.get("cache.url") or redis_url(CONFIG.get("redis.db"))
|
||||
REDIS_PROTOCOL_PREFIX = "redis://"
|
||||
if CONFIG.get_bool("redis.tls", False):
|
||||
REDIS_PROTOCOL_PREFIX = "rediss://"
|
||||
REDIS_URL = (
|
||||
f"{REDIS_PROTOCOL_PREFIX}"
|
||||
f"{quote_plus(CONFIG.get('redis.username'))}:"
|
||||
f"{quote_plus(CONFIG.get('redis.password'))}@"
|
||||
f"{quote_plus(CONFIG.get('redis.host'))}:"
|
||||
f"{CONFIG.get_int('redis.port')}/{CONFIG.get('redis.db')}"
|
||||
)
|
||||
while True:
|
||||
try:
|
||||
redis = Redis.from_url(url)
|
||||
redis = Redis.from_url(REDIS_URL)
|
||||
redis.ping()
|
||||
break
|
||||
except RedisError as exc:
|
||||
sleep(1)
|
||||
CONFIG.log("info", f"Redis Connection failed, retrying... ({exc})")
|
||||
CONFIG.log("info", f"Redis Connection failed, retrying... ({exc})", redis_url=REDIS_URL)
|
||||
CONFIG.log("info", "Redis Connection successful")
|
||||
|
||||
|
||||
|
Binary file not shown.
@ -19,7 +19,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-04-16 00:07+0000\n"
|
||||
"POT-Creation-Date: 2024-03-05 00:07+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"
|
||||
@ -138,14 +138,6 @@ msgstr "Group"
|
||||
msgid "Groups"
|
||||
msgstr "Groupes"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Add user to group"
|
||||
msgstr "Ajouter un utilisateur au groupe"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Remove user from group"
|
||||
msgstr "Retirer l'utilisateur du groupe"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "User's display name."
|
||||
msgstr "Nom d'affichage de l'utilisateur"
|
||||
@ -513,22 +505,6 @@ msgstr "Limite maximum de connection atteinte."
|
||||
msgid "(You are already connected in another tab/window)"
|
||||
msgstr "(Vous êtes déjà connecté dans un autre onglet/une autre fenêtre)"
|
||||
|
||||
#: authentik/enterprise/stages/source/models.py
|
||||
msgid ""
|
||||
"Amount of time a user can take to return from the source to continue the "
|
||||
"flow (Format: hours=-1;minutes=-2;seconds=-3)"
|
||||
msgstr ""
|
||||
"Durée que l'utilisateur peut prendre pour revenir de la source pour "
|
||||
"continuer le flux (Format: hours=-1;minutes=-2;seconds=-3)"
|
||||
|
||||
#: authentik/enterprise/stages/source/models.py
|
||||
msgid "Source Stage"
|
||||
msgstr "Étape Source"
|
||||
|
||||
#: authentik/enterprise/stages/source/models.py
|
||||
msgid "Source Stages"
|
||||
msgstr "Étapes Source"
|
||||
|
||||
#: authentik/events/api/tasks.py
|
||||
#, python-brace-format
|
||||
msgid "Successfully started task {name}."
|
||||
@ -1968,12 +1944,6 @@ msgstr "Les objets appliqués à ce filtre seront des groupes."
|
||||
msgid "Field which contains a unique Identifier."
|
||||
msgstr "Champ qui contient un identifiant unique."
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "Update internal authentik password when login succeeds with LDAP"
|
||||
msgstr ""
|
||||
"Mettre à jour le mot de passe interne à authentik lorsque la connexion avec "
|
||||
"LDAP réussi"
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid ""
|
||||
"When a user changes their password, sync it back to LDAP. This can only be "
|
||||
@ -2312,14 +2282,6 @@ msgstr "Connexion de l'utilisateur à la source SAML"
|
||||
msgid "User SAML Source Connections"
|
||||
msgstr "Connexion de l'utilisateur aux sources SAML"
|
||||
|
||||
#: authentik/sources/scim/models.py
|
||||
msgid "SCIM Source"
|
||||
msgstr "Source SCIM"
|
||||
|
||||
#: authentik/sources/scim/models.py
|
||||
msgid "SCIM Sources"
|
||||
msgstr "Sources SCIM"
|
||||
|
||||
#: authentik/stages/authenticator_duo/models.py
|
||||
msgid "Duo Authenticator Setup Stage"
|
||||
msgstr "Étape de configuration du Duo Authenticator"
|
||||
@ -2433,20 +2395,8 @@ msgid "TOTP Devices"
|
||||
msgstr "Équipements TOTP"
|
||||
|
||||
#: authentik/stages/authenticator_validate/challenge.py
|
||||
msgid ""
|
||||
"Invalid Token. Please ensure the time on your device is accurate and try "
|
||||
"again."
|
||||
msgstr ""
|
||||
"Jeton invalide. Merci de vous assurer que le temps défini sur votre appareil"
|
||||
" est juste et de réessayer,"
|
||||
|
||||
#: authentik/stages/authenticator_validate/challenge.py
|
||||
#: authentik/stages/authenticator_webauthn/stage.py
|
||||
#, python-brace-format
|
||||
msgid "Invalid device type. Contact your {brand} administrator for help."
|
||||
msgstr ""
|
||||
"Type d'appareil invalide. Merci de contacter l'administrateur de {brand} "
|
||||
"pour de l'assistance."
|
||||
msgid "Invalid Token"
|
||||
msgstr "Jeton Invalide"
|
||||
|
||||
#: authentik/stages/authenticator_validate/models.py
|
||||
msgid "Static"
|
||||
@ -2502,10 +2452,6 @@ msgstr "Étape de validation de l'authentificateur"
|
||||
msgid "Authenticator Validation Stages"
|
||||
msgstr "Étapes de validation de l'authentificateur"
|
||||
|
||||
#: authentik/stages/authenticator_validate/stage.py
|
||||
msgid "No (allowed) MFA authenticator configured."
|
||||
msgstr "Pas d'authentificateur MFA (autorisé) configuré."
|
||||
|
||||
#: authentik/stages/authenticator_webauthn/models.py
|
||||
msgid "WebAuthn Authenticator Setup Stage"
|
||||
msgstr "Étape de validation de l'authentificateur WebAuthn"
|
||||
@ -2522,14 +2468,6 @@ msgstr "Appareil WebAuthn"
|
||||
msgid "WebAuthn Devices"
|
||||
msgstr "Équipements WebAuthn"
|
||||
|
||||
#: authentik/stages/authenticator_webauthn/models.py
|
||||
msgid "WebAuthn Device type"
|
||||
msgstr "Type d'appareil WebAuthn"
|
||||
|
||||
#: authentik/stages/authenticator_webauthn/models.py
|
||||
msgid "WebAuthn Device types"
|
||||
msgstr "Types d'appareil WebAuthn"
|
||||
|
||||
#: authentik/stages/captcha/models.py
|
||||
msgid "Public key, acquired your captcha Provider."
|
||||
msgstr "Clé publique, acquise auprès de votre fournisseur captcha."
|
||||
@ -3191,14 +3129,6 @@ msgstr ""
|
||||
msgid "Globally enable/disable impersonation."
|
||||
msgstr "Activer/désactiver l'appropriation utilisateur de manière globale."
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "Default token duration"
|
||||
msgstr "Durée par défaut des jetons"
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "Default token length"
|
||||
msgstr "Longueur par défaut des jetons"
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "Tenant"
|
||||
msgstr "Tenant"
|
||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
618
poetry.lock
generated
618
poetry.lock
generated
@ -2,87 +2,87 @@
|
||||
|
||||
[[package]]
|
||||
name = "aiohttp"
|
||||
version = "3.9.4"
|
||||
version = "3.9.2"
|
||||
description = "Async http client/server framework (asyncio)"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "aiohttp-3.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:76d32588ef7e4a3f3adff1956a0ba96faabbdee58f2407c122dd45aa6e34f372"},
|
||||
{file = "aiohttp-3.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:56181093c10dbc6ceb8a29dfeea1e815e1dfdc020169203d87fd8d37616f73f9"},
|
||||
{file = "aiohttp-3.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7a5b676d3c65e88b3aca41816bf72831898fcd73f0cbb2680e9d88e819d1e4d"},
|
||||
{file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1df528a85fb404899d4207a8d9934cfd6be626e30e5d3a5544a83dbae6d8a7e"},
|
||||
{file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f595db1bceabd71c82e92df212dd9525a8a2c6947d39e3c994c4f27d2fe15b11"},
|
||||
{file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c0b09d76e5a4caac3d27752027fbd43dc987b95f3748fad2b924a03fe8632ad"},
|
||||
{file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:689eb4356649ec9535b3686200b231876fb4cab4aca54e3bece71d37f50c1d13"},
|
||||
{file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3666cf4182efdb44d73602379a66f5fdfd5da0db5e4520f0ac0dcca644a3497"},
|
||||
{file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b65b0f8747b013570eea2f75726046fa54fa8e0c5db60f3b98dd5d161052004a"},
|
||||
{file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1885d2470955f70dfdd33a02e1749613c5a9c5ab855f6db38e0b9389453dce7"},
|
||||
{file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0593822dcdb9483d41f12041ff7c90d4d1033ec0e880bcfaf102919b715f47f1"},
|
||||
{file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:47f6eb74e1ecb5e19a78f4a4228aa24df7fbab3b62d4a625d3f41194a08bd54f"},
|
||||
{file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c8b04a3dbd54de6ccb7604242fe3ad67f2f3ca558f2d33fe19d4b08d90701a89"},
|
||||
{file = "aiohttp-3.9.4-cp310-cp310-win32.whl", hash = "sha256:8a78dfb198a328bfb38e4308ca8167028920fb747ddcf086ce706fbdd23b2926"},
|
||||
{file = "aiohttp-3.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:e78da6b55275987cbc89141a1d8e75f5070e577c482dd48bd9123a76a96f0bbb"},
|
||||
{file = "aiohttp-3.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c111b3c69060d2bafc446917534150fd049e7aedd6cbf21ba526a5a97b4402a5"},
|
||||
{file = "aiohttp-3.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:efbdd51872cf170093998c87ccdf3cb5993add3559341a8e5708bcb311934c94"},
|
||||
{file = "aiohttp-3.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7bfdb41dc6e85d8535b00d73947548a748e9534e8e4fddd2638109ff3fb081df"},
|
||||
{file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bd9d334412961125e9f68d5b73c1d0ab9ea3f74a58a475e6b119f5293eee7ba"},
|
||||
{file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:35d78076736f4a668d57ade00c65d30a8ce28719d8a42471b2a06ccd1a2e3063"},
|
||||
{file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:824dff4f9f4d0f59d0fa3577932ee9a20e09edec8a2f813e1d6b9f89ced8293f"},
|
||||
{file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52b8b4e06fc15519019e128abedaeb56412b106ab88b3c452188ca47a25c4093"},
|
||||
{file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eae569fb1e7559d4f3919965617bb39f9e753967fae55ce13454bec2d1c54f09"},
|
||||
{file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:69b97aa5792428f321f72aeb2f118e56893371f27e0b7d05750bcad06fc42ca1"},
|
||||
{file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4d79aad0ad4b980663316f26d9a492e8fab2af77c69c0f33780a56843ad2f89e"},
|
||||
{file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:d6577140cd7db19e430661e4b2653680194ea8c22c994bc65b7a19d8ec834403"},
|
||||
{file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:9860d455847cd98eb67897f5957b7cd69fbcb436dd3f06099230f16a66e66f79"},
|
||||
{file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:69ff36d3f8f5652994e08bd22f093e11cfd0444cea310f92e01b45a4e46b624e"},
|
||||
{file = "aiohttp-3.9.4-cp311-cp311-win32.whl", hash = "sha256:e27d3b5ed2c2013bce66ad67ee57cbf614288bda8cdf426c8d8fe548316f1b5f"},
|
||||
{file = "aiohttp-3.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d6a67e26daa686a6fbdb600a9af8619c80a332556245fa8e86c747d226ab1a1e"},
|
||||
{file = "aiohttp-3.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c5ff8ff44825736a4065d8544b43b43ee4c6dd1530f3a08e6c0578a813b0aa35"},
|
||||
{file = "aiohttp-3.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d12a244627eba4e9dc52cbf924edef905ddd6cafc6513849b4876076a6f38b0e"},
|
||||
{file = "aiohttp-3.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dcad56c8d8348e7e468899d2fb3b309b9bc59d94e6db08710555f7436156097f"},
|
||||
{file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f7e69a7fd4b5ce419238388e55abd220336bd32212c673ceabc57ccf3d05b55"},
|
||||
{file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4870cb049f10d7680c239b55428916d84158798eb8f353e74fa2c98980dcc0b"},
|
||||
{file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2feaf1b7031ede1bc0880cec4b0776fd347259a723d625357bb4b82f62687b"},
|
||||
{file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:939393e8c3f0a5bcd33ef7ace67680c318dc2ae406f15e381c0054dd658397de"},
|
||||
{file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d2334e387b2adcc944680bebcf412743f2caf4eeebd550f67249c1c3696be04"},
|
||||
{file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e0198ea897680e480845ec0ffc5a14e8b694e25b3f104f63676d55bf76a82f1a"},
|
||||
{file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e40d2cd22914d67c84824045861a5bb0fb46586b15dfe4f046c7495bf08306b2"},
|
||||
{file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:aba80e77c227f4234aa34a5ff2b6ff30c5d6a827a91d22ff6b999de9175d71bd"},
|
||||
{file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:fb68dc73bc8ac322d2e392a59a9e396c4f35cb6fdbdd749e139d1d6c985f2527"},
|
||||
{file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f3460a92638dce7e47062cf088d6e7663adb135e936cb117be88d5e6c48c9d53"},
|
||||
{file = "aiohttp-3.9.4-cp312-cp312-win32.whl", hash = "sha256:32dc814ddbb254f6170bca198fe307920f6c1308a5492f049f7f63554b88ef36"},
|
||||
{file = "aiohttp-3.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:63f41a909d182d2b78fe3abef557fcc14da50c7852f70ae3be60e83ff64edba5"},
|
||||
{file = "aiohttp-3.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c3770365675f6be220032f6609a8fbad994d6dcf3ef7dbcf295c7ee70884c9af"},
|
||||
{file = "aiohttp-3.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:305edae1dea368ce09bcb858cf5a63a064f3bff4767dec6fa60a0cc0e805a1d3"},
|
||||
{file = "aiohttp-3.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6f121900131d116e4a93b55ab0d12ad72573f967b100e49086e496a9b24523ea"},
|
||||
{file = "aiohttp-3.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b71e614c1ae35c3d62a293b19eface83d5e4d194e3eb2fabb10059d33e6e8cbf"},
|
||||
{file = "aiohttp-3.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:419f009fa4cfde4d16a7fc070d64f36d70a8d35a90d71aa27670bba2be4fd039"},
|
||||
{file = "aiohttp-3.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b39476ee69cfe64061fd77a73bf692c40021f8547cda617a3466530ef63f947"},
|
||||
{file = "aiohttp-3.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b33f34c9c7decdb2ab99c74be6443942b730b56d9c5ee48fb7df2c86492f293c"},
|
||||
{file = "aiohttp-3.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c78700130ce2dcebb1a8103202ae795be2fa8c9351d0dd22338fe3dac74847d9"},
|
||||
{file = "aiohttp-3.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:268ba22d917655d1259af2d5659072b7dc11b4e1dc2cb9662fdd867d75afc6a4"},
|
||||
{file = "aiohttp-3.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:17e7c051f53a0d2ebf33013a9cbf020bb4e098c4bc5bce6f7b0c962108d97eab"},
|
||||
{file = "aiohttp-3.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:7be99f4abb008cb38e144f85f515598f4c2c8932bf11b65add0ff59c9c876d99"},
|
||||
{file = "aiohttp-3.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:d58a54d6ff08d2547656356eea8572b224e6f9bbc0cf55fa9966bcaac4ddfb10"},
|
||||
{file = "aiohttp-3.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7673a76772bda15d0d10d1aa881b7911d0580c980dbd16e59d7ba1422b2d83cd"},
|
||||
{file = "aiohttp-3.9.4-cp38-cp38-win32.whl", hash = "sha256:e4370dda04dc8951012f30e1ce7956a0a226ac0714a7b6c389fb2f43f22a250e"},
|
||||
{file = "aiohttp-3.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:eb30c4510a691bb87081192a394fb661860e75ca3896c01c6d186febe7c88530"},
|
||||
{file = "aiohttp-3.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:84e90494db7df3be5e056f91412f9fa9e611fbe8ce4aaef70647297f5943b276"},
|
||||
{file = "aiohttp-3.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7d4845f8501ab28ebfdbeab980a50a273b415cf69e96e4e674d43d86a464df9d"},
|
||||
{file = "aiohttp-3.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:69046cd9a2a17245c4ce3c1f1a4ff8c70c7701ef222fce3d1d8435f09042bba1"},
|
||||
{file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b73a06bafc8dcc508420db43b4dd5850e41e69de99009d0351c4f3007960019"},
|
||||
{file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:418bb0038dfafeac923823c2e63226179976c76f981a2aaad0ad5d51f2229bca"},
|
||||
{file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71a8f241456b6c2668374d5d28398f8e8cdae4cce568aaea54e0f39359cd928d"},
|
||||
{file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:935c369bf8acc2dc26f6eeb5222768aa7c62917c3554f7215f2ead7386b33748"},
|
||||
{file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74e4e48c8752d14ecfb36d2ebb3d76d614320570e14de0a3aa7a726ff150a03c"},
|
||||
{file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:916b0417aeddf2c8c61291238ce25286f391a6acb6f28005dd9ce282bd6311b6"},
|
||||
{file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9b6787b6d0b3518b2ee4cbeadd24a507756ee703adbac1ab6dc7c4434b8c572a"},
|
||||
{file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:221204dbda5ef350e8db6287937621cf75e85778b296c9c52260b522231940ed"},
|
||||
{file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:10afd99b8251022ddf81eaed1d90f5a988e349ee7d779eb429fb07b670751e8c"},
|
||||
{file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2506d9f7a9b91033201be9ffe7d89c6a54150b0578803cce5cb84a943d075bc3"},
|
||||
{file = "aiohttp-3.9.4-cp39-cp39-win32.whl", hash = "sha256:e571fdd9efd65e86c6af2f332e0e95dad259bfe6beb5d15b3c3eca3a6eb5d87b"},
|
||||
{file = "aiohttp-3.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:7d29dd5319d20aa3b7749719ac9685fbd926f71ac8c77b2477272725f882072d"},
|
||||
{file = "aiohttp-3.9.4.tar.gz", hash = "sha256:6ff71ede6d9a5a58cfb7b6fffc83ab5d4a63138276c771ac91ceaaddf5459644"},
|
||||
{file = "aiohttp-3.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:772fbe371788e61c58d6d3d904268e48a594ba866804d08c995ad71b144f94cb"},
|
||||
{file = "aiohttp-3.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:edd4f1af2253f227ae311ab3d403d0c506c9b4410c7fc8d9573dec6d9740369f"},
|
||||
{file = "aiohttp-3.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cfee9287778399fdef6f8a11c9e425e1cb13cc9920fd3a3df8f122500978292b"},
|
||||
{file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc158466f6a980a6095ee55174d1de5730ad7dec251be655d9a6a9dd7ea1ff9"},
|
||||
{file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:54ec82f45d57c9a65a1ead3953b51c704f9587440e6682f689da97f3e8defa35"},
|
||||
{file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abeb813a18eb387f0d835ef51f88568540ad0325807a77a6e501fed4610f864e"},
|
||||
{file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc91d07280d7d169f3a0f9179d8babd0ee05c79d4d891447629ff0d7d8089ec2"},
|
||||
{file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b65e861f4bebfb660f7f0f40fa3eb9f2ab9af10647d05dac824390e7af8f75b7"},
|
||||
{file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:04fd8ffd2be73d42bcf55fd78cde7958eeee6d4d8f73c3846b7cba491ecdb570"},
|
||||
{file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3d8d962b439a859b3ded9a1e111a4615357b01620a546bc601f25b0211f2da81"},
|
||||
{file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:8ceb658afd12b27552597cf9a65d9807d58aef45adbb58616cdd5ad4c258c39e"},
|
||||
{file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:0e4ee4df741670560b1bc393672035418bf9063718fee05e1796bf867e995fad"},
|
||||
{file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2dec87a556f300d3211decf018bfd263424f0690fcca00de94a837949fbcea02"},
|
||||
{file = "aiohttp-3.9.2-cp310-cp310-win32.whl", hash = "sha256:3e1a800f988ce7c4917f34096f81585a73dbf65b5c39618b37926b1238cf9bc4"},
|
||||
{file = "aiohttp-3.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:ea510718a41b95c236c992b89fdfc3d04cc7ca60281f93aaada497c2b4e05c46"},
|
||||
{file = "aiohttp-3.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6aaa6f99256dd1b5756a50891a20f0d252bd7bdb0854c5d440edab4495c9f973"},
|
||||
{file = "aiohttp-3.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a27d8c70ad87bcfce2e97488652075a9bdd5b70093f50b10ae051dfe5e6baf37"},
|
||||
{file = "aiohttp-3.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:54287bcb74d21715ac8382e9de146d9442b5f133d9babb7e5d9e453faadd005e"},
|
||||
{file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb3d05569aa83011fcb346b5266e00b04180105fcacc63743fc2e4a1862a891"},
|
||||
{file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c8534e7d69bb8e8d134fe2be9890d1b863518582f30c9874ed7ed12e48abe3c4"},
|
||||
{file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4bd9d5b989d57b41e4ff56ab250c5ddf259f32db17159cce630fd543376bd96b"},
|
||||
{file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa6904088e6642609981f919ba775838ebf7df7fe64998b1a954fb411ffb4663"},
|
||||
{file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bda42eb410be91b349fb4ee3a23a30ee301c391e503996a638d05659d76ea4c2"},
|
||||
{file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:193cc1ccd69d819562cc7f345c815a6fc51d223b2ef22f23c1a0f67a88de9a72"},
|
||||
{file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b9f1cb839b621f84a5b006848e336cf1496688059d2408e617af33e3470ba204"},
|
||||
{file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:d22a0931848b8c7a023c695fa2057c6aaac19085f257d48baa24455e67df97ec"},
|
||||
{file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4112d8ba61fbd0abd5d43a9cb312214565b446d926e282a6d7da3f5a5aa71d36"},
|
||||
{file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c4ad4241b52bb2eb7a4d2bde060d31c2b255b8c6597dd8deac2f039168d14fd7"},
|
||||
{file = "aiohttp-3.9.2-cp311-cp311-win32.whl", hash = "sha256:ee2661a3f5b529f4fc8a8ffee9f736ae054adfb353a0d2f78218be90617194b3"},
|
||||
{file = "aiohttp-3.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:4deae2c165a5db1ed97df2868ef31ca3cc999988812e82386d22937d9d6fed52"},
|
||||
{file = "aiohttp-3.9.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:6f4cdba12539215aaecf3c310ce9d067b0081a0795dd8a8805fdb67a65c0572a"},
|
||||
{file = "aiohttp-3.9.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:84e843b33d5460a5c501c05539809ff3aee07436296ff9fbc4d327e32aa3a326"},
|
||||
{file = "aiohttp-3.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8008d0f451d66140a5aa1c17e3eedc9d56e14207568cd42072c9d6b92bf19b52"},
|
||||
{file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61c47ab8ef629793c086378b1df93d18438612d3ed60dca76c3422f4fbafa792"},
|
||||
{file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc71f748e12284312f140eaa6599a520389273174b42c345d13c7e07792f4f57"},
|
||||
{file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a1c3a4d0ab2f75f22ec80bca62385db2e8810ee12efa8c9e92efea45c1849133"},
|
||||
{file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a87aa0b13bbee025faa59fa58861303c2b064b9855d4c0e45ec70182bbeba1b"},
|
||||
{file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2cc0d04688b9f4a7854c56c18aa7af9e5b0a87a28f934e2e596ba7e14783192"},
|
||||
{file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1956e3ac376b1711c1533266dec4efd485f821d84c13ce1217d53e42c9e65f08"},
|
||||
{file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:114da29f39eccd71b93a0fcacff178749a5c3559009b4a4498c2c173a6d74dff"},
|
||||
{file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:3f17999ae3927d8a9a823a1283b201344a0627272f92d4f3e3a4efe276972fe8"},
|
||||
{file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:f31df6a32217a34ae2f813b152a6f348154f948c83213b690e59d9e84020925c"},
|
||||
{file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7a75307ffe31329928a8d47eae0692192327c599113d41b278d4c12b54e1bd11"},
|
||||
{file = "aiohttp-3.9.2-cp312-cp312-win32.whl", hash = "sha256:972b63d589ff8f305463593050a31b5ce91638918da38139b9d8deaba9e0fed7"},
|
||||
{file = "aiohttp-3.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:200dc0246f0cb5405c80d18ac905c8350179c063ea1587580e3335bfc243ba6a"},
|
||||
{file = "aiohttp-3.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:158564d0d1020e0d3fe919a81d97aadad35171e13e7b425b244ad4337fc6793a"},
|
||||
{file = "aiohttp-3.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:da1346cd0ccb395f0ed16b113ebb626fa43b7b07fd7344fce33e7a4f04a8897a"},
|
||||
{file = "aiohttp-3.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:eaa9256de26ea0334ffa25f1913ae15a51e35c529a1ed9af8e6286dd44312554"},
|
||||
{file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1543e7fb00214fb4ccead42e6a7d86f3bb7c34751ec7c605cca7388e525fd0b4"},
|
||||
{file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:186e94570433a004e05f31f632726ae0f2c9dee4762a9ce915769ce9c0a23d89"},
|
||||
{file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d52d20832ac1560f4510d68e7ba8befbc801a2b77df12bd0cd2bcf3b049e52a4"},
|
||||
{file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c45e4e815ac6af3b72ca2bde9b608d2571737bb1e2d42299fc1ffdf60f6f9a1"},
|
||||
{file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa906b9bdfd4a7972dd0628dbbd6413d2062df5b431194486a78f0d2ae87bd55"},
|
||||
{file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:68bbee9e17d66f17bb0010aa15a22c6eb28583edcc8b3212e2b8e3f77f3ebe2a"},
|
||||
{file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4c189b64bd6d9a403a1a3f86a3ab3acbc3dc41a68f73a268a4f683f89a4dec1f"},
|
||||
{file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:8a7876f794523123bca6d44bfecd89c9fec9ec897a25f3dd202ee7fc5c6525b7"},
|
||||
{file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:d23fba734e3dd7b1d679b9473129cd52e4ec0e65a4512b488981a56420e708db"},
|
||||
{file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b141753be581fab842a25cb319f79536d19c2a51995d7d8b29ee290169868eab"},
|
||||
{file = "aiohttp-3.9.2-cp38-cp38-win32.whl", hash = "sha256:103daf41ff3b53ba6fa09ad410793e2e76c9d0269151812e5aba4b9dd674a7e8"},
|
||||
{file = "aiohttp-3.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:328918a6c2835861ff7afa8c6d2c70c35fdaf996205d5932351bdd952f33fa2f"},
|
||||
{file = "aiohttp-3.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5264d7327c9464786f74e4ec9342afbbb6ee70dfbb2ec9e3dfce7a54c8043aa3"},
|
||||
{file = "aiohttp-3.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07205ae0015e05c78b3288c1517afa000823a678a41594b3fdc870878d645305"},
|
||||
{file = "aiohttp-3.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ae0a1e638cffc3ec4d4784b8b4fd1cf28968febc4bd2718ffa25b99b96a741bd"},
|
||||
{file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d43302a30ba1166325974858e6ef31727a23bdd12db40e725bec0f759abce505"},
|
||||
{file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16a967685907003765855999af11a79b24e70b34dc710f77a38d21cd9fc4f5fe"},
|
||||
{file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6fa3ee92cd441d5c2d07ca88d7a9cef50f7ec975f0117cd0c62018022a184308"},
|
||||
{file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b500c5ad9c07639d48615a770f49618130e61be36608fc9bc2d9bae31732b8f"},
|
||||
{file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c07327b368745b1ce2393ae9e1aafed7073d9199e1dcba14e035cc646c7941bf"},
|
||||
{file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cc7d6502c23a0ec109687bf31909b3fb7b196faf198f8cff68c81b49eb316ea9"},
|
||||
{file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:07be2be7071723c3509ab5c08108d3a74f2181d4964e869f2504aaab68f8d3e8"},
|
||||
{file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:122468f6fee5fcbe67cb07014a08c195b3d4c41ff71e7b5160a7bcc41d585a5f"},
|
||||
{file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:00a9abcea793c81e7f8778ca195a1714a64f6d7436c4c0bb168ad2a212627000"},
|
||||
{file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7a9825fdd64ecac5c670234d80bb52bdcaa4139d1f839165f548208b3779c6c6"},
|
||||
{file = "aiohttp-3.9.2-cp39-cp39-win32.whl", hash = "sha256:5422cd9a4a00f24c7244e1b15aa9b87935c85fb6a00c8ac9b2527b38627a9211"},
|
||||
{file = "aiohttp-3.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:7d579dcd5d82a86a46f725458418458fa43686f6a7b252f2966d359033ffc8ab"},
|
||||
{file = "aiohttp-3.9.2.tar.gz", hash = "sha256:b0ad0a5e86ce73f5368a164c10ada10504bf91869c05ab75d982c6048217fbf7"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -269,23 +269,6 @@ tests = ["attrs[tests-no-zope]", "zope-interface"]
|
||||
tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"]
|
||||
tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"]
|
||||
|
||||
[[package]]
|
||||
name = "authentik-client"
|
||||
version = "2024.4.1.post1714149882"
|
||||
description = "authentik"
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.7"
|
||||
files = [
|
||||
{file = "authentik_client-2024.4.1.post1714149882-py3-none-any.whl", hash = "sha256:867b66b6a0fd59fac5a96d2098889fca1fa17409d042c00923d06ba21db27622"},
|
||||
{file = "authentik_client-2024.4.1.post1714149882.tar.gz", hash = "sha256:2917c6f531dcbe44392d6f758fe10065399972de0c324d5431ea183e4b11ed7b"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pydantic = ">=2"
|
||||
python-dateutil = ">=2.8.2"
|
||||
typing-extensions = ">=4.7.1"
|
||||
urllib3 = ">=1.25.3"
|
||||
|
||||
[[package]]
|
||||
name = "autobahn"
|
||||
version = "23.6.2"
|
||||
@ -409,33 +392,33 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "24.4.2"
|
||||
version = "24.4.0"
|
||||
description = "The uncompromising code formatter."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"},
|
||||
{file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"},
|
||||
{file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"},
|
||||
{file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"},
|
||||
{file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"},
|
||||
{file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"},
|
||||
{file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"},
|
||||
{file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"},
|
||||
{file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"},
|
||||
{file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"},
|
||||
{file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"},
|
||||
{file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"},
|
||||
{file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"},
|
||||
{file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"},
|
||||
{file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"},
|
||||
{file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"},
|
||||
{file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"},
|
||||
{file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"},
|
||||
{file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"},
|
||||
{file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"},
|
||||
{file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"},
|
||||
{file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"},
|
||||
{file = "black-24.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6ad001a9ddd9b8dfd1b434d566be39b1cd502802c8d38bbb1ba612afda2ef436"},
|
||||
{file = "black-24.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3a3a092b8b756c643fe45f4624dbd5a389f770a4ac294cf4d0fce6af86addaf"},
|
||||
{file = "black-24.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dae79397f367ac8d7adb6c779813328f6d690943f64b32983e896bcccd18cbad"},
|
||||
{file = "black-24.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:71d998b73c957444fb7c52096c3843875f4b6b47a54972598741fe9a7f737fcb"},
|
||||
{file = "black-24.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8e5537f456a22cf5cfcb2707803431d2feeb82ab3748ade280d6ccd0b40ed2e8"},
|
||||
{file = "black-24.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:64e60a7edd71fd542a10a9643bf369bfd2644de95ec71e86790b063aa02ff745"},
|
||||
{file = "black-24.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cd5b4f76056cecce3e69b0d4c228326d2595f506797f40b9233424e2524c070"},
|
||||
{file = "black-24.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:64578cf99b6b46a6301bc28bdb89f9d6f9b592b1c5837818a177c98525dbe397"},
|
||||
{file = "black-24.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f95cece33329dc4aa3b0e1a771c41075812e46cf3d6e3f1dfe3d91ff09826ed2"},
|
||||
{file = "black-24.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4396ca365a4310beef84d446ca5016f671b10f07abdba3e4e4304218d2c71d33"},
|
||||
{file = "black-24.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44d99dfdf37a2a00a6f7a8dcbd19edf361d056ee51093b2445de7ca09adac965"},
|
||||
{file = "black-24.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:21f9407063ec71c5580b8ad975653c66508d6a9f57bd008bb8691d273705adcd"},
|
||||
{file = "black-24.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:652e55bb722ca026299eb74e53880ee2315b181dfdd44dca98e43448620ddec1"},
|
||||
{file = "black-24.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7f2966b9b2b3b7104fca9d75b2ee856fe3fdd7ed9e47c753a4bb1a675f2caab8"},
|
||||
{file = "black-24.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bb9ca06e556a09f7f7177bc7cb604e5ed2d2df1e9119e4f7d2f1f7071c32e5d"},
|
||||
{file = "black-24.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:d4e71cdebdc8efeb6deaf5f2deb28325f8614d48426bed118ecc2dcaefb9ebf3"},
|
||||
{file = "black-24.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6644f97a7ef6f401a150cca551a1ff97e03c25d8519ee0bbc9b0058772882665"},
|
||||
{file = "black-24.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:75a2d0b4f5eb81f7eebc31f788f9830a6ce10a68c91fbe0fade34fff7a2836e6"},
|
||||
{file = "black-24.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb949f56a63c5e134dfdca12091e98ffb5fd446293ebae123d10fc1abad00b9e"},
|
||||
{file = "black-24.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:7852b05d02b5b9a8c893ab95863ef8986e4dda29af80bbbda94d7aee1abf8702"},
|
||||
{file = "black-24.4.0-py3-none-any.whl", hash = "sha256:74eb9b5420e26b42c00a3ff470dc0cd144b80a766128b1771d07643165e08d0e"},
|
||||
{file = "black-24.4.0.tar.gz", hash = "sha256:f07b69fda20578367eaebbd670ff8fc653ab181e1ff95d84497f9fa20e7d0641"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -564,13 +547,13 @@ test = ["coverage (>=7)", "hypothesis", "pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "celery"
|
||||
version = "5.4.0"
|
||||
version = "5.3.6"
|
||||
description = "Distributed Task Queue."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "celery-5.4.0-py3-none-any.whl", hash = "sha256:369631eb580cf8c51a82721ec538684994f8277637edde2dfc0dacd73ed97f64"},
|
||||
{file = "celery-5.4.0.tar.gz", hash = "sha256:504a19140e8d3029d5acad88330c541d4c3f64c789d85f94756762d8bca7e706"},
|
||||
{file = "celery-5.3.6-py3-none-any.whl", hash = "sha256:9da4ea0118d232ce97dff5ed4974587fb1c0ff5c10042eb15278487cdd27d1af"},
|
||||
{file = "celery-5.3.6.tar.gz", hash = "sha256:870cc71d737c0200c397290d730344cc991d13a057534353d124c9380267aab9"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -586,7 +569,7 @@ vine = ">=5.1.0,<6.0"
|
||||
|
||||
[package.extras]
|
||||
arangodb = ["pyArango (>=2.0.2)"]
|
||||
auth = ["cryptography (==42.0.5)"]
|
||||
auth = ["cryptography (==41.0.5)"]
|
||||
azureblockblob = ["azure-storage-blob (>=12.15.0)"]
|
||||
brotli = ["brotli (>=1.0.0)", "brotlipy (>=0.7.0)"]
|
||||
cassandra = ["cassandra-driver (>=3.25.0,<4)"]
|
||||
@ -596,23 +579,22 @@ couchbase = ["couchbase (>=3.0.0)"]
|
||||
couchdb = ["pycouchdb (==1.14.2)"]
|
||||
django = ["Django (>=2.2.28)"]
|
||||
dynamodb = ["boto3 (>=1.26.143)"]
|
||||
elasticsearch = ["elastic-transport (<=8.13.0)", "elasticsearch (<=8.13.0)"]
|
||||
elasticsearch = ["elastic-transport (<=8.10.0)", "elasticsearch (<=8.11.0)"]
|
||||
eventlet = ["eventlet (>=0.32.0)"]
|
||||
gcs = ["google-cloud-storage (>=2.10.0)"]
|
||||
gevent = ["gevent (>=1.5.0)"]
|
||||
librabbitmq = ["librabbitmq (>=2.0.0)"]
|
||||
memcache = ["pylibmc (==1.6.3)"]
|
||||
mongodb = ["pymongo[srv] (>=4.0.2)"]
|
||||
msgpack = ["msgpack (==1.0.8)"]
|
||||
pymemcache = ["python-memcached (>=1.61)"]
|
||||
msgpack = ["msgpack (==1.0.7)"]
|
||||
pymemcache = ["python-memcached (==1.59)"]
|
||||
pyro = ["pyro4 (==4.82)"]
|
||||
pytest = ["pytest-celery[all] (>=1.0.0)"]
|
||||
pytest = ["pytest-celery (==0.0.0)"]
|
||||
redis = ["redis (>=4.5.2,!=4.5.5,<6.0.0)"]
|
||||
s3 = ["boto3 (>=1.26.143)"]
|
||||
slmq = ["softlayer-messaging (>=1.0.3)"]
|
||||
solar = ["ephem (==4.1.5)"]
|
||||
sqlalchemy = ["sqlalchemy (>=1.4.48,<2.1)"]
|
||||
sqs = ["boto3 (>=1.26.143)", "kombu[sqs] (>=5.3.4)", "pycurl (>=7.43.0.5)", "urllib3 (>=1.26.16)"]
|
||||
sqs = ["boto3 (>=1.26.143)", "kombu[sqs] (>=5.3.0)", "pycurl (>=7.43.0.5)", "urllib3 (>=1.26.16)"]
|
||||
tblib = ["tblib (>=1.3.0)", "tblib (>=1.5.0)"]
|
||||
yaml = ["PyYAML (>=3.10)"]
|
||||
zookeeper = ["kazoo (>=1.3.1)"]
|
||||
@ -937,63 +919,63 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.5.0"
|
||||
version = "7.4.4"
|
||||
description = "Code coverage measurement for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "coverage-7.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:432949a32c3e3f820af808db1833d6d1631664d53dd3ce487aa25d574e18ad1c"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2bd7065249703cbeb6d4ce679c734bef0ee69baa7bff9724361ada04a15b7e3b"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbfe6389c5522b99768a93d89aca52ef92310a96b99782973b9d11e80511f932"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39793731182c4be939b4be0cdecde074b833f6171313cf53481f869937129ed3"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85a5dbe1ba1bf38d6c63b6d2c42132d45cbee6d9f0c51b52c59aa4afba057517"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:357754dcdfd811462a725e7501a9b4556388e8ecf66e79df6f4b988fa3d0b39a"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a81eb64feded34f40c8986869a2f764f0fe2db58c0530d3a4afbcde50f314880"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:51431d0abbed3a868e967f8257c5faf283d41ec882f58413cf295a389bb22e58"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-win32.whl", hash = "sha256:f609ebcb0242d84b7adeee2b06c11a2ddaec5464d21888b2c8255f5fd6a98ae4"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:6782cd6216fab5a83216cc39f13ebe30adfac2fa72688c5a4d8d180cd52e8f6a"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e768d870801f68c74c2b669fc909839660180c366501d4cc4b87efd6b0eee375"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:84921b10aeb2dd453247fd10de22907984eaf80901b578a5cf0bb1e279a587cb"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:710c62b6e35a9a766b99b15cdc56d5aeda0914edae8bb467e9c355f75d14ee95"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c379cdd3efc0658e652a14112d51a7668f6bfca7445c5a10dee7eabecabba19d"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fea9d3ca80bcf17edb2c08a4704259dadac196fe5e9274067e7a20511fad1743"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:41327143c5b1d715f5f98a397608f90ab9ebba606ae4e6f3389c2145410c52b1"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:565b2e82d0968c977e0b0f7cbf25fd06d78d4856289abc79694c8edcce6eb2de"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cf3539007202ebfe03923128fedfdd245db5860a36810136ad95a564a2fdffff"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-win32.whl", hash = "sha256:bf0b4b8d9caa8d64df838e0f8dcf68fb570c5733b726d1494b87f3da85db3a2d"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c6384cc90e37cfb60435bbbe0488444e54b98700f727f16f64d8bfda0b84656"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fed7a72d54bd52f4aeb6c6e951f363903bd7d70bc1cad64dd1f087980d309ab9"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cbe6581fcff7c8e262eb574244f81f5faaea539e712a058e6707a9d272fe5b64"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad97ec0da94b378e593ef532b980c15e377df9b9608c7c6da3506953182398af"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd4bacd62aa2f1a1627352fe68885d6ee694bdaebb16038b6e680f2924a9b2cc"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:adf032b6c105881f9d77fa17d9eebe0ad1f9bfb2ad25777811f97c5362aa07f2"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ba01d9ba112b55bfa4b24808ec431197bb34f09f66f7cb4fd0258ff9d3711b1"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f0bfe42523893c188e9616d853c47685e1c575fe25f737adf473d0405dcfa7eb"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a9a7ef30a1b02547c1b23fa9a5564f03c9982fc71eb2ecb7f98c96d7a0db5cf2"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-win32.whl", hash = "sha256:3c2b77f295edb9fcdb6a250f83e6481c679335ca7e6e4a955e4290350f2d22a4"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:427e1e627b0963ac02d7c8730ca6d935df10280d230508c0ba059505e9233475"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9dd88fce54abbdbf4c42fb1fea0e498973d07816f24c0e27a1ecaf91883ce69e"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a898c11dca8f8c97b467138004a30133974aacd572818c383596f8d5b2eb04a9"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07dfdd492d645eea1bd70fb1d6febdcf47db178b0d99161d8e4eed18e7f62fe7"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3d117890b6eee85887b1eed41eefe2e598ad6e40523d9f94c4c4b213258e4a4"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6afd2e84e7da40fe23ca588379f815fb6dbbb1b757c883935ed11647205111cb"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a9960dd1891b2ddf13a7fe45339cd59ecee3abb6b8326d8b932d0c5da208104f"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ced268e82af993d7801a9db2dbc1d2322e786c5dc76295d8e89473d46c6b84d4"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e7c211f25777746d468d76f11719e64acb40eed410d81c26cefac641975beb88"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-win32.whl", hash = "sha256:262fffc1f6c1a26125d5d573e1ec379285a3723363f3bd9c83923c9593a2ac25"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:eed462b4541c540d63ab57b3fc69e7d8c84d5957668854ee4e408b50e92ce26a"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d0194d654e360b3e6cc9b774e83235bae6b9b2cac3be09040880bb0e8a88f4a1"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:33c020d3322662e74bc507fb11488773a96894aa82a622c35a5a28673c0c26f5"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbdf2cae14a06827bec50bd58e49249452d211d9caddd8bd80e35b53cb04631"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3235d7c781232e525b0761730e052388a01548bd7f67d0067a253887c6e8df46"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2de4e546f0ec4b2787d625e0b16b78e99c3e21bc1722b4977c0dddf11ca84e"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4d0e206259b73af35c4ec1319fd04003776e11e859936658cb6ceffdeba0f5be"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2055c4fb9a6ff624253d432aa471a37202cd8f458c033d6d989be4499aed037b"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:075299460948cd12722a970c7eae43d25d37989da682997687b34ae6b87c0ef0"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-win32.whl", hash = "sha256:280132aada3bc2f0fac939a5771db4fbb84f245cb35b94fae4994d4c1f80dae7"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:c58536f6892559e030e6924896a44098bc1290663ea12532c78cef71d0df8493"},
|
||||
{file = "coverage-7.5.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:2b57780b51084d5223eee7b59f0d4911c31c16ee5aa12737c7a02455829ff067"},
|
||||
{file = "coverage-7.5.0.tar.gz", hash = "sha256:cf62d17310f34084c59c01e027259076479128d11e4661bb6c9acb38c5e19bb8"},
|
||||
{file = "coverage-7.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0be5efd5127542ef31f165de269f77560d6cdef525fffa446de6f7e9186cfb2"},
|
||||
{file = "coverage-7.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ccd341521be3d1b3daeb41960ae94a5e87abe2f46f17224ba5d6f2b8398016cf"},
|
||||
{file = "coverage-7.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fa497a8ab37784fbb20ab699c246053ac294d13fc7eb40ec007a5043ec91f8"},
|
||||
{file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b1a93009cb80730c9bca5d6d4665494b725b6e8e157c1cb7f2db5b4b122ea562"},
|
||||
{file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:690db6517f09336559dc0b5f55342df62370a48f5469fabf502db2c6d1cffcd2"},
|
||||
{file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:09c3255458533cb76ef55da8cc49ffab9e33f083739c8bd4f58e79fecfe288f7"},
|
||||
{file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ce1415194b4a6bd0cdcc3a1dfbf58b63f910dcb7330fe15bdff542c56949f87"},
|
||||
{file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b91cbc4b195444e7e258ba27ac33769c41b94967919f10037e6355e998af255c"},
|
||||
{file = "coverage-7.4.4-cp310-cp310-win32.whl", hash = "sha256:598825b51b81c808cb6f078dcb972f96af96b078faa47af7dfcdf282835baa8d"},
|
||||
{file = "coverage-7.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:09ef9199ed6653989ebbcaacc9b62b514bb63ea2f90256e71fea3ed74bd8ff6f"},
|
||||
{file = "coverage-7.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0f9f50e7ef2a71e2fae92774c99170eb8304e3fdf9c8c3c7ae9bab3e7229c5cf"},
|
||||
{file = "coverage-7.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:623512f8ba53c422fcfb2ce68362c97945095b864cda94a92edbaf5994201083"},
|
||||
{file = "coverage-7.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0513b9508b93da4e1716744ef6ebc507aff016ba115ffe8ecff744d1322a7b63"},
|
||||
{file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40209e141059b9370a2657c9b15607815359ab3ef9918f0196b6fccce8d3230f"},
|
||||
{file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a2b2b78c78293782fd3767d53e6474582f62443d0504b1554370bde86cc8227"},
|
||||
{file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:73bfb9c09951125d06ee473bed216e2c3742f530fc5acc1383883125de76d9cd"},
|
||||
{file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f384c3cc76aeedce208643697fb3e8437604b512255de6d18dae3f27655a384"},
|
||||
{file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:54eb8d1bf7cacfbf2a3186019bcf01d11c666bd495ed18717162f7eb1e9dd00b"},
|
||||
{file = "coverage-7.4.4-cp311-cp311-win32.whl", hash = "sha256:cac99918c7bba15302a2d81f0312c08054a3359eaa1929c7e4b26ebe41e9b286"},
|
||||
{file = "coverage-7.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:b14706df8b2de49869ae03a5ccbc211f4041750cd4a66f698df89d44f4bd30ec"},
|
||||
{file = "coverage-7.4.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:201bef2eea65e0e9c56343115ba3814e896afe6d36ffd37bab783261db430f76"},
|
||||
{file = "coverage-7.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41c9c5f3de16b903b610d09650e5e27adbfa7f500302718c9ffd1c12cf9d6818"},
|
||||
{file = "coverage-7.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d898fe162d26929b5960e4e138651f7427048e72c853607f2b200909794ed978"},
|
||||
{file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ea79bb50e805cd6ac058dfa3b5c8f6c040cb87fe83de10845857f5535d1db70"},
|
||||
{file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce4b94265ca988c3f8e479e741693d143026632672e3ff924f25fab50518dd51"},
|
||||
{file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:00838a35b882694afda09f85e469c96367daa3f3f2b097d846a7216993d37f4c"},
|
||||
{file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fdfafb32984684eb03c2d83e1e51f64f0906b11e64482df3c5db936ce3839d48"},
|
||||
{file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:69eb372f7e2ece89f14751fbcbe470295d73ed41ecd37ca36ed2eb47512a6ab9"},
|
||||
{file = "coverage-7.4.4-cp312-cp312-win32.whl", hash = "sha256:137eb07173141545e07403cca94ab625cc1cc6bc4c1e97b6e3846270e7e1fea0"},
|
||||
{file = "coverage-7.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:d71eec7d83298f1af3326ce0ff1d0ea83c7cb98f72b577097f9083b20bdaf05e"},
|
||||
{file = "coverage-7.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ae728ff3b5401cc320d792866987e7e7e880e6ebd24433b70a33b643bb0384"},
|
||||
{file = "coverage-7.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc4f1358cb0c78edef3ed237ef2c86056206bb8d9140e73b6b89fbcfcbdd40e1"},
|
||||
{file = "coverage-7.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8130a2aa2acb8788e0b56938786c33c7c98562697bf9f4c7d6e8e5e3a0501e4a"},
|
||||
{file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf271892d13e43bc2b51e6908ec9a6a5094a4df1d8af0bfc360088ee6c684409"},
|
||||
{file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4cdc86d54b5da0df6d3d3a2f0b710949286094c3a6700c21e9015932b81447e"},
|
||||
{file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ae71e7ddb7a413dd60052e90528f2f65270aad4b509563af6d03d53e979feafd"},
|
||||
{file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:38dd60d7bf242c4ed5b38e094baf6401faa114fc09e9e6632374388a404f98e7"},
|
||||
{file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa5b1c1bfc28384f1f53b69a023d789f72b2e0ab1b3787aae16992a7ca21056c"},
|
||||
{file = "coverage-7.4.4-cp38-cp38-win32.whl", hash = "sha256:dfa8fe35a0bb90382837b238fff375de15f0dcdb9ae68ff85f7a63649c98527e"},
|
||||
{file = "coverage-7.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:b2991665420a803495e0b90a79233c1433d6ed77ef282e8e152a324bbbc5e0c8"},
|
||||
{file = "coverage-7.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b799445b9f7ee8bf299cfaed6f5b226c0037b74886a4e11515e569b36fe310d"},
|
||||
{file = "coverage-7.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b4d33f418f46362995f1e9d4f3a35a1b6322cb959c31d88ae56b0298e1c22357"},
|
||||
{file = "coverage-7.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aadacf9a2f407a4688d700e4ebab33a7e2e408f2ca04dbf4aef17585389eff3e"},
|
||||
{file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c95949560050d04d46b919301826525597f07b33beba6187d04fa64d47ac82e"},
|
||||
{file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff7687ca3d7028d8a5f0ebae95a6e4827c5616b31a4ee1192bdfde697db110d4"},
|
||||
{file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5fc1de20b2d4a061b3df27ab9b7c7111e9a710f10dc2b84d33a4ab25065994ec"},
|
||||
{file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c74880fc64d4958159fbd537a091d2a585448a8f8508bf248d72112723974cbd"},
|
||||
{file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:742a76a12aa45b44d236815d282b03cfb1de3b4323f3e4ec933acfae08e54ade"},
|
||||
{file = "coverage-7.4.4-cp39-cp39-win32.whl", hash = "sha256:d89d7b2974cae412400e88f35d86af72208e1ede1a541954af5d944a8ba46c57"},
|
||||
{file = "coverage-7.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:9ca28a302acb19b6af89e90f33ee3e1906961f94b54ea37de6737b7ca9d8827c"},
|
||||
{file = "coverage-7.4.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:b2c5edc4ac10a7ef6605a966c58929ec6c1bd0917fb8c15cb3363f65aa40e677"},
|
||||
{file = "coverage-7.4.4.tar.gz", hash = "sha256:c901df83d097649e257e803be22592aedfd5182f07b3cc87d640bbb9afd50f49"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@ -1487,13 +1469,13 @@ tornado = ">=5.0.0,<7.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "freezegun"
|
||||
version = "1.5.0"
|
||||
version = "1.4.0"
|
||||
description = "Let your Python tests travel through time"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "freezegun-1.5.0-py3-none-any.whl", hash = "sha256:ec3f4ba030e34eb6cf7e1e257308aee2c60c3d038ff35996d7475760c9ff3719"},
|
||||
{file = "freezegun-1.5.0.tar.gz", hash = "sha256:200a64359b363aa3653d8aac289584078386c7c3da77339d257e46a01fb5c77c"},
|
||||
{file = "freezegun-1.4.0-py3-none-any.whl", hash = "sha256:55e0fc3c84ebf0a96a5aa23ff8b53d70246479e9a68863f1fcac5a3e52f19dd6"},
|
||||
{file = "freezegun-1.4.0.tar.gz", hash = "sha256:10939b0ba0ff5adaecf3b06a5c2f73071d9678e507c5eaedb23c761d56ac774b"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -1630,23 +1612,22 @@ requests = ["requests (>=2.20.0,<3.0.0.dev0)"]
|
||||
|
||||
[[package]]
|
||||
name = "gunicorn"
|
||||
version = "22.0.0"
|
||||
version = "21.2.0"
|
||||
description = "WSGI HTTP Server for UNIX"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "gunicorn-22.0.0-py3-none-any.whl", hash = "sha256:350679f91b24062c86e386e198a15438d53a7a8207235a78ba1b53df4c4378d9"},
|
||||
{file = "gunicorn-22.0.0.tar.gz", hash = "sha256:4a0b436239ff76fb33f11c07a16482c521a7e09c1ce3cc293c2330afe01bec63"},
|
||||
{file = "gunicorn-21.2.0-py3-none-any.whl", hash = "sha256:3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0"},
|
||||
{file = "gunicorn-21.2.0.tar.gz", hash = "sha256:88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
packaging = "*"
|
||||
|
||||
[package.extras]
|
||||
eventlet = ["eventlet (>=0.24.1,!=0.36.0)"]
|
||||
eventlet = ["eventlet (>=0.24.1)"]
|
||||
gevent = ["gevent (>=1.4.0)"]
|
||||
setproctitle = ["setproctitle"]
|
||||
testing = ["coverage", "eventlet", "gevent", "pytest", "pytest-cov"]
|
||||
tornado = ["tornado (>=0.2)"]
|
||||
|
||||
[[package]]
|
||||
@ -2731,13 +2712,13 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.5.0"
|
||||
version = "1.4.0"
|
||||
description = "plugin and hook calling mechanisms for python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
|
||||
{file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
|
||||
{file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"},
|
||||
{file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@ -2885,19 +2866,19 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.7.1"
|
||||
version = "2.7.0"
|
||||
description = "Data validation using Python type hints"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pydantic-2.7.1-py3-none-any.whl", hash = "sha256:e029badca45266732a9a79898a15ae2e8b14840b1eabbb25844be28f0b33f3d5"},
|
||||
{file = "pydantic-2.7.1.tar.gz", hash = "sha256:e9dbb5eada8abe4d9ae5f46b9939aead650cd2b68f249bb3a8139dbe125803cc"},
|
||||
{file = "pydantic-2.7.0-py3-none-any.whl", hash = "sha256:9dee74a271705f14f9a1567671d144a851c675b072736f0a7b2608fd9e495352"},
|
||||
{file = "pydantic-2.7.0.tar.gz", hash = "sha256:b5ecdd42262ca2462e2624793551e80911a1e989f462910bb81aef974b4bb383"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
annotated-types = ">=0.4.0"
|
||||
email-validator = {version = ">=2.0.0", optional = true, markers = "extra == \"email\""}
|
||||
pydantic-core = "2.18.2"
|
||||
pydantic-core = "2.18.1"
|
||||
typing-extensions = ">=4.6.1"
|
||||
|
||||
[package.extras]
|
||||
@ -2905,90 +2886,90 @@ email = ["email-validator (>=2.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.18.2"
|
||||
version = "2.18.1"
|
||||
description = "Core functionality for Pydantic validation and serialization"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pydantic_core-2.18.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9e08e867b306f525802df7cd16c44ff5ebbe747ff0ca6cf3fde7f36c05a59a81"},
|
||||
{file = "pydantic_core-2.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f0a21cbaa69900cbe1a2e7cad2aa74ac3cf21b10c3efb0fa0b80305274c0e8a2"},
|
||||
{file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0680b1f1f11fda801397de52c36ce38ef1c1dc841a0927a94f226dea29c3ae3d"},
|
||||
{file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:95b9d5e72481d3780ba3442eac863eae92ae43a5f3adb5b4d0a1de89d42bb250"},
|
||||
{file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fcf5cd9c4b655ad666ca332b9a081112cd7a58a8b5a6ca7a3104bc950f2038"},
|
||||
{file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b5155ff768083cb1d62f3e143b49a8a3432e6789a3abee8acd005c3c7af1c74"},
|
||||
{file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:553ef617b6836fc7e4df130bb851e32fe357ce36336d897fd6646d6058d980af"},
|
||||
{file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89ed9eb7d616ef5714e5590e6cf7f23b02d0d539767d33561e3675d6f9e3857"},
|
||||
{file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:75f7e9488238e920ab6204399ded280dc4c307d034f3924cd7f90a38b1829563"},
|
||||
{file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ef26c9e94a8c04a1b2924149a9cb081836913818e55681722d7f29af88fe7b38"},
|
||||
{file = "pydantic_core-2.18.2-cp310-none-win32.whl", hash = "sha256:182245ff6b0039e82b6bb585ed55a64d7c81c560715d1bad0cbad6dfa07b4027"},
|
||||
{file = "pydantic_core-2.18.2-cp310-none-win_amd64.whl", hash = "sha256:e23ec367a948b6d812301afc1b13f8094ab7b2c280af66ef450efc357d2ae543"},
|
||||
{file = "pydantic_core-2.18.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:219da3f096d50a157f33645a1cf31c0ad1fe829a92181dd1311022f986e5fbe3"},
|
||||
{file = "pydantic_core-2.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cc1cfd88a64e012b74e94cd00bbe0f9c6df57049c97f02bb07d39e9c852e19a4"},
|
||||
{file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b7133a6e6aeb8df37d6f413f7705a37ab4031597f64ab56384c94d98fa0e90"},
|
||||
{file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:224c421235f6102e8737032483f43c1a8cfb1d2f45740c44166219599358c2cd"},
|
||||
{file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b14d82cdb934e99dda6d9d60dc84a24379820176cc4a0d123f88df319ae9c150"},
|
||||
{file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2728b01246a3bba6de144f9e3115b532ee44bd6cf39795194fb75491824a1413"},
|
||||
{file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:470b94480bb5ee929f5acba6995251ada5e059a5ef3e0dfc63cca287283ebfa6"},
|
||||
{file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:997abc4df705d1295a42f95b4eec4950a37ad8ae46d913caeee117b6b198811c"},
|
||||
{file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75250dbc5290e3f1a0f4618db35e51a165186f9034eff158f3d490b3fed9f8a0"},
|
||||
{file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4456f2dca97c425231d7315737d45239b2b51a50dc2b6f0c2bb181fce6207664"},
|
||||
{file = "pydantic_core-2.18.2-cp311-none-win32.whl", hash = "sha256:269322dcc3d8bdb69f054681edff86276b2ff972447863cf34c8b860f5188e2e"},
|
||||
{file = "pydantic_core-2.18.2-cp311-none-win_amd64.whl", hash = "sha256:800d60565aec896f25bc3cfa56d2277d52d5182af08162f7954f938c06dc4ee3"},
|
||||
{file = "pydantic_core-2.18.2-cp311-none-win_arm64.whl", hash = "sha256:1404c69d6a676245199767ba4f633cce5f4ad4181f9d0ccb0577e1f66cf4c46d"},
|
||||
{file = "pydantic_core-2.18.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:fb2bd7be70c0fe4dfd32c951bc813d9fe6ebcbfdd15a07527796c8204bd36242"},
|
||||
{file = "pydantic_core-2.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6132dd3bd52838acddca05a72aafb6eab6536aa145e923bb50f45e78b7251043"},
|
||||
{file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d904828195733c183d20a54230c0df0eb46ec746ea1a666730787353e87182"},
|
||||
{file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9bd70772c720142be1020eac55f8143a34ec9f82d75a8e7a07852023e46617f"},
|
||||
{file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b8ed04b3582771764538f7ee7001b02e1170223cf9b75dff0bc698fadb00cf3"},
|
||||
{file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6dac87ddb34aaec85f873d737e9d06a3555a1cc1a8e0c44b7f8d5daeb89d86f"},
|
||||
{file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca4ae5a27ad7a4ee5170aebce1574b375de390bc01284f87b18d43a3984df72"},
|
||||
{file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:886eec03591b7cf058467a70a87733b35f44707bd86cf64a615584fd72488b7c"},
|
||||
{file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ca7b0c1f1c983e064caa85f3792dd2fe3526b3505378874afa84baf662e12241"},
|
||||
{file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b4356d3538c3649337df4074e81b85f0616b79731fe22dd11b99499b2ebbdf3"},
|
||||
{file = "pydantic_core-2.18.2-cp312-none-win32.whl", hash = "sha256:8b172601454f2d7701121bbec3425dd71efcb787a027edf49724c9cefc14c038"},
|
||||
{file = "pydantic_core-2.18.2-cp312-none-win_amd64.whl", hash = "sha256:b1bd7e47b1558ea872bd16c8502c414f9e90dcf12f1395129d7bb42a09a95438"},
|
||||
{file = "pydantic_core-2.18.2-cp312-none-win_arm64.whl", hash = "sha256:98758d627ff397e752bc339272c14c98199c613f922d4a384ddc07526c86a2ec"},
|
||||
{file = "pydantic_core-2.18.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:9fdad8e35f278b2c3eb77cbdc5c0a49dada440657bf738d6905ce106dc1de439"},
|
||||
{file = "pydantic_core-2.18.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1d90c3265ae107f91a4f279f4d6f6f1d4907ac76c6868b27dc7fb33688cfb347"},
|
||||
{file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:390193c770399861d8df9670fb0d1874f330c79caaca4642332df7c682bf6b91"},
|
||||
{file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:82d5d4d78e4448683cb467897fe24e2b74bb7b973a541ea1dcfec1d3cbce39fb"},
|
||||
{file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4774f3184d2ef3e14e8693194f661dea5a4d6ca4e3dc8e39786d33a94865cefd"},
|
||||
{file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4d938ec0adf5167cb335acb25a4ee69a8107e4984f8fbd2e897021d9e4ca21b"},
|
||||
{file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0e8b1be28239fc64a88a8189d1df7fad8be8c1ae47fcc33e43d4be15f99cc70"},
|
||||
{file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:868649da93e5a3d5eacc2b5b3b9235c98ccdbfd443832f31e075f54419e1b96b"},
|
||||
{file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:78363590ef93d5d226ba21a90a03ea89a20738ee5b7da83d771d283fd8a56761"},
|
||||
{file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:852e966fbd035a6468fc0a3496589b45e2208ec7ca95c26470a54daed82a0788"},
|
||||
{file = "pydantic_core-2.18.2-cp38-none-win32.whl", hash = "sha256:6a46e22a707e7ad4484ac9ee9f290f9d501df45954184e23fc29408dfad61350"},
|
||||
{file = "pydantic_core-2.18.2-cp38-none-win_amd64.whl", hash = "sha256:d91cb5ea8b11607cc757675051f61b3d93f15eca3cefb3e6c704a5d6e8440f4e"},
|
||||
{file = "pydantic_core-2.18.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:ae0a8a797a5e56c053610fa7be147993fe50960fa43609ff2a9552b0e07013e8"},
|
||||
{file = "pydantic_core-2.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:042473b6280246b1dbf530559246f6842b56119c2926d1e52b631bdc46075f2a"},
|
||||
{file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a388a77e629b9ec814c1b1e6b3b595fe521d2cdc625fcca26fbc2d44c816804"},
|
||||
{file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25add29b8f3b233ae90ccef2d902d0ae0432eb0d45370fe315d1a5cf231004b"},
|
||||
{file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f459a5ce8434614dfd39bbebf1041952ae01da6bed9855008cb33b875cb024c0"},
|
||||
{file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eff2de745698eb46eeb51193a9f41d67d834d50e424aef27df2fcdee1b153845"},
|
||||
{file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8309f67285bdfe65c372ea3722b7a5642680f3dba538566340a9d36e920b5f0"},
|
||||
{file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f93a8a2e3938ff656a7c1bc57193b1319960ac015b6e87d76c76bf14fe0244b4"},
|
||||
{file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:22057013c8c1e272eb8d0eebc796701167d8377441ec894a8fed1af64a0bf399"},
|
||||
{file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cfeecd1ac6cc1fb2692c3d5110781c965aabd4ec5d32799773ca7b1456ac636b"},
|
||||
{file = "pydantic_core-2.18.2-cp39-none-win32.whl", hash = "sha256:0d69b4c2f6bb3e130dba60d34c0845ba31b69babdd3f78f7c0c8fae5021a253e"},
|
||||
{file = "pydantic_core-2.18.2-cp39-none-win_amd64.whl", hash = "sha256:d9319e499827271b09b4e411905b24a426b8fb69464dfa1696258f53a3334641"},
|
||||
{file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a1874c6dd4113308bd0eb568418e6114b252afe44319ead2b4081e9b9521fe75"},
|
||||
{file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:ccdd111c03bfd3666bd2472b674c6899550e09e9f298954cfc896ab92b5b0e6d"},
|
||||
{file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e18609ceaa6eed63753037fc06ebb16041d17d28199ae5aba0052c51449650a9"},
|
||||
{file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e5c584d357c4e2baf0ff7baf44f4994be121e16a2c88918a5817331fc7599d7"},
|
||||
{file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43f0f463cf89ace478de71a318b1b4f05ebc456a9b9300d027b4b57c1a2064fb"},
|
||||
{file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e1b395e58b10b73b07b7cf740d728dd4ff9365ac46c18751bf8b3d8cca8f625a"},
|
||||
{file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0098300eebb1c837271d3d1a2cd2911e7c11b396eac9661655ee524a7f10587b"},
|
||||
{file = "pydantic_core-2.18.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:36789b70d613fbac0a25bb07ab3d9dba4d2e38af609c020cf4d888d165ee0bf3"},
|
||||
{file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3f9a801e7c8f1ef8718da265bba008fa121243dfe37c1cea17840b0944dfd72c"},
|
||||
{file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:3a6515ebc6e69d85502b4951d89131ca4e036078ea35533bb76327f8424531ce"},
|
||||
{file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20aca1e2298c56ececfd8ed159ae4dde2df0781988c97ef77d5c16ff4bd5b400"},
|
||||
{file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:223ee893d77a310a0391dca6df00f70bbc2f36a71a895cecd9a0e762dc37b349"},
|
||||
{file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2334ce8c673ee93a1d6a65bd90327588387ba073c17e61bf19b4fd97d688d63c"},
|
||||
{file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:cbca948f2d14b09d20268cda7b0367723d79063f26c4ffc523af9042cad95592"},
|
||||
{file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b3ef08e20ec49e02d5c6717a91bb5af9b20f1805583cb0adfe9ba2c6b505b5ae"},
|
||||
{file = "pydantic_core-2.18.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6fdc8627910eed0c01aed6a390a252fe3ea6d472ee70fdde56273f198938374"},
|
||||
{file = "pydantic_core-2.18.2.tar.gz", hash = "sha256:2e29d20810dfc3043ee13ac7d9e25105799817683348823f305ab3f349b9386e"},
|
||||
{file = "pydantic_core-2.18.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ee9cf33e7fe14243f5ca6977658eb7d1042caaa66847daacbd2117adb258b226"},
|
||||
{file = "pydantic_core-2.18.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6b7bbb97d82659ac8b37450c60ff2e9f97e4eb0f8a8a3645a5568b9334b08b50"},
|
||||
{file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df4249b579e75094f7e9bb4bd28231acf55e308bf686b952f43100a5a0be394c"},
|
||||
{file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d0491006a6ad20507aec2be72e7831a42efc93193d2402018007ff827dc62926"},
|
||||
{file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ae80f72bb7a3e397ab37b53a2b49c62cc5496412e71bc4f1277620a7ce3f52b"},
|
||||
{file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:58aca931bef83217fca7a390e0486ae327c4af9c3e941adb75f8772f8eeb03a1"},
|
||||
{file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1be91ad664fc9245404a789d60cba1e91c26b1454ba136d2a1bf0c2ac0c0505a"},
|
||||
{file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:667880321e916a8920ef49f5d50e7983792cf59f3b6079f3c9dac2b88a311d17"},
|
||||
{file = "pydantic_core-2.18.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f7054fdc556f5421f01e39cbb767d5ec5c1139ea98c3e5b350e02e62201740c7"},
|
||||
{file = "pydantic_core-2.18.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:030e4f9516f9947f38179249778709a460a3adb516bf39b5eb9066fcfe43d0e6"},
|
||||
{file = "pydantic_core-2.18.1-cp310-none-win32.whl", hash = "sha256:2e91711e36e229978d92642bfc3546333a9127ecebb3f2761372e096395fc649"},
|
||||
{file = "pydantic_core-2.18.1-cp310-none-win_amd64.whl", hash = "sha256:9a29726f91c6cb390b3c2338f0df5cd3e216ad7a938762d11c994bb37552edb0"},
|
||||
{file = "pydantic_core-2.18.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9ece8a49696669d483d206b4474c367852c44815fca23ac4e48b72b339807f80"},
|
||||
{file = "pydantic_core-2.18.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a5d83efc109ceddb99abd2c1316298ced2adb4570410defe766851a804fcd5b"},
|
||||
{file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7973c381283783cd1043a8c8f61ea5ce7a3a58b0369f0ee0ee975eaf2f2a1b"},
|
||||
{file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:54c7375c62190a7845091f521add19b0f026bcf6ae674bdb89f296972272e86d"},
|
||||
{file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd63cec4e26e790b70544ae5cc48d11b515b09e05fdd5eff12e3195f54b8a586"},
|
||||
{file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:561cf62c8a3498406495cfc49eee086ed2bb186d08bcc65812b75fda42c38294"},
|
||||
{file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68717c38a68e37af87c4da20e08f3e27d7e4212e99e96c3d875fbf3f4812abfc"},
|
||||
{file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d5728e93d28a3c63ee513d9ffbac9c5989de8c76e049dbcb5bfe4b923a9739d"},
|
||||
{file = "pydantic_core-2.18.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f0f17814c505f07806e22b28856c59ac80cee7dd0fbb152aed273e116378f519"},
|
||||
{file = "pydantic_core-2.18.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d816f44a51ba5175394bc6c7879ca0bd2be560b2c9e9f3411ef3a4cbe644c2e9"},
|
||||
{file = "pydantic_core-2.18.1-cp311-none-win32.whl", hash = "sha256:09f03dfc0ef8c22622eaa8608caa4a1e189cfb83ce847045eca34f690895eccb"},
|
||||
{file = "pydantic_core-2.18.1-cp311-none-win_amd64.whl", hash = "sha256:27f1009dc292f3b7ca77feb3571c537276b9aad5dd4efb471ac88a8bd09024e9"},
|
||||
{file = "pydantic_core-2.18.1-cp311-none-win_arm64.whl", hash = "sha256:48dd883db92e92519201f2b01cafa881e5f7125666141a49ffba8b9facc072b0"},
|
||||
{file = "pydantic_core-2.18.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b6b0e4912030c6f28bcb72b9ebe4989d6dc2eebcd2a9cdc35fefc38052dd4fe8"},
|
||||
{file = "pydantic_core-2.18.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3202a429fe825b699c57892d4371c74cc3456d8d71b7f35d6028c96dfecad31"},
|
||||
{file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3982b0a32d0a88b3907e4b0dc36809fda477f0757c59a505d4e9b455f384b8b"},
|
||||
{file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25595ac311f20e5324d1941909b0d12933f1fd2171075fcff763e90f43e92a0d"},
|
||||
{file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:14fe73881cf8e4cbdaded8ca0aa671635b597e42447fec7060d0868b52d074e6"},
|
||||
{file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca976884ce34070799e4dfc6fbd68cb1d181db1eefe4a3a94798ddfb34b8867f"},
|
||||
{file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:684d840d2c9ec5de9cb397fcb3f36d5ebb6fa0d94734f9886032dd796c1ead06"},
|
||||
{file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:54764c083bbe0264f0f746cefcded6cb08fbbaaf1ad1d78fb8a4c30cff999a90"},
|
||||
{file = "pydantic_core-2.18.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:201713f2f462e5c015b343e86e68bd8a530a4f76609b33d8f0ec65d2b921712a"},
|
||||
{file = "pydantic_core-2.18.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fd1a9edb9dd9d79fbeac1ea1f9a8dd527a6113b18d2e9bcc0d541d308dae639b"},
|
||||
{file = "pydantic_core-2.18.1-cp312-none-win32.whl", hash = "sha256:d5e6b7155b8197b329dc787356cfd2684c9d6a6b1a197f6bbf45f5555a98d411"},
|
||||
{file = "pydantic_core-2.18.1-cp312-none-win_amd64.whl", hash = "sha256:9376d83d686ec62e8b19c0ac3bf8d28d8a5981d0df290196fb6ef24d8a26f0d6"},
|
||||
{file = "pydantic_core-2.18.1-cp312-none-win_arm64.whl", hash = "sha256:c562b49c96906b4029b5685075fe1ebd3b5cc2601dfa0b9e16c2c09d6cbce048"},
|
||||
{file = "pydantic_core-2.18.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:3e352f0191d99fe617371096845070dee295444979efb8f27ad941227de6ad09"},
|
||||
{file = "pydantic_core-2.18.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c0295d52b012cbe0d3059b1dba99159c3be55e632aae1999ab74ae2bd86a33d7"},
|
||||
{file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56823a92075780582d1ffd4489a2e61d56fd3ebb4b40b713d63f96dd92d28144"},
|
||||
{file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dd3f79e17b56741b5177bcc36307750d50ea0698df6aa82f69c7db32d968c1c2"},
|
||||
{file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38a5024de321d672a132b1834a66eeb7931959c59964b777e8f32dbe9523f6b1"},
|
||||
{file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2ce426ee691319d4767748c8e0895cfc56593d725594e415f274059bcf3cb76"},
|
||||
{file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2adaeea59849ec0939af5c5d476935f2bab4b7f0335b0110f0f069a41024278e"},
|
||||
{file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9b6431559676a1079eac0f52d6d0721fb8e3c5ba43c37bc537c8c83724031feb"},
|
||||
{file = "pydantic_core-2.18.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:85233abb44bc18d16e72dc05bf13848a36f363f83757541f1a97db2f8d58cfd9"},
|
||||
{file = "pydantic_core-2.18.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:641a018af4fe48be57a2b3d7a1f0f5dbca07c1d00951d3d7463f0ac9dac66622"},
|
||||
{file = "pydantic_core-2.18.1-cp38-none-win32.whl", hash = "sha256:63d7523cd95d2fde0d28dc42968ac731b5bb1e516cc56b93a50ab293f4daeaad"},
|
||||
{file = "pydantic_core-2.18.1-cp38-none-win_amd64.whl", hash = "sha256:907a4d7720abfcb1c81619863efd47c8a85d26a257a2dbebdb87c3b847df0278"},
|
||||
{file = "pydantic_core-2.18.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:aad17e462f42ddbef5984d70c40bfc4146c322a2da79715932cd8976317054de"},
|
||||
{file = "pydantic_core-2.18.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:94b9769ba435b598b547c762184bcfc4783d0d4c7771b04a3b45775c3589ca44"},
|
||||
{file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80e0e57cc704a52fb1b48f16d5b2c8818da087dbee6f98d9bf19546930dc64b5"},
|
||||
{file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:76b86e24039c35280ceee6dce7e62945eb93a5175d43689ba98360ab31eebc4a"},
|
||||
{file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12a05db5013ec0ca4a32cc6433f53faa2a014ec364031408540ba858c2172bb0"},
|
||||
{file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:250ae39445cb5475e483a36b1061af1bc233de3e9ad0f4f76a71b66231b07f88"},
|
||||
{file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a32204489259786a923e02990249c65b0f17235073149d0033efcebe80095570"},
|
||||
{file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6395a4435fa26519fd96fdccb77e9d00ddae9dd6c742309bd0b5610609ad7fb2"},
|
||||
{file = "pydantic_core-2.18.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2533ad2883f001efa72f3d0e733fb846710c3af6dcdd544fe5bf14fa5fe2d7db"},
|
||||
{file = "pydantic_core-2.18.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b560b72ed4816aee52783c66854d96157fd8175631f01ef58e894cc57c84f0f6"},
|
||||
{file = "pydantic_core-2.18.1-cp39-none-win32.whl", hash = "sha256:582cf2cead97c9e382a7f4d3b744cf0ef1a6e815e44d3aa81af3ad98762f5a9b"},
|
||||
{file = "pydantic_core-2.18.1-cp39-none-win_amd64.whl", hash = "sha256:ca71d501629d1fa50ea7fa3b08ba884fe10cefc559f5c6c8dfe9036c16e8ae89"},
|
||||
{file = "pydantic_core-2.18.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e178e5b66a06ec5bf51668ec0d4ac8cfb2bdcb553b2c207d58148340efd00143"},
|
||||
{file = "pydantic_core-2.18.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:72722ce529a76a4637a60be18bd789d8fb871e84472490ed7ddff62d5fed620d"},
|
||||
{file = "pydantic_core-2.18.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fe0c1ce5b129455e43f941f7a46f61f3d3861e571f2905d55cdbb8b5c6f5e2c"},
|
||||
{file = "pydantic_core-2.18.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4284c621f06a72ce2cb55f74ea3150113d926a6eb78ab38340c08f770eb9b4d"},
|
||||
{file = "pydantic_core-2.18.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a0c3e718f4e064efde68092d9d974e39572c14e56726ecfaeebbe6544521f47"},
|
||||
{file = "pydantic_core-2.18.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2027493cc44c23b598cfaf200936110433d9caa84e2c6cf487a83999638a96ac"},
|
||||
{file = "pydantic_core-2.18.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:76909849d1a6bffa5a07742294f3fa1d357dc917cb1fe7b470afbc3a7579d539"},
|
||||
{file = "pydantic_core-2.18.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ee7ccc7fb7e921d767f853b47814c3048c7de536663e82fbc37f5eb0d532224b"},
|
||||
{file = "pydantic_core-2.18.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ee2794111c188548a4547eccc73a6a8527fe2af6cf25e1a4ebda2fd01cdd2e60"},
|
||||
{file = "pydantic_core-2.18.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a139fe9f298dc097349fb4f28c8b81cc7a202dbfba66af0e14be5cfca4ef7ce5"},
|
||||
{file = "pydantic_core-2.18.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d074b07a10c391fc5bbdcb37b2f16f20fcd9e51e10d01652ab298c0d07908ee2"},
|
||||
{file = "pydantic_core-2.18.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c69567ddbac186e8c0aadc1f324a60a564cfe25e43ef2ce81bcc4b8c3abffbae"},
|
||||
{file = "pydantic_core-2.18.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:baf1c7b78cddb5af00971ad5294a4583188bda1495b13760d9f03c9483bb6203"},
|
||||
{file = "pydantic_core-2.18.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2684a94fdfd1b146ff10689c6e4e815f6a01141781c493b97342cdc5b06f4d5d"},
|
||||
{file = "pydantic_core-2.18.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:73c1bc8a86a5c9e8721a088df234265317692d0b5cd9e86e975ce3bc3db62a59"},
|
||||
{file = "pydantic_core-2.18.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e60defc3c15defb70bb38dd605ff7e0fae5f6c9c7cbfe0ad7868582cb7e844a6"},
|
||||
{file = "pydantic_core-2.18.1.tar.gz", hash = "sha256:de9d3e8717560eb05e28739d1b35e4eac2e458553a52a301e51352a7ffc86a35"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -3116,23 +3097,23 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "8.2.0"
|
||||
version = "8.1.1"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pytest-8.2.0-py3-none-any.whl", hash = "sha256:1733f0620f6cda4095bbf0d9ff8022486e91892245bb9e7d5542c018f612f233"},
|
||||
{file = "pytest-8.2.0.tar.gz", hash = "sha256:d507d4482197eac0ba2bae2e9babf0672eb333017bcedaa5fb1a3d42c1174b3f"},
|
||||
{file = "pytest-8.1.1-py3-none-any.whl", hash = "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7"},
|
||||
{file = "pytest-8.1.1.tar.gz", hash = "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
iniconfig = "*"
|
||||
packaging = "*"
|
||||
pluggy = ">=1.5,<2.0"
|
||||
pluggy = ">=1.4,<2.0"
|
||||
|
||||
[package.extras]
|
||||
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
|
||||
testing = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-django"
|
||||
@ -3402,21 +3383,6 @@ requests = ">=2.0.0"
|
||||
[package.extras]
|
||||
rsa = ["oauthlib[signedtoken] (>=3.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "restrictedpython"
|
||||
version = "7.1"
|
||||
description = "RestrictedPython is a defined subset of the Python language which allows to provide a program input into a trusted environment."
|
||||
optional = false
|
||||
python-versions = ">=3.7, <3.13"
|
||||
files = [
|
||||
{file = "RestrictedPython-7.1-py3-none-any.whl", hash = "sha256:56d0c73e5de1757702053383601b0fcd3fb2e428039ee1df860409ad67b17d2b"},
|
||||
{file = "RestrictedPython-7.1.tar.gz", hash = "sha256:875aeb51c139d78e34cef8605dc65309b449168060dd08551a1fe9edb47cb9a5"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["Sphinx", "sphinx-rtd-theme"]
|
||||
test = ["pytest", "pytest-mock"]
|
||||
|
||||
[[package]]
|
||||
name = "rich"
|
||||
version = "13.7.0"
|
||||
@ -3559,28 +3525,28 @@ pyasn1 = ">=0.1.3"
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.4.2"
|
||||
version = "0.3.7"
|
||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "ruff-0.4.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:8d14dc8953f8af7e003a485ef560bbefa5f8cc1ad994eebb5b12136049bbccc5"},
|
||||
{file = "ruff-0.4.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:24016ed18db3dc9786af103ff49c03bdf408ea253f3cb9e3638f39ac9cf2d483"},
|
||||
{file = "ruff-0.4.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2e06459042ac841ed510196c350ba35a9b24a643e23db60d79b2db92af0c2b"},
|
||||
{file = "ruff-0.4.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3afabaf7ba8e9c485a14ad8f4122feff6b2b93cc53cd4dad2fd24ae35112d5c5"},
|
||||
{file = "ruff-0.4.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:799eb468ea6bc54b95527143a4ceaf970d5aa3613050c6cff54c85fda3fde480"},
|
||||
{file = "ruff-0.4.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ec4ba9436a51527fb6931a8839af4c36a5481f8c19e8f5e42c2f7ad3a49f5069"},
|
||||
{file = "ruff-0.4.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6a2243f8f434e487c2a010c7252150b1fdf019035130f41b77626f5655c9ca22"},
|
||||
{file = "ruff-0.4.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8772130a063f3eebdf7095da00c0b9898bd1774c43b336272c3e98667d4fb8fa"},
|
||||
{file = "ruff-0.4.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ab165ef5d72392b4ebb85a8b0fbd321f69832a632e07a74794c0e598e7a8376"},
|
||||
{file = "ruff-0.4.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1f32cadf44c2020e75e0c56c3408ed1d32c024766bd41aedef92aa3ca28eef68"},
|
||||
{file = "ruff-0.4.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:22e306bf15e09af45ca812bc42fa59b628646fa7c26072555f278994890bc7ac"},
|
||||
{file = "ruff-0.4.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:82986bb77ad83a1719c90b9528a9dd663c9206f7c0ab69282af8223566a0c34e"},
|
||||
{file = "ruff-0.4.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:652e4ba553e421a6dc2a6d4868bc3b3881311702633eb3672f9f244ded8908cd"},
|
||||
{file = "ruff-0.4.2-py3-none-win32.whl", hash = "sha256:7891ee376770ac094da3ad40c116258a381b86c7352552788377c6eb16d784fe"},
|
||||
{file = "ruff-0.4.2-py3-none-win_amd64.whl", hash = "sha256:5ec481661fb2fd88a5d6cf1f83403d388ec90f9daaa36e40e2c003de66751798"},
|
||||
{file = "ruff-0.4.2-py3-none-win_arm64.whl", hash = "sha256:cbd1e87c71bca14792948c4ccb51ee61c3296e164019d2d484f3eaa2d360dfaf"},
|
||||
{file = "ruff-0.4.2.tar.gz", hash = "sha256:33bcc160aee2520664bc0859cfeaebc84bb7323becff3f303b8f1f2d81cb4edc"},
|
||||
{file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0e8377cccb2f07abd25e84fc5b2cbe48eeb0fea9f1719cad7caedb061d70e5ce"},
|
||||
{file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:15a4d1cc1e64e556fa0d67bfd388fed416b7f3b26d5d1c3e7d192c897e39ba4b"},
|
||||
{file = "ruff-0.3.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d28bdf3d7dc71dd46929fafeec98ba89b7c3550c3f0978e36389b5631b793663"},
|
||||
{file = "ruff-0.3.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:379b67d4f49774ba679593b232dcd90d9e10f04d96e3c8ce4a28037ae473f7bb"},
|
||||
{file = "ruff-0.3.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c060aea8ad5ef21cdfbbe05475ab5104ce7827b639a78dd55383a6e9895b7c51"},
|
||||
{file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ebf8f615dde968272d70502c083ebf963b6781aacd3079081e03b32adfe4d58a"},
|
||||
{file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d48098bd8f5c38897b03604f5428901b65e3c97d40b3952e38637b5404b739a2"},
|
||||
{file = "ruff-0.3.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da8a4fda219bf9024692b1bc68c9cff4b80507879ada8769dc7e985755d662ea"},
|
||||
{file = "ruff-0.3.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c44e0149f1d8b48c4d5c33d88c677a4aa22fd09b1683d6a7ff55b816b5d074f"},
|
||||
{file = "ruff-0.3.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3050ec0af72b709a62ecc2aca941b9cd479a7bf2b36cc4562f0033d688e44fa1"},
|
||||
{file = "ruff-0.3.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a29cc38e4c1ab00da18a3f6777f8b50099d73326981bb7d182e54a9a21bb4ff7"},
|
||||
{file = "ruff-0.3.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b15cc59c19edca917f51b1956637db47e200b0fc5e6e1878233d3a938384b0b"},
|
||||
{file = "ruff-0.3.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e491045781b1e38b72c91247cf4634f040f8d0cb3e6d3d64d38dcf43616650b4"},
|
||||
{file = "ruff-0.3.7-py3-none-win32.whl", hash = "sha256:bc931de87593d64fad3a22e201e55ad76271f1d5bfc44e1a1887edd0903c7d9f"},
|
||||
{file = "ruff-0.3.7-py3-none-win_amd64.whl", hash = "sha256:5ef0e501e1e39f35e03c2acb1d1238c595b8bb36cf7a170e7c1df1b73da00e74"},
|
||||
{file = "ruff-0.3.7-py3-none-win_arm64.whl", hash = "sha256:789e144f6dc7019d1f92a812891c645274ed08af6037d11fc65fcbc183b7d59f"},
|
||||
{file = "ruff-0.3.7.tar.gz", hash = "sha256:d5c1aebee5162c2226784800ae031f660c350e7a3402c4d1f8ea4e97e232e3ba"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3619,13 +3585,13 @@ django-query = ["django (>=3.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "selenium"
|
||||
version = "4.20.0"
|
||||
version = "4.19.0"
|
||||
description = ""
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "selenium-4.20.0-py3-none-any.whl", hash = "sha256:b1d0c33b38ca27d0499183e48e1dd09ff26973481f5d3ef2983073813ae6588d"},
|
||||
{file = "selenium-4.20.0.tar.gz", hash = "sha256:0bd564ee166980d419a8aaf4ac00289bc152afcf2eadca5efe8c8e36711853fd"},
|
||||
{file = "selenium-4.19.0-py3-none-any.whl", hash = "sha256:5b4f49240d61e687a73f7968ae2517d403882aae3550eae2a229c745e619f1d9"},
|
||||
{file = "selenium-4.19.0.tar.gz", hash = "sha256:d9dfd6d0b021d71d0a48b865fe7746490ba82b81e9c87b212360006629eb1853"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -3637,18 +3603,18 @@ urllib3 = {version = ">=1.26,<3", extras = ["socks"]}
|
||||
|
||||
[[package]]
|
||||
name = "sentry-sdk"
|
||||
version = "2.0.1"
|
||||
version = "1.45.0"
|
||||
description = "Python client for Sentry (https://sentry.io)"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "sentry_sdk-2.0.1-py2.py3-none-any.whl", hash = "sha256:b54c54a2160f509cf2757260d0cf3885b608c6192c2555a3857e3a4d0f84bdb3"},
|
||||
{file = "sentry_sdk-2.0.1.tar.gz", hash = "sha256:c278e0f523f6f0ee69dc43ad26dcdb1202dffe5ac326ae31472e012d941bee21"},
|
||||
{file = "sentry-sdk-1.45.0.tar.gz", hash = "sha256:509aa9678c0512344ca886281766c2e538682f8acfa50fd8d405f8c417ad0625"},
|
||||
{file = "sentry_sdk-1.45.0-py2.py3-none-any.whl", hash = "sha256:1ce29e30240cc289a027011103a8c83885b15ef2f316a60bcc7c5300afa144f1"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
certifi = "*"
|
||||
urllib3 = ">=1.26.11"
|
||||
urllib3 = {version = ">=1.26.11", markers = "python_version >= \"3.6\""}
|
||||
|
||||
[package.extras]
|
||||
aiohttp = ["aiohttp (>=3.5)"]
|
||||
@ -3997,13 +3963,13 @@ wsproto = ">=0.14"
|
||||
|
||||
[[package]]
|
||||
name = "twilio"
|
||||
version = "9.0.5"
|
||||
version = "9.0.4"
|
||||
description = "Twilio API client and TwiML generator"
|
||||
optional = false
|
||||
python-versions = ">=3.7.0"
|
||||
files = [
|
||||
{file = "twilio-9.0.5-py2.py3-none-any.whl", hash = "sha256:5e09e910b9368f50f23cb3c3dd5ba77164d80a81e9d97db955cbac322deb2a4e"},
|
||||
{file = "twilio-9.0.5.tar.gz", hash = "sha256:e9b5727943584d25d618fe502f0100fc5283215f31c863f80b5c64581b4702b0"},
|
||||
{file = "twilio-9.0.4-py2.py3-none-any.whl", hash = "sha256:086abae1a575a0ee89a72c0792d814ee349fe55c8df76b563ecfc49463c3c533"},
|
||||
{file = "twilio-9.0.4.tar.gz", hash = "sha256:d493d5bde6361bb713dffec00b9465ff84978b71334dd75002152e79604688ba"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -4715,4 +4681,4 @@ files = [
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "~3.12"
|
||||
content-hash = "647a426ecb06caf2b8f3165c4e85ed0439568e692b6ade8c7933683985c90b18"
|
||||
content-hash = "a5774b4e09217805c887700b8a0f457a39c7af40ca59823f00c1f6e8678469e1"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "authentik"
|
||||
version = "2024.4.1"
|
||||
version = "2024.2.2"
|
||||
description = ""
|
||||
authors = ["authentik Team <hello@goauthentik.io>"]
|
||||
|
||||
@ -83,7 +83,6 @@ filterwarnings = [
|
||||
]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
authentik_client = "2024.4.1.post1714149882"
|
||||
argon2-cffi = "*"
|
||||
celery = "*"
|
||||
channels = { version = "*", extras = ["daphne"] }
|
||||
@ -133,7 +132,6 @@ pyjwt = "*"
|
||||
python = "~3.12"
|
||||
pyyaml = "*"
|
||||
requests-oauthlib = "*"
|
||||
restrictedpython = "*"
|
||||
scim2-filter-parser = "*"
|
||||
sentry-sdk = "*"
|
||||
service_identity = "*"
|
||||
|
39
schema.yml
39
schema.yml
@ -1,7 +1,7 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: authentik
|
||||
version: 2024.4.1
|
||||
version: 2024.2.2
|
||||
description: Making authentication simple.
|
||||
contact:
|
||||
email: hello@goauthentik.io
|
||||
@ -11852,10 +11852,6 @@ paths:
|
||||
name: execution_logging
|
||||
schema:
|
||||
type: boolean
|
||||
- in: query
|
||||
name: execution_user
|
||||
schema:
|
||||
type: integer
|
||||
- in: query
|
||||
name: expression
|
||||
schema:
|
||||
@ -33569,9 +33565,6 @@ components:
|
||||
readOnly: true
|
||||
expression:
|
||||
type: string
|
||||
execution_user:
|
||||
type: integer
|
||||
nullable: true
|
||||
required:
|
||||
- bound_to
|
||||
- component
|
||||
@ -33595,9 +33588,6 @@ components:
|
||||
expression:
|
||||
type: string
|
||||
minLength: 1
|
||||
execution_user:
|
||||
type: integer
|
||||
nullable: true
|
||||
required:
|
||||
- expression
|
||||
- name
|
||||
@ -38851,9 +38841,6 @@ components:
|
||||
expression:
|
||||
type: string
|
||||
minLength: 1
|
||||
execution_user:
|
||||
type: integer
|
||||
nullable: true
|
||||
PatchedFlowRequest:
|
||||
type: object
|
||||
description: Flow Serializer
|
||||
@ -44727,26 +44714,6 @@ components:
|
||||
- num_pk
|
||||
- parent_name
|
||||
- pk
|
||||
UserGroupRequest:
|
||||
type: object
|
||||
description: Simplified Group Serializer for user's groups
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 80
|
||||
is_superuser:
|
||||
type: boolean
|
||||
description: Users added to this group will be superusers.
|
||||
parent:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
attributes:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
required:
|
||||
- name
|
||||
UserLoginChallenge:
|
||||
type: object
|
||||
description: Empty challenge
|
||||
@ -45433,11 +45400,7 @@ components:
|
||||
- $ref: '#/components/schemas/WebAuthnDeviceType'
|
||||
readOnly: true
|
||||
nullable: true
|
||||
aaguid:
|
||||
type: string
|
||||
readOnly: true
|
||||
required:
|
||||
- aaguid
|
||||
- created_on
|
||||
- device_type
|
||||
- name
|
||||
|
@ -4,6 +4,7 @@ services:
|
||||
postgresql:
|
||||
container_name: postgres
|
||||
image: docker.io/library/postgres:16
|
||||
command: "-c max_connections=500"
|
||||
volumes:
|
||||
- db-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
|
0
tests/benchmark/__init__.py
Normal file
0
tests/benchmark/__init__.py
Normal file
30
tests/benchmark/docker-compose.yml
Normal file
30
tests/benchmark/docker-compose.yml
Normal file
@ -0,0 +1,30 @@
|
||||
---
|
||||
services:
|
||||
prometheus:
|
||||
image: quay.io/prometheus/prometheus:latest
|
||||
restart: unless-stopped
|
||||
command:
|
||||
- --enable-feature=native-histograms
|
||||
- --web.enable-remote-write-receiver
|
||||
- --config.file=/etc/prometheus/prometheus.yml
|
||||
- --storage.tsdb.path=/prometheus
|
||||
- --web.console.libraries=/usr/share/prometheus/console_libraries
|
||||
- --web.console.templates=/usr/share/prometheus/consoles
|
||||
ports:
|
||||
- 127.0.0.1:9090:9090
|
||||
volumes:
|
||||
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
|
||||
- ./prometheus:/prometheus
|
||||
user: root
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
GF_AUTH_ANONYMOUS_ENABLED: "true"
|
||||
GF_AUTH_ANONYMOUS_ORG_ROLE: Admin
|
||||
ports:
|
||||
- 127.0.0.1:3000:3000
|
||||
volumes:
|
||||
- ./grafana/provisioning:/etc/grafana/provisioning:ro
|
||||
- ./grafana/dashboards:/var/lib/grafana/dashboards:ro
|
79
tests/benchmark/event_list.js
Normal file
79
tests/benchmark/event_list.js
Normal file
@ -0,0 +1,79 @@
|
||||
import exec from "k6/execution";
|
||||
import http from "k6/http";
|
||||
import { check } from "k6";
|
||||
|
||||
const host = __ENV.BENCH_HOST ? __ENV.BENCH_HOST : "localhost";
|
||||
const VUs = __ENV.VUS ? __ENV.VUS : 8;
|
||||
|
||||
export const options = {
|
||||
discardResponseBodies: true,
|
||||
scenarios: Object.fromEntries(
|
||||
[
|
||||
// Number of events, page size
|
||||
[1000, 100],
|
||||
[10000, 20],
|
||||
[10000, 100],
|
||||
[100000, 100],
|
||||
[1000000, 100],
|
||||
].map((obj, i) => [
|
||||
`${obj[0]}_${obj[1]}`,
|
||||
{
|
||||
executor: "constant-vus",
|
||||
vus: VUs,
|
||||
duration: "150s",
|
||||
startTime: `${165 * i}s`,
|
||||
env: {
|
||||
EVENT_COUNT: `${obj[0]}`,
|
||||
PAGE_SIZE: `${obj[1]}`,
|
||||
},
|
||||
tags: {
|
||||
testid: `event_list_${obj[0]}_${obj[1]}`,
|
||||
event_count: `${obj[0]}`,
|
||||
page_size: `${obj[1]}`,
|
||||
},
|
||||
},
|
||||
]),
|
||||
),
|
||||
};
|
||||
|
||||
export default function () {
|
||||
const event_count = Number(__ENV.EVENT_COUNT);
|
||||
const domain = `event-list-${event_count}.${host}:9000`;
|
||||
const page_size = Number(__ENV.PAGE_SIZE);
|
||||
const pages = Math.round(event_count / page_size);
|
||||
const params = {
|
||||
headers: {
|
||||
Authorization: "Bearer akadmin",
|
||||
"Content-Type": "application/json",
|
||||
Accept: "*/*",
|
||||
},
|
||||
};
|
||||
|
||||
if (pages <= 10) {
|
||||
for (let page = 1; page <= pages; page++) {
|
||||
let res = http.get(
|
||||
http.url`http://${domain}/api/v3/events/events/?page=${page}&page_size=${page_size}`,
|
||||
params,
|
||||
);
|
||||
check(res, {
|
||||
"status is 200": (res) => res.status === 200,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
let requests = [];
|
||||
for (let page = 1; page <= pages; page++) {
|
||||
requests.push([
|
||||
"GET",
|
||||
http.url`http://${domain}/api/v3/events/events/?page=${page}&page_size=${page_size}`,
|
||||
null,
|
||||
params,
|
||||
]);
|
||||
}
|
||||
const responses = http.batch(requests);
|
||||
for (let page = 1; page <= pages; page++) {
|
||||
check(responses[page - 1], {
|
||||
"status is 200": (res) => res.status === 200,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
345
tests/benchmark/fixtures.py
Executable file
345
tests/benchmark/fixtures.py
Executable file
@ -0,0 +1,345 @@
|
||||
#!/usr/bin/env python3
|
||||
import random
|
||||
import sys
|
||||
from collections.abc import Iterable
|
||||
from multiprocessing import Process
|
||||
from os import environ
|
||||
from uuid import uuid4
|
||||
|
||||
import django
|
||||
|
||||
environ.setdefault("DJANGO_SETTINGS_MODULE", "authentik.root.settings")
|
||||
environ.setdefault("AUTHENTIK_BOOTSTRAP_PASSWORD", "akadmin")
|
||||
environ.setdefault("AUTHENTIK_BOOTSTRAP_TOKEN", "akadmin")
|
||||
environ.setdefault("AUTHENTIK_BOOTSTRAP_EMAIL", "akadmin@authentik.test")
|
||||
django.setup()
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from authentik.core.models import Application, Group, User
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.policies.expression.models import ExpressionPolicy
|
||||
from authentik.policies.models import PolicyBinding
|
||||
from authentik.providers.oauth2.models import OAuth2Provider
|
||||
from authentik.stages.authenticator_static.models import StaticToken
|
||||
from authentik.tenants.models import Domain, Tenant
|
||||
|
||||
settings.CELERY["task_always_eager"] = True
|
||||
|
||||
host = environ.get("BENCH_HOST", "localhost")
|
||||
|
||||
|
||||
class TestSuite:
|
||||
TEST_NAME: str
|
||||
TEST_CASES: Iterable[Iterable[int | str | bool]]
|
||||
|
||||
@classmethod
|
||||
def get_testcases(cls):
|
||||
return [cls(params) for params in cls.TEST_CASES]
|
||||
|
||||
def __init__(self, params: Iterable[int | str | bool]):
|
||||
self.params = params
|
||||
|
||||
def __str__(self):
|
||||
return (
|
||||
"-".join([self.TEST_NAME] + [str(param) for param in self.params])
|
||||
.replace("_", "-")
|
||||
.lower()
|
||||
)
|
||||
|
||||
@property
|
||||
def schema_name(self):
|
||||
return f"t_{str(self).replace('-', '_')}"
|
||||
|
||||
@property
|
||||
def domain_name(self):
|
||||
return f"{str(self)}.{host}"
|
||||
|
||||
def create(self):
|
||||
created = False
|
||||
t = Tenant.objects.filter(schema_name=self.schema_name).first()
|
||||
if not t:
|
||||
created = True
|
||||
t = Tenant.objects.create(schema_name=self.schema_name, name=uuid4())
|
||||
Domain.objects.get_or_create(tenant=t, domain=self.domain_name)
|
||||
if created:
|
||||
with t:
|
||||
self.create_data(*self.params)
|
||||
|
||||
def create_data(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def delete(self):
|
||||
Tenant.objects.filter(schema_name=self.schema_name).delete()
|
||||
|
||||
|
||||
class UserList(TestSuite):
|
||||
TEST_NAME = "user-list"
|
||||
TEST_CASES = [
|
||||
(1000, 0, 0),
|
||||
(10000, 0, 0),
|
||||
(1000, 3, 0),
|
||||
(10000, 3, 0),
|
||||
(1000, 20, 0),
|
||||
(10000, 20, 0),
|
||||
(1000, 20, 3),
|
||||
(10000, 20, 3),
|
||||
]
|
||||
|
||||
def create_data(self, user_count: int, groups_per_user: int, parents_per_group: int):
|
||||
Group.objects.bulk_create([Group(name=uuid4()) for _ in range(groups_per_user * 5)])
|
||||
for group in Group.objects.exclude(name="authentik Admins"):
|
||||
for _ in range(parents_per_group):
|
||||
new_group = Group.objects.create(name=uuid4())
|
||||
group.parent = new_group
|
||||
group.save()
|
||||
group = new_group
|
||||
User.objects.bulk_create(
|
||||
[
|
||||
User(
|
||||
username=uuid4(),
|
||||
name=uuid4(),
|
||||
)
|
||||
for _ in range(user_count)
|
||||
]
|
||||
)
|
||||
if groups_per_user:
|
||||
for user in User.objects.exclude_anonymous().exclude(username="akadmin"):
|
||||
user.ak_groups.set(
|
||||
Group.objects.exclude(name="authentik Admins").order_by("?")[:groups_per_user]
|
||||
)
|
||||
|
||||
|
||||
class GroupList(TestSuite):
|
||||
TEST_NAME = "group-list"
|
||||
TEST_CASES = [
|
||||
(1000, 0, False),
|
||||
(10000, 0, False),
|
||||
(1000, 1000, False),
|
||||
(1000, 10000, False),
|
||||
(1000, 0, True),
|
||||
(10000, 0, True),
|
||||
]
|
||||
|
||||
def create_data(self, group_count, users_per_group, with_parent):
|
||||
User.objects.bulk_create(
|
||||
[
|
||||
User(
|
||||
username=uuid4(),
|
||||
name=uuid4(),
|
||||
)
|
||||
for _ in range(users_per_group * 5)
|
||||
]
|
||||
)
|
||||
if with_parent:
|
||||
parents = Group.objects.bulk_create([Group(name=uuid4()) for _ in range(group_count)])
|
||||
groups = Group.objects.bulk_create(
|
||||
[
|
||||
Group(name=uuid4(), parent=(parents[i] if with_parent else None))
|
||||
for i in range(group_count)
|
||||
]
|
||||
)
|
||||
if users_per_group:
|
||||
for group in groups:
|
||||
group.users.set(
|
||||
User.objects.exclude_anonymous()
|
||||
.exclude(username="akadmin")
|
||||
.order_by("?")[:users_per_group]
|
||||
)
|
||||
|
||||
|
||||
class Login(TestSuite):
|
||||
TEST_NAME = "login"
|
||||
TEST_CASES = [
|
||||
("no-mfa",),
|
||||
("with-mfa",),
|
||||
]
|
||||
|
||||
def create_data(self, mfa: str):
|
||||
user = User(username="test", name=uuid4())
|
||||
user.set_password("verySecurePassword")
|
||||
user.save()
|
||||
|
||||
if mfa == "with-mfa":
|
||||
device = user.staticdevice_set.create()
|
||||
# Multiple token with same token for all the iterations in the test
|
||||
device.token_set.bulk_create(
|
||||
[StaticToken(device=device, token=f"staticToken") for _ in range(1_000_000)]
|
||||
)
|
||||
|
||||
|
||||
class ProviderOauth2(TestSuite):
|
||||
TEST_NAME = "provider-oauth2"
|
||||
TEST_CASES = [
|
||||
(2, 50, 2),
|
||||
(0, 0, 0),
|
||||
(10, 0, 0),
|
||||
(100, 0, 0),
|
||||
(0, 10, 0),
|
||||
(0, 100, 0),
|
||||
(0, 0, 10),
|
||||
(0, 0, 100),
|
||||
(10, 10, 10),
|
||||
(100, 100, 100),
|
||||
]
|
||||
|
||||
def create_data(
|
||||
self, user_policies_count: int, group_policies_count: int, expression_policies_count: int
|
||||
):
|
||||
user = User(username="test", name=uuid4())
|
||||
user.set_password("verySecurePassword")
|
||||
user.save()
|
||||
|
||||
provider = OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
authorization_flow=Flow.objects.get(
|
||||
slug="default-provider-authorization-implicit-consent"
|
||||
),
|
||||
signing_key=CertificateKeyPair.objects.get(name="authentik Self-signed Certificate"),
|
||||
redirect_uris="http://test.localhost",
|
||||
client_id="123456",
|
||||
client_secret="123456",
|
||||
)
|
||||
application = Application.objects.create(slug="test", name="test", provider=provider)
|
||||
|
||||
User.objects.bulk_create(
|
||||
[
|
||||
User(
|
||||
username=uuid4(),
|
||||
name=uuid4(),
|
||||
)
|
||||
for _ in range(user_policies_count)
|
||||
]
|
||||
)
|
||||
PolicyBinding.objects.bulk_create(
|
||||
[
|
||||
PolicyBinding(
|
||||
user=user,
|
||||
target=application,
|
||||
order=random.randint(1, 1_000_000),
|
||||
)
|
||||
for user in User.objects.exclude(username="akadmin").exclude_anonymous()
|
||||
]
|
||||
)
|
||||
|
||||
Group.objects.bulk_create([Group(name=uuid4()) for _ in range(group_policies_count)])
|
||||
PolicyBinding.objects.bulk_create(
|
||||
[
|
||||
PolicyBinding(
|
||||
group=group,
|
||||
target=application,
|
||||
order=random.randint(1, 1_000_000),
|
||||
)
|
||||
for group in Group.objects.exclude(name="authentik Admins")
|
||||
]
|
||||
)
|
||||
user.ak_groups.set(Group.objects.exclude(name="authentik Admins").order_by("?")[:1])
|
||||
|
||||
[
|
||||
ExpressionPolicy(
|
||||
name=f"test-{uuid4()}",
|
||||
expression="return True",
|
||||
).save()
|
||||
for _ in range(expression_policies_count)
|
||||
]
|
||||
PolicyBinding.objects.bulk_create(
|
||||
[
|
||||
PolicyBinding(
|
||||
policy=policy,
|
||||
target=application,
|
||||
order=random.randint(1, 1_000_000),
|
||||
)
|
||||
for policy in ExpressionPolicy.objects.filter(name__startswith="test-")
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class EventList(TestSuite):
|
||||
TEST_NAME = "event-list"
|
||||
TEST_CASES = [
|
||||
(1_000,),
|
||||
(10_000,),
|
||||
(100_000,),
|
||||
(1_000_000,),
|
||||
]
|
||||
|
||||
def create_data(self, event_count: int):
|
||||
for _ in range(event_count // 1000):
|
||||
Event.objects.bulk_create(
|
||||
[
|
||||
Event(
|
||||
user={
|
||||
"pk": str(uuid4()),
|
||||
"name": str(uuid4()),
|
||||
"username": str(uuid4()),
|
||||
"email": f"{uuid4()}@example.org",
|
||||
},
|
||||
action="custom_benchmark",
|
||||
app="tests_benchmarks",
|
||||
context={
|
||||
str(uuid4()): str(uuid4()),
|
||||
str(uuid4()): str(uuid4()),
|
||||
str(uuid4()): str(uuid4()),
|
||||
str(uuid4()): str(uuid4()),
|
||||
str(uuid4()): str(uuid4()),
|
||||
},
|
||||
client_ip="192.0.2.42",
|
||||
)
|
||||
for _ in range(1000)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class UserGroupCreate(TestSuite):
|
||||
TEST_NAME = "user-group-create"
|
||||
TEST_CASES = [
|
||||
(),
|
||||
]
|
||||
|
||||
def create_data(self):
|
||||
pass
|
||||
|
||||
|
||||
def main(action: str, selected_suite: str | None = None):
|
||||
testsuites = TestSuite.__subclasses__()
|
||||
testcases = []
|
||||
for testsuite in testsuites:
|
||||
testcases += testsuite.get_testcases()
|
||||
|
||||
match action:
|
||||
case "create":
|
||||
to_create = []
|
||||
for testcase in testcases:
|
||||
if selected_suite and testcase.TEST_NAME != selected_suite:
|
||||
continue
|
||||
testcase.create()
|
||||
# to_create.append(testcase)
|
||||
# processes = [Process(target=testcase.create) for testcase in to_create]
|
||||
# for p in processes:
|
||||
# p.start()
|
||||
# for p in processes:
|
||||
# p.join()
|
||||
case "list":
|
||||
print(*[testsuite.TEST_NAME for testsuite in testsuites], sep="\n")
|
||||
case "delete":
|
||||
for testcase in testcases:
|
||||
if selected_suite and testcase.TEST_NAME != selected_suite:
|
||||
continue
|
||||
testcase.delete()
|
||||
case _:
|
||||
print("Unknown action. Should be create, list or delete")
|
||||
exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
action = "create"
|
||||
else:
|
||||
action = sys.argv[1]
|
||||
if len(sys.argv) < 3:
|
||||
testsuite = None
|
||||
else:
|
||||
testsuite = sys.argv[2]
|
||||
main(action, testsuite)
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user