Compare commits
159 Commits
version/20
...
enterprise
| Author | SHA1 | Date | |
|---|---|---|---|
| 60000812fd | |||
| 929e70669a | |||
| 36114284bf | |||
| a65ea0de94 | |||
| 2056b0cbee | |||
| af85ddf60b | |||
| e8e42261e3 | |||
| 9fda4e91ad | |||
| a11f1258e1 | |||
| a97578ac62 | |||
| 41aa36d06f | |||
| 62fc4c56e4 | |||
| 4514412010 | |||
| 463efac469 | |||
| f4508659cf | |||
| 336f6f0dc2 | |||
| c19a887356 | |||
| 09931bcbc2 | |||
| 7a4293bf17 | |||
| 6e569acd84 | |||
| 02c69d767f | |||
| 1863a9a12b | |||
| b981bc5ba1 | |||
| 5da02971eb | |||
| 1f49ee77df | |||
| baf8f18d54 | |||
| 5445b1235a | |||
| 2893a54ffb | |||
| 94eff50306 | |||
| 0befc26507 | |||
| 629d5df763 | |||
| 3098313981 | |||
| c0a370bb2b | |||
| a19d915d2b | |||
| 9a0dc50174 | |||
| ac0a708f92 | |||
| 0ffaf0393e | |||
| 9bb3aa0374 | |||
| f6a32dc6e5 | |||
| af83fc7245 | |||
| 84de15568a | |||
| 29f8a82b49 | |||
| cd05c0ec19 | |||
| c19a1b373a | |||
| 31b9cbfb85 | |||
| c0fe0dab61 | |||
| 1bd42345b9 | |||
| 90e7545d57 | |||
| 78d42c391d | |||
| 2ad831adb0 | |||
| 5eaa94917b | |||
| 6c0d462410 | |||
| 9dc2c26ba9 | |||
| 774a84f9e6 | |||
| 56015d883b | |||
| 9d15fa4a57 | |||
| bb7338f5c1 | |||
| f949141d03 | |||
| 646d133c30 | |||
| 3ee3adc509 | |||
| 1b4fee2bac | |||
| 10c358401d | |||
| 9dddbd2f0c | |||
| 078d643c20 | |||
| 733b7cf139 | |||
| f83fab214b | |||
| 9ce460a0ac | |||
| e69a380a39 | |||
| 2d89f42c68 | |||
| 3d4d167542 | |||
| ee8d3c5146 | |||
| 0406b0d95a | |||
| 44d49bb14c | |||
| afb1686be7 | |||
| 6b1802697d | |||
| 943fd6b78b | |||
| ed33d314cd | |||
| d343ccc539 | |||
| 31e8fb7c8c | |||
| 23faa0b839 | |||
| 3cbfd836ac | |||
| 10ab6e4327 | |||
| 561d2220bc | |||
| e6c47db9f8 | |||
| 5f5171c472 | |||
| bdf4236973 | |||
| a61a41d7d0 | |||
| c7532d35f2 | |||
| 27baedfea4 | |||
| e3011eab9a | |||
| 9635dd98f3 | |||
| bd0d7edbc4 | |||
| 9b05418306 | |||
| d4e15f0f39 | |||
| ec9c2266eb | |||
| 5ebd280087 | |||
| 1cc8d80600 | |||
| 3b70cd735e | |||
| 42766e13da | |||
| 8938fa5a7e | |||
| 4c8f610cdb | |||
| 8690200cd8 | |||
| 91145b7929 | |||
| d255e53756 | |||
| d51e6a5551 | |||
| 5433839ea0 | |||
| 863a7e6095 | |||
| 50db80428c | |||
| ffd5234396 | |||
| 95890638a5 | |||
| f7d2a68b1d | |||
| 83ecb64f33 | |||
| 40b0f7df8d | |||
| ee6fcdfbd8 | |||
| 94623615a6 | |||
| aa4f817856 | |||
| c3aefd55a2 | |||
| 1298cdc338 | |||
| 3eaaa35a4c | |||
| d17f781d11 | |||
| c82b79f10f | |||
| 0aa7be6e2c | |||
| 9811ec57df | |||
| 393e5f236c | |||
| 59ae9c6148 | |||
| fd8e20bdeb | |||
| 737aced000 | |||
| dc3559c7e9 | |||
| 02bd699917 | |||
| 5fccbd7c04 | |||
| 6fc92bd50c | |||
| 687f6d683a | |||
| 4a8329649c | |||
| 0c296efede | |||
| 112520fd88 | |||
| ee648269f7 | |||
| 15be3f2461 | |||
| ef9557c578 | |||
| 48700c0e9c | |||
| 18a48030a8 | |||
| 640d0a4a95 | |||
| 6b8782556c | |||
| 7f6f3b6602 | |||
| 3367ac0e08 | |||
| d5ea0ffdc6 | |||
| 93f1638b39 | |||
| 37525175fa | |||
| 0db1e52f90 | |||
| 3e8620b686 | |||
| 6687ffc6d2 | |||
| e265ee253b | |||
| 7763a3673c | |||
| d99005e130 | |||
| c61f96e770 | |||
| 83622dd934 | |||
| 2eebd0eaa1 | |||
| b61d918c5c | |||
| 076a4f4772 | |||
| b3872b35f8 |
@ -3,14 +3,14 @@ current_version = 2024.12.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*))?
|
||||
serialize =
|
||||
serialize =
|
||||
{major}.{minor}.{patch}-{rc_t}{rc_n}
|
||||
{major}.{minor}.{patch}
|
||||
message = release: {new_version}
|
||||
tag_name = version/{new_version}
|
||||
|
||||
[bumpversion:part:rc_t]
|
||||
values =
|
||||
values =
|
||||
rc
|
||||
final
|
||||
optional_value = final
|
||||
|
||||
6
.github/workflows/ci-main.yml
vendored
6
.github/workflows/ci-main.yml
vendored
@ -134,7 +134,7 @@ jobs:
|
||||
- name: Setup authentik env
|
||||
uses: ./.github/actions/setup
|
||||
- name: Create k8s Kind Cluster
|
||||
uses: helm/kind-action@v1.11.0
|
||||
uses: helm/kind-action@v1.12.0
|
||||
- name: run integration
|
||||
run: |
|
||||
poetry run coverage run manage.py test tests/integration
|
||||
@ -168,6 +168,8 @@ jobs:
|
||||
glob: tests/e2e/test_provider_saml* tests/e2e/test_source_saml*
|
||||
- name: ldap
|
||||
glob: tests/e2e/test_provider_ldap* tests/e2e/test_source_ldap*
|
||||
- name: rac
|
||||
glob: tests/e2e/test_provider_rac*
|
||||
- name: radius
|
||||
glob: tests/e2e/test_provider_radius*
|
||||
- name: scim
|
||||
@ -243,7 +245,7 @@ jobs:
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3.2.0
|
||||
uses: docker/setup-qemu-action@v3.3.0
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: prepare variables
|
||||
|
||||
2
.github/workflows/ci-outpost.yml
vendored
2
.github/workflows/ci-outpost.yml
vendored
@ -82,7 +82,7 @@ jobs:
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3.2.0
|
||||
uses: docker/setup-qemu-action@v3.3.0
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: prepare variables
|
||||
|
||||
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@ -2,7 +2,7 @@ name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, next, version*]
|
||||
branches: [main, "*", next, version*]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
schedule:
|
||||
|
||||
8
.github/workflows/release-publish.yml
vendored
8
.github/workflows/release-publish.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3.2.0
|
||||
uses: docker/setup-qemu-action@v3.3.0
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: prepare variables
|
||||
@ -83,7 +83,7 @@ jobs:
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3.2.0
|
||||
uses: docker/setup-qemu-action@v3.3.0
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: prepare variables
|
||||
@ -188,8 +188,8 @@ jobs:
|
||||
aws-region: ${{ env.AWS_REGION }}
|
||||
- name: Upload template
|
||||
run: |
|
||||
aws s3 cp website/docs/install-config/install/aws/template.yaml s3://authentik-cloudformation-templates/authentik.ecs.${{ github.ref }}.yaml
|
||||
aws s3 cp website/docs/install-config/install/aws/template.yaml s3://authentik-cloudformation-templates/authentik.ecs.latest.yaml
|
||||
aws s3 cp --acl=public-read website/docs/install-config/install/aws/template.yaml s3://authentik-cloudformation-templates/authentik.ecs.${{ github.ref }}.yaml
|
||||
aws s3 cp --acl=public-read website/docs/install-config/install/aws/template.yaml s3://authentik-cloudformation-templates/authentik.ecs.latest.yaml
|
||||
test-release:
|
||||
needs:
|
||||
- build-server
|
||||
|
||||
@ -16,5 +16,5 @@ def get_full_version() -> str:
|
||||
"""Get full version, with build hash appended"""
|
||||
version = __version__
|
||||
if (build_hash := get_build_hash()) != "":
|
||||
version += "." + build_hash
|
||||
return f"{version}+{build_hash}"
|
||||
return version
|
||||
|
||||
@ -7,7 +7,9 @@ from sys import version as python_version
|
||||
from typing import TypedDict
|
||||
|
||||
from cryptography.hazmat.backends.openssl.backend import backend
|
||||
from django.conf import settings
|
||||
from django.utils.timezone import now
|
||||
from django.views.debug import SafeExceptionReporterFilter
|
||||
from drf_spectacular.utils import extend_schema
|
||||
from rest_framework.fields import SerializerMethodField
|
||||
from rest_framework.request import Request
|
||||
@ -52,10 +54,16 @@ class SystemInfoSerializer(PassiveSerializer):
|
||||
def get_http_headers(self, request: Request) -> dict[str, str]:
|
||||
"""Get HTTP Request headers"""
|
||||
headers = {}
|
||||
raw_session = request._request.COOKIES.get(settings.SESSION_COOKIE_NAME)
|
||||
for key, value in request.META.items():
|
||||
if not isinstance(value, str):
|
||||
continue
|
||||
headers[key] = value
|
||||
actual_value = value
|
||||
if raw_session in actual_value:
|
||||
actual_value = actual_value.replace(
|
||||
raw_session, SafeExceptionReporterFilter.cleansed_substitute
|
||||
)
|
||||
headers[key] = actual_value
|
||||
return headers
|
||||
|
||||
def get_http_host(self, request: Request) -> str:
|
||||
|
||||
@ -1,12 +1,16 @@
|
||||
"""authentik administration overview"""
|
||||
|
||||
from socket import gethostname
|
||||
|
||||
from django.conf import settings
|
||||
from drf_spectacular.utils import extend_schema, inline_serializer
|
||||
from rest_framework.fields import IntegerField
|
||||
from packaging.version import parse
|
||||
from rest_framework.fields import BooleanField, CharField
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from authentik import get_full_version
|
||||
from authentik.rbac.permissions import HasPermission
|
||||
from authentik.root.celery import CELERY_APP
|
||||
|
||||
@ -16,11 +20,38 @@ class WorkerView(APIView):
|
||||
|
||||
permission_classes = [HasPermission("authentik_rbac.view_system_info")]
|
||||
|
||||
@extend_schema(responses=inline_serializer("Workers", fields={"count": IntegerField()}))
|
||||
@extend_schema(
|
||||
responses=inline_serializer(
|
||||
"Worker",
|
||||
fields={
|
||||
"worker_id": CharField(),
|
||||
"version": CharField(),
|
||||
"version_matching": BooleanField(),
|
||||
},
|
||||
many=True,
|
||||
)
|
||||
)
|
||||
def get(self, request: Request) -> Response:
|
||||
"""Get currently connected worker count."""
|
||||
count = len(CELERY_APP.control.ping(timeout=0.5))
|
||||
raw: list[dict[str, dict]] = CELERY_APP.control.ping(timeout=0.5)
|
||||
our_version = parse(get_full_version())
|
||||
response = []
|
||||
for worker in raw:
|
||||
key = list(worker.keys())[0]
|
||||
version = worker[key].get("version")
|
||||
version_matching = False
|
||||
if version:
|
||||
version_matching = parse(version) == our_version
|
||||
response.append(
|
||||
{"worker_id": key, "version": version, "version_matching": version_matching}
|
||||
)
|
||||
# In debug we run with `task_always_eager`, so tasks are ran on the main process
|
||||
if settings.DEBUG: # pragma: no cover
|
||||
count += 1
|
||||
return Response({"count": count})
|
||||
response.append(
|
||||
{
|
||||
"worker_id": f"authentik-debug@{gethostname()}",
|
||||
"version": get_full_version(),
|
||||
"version_matching": True,
|
||||
}
|
||||
)
|
||||
return Response(response)
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
"""authentik admin app config"""
|
||||
|
||||
from prometheus_client import Gauge, Info
|
||||
from prometheus_client import Info
|
||||
|
||||
from authentik.blueprints.apps import ManagedAppConfig
|
||||
|
||||
PROM_INFO = Info("authentik_version", "Currently running authentik version")
|
||||
GAUGE_WORKERS = Gauge("authentik_admin_workers", "Currently connected workers")
|
||||
|
||||
|
||||
class AuthentikAdminConfig(ManagedAppConfig):
|
||||
|
||||
@ -1,14 +1,35 @@
|
||||
"""admin signals"""
|
||||
|
||||
from django.dispatch import receiver
|
||||
from packaging.version import parse
|
||||
from prometheus_client import Gauge
|
||||
|
||||
from authentik.admin.apps import GAUGE_WORKERS
|
||||
from authentik import get_full_version
|
||||
from authentik.root.celery import CELERY_APP
|
||||
from authentik.root.monitoring import monitoring_set
|
||||
|
||||
GAUGE_WORKERS = Gauge(
|
||||
"authentik_admin_workers",
|
||||
"Currently connected workers, their versions and if they are the same version as authentik",
|
||||
["version", "version_matched"],
|
||||
)
|
||||
|
||||
|
||||
_version = parse(get_full_version())
|
||||
|
||||
|
||||
@receiver(monitoring_set)
|
||||
def monitoring_set_workers(sender, **kwargs):
|
||||
"""Set worker gauge"""
|
||||
count = len(CELERY_APP.control.ping(timeout=0.5))
|
||||
GAUGE_WORKERS.set(count)
|
||||
raw: list[dict[str, dict]] = CELERY_APP.control.ping(timeout=0.5)
|
||||
worker_version_count = {}
|
||||
for worker in raw:
|
||||
key = list(worker.keys())[0]
|
||||
version = worker[key].get("version")
|
||||
version_matching = False
|
||||
if version:
|
||||
version_matching = parse(version) == _version
|
||||
worker_version_count.setdefault(version, {"count": 0, "matching": version_matching})
|
||||
worker_version_count[version]["count"] += 1
|
||||
for version, stats in worker_version_count.items():
|
||||
GAUGE_WORKERS.labels(version, stats["matching"]).set(stats["count"])
|
||||
|
||||
@ -34,7 +34,7 @@ class TestAdminAPI(TestCase):
|
||||
response = self.client.get(reverse("authentik_api:admin_workers"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
body = loads(response.content)
|
||||
self.assertEqual(body["count"], 0)
|
||||
self.assertEqual(len(body), 0)
|
||||
|
||||
def test_metrics(self):
|
||||
"""Test metrics API"""
|
||||
|
||||
@ -1,67 +0,0 @@
|
||||
"""API Authorization"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.db.models import Model
|
||||
from django.db.models.query import QuerySet
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework.authentication import get_authorization_header
|
||||
from rest_framework.filters import BaseFilterBackend
|
||||
from rest_framework.permissions import BasePermission
|
||||
from rest_framework.request import Request
|
||||
|
||||
from authentik.api.authentication import validate_auth
|
||||
from authentik.rbac.filters import ObjectFilter
|
||||
|
||||
|
||||
class OwnerFilter(BaseFilterBackend):
|
||||
"""Filter objects by their owner"""
|
||||
|
||||
owner_key = "user"
|
||||
|
||||
def filter_queryset(self, request: Request, queryset: QuerySet, view) -> QuerySet:
|
||||
if request.user.is_superuser:
|
||||
return queryset
|
||||
return queryset.filter(**{self.owner_key: request.user})
|
||||
|
||||
|
||||
class SecretKeyFilter(DjangoFilterBackend):
|
||||
"""Allow access to all objects when authenticated with secret key as token.
|
||||
|
||||
Replaces both DjangoFilterBackend and ObjectFilter"""
|
||||
|
||||
def filter_queryset(self, request: Request, queryset: QuerySet, view) -> QuerySet:
|
||||
auth_header = get_authorization_header(request)
|
||||
token = validate_auth(auth_header)
|
||||
if token and token == settings.SECRET_KEY:
|
||||
return queryset
|
||||
queryset = ObjectFilter().filter_queryset(request, queryset, view)
|
||||
return super().filter_queryset(request, queryset, view)
|
||||
|
||||
|
||||
class OwnerPermissions(BasePermission):
|
||||
"""Authorize requests by an object's owner matching the requesting user"""
|
||||
|
||||
owner_key = "user"
|
||||
|
||||
def has_permission(self, request: Request, view) -> bool:
|
||||
"""If the user is authenticated, we allow all requests here. For listing, the
|
||||
object-level permissions are done by the filter backend"""
|
||||
return request.user.is_authenticated
|
||||
|
||||
def has_object_permission(self, request: Request, view, obj: Model) -> bool:
|
||||
"""Check if the object's owner matches the currently logged in user"""
|
||||
if not hasattr(obj, self.owner_key):
|
||||
return False
|
||||
owner = getattr(obj, self.owner_key)
|
||||
if owner != request.user:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class OwnerSuperuserPermissions(OwnerPermissions):
|
||||
"""Similar to OwnerPermissions, except always allow access for superusers"""
|
||||
|
||||
def has_object_permission(self, request: Request, view, obj: Model) -> bool:
|
||||
if request.user.is_superuser:
|
||||
return True
|
||||
return super().has_object_permission(request, view, obj)
|
||||
68
authentik/blueprints/management/commands/blueprint_shell.py
Normal file
68
authentik/blueprints/management/commands/blueprint_shell.py
Normal file
@ -0,0 +1,68 @@
|
||||
"""Test and debug Blueprints"""
|
||||
|
||||
import atexit
|
||||
import readline
|
||||
from pathlib import Path
|
||||
from pprint import pformat
|
||||
from sys import exit as sysexit
|
||||
from textwrap import indent
|
||||
|
||||
from django.core.management.base import BaseCommand, no_translations
|
||||
from structlog.stdlib import get_logger
|
||||
from yaml import load
|
||||
|
||||
from authentik.blueprints.v1.common import BlueprintLoader, EntryInvalidError
|
||||
from authentik.core.management.commands.shell import get_banner_text
|
||||
from authentik.lib.utils.errors import exception_to_string
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""Test and debug Blueprints"""
|
||||
|
||||
lines = []
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
histfolder = Path("~").expanduser() / Path(".local/share/authentik")
|
||||
histfolder.mkdir(parents=True, exist_ok=True)
|
||||
histfile = histfolder / Path("blueprint_shell_history")
|
||||
readline.parse_and_bind("tab: complete")
|
||||
readline.parse_and_bind("set editing-mode vi")
|
||||
|
||||
try:
|
||||
readline.read_history_file(str(histfile))
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
atexit.register(readline.write_history_file, str(histfile))
|
||||
|
||||
@no_translations
|
||||
def handle(self, *args, **options):
|
||||
"""Interactively debug blueprint files"""
|
||||
self.stdout.write(get_banner_text("Blueprint shell"))
|
||||
self.stdout.write("Type '.eval' to evaluate previously entered statement(s).")
|
||||
|
||||
def do_eval():
|
||||
yaml_input = "\n".join([line for line in self.lines if line])
|
||||
data = load(yaml_input, BlueprintLoader)
|
||||
self.stdout.write(pformat(data))
|
||||
self.lines = []
|
||||
|
||||
while True:
|
||||
try:
|
||||
line = input("> ")
|
||||
if line == ".eval":
|
||||
do_eval()
|
||||
else:
|
||||
self.lines.append(line)
|
||||
except EntryInvalidError as exc:
|
||||
self.stdout.write("Failed to evaluate expression:")
|
||||
self.stdout.write(indent(exception_to_string(exc), prefix=" "))
|
||||
except EOFError:
|
||||
break
|
||||
except KeyboardInterrupt:
|
||||
self.stdout.write()
|
||||
sysexit(0)
|
||||
self.stdout.write()
|
||||
@ -126,7 +126,7 @@ class Command(BaseCommand):
|
||||
def_name_perm = f"model_{model_path}_permissions"
|
||||
def_path_perm = f"#/$defs/{def_name_perm}"
|
||||
self.schema["$defs"][def_name_perm] = self.model_permissions(model)
|
||||
return {
|
||||
template = {
|
||||
"type": "object",
|
||||
"required": ["model", "identifiers"],
|
||||
"properties": {
|
||||
@ -143,6 +143,11 @@ class Command(BaseCommand):
|
||||
"identifiers": {"$ref": def_path},
|
||||
},
|
||||
}
|
||||
# Meta models don't require identifiers, as there's no matching database model to find
|
||||
if issubclass(model, BaseMetaModel):
|
||||
del template["properties"]["identifiers"]
|
||||
template["required"].remove("identifiers")
|
||||
return template
|
||||
|
||||
def field_to_jsonschema(self, field: Field) -> dict:
|
||||
"""Convert a single field to json schema"""
|
||||
|
||||
@ -202,6 +202,9 @@ class Blueprint:
|
||||
class YAMLTag:
|
||||
"""Base class for all YAML Tags"""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return str(self.resolve(BlueprintEntry(""), Blueprint()))
|
||||
|
||||
def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any:
|
||||
"""Implement yaml tag logic"""
|
||||
raise NotImplementedError
|
||||
|
||||
@ -14,10 +14,10 @@ from rest_framework.response import Response
|
||||
from rest_framework.validators import UniqueValidator
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from authentik.api.authorization import SecretKeyFilter
|
||||
from authentik.brands.models import Brand
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import ModelSerializer, PassiveSerializer
|
||||
from authentik.rbac.filters import SecretKeyFilter
|
||||
from authentik.tenants.utils import get_current_tenant
|
||||
|
||||
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
"""Application Roles API Viewset"""
|
||||
|
||||
from django.http import HttpRequest
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import ModelSerializer
|
||||
from authentik.core.models import (
|
||||
Application,
|
||||
ApplicationEntitlement,
|
||||
User,
|
||||
)
|
||||
|
||||
|
||||
@ -18,7 +19,10 @@ class ApplicationEntitlementSerializer(ModelSerializer):
|
||||
|
||||
def validate_app(self, app: Application) -> Application:
|
||||
"""Ensure user has permission to view"""
|
||||
user: User = self._context["request"].user
|
||||
request: HttpRequest = self.context.get("request")
|
||||
if not request and SERIALIZER_CONTEXT_BLUEPRINT in self.context:
|
||||
return app
|
||||
user = request.user
|
||||
if user.has_perm("view_application", app) or user.has_perm(
|
||||
"authentik_core.view_application"
|
||||
):
|
||||
|
||||
@ -2,16 +2,12 @@
|
||||
|
||||
from typing import TypedDict
|
||||
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from guardian.utils import get_anonymous_user
|
||||
from rest_framework import mixins
|
||||
from rest_framework.fields import SerializerMethodField
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.viewsets import GenericViewSet
|
||||
from ua_parser import user_agent_parser
|
||||
|
||||
from authentik.api.authorization import OwnerSuperuserPermissions
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import ModelSerializer
|
||||
from authentik.core.models import AuthenticatedSession
|
||||
@ -110,11 +106,4 @@ class AuthenticatedSessionViewSet(
|
||||
search_fields = ["user__username", "last_ip", "last_user_agent"]
|
||||
filterset_fields = ["user__username", "last_ip", "last_user_agent"]
|
||||
ordering = ["user__username"]
|
||||
permission_classes = [OwnerSuperuserPermissions]
|
||||
filter_backends = [DjangoFilterBackend, OrderingFilter, SearchFilter]
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user if self.request else get_anonymous_user()
|
||||
if user.is_superuser:
|
||||
return super().get_queryset()
|
||||
return super().get_queryset().filter(user=user.pk)
|
||||
owner_field = "user"
|
||||
|
||||
@ -2,19 +2,16 @@
|
||||
|
||||
from collections.abc import Iterable
|
||||
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from drf_spectacular.utils import OpenApiResponse, extend_schema
|
||||
from rest_framework import mixins
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.fields import CharField, ReadOnlyField, SerializerMethodField
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.parsers import MultiPartParser
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.viewsets import GenericViewSet
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.api.authorization import OwnerFilter, OwnerSuperuserPermissions
|
||||
from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT
|
||||
from authentik.core.api.object_types import TypesMixin
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
@ -189,11 +186,10 @@ class UserSourceConnectionViewSet(
|
||||
|
||||
queryset = UserSourceConnection.objects.all()
|
||||
serializer_class = UserSourceConnectionSerializer
|
||||
permission_classes = [OwnerSuperuserPermissions]
|
||||
filterset_fields = ["user", "source__slug"]
|
||||
search_fields = ["source__slug"]
|
||||
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
|
||||
ordering = ["source__slug", "pk"]
|
||||
owner_field = "user"
|
||||
|
||||
|
||||
class GroupSourceConnectionSerializer(SourceSerializer):
|
||||
@ -228,8 +224,7 @@ class GroupSourceConnectionViewSet(
|
||||
|
||||
queryset = GroupSourceConnection.objects.all()
|
||||
serializer_class = GroupSourceConnectionSerializer
|
||||
permission_classes = [OwnerSuperuserPermissions]
|
||||
filterset_fields = ["group", "source__slug"]
|
||||
search_fields = ["source__slug"]
|
||||
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
|
||||
ordering = ["source__slug", "pk"]
|
||||
owner_field = "user"
|
||||
|
||||
@ -3,18 +3,15 @@
|
||||
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
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.fields import CharField
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from authentik.api.authorization import OwnerSuperuserPermissions
|
||||
from authentik.blueprints.api import ManagedSerializer
|
||||
from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
@ -138,8 +135,8 @@ class TokenViewSet(UsedByMixin, ModelViewSet):
|
||||
"managed",
|
||||
]
|
||||
ordering = ["identifier", "expires"]
|
||||
permission_classes = [OwnerSuperuserPermissions]
|
||||
filter_backends = [DjangoFilterBackend, OrderingFilter, SearchFilter]
|
||||
owner_field = "user"
|
||||
rbac_allow_create_without_perm = True
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user if self.request else get_anonymous_user()
|
||||
|
||||
@ -585,7 +585,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
|
||||
"""Set password for user"""
|
||||
user: User = self.get_object()
|
||||
try:
|
||||
user.set_password(request.data.get("password"))
|
||||
user.set_password(request.data.get("password"), request=request)
|
||||
user.save()
|
||||
except (ValidationError, IntegrityError) as exc:
|
||||
LOGGER.debug("Failed to set password", exc=exc)
|
||||
|
||||
@ -44,13 +44,12 @@ class TokenBackend(InbuiltBackend):
|
||||
self, request: HttpRequest, username: str | None, password: str | None, **kwargs: Any
|
||||
) -> User | None:
|
||||
try:
|
||||
|
||||
user = User._default_manager.get_by_natural_key(username)
|
||||
|
||||
except User.DoesNotExist:
|
||||
# Run the default password hasher once to reduce the timing
|
||||
# difference between an existing and a nonexistent user (#20760).
|
||||
User().set_password(password)
|
||||
User().set_password(password, request=request)
|
||||
return None
|
||||
|
||||
tokens = Token.filter_not_expired(
|
||||
|
||||
@ -58,6 +58,7 @@ class PropertyMappingEvaluator(BaseEvaluator):
|
||||
self._context["user"] = user
|
||||
if request:
|
||||
req.http_request = request
|
||||
self._context["http_request"] = request
|
||||
req.context.update(**kwargs)
|
||||
self._context["request"] = req
|
||||
self._context.update(**kwargs)
|
||||
|
||||
@ -17,7 +17,9 @@ from authentik.events.middleware import should_log_model
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.events.utils import model_to_dict
|
||||
|
||||
BANNER_TEXT = f"""### authentik shell ({get_full_version()})
|
||||
|
||||
def get_banner_text(shell_type="shell") -> str:
|
||||
return f"""### authentik {shell_type} ({get_full_version()})
|
||||
### Node {platform.node()} | Arch {platform.machine()} | Python {platform.python_version()} """
|
||||
|
||||
|
||||
@ -114,4 +116,4 @@ class Command(BaseCommand):
|
||||
readline.parse_and_bind("tab: complete")
|
||||
|
||||
# Run interactive shell
|
||||
code.interact(banner=BANNER_TEXT, local=namespace)
|
||||
code.interact(banner=get_banner_text(), local=namespace)
|
||||
|
||||
@ -0,0 +1,45 @@
|
||||
# Generated by Django 5.0.10 on 2025-01-13 18:05
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_core", "0041_applicationentitlement"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddIndex(
|
||||
model_name="authenticatedsession",
|
||||
index=models.Index(fields=["expires"], name="authentik_c_expires_08251d_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="authenticatedsession",
|
||||
index=models.Index(fields=["expiring"], name="authentik_c_expirin_9cd839_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="authenticatedsession",
|
||||
index=models.Index(
|
||||
fields=["expiring", "expires"], name="authentik_c_expirin_195a84_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="authenticatedsession",
|
||||
index=models.Index(fields=["session_key"], name="authentik_c_session_d0f005_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="token",
|
||||
index=models.Index(fields=["expires"], name="authentik_c_expires_a62b4b_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="token",
|
||||
index=models.Index(fields=["expiring"], name="authentik_c_expirin_a1b838_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="token",
|
||||
index=models.Index(
|
||||
fields=["expiring", "expires"], name="authentik_c_expirin_ba04d9_idx"
|
||||
),
|
||||
),
|
||||
]
|
||||
@ -356,13 +356,13 @@ class User(SerializerModel, GuardianUserMixin, AttributesMixin, AbstractUser):
|
||||
"""superuser == staff user"""
|
||||
return self.is_superuser # type: ignore
|
||||
|
||||
def set_password(self, raw_password, signal=True, sender=None):
|
||||
def set_password(self, raw_password, signal=True, sender=None, request=None):
|
||||
if self.pk and signal:
|
||||
from authentik.core.signals import password_changed
|
||||
|
||||
if not sender:
|
||||
sender = self
|
||||
password_changed.send(sender=sender, user=self, password=raw_password)
|
||||
password_changed.send(sender=sender, user=self, password=raw_password, request=request)
|
||||
self.password_change_date = now()
|
||||
return super().set_password(raw_password)
|
||||
|
||||
@ -846,6 +846,11 @@ class ExpiringModel(models.Model):
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
indexes = [
|
||||
models.Index(fields=["expires"]),
|
||||
models.Index(fields=["expiring"]),
|
||||
models.Index(fields=["expiring", "expires"]),
|
||||
]
|
||||
|
||||
def expire_action(self, *args, **kwargs):
|
||||
"""Handler which is called when this object is expired. By
|
||||
@ -901,7 +906,7 @@ class Token(SerializerModel, ManagedModel, ExpiringModel):
|
||||
class Meta:
|
||||
verbose_name = _("Token")
|
||||
verbose_name_plural = _("Tokens")
|
||||
indexes = [
|
||||
indexes = ExpiringModel.Meta.indexes + [
|
||||
models.Index(fields=["identifier"]),
|
||||
models.Index(fields=["key"]),
|
||||
]
|
||||
@ -1001,6 +1006,9 @@ class AuthenticatedSession(ExpiringModel):
|
||||
class Meta:
|
||||
verbose_name = _("Authenticated Session")
|
||||
verbose_name_plural = _("Authenticated Sessions")
|
||||
indexes = ExpiringModel.Meta.indexes + [
|
||||
models.Index(fields=["session_key"]),
|
||||
]
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"Authenticated Session {self.session_key[:10]}"
|
||||
|
||||
@ -28,7 +28,6 @@ from rest_framework.validators import UniqueValidator
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.api.authorization import SecretKeyFilter
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import ModelSerializer, PassiveSerializer
|
||||
from authentik.crypto.apps import MANAGED_KEY
|
||||
@ -36,7 +35,7 @@ from authentik.crypto.builder import CertificateBuilder, PrivateKeyAlg
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.rbac.decorators import permission_required
|
||||
from authentik.rbac.filters import ObjectFilter
|
||||
from authentik.rbac.filters import ObjectFilter, SecretKeyFilter
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
@ -0,0 +1,27 @@
|
||||
# Generated by Django 5.0.10 on 2025-01-13 18:05
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_enterprise", "0003_remove_licenseusage_within_limits_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddIndex(
|
||||
model_name="licenseusage",
|
||||
index=models.Index(fields=["expires"], name="authentik_e_expires_3f2956_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="licenseusage",
|
||||
index=models.Index(fields=["expiring"], name="authentik_e_expirin_11d3d7_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="licenseusage",
|
||||
index=models.Index(
|
||||
fields=["expiring", "expires"], name="authentik_e_expirin_4d558f_idx"
|
||||
),
|
||||
),
|
||||
]
|
||||
@ -93,3 +93,4 @@ class LicenseUsage(ExpiringModel):
|
||||
class Meta:
|
||||
verbose_name = _("License Usage")
|
||||
verbose_name_plural = _("License Usage Records")
|
||||
indexes = ExpiringModel.Meta.indexes
|
||||
|
||||
@ -1,11 +1,8 @@
|
||||
"""RAC Provider API Views"""
|
||||
|
||||
from django_filters.rest_framework.backends import DjangoFilterBackend
|
||||
from rest_framework import mixins
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.viewsets import GenericViewSet
|
||||
|
||||
from authentik.api.authorization import OwnerFilter, OwnerSuperuserPermissions
|
||||
from authentik.core.api.groups import GroupMemberSerializer
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import ModelSerializer
|
||||
@ -34,12 +31,6 @@ class ConnectionTokenSerializer(EnterpriseRequiredMixin, ModelSerializer):
|
||||
]
|
||||
|
||||
|
||||
class ConnectionTokenOwnerFilter(OwnerFilter):
|
||||
"""Owner filter for connection tokens (checks session's user)"""
|
||||
|
||||
owner_key = "session__user"
|
||||
|
||||
|
||||
class ConnectionTokenViewSet(
|
||||
mixins.RetrieveModelMixin,
|
||||
mixins.UpdateModelMixin,
|
||||
@ -55,10 +46,4 @@ class ConnectionTokenViewSet(
|
||||
filterset_fields = ["endpoint", "session__user", "provider"]
|
||||
search_fields = ["endpoint__name", "provider__name"]
|
||||
ordering = ["endpoint__name", "provider__name"]
|
||||
permission_classes = [OwnerSuperuserPermissions]
|
||||
filter_backends = [
|
||||
ConnectionTokenOwnerFilter,
|
||||
DjangoFilterBackend,
|
||||
OrderingFilter,
|
||||
SearchFilter,
|
||||
]
|
||||
owner_field = "session__user"
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
# Generated by Django 5.0.10 on 2025-01-13 18:05
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_core", "0042_authenticatedsession_authentik_c_expires_08251d_idx_and_more"),
|
||||
("authentik_providers_rac", "0005_alter_racpropertymapping_options"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddIndex(
|
||||
model_name="connectiontoken",
|
||||
index=models.Index(fields=["expires"], name="authentik_p_expires_91f148_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="connectiontoken",
|
||||
index=models.Index(fields=["expiring"], name="authentik_p_expirin_59a5a7_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="connectiontoken",
|
||||
index=models.Index(
|
||||
fields=["expiring", "expires"], name="authentik_p_expirin_aed3ca_idx"
|
||||
),
|
||||
),
|
||||
]
|
||||
@ -159,9 +159,9 @@ class ConnectionToken(ExpiringModel):
|
||||
default_settings["port"] = str(port)
|
||||
else:
|
||||
default_settings["hostname"] = self.endpoint.host
|
||||
default_settings["client-name"] = "authentik"
|
||||
# default_settings["enable-drive"] = "true"
|
||||
# default_settings["drive-name"] = "authentik"
|
||||
if self.endpoint.protocol == Protocols.RDP:
|
||||
default_settings["resize-method"] = "display-update"
|
||||
default_settings["client-name"] = f"authentik - {self.session.user}"
|
||||
settings = {}
|
||||
always_merger.merge(settings, default_settings)
|
||||
always_merger.merge(settings, self.endpoint.provider.settings)
|
||||
@ -211,3 +211,4 @@ class ConnectionToken(ExpiringModel):
|
||||
class Meta:
|
||||
verbose_name = _("RAC Connection token")
|
||||
verbose_name_plural = _("RAC Connection tokens")
|
||||
indexes = ExpiringModel.Meta.indexes
|
||||
|
||||
@ -50,9 +50,10 @@ class TestModels(TransactionTestCase):
|
||||
{
|
||||
"hostname": self.endpoint.host.split(":")[0],
|
||||
"port": "1324",
|
||||
"client-name": "authentik",
|
||||
"client-name": f"authentik - {self.user}",
|
||||
"drive-path": path,
|
||||
"create-drive-path": "true",
|
||||
"resize-method": "display-update",
|
||||
},
|
||||
)
|
||||
# Set settings in provider
|
||||
@ -63,10 +64,11 @@ class TestModels(TransactionTestCase):
|
||||
{
|
||||
"hostname": self.endpoint.host.split(":")[0],
|
||||
"port": "1324",
|
||||
"client-name": "authentik",
|
||||
"client-name": f"authentik - {self.user}",
|
||||
"drive-path": path,
|
||||
"create-drive-path": "true",
|
||||
"level": "provider",
|
||||
"resize-method": "display-update",
|
||||
},
|
||||
)
|
||||
# Set settings in endpoint
|
||||
@ -79,10 +81,11 @@ class TestModels(TransactionTestCase):
|
||||
{
|
||||
"hostname": self.endpoint.host.split(":")[0],
|
||||
"port": "1324",
|
||||
"client-name": "authentik",
|
||||
"client-name": f"authentik - {self.user}",
|
||||
"drive-path": path,
|
||||
"create-drive-path": "true",
|
||||
"level": "endpoint",
|
||||
"resize-method": "display-update",
|
||||
},
|
||||
)
|
||||
# Set settings in token
|
||||
@ -95,10 +98,11 @@ class TestModels(TransactionTestCase):
|
||||
{
|
||||
"hostname": self.endpoint.host.split(":")[0],
|
||||
"port": "1324",
|
||||
"client-name": "authentik",
|
||||
"client-name": f"authentik - {self.user}",
|
||||
"drive-path": path,
|
||||
"create-drive-path": "true",
|
||||
"level": "token",
|
||||
"resize-method": "display-update",
|
||||
},
|
||||
)
|
||||
# Set settings in property mapping (provider)
|
||||
@ -114,10 +118,11 @@ class TestModels(TransactionTestCase):
|
||||
{
|
||||
"hostname": self.endpoint.host.split(":")[0],
|
||||
"port": "1324",
|
||||
"client-name": "authentik",
|
||||
"client-name": f"authentik - {self.user}",
|
||||
"drive-path": path,
|
||||
"create-drive-path": "true",
|
||||
"level": "property_mapping_provider",
|
||||
"resize-method": "display-update",
|
||||
},
|
||||
)
|
||||
# Set settings in property mapping (endpoint)
|
||||
@ -135,11 +140,12 @@ class TestModels(TransactionTestCase):
|
||||
{
|
||||
"hostname": self.endpoint.host.split(":")[0],
|
||||
"port": "1324",
|
||||
"client-name": "authentik",
|
||||
"client-name": f"authentik - {self.user}",
|
||||
"drive-path": path,
|
||||
"create-drive-path": "true",
|
||||
"level": "property_mapping_endpoint",
|
||||
"foo": "true",
|
||||
"bar": "6",
|
||||
"resize-method": "display-update",
|
||||
},
|
||||
)
|
||||
|
||||
@ -1,14 +1,11 @@
|
||||
"""AuthenticatorEndpointGDTCStage API Views"""
|
||||
|
||||
from django_filters.rest_framework.backends import DjangoFilterBackend
|
||||
from rest_framework import mixins
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.permissions import IsAdminUser
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.api.authorization import OwnerFilter, OwnerPermissions
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.enterprise.api import EnterpriseRequiredMixin
|
||||
from authentik.enterprise.stages.authenticator_endpoint_gdtc.models import (
|
||||
@ -67,8 +64,7 @@ class EndpointDeviceViewSet(
|
||||
search_fields = ["name"]
|
||||
filterset_fields = ["name"]
|
||||
ordering = ["name"]
|
||||
permission_classes = [OwnerPermissions]
|
||||
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
|
||||
owner_field = "user"
|
||||
|
||||
|
||||
class EndpointAdminDeviceViewSet(ModelViewSet):
|
||||
|
||||
@ -1,17 +1,15 @@
|
||||
"""Notification API Views"""
|
||||
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import OpenApiResponse, extend_schema
|
||||
from rest_framework import mixins
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.fields import ReadOnlyField
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.viewsets import GenericViewSet
|
||||
|
||||
from authentik.api.authorization import OwnerFilter, OwnerPermissions
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import ModelSerializer
|
||||
from authentik.events.api.events import EventSerializer
|
||||
@ -57,8 +55,7 @@ class NotificationViewSet(
|
||||
"seen",
|
||||
"user",
|
||||
]
|
||||
permission_classes = [OwnerPermissions]
|
||||
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
|
||||
owner_field = "user"
|
||||
|
||||
@extend_schema(
|
||||
request=OpenApiTypes.NONE,
|
||||
@ -66,7 +63,7 @@ class NotificationViewSet(
|
||||
204: OpenApiResponse(description="Marked tasks as read successfully."),
|
||||
},
|
||||
)
|
||||
@action(detail=False, methods=["post"])
|
||||
@action(detail=False, methods=["post"], permission_classes=[IsAuthenticated])
|
||||
def mark_all_seen(self, request: Request) -> Response:
|
||||
"""Mark all the user's notifications as seen"""
|
||||
Notification.objects.filter(user=request.user, seen=False).update(seen=True)
|
||||
|
||||
@ -0,0 +1,41 @@
|
||||
# Generated by Django 5.0.10 on 2025-01-13 18:05
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_events", "0007_event_authentik_e_action_9a9dd9_idx_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddIndex(
|
||||
model_name="event",
|
||||
index=models.Index(fields=["expires"], name="authentik_e_expires_8c73a8_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="event",
|
||||
index=models.Index(fields=["expiring"], name="authentik_e_expirin_b5cb5e_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="event",
|
||||
index=models.Index(
|
||||
fields=["expiring", "expires"], name="authentik_e_expirin_e37180_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="systemtask",
|
||||
index=models.Index(fields=["expires"], name="authentik_e_expires_4d3985_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="systemtask",
|
||||
index=models.Index(fields=["expiring"], name="authentik_e_expirin_81d649_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="systemtask",
|
||||
index=models.Index(
|
||||
fields=["expiring", "expires"], name="authentik_e_expirin_eb3598_idx"
|
||||
),
|
||||
),
|
||||
]
|
||||
@ -306,7 +306,7 @@ class Event(SerializerModel, ExpiringModel):
|
||||
class Meta:
|
||||
verbose_name = _("Event")
|
||||
verbose_name_plural = _("Events")
|
||||
indexes = [
|
||||
indexes = ExpiringModel.Meta.indexes + [
|
||||
models.Index(fields=["action"]),
|
||||
models.Index(fields=["user"]),
|
||||
models.Index(fields=["app"]),
|
||||
@ -694,3 +694,4 @@ class SystemTask(SerializerModel, ExpiringModel):
|
||||
permissions = [("run_task", _("Run task"))]
|
||||
verbose_name = _("System Task")
|
||||
verbose_name_plural = _("System Tasks")
|
||||
indexes = ExpiringModel.Meta.indexes
|
||||
|
||||
@ -106,9 +106,9 @@ def on_invitation_used(sender, request: HttpRequest, invitation: Invitation, **_
|
||||
|
||||
|
||||
@receiver(password_changed)
|
||||
def on_password_changed(sender, user: User, password: str, **_):
|
||||
def on_password_changed(sender, user: User, password: str, request: HttpRequest | None, **_):
|
||||
"""Log password change"""
|
||||
Event.new(EventAction.PASSWORD_SET).from_http(None, user=user)
|
||||
Event.new(EventAction.PASSWORD_SET).from_http(request, user=user)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Event)
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
"""Flow Stage API Views"""
|
||||
|
||||
from uuid import uuid4
|
||||
|
||||
from django.urls.base import reverse
|
||||
from drf_spectacular.utils import extend_schema
|
||||
from rest_framework import mixins
|
||||
@ -27,6 +29,11 @@ class StageSerializer(ModelSerializer, MetaNameSerializer):
|
||||
component = SerializerMethodField()
|
||||
flow_set = FlowSetSerializer(many=True, required=False)
|
||||
|
||||
def to_representation(self, instance: Stage):
|
||||
if isinstance(instance, Stage) and instance.is_in_memory:
|
||||
instance.stage_uuid = uuid4()
|
||||
return super().to_representation(instance)
|
||||
|
||||
def get_component(self, obj: Stage) -> str:
|
||||
"""Get object type so that we know how to edit the object"""
|
||||
if obj.__class__ == Stage:
|
||||
|
||||
@ -88,7 +88,8 @@ class Migration(migrations.Migration):
|
||||
model_name="flowstagebinding",
|
||||
name="re_evaluate_policies",
|
||||
field=models.BooleanField(
|
||||
default=False, help_text="Evaluate policies when the Stage is present to the user."
|
||||
default=False,
|
||||
help_text="Evaluate policies when the Stage is presented to the user.",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
|
||||
@ -20,7 +20,7 @@ class Migration(migrations.Migration):
|
||||
model_name="flowstagebinding",
|
||||
name="re_evaluate_policies",
|
||||
field=models.BooleanField(
|
||||
default=True, help_text="Evaluate policies when the Stage is present to the user."
|
||||
default=True, help_text="Evaluate policies when the Stage is presented to the user."
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@ -102,8 +102,12 @@ class Stage(SerializerModel):
|
||||
user settings are available, or a challenge."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def is_in_memory(self):
|
||||
return hasattr(self, "__in_memory_type")
|
||||
|
||||
def __str__(self):
|
||||
if hasattr(self, "__in_memory_type"):
|
||||
if self.is_in_memory:
|
||||
return f"In-memory Stage {getattr(self, '__in_memory_type')}"
|
||||
return f"Stage {self.name}"
|
||||
|
||||
@ -227,7 +231,7 @@ class FlowStageBinding(SerializerModel, PolicyBindingModel):
|
||||
)
|
||||
re_evaluate_policies = models.BooleanField(
|
||||
default=True,
|
||||
help_text=_("Evaluate policies when the Stage is present to the user."),
|
||||
help_text=_("Evaluate policies when the Stage is presented to the user."),
|
||||
)
|
||||
|
||||
invalid_response_action = models.TextField(
|
||||
|
||||
@ -159,9 +159,17 @@ class FlowPlan:
|
||||
stage = final_stage(request=request, executor=temp_exec)
|
||||
return stage.dispatch(request)
|
||||
|
||||
get_qs = request.GET.copy()
|
||||
if request.user.is_authenticated and (
|
||||
# Object-scoped permission or global permission
|
||||
request.user.has_perm("authentik_flows.inspect_flow", flow)
|
||||
or request.user.has_perm("authentik_flows.inspect_flow")
|
||||
):
|
||||
get_qs["inspector"] = "available"
|
||||
|
||||
return redirect_with_qs(
|
||||
"authentik_core:if-flow",
|
||||
request.GET,
|
||||
get_qs,
|
||||
flow_slug=flow.slug,
|
||||
)
|
||||
|
||||
|
||||
@ -7,8 +7,8 @@ from django.http import HttpRequest, HttpResponse
|
||||
from django.test.client import RequestFactory
|
||||
from django.urls import reverse
|
||||
|
||||
from authentik.core.models import User
|
||||
from authentik.core.tests.utils import create_test_flow
|
||||
from authentik.core.models import Group, User
|
||||
from authentik.core.tests.utils import create_test_flow, create_test_user
|
||||
from authentik.flows.markers import ReevaluateMarker, StageMarker
|
||||
from authentik.flows.models import (
|
||||
FlowDeniedAction,
|
||||
@ -255,7 +255,11 @@ class TestFlowExecutor(FlowTestCase):
|
||||
)
|
||||
|
||||
binding = FlowStageBinding.objects.create(
|
||||
target=flow, stage=DummyStage.objects.create(name=generate_id()), order=0
|
||||
target=flow,
|
||||
stage=DummyStage.objects.create(name=generate_id()),
|
||||
order=0,
|
||||
evaluate_on_plan=True,
|
||||
re_evaluate_policies=False,
|
||||
)
|
||||
binding2 = FlowStageBinding.objects.create(
|
||||
target=flow,
|
||||
@ -278,8 +282,8 @@ class TestFlowExecutor(FlowTestCase):
|
||||
self.assertEqual(plan.bindings[0], binding)
|
||||
self.assertEqual(plan.bindings[1], binding2)
|
||||
|
||||
self.assertIsInstance(plan.markers[0], StageMarker)
|
||||
self.assertIsInstance(plan.markers[1], ReevaluateMarker)
|
||||
self.assertEqual(plan.markers[0].__class__, StageMarker)
|
||||
self.assertEqual(plan.markers[1].__class__, ReevaluateMarker)
|
||||
|
||||
# Second request, this passes the first dummy stage
|
||||
response = self.client.post(exec_url)
|
||||
@ -301,7 +305,11 @@ class TestFlowExecutor(FlowTestCase):
|
||||
)
|
||||
|
||||
binding = FlowStageBinding.objects.create(
|
||||
target=flow, stage=DummyStage.objects.create(name=generate_id()), order=0
|
||||
target=flow,
|
||||
stage=DummyStage.objects.create(name=generate_id()),
|
||||
order=0,
|
||||
evaluate_on_plan=True,
|
||||
re_evaluate_policies=False,
|
||||
)
|
||||
binding2 = FlowStageBinding.objects.create(
|
||||
target=flow,
|
||||
@ -310,7 +318,11 @@ class TestFlowExecutor(FlowTestCase):
|
||||
re_evaluate_policies=True,
|
||||
)
|
||||
binding3 = FlowStageBinding.objects.create(
|
||||
target=flow, stage=DummyStage.objects.create(name=generate_id()), order=2
|
||||
target=flow,
|
||||
stage=DummyStage.objects.create(name=generate_id()),
|
||||
order=2,
|
||||
evaluate_on_plan=True,
|
||||
re_evaluate_policies=False,
|
||||
)
|
||||
|
||||
PolicyBinding.objects.create(policy=false_policy, target=binding2, order=0)
|
||||
@ -328,9 +340,9 @@ class TestFlowExecutor(FlowTestCase):
|
||||
self.assertEqual(plan.bindings[1], binding2)
|
||||
self.assertEqual(plan.bindings[2], binding3)
|
||||
|
||||
self.assertIsInstance(plan.markers[0], StageMarker)
|
||||
self.assertIsInstance(plan.markers[1], ReevaluateMarker)
|
||||
self.assertIsInstance(plan.markers[2], StageMarker)
|
||||
self.assertEqual(plan.markers[0].__class__, StageMarker)
|
||||
self.assertEqual(plan.markers[1].__class__, ReevaluateMarker)
|
||||
self.assertEqual(plan.markers[2].__class__, StageMarker)
|
||||
|
||||
# Second request, this passes the first dummy stage
|
||||
response = self.client.post(exec_url)
|
||||
@ -341,8 +353,8 @@ class TestFlowExecutor(FlowTestCase):
|
||||
self.assertEqual(plan.bindings[0], binding2)
|
||||
self.assertEqual(plan.bindings[1], binding3)
|
||||
|
||||
self.assertIsInstance(plan.markers[0], StageMarker)
|
||||
self.assertIsInstance(plan.markers[1], StageMarker)
|
||||
self.assertEqual(plan.markers[0].__class__, ReevaluateMarker)
|
||||
self.assertEqual(plan.markers[1].__class__, StageMarker)
|
||||
|
||||
# third request, this should trigger the re-evaluate
|
||||
# We do this request without the patch, so the policy results in false
|
||||
@ -360,7 +372,11 @@ class TestFlowExecutor(FlowTestCase):
|
||||
)
|
||||
|
||||
binding = FlowStageBinding.objects.create(
|
||||
target=flow, stage=DummyStage.objects.create(name=generate_id()), order=0
|
||||
target=flow,
|
||||
stage=DummyStage.objects.create(name=generate_id()),
|
||||
order=0,
|
||||
evaluate_on_plan=True,
|
||||
re_evaluate_policies=False,
|
||||
)
|
||||
binding2 = FlowStageBinding.objects.create(
|
||||
target=flow,
|
||||
@ -369,7 +385,11 @@ class TestFlowExecutor(FlowTestCase):
|
||||
re_evaluate_policies=True,
|
||||
)
|
||||
binding3 = FlowStageBinding.objects.create(
|
||||
target=flow, stage=DummyStage.objects.create(name=generate_id()), order=2
|
||||
target=flow,
|
||||
stage=DummyStage.objects.create(name=generate_id()),
|
||||
order=2,
|
||||
evaluate_on_plan=True,
|
||||
re_evaluate_policies=False,
|
||||
)
|
||||
|
||||
PolicyBinding.objects.create(policy=true_policy, target=binding2, order=0)
|
||||
@ -387,9 +407,9 @@ class TestFlowExecutor(FlowTestCase):
|
||||
self.assertEqual(plan.bindings[1], binding2)
|
||||
self.assertEqual(plan.bindings[2], binding3)
|
||||
|
||||
self.assertIsInstance(plan.markers[0], StageMarker)
|
||||
self.assertIsInstance(plan.markers[1], ReevaluateMarker)
|
||||
self.assertIsInstance(plan.markers[2], StageMarker)
|
||||
self.assertEqual(plan.markers[0].__class__, StageMarker)
|
||||
self.assertEqual(plan.markers[1].__class__, ReevaluateMarker)
|
||||
self.assertEqual(plan.markers[2].__class__, StageMarker)
|
||||
|
||||
# Second request, this passes the first dummy stage
|
||||
response = self.client.post(exec_url)
|
||||
@ -400,8 +420,8 @@ class TestFlowExecutor(FlowTestCase):
|
||||
self.assertEqual(plan.bindings[0], binding2)
|
||||
self.assertEqual(plan.bindings[1], binding3)
|
||||
|
||||
self.assertIsInstance(plan.markers[0], StageMarker)
|
||||
self.assertIsInstance(plan.markers[1], StageMarker)
|
||||
self.assertEqual(plan.markers[0].__class__, ReevaluateMarker)
|
||||
self.assertEqual(plan.markers[1].__class__, StageMarker)
|
||||
|
||||
# Third request, this passes the first dummy stage
|
||||
response = self.client.post(exec_url)
|
||||
@ -411,7 +431,7 @@ class TestFlowExecutor(FlowTestCase):
|
||||
|
||||
self.assertEqual(plan.bindings[0], binding3)
|
||||
|
||||
self.assertIsInstance(plan.markers[0], StageMarker)
|
||||
self.assertEqual(plan.markers[0].__class__, StageMarker)
|
||||
|
||||
# third request, this should trigger the re-evaluate
|
||||
# We do this request without the patch, so the policy results in false
|
||||
@ -429,7 +449,11 @@ class TestFlowExecutor(FlowTestCase):
|
||||
)
|
||||
|
||||
binding = FlowStageBinding.objects.create(
|
||||
target=flow, stage=DummyStage.objects.create(name=generate_id()), order=0
|
||||
target=flow,
|
||||
stage=DummyStage.objects.create(name=generate_id()),
|
||||
order=0,
|
||||
evaluate_on_plan=True,
|
||||
re_evaluate_policies=False,
|
||||
)
|
||||
binding2 = FlowStageBinding.objects.create(
|
||||
target=flow,
|
||||
@ -444,7 +468,11 @@ class TestFlowExecutor(FlowTestCase):
|
||||
re_evaluate_policies=True,
|
||||
)
|
||||
binding4 = FlowStageBinding.objects.create(
|
||||
target=flow, stage=DummyStage.objects.create(name=generate_id()), order=2
|
||||
target=flow,
|
||||
stage=DummyStage.objects.create(name=generate_id()),
|
||||
order=2,
|
||||
evaluate_on_plan=True,
|
||||
re_evaluate_policies=False,
|
||||
)
|
||||
|
||||
PolicyBinding.objects.create(policy=false_policy, target=binding2, order=0)
|
||||
@ -465,10 +493,10 @@ class TestFlowExecutor(FlowTestCase):
|
||||
self.assertEqual(plan.bindings[2], binding3)
|
||||
self.assertEqual(plan.bindings[3], binding4)
|
||||
|
||||
self.assertIsInstance(plan.markers[0], StageMarker)
|
||||
self.assertIsInstance(plan.markers[1], ReevaluateMarker)
|
||||
self.assertIsInstance(plan.markers[2], ReevaluateMarker)
|
||||
self.assertIsInstance(plan.markers[3], StageMarker)
|
||||
self.assertEqual(plan.markers[0].__class__, StageMarker)
|
||||
self.assertEqual(plan.markers[1].__class__, ReevaluateMarker)
|
||||
self.assertEqual(plan.markers[2].__class__, ReevaluateMarker)
|
||||
self.assertEqual(plan.markers[3].__class__, StageMarker)
|
||||
|
||||
# Second request, this passes the first dummy stage
|
||||
response = self.client.post(exec_url)
|
||||
@ -519,9 +547,9 @@ class TestFlowExecutor(FlowTestCase):
|
||||
)
|
||||
# Stage 0 is a deny stage that is added dynamically
|
||||
# when the reputation policy says so
|
||||
deny_stage = DenyStage.objects.create(name="deny")
|
||||
deny_stage = DenyStage.objects.create(name=generate_id())
|
||||
reputation_policy = ReputationPolicy.objects.create(
|
||||
name="reputation", threshold=-1, check_ip=False
|
||||
name=generate_id(), threshold=-1, check_ip=False
|
||||
)
|
||||
deny_binding = FlowStageBinding.objects.create(
|
||||
target=flow,
|
||||
@ -534,7 +562,7 @@ class TestFlowExecutor(FlowTestCase):
|
||||
|
||||
# Stage 1 is an identification stage
|
||||
ident_stage = IdentificationStage.objects.create(
|
||||
name="ident",
|
||||
name=generate_id(),
|
||||
user_fields=[UserFields.E_MAIL],
|
||||
pretend_user_exists=False,
|
||||
)
|
||||
@ -559,3 +587,64 @@ class TestFlowExecutor(FlowTestCase):
|
||||
)
|
||||
response = self.client.post(exec_url, {"uid_field": "invalid-string"}, follow=True)
|
||||
self.assertStageResponse(response, flow, component="ak-stage-access-denied")
|
||||
|
||||
def test_re_evaluate_group_binding(self):
|
||||
"""Test re-evaluate stage binding that has a policy binding to a group"""
|
||||
flow = create_test_flow()
|
||||
|
||||
user_group_membership = create_test_user()
|
||||
user_direct_binding = create_test_user()
|
||||
user_other = create_test_user()
|
||||
|
||||
group_a = Group.objects.create(name=generate_id())
|
||||
user_group_membership.ak_groups.add(group_a)
|
||||
|
||||
# Stage 0 is an identification stage
|
||||
ident_stage = IdentificationStage.objects.create(
|
||||
name=generate_id(),
|
||||
user_fields=[UserFields.USERNAME],
|
||||
pretend_user_exists=False,
|
||||
)
|
||||
FlowStageBinding.objects.create(
|
||||
target=flow,
|
||||
stage=ident_stage,
|
||||
order=0,
|
||||
)
|
||||
|
||||
# Stage 1 is a dummy stage that is only shown for users in group_a
|
||||
dummy_stage = DummyStage.objects.create(name=generate_id())
|
||||
dummy_binding = FlowStageBinding.objects.create(target=flow, stage=dummy_stage, order=1)
|
||||
PolicyBinding.objects.create(group=group_a, target=dummy_binding, order=0)
|
||||
PolicyBinding.objects.create(user=user_direct_binding, target=dummy_binding, order=0)
|
||||
|
||||
# Stage 2 is a deny stage that (in this case) only user_b will see
|
||||
deny_stage = DenyStage.objects.create(name=generate_id())
|
||||
FlowStageBinding.objects.create(target=flow, stage=deny_stage, order=2)
|
||||
|
||||
exec_url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug})
|
||||
|
||||
with self.subTest(f"Test user access through group: {user_group_membership}"):
|
||||
self.client.logout()
|
||||
# First request, run the planner
|
||||
response = self.client.get(exec_url)
|
||||
self.assertStageResponse(response, flow, component="ak-stage-identification")
|
||||
response = self.client.post(
|
||||
exec_url, {"uid_field": user_group_membership.username}, follow=True
|
||||
)
|
||||
self.assertStageResponse(response, flow, component="ak-stage-dummy")
|
||||
with self.subTest(f"Test user access through user: {user_direct_binding}"):
|
||||
self.client.logout()
|
||||
# First request, run the planner
|
||||
response = self.client.get(exec_url)
|
||||
self.assertStageResponse(response, flow, component="ak-stage-identification")
|
||||
response = self.client.post(
|
||||
exec_url, {"uid_field": user_direct_binding.username}, follow=True
|
||||
)
|
||||
self.assertStageResponse(response, flow, component="ak-stage-dummy")
|
||||
with self.subTest(f"Test user has no access: {user_other}"):
|
||||
self.client.logout()
|
||||
# First request, run the planner
|
||||
response = self.client.get(exec_url)
|
||||
self.assertStageResponse(response, flow, component="ak-stage-identification")
|
||||
response = self.client.post(exec_url, {"uid_field": user_other.username}, follow=True)
|
||||
self.assertStageResponse(response, flow, component="ak-stage-access-denied")
|
||||
|
||||
@ -8,6 +8,7 @@ from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
|
||||
from authentik.flows.models import FlowDesignation, FlowStageBinding, InvalidResponseAction
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.stages.dummy.models import DummyStage
|
||||
from authentik.stages.identification.models import IdentificationStage, UserFields
|
||||
|
||||
@ -26,7 +27,7 @@ class TestFlowInspector(APITestCase):
|
||||
|
||||
# Stage 1 is an identification stage
|
||||
ident_stage = IdentificationStage.objects.create(
|
||||
name="ident",
|
||||
name=generate_id(),
|
||||
user_fields=[UserFields.USERNAME],
|
||||
)
|
||||
FlowStageBinding.objects.create(
|
||||
@ -35,9 +36,8 @@ class TestFlowInspector(APITestCase):
|
||||
order=1,
|
||||
invalid_response_action=InvalidResponseAction.RESTART_WITH_CONTEXT,
|
||||
)
|
||||
FlowStageBinding.objects.create(
|
||||
target=flow, stage=DummyStage.objects.create(name="dummy2"), order=1
|
||||
)
|
||||
dummy_stage = DummyStage.objects.create(name=generate_id())
|
||||
FlowStageBinding.objects.create(target=flow, stage=dummy_stage, order=1)
|
||||
|
||||
res = self.client.get(
|
||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
|
||||
@ -68,9 +68,11 @@ class TestFlowInspector(APITestCase):
|
||||
)
|
||||
content = loads(ins.content)
|
||||
self.assertEqual(content["is_completed"], False)
|
||||
self.assertEqual(content["current_plan"]["current_stage"]["stage_obj"]["name"], "ident")
|
||||
self.assertEqual(
|
||||
content["current_plan"]["next_planned_stage"]["stage_obj"]["name"], "dummy2"
|
||||
content["current_plan"]["current_stage"]["stage_obj"]["name"], ident_stage.name
|
||||
)
|
||||
self.assertEqual(
|
||||
content["current_plan"]["next_planned_stage"]["stage_obj"]["name"], dummy_stage.name
|
||||
)
|
||||
|
||||
self.client.post(
|
||||
@ -84,8 +86,12 @@ class TestFlowInspector(APITestCase):
|
||||
)
|
||||
content = loads(ins.content)
|
||||
self.assertEqual(content["is_completed"], False)
|
||||
self.assertEqual(content["plans"][0]["current_stage"]["stage_obj"]["name"], "ident")
|
||||
self.assertEqual(content["current_plan"]["current_stage"]["stage_obj"]["name"], "dummy2")
|
||||
self.assertEqual(
|
||||
content["plans"][0]["current_stage"]["stage_obj"]["name"], ident_stage.name
|
||||
)
|
||||
self.assertEqual(
|
||||
content["current_plan"]["current_stage"]["stage_obj"]["name"], dummy_stage.name
|
||||
)
|
||||
self.assertEqual(
|
||||
content["current_plan"]["plan_context"]["pending_user"]["username"], self.admin.username
|
||||
)
|
||||
|
||||
@ -29,6 +29,7 @@ from authentik.flows.planner import (
|
||||
cache_key,
|
||||
)
|
||||
from authentik.flows.stage import StageView
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.lib.tests.utils import dummy_get_response
|
||||
from authentik.outposts.apps import MANAGED_OUTPOST
|
||||
from authentik.outposts.models import Outpost
|
||||
@ -153,7 +154,7 @@ class TestFlowPlanner(TestCase):
|
||||
"""Test planner cache"""
|
||||
flow = create_test_flow(FlowDesignation.AUTHENTICATION)
|
||||
FlowStageBinding.objects.create(
|
||||
target=flow, stage=DummyStage.objects.create(name="dummy"), order=0
|
||||
target=flow, stage=DummyStage.objects.create(name=generate_id()), order=0
|
||||
)
|
||||
request = self.request_factory.get(
|
||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
|
||||
@ -172,7 +173,7 @@ class TestFlowPlanner(TestCase):
|
||||
"""Test planner with default_context"""
|
||||
flow = create_test_flow()
|
||||
FlowStageBinding.objects.create(
|
||||
target=flow, stage=DummyStage.objects.create(name="dummy"), order=0
|
||||
target=flow, stage=DummyStage.objects.create(name=generate_id()), order=0
|
||||
)
|
||||
|
||||
user = User.objects.create(username="test-user")
|
||||
@ -191,7 +192,7 @@ class TestFlowPlanner(TestCase):
|
||||
|
||||
FlowStageBinding.objects.create(
|
||||
target=flow,
|
||||
stage=DummyStage.objects.create(name="dummy1"),
|
||||
stage=DummyStage.objects.create(name=generate_id()),
|
||||
order=0,
|
||||
re_evaluate_policies=True,
|
||||
)
|
||||
@ -204,7 +205,7 @@ class TestFlowPlanner(TestCase):
|
||||
planner = FlowPlanner(flow)
|
||||
plan = planner.plan(request)
|
||||
|
||||
self.assertIsInstance(plan.markers[0], ReevaluateMarker)
|
||||
self.assertEqual(plan.markers[0].__class__, ReevaluateMarker)
|
||||
|
||||
def test_planner_reevaluate_actual(self):
|
||||
"""Test planner with re-evaluate"""
|
||||
@ -212,11 +213,14 @@ class TestFlowPlanner(TestCase):
|
||||
false_policy = DummyPolicy.objects.create(result=False, wait_min=1, wait_max=2)
|
||||
|
||||
binding = FlowStageBinding.objects.create(
|
||||
target=flow, stage=DummyStage.objects.create(name="dummy1"), order=0
|
||||
target=flow,
|
||||
stage=DummyStage.objects.create(name=generate_id()),
|
||||
order=0,
|
||||
re_evaluate_policies=False,
|
||||
)
|
||||
binding2 = FlowStageBinding.objects.create(
|
||||
target=flow,
|
||||
stage=DummyStage.objects.create(name="dummy2"),
|
||||
stage=DummyStage.objects.create(name=generate_id()),
|
||||
order=1,
|
||||
re_evaluate_policies=True,
|
||||
)
|
||||
@ -240,6 +244,8 @@ class TestFlowPlanner(TestCase):
|
||||
self.assertEqual(plan.bindings[0], binding)
|
||||
self.assertEqual(plan.bindings[1], binding2)
|
||||
|
||||
self.assertEqual(plan.markers[0].__class__, StageMarker)
|
||||
self.assertEqual(plan.markers[1].__class__, ReevaluateMarker)
|
||||
self.assertIsInstance(plan.markers[0], StageMarker)
|
||||
self.assertIsInstance(plan.markers[1], ReevaluateMarker)
|
||||
|
||||
|
||||
@ -78,7 +78,9 @@ class FlowInspectorView(APIView):
|
||||
self.flow = get_object_or_404(Flow.objects.select_related(), slug=flow_slug)
|
||||
if settings.DEBUG:
|
||||
return
|
||||
if request.user.has_perm("authentik_flow.inspect_flow", self.flow):
|
||||
if request.user.has_perm(
|
||||
"authentik_flows.inspect_flow", self.flow
|
||||
) or request.user.has_perm("authentik_flows.inspect_flow"):
|
||||
return
|
||||
raise Http404
|
||||
|
||||
@ -94,6 +96,9 @@ class FlowInspectorView(APIView):
|
||||
"""Get current flow state and record it"""
|
||||
plans = []
|
||||
for plan in request.session.get(SESSION_KEY_HISTORY, []):
|
||||
plan: FlowPlan
|
||||
if plan.flow_pk != self.flow.pk.hex:
|
||||
continue
|
||||
plan_serializer = FlowInspectorPlanSerializer(
|
||||
instance=plan, context={"request": request}
|
||||
)
|
||||
|
||||
@ -9,20 +9,25 @@ from typing import Any
|
||||
|
||||
from cachetools import TLRUCache, cached
|
||||
from django.core.exceptions import FieldError
|
||||
from django.http import HttpRequest
|
||||
from django.utils.text import slugify
|
||||
from django.utils.timezone import now
|
||||
from guardian.shortcuts import get_anonymous_user
|
||||
from rest_framework.serializers import ValidationError
|
||||
from sentry_sdk import start_span
|
||||
from sentry_sdk.tracing import Span
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.core.models import User
|
||||
from authentik.core.models import AuthenticatedSession, User
|
||||
from authentik.events.models import Event
|
||||
from authentik.lib.expression.exceptions import ControlFlowException
|
||||
from authentik.lib.utils.http import get_http_session
|
||||
from authentik.lib.utils.time import timedelta_from_string
|
||||
from authentik.policies.models import Policy, PolicyBinding
|
||||
from authentik.policies.process import PolicyProcess
|
||||
from authentik.policies.types import PolicyRequest, PolicyResult
|
||||
from authentik.providers.oauth2.id_token import IDToken
|
||||
from authentik.providers.oauth2.models import AccessToken, OAuth2Provider
|
||||
from authentik.stages.authenticator import devices_for_user
|
||||
|
||||
LOGGER = get_logger()
|
||||
@ -56,6 +61,7 @@ class BaseEvaluator:
|
||||
"ak_logger": get_logger(self._filename).bind(),
|
||||
"ak_user_by": BaseEvaluator.expr_user_by,
|
||||
"ak_user_has_authenticator": BaseEvaluator.expr_func_user_has_authenticator,
|
||||
"ak_create_jwt": self.expr_create_jwt,
|
||||
"ip_address": ip_address,
|
||||
"ip_network": ip_network,
|
||||
"list_flatten": BaseEvaluator.expr_flatten,
|
||||
@ -182,6 +188,36 @@ class BaseEvaluator:
|
||||
proc = PolicyProcess(PolicyBinding(policy=policy), request=req, connection=None)
|
||||
return proc.profiling_wrapper()
|
||||
|
||||
def expr_create_jwt(
|
||||
self,
|
||||
user: User,
|
||||
provider: OAuth2Provider | str,
|
||||
scopes: list[str],
|
||||
validity: str = "seconds=60",
|
||||
) -> str | None:
|
||||
"""Issue a JWT for a given provider"""
|
||||
request: HttpRequest = self._context.get("http_request")
|
||||
if not request:
|
||||
return None
|
||||
if not isinstance(provider, OAuth2Provider):
|
||||
provider = OAuth2Provider.objects.get(name=provider)
|
||||
session = None
|
||||
if hasattr(request, "session") and request.session.session_key:
|
||||
session = AuthenticatedSession.objects.filter(
|
||||
session_key=request.session.session_key
|
||||
).first()
|
||||
access_token = AccessToken(
|
||||
provider=provider,
|
||||
user=user,
|
||||
expires=now() + timedelta_from_string(validity),
|
||||
scope=scopes,
|
||||
auth_time=now(),
|
||||
session=session,
|
||||
)
|
||||
access_token.id_token = IDToken.new(provider, access_token, request)
|
||||
access_token.save()
|
||||
return access_token.token
|
||||
|
||||
def wrap_expression(self, expression: str) -> str:
|
||||
"""Wrap expression in a function, call it, and save the result as `result`"""
|
||||
handler_signature = ",".join(sanitize_arg(x) for x in self._context.keys())
|
||||
|
||||
@ -1,11 +1,15 @@
|
||||
"""Test Evaluator base functions"""
|
||||
|
||||
from django.test import TestCase
|
||||
from django.test import RequestFactory, TestCase
|
||||
from django.urls import reverse
|
||||
from jwt import decode
|
||||
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
from authentik.blueprints.tests import apply_blueprint
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_flow, create_test_user
|
||||
from authentik.events.models import Event
|
||||
from authentik.lib.expression.evaluator import BaseEvaluator
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.providers.oauth2.models import OAuth2Provider, ScopeMapping
|
||||
|
||||
|
||||
class TestEvaluator(TestCase):
|
||||
@ -41,3 +45,35 @@ class TestEvaluator(TestCase):
|
||||
event = Event.objects.filter(action="custom_foo").first()
|
||||
self.assertIsNotNone(event)
|
||||
self.assertEqual(event.context, {"bar": "baz", "foo": "bar"})
|
||||
|
||||
@apply_blueprint("system/providers-oauth2.yaml")
|
||||
def test_expr_create_jwt(self):
|
||||
"""Test expr_create_jwt"""
|
||||
rf = RequestFactory()
|
||||
user = create_test_user()
|
||||
provider = OAuth2Provider.objects.create(
|
||||
name=generate_id(),
|
||||
authorization_flow=create_test_flow(),
|
||||
)
|
||||
provider.property_mappings.set(
|
||||
ScopeMapping.objects.filter(
|
||||
managed__in=[
|
||||
"goauthentik.io/providers/oauth2/scope-openid",
|
||||
"goauthentik.io/providers/oauth2/scope-email",
|
||||
"goauthentik.io/providers/oauth2/scope-profile",
|
||||
]
|
||||
)
|
||||
)
|
||||
evaluator = BaseEvaluator(generate_id())
|
||||
evaluator._context = {
|
||||
"http_request": rf.get(reverse("authentik_core:root-redirect")),
|
||||
"user": user,
|
||||
"provider": provider.name,
|
||||
}
|
||||
jwt = evaluator.evaluate(
|
||||
"return ak_create_jwt(user, provider, ['openid', 'email', 'profile'])"
|
||||
)
|
||||
decoded = decode(
|
||||
jwt, provider.client_secret, algorithms=["HS256"], audience=provider.client_id
|
||||
)
|
||||
self.assertEqual(decoded["preferred_username"], user.username)
|
||||
|
||||
@ -207,7 +207,7 @@ class KubernetesObjectReconciler(Generic[T]):
|
||||
"app.kubernetes.io/instance": slugify(self.controller.outpost.name),
|
||||
"app.kubernetes.io/managed-by": "goauthentik.io",
|
||||
"app.kubernetes.io/name": f"authentik-{self.controller.outpost.type.lower()}",
|
||||
"app.kubernetes.io/version": get_version(),
|
||||
"app.kubernetes.io/version": get_version().replace("+", "-"),
|
||||
"goauthentik.io/outpost-name": slugify(self.controller.outpost.name),
|
||||
"goauthentik.io/outpost-type": str(self.controller.outpost.type),
|
||||
"goauthentik.io/outpost-uuid": self.controller.outpost.uuid.hex,
|
||||
|
||||
@ -94,7 +94,7 @@ class DeploymentReconciler(KubernetesObjectReconciler[V1Deployment]):
|
||||
meta = self.get_object_meta(name=self.name)
|
||||
image_name = self.controller.get_container_image()
|
||||
image_pull_secrets = self.outpost.config.kubernetes_image_pull_secrets
|
||||
version = get_full_version()
|
||||
version = get_full_version().replace("+", "-")
|
||||
return V1Deployment(
|
||||
metadata=meta,
|
||||
spec=V1DeploymentSpec(
|
||||
|
||||
@ -13,7 +13,7 @@ if TYPE_CHECKING:
|
||||
from authentik.outposts.controllers.kubernetes import KubernetesController
|
||||
|
||||
|
||||
@dataclass
|
||||
@dataclass(slots=True)
|
||||
class PrometheusServiceMonitorSpecEndpoint:
|
||||
"""Prometheus ServiceMonitor endpoint spec"""
|
||||
|
||||
@ -21,14 +21,14 @@ class PrometheusServiceMonitorSpecEndpoint:
|
||||
path: str = field(default="/metrics")
|
||||
|
||||
|
||||
@dataclass
|
||||
@dataclass(slots=True)
|
||||
class PrometheusServiceMonitorSpecSelector:
|
||||
"""Prometheus ServiceMonitor selector spec"""
|
||||
|
||||
matchLabels: dict
|
||||
|
||||
|
||||
@dataclass
|
||||
@dataclass(slots=True)
|
||||
class PrometheusServiceMonitorSpec:
|
||||
"""Prometheus ServiceMonitor spec"""
|
||||
|
||||
@ -37,7 +37,7 @@ class PrometheusServiceMonitorSpec:
|
||||
selector: PrometheusServiceMonitorSpecSelector
|
||||
|
||||
|
||||
@dataclass
|
||||
@dataclass(slots=True)
|
||||
class PrometheusServiceMonitorMetadata:
|
||||
"""Prometheus ServiceMonitor metadata"""
|
||||
|
||||
@ -46,7 +46,7 @@ class PrometheusServiceMonitorMetadata:
|
||||
labels: dict = field(default_factory=dict)
|
||||
|
||||
|
||||
@dataclass
|
||||
@dataclass(slots=True)
|
||||
class PrometheusServiceMonitor:
|
||||
"""Prometheus ServiceMonitor"""
|
||||
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
# Generated by Django 5.0.10 on 2025-01-13 18:05
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
(
|
||||
"authentik_policies_reputation",
|
||||
"0007_reputation_authentik_p_identif_9434d7_idx_and_more",
|
||||
),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddIndex(
|
||||
model_name="reputation",
|
||||
index=models.Index(fields=["expires"], name="authentik_p_expires_da493f_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="reputation",
|
||||
index=models.Index(fields=["expiring"], name="authentik_p_expirin_2ab34f_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="reputation",
|
||||
index=models.Index(
|
||||
fields=["expiring", "expires"], name="authentik_p_expirin_2a8ec7_idx"
|
||||
),
|
||||
),
|
||||
]
|
||||
@ -96,7 +96,7 @@ class Reputation(ExpiringModel, SerializerModel):
|
||||
verbose_name = _("Reputation Score")
|
||||
verbose_name_plural = _("Reputation Scores")
|
||||
unique_together = ("identifier", "ip")
|
||||
indexes = [
|
||||
indexes = ExpiringModel.Meta.indexes + [
|
||||
models.Index(fields=["identifier"]),
|
||||
models.Index(fields=["ip"]),
|
||||
models.Index(fields=["ip", "identifier"]),
|
||||
|
||||
@ -0,0 +1,72 @@
|
||||
# Generated by Django 5.0.10 on 2025-01-13 18:05
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_core", "0042_authenticatedsession_authentik_c_expires_08251d_idx_and_more"),
|
||||
("authentik_providers_oauth2", "0026_alter_accesstoken_session_and_more"),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddIndex(
|
||||
model_name="accesstoken",
|
||||
index=models.Index(fields=["expires"], name="authentik_p_expires_9f24a5_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="accesstoken",
|
||||
index=models.Index(fields=["expiring"], name="authentik_p_expirin_2d9205_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="accesstoken",
|
||||
index=models.Index(
|
||||
fields=["expiring", "expires"], name="authentik_p_expirin_c74005_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="authorizationcode",
|
||||
index=models.Index(fields=["expires"], name="authentik_p_expires_f594b2_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="authorizationcode",
|
||||
index=models.Index(fields=["expiring"], name="authentik_p_expirin_6a5e2c_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="authorizationcode",
|
||||
index=models.Index(
|
||||
fields=["expiring", "expires"], name="authentik_p_expirin_c0f353_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="devicetoken",
|
||||
index=models.Index(fields=["expires"], name="authentik_p_expires_961437_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="devicetoken",
|
||||
index=models.Index(fields=["expiring"], name="authentik_p_expirin_4fd278_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="devicetoken",
|
||||
index=models.Index(
|
||||
fields=["expiring", "expires"], name="authentik_p_expirin_cd6b1c_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="refreshtoken",
|
||||
index=models.Index(fields=["expires"], name="authentik_p_expires_c479a7_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="refreshtoken",
|
||||
index=models.Index(fields=["expiring"], name="authentik_p_expirin_d4d17f_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="refreshtoken",
|
||||
index=models.Index(
|
||||
fields=["expiring", "expires"], name="authentik_p_expirin_acb4a5_idx"
|
||||
),
|
||||
),
|
||||
]
|
||||
@ -425,6 +425,7 @@ class AuthorizationCode(SerializerModel, ExpiringModel, BaseGrantModel):
|
||||
class Meta:
|
||||
verbose_name = _("Authorization Code")
|
||||
verbose_name_plural = _("Authorization Codes")
|
||||
indexes = ExpiringModel.Meta.indexes
|
||||
|
||||
def __str__(self):
|
||||
return f"Authorization code for {self.provider_id} for user {self.user_id}"
|
||||
@ -453,7 +454,7 @@ class AccessToken(SerializerModel, ExpiringModel, BaseGrantModel):
|
||||
_id_token = models.TextField()
|
||||
|
||||
class Meta:
|
||||
indexes = [
|
||||
indexes = ExpiringModel.Meta.indexes + [
|
||||
HashIndex(fields=["token"]),
|
||||
]
|
||||
verbose_name = _("OAuth2 Access Token")
|
||||
@ -504,7 +505,7 @@ class RefreshToken(SerializerModel, ExpiringModel, BaseGrantModel):
|
||||
)
|
||||
|
||||
class Meta:
|
||||
indexes = [
|
||||
indexes = ExpiringModel.Meta.indexes + [
|
||||
HashIndex(fields=["token"]),
|
||||
]
|
||||
verbose_name = _("OAuth2 Refresh Token")
|
||||
@ -556,6 +557,7 @@ class DeviceToken(ExpiringModel):
|
||||
class Meta:
|
||||
verbose_name = _("Device Token")
|
||||
verbose_name_plural = _("Device Tokens")
|
||||
indexes = ExpiringModel.Meta.indexes
|
||||
|
||||
def __str__(self):
|
||||
return f"Device Token for {self.provider_id}"
|
||||
|
||||
@ -49,7 +49,9 @@ class TesOAuth2DeviceInit(OAuthTestCase):
|
||||
kwargs={
|
||||
"flow_slug": self.device_flow.slug,
|
||||
},
|
||||
),
|
||||
)
|
||||
+ "?"
|
||||
+ urlencode({"inspector": "available"}),
|
||||
)
|
||||
|
||||
def test_device_init_post(self):
|
||||
@ -63,7 +65,9 @@ class TesOAuth2DeviceInit(OAuthTestCase):
|
||||
kwargs={
|
||||
"flow_slug": self.device_flow.slug,
|
||||
},
|
||||
),
|
||||
)
|
||||
+ "?"
|
||||
+ urlencode({"inspector": "available"}),
|
||||
)
|
||||
res = self.api_client.get(
|
||||
reverse(
|
||||
@ -118,7 +122,9 @@ class TesOAuth2DeviceInit(OAuthTestCase):
|
||||
kwargs={
|
||||
"flow_slug": provider.authorization_flow.slug,
|
||||
},
|
||||
),
|
||||
)
|
||||
+ "?"
|
||||
+ urlencode({"inspector": "available"}),
|
||||
},
|
||||
)
|
||||
|
||||
@ -150,7 +156,7 @@ class TesOAuth2DeviceInit(OAuthTestCase):
|
||||
},
|
||||
)
|
||||
+ "?"
|
||||
+ urlencode({QS_KEY_CODE: token.user_code}),
|
||||
+ urlencode({QS_KEY_CODE: token.user_code, "inspector": "available"}),
|
||||
)
|
||||
|
||||
def test_device_init_denied(self):
|
||||
|
||||
@ -15,7 +15,7 @@ if TYPE_CHECKING:
|
||||
from authentik.outposts.controllers.kubernetes import KubernetesController
|
||||
|
||||
|
||||
@dataclass
|
||||
@dataclass(slots=True)
|
||||
class TraefikMiddlewareSpecForwardAuth:
|
||||
"""traefik middleware forwardAuth spec"""
|
||||
|
||||
@ -28,14 +28,14 @@ class TraefikMiddlewareSpecForwardAuth:
|
||||
trustForwardHeader: bool = field(default=True)
|
||||
|
||||
|
||||
@dataclass
|
||||
@dataclass(slots=True)
|
||||
class TraefikMiddlewareSpec:
|
||||
"""Traefik middleware spec"""
|
||||
|
||||
forwardAuth: TraefikMiddlewareSpecForwardAuth
|
||||
|
||||
|
||||
@dataclass
|
||||
@dataclass(slots=True)
|
||||
class TraefikMiddlewareMetadata:
|
||||
"""Traefik Middleware metadata"""
|
||||
|
||||
@ -44,7 +44,7 @@ class TraefikMiddlewareMetadata:
|
||||
labels: dict = field(default_factory=dict)
|
||||
|
||||
|
||||
@dataclass
|
||||
@dataclass(slots=True)
|
||||
class TraefikMiddleware:
|
||||
"""Traefik Middleware"""
|
||||
|
||||
|
||||
@ -16,6 +16,7 @@ from rest_framework.decorators import action
|
||||
from rest_framework.fields import CharField, FileField, SerializerMethodField
|
||||
from rest_framework.parsers import MultiPartParser
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.renderers import BaseRenderer, JSONRenderer
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.serializers import PrimaryKeyRelatedField, ValidationError
|
||||
@ -38,6 +39,16 @@ from authentik.sources.saml.processors.constants import SAML_BINDING_POST, SAML_
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
class RawXMLDataRenderer(BaseRenderer):
|
||||
"""Renderer to allow application/xml as value for 'Accept' in the metadata endpoint."""
|
||||
|
||||
media_type = "application/xml"
|
||||
format = "xml"
|
||||
|
||||
def render(self, data, accepted_media_type=None, renderer_context=None):
|
||||
return data
|
||||
|
||||
|
||||
class SAMLProviderSerializer(ProviderSerializer):
|
||||
"""SAMLProvider Serializer"""
|
||||
|
||||
@ -54,9 +65,23 @@ class SAMLProviderSerializer(ProviderSerializer):
|
||||
if "request" not in self._context:
|
||||
return ""
|
||||
request: HttpRequest = self._context["request"]._request
|
||||
return request.build_absolute_uri(
|
||||
reverse("authentik_api:samlprovider-metadata", kwargs={"pk": instance.pk}) + "?download"
|
||||
)
|
||||
try:
|
||||
return request.build_absolute_uri(
|
||||
reverse(
|
||||
"authentik_providers_saml:metadata-download",
|
||||
kwargs={"application_slug": instance.application.slug},
|
||||
)
|
||||
)
|
||||
except Provider.application.RelatedObjectDoesNotExist:
|
||||
return request.build_absolute_uri(
|
||||
reverse(
|
||||
"authentik_api:samlprovider-metadata",
|
||||
kwargs={
|
||||
"pk": instance.pk,
|
||||
},
|
||||
)
|
||||
+ "?download"
|
||||
)
|
||||
|
||||
def get_url_sso_post(self, instance: SAMLProvider) -> str:
|
||||
"""Get SSO Post URL"""
|
||||
@ -224,9 +249,21 @@ class SAMLProviderViewSet(UsedByMixin, ModelViewSet):
|
||||
],
|
||||
description="Optionally force the metadata to only include one binding.",
|
||||
),
|
||||
# Explicitly excluded, because otherwise spectacular automatically
|
||||
# add it when using multiple renderer_classes
|
||||
OpenApiParameter(
|
||||
name="format",
|
||||
exclude=True,
|
||||
required=False,
|
||||
),
|
||||
],
|
||||
)
|
||||
@action(methods=["GET"], detail=True, permission_classes=[AllowAny])
|
||||
@action(
|
||||
methods=["GET"],
|
||||
detail=True,
|
||||
permission_classes=[AllowAny],
|
||||
renderer_classes=[JSONRenderer, RawXMLDataRenderer],
|
||||
)
|
||||
def metadata(self, request: Request, pk: int) -> Response:
|
||||
"""Return metadata as XML string"""
|
||||
# We don't use self.get_object() on purpose as this view is un-authenticated
|
||||
@ -244,9 +281,9 @@ class SAMLProviderViewSet(UsedByMixin, ModelViewSet):
|
||||
f'attachment; filename="{provider.name}_authentik_meta.xml"'
|
||||
)
|
||||
return response
|
||||
return Response({"metadata": metadata})
|
||||
return Response({"metadata": metadata}, content_type="application/json")
|
||||
except Provider.application.RelatedObjectDoesNotExist:
|
||||
return Response({"metadata": ""})
|
||||
return Response({"metadata": ""}, content_type="application/json")
|
||||
|
||||
@permission_required(
|
||||
None,
|
||||
|
||||
@ -104,6 +104,22 @@ class TestSAMLProviderAPI(APITestCase):
|
||||
)
|
||||
self.assertEqual(200, response.status_code)
|
||||
self.assertIn("Content-Disposition", response)
|
||||
# Test download with Accept: application/xml
|
||||
response = self.client.get(
|
||||
reverse("authentik_api:samlprovider-metadata", kwargs={"pk": provider.pk})
|
||||
+ "?download",
|
||||
HTTP_ACCEPT="application/xml",
|
||||
)
|
||||
self.assertEqual(200, response.status_code)
|
||||
self.assertIn("Content-Disposition", response)
|
||||
|
||||
response = self.client.get(
|
||||
reverse("authentik_api:samlprovider-metadata", kwargs={"pk": provider.pk})
|
||||
+ "?download",
|
||||
HTTP_ACCEPT="application/xml;charset=UTF-8",
|
||||
)
|
||||
self.assertEqual(200, response.status_code)
|
||||
self.assertIn("Content-Disposition", response)
|
||||
|
||||
def test_metadata_invalid(self):
|
||||
"""Test metadata export (invalid)"""
|
||||
@ -121,6 +137,11 @@ class TestSAMLProviderAPI(APITestCase):
|
||||
reverse("authentik_api:samlprovider-metadata", kwargs={"pk": "abc"}),
|
||||
)
|
||||
self.assertEqual(404, response.status_code)
|
||||
response = self.client.get(
|
||||
reverse("authentik_api:samlprovider-metadata", kwargs={"pk": provider.pk}),
|
||||
HTTP_ACCEPT="application/invalid-mime-type",
|
||||
)
|
||||
self.assertEqual(406, response.status_code)
|
||||
|
||||
def test_import_success(self):
|
||||
"""Test metadata import (success case)"""
|
||||
|
||||
@ -1,10 +1,15 @@
|
||||
"""RBAC API Filter"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.db.models import QuerySet
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework.authentication import get_authorization_header
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework_guardian.filters import ObjectPermissionsFilter
|
||||
|
||||
from authentik.api.authentication import validate_auth
|
||||
from authentik.core.models import UserTypes
|
||||
|
||||
|
||||
@ -12,7 +17,7 @@ class ObjectFilter(ObjectPermissionsFilter):
|
||||
"""Object permission filter that grants global permission higher priority than
|
||||
per-object permissions"""
|
||||
|
||||
def filter_queryset(self, request: Request, queryset: QuerySet, view) -> QuerySet:
|
||||
def filter_queryset(self, request: Request, queryset: QuerySet, view: APIView) -> QuerySet:
|
||||
permission = self.perm_format % {
|
||||
"app_label": queryset.model._meta.app_label,
|
||||
"model_name": queryset.model._meta.model_name,
|
||||
@ -21,6 +26,9 @@ class ObjectFilter(ObjectPermissionsFilter):
|
||||
# per-object permissions
|
||||
if request.user.has_perm(permission):
|
||||
return queryset
|
||||
# User does not have permissions, but we have an owner field defined, so filter by that
|
||||
if owner_field := getattr(view, "owner_field", None):
|
||||
return queryset.filter(**{owner_field: request.user})
|
||||
queryset = super().filter_queryset(request, queryset, view)
|
||||
# Outposts (which are the only objects using internal service accounts)
|
||||
# except requests to return an empty list when they have no objects
|
||||
@ -32,3 +40,17 @@ class ObjectFilter(ObjectPermissionsFilter):
|
||||
# and also no object permissions assigned (directly or via role)
|
||||
raise PermissionDenied()
|
||||
return queryset
|
||||
|
||||
|
||||
class SecretKeyFilter(DjangoFilterBackend):
|
||||
"""Allow access to all objects when authenticated with secret key as token.
|
||||
|
||||
Replaces both DjangoFilterBackend and ObjectFilter"""
|
||||
|
||||
def filter_queryset(self, request: Request, queryset: QuerySet, view) -> QuerySet:
|
||||
auth_header = get_authorization_header(request)
|
||||
token = validate_auth(auth_header)
|
||||
if token and token == settings.SECRET_KEY:
|
||||
return queryset
|
||||
queryset = ObjectFilter().filter_queryset(request, queryset, view)
|
||||
return super().filter_queryset(request, queryset, view)
|
||||
|
||||
@ -15,6 +15,17 @@ class ObjectPermissions(DjangoObjectPermissions):
|
||||
lookup = getattr(view, "lookup_url_kwarg", None) or getattr(view, "lookup_field", None)
|
||||
if lookup and lookup in view.kwargs:
|
||||
return True
|
||||
# Legacy behaviour:
|
||||
# Allow creation of objects even without explicit permission
|
||||
queryset = self._queryset(view)
|
||||
required_perms = self.get_required_permissions(request.method, queryset.model)
|
||||
if (
|
||||
len(required_perms) == 1
|
||||
and f"{queryset.model._meta.app_label}.add_{queryset.model._meta.model_name}"
|
||||
in required_perms
|
||||
and getattr(view, "rbac_allow_create_without_perm", False)
|
||||
):
|
||||
return True
|
||||
return super().has_permission(request, view)
|
||||
|
||||
def has_object_permission(self, request: Request, view, obj: Model) -> bool:
|
||||
@ -24,6 +35,10 @@ class ObjectPermissions(DjangoObjectPermissions):
|
||||
# Rank global permissions higher than per-object permissions
|
||||
if request.user.has_perms(perms):
|
||||
return True
|
||||
# Allow access for owners if configured
|
||||
if owner_field := getattr(view, "owner_field", None):
|
||||
if getattr(obj, owner_field) == request.user:
|
||||
return True
|
||||
return super().has_object_permission(request, view, obj)
|
||||
|
||||
|
||||
|
||||
@ -18,6 +18,7 @@ from celery.signals import (
|
||||
task_prerun,
|
||||
worker_ready,
|
||||
)
|
||||
from celery.worker.control import inspect_command
|
||||
from django.conf import settings
|
||||
from django.db import ProgrammingError
|
||||
from django_tenants.utils import get_public_schema_name
|
||||
@ -25,6 +26,7 @@ from structlog.contextvars import STRUCTLOG_KEY_PREFIX
|
||||
from structlog.stdlib import get_logger
|
||||
from tenant_schemas_celery.app import CeleryApp as TenantAwareCeleryApp
|
||||
|
||||
from authentik import get_full_version
|
||||
from authentik.lib.sentry import before_send
|
||||
from authentik.lib.utils.errors import exception_to_string
|
||||
|
||||
@ -159,6 +161,12 @@ class LivenessProbe(bootsteps.StartStopStep):
|
||||
HEARTBEAT_FILE.touch()
|
||||
|
||||
|
||||
@inspect_command(default_timeout=0.2)
|
||||
def ping(state, **kwargs):
|
||||
"""Ping worker(s)."""
|
||||
return {"ok": "pong", "version": get_full_version()}
|
||||
|
||||
|
||||
CELERY_APP.config_from_object(settings.CELERY)
|
||||
|
||||
# Load task modules from all registered Django app configs.
|
||||
|
||||
@ -31,6 +31,8 @@ class PytestTestRunner(DiscoverRunner): # pragma: no cover
|
||||
|
||||
if kwargs.get("randomly_seed", None):
|
||||
self.args.append(f"--randomly-seed={kwargs['randomly_seed']}")
|
||||
if kwargs.get("no_capture"):
|
||||
self.args.append("-s")
|
||||
|
||||
settings.TEST = True
|
||||
settings.CELERY["task_always_eager"] = True
|
||||
@ -56,6 +58,10 @@ class PytestTestRunner(DiscoverRunner): # pragma: no cover
|
||||
def add_arguments(cls, parser: ArgumentParser):
|
||||
"""Add more pytest-specific arguments"""
|
||||
DiscoverRunner.add_arguments(parser)
|
||||
parser.add_argument(
|
||||
"--no-capture",
|
||||
action="store_true",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--randomly-seed",
|
||||
type=int,
|
||||
|
||||
@ -1,10 +1,7 @@
|
||||
"""Kerberos Source Serializer"""
|
||||
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from authentik.api.authorization import OwnerFilter, OwnerSuperuserPermissions
|
||||
from authentik.core.api.sources import (
|
||||
GroupSourceConnectionSerializer,
|
||||
GroupSourceConnectionViewSet,
|
||||
@ -32,9 +29,8 @@ class UserKerberosSourceConnectionViewSet(UsedByMixin, ModelViewSet):
|
||||
serializer_class = UserKerberosSourceConnectionSerializer
|
||||
filterset_fields = ["source__slug"]
|
||||
search_fields = ["source__slug"]
|
||||
permission_classes = [OwnerSuperuserPermissions]
|
||||
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
|
||||
ordering = ["source__slug"]
|
||||
owner_field = "user"
|
||||
|
||||
|
||||
class GroupKerberosSourceConnectionSerializer(GroupSourceConnectionSerializer):
|
||||
|
||||
@ -28,14 +28,14 @@ class KerberosBackend(InbuiltBackend):
|
||||
if "@" in username:
|
||||
username, realm = username.rsplit("@", 1)
|
||||
|
||||
user, source = self.auth_user(username, realm, **kwargs)
|
||||
user, source = self.auth_user(request, username, realm, **kwargs)
|
||||
if user:
|
||||
self.set_method("kerberos", request, source=source)
|
||||
return user
|
||||
return None
|
||||
|
||||
def auth_user(
|
||||
self, username: str, realm: str | None, password: str, **filters
|
||||
self, request: HttpRequest, username: str, realm: str | None, password: str, **filters
|
||||
) -> tuple[User | None, KerberosSource | None]:
|
||||
sources = KerberosSource.objects.filter(enabled=True)
|
||||
user = User.objects.filter(
|
||||
@ -76,7 +76,7 @@ class KerberosBackend(InbuiltBackend):
|
||||
user=user_source_connection.user,
|
||||
)
|
||||
user_source_connection.user.set_password(
|
||||
password, sender=user_source_connection.source
|
||||
password, sender=user_source_connection.source, request=request
|
||||
)
|
||||
user_source_connection.user.save()
|
||||
return user_source_connection.user, user_source_connection.source
|
||||
|
||||
@ -20,13 +20,15 @@ class LDAPBackend(InbuiltBackend):
|
||||
return None
|
||||
for source in LDAPSource.objects.filter(enabled=True):
|
||||
LOGGER.debug("LDAP Auth attempt", source=source)
|
||||
user = self.auth_user(source, **kwargs)
|
||||
user = self.auth_user(request, source, **kwargs)
|
||||
if user:
|
||||
self.set_method("ldap", request, source=source)
|
||||
return user
|
||||
return None
|
||||
|
||||
def auth_user(self, source: LDAPSource, password: str, **filters: str) -> User | None:
|
||||
def auth_user(
|
||||
self, request: HttpRequest, source: LDAPSource, password: str, **filters: str
|
||||
) -> User | None:
|
||||
"""Try to bind as either user_dn or mail with password.
|
||||
Returns True on success, otherwise False"""
|
||||
users = User.objects.filter(**filters)
|
||||
@ -43,7 +45,7 @@ class LDAPBackend(InbuiltBackend):
|
||||
if source.password_login_update_internal_password:
|
||||
# Password given successfully binds to LDAP, so we save it in our Database
|
||||
LOGGER.debug("Updating user's password in DB", user=user)
|
||||
user.set_password(password, sender=source)
|
||||
user.set_password(password, sender=source, request=request)
|
||||
user.save()
|
||||
return user
|
||||
# Password doesn't match
|
||||
|
||||
@ -88,6 +88,55 @@ class TestSCIMUsers(APITestCase):
|
||||
).exists()
|
||||
)
|
||||
|
||||
def test_user_create_duplicate_by_username(self):
|
||||
"""Test user create"""
|
||||
user = create_test_user()
|
||||
username = generate_id()
|
||||
obj1 = {
|
||||
"userName": username,
|
||||
"externalId": generate_id(),
|
||||
"emails": [
|
||||
{
|
||||
"primary": True,
|
||||
"value": user.email,
|
||||
}
|
||||
],
|
||||
}
|
||||
obj2 = obj1.copy()
|
||||
obj2.update({"externalId": generate_id()})
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"authentik_sources_scim:v2-users",
|
||||
kwargs={
|
||||
"source_slug": self.source.slug,
|
||||
},
|
||||
),
|
||||
data=dumps(obj1),
|
||||
content_type=SCIM_CONTENT_TYPE,
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||
)
|
||||
self.assertEqual(response.status_code, 201)
|
||||
self.assertTrue(
|
||||
SCIMSourceUser.objects.filter(source=self.source, user__username=username).exists()
|
||||
)
|
||||
self.assertTrue(
|
||||
Event.objects.filter(
|
||||
action=EventAction.MODEL_CREATED, user__username=self.source.token.user.username
|
||||
).exists()
|
||||
)
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"authentik_sources_scim:v2-users",
|
||||
kwargs={
|
||||
"source_slug": self.source.slug,
|
||||
},
|
||||
),
|
||||
data=dumps(obj2),
|
||||
content_type=SCIM_CONTENT_TYPE,
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||
)
|
||||
self.assertEqual(response.status_code, 409)
|
||||
|
||||
def test_user_property_mappings(self):
|
||||
"""Test user property_mappings"""
|
||||
self.source.user_property_mappings.set(
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
from uuid import uuid4
|
||||
|
||||
from django.db.models import Q
|
||||
from django.db.transaction import atomic
|
||||
from django.http import Http404, QueryDict
|
||||
from django.urls import reverse
|
||||
@ -113,8 +114,11 @@ class UsersView(SCIMObjectView):
|
||||
def post(self, request: Request, **kwargs) -> Response:
|
||||
"""Create user handler"""
|
||||
connection = SCIMSourceUser.objects.filter(
|
||||
Q(
|
||||
Q(user__uuid=request.data.get("id"))
|
||||
| Q(user__username=request.data.get("userName"))
|
||||
),
|
||||
source=self.source,
|
||||
user__uuid=request.data.get("id"),
|
||||
).first()
|
||||
if connection:
|
||||
self.logger.debug("Found existing user")
|
||||
|
||||
@ -1,20 +1,18 @@
|
||||
"""AuthenticatorDuoStage API Views"""
|
||||
|
||||
from django.http import Http404
|
||||
from django_filters.rest_framework.backends import DjangoFilterBackend
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import OpenApiResponse, extend_schema, inline_serializer
|
||||
from guardian.shortcuts import get_objects_for_user
|
||||
from rest_framework import mixins
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.fields import CharField, ChoiceField, IntegerField
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.api.authorization import OwnerFilter, OwnerPermissions
|
||||
from authentik.core.api.groups import GroupMemberSerializer
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import ModelSerializer
|
||||
from authentik.flows.api.stages import StageSerializer
|
||||
@ -168,9 +166,11 @@ class AuthenticatorDuoStageViewSet(UsedByMixin, ModelViewSet):
|
||||
class DuoDeviceSerializer(ModelSerializer):
|
||||
"""Serializer for Duo authenticator devices"""
|
||||
|
||||
user = GroupMemberSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = DuoDevice
|
||||
fields = ["pk", "name"]
|
||||
fields = ["pk", "name", "user"]
|
||||
depth = 2
|
||||
|
||||
|
||||
@ -189,8 +189,7 @@ class DuoDeviceViewSet(
|
||||
search_fields = ["name"]
|
||||
filterset_fields = ["name"]
|
||||
ordering = ["name"]
|
||||
permission_classes = [OwnerPermissions]
|
||||
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
|
||||
owner_field = "user"
|
||||
|
||||
|
||||
class DuoAdminDeviceViewSet(ModelViewSet):
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
"""AuthenticatorSMSStage API Views"""
|
||||
|
||||
from django_filters.rest_framework.backends import DjangoFilterBackend
|
||||
from rest_framework import mixins
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
||||
|
||||
from authentik.api.authorization import OwnerFilter, OwnerPermissions
|
||||
from authentik.core.api.groups import GroupMemberSerializer
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import ModelSerializer
|
||||
from authentik.flows.api.stages import StageSerializer
|
||||
@ -44,9 +42,11 @@ class AuthenticatorSMSStageViewSet(UsedByMixin, ModelViewSet):
|
||||
class SMSDeviceSerializer(ModelSerializer):
|
||||
"""Serializer for sms authenticator devices"""
|
||||
|
||||
user = GroupMemberSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = SMSDevice
|
||||
fields = ["name", "pk", "phone_number"]
|
||||
fields = ["name", "pk", "phone_number", "user"]
|
||||
depth = 2
|
||||
extra_kwargs = {
|
||||
"phone_number": {"read_only": True},
|
||||
@ -65,11 +65,10 @@ class SMSDeviceViewSet(
|
||||
|
||||
queryset = SMSDevice.objects.all()
|
||||
serializer_class = SMSDeviceSerializer
|
||||
permission_classes = [OwnerPermissions]
|
||||
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
|
||||
search_fields = ["name"]
|
||||
filterset_fields = ["name"]
|
||||
ordering = ["name"]
|
||||
owner_field = "user"
|
||||
|
||||
|
||||
class SMSAdminDeviceViewSet(ModelViewSet):
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
"""AuthenticatorStaticStage API Views"""
|
||||
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework import mixins
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
||||
|
||||
from authentik.api.authorization import OwnerFilter, OwnerPermissions
|
||||
from authentik.core.api.groups import GroupMemberSerializer
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import ModelSerializer
|
||||
from authentik.flows.api.stages import StageSerializer
|
||||
@ -51,10 +49,11 @@ class StaticDeviceSerializer(ModelSerializer):
|
||||
"""Serializer for static authenticator devices"""
|
||||
|
||||
token_set = StaticDeviceTokenSerializer(many=True, read_only=True)
|
||||
user = GroupMemberSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = StaticDevice
|
||||
fields = ["name", "token_set", "pk"]
|
||||
fields = ["name", "token_set", "pk", "user"]
|
||||
|
||||
|
||||
class StaticDeviceViewSet(
|
||||
@ -69,11 +68,10 @@ class StaticDeviceViewSet(
|
||||
|
||||
queryset = StaticDevice.objects.filter(confirmed=True)
|
||||
serializer_class = StaticDeviceSerializer
|
||||
permission_classes = [OwnerPermissions]
|
||||
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
|
||||
search_fields = ["name"]
|
||||
filterset_fields = ["name"]
|
||||
ordering = ["name"]
|
||||
owner_field = "user"
|
||||
|
||||
|
||||
class StaticAdminDeviceViewSet(ModelViewSet):
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
"""AuthenticatorTOTPStage API Views"""
|
||||
|
||||
from django_filters.rest_framework.backends import DjangoFilterBackend
|
||||
from rest_framework import mixins
|
||||
from rest_framework.fields import ChoiceField
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
||||
|
||||
from authentik.api.authorization import OwnerFilter, OwnerPermissions
|
||||
from authentik.core.api.groups import GroupMemberSerializer
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import ModelSerializer
|
||||
from authentik.flows.api.stages import StageSerializer
|
||||
@ -40,11 +38,14 @@ class AuthenticatorTOTPStageViewSet(UsedByMixin, ModelViewSet):
|
||||
class TOTPDeviceSerializer(ModelSerializer):
|
||||
"""Serializer for totp authenticator devices"""
|
||||
|
||||
user = GroupMemberSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = TOTPDevice
|
||||
fields = [
|
||||
"name",
|
||||
"pk",
|
||||
"user",
|
||||
]
|
||||
depth = 2
|
||||
|
||||
@ -61,11 +62,10 @@ class TOTPDeviceViewSet(
|
||||
|
||||
queryset = TOTPDevice.objects.filter(confirmed=True)
|
||||
serializer_class = TOTPDeviceSerializer
|
||||
permission_classes = [OwnerPermissions]
|
||||
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
|
||||
search_fields = ["name"]
|
||||
filterset_fields = ["name"]
|
||||
ordering = ["name"]
|
||||
owner_field = "user"
|
||||
|
||||
|
||||
class TOTPAdminDeviceViewSet(ModelViewSet):
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
"""AuthenticatorWebAuthnStage API Views"""
|
||||
|
||||
from django_filters.rest_framework.backends import DjangoFilterBackend
|
||||
from rest_framework import mixins
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
||||
|
||||
from authentik.api.authorization import OwnerFilter, OwnerPermissions
|
||||
from authentik.core.api.groups import GroupMemberSerializer
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import ModelSerializer
|
||||
from authentik.stages.authenticator_webauthn.api.device_types import WebAuthnDeviceTypeSerializer
|
||||
@ -16,10 +14,11 @@ class WebAuthnDeviceSerializer(ModelSerializer):
|
||||
"""Serializer for WebAuthn authenticator devices"""
|
||||
|
||||
device_type = WebAuthnDeviceTypeSerializer(read_only=True, allow_null=True)
|
||||
user = GroupMemberSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = WebAuthnDevice
|
||||
fields = ["pk", "name", "created_on", "device_type", "aaguid"]
|
||||
fields = ["pk", "name", "created_on", "device_type", "aaguid", "user"]
|
||||
extra_kwargs = {
|
||||
"aaguid": {"read_only": True},
|
||||
}
|
||||
@ -40,8 +39,7 @@ class WebAuthnDeviceViewSet(
|
||||
search_fields = ["name"]
|
||||
filterset_fields = ["name"]
|
||||
ordering = ["name"]
|
||||
permission_classes = [OwnerPermissions]
|
||||
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
|
||||
owner_field = "user"
|
||||
|
||||
|
||||
class WebAuthnAdminDeviceViewSet(ModelViewSet):
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -1,12 +1,8 @@
|
||||
"""ConsentStage API Views"""
|
||||
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from guardian.utils import get_anonymous_user
|
||||
from rest_framework import mixins
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
||||
|
||||
from authentik.api.authorization import OwnerFilter, OwnerSuperuserPermissions
|
||||
from authentik.core.api.applications import ApplicationSerializer
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.users import UserSerializer
|
||||
@ -57,11 +53,4 @@ class UserConsentViewSet(
|
||||
filterset_fields = ["user", "application"]
|
||||
ordering = ["application", "expires"]
|
||||
search_fields = ["user__username"]
|
||||
permission_classes = [OwnerSuperuserPermissions]
|
||||
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user if self.request else get_anonymous_user()
|
||||
if user.is_superuser:
|
||||
return super().get_queryset()
|
||||
return super().get_queryset().filter(user=user.pk)
|
||||
owner_field = "user"
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
# Generated by Django 5.0.10 on 2025-01-13 18:05
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_core", "0042_authenticatedsession_authentik_c_expires_08251d_idx_and_more"),
|
||||
("authentik_stages_consent", "0006_alter_userconsent_expires"),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddIndex(
|
||||
model_name="userconsent",
|
||||
index=models.Index(fields=["expires"], name="authentik_s_expires_0e99e8_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="userconsent",
|
||||
index=models.Index(fields=["expiring"], name="authentik_s_expirin_8f51e5_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="userconsent",
|
||||
index=models.Index(
|
||||
fields=["expiring", "expires"], name="authentik_s_expirin_e50090_idx"
|
||||
),
|
||||
),
|
||||
]
|
||||
@ -71,3 +71,4 @@ class UserConsent(SerializerModel, ExpiringModel):
|
||||
unique_together = (("user", "application", "permissions"),)
|
||||
verbose_name = _("User Consent")
|
||||
verbose_name_plural = _("User Consents")
|
||||
indexes = ExpiringModel.Meta.indexes
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
# Generated by Django 5.0.10 on 2025-01-13 18:05
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_flows", "0027_auto_20231028_1424"),
|
||||
("authentik_stages_invitation", "0008_alter_invitation_expires"),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddIndex(
|
||||
model_name="invitation",
|
||||
index=models.Index(fields=["expires"], name="authentik_s_expires_96f4b8_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="invitation",
|
||||
index=models.Index(fields=["expiring"], name="authentik_s_expirin_4f8f35_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="invitation",
|
||||
index=models.Index(
|
||||
fields=["expiring", "expires"], name="authentik_s_expirin_4f8096_idx"
|
||||
),
|
||||
),
|
||||
]
|
||||
@ -84,3 +84,4 @@ class Invitation(SerializerModel, ExpiringModel):
|
||||
class Meta:
|
||||
verbose_name = _("Invitation")
|
||||
verbose_name_plural = _("Invitations")
|
||||
indexes = ExpiringModel.Meta.indexes
|
||||
|
||||
@ -104,7 +104,9 @@ class UserWriteStageView(StageView):
|
||||
for key, value in data.items():
|
||||
setter_name = f"set_{key}"
|
||||
# Check if user has a setter for this key, like set_password
|
||||
if hasattr(user, setter_name):
|
||||
if key == "password":
|
||||
user.set_password(value, request=self.request)
|
||||
elif hasattr(user, setter_name):
|
||||
setter = getattr(user, setter_name)
|
||||
if callable(setter):
|
||||
setter(value)
|
||||
|
||||
@ -8,11 +8,11 @@ from django.http import HttpResponseNotFound
|
||||
from django.http.request import urljoin
|
||||
from django.utils.timezone import now
|
||||
from drf_spectacular.utils import OpenApiResponse, extend_schema
|
||||
from rest_framework import permissions
|
||||
from rest_framework.authentication import get_authorization_header
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.fields import CharField, IntegerField
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.permissions import BasePermission
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.serializers import DateTimeField, ModelSerializer
|
||||
@ -27,7 +27,7 @@ from authentik.recovery.lib import create_admin_group, create_recovery_token
|
||||
from authentik.tenants.models import Tenant
|
||||
|
||||
|
||||
class TenantApiKeyPermission(permissions.BasePermission):
|
||||
class TenantApiKeyPermission(BasePermission):
|
||||
"""Authentication based on tenants.api_key"""
|
||||
|
||||
def has_permission(self, request: Request, view: View) -> bool:
|
||||
|
||||
@ -3884,8 +3884,7 @@
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"model",
|
||||
"identifiers"
|
||||
"model"
|
||||
],
|
||||
"properties": {
|
||||
"model": {
|
||||
@ -3915,9 +3914,6 @@
|
||||
},
|
||||
"attrs": {
|
||||
"$ref": "#/$defs/model_authentik_blueprints.metaapplyblueprint"
|
||||
},
|
||||
"identifiers": {
|
||||
"$ref": "#/$defs/model_authentik_blueprints.metaapplyblueprint"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4163,7 +4159,7 @@
|
||||
"re_evaluate_policies": {
|
||||
"type": "boolean",
|
||||
"title": "Re evaluate policies",
|
||||
"description": "Evaluate policies when the Stage is present to the user."
|
||||
"description": "Evaluate policies when the Stage is presented to the user."
|
||||
},
|
||||
"order": {
|
||||
"type": "integer",
|
||||
|
||||
10
go.mod
10
go.mod
@ -6,10 +6,10 @@ toolchain go1.23.0
|
||||
|
||||
require (
|
||||
beryju.io/ldap v0.1.0
|
||||
github.com/coreos/go-oidc/v3 v3.11.0
|
||||
github.com/getsentry/sentry-go v0.30.0
|
||||
github.com/coreos/go-oidc/v3 v3.12.0
|
||||
github.com/getsentry/sentry-go v0.31.1
|
||||
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
|
||||
github.com/go-ldap/ldap/v3 v3.4.9
|
||||
github.com/go-ldap/ldap/v3 v3.4.10
|
||||
github.com/go-openapi/runtime v0.28.0
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/google/uuid v1.6.0
|
||||
@ -29,9 +29,9 @@ require (
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/wwt/guac v1.3.2
|
||||
goauthentik.io/api/v3 v3.2024105.3
|
||||
goauthentik.io/api/v3 v3.2024122.2
|
||||
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
||||
golang.org/x/oauth2 v0.24.0
|
||||
golang.org/x/oauth2 v0.25.0
|
||||
golang.org/x/sync v0.10.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
layeh.com/radius v0.0.0-20210819152912-ad72663a72ab
|
||||
|
||||
25
go.sum
25
go.sum
@ -55,8 +55,8 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI=
|
||||
github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
|
||||
github.com/coreos/go-oidc/v3 v3.12.0 h1:sJk+8G2qq94rDI6ehZ71Bol3oUHy63qNYmkiSjrc/Jo=
|
||||
github.com/coreos/go-oidc/v3 v3.12.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
@ -69,8 +69,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
|
||||
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/getsentry/sentry-go v0.30.0 h1:lWUwDnY7sKHaVIoZ9wYqRHJ5iEmoc0pqcRqFkosKzBo=
|
||||
github.com/getsentry/sentry-go v0.30.0/go.mod h1:WU9B9/1/sHDqeV8T+3VwwbjeR5MSXs/6aqG3mqZrezA=
|
||||
github.com/getsentry/sentry-go v0.31.1 h1:ELVc0h7gwyhnXHDouXkhqTFSO5oslsRDk0++eyE0KJ4=
|
||||
github.com/getsentry/sentry-go v0.31.1/go.mod h1:CYNcMMz73YigoHljQRG+qPF+eMq8gG72XcGN/p71BAY=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.7/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||
@ -86,8 +86,8 @@ github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a h1:v6zMvHuY9
|
||||
github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a/go.mod h1:I79BieaU4fxrw4LMXby6q5OS9XnoR9UIKLOzDFjUmuw=
|
||||
github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
|
||||
github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
|
||||
github.com/go-ldap/ldap/v3 v3.4.9 h1:KxX9eO44/MpqPXVVMPJDB+k/35GEePHE/Jfvl7oRMUo=
|
||||
github.com/go-ldap/ldap/v3 v3.4.9/go.mod h1:+CE/4PPOOdEPGTi2B7qXKQOq+pNBvXZtlBNcVZY0AWI=
|
||||
github.com/go-ldap/ldap/v3 v3.4.10 h1:ot/iwPOhfpNVgB1o+AVXljizWZ9JTp7YF5oeyONmcJU=
|
||||
github.com/go-ldap/ldap/v3 v3.4.10/go.mod h1:JXh4Uxgi40P6E9rdsYqpUtbW46D9UTjJ9QSwGRznplY=
|
||||
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=
|
||||
@ -299,8 +299,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
|
||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
goauthentik.io/api/v3 v3.2024105.3 h1:Vl1vwPkCtA8hChsxwO3NUI8nupFC7r93jUHvqM+kYVw=
|
||||
goauthentik.io/api/v3 v3.2024105.3/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
||||
goauthentik.io/api/v3 v3.2024122.2 h1:QC+ZQ+AxlPwl9OG1X/Z62EVepmTGyfvJUxhUdFjs+4s=
|
||||
goauthentik.io/api/v3 v3.2024122.2/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=
|
||||
@ -312,7 +312,6 @@ golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@ -386,16 +385,16 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
|
||||
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
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=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
|
||||
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
|
||||
golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
||||
@ -26,14 +26,14 @@ 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"`
|
||||
TLSCaCert string `yaml:"tls_ca_certs" env:"TLS_CA_CERT, overwrite"`
|
||||
}
|
||||
|
||||
type ListenConfig struct {
|
||||
|
||||
@ -16,7 +16,7 @@ func BUILD(def string) string {
|
||||
func FullVersion() string {
|
||||
ver := VERSION
|
||||
if b := BUILD(""); b != "" {
|
||||
ver = fmt.Sprintf("%s.%s", ver, b)
|
||||
return fmt.Sprintf("%s+%s", ver, b)
|
||||
}
|
||||
return ver
|
||||
}
|
||||
|
||||
@ -45,15 +45,15 @@ func (a *Application) getStore(p api.ProxyOutpostConfig, externalHost *url.URL)
|
||||
break
|
||||
}
|
||||
ca := config.Get().Redis.TLSCaCert
|
||||
if ca != nil {
|
||||
if ca != "" {
|
||||
// Get the SystemCertPool, continue with an empty pool on error
|
||||
rootCAs, _ := x509.SystemCertPool()
|
||||
if rootCAs == nil {
|
||||
rootCAs = x509.NewCertPool()
|
||||
}
|
||||
certs, err := os.ReadFile(*ca)
|
||||
certs, err := os.ReadFile(ca)
|
||||
if err != nil {
|
||||
a.log.WithError(err).Fatalf("Failed to append %s to RootCAs", *ca)
|
||||
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 {
|
||||
|
||||
@ -13,6 +13,10 @@ import (
|
||||
"goauthentik.io/internal/utils/sentry"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrAuthentikStarting = errors.New("authentik starting")
|
||||
)
|
||||
|
||||
func (ws *WebServer) configureProxy() {
|
||||
// Reverse proxy to the application server
|
||||
director := func(req *http.Request) {
|
||||
@ -38,7 +42,7 @@ func (ws *WebServer) configureProxy() {
|
||||
}))
|
||||
ws.mainRouter.PathPrefix(config.Get().Web.Path).HandlerFunc(sentry.SentryNoSample(func(rw http.ResponseWriter, r *http.Request) {
|
||||
if !ws.g.IsRunning() {
|
||||
ws.proxyErrorHandler(rw, r, errors.New("authentik starting"))
|
||||
ws.proxyErrorHandler(rw, r, ErrAuthentikStarting)
|
||||
return
|
||||
}
|
||||
before := time.Now()
|
||||
@ -59,7 +63,9 @@ func (ws *WebServer) configureProxy() {
|
||||
}
|
||||
|
||||
func (ws *WebServer) proxyErrorHandler(rw http.ResponseWriter, req *http.Request, err error) {
|
||||
ws.log.WithError(err).Warning("failed to proxy to backend")
|
||||
if !errors.Is(err, ErrAuthentikStarting) {
|
||||
ws.log.WithError(err).Warning("failed to proxy to backend")
|
||||
}
|
||||
rw.WriteHeader(http.StatusBadGateway)
|
||||
em := fmt.Sprintf("failed to connect to authentik backend: %v", err)
|
||||
// return json if the client asks for json
|
||||
|
||||
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-12-18 13:31+0000\n"
|
||||
"POT-Creation-Date: 2024-12-20 00:08+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -101,6 +101,10 @@ msgstr ""
|
||||
msgid "Brands"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/api/application_entitlements.py
|
||||
msgid "User does not have access to application."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/api/devices.py
|
||||
msgid "Extra description not available"
|
||||
msgstr ""
|
||||
@ -225,6 +229,14 @@ msgstr ""
|
||||
msgid "Applications"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Application Entitlement"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Application Entitlements"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Use the source-specific identifier"
|
||||
msgstr ""
|
||||
|
||||
3603
locale/fi/LC_MESSAGES/django.po
Normal file
3603
locale/fi/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load Diff
@ -12,16 +12,16 @@
|
||||
# Charles Leclerc, 2024
|
||||
# nerdinator <florian.dupret@gmail.com>, 2024
|
||||
# Tina, 2024
|
||||
# Marc Schmitt, 2024
|
||||
# Marc Schmitt, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-12-18 13:31+0000\n"
|
||||
"POT-Creation-Date: 2024-12-20 00:08+0000\n"
|
||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||
"Last-Translator: Marc Schmitt, 2024\n"
|
||||
"Last-Translator: Marc Schmitt, 2025\n"
|
||||
"Language-Team: French (https://app.transifex.com/authentik/teams/119923/fr/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@ -121,6 +121,10 @@ msgstr "Marque"
|
||||
msgid "Brands"
|
||||
msgstr "Marques"
|
||||
|
||||
#: authentik/core/api/application_entitlements.py
|
||||
msgid "User does not have access to application."
|
||||
msgstr "L'utilisateur n'a pas accès à l'application."
|
||||
|
||||
#: authentik/core/api/devices.py
|
||||
msgid "Extra description not available"
|
||||
msgstr "Description supplémentaire indisponible"
|
||||
@ -256,6 +260,14 @@ msgstr "Application"
|
||||
msgid "Applications"
|
||||
msgstr "Applications"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Application Entitlement"
|
||||
msgstr "Droit applicatif"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Application Entitlements"
|
||||
msgstr "Droits applicatifs"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Use the source-specific identifier"
|
||||
msgstr "Utiliser l'identifiant spécifique à la source"
|
||||
|
||||
Binary file not shown.
@ -15,7 +15,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-12-18 13:31+0000\n"
|
||||
"POT-Creation-Date: 2024-12-20 00:08+0000\n"
|
||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||
"Last-Translator: deluxghost, 2024\n"
|
||||
"Language-Team: Chinese Simplified (https://app.transifex.com/authentik/teams/119923/zh-Hans/)\n"
|
||||
@ -110,6 +110,10 @@ msgstr "品牌"
|
||||
msgid "Brands"
|
||||
msgstr "品牌"
|
||||
|
||||
#: authentik/core/api/application_entitlements.py
|
||||
msgid "User does not have access to application."
|
||||
msgstr "用户没有访问此应用程序的权限。"
|
||||
|
||||
#: authentik/core/api/devices.py
|
||||
msgid "Extra description not available"
|
||||
msgstr "额外描述不可用"
|
||||
@ -235,6 +239,14 @@ msgstr "应用程序"
|
||||
msgid "Applications"
|
||||
msgstr "应用程序"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Application Entitlement"
|
||||
msgstr "应用程序授权"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Application Entitlements"
|
||||
msgstr "应用程序授权"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Use the source-specific identifier"
|
||||
msgstr "使用源特定的标识符"
|
||||
|
||||
@ -14,7 +14,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-12-18 13:31+0000\n"
|
||||
"POT-Creation-Date: 2024-12-20 00:08+0000\n"
|
||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||
"Last-Translator: deluxghost, 2024\n"
|
||||
"Language-Team: Chinese (China) (https://app.transifex.com/authentik/teams/119923/zh_CN/)\n"
|
||||
@ -109,6 +109,10 @@ msgstr "品牌"
|
||||
msgid "Brands"
|
||||
msgstr "品牌"
|
||||
|
||||
#: authentik/core/api/application_entitlements.py
|
||||
msgid "User does not have access to application."
|
||||
msgstr "用户没有访问此应用程序的权限。"
|
||||
|
||||
#: authentik/core/api/devices.py
|
||||
msgid "Extra description not available"
|
||||
msgstr "额外描述不可用"
|
||||
@ -234,6 +238,14 @@ msgstr "应用程序"
|
||||
msgid "Applications"
|
||||
msgstr "应用程序"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Application Entitlement"
|
||||
msgstr "应用程序授权"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Application Entitlements"
|
||||
msgstr "应用程序授权"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Use the source-specific identifier"
|
||||
msgstr "使用源特定的标识符"
|
||||
|
||||
525
poetry.lock
generated
525
poetry.lock
generated
@ -392,36 +392,36 @@ typeguard = ">=2.13.3,<5.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "aws-cdk-cloud-assembly-schema"
|
||||
version = "38.0.1"
|
||||
version = "39.1.22"
|
||||
description = "Cloud Assembly Schema"
|
||||
optional = false
|
||||
python-versions = "~=3.8"
|
||||
files = [
|
||||
{file = "aws_cdk.cloud_assembly_schema-38.0.1-py3-none-any.whl", hash = "sha256:92613b46213b460681e9424f09b77f06ff059eb1c773092540364ef82fcecf55"},
|
||||
{file = "aws_cdk_cloud_assembly_schema-38.0.1.tar.gz", hash = "sha256:7c75861adc41f7b959910d4b3b191ea242815402e599dbfa31934892838ae25e"},
|
||||
{file = "aws_cdk.cloud_assembly_schema-39.1.22-py3-none-any.whl", hash = "sha256:e66ea238cd6a0c1c117b945195c4355ee7dae8f0ac60fa54d82c297dd8a16425"},
|
||||
{file = "aws_cdk_cloud_assembly_schema-39.1.22.tar.gz", hash = "sha256:8a16625c5907cc25e42246c98d2ceb8b96949867aa6979436fa98ed966832e92"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
jsii = ">=1.103.1,<2.0.0"
|
||||
jsii = ">=1.105.0,<2.0.0"
|
||||
publication = ">=0.0.3"
|
||||
typeguard = ">=2.13.3,<5.0.0"
|
||||
typeguard = ">=2.13.3,<4.3.0"
|
||||
|
||||
[[package]]
|
||||
name = "aws-cdk-lib"
|
||||
version = "2.173.2"
|
||||
version = "2.175.1"
|
||||
description = "Version 2 of the AWS Cloud Development Kit library"
|
||||
optional = false
|
||||
python-versions = "~=3.8"
|
||||
files = [
|
||||
{file = "aws_cdk_lib-2.173.2-py3-none-any.whl", hash = "sha256:1b76846669de83e6572e9c46f5708f6ac045d8e710bafb044230f24e722601ef"},
|
||||
{file = "aws_cdk_lib-2.173.2.tar.gz", hash = "sha256:9eb355c4fd5c1aa56317549600baf88dd4d3b520e2081132119b51349ead8c03"},
|
||||
{file = "aws_cdk_lib-2.175.1-py3-none-any.whl", hash = "sha256:d66ac587a3571b6bfcf11b07f04f02ff3f12e42e87c8783aadb6043df7f638f6"},
|
||||
{file = "aws_cdk_lib-2.175.1.tar.gz", hash = "sha256:e7bafecb2b9de7e315f0c615a88bc91d226e1ddea3cdfaf4c72c6b6f48a78c74"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
"aws-cdk.asset-awscli-v1" = ">=2.2.208,<3.0.0"
|
||||
"aws-cdk.asset-kubectl-v20" = ">=2.1.3,<3.0.0"
|
||||
"aws-cdk.asset-node-proxy-agent-v6" = ">=2.1.0,<3.0.0"
|
||||
"aws-cdk.cloud-assembly-schema" = ">=38.0.1,<39.0.0"
|
||||
"aws-cdk.cloud-assembly-schema" = ">=39.0.1,<40.0.0"
|
||||
constructs = ">=10.0.0,<11.0.0"
|
||||
jsii = ">=1.104.0,<2.0.0"
|
||||
publication = ">=0.0.3"
|
||||
@ -466,13 +466,13 @@ typing-extensions = ">=4.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "bandit"
|
||||
version = "1.8.0"
|
||||
version = "1.8.2"
|
||||
description = "Security oriented static analyser for python code."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "bandit-1.8.0-py3-none-any.whl", hash = "sha256:b1a61d829c0968aed625381e426aa378904b996529d048f8d908fa28f6b13e38"},
|
||||
{file = "bandit-1.8.0.tar.gz", hash = "sha256:b5bfe55a095abd9fe20099178a7c6c060f844bfd4fe4c76d28e35e4c52b9d31e"},
|
||||
{file = "bandit-1.8.2-py3-none-any.whl", hash = "sha256:df6146ad73dd30e8cbda4e29689ddda48364e36ff655dbfc86998401fcf1721f"},
|
||||
{file = "bandit-1.8.2.tar.gz", hash = "sha256:e00ad5a6bc676c0954669fe13818024d66b70e42cf5adb971480cf3b671e835f"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -1116,73 +1116,73 @@ typeguard = ">=2.13.3,<2.14.0"
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.6.9"
|
||||
version = "7.6.10"
|
||||
description = "Code coverage measurement for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "coverage-7.6.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85d9636f72e8991a1706b2b55b06c27545448baf9f6dbf51c4004609aacd7dcb"},
|
||||
{file = "coverage-7.6.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:608a7fd78c67bee8936378299a6cb9f5149bb80238c7a566fc3e6717a4e68710"},
|
||||
{file = "coverage-7.6.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96d636c77af18b5cb664ddf12dab9b15a0cfe9c0bde715da38698c8cea748bfa"},
|
||||
{file = "coverage-7.6.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d75cded8a3cff93da9edc31446872d2997e327921d8eed86641efafd350e1df1"},
|
||||
{file = "coverage-7.6.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7b15f589593110ae767ce997775d645b47e5cbbf54fd322f8ebea6277466cec"},
|
||||
{file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:44349150f6811b44b25574839b39ae35291f6496eb795b7366fef3bd3cf112d3"},
|
||||
{file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d891c136b5b310d0e702e186d70cd16d1119ea8927347045124cb286b29297e5"},
|
||||
{file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:db1dab894cc139f67822a92910466531de5ea6034ddfd2b11c0d4c6257168073"},
|
||||
{file = "coverage-7.6.9-cp310-cp310-win32.whl", hash = "sha256:41ff7b0da5af71a51b53f501a3bac65fb0ec311ebed1632e58fc6107f03b9198"},
|
||||
{file = "coverage-7.6.9-cp310-cp310-win_amd64.whl", hash = "sha256:35371f8438028fdccfaf3570b31d98e8d9eda8bb1d6ab9473f5a390969e98717"},
|
||||
{file = "coverage-7.6.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:932fc826442132dde42ee52cf66d941f581c685a6313feebed358411238f60f9"},
|
||||
{file = "coverage-7.6.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:085161be5f3b30fd9b3e7b9a8c301f935c8313dcf928a07b116324abea2c1c2c"},
|
||||
{file = "coverage-7.6.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccc660a77e1c2bf24ddbce969af9447a9474790160cfb23de6be4fa88e3951c7"},
|
||||
{file = "coverage-7.6.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c69e42c892c018cd3c8d90da61d845f50a8243062b19d228189b0224150018a9"},
|
||||
{file = "coverage-7.6.9-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0824a28ec542a0be22f60c6ac36d679e0e262e5353203bea81d44ee81fe9c6d4"},
|
||||
{file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4401ae5fc52ad8d26d2a5d8a7428b0f0c72431683f8e63e42e70606374c311a1"},
|
||||
{file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98caba4476a6c8d59ec1eb00c7dd862ba9beca34085642d46ed503cc2d440d4b"},
|
||||
{file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ee5defd1733fd6ec08b168bd4f5387d5b322f45ca9e0e6c817ea6c4cd36313e3"},
|
||||
{file = "coverage-7.6.9-cp311-cp311-win32.whl", hash = "sha256:f2d1ec60d6d256bdf298cb86b78dd715980828f50c46701abc3b0a2b3f8a0dc0"},
|
||||
{file = "coverage-7.6.9-cp311-cp311-win_amd64.whl", hash = "sha256:0d59fd927b1f04de57a2ba0137166d31c1a6dd9e764ad4af552912d70428c92b"},
|
||||
{file = "coverage-7.6.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:99e266ae0b5d15f1ca8d278a668df6f51cc4b854513daab5cae695ed7b721cf8"},
|
||||
{file = "coverage-7.6.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9901d36492009a0a9b94b20e52ebfc8453bf49bb2b27bca2c9706f8b4f5a554a"},
|
||||
{file = "coverage-7.6.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abd3e72dd5b97e3af4246cdada7738ef0e608168de952b837b8dd7e90341f015"},
|
||||
{file = "coverage-7.6.9-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff74026a461eb0660366fb01c650c1d00f833a086b336bdad7ab00cc952072b3"},
|
||||
{file = "coverage-7.6.9-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65dad5a248823a4996724a88eb51d4b31587aa7aa428562dbe459c684e5787ae"},
|
||||
{file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:22be16571504c9ccea919fcedb459d5ab20d41172056206eb2994e2ff06118a4"},
|
||||
{file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f957943bc718b87144ecaee70762bc2bc3f1a7a53c7b861103546d3a403f0a6"},
|
||||
{file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ae1387db4aecb1f485fb70a6c0148c6cdaebb6038f1d40089b1fc84a5db556f"},
|
||||
{file = "coverage-7.6.9-cp312-cp312-win32.whl", hash = "sha256:1a330812d9cc7ac2182586f6d41b4d0fadf9be9049f350e0efb275c8ee8eb692"},
|
||||
{file = "coverage-7.6.9-cp312-cp312-win_amd64.whl", hash = "sha256:b12c6b18269ca471eedd41c1b6a1065b2f7827508edb9a7ed5555e9a56dcfc97"},
|
||||
{file = "coverage-7.6.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:899b8cd4781c400454f2f64f7776a5d87bbd7b3e7f7bda0cb18f857bb1334664"},
|
||||
{file = "coverage-7.6.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:61f70dc68bd36810972e55bbbe83674ea073dd1dcc121040a08cdf3416c5349c"},
|
||||
{file = "coverage-7.6.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a289d23d4c46f1a82d5db4abeb40b9b5be91731ee19a379d15790e53031c014"},
|
||||
{file = "coverage-7.6.9-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e216d8044a356fc0337c7a2a0536d6de07888d7bcda76febcb8adc50bdbbd00"},
|
||||
{file = "coverage-7.6.9-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c026eb44f744acaa2bda7493dad903aa5bf5fc4f2554293a798d5606710055d"},
|
||||
{file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e77363e8425325384f9d49272c54045bbed2f478e9dd698dbc65dbc37860eb0a"},
|
||||
{file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:777abfab476cf83b5177b84d7486497e034eb9eaea0d746ce0c1268c71652077"},
|
||||
{file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:447af20e25fdbe16f26e84eb714ba21d98868705cb138252d28bc400381f6ffb"},
|
||||
{file = "coverage-7.6.9-cp313-cp313-win32.whl", hash = "sha256:d872ec5aeb086cbea771c573600d47944eea2dcba8be5f3ee649bfe3cb8dc9ba"},
|
||||
{file = "coverage-7.6.9-cp313-cp313-win_amd64.whl", hash = "sha256:fd1213c86e48dfdc5a0cc676551db467495a95a662d2396ecd58e719191446e1"},
|
||||
{file = "coverage-7.6.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ba9e7484d286cd5a43744e5f47b0b3fb457865baf07bafc6bee91896364e1419"},
|
||||
{file = "coverage-7.6.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e5ea1cf0872ee455c03e5674b5bca5e3e68e159379c1af0903e89f5eba9ccc3a"},
|
||||
{file = "coverage-7.6.9-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d10e07aa2b91835d6abec555ec8b2733347956991901eea6ffac295f83a30e4"},
|
||||
{file = "coverage-7.6.9-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13a9e2d3ee855db3dd6ea1ba5203316a1b1fd8eaeffc37c5b54987e61e4194ae"},
|
||||
{file = "coverage-7.6.9-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c38bf15a40ccf5619fa2fe8f26106c7e8e080d7760aeccb3722664c8656b030"},
|
||||
{file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d5275455b3e4627c8e7154feaf7ee0743c2e7af82f6e3b561967b1cca755a0be"},
|
||||
{file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8f8770dfc6e2c6a2d4569f411015c8d751c980d17a14b0530da2d7f27ffdd88e"},
|
||||
{file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8d2dfa71665a29b153a9681edb1c8d9c1ea50dfc2375fb4dac99ea7e21a0bcd9"},
|
||||
{file = "coverage-7.6.9-cp313-cp313t-win32.whl", hash = "sha256:5e6b86b5847a016d0fbd31ffe1001b63355ed309651851295315031ea7eb5a9b"},
|
||||
{file = "coverage-7.6.9-cp313-cp313t-win_amd64.whl", hash = "sha256:97ddc94d46088304772d21b060041c97fc16bdda13c6c7f9d8fcd8d5ae0d8611"},
|
||||
{file = "coverage-7.6.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:adb697c0bd35100dc690de83154627fbab1f4f3c0386df266dded865fc50a902"},
|
||||
{file = "coverage-7.6.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:be57b6d56e49c2739cdf776839a92330e933dd5e5d929966fbbd380c77f060be"},
|
||||
{file = "coverage-7.6.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1592791f8204ae9166de22ba7e6705fa4ebd02936c09436a1bb85aabca3e599"},
|
||||
{file = "coverage-7.6.9-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e12ae8cc979cf83d258acb5e1f1cf2f3f83524d1564a49d20b8bec14b637f08"},
|
||||
{file = "coverage-7.6.9-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb5555cff66c4d3d6213a296b360f9e1a8e323e74e0426b6c10ed7f4d021e464"},
|
||||
{file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b9389a429e0e5142e69d5bf4a435dd688c14478a19bb901735cdf75e57b13845"},
|
||||
{file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:592ac539812e9b46046620341498caf09ca21023c41c893e1eb9dbda00a70cbf"},
|
||||
{file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a27801adef24cc30871da98a105f77995e13a25a505a0161911f6aafbd66e678"},
|
||||
{file = "coverage-7.6.9-cp39-cp39-win32.whl", hash = "sha256:8e3c3e38930cfb729cb8137d7f055e5a473ddaf1217966aa6238c88bd9fd50e6"},
|
||||
{file = "coverage-7.6.9-cp39-cp39-win_amd64.whl", hash = "sha256:e28bf44afa2b187cc9f41749138a64435bf340adfcacb5b2290c070ce99839d4"},
|
||||
{file = "coverage-7.6.9-pp39.pp310-none-any.whl", hash = "sha256:f3ca78518bc6bc92828cd11867b121891d75cae4ea9e908d72030609b996db1b"},
|
||||
{file = "coverage-7.6.9.tar.gz", hash = "sha256:4a8d8977b0c6ef5aeadcb644da9e69ae0dcfe66ec7f368c89c72e058bd71164d"},
|
||||
{file = "coverage-7.6.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c912978f7fbf47ef99cec50c4401340436d200d41d714c7a4766f377c5b7b78"},
|
||||
{file = "coverage-7.6.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a01ec4af7dfeb96ff0078ad9a48810bb0cc8abcb0115180c6013a6b26237626c"},
|
||||
{file = "coverage-7.6.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3b204c11e2b2d883946fe1d97f89403aa1811df28ce0447439178cc7463448a"},
|
||||
{file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32ee6d8491fcfc82652a37109f69dee9a830e9379166cb73c16d8dc5c2915165"},
|
||||
{file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675cefc4c06e3b4c876b85bfb7c59c5e2218167bbd4da5075cbe3b5790a28988"},
|
||||
{file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f4f620668dbc6f5e909a0946a877310fb3d57aea8198bde792aae369ee1c23b5"},
|
||||
{file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4eea95ef275de7abaef630c9b2c002ffbc01918b726a39f5a4353916ec72d2f3"},
|
||||
{file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e2f0280519e42b0a17550072861e0bc8a80a0870de260f9796157d3fca2733c5"},
|
||||
{file = "coverage-7.6.10-cp310-cp310-win32.whl", hash = "sha256:bc67deb76bc3717f22e765ab3e07ee9c7a5e26b9019ca19a3b063d9f4b874244"},
|
||||
{file = "coverage-7.6.10-cp310-cp310-win_amd64.whl", hash = "sha256:0f460286cb94036455e703c66988851d970fdfd8acc2a1122ab7f4f904e4029e"},
|
||||
{file = "coverage-7.6.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ea3c8f04b3e4af80e17bab607c386a830ffc2fb88a5484e1df756478cf70d1d3"},
|
||||
{file = "coverage-7.6.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:507a20fc863cae1d5720797761b42d2d87a04b3e5aeb682ef3b7332e90598f43"},
|
||||
{file = "coverage-7.6.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d37a84878285b903c0fe21ac8794c6dab58150e9359f1aaebbeddd6412d53132"},
|
||||
{file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a534738b47b0de1995f85f582d983d94031dffb48ab86c95bdf88dc62212142f"},
|
||||
{file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d7a2bf79378d8fb8afaa994f91bfd8215134f8631d27eba3e0e2c13546ce994"},
|
||||
{file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6713ba4b4ebc330f3def51df1d5d38fad60b66720948112f114968feb52d3f99"},
|
||||
{file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab32947f481f7e8c763fa2c92fd9f44eeb143e7610c4ca9ecd6a36adab4081bd"},
|
||||
{file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7bbd8c8f1b115b892e34ba66a097b915d3871db7ce0e6b9901f462ff3a975377"},
|
||||
{file = "coverage-7.6.10-cp311-cp311-win32.whl", hash = "sha256:299e91b274c5c9cdb64cbdf1b3e4a8fe538a7a86acdd08fae52301b28ba297f8"},
|
||||
{file = "coverage-7.6.10-cp311-cp311-win_amd64.whl", hash = "sha256:489a01f94aa581dbd961f306e37d75d4ba16104bbfa2b0edb21d29b73be83609"},
|
||||
{file = "coverage-7.6.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27c6e64726b307782fa5cbe531e7647aee385a29b2107cd87ba7c0105a5d3853"},
|
||||
{file = "coverage-7.6.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c56e097019e72c373bae32d946ecf9858fda841e48d82df7e81c63ac25554078"},
|
||||
{file = "coverage-7.6.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7827a5bc7bdb197b9e066cdf650b2887597ad124dd99777332776f7b7c7d0d0"},
|
||||
{file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204a8238afe787323a8b47d8be4df89772d5c1e4651b9ffa808552bdf20e1d50"},
|
||||
{file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67926f51821b8e9deb6426ff3164870976fe414d033ad90ea75e7ed0c2e5022"},
|
||||
{file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e78b270eadb5702938c3dbe9367f878249b5ef9a2fcc5360ac7bff694310d17b"},
|
||||
{file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:714f942b9c15c3a7a5fe6876ce30af831c2ad4ce902410b7466b662358c852c0"},
|
||||
{file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:abb02e2f5a3187b2ac4cd46b8ced85a0858230b577ccb2c62c81482ca7d18852"},
|
||||
{file = "coverage-7.6.10-cp312-cp312-win32.whl", hash = "sha256:55b201b97286cf61f5e76063f9e2a1d8d2972fc2fcfd2c1272530172fd28c359"},
|
||||
{file = "coverage-7.6.10-cp312-cp312-win_amd64.whl", hash = "sha256:e4ae5ac5e0d1e4edfc9b4b57b4cbecd5bc266a6915c500f358817a8496739247"},
|
||||
{file = "coverage-7.6.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05fca8ba6a87aabdd2d30d0b6c838b50510b56cdcfc604d40760dae7153b73d9"},
|
||||
{file = "coverage-7.6.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9e80eba8801c386f72e0712a0453431259c45c3249f0009aff537a517b52942b"},
|
||||
{file = "coverage-7.6.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a372c89c939d57abe09e08c0578c1d212e7a678135d53aa16eec4430adc5e690"},
|
||||
{file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec22b5e7fe7a0fa8509181c4aac1db48f3dd4d3a566131b313d1efc102892c18"},
|
||||
{file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26bcf5c4df41cad1b19c84af71c22cbc9ea9a547fc973f1f2cc9a290002c8b3c"},
|
||||
{file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e4630c26b6084c9b3cb53b15bd488f30ceb50b73c35c5ad7871b869cb7365fd"},
|
||||
{file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2396e8116db77789f819d2bc8a7e200232b7a282c66e0ae2d2cd84581a89757e"},
|
||||
{file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79109c70cc0882e4d2d002fe69a24aa504dec0cc17169b3c7f41a1d341a73694"},
|
||||
{file = "coverage-7.6.10-cp313-cp313-win32.whl", hash = "sha256:9e1747bab246d6ff2c4f28b4d186b205adced9f7bd9dc362051cc37c4a0c7bd6"},
|
||||
{file = "coverage-7.6.10-cp313-cp313-win_amd64.whl", hash = "sha256:254f1a3b1eef5f7ed23ef265eaa89c65c8c5b6b257327c149db1ca9d4a35f25e"},
|
||||
{file = "coverage-7.6.10-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ccf240eb719789cedbb9fd1338055de2761088202a9a0b73032857e53f612fe"},
|
||||
{file = "coverage-7.6.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0c807ca74d5a5e64427c8805de15b9ca140bba13572d6d74e262f46f50b13273"},
|
||||
{file = "coverage-7.6.10-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bcfa46d7709b5a7ffe089075799b902020b62e7ee56ebaed2f4bdac04c508d8"},
|
||||
{file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e0de1e902669dccbf80b0415fb6b43d27edca2fbd48c74da378923b05316098"},
|
||||
{file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7b444c42bbc533aaae6b5a2166fd1a797cdb5eb58ee51a92bee1eb94a1e1cb"},
|
||||
{file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b330368cb99ef72fcd2dc3ed260adf67b31499584dc8a20225e85bfe6f6cfed0"},
|
||||
{file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9a7cfb50515f87f7ed30bc882f68812fd98bc2852957df69f3003d22a2aa0abf"},
|
||||
{file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f93531882a5f68c28090f901b1d135de61b56331bba82028489bc51bdd818d2"},
|
||||
{file = "coverage-7.6.10-cp313-cp313t-win32.whl", hash = "sha256:89d76815a26197c858f53c7f6a656686ec392b25991f9e409bcef020cd532312"},
|
||||
{file = "coverage-7.6.10-cp313-cp313t-win_amd64.whl", hash = "sha256:54a5f0f43950a36312155dae55c505a76cd7f2b12d26abeebbe7a0b36dbc868d"},
|
||||
{file = "coverage-7.6.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:656c82b8a0ead8bba147de9a89bda95064874c91a3ed43a00e687f23cc19d53a"},
|
||||
{file = "coverage-7.6.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccc2b70a7ed475c68ceb548bf69cec1e27305c1c2606a5eb7c3afff56a1b3b27"},
|
||||
{file = "coverage-7.6.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5e37dc41d57ceba70956fa2fc5b63c26dba863c946ace9705f8eca99daecdc4"},
|
||||
{file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0aa9692b4fdd83a4647eeb7db46410ea1322b5ed94cd1715ef09d1d5922ba87f"},
|
||||
{file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa744da1820678b475e4ba3dfd994c321c5b13381d1041fe9c608620e6676e25"},
|
||||
{file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c0b1818063dc9e9d838c09e3a473c1422f517889436dd980f5d721899e66f315"},
|
||||
{file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:59af35558ba08b758aec4d56182b222976330ef8d2feacbb93964f576a7e7a90"},
|
||||
{file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7ed2f37cfce1ce101e6dffdfd1c99e729dd2ffc291d02d3e2d0af8b53d13840d"},
|
||||
{file = "coverage-7.6.10-cp39-cp39-win32.whl", hash = "sha256:4bcc276261505d82f0ad426870c3b12cb177752834a633e737ec5ee79bbdff18"},
|
||||
{file = "coverage-7.6.10-cp39-cp39-win_amd64.whl", hash = "sha256:457574f4599d2b00f7f637a0700a6422243b3565509457b2dbd3f50703e11f59"},
|
||||
{file = "coverage-7.6.10-pp39.pp310-none-any.whl", hash = "sha256:fd34e7b3405f0cc7ab03d54a334c17a9e802897580d964bd8c2001f4b9fd488f"},
|
||||
{file = "coverage-7.6.10.tar.gz", hash = "sha256:7fb105327c8f8f0682e29843e2ff96af9dcbe5bab8eeb4b398c6a33a16d80a23"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@ -1359,13 +1359,13 @@ dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"]
|
||||
|
||||
[[package]]
|
||||
name = "django"
|
||||
version = "5.0.10"
|
||||
version = "5.0.11"
|
||||
description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
files = [
|
||||
{file = "Django-5.0.10-py3-none-any.whl", hash = "sha256:c8fab2c553750933c8e7f5f95e5507e138e6acf6c2b4581cb691e70fe3ed747b"},
|
||||
{file = "Django-5.0.10.tar.gz", hash = "sha256:0f6cbc56cc298b0451d20a5120c6a8731e9073330fb5d84295c23c151a1eb300"},
|
||||
{file = "Django-5.0.11-py3-none-any.whl", hash = "sha256:09e8128f717266bf382d82ffa4933f13da05d82579abf008ede86acb15dec88b"},
|
||||
{file = "Django-5.0.11.tar.gz", hash = "sha256:e7d98fa05ce09cb3e8d5ad6472fb602322acd1740bfdadc29c8404182d664f65"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -1922,13 +1922,13 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"]
|
||||
|
||||
[[package]]
|
||||
name = "google-api-python-client"
|
||||
version = "2.155.0"
|
||||
version = "2.158.0"
|
||||
description = "Google API Client Library for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "google_api_python_client-2.155.0-py2.py3-none-any.whl", hash = "sha256:83fe9b5aa4160899079d7c93a37be306546a17e6686e2549bcc9584f1a229747"},
|
||||
{file = "google_api_python_client-2.155.0.tar.gz", hash = "sha256:25529f89f0d13abcf3c05c089c423fb2858ac16e0b3727543393468d0d7af67c"},
|
||||
{file = "google_api_python_client-2.158.0-py2.py3-none-any.whl", hash = "sha256:36f8c8d2e79e50f76790ca5946d2f3f8333e210dc8539a6c88e0742416474ad2"},
|
||||
{file = "google_api_python_client-2.158.0.tar.gz", hash = "sha256:b6664597a9955e04977a62752e33fe44cb35c580e190c1cb08a041893172bd67"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -2332,13 +2332,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.4"
|
||||
version = "3.1.5"
|
||||
description = "A very fast and expressive template engine."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"},
|
||||
{file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"},
|
||||
{file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"},
|
||||
{file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -3107,13 +3107,13 @@ dev = ["bumpver", "isort", "mypy", "pylint", "pytest", "yapf"]
|
||||
|
||||
[[package]]
|
||||
name = "msgraph-sdk"
|
||||
version = "1.14.0"
|
||||
version = "1.16.0"
|
||||
description = "The Microsoft Graph Python SDK"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "msgraph_sdk-1.14.0-py3-none-any.whl", hash = "sha256:1a2f327dc8fbe5a5e6d0d84cf71d605e7b118b3066b1e16f011ccd8fd927bb03"},
|
||||
{file = "msgraph_sdk-1.14.0.tar.gz", hash = "sha256:5bbda80941c5d1794682753b8b291bd2ebed719a43d6de949fd0cd613b6dfbbd"},
|
||||
{file = "msgraph_sdk-1.16.0-py3-none-any.whl", hash = "sha256:1dd26ece74c43167818e2ff58b062180233ce7187ad2a061057af1195395c56c"},
|
||||
{file = "msgraph_sdk-1.16.0.tar.gz", hash = "sha256:980d19617d8d8b20545ef77fa5629fef768ce4ea1f2d1a124c5a9dd88d77940c"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -3881,19 +3881,19 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.10.3"
|
||||
version = "2.10.5"
|
||||
description = "Data validation using Python type hints"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pydantic-2.10.3-py3-none-any.whl", hash = "sha256:be04d85bbc7b65651c5f8e6b9976ed9c6f41782a55524cef079a34a0bb82144d"},
|
||||
{file = "pydantic-2.10.3.tar.gz", hash = "sha256:cb5ac360ce894ceacd69c403187900a02c4b20b693a9dd1d643e1effab9eadf9"},
|
||||
{file = "pydantic-2.10.5-py3-none-any.whl", hash = "sha256:4dd4e322dbe55472cb7ca7e73f4b63574eecccf2835ffa2af9021ce113c83c53"},
|
||||
{file = "pydantic-2.10.5.tar.gz", hash = "sha256:278b38dbbaec562011d659ee05f63346951b3a248a6f3642e1bc68894ea2b4ff"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
annotated-types = ">=0.6.0"
|
||||
email-validator = {version = ">=2.0.0", optional = true, markers = "extra == \"email\""}
|
||||
pydantic-core = "2.27.1"
|
||||
pydantic-core = "2.27.2"
|
||||
typing-extensions = ">=4.12.2"
|
||||
|
||||
[package.extras]
|
||||
@ -3902,111 +3902,111 @@ timezone = ["tzdata"]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.27.1"
|
||||
version = "2.27.2"
|
||||
description = "Core functionality for Pydantic validation and serialization"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pydantic_core-2.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71a5e35c75c021aaf400ac048dacc855f000bdfed91614b4a726f7432f1f3d6a"},
|
||||
{file = "pydantic_core-2.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f82d068a2d6ecfc6e054726080af69a6764a10015467d7d7b9f66d6ed5afa23b"},
|
||||
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:121ceb0e822f79163dd4699e4c54f5ad38b157084d97b34de8b232bcaad70278"},
|
||||
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4603137322c18eaf2e06a4495f426aa8d8388940f3c457e7548145011bb68e05"},
|
||||
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a33cd6ad9017bbeaa9ed78a2e0752c5e250eafb9534f308e7a5f7849b0b1bfb4"},
|
||||
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15cc53a3179ba0fcefe1e3ae50beb2784dede4003ad2dfd24f81bba4b23a454f"},
|
||||
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45d9c5eb9273aa50999ad6adc6be5e0ecea7e09dbd0d31bd0c65a55a2592ca08"},
|
||||
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bf7b66ce12a2ac52d16f776b31d16d91033150266eb796967a7e4621707e4f6"},
|
||||
{file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:655d7dd86f26cb15ce8a431036f66ce0318648f8853d709b4167786ec2fa4807"},
|
||||
{file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:5556470f1a2157031e676f776c2bc20acd34c1990ca5f7e56f1ebf938b9ab57c"},
|
||||
{file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f69ed81ab24d5a3bd93861c8c4436f54afdf8e8cc421562b0c7504cf3be58206"},
|
||||
{file = "pydantic_core-2.27.1-cp310-none-win32.whl", hash = "sha256:f5a823165e6d04ccea61a9f0576f345f8ce40ed533013580e087bd4d7442b52c"},
|
||||
{file = "pydantic_core-2.27.1-cp310-none-win_amd64.whl", hash = "sha256:57866a76e0b3823e0b56692d1a0bf722bffb324839bb5b7226a7dbd6c9a40b17"},
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac3b20653bdbe160febbea8aa6c079d3df19310d50ac314911ed8cc4eb7f8cb8"},
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a5a8e19d7c707c4cadb8c18f5f60c843052ae83c20fa7d44f41594c644a1d330"},
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f7059ca8d64fea7f238994c97d91f75965216bcbe5f695bb44f354893f11d52"},
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bed0f8a0eeea9fb72937ba118f9db0cb7e90773462af7962d382445f3005e5a4"},
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3cb37038123447cf0f3ea4c74751f6a9d7afef0eb71aa07bf5f652b5e6a132c"},
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84286494f6c5d05243456e04223d5a9417d7f443c3b76065e75001beb26f88de"},
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acc07b2cfc5b835444b44a9956846b578d27beeacd4b52e45489e93276241025"},
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4fefee876e07a6e9aad7a8c8c9f85b0cdbe7df52b8a9552307b09050f7512c7e"},
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:258c57abf1188926c774a4c94dd29237e77eda19462e5bb901d88adcab6af919"},
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:35c14ac45fcfdf7167ca76cc80b2001205a8d5d16d80524e13508371fb8cdd9c"},
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d1b26e1dff225c31897696cab7d4f0a315d4c0d9e8666dbffdb28216f3b17fdc"},
|
||||
{file = "pydantic_core-2.27.1-cp311-none-win32.whl", hash = "sha256:2cdf7d86886bc6982354862204ae3b2f7f96f21a3eb0ba5ca0ac42c7b38598b9"},
|
||||
{file = "pydantic_core-2.27.1-cp311-none-win_amd64.whl", hash = "sha256:3af385b0cee8df3746c3f406f38bcbfdc9041b5c2d5ce3e5fc6637256e60bbc5"},
|
||||
{file = "pydantic_core-2.27.1-cp311-none-win_arm64.whl", hash = "sha256:81f2ec23ddc1b476ff96563f2e8d723830b06dceae348ce02914a37cb4e74b89"},
|
||||
{file = "pydantic_core-2.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9cbd94fc661d2bab2bc702cddd2d3370bbdcc4cd0f8f57488a81bcce90c7a54f"},
|
||||
{file = "pydantic_core-2.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f8c4718cd44ec1580e180cb739713ecda2bdee1341084c1467802a417fe0f02"},
|
||||
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15aae984e46de8d376df515f00450d1522077254ef6b7ce189b38ecee7c9677c"},
|
||||
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ba5e3963344ff25fc8c40da90f44b0afca8cfd89d12964feb79ac1411a260ac"},
|
||||
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:992cea5f4f3b29d6b4f7f1726ed8ee46c8331c6b4eed6db5b40134c6fe1768bb"},
|
||||
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0325336f348dbee6550d129b1627cb8f5351a9dc91aad141ffb96d4937bd9529"},
|
||||
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7597c07fbd11515f654d6ece3d0e4e5093edc30a436c63142d9a4b8e22f19c35"},
|
||||
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3bbd5d8cc692616d5ef6fbbbd50dbec142c7e6ad9beb66b78a96e9c16729b089"},
|
||||
{file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:dc61505e73298a84a2f317255fcc72b710b72980f3a1f670447a21efc88f8381"},
|
||||
{file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:e1f735dc43da318cad19b4173dd1ffce1d84aafd6c9b782b3abc04a0d5a6f5bb"},
|
||||
{file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f4e5658dbffe8843a0f12366a4c2d1c316dbe09bb4dfbdc9d2d9cd6031de8aae"},
|
||||
{file = "pydantic_core-2.27.1-cp312-none-win32.whl", hash = "sha256:672ebbe820bb37988c4d136eca2652ee114992d5d41c7e4858cdd90ea94ffe5c"},
|
||||
{file = "pydantic_core-2.27.1-cp312-none-win_amd64.whl", hash = "sha256:66ff044fd0bb1768688aecbe28b6190f6e799349221fb0de0e6f4048eca14c16"},
|
||||
{file = "pydantic_core-2.27.1-cp312-none-win_arm64.whl", hash = "sha256:9a3b0793b1bbfd4146304e23d90045f2a9b5fd5823aa682665fbdaf2a6c28f3e"},
|
||||
{file = "pydantic_core-2.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f216dbce0e60e4d03e0c4353c7023b202d95cbaeff12e5fd2e82ea0a66905073"},
|
||||
{file = "pydantic_core-2.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a2e02889071850bbfd36b56fd6bc98945e23670773bc7a76657e90e6b6603c08"},
|
||||
{file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b0e23f119b2b456d07ca91b307ae167cc3f6c846a7b169fca5326e32fdc6cf"},
|
||||
{file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:764be71193f87d460a03f1f7385a82e226639732214b402f9aa61f0d025f0737"},
|
||||
{file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c00666a3bd2f84920a4e94434f5974d7bbc57e461318d6bb34ce9cdbbc1f6b2"},
|
||||
{file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccaa88b24eebc0f849ce0a4d09e8a408ec5a94afff395eb69baf868f5183107"},
|
||||
{file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c65af9088ac534313e1963443d0ec360bb2b9cba6c2909478d22c2e363d98a51"},
|
||||
{file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206b5cf6f0c513baffaeae7bd817717140770c74528f3e4c3e1cec7871ddd61a"},
|
||||
{file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:062f60e512fc7fff8b8a9d680ff0ddaaef0193dba9fa83e679c0c5f5fbd018bc"},
|
||||
{file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:a0697803ed7d4af5e4c1adf1670af078f8fcab7a86350e969f454daf598c4960"},
|
||||
{file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:58ca98a950171f3151c603aeea9303ef6c235f692fe555e883591103da709b23"},
|
||||
{file = "pydantic_core-2.27.1-cp313-none-win32.whl", hash = "sha256:8065914ff79f7eab1599bd80406681f0ad08f8e47c880f17b416c9f8f7a26d05"},
|
||||
{file = "pydantic_core-2.27.1-cp313-none-win_amd64.whl", hash = "sha256:ba630d5e3db74c79300d9a5bdaaf6200172b107f263c98a0539eeecb857b2337"},
|
||||
{file = "pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5"},
|
||||
{file = "pydantic_core-2.27.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:5897bec80a09b4084aee23f9b73a9477a46c3304ad1d2d07acca19723fb1de62"},
|
||||
{file = "pydantic_core-2.27.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d0165ab2914379bd56908c02294ed8405c252250668ebcb438a55494c69f44ab"},
|
||||
{file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b9af86e1d8e4cfc82c2022bfaa6f459381a50b94a29e95dcdda8442d6d83864"},
|
||||
{file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f6c8a66741c5f5447e047ab0ba7a1c61d1e95580d64bce852e3df1f895c4067"},
|
||||
{file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a42d6a8156ff78981f8aa56eb6394114e0dedb217cf8b729f438f643608cbcd"},
|
||||
{file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64c65f40b4cd8b0e049a8edde07e38b476da7e3aaebe63287c899d2cff253fa5"},
|
||||
{file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdcf339322a3fae5cbd504edcefddd5a50d9ee00d968696846f089b4432cf78"},
|
||||
{file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bf99c8404f008750c846cb4ac4667b798a9f7de673ff719d705d9b2d6de49c5f"},
|
||||
{file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8f1edcea27918d748c7e5e4d917297b2a0ab80cad10f86631e488b7cddf76a36"},
|
||||
{file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:159cac0a3d096f79ab6a44d77a961917219707e2a130739c64d4dd46281f5c2a"},
|
||||
{file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:029d9757eb621cc6e1848fa0b0310310de7301057f623985698ed7ebb014391b"},
|
||||
{file = "pydantic_core-2.27.1-cp38-none-win32.whl", hash = "sha256:a28af0695a45f7060e6f9b7092558a928a28553366519f64083c63a44f70e618"},
|
||||
{file = "pydantic_core-2.27.1-cp38-none-win_amd64.whl", hash = "sha256:2d4567c850905d5eaaed2f7a404e61012a51caf288292e016360aa2b96ff38d4"},
|
||||
{file = "pydantic_core-2.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:e9386266798d64eeb19dd3677051f5705bf873e98e15897ddb7d76f477131967"},
|
||||
{file = "pydantic_core-2.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4228b5b646caa73f119b1ae756216b59cc6e2267201c27d3912b592c5e323b60"},
|
||||
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3dfe500de26c52abe0477dde16192ac39c98f05bf2d80e76102d394bd13854"},
|
||||
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aee66be87825cdf72ac64cb03ad4c15ffef4143dbf5c113f64a5ff4f81477bf9"},
|
||||
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b748c44bb9f53031c8cbc99a8a061bc181c1000c60a30f55393b6e9c45cc5bd"},
|
||||
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ca038c7f6a0afd0b2448941b6ef9d5e1949e999f9e5517692eb6da58e9d44be"},
|
||||
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e0bd57539da59a3e4671b90a502da9a28c72322a4f17866ba3ac63a82c4498e"},
|
||||
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ac6c2c45c847bbf8f91930d88716a0fb924b51e0c6dad329b793d670ec5db792"},
|
||||
{file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b94d4ba43739bbe8b0ce4262bcc3b7b9f31459ad120fb595627eaeb7f9b9ca01"},
|
||||
{file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:00e6424f4b26fe82d44577b4c842d7df97c20be6439e8e685d0d715feceb9fb9"},
|
||||
{file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:38de0a70160dd97540335b7ad3a74571b24f1dc3ed33f815f0880682e6880131"},
|
||||
{file = "pydantic_core-2.27.1-cp39-none-win32.whl", hash = "sha256:7ccebf51efc61634f6c2344da73e366c75e735960b5654b63d7e6f69a5885fa3"},
|
||||
{file = "pydantic_core-2.27.1-cp39-none-win_amd64.whl", hash = "sha256:a57847b090d7892f123726202b7daa20df6694cbd583b67a592e856bff603d6c"},
|
||||
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3fa80ac2bd5856580e242dbc202db873c60a01b20309c8319b5c5986fbe53ce6"},
|
||||
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d950caa237bb1954f1b8c9227b5065ba6875ac9771bb8ec790d956a699b78676"},
|
||||
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e4216e64d203e39c62df627aa882f02a2438d18a5f21d7f721621f7a5d3611d"},
|
||||
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02a3d637bd387c41d46b002f0e49c52642281edacd2740e5a42f7017feea3f2c"},
|
||||
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:161c27ccce13b6b0c8689418da3885d3220ed2eae2ea5e9b2f7f3d48f1d52c27"},
|
||||
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:19910754e4cc9c63bc1c7f6d73aa1cfee82f42007e407c0f413695c2f7ed777f"},
|
||||
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e173486019cc283dc9778315fa29a363579372fe67045e971e89b6365cc035ed"},
|
||||
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:af52d26579b308921b73b956153066481f064875140ccd1dfd4e77db89dbb12f"},
|
||||
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:981fb88516bd1ae8b0cbbd2034678a39dedc98752f264ac9bc5839d3923fa04c"},
|
||||
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5fde892e6c697ce3e30c61b239330fc5d569a71fefd4eb6512fc6caec9dd9e2f"},
|
||||
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:816f5aa087094099fff7edabb5e01cc370eb21aa1a1d44fe2d2aefdfb5599b31"},
|
||||
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c10c309e18e443ddb108f0ef64e8729363adbfd92d6d57beec680f6261556f3"},
|
||||
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98476c98b02c8e9b2eec76ac4156fd006628b1b2d0ef27e548ffa978393fd154"},
|
||||
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3027001c28434e7ca5a6e1e527487051136aa81803ac812be51802150d880dd"},
|
||||
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7699b1df36a48169cdebda7ab5a2bac265204003f153b4bd17276153d997670a"},
|
||||
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1c39b07d90be6b48968ddc8c19e7585052088fd7ec8d568bb31ff64c70ae3c97"},
|
||||
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:46ccfe3032b3915586e469d4972973f893c0a2bb65669194a5bdea9bacc088c2"},
|
||||
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:62ba45e21cf6571d7f716d903b5b7b6d2617e2d5d67c0923dc47b9d41369f840"},
|
||||
{file = "pydantic_core-2.27.1.tar.gz", hash = "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"},
|
||||
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"},
|
||||
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"},
|
||||
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"},
|
||||
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"},
|
||||
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"},
|
||||
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"},
|
||||
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"},
|
||||
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"},
|
||||
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"},
|
||||
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"},
|
||||
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"},
|
||||
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"},
|
||||
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"},
|
||||
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"},
|
||||
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"},
|
||||
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"},
|
||||
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"},
|
||||
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"},
|
||||
{file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -4258,48 +4258,48 @@ cli = ["click (>=5.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "python-kadmin-rs"
|
||||
version = "0.5.2"
|
||||
version = "0.5.3"
|
||||
description = "Python interface to the Kerberos administration interface (kadm5)"
|
||||
optional = false
|
||||
python-versions = "<3.14,>=3.9"
|
||||
files = [
|
||||
{file = "python_kadmin_rs-0.5.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:1399e507467881882275eb822caee73f7eb509d25c25af406e91a75221a08ec9"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:86c5f0c799ea903fcc7d67ed47ce9080ea639c8468483c4d6e3a854ab268c959"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:67e8805cbdc75e9d0a88378f30acf0bed34fcca5d2130c4d6a613e57676123a7"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:244fca7d8ca7793729b8a01ae9f2a3c5931fca6bc11d7f3b67fa95297146cd8e"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:034bed577e20cdf4682f4d591ec68d51a44e85a101f2d905c3728143390d93f1"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:048e73490278f74510ac7f19a11ca7860c88863f55f2c79a47c875fc174bb2aa"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:dc580a38397dcdd2021127861c0d35a0c85e556644673387e40331f3fb402dc6"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:abb4df1a35bb177a7a9d2aee82d99d2285240368e6a1784c5066003872374679"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:22fd617f126f5dae2e17c4770cea8ffeca7196885508d922798bbdc9368606f1"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:83574caf964140e87df04a1d97d84b1dd1d60395cae430429b8c1b78a1f5e6de"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:01fc8c3cf707bbe011610107a6803ea2cb9025f4152931f40a39dc8b8d29d42a"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bb5091dbeb0159f95292768b5dc7cce057a29339d5f9c085921a8f16baa3cb32"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:0ece4d210b70f7810a8d909f32872bb47602f8c9ca00289fb8d34a6ee79f5b19"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:d351b5793d8340e9101bdd2684dc6e84156e37af910140530e762d2d92905819"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:13e13c0487dfb9f6986fc6a11e8526875c935aa9bbdf9514049f2c5b5b5cdae7"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:40fd1663c47bcada61e0bb7c681a1518b9fd1d17f03e3193bdfb6313e5afa6d0"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:945a9314e47d930825e46f532341ea1f595a7a78a9d75866e5564bd28cd4b6af"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:40cc14b24028a23a796fa5a53e6236c72c90247be803c6a8976f6e758b377f67"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:cd5b032fb5c8d609d38bc417e1e5405885d153d39742bbac6514af28b8930a74"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6f904a912ea04cd285b0d33107d6e68c904b046fa5bd7555c48986ee4ef139f7"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:4234bc17dff770cbc32c14b22659651f4c9a882086cc19be7467f4755357f756"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:bb3abbf9a0a91a9205cef8ff4fb45bdeb7ee773d2eda67e3a8c01a2f9f561b7f"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6503feef30cb59fd79b573cde5a2e9f892e5b89ffdb78e78db21815f67a14b80"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b6bfe54524573ccf4424344af88e57804399061f16aaf2db1965cafce79f3c76"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:c953f2cdfd92217d8ae4d3dc0374305ed0bd21cbfa7de50c5f7dfc53c44eaa7a"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:2632b02116651a23e3b5b7fce87f939067918f41b9d542af21ee09d964d41bfd"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:8b8a4a042179e3682a826a5c4bc6ee39055c6133d13d5415d6be2bb0e1d79e4a"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:ed6eafd0f9606d1d554aae7b9f5ebae681ef0dc33b08b0affb363fa65b367ad6"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:81df51e55e45fee08890f85230a33ddb066a7116ef8bdbe9ce854f3b95ed4c2d"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:fb0954ff796e2cb5813665575ecd8f51df28dfeb52a81601516b056288418a94"},
|
||||
{file = "python_kadmin_rs-0.5.2-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:6fcb5f5c49e96e8ec6c5096c701871978bd2a3a7ef4ebdcbc3abb6a05aa8a5b7"},
|
||||
{file = "python_kadmin_rs-0.5.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8437ccce96206c26eb877ddfd7f14c8d8fec0a7cc9344e7dbf982637cd4c28ac"},
|
||||
{file = "python_kadmin_rs-0.5.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:beb2619c27b2f079d7d0c67f3e998712f236808f0c2c0a5389f07d1977246762"},
|
||||
{file = "python_kadmin_rs-0.5.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:cecaeebe7acf78e17730b1fa8e5be7aae0e9052c347fc35b1a2d3f77fd69bfe1"},
|
||||
{file = "python_kadmin_rs-0.5.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e14f3ba4017b8266f6db31aba4bf931593373b9ea8a17b5f9cc05cd2e3674a8b"},
|
||||
{file = "python_kadmin_rs-0.5.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c9b2692f6e07461703ac1d20c590ffd5e980d918cdb19c95d875e5f1cf1df397"},
|
||||
{file = "python_kadmin_rs-0.5.2.tar.gz", hash = "sha256:8ff0c8cc8f2a10ce20ae0cf1dd5b2d5569e47d1d54cf53c4fbc95f9120e91bd8"},
|
||||
{file = "python_kadmin_rs-0.5.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:21f61723687f6fc3f57a8d47f89db0c0eb2394049c6910b3e4b42a77ff4a4d93"},
|
||||
{file = "python_kadmin_rs-0.5.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:24e88e19a1774355e573a71a4bae9387459bdf11c0de86078ae69411e6046067"},
|
||||
{file = "python_kadmin_rs-0.5.3-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6d89c96e4353a805e6c410a19ca028e8d18c19fbfdf097f56f39129fd4b26ad7"},
|
||||
{file = "python_kadmin_rs-0.5.3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:d69d57327765e54913c2c6786cd8260f5d74363a72bb1b18ccbc340957c1b920"},
|
||||
{file = "python_kadmin_rs-0.5.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f89748b3b746236cd29bfec9e5d5d5411c540708e537ca1ad7d18d000f7be073"},
|
||||
{file = "python_kadmin_rs-0.5.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:39b6bddc1f345b43a72d9ec5eea5f0a79e898e141e32e23fa25290fbebe52013"},
|
||||
{file = "python_kadmin_rs-0.5.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:67b66577800fcc33ca279c5524687319fb6d41d2be34b34fa50313d183f0b2f5"},
|
||||
{file = "python_kadmin_rs-0.5.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8720e5bb58bf01c900c3fa3bb728f54fbed3362afe2cb29359418f2212dfe67b"},
|
||||
{file = "python_kadmin_rs-0.5.3-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:8d5cd055a5b7f5be44811fade648f95cab24ad1916d9994a0cafb7b17d790c4b"},
|
||||
{file = "python_kadmin_rs-0.5.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:20aaed5407895b0c77900795b953732eaae2516e72f403768b4fa1f341e67b61"},
|
||||
{file = "python_kadmin_rs-0.5.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6915f9c32f5b092a4ca4239a8de8d7418fa6f994586fdbb6c142bd9bf04848d4"},
|
||||
{file = "python_kadmin_rs-0.5.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8a06ed547840db7a20977be2776d65416d89f8e5d43c1edc1afc15f81621577c"},
|
||||
{file = "python_kadmin_rs-0.5.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:54b5e1c2e22da0d16c1418eb2b46da8baa11699a5db8db2afc52dbfd02d14958"},
|
||||
{file = "python_kadmin_rs-0.5.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:d1dc7ad1f07bbfd09baeb1fb0dfc45c87776ed717052081e63d3bdba340a250e"},
|
||||
{file = "python_kadmin_rs-0.5.3-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:86404a1060ece916088ae4a0d188e9309fd46e0b3003779ee7a8dc7493176779"},
|
||||
{file = "python_kadmin_rs-0.5.3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:7aa62a618af2b2112f708fd44f9cc3cf25e28f1562ea66a2036fb3cd1a47e649"},
|
||||
{file = "python_kadmin_rs-0.5.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80813af82dfbcc6a90505183c822eab11de77b6703e5691e37ed77d292224dd9"},
|
||||
{file = "python_kadmin_rs-0.5.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6799a0faddb4ccf200acfa87da38e5fa2af54970d066b2c876e752bbf794b204"},
|
||||
{file = "python_kadmin_rs-0.5.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7121a9c206c69cde1d05a479515abef74f1c8f8a40befbec6e0c3372c080b2b7"},
|
||||
{file = "python_kadmin_rs-0.5.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:60d1ce08126ae77aa14fbd0d2137a1259043518c67ca30f87d903a2aa190f50b"},
|
||||
{file = "python_kadmin_rs-0.5.3-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:109450153c33f9e6dd26a9da6f74e1fd58cc9367c1e55edc677e5697f92d977f"},
|
||||
{file = "python_kadmin_rs-0.5.3-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:316a12f972b2881e642b5c5dde8f32f03d9829a1c382493b55239d8960e2ed90"},
|
||||
{file = "python_kadmin_rs-0.5.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e42f6598ff7591c5bc6823b7da403d2b853f6d74c64cacb9aa929cd9246ca41c"},
|
||||
{file = "python_kadmin_rs-0.5.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4dd05225b469726935f9adf61e76ab93f0841e81204db62f0f7e017aac6d5610"},
|
||||
{file = "python_kadmin_rs-0.5.3-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:e5fe560c468428fc8dd75fc492993a7bebd27f87f916229f9bca2bb432f1a3e0"},
|
||||
{file = "python_kadmin_rs-0.5.3-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:81a6b16c0eadaefc7a22ac623df91af7d33a7796603468fe15a8c5c83edae1a3"},
|
||||
{file = "python_kadmin_rs-0.5.3-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:2ffdae3516d5b350189925cd403007960f989751d48c7eabcecef527b181762a"},
|
||||
{file = "python_kadmin_rs-0.5.3-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:2756362289dffd2656ab50fa4723fc29b82f2035d6d927b3ec889e88fe36009c"},
|
||||
{file = "python_kadmin_rs-0.5.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b18fee5f03f1ac89d70c621e99e2ebfc9c973daa157662fec55008980fd70247"},
|
||||
{file = "python_kadmin_rs-0.5.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8056f03f82822ff7580a11969f16a4073f3afcece7c73c0b633d1e73e1abcc08"},
|
||||
{file = "python_kadmin_rs-0.5.3-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2d7b1d6a94d93191ec9b12bbe70994b18f8d7a9e5805faa55a22847d3f49c24e"},
|
||||
{file = "python_kadmin_rs-0.5.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:52061b7228bc5d497e91627f805734c280c71a00a82e09f093f5df6e13112d61"},
|
||||
{file = "python_kadmin_rs-0.5.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:a261179cab163eb23e7adb2e0e6637339d1e99dbcf5d0ae58c0dcc0f31c844d2"},
|
||||
{file = "python_kadmin_rs-0.5.3-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:058b6e305e01896ce52810324dd21562345ed7b13c651c83ef1d6c384a42f239"},
|
||||
{file = "python_kadmin_rs-0.5.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8d12ded8ee64843494188ab1f8e6bce9360edb7f9f3aedb3909ef309154e6d4d"},
|
||||
{file = "python_kadmin_rs-0.5.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f2895dcc2b03db5bddbda1eb4eebda0da5371347c2408a331762a4dad1942516"},
|
||||
{file = "python_kadmin_rs-0.5.3.tar.gz", hash = "sha256:4f46fd854af622896136c3ac4fc5e6a37d37bfffb5b2023e438001ffa62ab7e3"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4630,29 +4630,29 @@ pyasn1 = ">=0.1.3"
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.8.3"
|
||||
version = "0.9.1"
|
||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "ruff-0.8.3-py3-none-linux_armv6l.whl", hash = "sha256:8d5d273ffffff0acd3db5bf626d4b131aa5a5ada1276126231c4174543ce20d6"},
|
||||
{file = "ruff-0.8.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e4d66a21de39f15c9757d00c50c8cdd20ac84f55684ca56def7891a025d7e939"},
|
||||
{file = "ruff-0.8.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c356e770811858bd20832af696ff6c7e884701115094f427b64b25093d6d932d"},
|
||||
{file = "ruff-0.8.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c0a60a825e3e177116c84009d5ebaa90cf40dfab56e1358d1df4e29a9a14b13"},
|
||||
{file = "ruff-0.8.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75fb782f4db39501210ac093c79c3de581d306624575eddd7e4e13747e61ba18"},
|
||||
{file = "ruff-0.8.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f26bc76a133ecb09a38b7868737eded6941b70a6d34ef53a4027e83913b6502"},
|
||||
{file = "ruff-0.8.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:01b14b2f72a37390c1b13477c1c02d53184f728be2f3ffc3ace5b44e9e87b90d"},
|
||||
{file = "ruff-0.8.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53babd6e63e31f4e96ec95ea0d962298f9f0d9cc5990a1bbb023a6baf2503a82"},
|
||||
{file = "ruff-0.8.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ae441ce4cf925b7f363d33cd6570c51435972d697e3e58928973994e56e1452"},
|
||||
{file = "ruff-0.8.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7c65bc0cadce32255e93c57d57ecc2cca23149edd52714c0c5d6fa11ec328cd"},
|
||||
{file = "ruff-0.8.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5be450bb18f23f0edc5a4e5585c17a56ba88920d598f04a06bd9fd76d324cb20"},
|
||||
{file = "ruff-0.8.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8faeae3827eaa77f5721f09b9472a18c749139c891dbc17f45e72d8f2ca1f8fc"},
|
||||
{file = "ruff-0.8.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:db503486e1cf074b9808403991663e4277f5c664d3fe237ee0d994d1305bb060"},
|
||||
{file = "ruff-0.8.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6567be9fb62fbd7a099209257fef4ad2c3153b60579818b31a23c886ed4147ea"},
|
||||
{file = "ruff-0.8.3-py3-none-win32.whl", hash = "sha256:19048f2f878f3ee4583fc6cb23fb636e48c2635e30fb2022b3a1cd293402f964"},
|
||||
{file = "ruff-0.8.3-py3-none-win_amd64.whl", hash = "sha256:f7df94f57d7418fa7c3ffb650757e0c2b96cf2501a0b192c18e4fb5571dfada9"},
|
||||
{file = "ruff-0.8.3-py3-none-win_arm64.whl", hash = "sha256:fe2756edf68ea79707c8d68b78ca9a58ed9af22e430430491ee03e718b5e4936"},
|
||||
{file = "ruff-0.8.3.tar.gz", hash = "sha256:5e7558304353b84279042fc584a4f4cb8a07ae79b2bf3da1a7551d960b5626d3"},
|
||||
{file = "ruff-0.9.1-py3-none-linux_armv6l.whl", hash = "sha256:84330dda7abcc270e6055551aca93fdde1b0685fc4fd358f26410f9349cf1743"},
|
||||
{file = "ruff-0.9.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3cae39ba5d137054b0e5b472aee3b78a7c884e61591b100aeb544bcd1fc38d4f"},
|
||||
{file = "ruff-0.9.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:50c647ff96f4ba288db0ad87048257753733763b409b2faf2ea78b45c8bb7fcb"},
|
||||
{file = "ruff-0.9.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0c8b149e9c7353cace7d698e1656ffcf1e36e50f8ea3b5d5f7f87ff9986a7ca"},
|
||||
{file = "ruff-0.9.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:beb3298604540c884d8b282fe7625651378e1986c25df51dec5b2f60cafc31ce"},
|
||||
{file = "ruff-0.9.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39d0174ccc45c439093971cc06ed3ac4dc545f5e8bdacf9f067adf879544d969"},
|
||||
{file = "ruff-0.9.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:69572926c0f0c9912288915214ca9b2809525ea263603370b9e00bed2ba56dbd"},
|
||||
{file = "ruff-0.9.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:937267afce0c9170d6d29f01fcd1f4378172dec6760a9f4dface48cdabf9610a"},
|
||||
{file = "ruff-0.9.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:186c2313de946f2c22bdf5954b8dd083e124bcfb685732cfb0beae0c47233d9b"},
|
||||
{file = "ruff-0.9.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f94942a3bb767675d9a051867c036655fe9f6c8a491539156a6f7e6b5f31831"},
|
||||
{file = "ruff-0.9.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:728d791b769cc28c05f12c280f99e8896932e9833fef1dd8756a6af2261fd1ab"},
|
||||
{file = "ruff-0.9.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2f312c86fb40c5c02b44a29a750ee3b21002bd813b5233facdaf63a51d9a85e1"},
|
||||
{file = "ruff-0.9.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ae017c3a29bee341ba584f3823f805abbe5fe9cd97f87ed07ecbf533c4c88366"},
|
||||
{file = "ruff-0.9.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5dc40a378a0e21b4cfe2b8a0f1812a6572fc7b230ef12cd9fac9161aa91d807f"},
|
||||
{file = "ruff-0.9.1-py3-none-win32.whl", hash = "sha256:46ebf5cc106cf7e7378ca3c28ce4293b61b449cd121b98699be727d40b79ba72"},
|
||||
{file = "ruff-0.9.1-py3-none-win_amd64.whl", hash = "sha256:342a824b46ddbcdddd3abfbb332fa7fcaac5488bf18073e841236aadf4ad5c19"},
|
||||
{file = "ruff-0.9.1-py3-none-win_arm64.whl", hash = "sha256:1cd76c7f9c679e6e8f2af8f778367dca82b95009bc7b1a85a47f1521ae524fa7"},
|
||||
{file = "ruff-0.9.1.tar.gz", hash = "sha256:fd2b25ecaf907d6458fa842675382c8597b3c746a2dde6717fe3415425df0c17"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -5087,13 +5087,13 @@ wsproto = ">=0.14"
|
||||
|
||||
[[package]]
|
||||
name = "twilio"
|
||||
version = "9.4.1"
|
||||
version = "9.4.3"
|
||||
description = "Twilio API client and TwiML generator"
|
||||
optional = false
|
||||
python-versions = ">=3.7.0"
|
||||
files = [
|
||||
{file = "twilio-9.4.1-py2.py3-none-any.whl", hash = "sha256:2447e041cec11167d7765aaa62ab1dae3b82b712245ca9a966096acd8b9f426f"},
|
||||
{file = "twilio-9.4.1.tar.gz", hash = "sha256:e24c640696ccc726bba14160951da3cfc6b4bcb772fdcb3e8c16dc3cc851ef12"},
|
||||
{file = "twilio-9.4.3-py2.py3-none-any.whl", hash = "sha256:25b383c5f3e2a2d18c5740ae3cc6ac0ed1f3725e84a83021c2443da6124bcd8b"},
|
||||
{file = "twilio-9.4.3.tar.gz", hash = "sha256:875566e64179644a95fa20b2db6916ae5f43245988761379afda5fb705e9052a"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -5225,6 +5225,17 @@ files = [
|
||||
[package.dependencies]
|
||||
ua-parser = "*"
|
||||
|
||||
[[package]]
|
||||
name = "unidecode"
|
||||
version = "1.3.8"
|
||||
description = "ASCII transliterations of Unicode text"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "Unidecode-1.3.8-py3-none-any.whl", hash = "sha256:d130a61ce6696f8148a3bd8fe779c99adeb4b870584eeb9526584e9aa091fd39"},
|
||||
{file = "Unidecode-1.3.8.tar.gz", hash = "sha256:cfdb349d46ed3873ece4586b96aa75258726e2fa8ec21d6f00a591d98806c2f4"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uritemplate"
|
||||
version = "4.1.1"
|
||||
@ -5238,13 +5249,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.2.3"
|
||||
version = "2.3.0"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"},
|
||||
{file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"},
|
||||
{file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"},
|
||||
{file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -5934,4 +5945,4 @@ files = [
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "~3.12"
|
||||
content-hash = "38089ad25be7638c118f4b503ad2f8495c941667f5485efe60b2bbdb14d6f44c"
|
||||
content-hash = "a3915ac2ef2bb53f7cd67070912cdaf717c3bf73ed972fa337a9b07fce162451"
|
||||
|
||||
@ -131,7 +131,7 @@ pydantic-scim = "*"
|
||||
pyjwt = "*"
|
||||
pyrad = "*"
|
||||
python = "~3.12"
|
||||
python-kadmin-rs = "0.5.2"
|
||||
python-kadmin-rs = "0.5.3"
|
||||
pyyaml = "*"
|
||||
requests-oauthlib = "*"
|
||||
scim2-filter-parser = "*"
|
||||
@ -143,6 +143,7 @@ swagger-spec-validator = "*"
|
||||
tenant-schemas-celery = "*"
|
||||
twilio = "*"
|
||||
ua-parser = "*"
|
||||
unidecode = "*"
|
||||
# Pinned because of botocore https://github.com/orgs/python-poetry/discussions/7937
|
||||
urllib3 = { extras = ["secure"], version = "<3" }
|
||||
uvicorn = { extras = ["standard"], version = "*" }
|
||||
@ -152,7 +153,7 @@ wsproto = "*"
|
||||
xmlsec = "*"
|
||||
zxcvbn = "*"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
aws-cdk-lib = "*"
|
||||
bandit = "*"
|
||||
black = "*"
|
||||
|
||||
54
schema.yml
54
schema.yml
@ -349,7 +349,7 @@ paths:
|
||||
description: ''
|
||||
/admin/workers/:
|
||||
get:
|
||||
operationId: admin_workers_retrieve
|
||||
operationId: admin_workers_list
|
||||
description: Get currently connected worker count.
|
||||
tags:
|
||||
- admin
|
||||
@ -360,7 +360,9 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Workers'
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Worker'
|
||||
description: ''
|
||||
'400':
|
||||
content:
|
||||
@ -22088,6 +22090,9 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SAMLMetadata'
|
||||
application/xml:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SAMLMetadata'
|
||||
description: ''
|
||||
'404':
|
||||
description: Provider has no application assigned
|
||||
@ -41221,9 +41226,14 @@ components:
|
||||
type: string
|
||||
description: The human-readable name of this device.
|
||||
maxLength: 64
|
||||
user:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/GroupMember'
|
||||
readOnly: true
|
||||
required:
|
||||
- name
|
||||
- pk
|
||||
- user
|
||||
DuoDeviceEnrollmentStatus:
|
||||
type: object
|
||||
properties:
|
||||
@ -42391,7 +42401,7 @@ components:
|
||||
description: Evaluate policies during the Flow planning process.
|
||||
re_evaluate_policies:
|
||||
type: boolean
|
||||
description: Evaluate policies when the Stage is present to the user.
|
||||
description: Evaluate policies when the Stage is presented to the user.
|
||||
order:
|
||||
type: integer
|
||||
maximum: 2147483647
|
||||
@ -42427,7 +42437,7 @@ components:
|
||||
description: Evaluate policies during the Flow planning process.
|
||||
re_evaluate_policies:
|
||||
type: boolean
|
||||
description: Evaluate policies when the Stage is present to the user.
|
||||
description: Evaluate policies when the Stage is presented to the user.
|
||||
order:
|
||||
type: integer
|
||||
maximum: 2147483647
|
||||
@ -48949,7 +48959,7 @@ components:
|
||||
description: Evaluate policies during the Flow planning process.
|
||||
re_evaluate_policies:
|
||||
type: boolean
|
||||
description: Evaluate policies when the Stage is present to the user.
|
||||
description: Evaluate policies when the Stage is presented to the user.
|
||||
order:
|
||||
type: integer
|
||||
maximum: 2147483647
|
||||
@ -54565,10 +54575,15 @@ components:
|
||||
phone_number:
|
||||
type: string
|
||||
readOnly: true
|
||||
user:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/GroupMember'
|
||||
readOnly: true
|
||||
required:
|
||||
- name
|
||||
- phone_number
|
||||
- pk
|
||||
- user
|
||||
SMSDeviceRequest:
|
||||
type: object
|
||||
description: Serializer for sms authenticator devices
|
||||
@ -55270,10 +55285,15 @@ components:
|
||||
type: integer
|
||||
readOnly: true
|
||||
title: ID
|
||||
user:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/GroupMember'
|
||||
readOnly: true
|
||||
required:
|
||||
- name
|
||||
- pk
|
||||
- token_set
|
||||
- user
|
||||
StaticDeviceRequest:
|
||||
type: object
|
||||
description: Serializer for static authenticator devices
|
||||
@ -55502,9 +55522,14 @@ components:
|
||||
type: integer
|
||||
readOnly: true
|
||||
title: ID
|
||||
user:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/GroupMember'
|
||||
readOnly: true
|
||||
required:
|
||||
- name
|
||||
- pk
|
||||
- user
|
||||
TOTPDeviceRequest:
|
||||
type: object
|
||||
description: Serializer for totp authenticator devices
|
||||
@ -56946,12 +56971,17 @@ components:
|
||||
aaguid:
|
||||
type: string
|
||||
readOnly: true
|
||||
user:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/GroupMember'
|
||||
readOnly: true
|
||||
required:
|
||||
- aaguid
|
||||
- created_on
|
||||
- device_type
|
||||
- name
|
||||
- pk
|
||||
- user
|
||||
WebAuthnDeviceRequest:
|
||||
type: object
|
||||
description: Serializer for WebAuthn authenticator devices
|
||||
@ -56987,13 +57017,19 @@ components:
|
||||
required:
|
||||
- aaguid
|
||||
- description
|
||||
Workers:
|
||||
Worker:
|
||||
type: object
|
||||
properties:
|
||||
count:
|
||||
type: integer
|
||||
worker_id:
|
||||
type: string
|
||||
version:
|
||||
type: string
|
||||
version_matching:
|
||||
type: boolean
|
||||
required:
|
||||
- count
|
||||
- version
|
||||
- version_matching
|
||||
- worker_id
|
||||
modelRequest:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/GoogleWorkspaceProviderRequest'
|
||||
|
||||
44
tests/e2e/_process.py
Normal file
44
tests/e2e/_process.py
Normal file
@ -0,0 +1,44 @@
|
||||
"""authentik e2e testing utilities"""
|
||||
|
||||
from datetime import timedelta
|
||||
from time import mktime
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from daphne.testing import DaphneProcess
|
||||
from django import setup as django_setup
|
||||
from django.conf import settings
|
||||
from django.utils.timezone import now
|
||||
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.lib.generators import generate_id
|
||||
|
||||
|
||||
class TestDatabaseProcess(DaphneProcess):
|
||||
"""Channels does not correctly switch to the test database by default.
|
||||
https://github.com/django/channels/issues/2048"""
|
||||
|
||||
def run(self):
|
||||
if not settings.configured: # Fix For raise AppRegistryNotReady("Apps aren't loaded yet.")
|
||||
django_setup() # Ensure Django is fully set up before using settings
|
||||
if not settings.DATABASES[list(settings.DATABASES.keys())[0]]["NAME"].startswith("test_"):
|
||||
for _, db_settings in settings.DATABASES.items():
|
||||
db_settings["NAME"] = f"test_{db_settings['NAME']}"
|
||||
settings.TEST = True
|
||||
from authentik.enterprise.license import LicenseKey
|
||||
|
||||
with (
|
||||
patch(
|
||||
"authentik.enterprise.license.LicenseKey.validate",
|
||||
MagicMock(
|
||||
return_value=LicenseKey(
|
||||
aud="",
|
||||
exp=int(mktime((now() + timedelta(days=3000)).timetuple())),
|
||||
name=generate_id(),
|
||||
internal_users=100,
|
||||
external_users=100,
|
||||
)
|
||||
),
|
||||
),
|
||||
CONFIG.patch("email.port", 1025),
|
||||
):
|
||||
return super().run()
|
||||
@ -1,7 +1,6 @@
|
||||
"""LDAP and Outpost e2e tests"""
|
||||
|
||||
from dataclasses import asdict
|
||||
from time import sleep
|
||||
|
||||
from guardian.shortcuts import assign_perm
|
||||
from ldap3 import ALL, ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES, SUBTREE, Connection, Server
|
||||
@ -56,17 +55,7 @@ class TestProviderLDAP(SeleniumTestCase):
|
||||
outpost.providers.add(ldap)
|
||||
|
||||
self.start_ldap(outpost)
|
||||
|
||||
# Wait until outpost healthcheck succeeds
|
||||
healthcheck_retries = 0
|
||||
while healthcheck_retries < 50: # noqa: PLR2004
|
||||
if len(outpost.state) > 0:
|
||||
state = outpost.state[0]
|
||||
if state.last_seen:
|
||||
break
|
||||
healthcheck_retries += 1
|
||||
sleep(0.5)
|
||||
sleep(5)
|
||||
self.wait_for_outpost(outpost)
|
||||
return outpost
|
||||
|
||||
@retry()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user