Compare commits
116 Commits
analytics
...
expiring-m
Author | SHA1 | Date | |
---|---|---|---|
929e42d3f2 | |||
863958b4d6 | |||
2249b9307e | |||
0a1d283ac8 | |||
6b79190f6c | |||
075944abba | |||
eb98af45e1 | |||
752796a6d7 | |||
466360ecf6 | |||
5b66dbe890 | |||
5bbf9ae189 | |||
dc7bffded9 | |||
fcb61c1516 | |||
7757f5b857 | |||
a0b2a80a42 | |||
9010094425 | |||
825f3382dd | |||
09e8f07d55 | |||
77c595a0fd | |||
fc7e78444f | |||
d636002f4d | |||
7fe3b7f353 | |||
3567dd8c7b | |||
fa08fdd06d | |||
058a388518 | |||
795e0ff100 | |||
dec1014eb1 | |||
9f3909ad2f | |||
33ab4222f6 | |||
9e9016f543 | |||
68029fc94d | |||
15d1d625b9 | |||
b363951c1b | |||
555bec8489 | |||
4d5fba42ea | |||
53ef19c1e6 | |||
6d5172d18a | |||
6b2fced1b9 | |||
9a89a5f94b | |||
9200a598ec | |||
72a904512c | |||
6e04771e64 | |||
0110912ec3 | |||
ddee02e055 | |||
97b087291b | |||
1a094b2fe2 | |||
08a1bf1ca4 | |||
9e2620a5b9 | |||
63196be36a | |||
bcb2e7aeff | |||
6242dec1f0 | |||
5257370e4a | |||
22a77a7fc4 | |||
dec8cfbb39 | |||
5d65fa2aab | |||
d42d0b2254 | |||
93b4cdce39 | |||
ca51857f9a | |||
97ebfeb045 | |||
08041792f2 | |||
d7e4489cde | |||
efea8fcbb6 | |||
a80d4c4351 | |||
ec2d63180f | |||
dc4f341399 | |||
e7698d2c33 | |||
dc1562a7de | |||
524f9ff18e | |||
2f3bd625c6 | |||
b0e471fbd4 | |||
982aee44bb | |||
09fe224895 | |||
68fbec404d | |||
dbc05b88ad | |||
da1b7e4248 | |||
5453710c39 | |||
2e79ad5adf | |||
31d908f3e2 | |||
7cf8991cfc | |||
17d666670a | |||
4a5058dd15 | |||
4af2be894b | |||
975b6e53a6 | |||
384ca87aad | |||
5ce8d65546 | |||
ba28e6de41 | |||
97a36b6c4e | |||
6088e59b9f | |||
cdeed5e13b | |||
6d291d378e | |||
49b79c477a | |||
02ffac791c | |||
95c2a24bd2 | |||
7a0bf613b4 | |||
035648f0de | |||
ff53bccc0f | |||
4fbc13ad81 | |||
48f3abfd91 | |||
1168856d33 | |||
6bfa0f7d7b | |||
ec90b668a1 | |||
bb4d7adfd1 | |||
5655b9c2bf | |||
b7b9bd4fbe | |||
51065b415e | |||
c958df9e7c | |||
004fb105ed | |||
0fde303749 | |||
896ee8925f | |||
dd7ce456b1 | |||
e941981a3a | |||
4fd8b3c532 | |||
91a315d864 | |||
05b990e378 | |||
7964c0cff7 | |||
0cae4a2de2 |
@ -1,5 +1,5 @@
|
||||
[bumpversion]
|
||||
current_version = 2024.8.2
|
||||
current_version = 2024.8.3
|
||||
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*))?
|
||||
|
1
.github/dependabot.yml
vendored
1
.github/dependabot.yml
vendored
@ -23,7 +23,6 @@ updates:
|
||||
- package-ecosystem: npm
|
||||
directories:
|
||||
- "/web"
|
||||
- "/tests/wdio"
|
||||
- "/web/sfe"
|
||||
schedule:
|
||||
interval: daily
|
||||
|
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@ -1,7 +1,7 @@
|
||||
<!--
|
||||
👋 Hi there! Welcome.
|
||||
|
||||
Please check the Contributing guidelines: https://goauthentik.io/developer-docs/#how-can-i-contribute
|
||||
Please check the Contributing guidelines: https://docs.goauthentik.io/docs/developer-docs/#how-can-i-contribute
|
||||
-->
|
||||
|
||||
## Details
|
||||
|
21
.github/workflows/ci-web.yml
vendored
21
.github/workflows/ci-web.yml
vendored
@ -24,17 +24,11 @@ jobs:
|
||||
- prettier-check
|
||||
project:
|
||||
- web
|
||||
- tests/wdio
|
||||
include:
|
||||
- command: tsc
|
||||
project: web
|
||||
- command: lit-analyse
|
||||
project: web
|
||||
exclude:
|
||||
- command: lint:lockfile
|
||||
project: tests/wdio
|
||||
- command: tsc
|
||||
project: tests/wdio
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
@ -50,15 +44,7 @@ jobs:
|
||||
- name: Lint
|
||||
working-directory: ${{ matrix.project }}/
|
||||
run: npm run ${{ matrix.command }}
|
||||
ci-web-mark:
|
||||
needs:
|
||||
- lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo mark
|
||||
build:
|
||||
needs:
|
||||
- ci-web-mark
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@ -74,6 +60,13 @@ jobs:
|
||||
- name: build
|
||||
working-directory: web/
|
||||
run: npm run build
|
||||
ci-web-mark:
|
||||
needs:
|
||||
- build
|
||||
- lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo mark
|
||||
test:
|
||||
needs:
|
||||
- ci-web-mark
|
||||
|
@ -94,7 +94,7 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
|
||||
/bin/sh -c "/usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
|
||||
|
||||
# Stage 5: Python dependencies
|
||||
FROM ghcr.io/goauthentik/fips-python:3.12.6-slim-bookworm-fips-full AS python-deps
|
||||
FROM ghcr.io/goauthentik/fips-python:3.12.7-slim-bookworm-fips-full AS python-deps
|
||||
|
||||
ARG TARGETARCH
|
||||
ARG TARGETVARIANT
|
||||
@ -124,7 +124,7 @@ RUN --mount=type=bind,target=./pyproject.toml,src=./pyproject.toml \
|
||||
pip install --force-reinstall /wheels/*"
|
||||
|
||||
# Stage 6: Run
|
||||
FROM ghcr.io/goauthentik/fips-python:3.12.6-slim-bookworm-fips-full AS final-image
|
||||
FROM ghcr.io/goauthentik/fips-python:3.12.7-slim-bookworm-fips-full AS final-image
|
||||
|
||||
ARG VERSION
|
||||
ARG GIT_BUILD_HASH
|
||||
|
3
Makefile
3
Makefile
@ -19,14 +19,13 @@ pg_name := $(shell python -m authentik.lib.config postgresql.name 2>/dev/null)
|
||||
CODESPELL_ARGS = -D - -D .github/codespell-dictionary.txt \
|
||||
-I .github/codespell-words.txt \
|
||||
-S 'web/src/locales/**' \
|
||||
-S 'website/developer-docs/api/reference/**' \
|
||||
-S 'website/docs/developer-docs/api/reference/**' \
|
||||
authentik \
|
||||
internal \
|
||||
cmd \
|
||||
web/src \
|
||||
website/src \
|
||||
website/blog \
|
||||
website/developer-docs \
|
||||
website/docs \
|
||||
website/integrations \
|
||||
website/src
|
||||
|
@ -34,7 +34,7 @@ For bigger setups, there is a Helm Chart [here](https://github.com/goauthentik/h
|
||||
|
||||
## Development
|
||||
|
||||
See [Developer Documentation](https://goauthentik.io/developer-docs/?utm_source=github)
|
||||
See [Developer Documentation](https://docs.goauthentik.io/docs/developer-docs/?utm_source=github)
|
||||
|
||||
## Security
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
from os import environ
|
||||
|
||||
__version__ = "2024.8.2"
|
||||
__version__ = "2024.8.3"
|
||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||
|
||||
|
||||
|
@ -51,9 +51,11 @@ class BlueprintInstanceSerializer(ModelSerializer):
|
||||
context = self.instance.context if self.instance else {}
|
||||
valid, logs = Importer.from_string(content, context).validate()
|
||||
if not valid:
|
||||
text_logs = "\n".join([x["event"] for x in logs])
|
||||
raise ValidationError(
|
||||
_("Failed to validate blueprint: {logs}".format_map({"logs": text_logs}))
|
||||
[
|
||||
_("Failed to validate blueprint"),
|
||||
*[f"- {x.event}" for x in logs],
|
||||
]
|
||||
)
|
||||
return content
|
||||
|
||||
|
@ -29,9 +29,7 @@ def check_blueprint_v1_file(BlueprintInstance: type, db_alias, path: Path):
|
||||
if version != 1:
|
||||
return
|
||||
blueprint_file.seek(0)
|
||||
instance: BlueprintInstance = (
|
||||
BlueprintInstance.objects.using(db_alias).filter(path=path).first()
|
||||
)
|
||||
instance = BlueprintInstance.objects.using(db_alias).filter(path=path).first()
|
||||
rel_path = path.relative_to(Path(CONFIG.get("blueprints_dir")))
|
||||
meta = None
|
||||
if metadata:
|
||||
|
@ -78,5 +78,5 @@ class TestBlueprintsV1API(APITestCase):
|
||||
self.assertEqual(res.status_code, 400)
|
||||
self.assertJSONEqual(
|
||||
res.content.decode(),
|
||||
{"content": ["Failed to validate blueprint: Invalid blueprint version"]},
|
||||
{"content": ["Failed to validate blueprint", "- Invalid blueprint version"]},
|
||||
)
|
||||
|
@ -69,7 +69,7 @@ from authentik.stages.authenticator_webauthn.models import WebAuthnDeviceType
|
||||
from authentik.tenants.models import Tenant
|
||||
|
||||
# Context set when the serializer is created in a blueprint context
|
||||
# Update website/developer-docs/blueprints/v1/models.md when used
|
||||
# Update website/docs/customize/blueprints/v1/models.md when used
|
||||
SERIALIZER_CONTEXT_BLUEPRINT = "blueprint_entry"
|
||||
|
||||
|
||||
@ -429,7 +429,7 @@ class Importer:
|
||||
orig_import = deepcopy(self._import)
|
||||
if self._import.version != 1:
|
||||
self.logger.warning("Invalid blueprint version")
|
||||
return False, [{"event": "Invalid blueprint version"}]
|
||||
return False, [LogEvent("Invalid blueprint version", log_level="warning", logger=None)]
|
||||
with (
|
||||
transaction_rollback(),
|
||||
capture_logs() as logs,
|
||||
|
@ -38,6 +38,7 @@ class ProviderSerializer(ModelSerializer, MetaNameSerializer):
|
||||
"name",
|
||||
"authentication_flow",
|
||||
"authorization_flow",
|
||||
"invalidation_flow",
|
||||
"property_mappings",
|
||||
"component",
|
||||
"assigned_application_slug",
|
||||
@ -50,6 +51,7 @@ class ProviderSerializer(ModelSerializer, MetaNameSerializer):
|
||||
]
|
||||
extra_kwargs = {
|
||||
"authorization_flow": {"required": True, "allow_null": False},
|
||||
"invalidation_flow": {"required": True, "allow_null": False},
|
||||
}
|
||||
|
||||
|
||||
|
@ -679,7 +679,10 @@ class UserViewSet(UsedByMixin, ModelViewSet):
|
||||
LOGGER.debug("User attempted to impersonate", user=request.user)
|
||||
return Response(status=401)
|
||||
user_to_be = self.get_object()
|
||||
if not request.user.has_perm("impersonate", user_to_be):
|
||||
# Check both object-level perms and global perms
|
||||
if not request.user.has_perm(
|
||||
"authentik_core.impersonate", user_to_be
|
||||
) and not request.user.has_perm("authentik_core.impersonate"):
|
||||
LOGGER.debug("User attempted to impersonate without permissions", user=request.user)
|
||||
return Response(status=401)
|
||||
if user_to_be.pk == self.request.user.pk:
|
||||
|
55
authentik/core/migrations/0040_provider_invalidation_flow.py
Normal file
55
authentik/core/migrations/0040_provider_invalidation_flow.py
Normal file
@ -0,0 +1,55 @@
|
||||
# Generated by Django 5.0.9 on 2024-10-02 11:35
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
from django.apps.registry import Apps
|
||||
from django.db import migrations, models
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
|
||||
def migrate_invalidation_flow_default(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
from authentik.flows.models import FlowDesignation, FlowAuthenticationRequirement
|
||||
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
Flow = apps.get_model("authentik_flows", "Flow")
|
||||
Provider = apps.get_model("authentik_core", "Provider")
|
||||
|
||||
# So this flow is managed via a blueprint, bue we're in a migration so we don't want to rely on that
|
||||
# since the blueprint is just an empty flow we can just create it here
|
||||
# and let it be managed by the blueprint later
|
||||
flow, _ = Flow.objects.using(db_alias).update_or_create(
|
||||
slug="default-provider-invalidation-flow",
|
||||
defaults={
|
||||
"name": "Logged out of application",
|
||||
"title": "You've logged out of %(app)s.",
|
||||
"authentication": FlowAuthenticationRequirement.NONE,
|
||||
"designation": FlowDesignation.INVALIDATION,
|
||||
},
|
||||
)
|
||||
Provider.objects.using(db_alias).filter(invalidation_flow=None).update(invalidation_flow=flow)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_core", "0039_source_group_matching_mode_alter_group_name_and_more"),
|
||||
("authentik_flows", "0027_auto_20231028_1424"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="provider",
|
||||
name="invalidation_flow",
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
help_text="Flow used ending the session from a provider.",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_DEFAULT,
|
||||
related_name="provider_invalidation",
|
||||
to="authentik_flows.flow",
|
||||
),
|
||||
),
|
||||
migrations.RunPython(migrate_invalidation_flow_default),
|
||||
]
|
@ -391,14 +391,23 @@ class Provider(SerializerModel):
|
||||
),
|
||||
related_name="provider_authentication",
|
||||
)
|
||||
|
||||
authorization_flow = models.ForeignKey(
|
||||
"authentik_flows.Flow",
|
||||
# Set to cascade even though null is allowed, since most providers
|
||||
# still require an authorization flow set
|
||||
on_delete=models.CASCADE,
|
||||
null=True,
|
||||
help_text=_("Flow used when authorizing this provider."),
|
||||
related_name="provider_authorization",
|
||||
)
|
||||
invalidation_flow = models.ForeignKey(
|
||||
"authentik_flows.Flow",
|
||||
on_delete=models.SET_DEFAULT,
|
||||
default=None,
|
||||
null=True,
|
||||
help_text=_("Flow used ending the session from a provider."),
|
||||
related_name="provider_invalidation",
|
||||
)
|
||||
|
||||
property_mappings = models.ManyToManyField("PropertyMapping", default=None, blank=True)
|
||||
|
||||
@ -793,12 +802,25 @@ class ExpiringModel(models.Model):
|
||||
return self.delete(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def filter_not_expired(cls, **kwargs) -> QuerySet["Token"]:
|
||||
def _not_expired_filter(cls):
|
||||
return Q(expires__gt=now(), expiring=True) | Q(expiring=False)
|
||||
|
||||
@classmethod
|
||||
def filter_not_expired(cls, delete_expired=False, **kwargs) -> QuerySet["ExpiringModel"]:
|
||||
"""Filer for tokens which are not expired yet or are not expiring,
|
||||
and match filters in `kwargs`"""
|
||||
for obj in cls.objects.filter(**kwargs).filter(Q(expires__lt=now(), expiring=True)):
|
||||
obj.delete()
|
||||
return cls.objects.filter(**kwargs)
|
||||
if delete_expired:
|
||||
cls.delete_expired(**kwargs)
|
||||
return cls.objects.filter(cls._not_expired_filter()).filter(**kwargs)
|
||||
|
||||
@classmethod
|
||||
def delete_expired(cls, **kwargs) -> int:
|
||||
objects = cls.objects.all().exclude(cls._not_expired_filter()).filter(**kwargs)
|
||||
amount = 0
|
||||
for obj in objects:
|
||||
obj.expire_action()
|
||||
amount += 1
|
||||
return amount
|
||||
|
||||
@property
|
||||
def is_expired(self) -> bool:
|
||||
|
@ -30,12 +30,7 @@ def clean_expired_models(self: SystemTask):
|
||||
messages = []
|
||||
for cls in ExpiringModel.__subclasses__():
|
||||
cls: ExpiringModel
|
||||
objects = (
|
||||
cls.objects.all().exclude(expiring=False).exclude(expiring=True, expires__gt=now())
|
||||
)
|
||||
amount = objects.count()
|
||||
for obj in objects:
|
||||
obj.expire_action()
|
||||
amount = cls.delete_expired()
|
||||
LOGGER.debug("Expired models", model=cls, amount=amount)
|
||||
messages.append(f"Expired {amount} {cls._meta.verbose_name_plural}")
|
||||
# Special case
|
||||
|
@ -1,43 +0,0 @@
|
||||
{% extends 'login/base_full.html' %}
|
||||
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}
|
||||
{% trans 'End session' %} - {{ brand.branding_title }}
|
||||
{% endblock %}
|
||||
|
||||
{% block card_title %}
|
||||
{% blocktrans with application=application.name %}
|
||||
You've logged out of {{ application }}.
|
||||
{% endblocktrans %}
|
||||
{% endblock %}
|
||||
|
||||
{% block card %}
|
||||
<form method="POST" class="pf-c-form">
|
||||
<p>
|
||||
{% blocktrans with application=application.name branding_title=brand.branding_title %}
|
||||
You've logged out of {{ application }}. You can go back to the overview to launch another application, or log out of your {{ branding_title }} account.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<a id="ak-back-home" href="{% url 'authentik_core:root-redirect' %}" class="pf-c-button pf-m-primary">
|
||||
{% trans 'Go back to overview' %}
|
||||
</a>
|
||||
|
||||
<a id="logout" href="{% url 'authentik_flows:default-invalidation' %}" class="pf-c-button pf-m-secondary">
|
||||
{% blocktrans with branding_title=brand.branding_title %}
|
||||
Log out of {{ branding_title }}
|
||||
{% endblocktrans %}
|
||||
</a>
|
||||
|
||||
{% if application.get_launch_url %}
|
||||
<a href="{{ application.get_launch_url }}" class="pf-c-button pf-m-secondary">
|
||||
{% blocktrans with application=application.name %}
|
||||
Log back into {{ application }}
|
||||
{% endblocktrans %}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
</form>
|
||||
{% endblock %}
|
@ -134,6 +134,7 @@ class TestApplicationsAPI(APITestCase):
|
||||
"assigned_application_name": "allowed",
|
||||
"assigned_application_slug": "allowed",
|
||||
"authentication_flow": None,
|
||||
"invalidation_flow": None,
|
||||
"authorization_flow": str(self.provider.authorization_flow.pk),
|
||||
"component": "ak-provider-oauth2-form",
|
||||
"meta_model_name": "authentik_providers_oauth2.oauth2provider",
|
||||
@ -186,6 +187,7 @@ class TestApplicationsAPI(APITestCase):
|
||||
"assigned_application_name": "allowed",
|
||||
"assigned_application_slug": "allowed",
|
||||
"authentication_flow": None,
|
||||
"invalidation_flow": None,
|
||||
"authorization_flow": str(self.provider.authorization_flow.pk),
|
||||
"component": "ak-provider-oauth2-form",
|
||||
"meta_model_name": "authentik_providers_oauth2.oauth2provider",
|
||||
|
@ -44,6 +44,26 @@ class TestImpersonation(APITestCase):
|
||||
self.assertEqual(response_body["user"]["username"], self.user.username)
|
||||
self.assertNotIn("original", response_body)
|
||||
|
||||
def test_impersonate_global(self):
|
||||
"""Test impersonation with global permissions"""
|
||||
new_user = create_test_user()
|
||||
assign_perm("authentik_core.impersonate", new_user)
|
||||
assign_perm("authentik_core.view_user", new_user)
|
||||
self.client.force_login(new_user)
|
||||
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"authentik_api:user-impersonate",
|
||||
kwargs={"pk": self.other_user.pk},
|
||||
)
|
||||
)
|
||||
self.assertEqual(response.status_code, 201)
|
||||
|
||||
response = self.client.get(reverse("authentik_api:user-me"))
|
||||
response_body = loads(response.content.decode())
|
||||
self.assertEqual(response_body["user"]["username"], self.other_user.username)
|
||||
self.assertEqual(response_body["original"]["username"], new_user.username)
|
||||
|
||||
def test_impersonate_scoped(self):
|
||||
"""Test impersonation with scoped permissions"""
|
||||
new_user = create_test_user()
|
||||
|
@ -19,7 +19,6 @@ class TestTransactionalApplicationsAPI(APITestCase):
|
||||
"""Test transactional Application + provider creation"""
|
||||
self.client.force_login(self.user)
|
||||
uid = generate_id()
|
||||
authorization_flow = create_test_flow()
|
||||
response = self.client.put(
|
||||
reverse("authentik_api:core-transactional-application"),
|
||||
data={
|
||||
@ -30,7 +29,8 @@ class TestTransactionalApplicationsAPI(APITestCase):
|
||||
"provider_model": "authentik_providers_oauth2.oauth2provider",
|
||||
"provider": {
|
||||
"name": uid,
|
||||
"authorization_flow": str(authorization_flow.pk),
|
||||
"authorization_flow": str(create_test_flow().pk),
|
||||
"invalidation_flow": str(create_test_flow().pk),
|
||||
},
|
||||
},
|
||||
)
|
||||
@ -56,10 +56,16 @@ class TestTransactionalApplicationsAPI(APITestCase):
|
||||
"provider": {
|
||||
"name": uid,
|
||||
"authorization_flow": "",
|
||||
"invalidation_flow": "",
|
||||
},
|
||||
},
|
||||
)
|
||||
self.assertJSONEqual(
|
||||
response.content.decode(),
|
||||
{"provider": {"authorization_flow": ["This field may not be null."]}},
|
||||
{
|
||||
"provider": {
|
||||
"authorization_flow": ["This field may not be null."],
|
||||
"invalidation_flow": ["This field may not be null."],
|
||||
}
|
||||
},
|
||||
)
|
||||
|
@ -24,7 +24,6 @@ from authentik.core.views.interface import (
|
||||
InterfaceView,
|
||||
RootRedirectView,
|
||||
)
|
||||
from authentik.core.views.session import EndSessionView
|
||||
from authentik.flows.views.interface import FlowInterfaceView
|
||||
from authentik.root.asgi_middleware import SessionMiddleware
|
||||
from authentik.root.messages.consumer import MessageConsumer
|
||||
@ -60,11 +59,6 @@ urlpatterns = [
|
||||
ensure_csrf_cookie(FlowInterfaceView.as_view()),
|
||||
name="if-flow",
|
||||
),
|
||||
path(
|
||||
"if/session-end/<slug:application_slug>/",
|
||||
ensure_csrf_cookie(EndSessionView.as_view()),
|
||||
name="if-session-end",
|
||||
),
|
||||
# Fallback for WS
|
||||
path("ws/outpost/<uuid:pk>/", InterfaceView.as_view(template_name="if/admin.html")),
|
||||
path(
|
||||
|
@ -1,23 +0,0 @@
|
||||
"""authentik Session Views"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.views.generic.base import TemplateView
|
||||
|
||||
from authentik.core.models import Application
|
||||
from authentik.policies.views import PolicyAccessView
|
||||
|
||||
|
||||
class EndSessionView(TemplateView, PolicyAccessView):
|
||||
"""Allow the client to end the Session"""
|
||||
|
||||
template_name = "if/end_session.html"
|
||||
|
||||
def resolve_provider_application(self):
|
||||
self.application = get_object_or_404(Application, slug=self.kwargs["application_slug"])
|
||||
|
||||
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["application"] = self.application
|
||||
return context
|
@ -68,6 +68,7 @@ class TestEndpointsAPI(APITestCase):
|
||||
"name": self.provider.name,
|
||||
"authentication_flow": None,
|
||||
"authorization_flow": None,
|
||||
"invalidation_flow": None,
|
||||
"property_mappings": [],
|
||||
"connection_expiry": "hours=8",
|
||||
"delete_token_on_disconnect": False,
|
||||
@ -120,6 +121,7 @@ class TestEndpointsAPI(APITestCase):
|
||||
"name": self.provider.name,
|
||||
"authentication_flow": None,
|
||||
"authorization_flow": None,
|
||||
"invalidation_flow": None,
|
||||
"property_mappings": [],
|
||||
"component": "ak-provider-rac-form",
|
||||
"assigned_application_slug": self.app.slug,
|
||||
@ -149,6 +151,7 @@ class TestEndpointsAPI(APITestCase):
|
||||
"name": self.provider.name,
|
||||
"authentication_flow": None,
|
||||
"authorization_flow": None,
|
||||
"invalidation_flow": None,
|
||||
"property_mappings": [],
|
||||
"component": "ak-provider-rac-form",
|
||||
"assigned_application_slug": self.app.slug,
|
||||
|
@ -50,7 +50,7 @@ class ASNContextProcessor(MMDBContextProcessor):
|
||||
"""Wrapper for Reader.asn"""
|
||||
with start_span(
|
||||
op="authentik.events.asn.asn",
|
||||
description=ip_address,
|
||||
name=ip_address,
|
||||
):
|
||||
if not self.configured():
|
||||
return None
|
||||
|
@ -51,7 +51,7 @@ class GeoIPContextProcessor(MMDBContextProcessor):
|
||||
"""Wrapper for Reader.city"""
|
||||
with start_span(
|
||||
op="authentik.events.geo.city",
|
||||
description=ip_address,
|
||||
name=ip_address,
|
||||
):
|
||||
if not self.configured():
|
||||
return None
|
||||
|
@ -110,9 +110,22 @@ class FlowErrorChallenge(Challenge):
|
||||
class AccessDeniedChallenge(WithUserInfoChallenge):
|
||||
"""Challenge when a flow's active stage calls `stage_invalid()`."""
|
||||
|
||||
error_message = CharField(required=False)
|
||||
component = CharField(default="ak-stage-access-denied")
|
||||
|
||||
error_message = CharField(required=False)
|
||||
|
||||
|
||||
class SessionEndChallenge(WithUserInfoChallenge):
|
||||
"""Challenge for ending a session"""
|
||||
|
||||
component = CharField(default="ak-stage-session-end")
|
||||
|
||||
application_name = CharField(required=False)
|
||||
application_launch_url = CharField(required=False)
|
||||
|
||||
invalidation_flow_url = CharField(required=False)
|
||||
brand_name = CharField(required=True)
|
||||
|
||||
|
||||
class PermissionDict(TypedDict):
|
||||
"""Consent Permission"""
|
||||
|
@ -6,20 +6,18 @@ from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
|
||||
def set_oobe_flow_authentication(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
from guardian.shortcuts import get_anonymous_user
|
||||
from guardian.conf import settings as guardian_settings
|
||||
|
||||
Flow = apps.get_model("authentik_flows", "Flow")
|
||||
User = apps.get_model("authentik_core", "User")
|
||||
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
users = User.objects.using(db_alias).exclude(username="akadmin")
|
||||
try:
|
||||
users = users.exclude(pk=get_anonymous_user().pk)
|
||||
|
||||
except Exception: # nosec
|
||||
pass
|
||||
|
||||
users = (
|
||||
User.objects.using(db_alias)
|
||||
.exclude(username="akadmin")
|
||||
.exclude(username=guardian_settings.ANONYMOUS_USER_NAME)
|
||||
)
|
||||
if users.exists():
|
||||
Flow.objects.using(db_alias).filter(slug="initial-setup").update(
|
||||
authentication="require_superuser"
|
||||
|
@ -107,7 +107,9 @@ class Stage(SerializerModel):
|
||||
|
||||
|
||||
def in_memory_stage(view: type["StageView"], **kwargs) -> Stage:
|
||||
"""Creates an in-memory stage instance, based on a `view` as view."""
|
||||
"""Creates an in-memory stage instance, based on a `view` as view.
|
||||
Any key-word arguments are set as attributes on the stage object,
|
||||
accessible via `self.executor.current_stage`."""
|
||||
stage = Stage()
|
||||
# Because we can't pickle a locally generated function,
|
||||
# we set the view as a separate property and reference a generic function
|
||||
|
@ -166,7 +166,7 @@ class FlowPlanner:
|
||||
def plan(self, request: HttpRequest, default_context: dict[str, Any] | None = None) -> FlowPlan:
|
||||
"""Check each of the flows' policies, check policies for each stage with PolicyBinding
|
||||
and return ordered list"""
|
||||
with start_span(op="authentik.flow.planner.plan", description=self.flow.slug) as span:
|
||||
with start_span(op="authentik.flow.planner.plan", name=self.flow.slug) as span:
|
||||
span: Span
|
||||
span.set_data("flow", self.flow)
|
||||
span.set_data("request", request)
|
||||
@ -233,7 +233,7 @@ class FlowPlanner:
|
||||
with (
|
||||
start_span(
|
||||
op="authentik.flow.planner.build_plan",
|
||||
description=self.flow.slug,
|
||||
name=self.flow.slug,
|
||||
) as span,
|
||||
HIST_FLOWS_PLAN_TIME.labels(flow_slug=self.flow.slug).time(),
|
||||
):
|
||||
|
@ -13,7 +13,7 @@ from rest_framework.request import Request
|
||||
from sentry_sdk import start_span
|
||||
from structlog.stdlib import BoundLogger, get_logger
|
||||
|
||||
from authentik.core.models import User
|
||||
from authentik.core.models import Application, User
|
||||
from authentik.flows.challenge import (
|
||||
AccessDeniedChallenge,
|
||||
Challenge,
|
||||
@ -21,6 +21,7 @@ from authentik.flows.challenge import (
|
||||
ContextualFlowInfo,
|
||||
HttpChallengeResponse,
|
||||
RedirectChallenge,
|
||||
SessionEndChallenge,
|
||||
WithUserInfoChallenge,
|
||||
)
|
||||
from authentik.flows.exceptions import StageInvalidException
|
||||
@ -125,7 +126,7 @@ class ChallengeStageView(StageView):
|
||||
with (
|
||||
start_span(
|
||||
op="authentik.flow.stage.challenge_invalid",
|
||||
description=self.__class__.__name__,
|
||||
name=self.__class__.__name__,
|
||||
),
|
||||
HIST_FLOWS_STAGE_TIME.labels(
|
||||
stage_type=self.__class__.__name__, method="challenge_invalid"
|
||||
@ -135,7 +136,7 @@ class ChallengeStageView(StageView):
|
||||
with (
|
||||
start_span(
|
||||
op="authentik.flow.stage.challenge_valid",
|
||||
description=self.__class__.__name__,
|
||||
name=self.__class__.__name__,
|
||||
),
|
||||
HIST_FLOWS_STAGE_TIME.labels(
|
||||
stage_type=self.__class__.__name__, method="challenge_valid"
|
||||
@ -161,7 +162,7 @@ class ChallengeStageView(StageView):
|
||||
with (
|
||||
start_span(
|
||||
op="authentik.flow.stage.get_challenge",
|
||||
description=self.__class__.__name__,
|
||||
name=self.__class__.__name__,
|
||||
),
|
||||
HIST_FLOWS_STAGE_TIME.labels(
|
||||
stage_type=self.__class__.__name__, method="get_challenge"
|
||||
@ -174,7 +175,7 @@ class ChallengeStageView(StageView):
|
||||
return self.executor.stage_invalid()
|
||||
with start_span(
|
||||
op="authentik.flow.stage._get_challenge",
|
||||
description=self.__class__.__name__,
|
||||
name=self.__class__.__name__,
|
||||
):
|
||||
if not hasattr(challenge, "initial_data"):
|
||||
challenge.initial_data = {}
|
||||
@ -230,7 +231,7 @@ class ChallengeStageView(StageView):
|
||||
return HttpChallengeResponse(challenge_response)
|
||||
|
||||
|
||||
class AccessDeniedChallengeView(ChallengeStageView):
|
||||
class AccessDeniedStage(ChallengeStageView):
|
||||
"""Used internally by FlowExecutor's stage_invalid()"""
|
||||
|
||||
error_message: str | None
|
||||
@ -268,3 +269,31 @@ class RedirectStage(ChallengeStageView):
|
||||
|
||||
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
|
||||
return HttpChallengeResponse(self.get_challenge())
|
||||
|
||||
|
||||
class SessionEndStage(ChallengeStageView):
|
||||
"""Stage inserted when a flow is used as invalidation flow. By default shows actions
|
||||
that the user is likely to take after signing out of a provider."""
|
||||
|
||||
def get_challenge(self, *args, **kwargs) -> Challenge:
|
||||
application: Application | None = self.executor.plan.context.get(PLAN_CONTEXT_APPLICATION)
|
||||
data = {
|
||||
"component": "ak-stage-session-end",
|
||||
"brand_name": self.request.brand.branding_title,
|
||||
}
|
||||
if application:
|
||||
data["application_name"] = application.name
|
||||
data["application_launch_url"] = application.get_launch_url(self.get_pending_user())
|
||||
if self.request.brand.flow_invalidation:
|
||||
data["invalidation_flow_url"] = reverse(
|
||||
"authentik_core:if-flow",
|
||||
kwargs={
|
||||
"flow_slug": self.request.brand.flow_invalidation.slug,
|
||||
},
|
||||
)
|
||||
return SessionEndChallenge(data=data)
|
||||
|
||||
# This can never be reached since this challenge is created on demand and only the
|
||||
# .get() method is called
|
||||
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse: # pragma: no cover
|
||||
return self.executor.cancel()
|
||||
|
@ -54,7 +54,7 @@ from authentik.flows.planner import (
|
||||
FlowPlan,
|
||||
FlowPlanner,
|
||||
)
|
||||
from authentik.flows.stage import AccessDeniedChallengeView, StageView
|
||||
from authentik.flows.stage import AccessDeniedStage, StageView
|
||||
from authentik.lib.sentry import SentryIgnoredException
|
||||
from authentik.lib.utils.errors import exception_to_string
|
||||
from authentik.lib.utils.reflection import all_subclasses, class_to_path
|
||||
@ -153,7 +153,7 @@ class FlowExecutorView(APIView):
|
||||
return plan
|
||||
|
||||
def dispatch(self, request: HttpRequest, flow_slug: str) -> HttpResponse:
|
||||
with start_span(op="authentik.flow.executor.dispatch", description=self.flow.slug) as span:
|
||||
with start_span(op="authentik.flow.executor.dispatch", name=self.flow.slug) as span:
|
||||
span.set_data("authentik Flow", self.flow.slug)
|
||||
get_params = QueryDict(request.GET.get(QS_QUERY, ""))
|
||||
if QS_KEY_TOKEN in get_params:
|
||||
@ -273,7 +273,7 @@ class FlowExecutorView(APIView):
|
||||
with (
|
||||
start_span(
|
||||
op="authentik.flow.executor.stage",
|
||||
description=class_path,
|
||||
name=class_path,
|
||||
) as span,
|
||||
HIST_FLOW_EXECUTION_STAGE_TIME.labels(
|
||||
method=request.method.upper(),
|
||||
@ -324,7 +324,7 @@ class FlowExecutorView(APIView):
|
||||
with (
|
||||
start_span(
|
||||
op="authentik.flow.executor.stage",
|
||||
description=class_path,
|
||||
name=class_path,
|
||||
) as span,
|
||||
HIST_FLOW_EXECUTION_STAGE_TIME.labels(
|
||||
method=request.method.upper(),
|
||||
@ -441,7 +441,7 @@ class FlowExecutorView(APIView):
|
||||
)
|
||||
return self.restart_flow(keep_context)
|
||||
self.cancel()
|
||||
challenge_view = AccessDeniedChallengeView(self, error_message)
|
||||
challenge_view = AccessDeniedStage(self, error_message)
|
||||
challenge_view.request = self.request
|
||||
return to_stage_response(self.request, challenge_view.get(self.request))
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# update website/docs/installation/configuration.mdx
|
||||
# update website/docs/install-config/configuration/configuration.mdx
|
||||
# This is the default configuration file
|
||||
postgresql:
|
||||
host: localhost
|
||||
|
@ -30,6 +30,11 @@ class TestHTTP(TestCase):
|
||||
request = self.factory.get("/", HTTP_X_FORWARDED_FOR="127.0.0.2")
|
||||
self.assertEqual(ClientIPMiddleware.get_client_ip(request), "127.0.0.2")
|
||||
|
||||
def test_forward_for_invalid(self):
|
||||
"""Test invalid forward for"""
|
||||
request = self.factory.get("/", HTTP_X_FORWARDED_FOR="foobar")
|
||||
self.assertEqual(ClientIPMiddleware.get_client_ip(request), ClientIPMiddleware.default_ip)
|
||||
|
||||
def test_fake_outpost(self):
|
||||
"""Test faked IP which is overridden by an outpost"""
|
||||
token = Token.objects.create(
|
||||
@ -53,6 +58,17 @@ class TestHTTP(TestCase):
|
||||
},
|
||||
)
|
||||
self.assertEqual(ClientIPMiddleware.get_client_ip(request), "127.0.0.1")
|
||||
# Invalid, not a real IP
|
||||
self.user.type = UserTypes.INTERNAL_SERVICE_ACCOUNT
|
||||
self.user.save()
|
||||
request = self.factory.get(
|
||||
"/",
|
||||
**{
|
||||
ClientIPMiddleware.outpost_remote_ip_header: "foobar",
|
||||
ClientIPMiddleware.outpost_token_header: token.key,
|
||||
},
|
||||
)
|
||||
self.assertEqual(ClientIPMiddleware.get_client_ip(request), "127.0.0.1")
|
||||
# Valid
|
||||
self.user.type = UserTypes.INTERNAL_SERVICE_ACCOUNT
|
||||
self.user.save()
|
||||
|
@ -9,7 +9,7 @@ from uuid import uuid4
|
||||
from dacite.core import from_dict
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.core.cache import cache
|
||||
from django.db import IntegrityError, models, transaction
|
||||
from django.db import models, transaction
|
||||
from django.db.models.base import Model
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from guardian.models import UserObjectPermission
|
||||
@ -53,7 +53,7 @@ class ServiceConnectionInvalid(SentryIgnoredException):
|
||||
class OutpostConfig:
|
||||
"""Configuration an outpost uses to configure it self"""
|
||||
|
||||
# update website/docs/outposts/_config.md
|
||||
# update website/docs/add-secure-apps/outposts/_config.md
|
||||
|
||||
authentik_host: str = ""
|
||||
authentik_host_insecure: bool = False
|
||||
@ -380,26 +380,22 @@ class Outpost(SerializerModel, ManagedModel):
|
||||
"""Get/create token for auto-generated user"""
|
||||
managed = f"goauthentik.io/outpost/{self.token_identifier}"
|
||||
tokens = Token.filter_not_expired(
|
||||
delete_expired=True,
|
||||
identifier=self.token_identifier,
|
||||
intent=TokenIntents.INTENT_API,
|
||||
managed=managed,
|
||||
)
|
||||
if tokens.exists():
|
||||
return tokens.first()
|
||||
try:
|
||||
return Token.objects.create(
|
||||
user=self.user,
|
||||
identifier=self.token_identifier,
|
||||
intent=TokenIntents.INTENT_API,
|
||||
description=f"Autogenerated by authentik for Outpost {self.name}",
|
||||
expiring=False,
|
||||
managed=managed,
|
||||
)
|
||||
except IntegrityError:
|
||||
# Integrity error happens mostly when managed is reused
|
||||
Token.objects.filter(managed=managed).delete()
|
||||
Token.objects.filter(identifier=self.token_identifier).delete()
|
||||
return self.token
|
||||
token: Token | None = tokens.first()
|
||||
if token:
|
||||
return token
|
||||
return Token.objects.create(
|
||||
user=self.user,
|
||||
identifier=self.token_identifier,
|
||||
intent=TokenIntents.INTENT_API,
|
||||
description=f"Autogenerated by authentik for Outpost {self.name}",
|
||||
expiring=False,
|
||||
managed=managed,
|
||||
)
|
||||
|
||||
def get_required_objects(self) -> Iterable[models.Model | str]:
|
||||
"""Get an iterator of all objects the user needs read access to"""
|
||||
|
@ -113,7 +113,7 @@ class PolicyEngine:
|
||||
with (
|
||||
start_span(
|
||||
op="authentik.policy.engine.build",
|
||||
description=self.__pbm,
|
||||
name=self.__pbm,
|
||||
) as span,
|
||||
HIST_POLICIES_ENGINE_TOTAL_TIME.labels(
|
||||
obj_type=class_to_path(self.__pbm.__class__),
|
||||
|
@ -87,6 +87,7 @@ class LDAPOutpostConfigSerializer(ModelSerializer):
|
||||
|
||||
application_slug = SerializerMethodField()
|
||||
bind_flow_slug = CharField(source="authorization_flow.slug")
|
||||
unbind_flow_slug = SerializerMethodField()
|
||||
|
||||
def get_application_slug(self, instance: LDAPProvider) -> str:
|
||||
"""Prioritise backchannel slug over direct application slug"""
|
||||
@ -94,6 +95,16 @@ class LDAPOutpostConfigSerializer(ModelSerializer):
|
||||
return instance.backchannel_application.slug
|
||||
return instance.application.slug
|
||||
|
||||
def get_unbind_flow_slug(self, instance: LDAPProvider) -> str | None:
|
||||
"""Get slug for unbind flow, defaulting to brand's default flow."""
|
||||
flow = instance.invalidation_flow
|
||||
if not flow and "request" in self.context:
|
||||
request = self.context.get("request")
|
||||
flow = request.brand.flow_invalidation
|
||||
if not flow:
|
||||
return None
|
||||
return flow.slug
|
||||
|
||||
class Meta:
|
||||
model = LDAPProvider
|
||||
fields = [
|
||||
@ -101,6 +112,7 @@ class LDAPOutpostConfigSerializer(ModelSerializer):
|
||||
"name",
|
||||
"base_dn",
|
||||
"bind_flow_slug",
|
||||
"unbind_flow_slug",
|
||||
"application_slug",
|
||||
"certificate",
|
||||
"tls_server_name",
|
||||
|
@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.0.9 on 2024-09-26 16:25
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_providers_oauth2", "0018_alter_accesstoken_expires_and_more"),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddIndex(
|
||||
model_name="accesstoken",
|
||||
index=models.Index(fields=["token"], name="authentik_p_token_4bc870_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="refreshtoken",
|
||||
index=models.Index(fields=["token"], name="authentik_p_token_1a841f_idx"),
|
||||
),
|
||||
]
|
@ -0,0 +1,31 @@
|
||||
# Generated by Django 5.0.9 on 2024-09-27 14:50
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_providers_oauth2", "0019_accesstoken_authentik_p_token_4bc870_idx_and_more"),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveIndex(
|
||||
model_name="accesstoken",
|
||||
name="authentik_p_token_4bc870_idx",
|
||||
),
|
||||
migrations.RemoveIndex(
|
||||
model_name="refreshtoken",
|
||||
name="authentik_p_token_1a841f_idx",
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="accesstoken",
|
||||
index=models.Index(fields=["token", "provider"], name="authentik_p_token_f99422_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="refreshtoken",
|
||||
index=models.Index(fields=["token", "provider"], name="authentik_p_token_a1d921_idx"),
|
||||
),
|
||||
]
|
@ -376,6 +376,9 @@ class AccessToken(SerializerModel, ExpiringModel, BaseGrantModel):
|
||||
_id_token = models.TextField()
|
||||
|
||||
class Meta:
|
||||
indexes = [
|
||||
models.Index(fields=["token", "provider"]),
|
||||
]
|
||||
verbose_name = _("OAuth2 Access Token")
|
||||
verbose_name_plural = _("OAuth2 Access Tokens")
|
||||
|
||||
@ -419,6 +422,9 @@ class RefreshToken(SerializerModel, ExpiringModel, BaseGrantModel):
|
||||
_id_token = models.TextField(verbose_name=_("ID Token"))
|
||||
|
||||
class Meta:
|
||||
indexes = [
|
||||
models.Index(fields=["token", "provider"]),
|
||||
]
|
||||
verbose_name = _("OAuth2 Refresh Token")
|
||||
verbose_name_plural = _("OAuth2 Refresh Tokens")
|
||||
|
||||
|
@ -29,7 +29,6 @@ class TesOAuth2Introspection(OAuthTestCase):
|
||||
self.app = Application.objects.create(
|
||||
name=generate_id(), slug=generate_id(), provider=self.provider
|
||||
)
|
||||
self.app.save()
|
||||
self.user = create_test_admin_user()
|
||||
self.auth = b64encode(
|
||||
f"{self.provider.client_id}:{self.provider.client_secret}".encode()
|
||||
@ -114,6 +113,41 @@ class TesOAuth2Introspection(OAuthTestCase):
|
||||
},
|
||||
)
|
||||
|
||||
def test_introspect_invalid_provider(self):
|
||||
"""Test introspection (mismatched provider and token)"""
|
||||
provider: OAuth2Provider = OAuth2Provider.objects.create(
|
||||
name=generate_id(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="",
|
||||
signing_key=create_test_cert(),
|
||||
)
|
||||
auth = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
|
||||
|
||||
token: AccessToken = AccessToken.objects.create(
|
||||
provider=self.provider,
|
||||
user=self.user,
|
||||
token=generate_id(),
|
||||
auth_time=timezone.now(),
|
||||
_scope="openid user profile",
|
||||
_id_token=json.dumps(
|
||||
asdict(
|
||||
IDToken("foo", "bar"),
|
||||
)
|
||||
),
|
||||
)
|
||||
res = self.client.post(
|
||||
reverse("authentik_providers_oauth2:token-introspection"),
|
||||
HTTP_AUTHORIZATION=f"Basic {auth}",
|
||||
data={"token": token.token},
|
||||
)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertJSONEqual(
|
||||
res.content.decode(),
|
||||
{
|
||||
"active": False,
|
||||
},
|
||||
)
|
||||
|
||||
def test_introspect_invalid_auth(self):
|
||||
"""Test introspect (invalid auth)"""
|
||||
res = self.client.post(
|
||||
|
@ -12,6 +12,7 @@ from authentik.providers.oauth2.api.tokens import (
|
||||
)
|
||||
from authentik.providers.oauth2.views.authorize import AuthorizationFlowInitView
|
||||
from authentik.providers.oauth2.views.device_backchannel import DeviceView
|
||||
from authentik.providers.oauth2.views.end_session import EndSessionView
|
||||
from authentik.providers.oauth2.views.introspection import TokenIntrospectionView
|
||||
from authentik.providers.oauth2.views.jwks import JWKSView
|
||||
from authentik.providers.oauth2.views.provider import ProviderInfoView
|
||||
@ -44,7 +45,7 @@ urlpatterns = [
|
||||
),
|
||||
path(
|
||||
"<slug:application_slug>/end-session/",
|
||||
RedirectView.as_view(pattern_name="authentik_core:if-session-end", query_string=True),
|
||||
EndSessionView.as_view(),
|
||||
name="end-session",
|
||||
),
|
||||
path("<slug:application_slug>/jwks/", JWKSView.as_view(), name="jwks"),
|
||||
|
45
authentik/providers/oauth2/views/end_session.py
Normal file
45
authentik/providers/oauth2/views/end_session.py
Normal file
@ -0,0 +1,45 @@
|
||||
"""oauth2 provider end_session Views"""
|
||||
|
||||
from django.http import Http404, HttpRequest, HttpResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from authentik.core.models import Application
|
||||
from authentik.flows.models import Flow, in_memory_stage
|
||||
from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, FlowPlanner
|
||||
from authentik.flows.stage import SessionEndStage
|
||||
from authentik.flows.views.executor import SESSION_KEY_PLAN
|
||||
from authentik.lib.utils.urls import redirect_with_qs
|
||||
from authentik.policies.views import PolicyAccessView
|
||||
|
||||
|
||||
class EndSessionView(PolicyAccessView):
|
||||
"""Redirect to application's provider's invalidation flow"""
|
||||
|
||||
flow: Flow
|
||||
|
||||
def resolve_provider_application(self):
|
||||
self.application = get_object_or_404(Application, slug=self.kwargs["application_slug"])
|
||||
self.provider = self.application.get_provider()
|
||||
if not self.provider:
|
||||
raise Http404
|
||||
self.flow = self.provider.invalidation_flow or self.request.brand.flow_invalidation
|
||||
if not self.flow:
|
||||
raise Http404
|
||||
|
||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||
"""Dispatch the flow planner for the invalidation flow"""
|
||||
planner = FlowPlanner(self.flow)
|
||||
planner.allow_empty_flows = True
|
||||
plan = planner.plan(
|
||||
request,
|
||||
{
|
||||
PLAN_CONTEXT_APPLICATION: self.application,
|
||||
},
|
||||
)
|
||||
plan.insert_stage(in_memory_stage(SessionEndStage))
|
||||
request.session[SESSION_KEY_PLAN] = plan
|
||||
return redirect_with_qs(
|
||||
"authentik_core:if-flow",
|
||||
self.request.GET,
|
||||
flow_slug=self.flow.slug,
|
||||
)
|
@ -46,10 +46,10 @@ class TokenIntrospectionParams:
|
||||
if not provider:
|
||||
raise TokenIntrospectionError
|
||||
|
||||
access_token = AccessToken.objects.filter(token=raw_token).first()
|
||||
access_token = AccessToken.objects.filter(token=raw_token, provider=provider).first()
|
||||
if access_token:
|
||||
return TokenIntrospectionParams(access_token, provider)
|
||||
refresh_token = RefreshToken.objects.filter(token=raw_token).first()
|
||||
refresh_token = RefreshToken.objects.filter(token=raw_token, provider=provider).first()
|
||||
if refresh_token:
|
||||
return TokenIntrospectionParams(refresh_token, provider)
|
||||
LOGGER.debug("Token does not exist", token=raw_token)
|
||||
|
@ -24,6 +24,7 @@ class ProxyProviderTests(APITestCase):
|
||||
"name": generate_id(),
|
||||
"mode": ProxyMode.PROXY,
|
||||
"authorization_flow": create_test_flow().pk.hex,
|
||||
"invalidation_flow": create_test_flow().pk.hex,
|
||||
"external_host": "http://localhost",
|
||||
"internal_host": "http://localhost",
|
||||
"basic_auth_enabled": True,
|
||||
@ -41,6 +42,7 @@ class ProxyProviderTests(APITestCase):
|
||||
"name": generate_id(),
|
||||
"mode": ProxyMode.PROXY,
|
||||
"authorization_flow": create_test_flow().pk.hex,
|
||||
"invalidation_flow": create_test_flow().pk.hex,
|
||||
"external_host": "http://localhost",
|
||||
"internal_host": "http://localhost",
|
||||
"basic_auth_enabled": True,
|
||||
@ -64,6 +66,7 @@ class ProxyProviderTests(APITestCase):
|
||||
"name": generate_id(),
|
||||
"mode": ProxyMode.PROXY,
|
||||
"authorization_flow": create_test_flow().pk.hex,
|
||||
"invalidation_flow": create_test_flow().pk.hex,
|
||||
"external_host": "http://localhost",
|
||||
},
|
||||
)
|
||||
@ -82,6 +85,7 @@ class ProxyProviderTests(APITestCase):
|
||||
"name": name,
|
||||
"mode": ProxyMode.PROXY,
|
||||
"authorization_flow": create_test_flow().pk.hex,
|
||||
"invalidation_flow": create_test_flow().pk.hex,
|
||||
"external_host": "http://localhost",
|
||||
"internal_host": "http://localhost",
|
||||
},
|
||||
@ -99,6 +103,7 @@ class ProxyProviderTests(APITestCase):
|
||||
"name": name,
|
||||
"mode": ProxyMode.PROXY,
|
||||
"authorization_flow": create_test_flow().pk.hex,
|
||||
"invalidation_flow": create_test_flow().pk.hex,
|
||||
"external_host": "http://localhost",
|
||||
"internal_host": "http://localhost",
|
||||
},
|
||||
@ -114,6 +119,7 @@ class ProxyProviderTests(APITestCase):
|
||||
"name": name,
|
||||
"mode": ProxyMode.PROXY,
|
||||
"authorization_flow": create_test_flow().pk.hex,
|
||||
"invalidation_flow": create_test_flow().pk.hex,
|
||||
"external_host": "http://localhost",
|
||||
"internal_host": "http://localhost",
|
||||
},
|
||||
|
@ -188,6 +188,9 @@ class SAMLProviderImportSerializer(PassiveSerializer):
|
||||
authorization_flow = PrimaryKeyRelatedField(
|
||||
queryset=Flow.objects.filter(designation=FlowDesignation.AUTHORIZATION),
|
||||
)
|
||||
invalidation_flow = PrimaryKeyRelatedField(
|
||||
queryset=Flow.objects.filter(designation=FlowDesignation.INVALIDATION),
|
||||
)
|
||||
file = FileField()
|
||||
|
||||
|
||||
@ -277,7 +280,9 @@ class SAMLProviderViewSet(UsedByMixin, ModelViewSet):
|
||||
try:
|
||||
metadata = ServiceProviderMetadataParser().parse(file.read().decode())
|
||||
metadata.to_provider(
|
||||
data.validated_data["name"], data.validated_data["authorization_flow"]
|
||||
data.validated_data["name"],
|
||||
data.validated_data["authorization_flow"],
|
||||
data.validated_data["invalidation_flow"],
|
||||
)
|
||||
except ValueError as exc: # pragma: no cover
|
||||
LOGGER.warning(str(exc))
|
||||
|
@ -49,12 +49,13 @@ class ServiceProviderMetadata:
|
||||
|
||||
signing_keypair: CertificateKeyPair | None = None
|
||||
|
||||
def to_provider(self, name: str, authorization_flow: Flow) -> SAMLProvider:
|
||||
def to_provider(
|
||||
self, name: str, authorization_flow: Flow, invalidation_flow: Flow
|
||||
) -> SAMLProvider:
|
||||
"""Create a SAMLProvider instance from the details. `name` is required,
|
||||
as depending on the metadata CertificateKeypairs might have to be created."""
|
||||
provider = SAMLProvider.objects.create(
|
||||
name=name,
|
||||
authorization_flow=authorization_flow,
|
||||
name=name, authorization_flow=authorization_flow, invalidation_flow=invalidation_flow
|
||||
)
|
||||
provider.issuer = self.entity_id
|
||||
provider.sp_binding = self.acs_binding
|
||||
|
@ -47,11 +47,12 @@ class TestSAMLProviderAPI(APITestCase):
|
||||
data={
|
||||
"name": generate_id(),
|
||||
"authorization_flow": create_test_flow().pk,
|
||||
"invalidation_flow": create_test_flow().pk,
|
||||
"acs_url": "http://localhost",
|
||||
"signing_kp": cert.pk,
|
||||
},
|
||||
)
|
||||
self.assertEqual(400, response.status_code)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertJSONEqual(
|
||||
response.content,
|
||||
{
|
||||
@ -68,12 +69,13 @@ class TestSAMLProviderAPI(APITestCase):
|
||||
data={
|
||||
"name": generate_id(),
|
||||
"authorization_flow": create_test_flow().pk,
|
||||
"invalidation_flow": create_test_flow().pk,
|
||||
"acs_url": "http://localhost",
|
||||
"signing_kp": cert.pk,
|
||||
"sign_assertion": True,
|
||||
},
|
||||
)
|
||||
self.assertEqual(201, response.status_code)
|
||||
self.assertEqual(response.status_code, 201)
|
||||
|
||||
def test_metadata(self):
|
||||
"""Test metadata export (normal)"""
|
||||
@ -131,6 +133,7 @@ class TestSAMLProviderAPI(APITestCase):
|
||||
"file": metadata,
|
||||
"name": generate_id(),
|
||||
"authorization_flow": create_test_flow(FlowDesignation.AUTHORIZATION).pk,
|
||||
"invalidation_flow": create_test_flow(FlowDesignation.INVALIDATION).pk,
|
||||
},
|
||||
format="multipart",
|
||||
)
|
||||
|
@ -82,7 +82,7 @@ class TestServiceProviderMetadataParser(TestCase):
|
||||
def test_simple(self):
|
||||
"""Test simple metadata without Signing"""
|
||||
metadata = ServiceProviderMetadataParser().parse(load_fixture("fixtures/simple.xml"))
|
||||
provider = metadata.to_provider("test", self.flow)
|
||||
provider = metadata.to_provider("test", self.flow, self.flow)
|
||||
self.assertEqual(provider.acs_url, "http://localhost:8080/saml/acs")
|
||||
self.assertEqual(provider.issuer, "http://localhost:8080/saml/metadata")
|
||||
self.assertEqual(provider.sp_binding, SAMLBindings.POST)
|
||||
@ -95,7 +95,7 @@ class TestServiceProviderMetadataParser(TestCase):
|
||||
"""Test Metadata with signing cert"""
|
||||
create_test_cert()
|
||||
metadata = ServiceProviderMetadataParser().parse(load_fixture("fixtures/cert.xml"))
|
||||
provider = metadata.to_provider("test", self.flow)
|
||||
provider = metadata.to_provider("test", self.flow, self.flow)
|
||||
self.assertEqual(provider.acs_url, "http://localhost:8080/apps/user_saml/saml/acs")
|
||||
self.assertEqual(provider.issuer, "http://localhost:8080/apps/user_saml/saml/metadata")
|
||||
self.assertEqual(provider.sp_binding, SAMLBindings.POST)
|
||||
|
@ -1,8 +1,8 @@
|
||||
"""SLO Views"""
|
||||
|
||||
from django.http import HttpRequest
|
||||
from django.http import Http404, HttpRequest
|
||||
from django.http.response import HttpResponse
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.clickjacking import xframe_options_sameorigin
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
@ -10,6 +10,11 @@ from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.core.models import Application
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.flows.models import Flow, in_memory_stage
|
||||
from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, FlowPlanner
|
||||
from authentik.flows.stage import SessionEndStage
|
||||
from authentik.flows.views.executor import SESSION_KEY_PLAN
|
||||
from authentik.lib.utils.urls import redirect_with_qs
|
||||
from authentik.lib.views import bad_request_message
|
||||
from authentik.policies.views import PolicyAccessView
|
||||
from authentik.providers.saml.exceptions import CannotHandleAssertion
|
||||
@ -28,11 +33,16 @@ class SAMLSLOView(PolicyAccessView):
|
||||
""" "SAML SLO Base View, which plans a flow and injects our final stage.
|
||||
Calls get/post handler."""
|
||||
|
||||
flow: Flow
|
||||
|
||||
def resolve_provider_application(self):
|
||||
self.application = get_object_or_404(Application, slug=self.kwargs["application_slug"])
|
||||
self.provider: SAMLProvider = get_object_or_404(
|
||||
SAMLProvider, pk=self.application.provider_id
|
||||
)
|
||||
self.flow = self.provider.invalidation_flow or self.request.brand.flow_invalidation
|
||||
if not self.flow:
|
||||
raise Http404
|
||||
|
||||
def check_saml_request(self) -> HttpRequest | None:
|
||||
"""Handler to verify the SAML Request. Must be implemented by a subclass"""
|
||||
@ -45,9 +55,20 @@ class SAMLSLOView(PolicyAccessView):
|
||||
method_response = self.check_saml_request()
|
||||
if method_response:
|
||||
return method_response
|
||||
return redirect(
|
||||
"authentik_core:if-session-end",
|
||||
application_slug=self.kwargs["application_slug"],
|
||||
planner = FlowPlanner(self.flow)
|
||||
planner.allow_empty_flows = True
|
||||
plan = planner.plan(
|
||||
request,
|
||||
{
|
||||
PLAN_CONTEXT_APPLICATION: self.application,
|
||||
},
|
||||
)
|
||||
plan.insert_stage(in_memory_stage(SessionEndStage))
|
||||
request.session[SESSION_KEY_PLAN] = plan
|
||||
return redirect_with_qs(
|
||||
"authentik_core:if-flow",
|
||||
self.request.GET,
|
||||
flow_slug=self.flow.slug,
|
||||
)
|
||||
|
||||
def post(self, request: HttpRequest, application_slug: str) -> HttpResponse:
|
||||
|
@ -26,6 +26,7 @@ class SCIMProviderSerializer(ProviderSerializer):
|
||||
"verbose_name_plural",
|
||||
"meta_model_name",
|
||||
"url",
|
||||
"verify_certificates",
|
||||
"token",
|
||||
"exclude_users_service_account",
|
||||
"filter_group",
|
||||
|
@ -42,6 +42,7 @@ class SCIMClient[TModel: "Model", TConnection: "Model", TSchema: "BaseModel"](
|
||||
def __init__(self, provider: SCIMProvider):
|
||||
super().__init__(provider)
|
||||
self._session = get_http_session()
|
||||
self._session.verify = provider.verify_certificates
|
||||
self.provider = provider
|
||||
# Remove trailing slashes as we assume the URL doesn't have any
|
||||
base_url = provider.url
|
||||
|
@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.0.9 on 2024-09-19 14:02
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_providers_scim", "0009_alter_scimmapping_options"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="scimprovider",
|
||||
name="verify_certificates",
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
]
|
@ -68,6 +68,7 @@ class SCIMProvider(OutgoingSyncProvider, BackchannelProvider):
|
||||
|
||||
url = models.TextField(help_text=_("Base URL to SCIM requests, usually ends in /v2"))
|
||||
token = models.TextField(help_text=_("Authentication token"))
|
||||
verify_certificates = models.BooleanField(default=True)
|
||||
|
||||
property_mappings_group = models.ManyToManyField(
|
||||
PropertyMapping,
|
||||
|
@ -22,7 +22,7 @@ def create_admin_group(user: User) -> Group:
|
||||
return group
|
||||
|
||||
|
||||
def create_recovery_token(user: User, expiry: datetime, generated_from: str) -> (Token, str):
|
||||
def create_recovery_token(user: User, expiry: datetime, generated_from: str) -> tuple[Token, str]:
|
||||
"""Create recovery token and associated link"""
|
||||
_now = now()
|
||||
token = Token.objects.create(
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
from collections.abc import Callable
|
||||
from hashlib import sha512
|
||||
from ipaddress import ip_address
|
||||
from time import perf_counter, time
|
||||
from typing import Any
|
||||
|
||||
@ -174,6 +175,7 @@ class ClientIPMiddleware:
|
||||
|
||||
def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]):
|
||||
self.get_response = get_response
|
||||
self.logger = get_logger().bind()
|
||||
|
||||
def _get_client_ip_from_meta(self, meta: dict[str, Any]) -> str:
|
||||
"""Attempt to get the client's IP by checking common HTTP Headers.
|
||||
@ -185,11 +187,16 @@ class ClientIPMiddleware:
|
||||
"HTTP_X_FORWARDED_FOR",
|
||||
"REMOTE_ADDR",
|
||||
)
|
||||
for _header in headers:
|
||||
if _header in meta:
|
||||
ips: list[str] = meta.get(_header).split(",")
|
||||
return ips[0].strip()
|
||||
return self.default_ip
|
||||
try:
|
||||
for _header in headers:
|
||||
if _header in meta:
|
||||
ips: list[str] = meta.get(_header).split(",")
|
||||
# Ensure the IP parses as a valid IP
|
||||
return str(ip_address(ips[0].strip()))
|
||||
return self.default_ip
|
||||
except ValueError as exc:
|
||||
self.logger.debug("Invalid remote IP", exc=exc)
|
||||
return self.default_ip
|
||||
|
||||
# FIXME: this should probably not be in `root` but rather in a middleware in `outposts`
|
||||
# but for now it's fine
|
||||
@ -226,7 +233,11 @@ class ClientIPMiddleware:
|
||||
Scope.get_isolation_scope().set_user(sentry_user)
|
||||
# Set the outpost service account on the request
|
||||
setattr(request, self.request_attr_outpost_user, user)
|
||||
return delegated_ip
|
||||
try:
|
||||
return str(ip_address(delegated_ip))
|
||||
except ValueError as exc:
|
||||
self.logger.debug("Invalid remote IP from Outpost", exc=exc)
|
||||
return None
|
||||
|
||||
def _get_client_ip(self, request: HttpRequest | None) -> str:
|
||||
"""Attempt to get the client's IP by checking common HTTP Headers.
|
||||
|
@ -3,6 +3,7 @@
|
||||
from typing import Any
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from drf_spectacular.utils import extend_schema, inline_serializer
|
||||
from guardian.shortcuts import get_objects_for_user
|
||||
from rest_framework.decorators import action
|
||||
@ -39,9 +40,8 @@ class LDAPSourceSerializer(SourceSerializer):
|
||||
"""Get cached source connectivity"""
|
||||
return cache.get(CACHE_KEY_STATUS + source.slug, None)
|
||||
|
||||
def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:
|
||||
def validate_sync_users_password(self, sync_users_password: bool) -> bool:
|
||||
"""Check that only a single source has password_sync on"""
|
||||
sync_users_password = attrs.get("sync_users_password", True)
|
||||
if sync_users_password:
|
||||
sources = LDAPSource.objects.filter(sync_users_password=True)
|
||||
if self.instance:
|
||||
@ -49,11 +49,31 @@ class LDAPSourceSerializer(SourceSerializer):
|
||||
if sources.exists():
|
||||
raise ValidationError(
|
||||
{
|
||||
"sync_users_password": (
|
||||
"sync_users_password": _(
|
||||
"Only a single LDAP Source with password synchronization is allowed"
|
||||
)
|
||||
}
|
||||
)
|
||||
return sync_users_password
|
||||
|
||||
def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Validate property mappings with sync_ flags"""
|
||||
types = ["user", "group"]
|
||||
for type in types:
|
||||
toggle_value = attrs.get(f"sync_{type}s", False)
|
||||
mappings_field = f"{type}_property_mappings"
|
||||
mappings_value = attrs.get(mappings_field, [])
|
||||
if toggle_value and len(mappings_value) == 0:
|
||||
raise ValidationError(
|
||||
{
|
||||
mappings_field: _(
|
||||
(
|
||||
"When 'Sync {type}s' is enabled, '{type}s property "
|
||||
"mappings' cannot be empty."
|
||||
).format(type=type)
|
||||
)
|
||||
}
|
||||
)
|
||||
return super().validate(attrs)
|
||||
|
||||
class Meta:
|
||||
@ -166,11 +186,12 @@ class LDAPSourceViewSet(UsedByMixin, ModelViewSet):
|
||||
for sync_class in SYNC_CLASSES:
|
||||
class_name = sync_class.name()
|
||||
all_objects.setdefault(class_name, [])
|
||||
for obj in sync_class(source).get_objects(size_limit=10):
|
||||
obj: dict
|
||||
obj.pop("raw_attributes", None)
|
||||
obj.pop("raw_dn", None)
|
||||
all_objects[class_name].append(obj)
|
||||
for page in sync_class(source).get_objects(size_limit=10):
|
||||
for obj in page:
|
||||
obj: dict
|
||||
obj.pop("raw_attributes", None)
|
||||
obj.pop("raw_dn", None)
|
||||
all_objects[class_name].append(obj)
|
||||
return Response(data=all_objects)
|
||||
|
||||
|
||||
|
@ -26,17 +26,16 @@ def sync_ldap_source_on_save(sender, instance: LDAPSource, **_):
|
||||
"""Ensure that source is synced on save (if enabled)"""
|
||||
if not instance.enabled:
|
||||
return
|
||||
ldap_connectivity_check.delay(instance.pk)
|
||||
# Don't sync sources when they don't have any property mappings. This will only happen if:
|
||||
# - the user forgets to set them or
|
||||
# - the source is newly created, this is the first save event
|
||||
# and the mappings are created with an m2m event
|
||||
if (
|
||||
not instance.user_property_mappings.exists()
|
||||
or not instance.group_property_mappings.exists()
|
||||
):
|
||||
if instance.sync_users and not instance.user_property_mappings.exists():
|
||||
return
|
||||
if instance.sync_groups and not instance.group_property_mappings.exists():
|
||||
return
|
||||
ldap_sync_single.delay(instance.pk)
|
||||
ldap_connectivity_check.delay(instance.pk)
|
||||
|
||||
|
||||
@receiver(password_validate)
|
||||
|
4
authentik/sources/ldap/sync/vendor/ms_ad.py
vendored
4
authentik/sources/ldap/sync/vendor/ms_ad.py
vendored
@ -78,7 +78,9 @@ class MicrosoftActiveDirectory(BaseLDAPSynchronizer):
|
||||
# /useraccountcontrol-manipulate-account-properties
|
||||
uac_bit = attributes.get("userAccountControl", 512)
|
||||
uac = UserAccountControl(uac_bit)
|
||||
is_active = UserAccountControl.ACCOUNTDISABLE not in uac
|
||||
is_active = (
|
||||
UserAccountControl.ACCOUNTDISABLE not in uac and UserAccountControl.LOCKOUT not in uac
|
||||
)
|
||||
if is_active != user.is_active:
|
||||
user.is_active = is_active
|
||||
user.save()
|
||||
|
@ -50,3 +50,35 @@ class LDAPAPITests(APITestCase):
|
||||
}
|
||||
)
|
||||
self.assertFalse(serializer.is_valid())
|
||||
|
||||
def test_sync_users_mapping_empty(self):
|
||||
"""Check that when sync_users is enabled, property mappings must be set"""
|
||||
serializer = LDAPSourceSerializer(
|
||||
data={
|
||||
"name": "foo",
|
||||
"slug": " foo",
|
||||
"server_uri": "ldaps://1.2.3.4",
|
||||
"bind_cn": "",
|
||||
"bind_password": LDAP_PASSWORD,
|
||||
"base_dn": "dc=foo",
|
||||
"sync_users": True,
|
||||
"user_property_mappings": [],
|
||||
}
|
||||
)
|
||||
self.assertFalse(serializer.is_valid())
|
||||
|
||||
def test_sync_groups_mapping_empty(self):
|
||||
"""Check that when sync_groups is enabled, property mappings must be set"""
|
||||
serializer = LDAPSourceSerializer(
|
||||
data={
|
||||
"name": "foo",
|
||||
"slug": " foo",
|
||||
"server_uri": "ldaps://1.2.3.4",
|
||||
"bind_cn": "",
|
||||
"bind_password": LDAP_PASSWORD,
|
||||
"base_dn": "dc=foo",
|
||||
"sync_groups": True,
|
||||
"group_property_mappings": [],
|
||||
}
|
||||
)
|
||||
self.assertFalse(serializer.is_valid())
|
||||
|
@ -15,12 +15,13 @@ from authentik.sources.oauth.models import OAuthSource
|
||||
from authentik.sources.oauth.types.registry import SourceType, registry
|
||||
from authentik.sources.oauth.views.callback import OAuthCallback
|
||||
from authentik.sources.oauth.views.redirect import OAuthRedirect
|
||||
from authentik.stages.identification.stage import LoginChallengeMixin
|
||||
|
||||
LOGGER = get_logger()
|
||||
APPLE_CLIENT_ID_PARTS = 3
|
||||
|
||||
|
||||
class AppleLoginChallenge(Challenge):
|
||||
class AppleLoginChallenge(LoginChallengeMixin, Challenge):
|
||||
"""Special challenge for apple-native authentication flow, which happens on the client."""
|
||||
|
||||
client_id = CharField()
|
||||
|
@ -19,9 +19,10 @@ from authentik.core.models import (
|
||||
from authentik.core.types import UILoginButton, UserSettingSerializer
|
||||
from authentik.flows.challenge import Challenge, ChallengeResponse
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.stages.identification.stage import LoginChallengeMixin
|
||||
|
||||
|
||||
class PlexAuthenticationChallenge(Challenge):
|
||||
class PlexAuthenticationChallenge(LoginChallengeMixin, Challenge):
|
||||
"""Challenge shown to the user in identification stage"""
|
||||
|
||||
client_id = CharField()
|
||||
|
@ -0,0 +1,26 @@
|
||||
# Generated by Django 5.0.9 on 2024-10-10 15:45
|
||||
|
||||
from django.db import migrations
|
||||
from django.apps.registry import Apps
|
||||
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
|
||||
def fix_X509SubjectName(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
SAMLSource = apps.get_model("authentik_sources_saml", "SAMLSource")
|
||||
SAMLSource.objects.using(db_alias).filter(
|
||||
name_id_policy="urn:oasis:names:tc:SAML:2.0:nameid-format:X509SubjectName"
|
||||
).update(name_id_policy="urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName")
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_sources_saml", "0016_samlsource_encryption_kp"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(fix_X509SubjectName),
|
||||
]
|
@ -19,7 +19,7 @@ NS_MAP = {
|
||||
SAML_NAME_ID_FORMAT_EMAIL = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
||||
SAML_NAME_ID_FORMAT_PERSISTENT = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
|
||||
SAML_NAME_ID_FORMAT_UNSPECIFIED = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
|
||||
SAML_NAME_ID_FORMAT_X509 = "urn:oasis:names:tc:SAML:2.0:nameid-format:X509SubjectName"
|
||||
SAML_NAME_ID_FORMAT_X509 = "urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName"
|
||||
SAML_NAME_ID_FORMAT_WINDOWS = "urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName"
|
||||
SAML_NAME_ID_FORMAT_TRANSIENT = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
"""SAML Service Provider Metadata Processor"""
|
||||
|
||||
from collections.abc import Iterator
|
||||
from typing import Optional
|
||||
|
||||
from django.http import HttpRequest
|
||||
@ -13,11 +12,6 @@ from authentik.sources.saml.processors.constants import (
|
||||
NS_SAML_METADATA,
|
||||
NS_SIGNATURE,
|
||||
SAML_BINDING_POST,
|
||||
SAML_NAME_ID_FORMAT_EMAIL,
|
||||
SAML_NAME_ID_FORMAT_PERSISTENT,
|
||||
SAML_NAME_ID_FORMAT_TRANSIENT,
|
||||
SAML_NAME_ID_FORMAT_WINDOWS,
|
||||
SAML_NAME_ID_FORMAT_X509,
|
||||
)
|
||||
|
||||
|
||||
@ -60,19 +54,10 @@ class MetadataProcessor:
|
||||
return key_descriptor
|
||||
return None
|
||||
|
||||
def get_name_id_formats(self) -> Iterator[Element]:
|
||||
"""Get compatible NameID Formats"""
|
||||
formats = [
|
||||
SAML_NAME_ID_FORMAT_EMAIL,
|
||||
SAML_NAME_ID_FORMAT_PERSISTENT,
|
||||
SAML_NAME_ID_FORMAT_X509,
|
||||
SAML_NAME_ID_FORMAT_WINDOWS,
|
||||
SAML_NAME_ID_FORMAT_TRANSIENT,
|
||||
]
|
||||
for name_id_format in formats:
|
||||
element = Element(f"{{{NS_SAML_METADATA}}}NameIDFormat")
|
||||
element.text = name_id_format
|
||||
yield element
|
||||
def get_name_id_format(self) -> Element:
|
||||
element = Element(f"{{{NS_SAML_METADATA}}}NameIDFormat")
|
||||
element.text = self.source.name_id_policy
|
||||
return element
|
||||
|
||||
def build_entity_descriptor(self) -> str:
|
||||
"""Build full EntityDescriptor"""
|
||||
@ -92,8 +77,7 @@ class MetadataProcessor:
|
||||
if encryption_descriptor is not None:
|
||||
sp_sso_descriptor.append(encryption_descriptor)
|
||||
|
||||
for name_id_format in self.get_name_id_formats():
|
||||
sp_sso_descriptor.append(name_id_format)
|
||||
sp_sso_descriptor.append(self.get_name_id_format())
|
||||
|
||||
assertion_consumer_service = SubElement(
|
||||
sp_sso_descriptor, f"{{{NS_SAML_METADATA}}}AssertionConsumerService"
|
||||
|
File diff suppressed because one or more lines are too long
@ -96,8 +96,9 @@ class ConsentStageView(ChallengeStageView):
|
||||
if PLAN_CONTEXT_PENDING_USER in self.executor.plan.context:
|
||||
user = self.executor.plan.context[PLAN_CONTEXT_PENDING_USER]
|
||||
|
||||
# Remove expired consents to prevent database unique constraints errors
|
||||
consent: UserConsent | None = UserConsent.filter_not_expired(
|
||||
user=user, application=application
|
||||
delete_expired=True, user=user, application=application
|
||||
).first()
|
||||
self.executor.plan.context[PLAN_CONTEXT_CONSENT] = consent
|
||||
|
||||
|
@ -26,23 +26,31 @@ from authentik.flows.models import FlowDesignation
|
||||
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
|
||||
from authentik.flows.stage import PLAN_CONTEXT_PENDING_USER_IDENTIFIER, ChallengeStageView
|
||||
from authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE, SESSION_KEY_GET
|
||||
from authentik.lib.utils.reflection import all_subclasses
|
||||
from authentik.lib.utils.urls import reverse_with_qs
|
||||
from authentik.root.middleware import ClientIPMiddleware
|
||||
from authentik.sources.oauth.types.apple import AppleLoginChallenge
|
||||
from authentik.sources.plex.models import PlexAuthenticationChallenge
|
||||
from authentik.stages.identification.models import IdentificationStage
|
||||
from authentik.stages.identification.signals import identification_failed
|
||||
from authentik.stages.password.stage import authenticate
|
||||
|
||||
|
||||
class LoginChallengeMixin:
|
||||
"""Base login challenge for Identification stage"""
|
||||
|
||||
|
||||
def get_login_serializers():
|
||||
mapping = {
|
||||
RedirectChallenge().fields["component"].default: RedirectChallenge,
|
||||
}
|
||||
for cls in all_subclasses(LoginChallengeMixin):
|
||||
mapping[cls().fields["component"].default] = cls
|
||||
return mapping
|
||||
|
||||
|
||||
@extend_schema_field(
|
||||
PolymorphicProxySerializer(
|
||||
component_name="LoginChallengeTypes",
|
||||
serializers={
|
||||
RedirectChallenge().fields["component"].default: RedirectChallenge,
|
||||
PlexAuthenticationChallenge().fields["component"].default: PlexAuthenticationChallenge,
|
||||
AppleLoginChallenge().fields["component"].default: AppleLoginChallenge,
|
||||
},
|
||||
serializers=get_login_serializers,
|
||||
resource_type_field_name="component",
|
||||
)
|
||||
)
|
||||
@ -96,7 +104,7 @@ class IdentificationChallengeResponse(ChallengeResponse):
|
||||
if not pre_user:
|
||||
with start_span(
|
||||
op="authentik.stages.identification.validate_invalid_wait",
|
||||
description="Sleep random time on invalid user identifier",
|
||||
name="Sleep random time on invalid user identifier",
|
||||
):
|
||||
# Sleep a random time (between 90 and 210ms) to "prevent" user enumeration attacks
|
||||
sleep(0.030 * SystemRandom().randint(3, 7))
|
||||
@ -138,7 +146,7 @@ class IdentificationChallengeResponse(ChallengeResponse):
|
||||
try:
|
||||
with start_span(
|
||||
op="authentik.stages.identification.authenticate",
|
||||
description="User authenticate call (combo stage)",
|
||||
name="User authenticate call (combo stage)",
|
||||
):
|
||||
user = authenticate(
|
||||
self.stage.request,
|
||||
|
@ -49,7 +49,7 @@ def authenticate(
|
||||
LOGGER.debug("Attempting authentication...", backend=backend_path)
|
||||
with start_span(
|
||||
op="authentik.stages.password.authenticate",
|
||||
description=backend_path,
|
||||
name=backend_path,
|
||||
):
|
||||
user = backend.authenticate(request, **credentials)
|
||||
if user is None:
|
||||
|
@ -38,7 +38,7 @@ LOGGER = get_logger()
|
||||
class FieldTypes(models.TextChoices):
|
||||
"""Field types an Prompt can be"""
|
||||
|
||||
# update website/docs/flow/stages/prompt/index.md
|
||||
# update website/docs/add-secure-apps/flows-stages/stages/prompt/index.md
|
||||
|
||||
# Simple text field
|
||||
TEXT = "text", _("Text: Simple Text input")
|
||||
|
@ -82,3 +82,5 @@ entries:
|
||||
order: 10
|
||||
target: !KeyOf default-authentication-flow-password-binding
|
||||
policy: !KeyOf default-authentication-flow-password-optional
|
||||
attrs:
|
||||
failure_result: true
|
||||
|
13
blueprints/default/flow-default-provider-invalidation.yaml
Normal file
13
blueprints/default/flow-default-provider-invalidation.yaml
Normal file
@ -0,0 +1,13 @@
|
||||
version: 1
|
||||
metadata:
|
||||
name: Default - Provider invalidation flow
|
||||
entries:
|
||||
- attrs:
|
||||
designation: invalidation
|
||||
name: Logged out of application
|
||||
title: You've logged out of %(app)s.
|
||||
authentication: none
|
||||
identifiers:
|
||||
slug: default-provider-invalidation-flow
|
||||
model: authentik_flows.flow
|
||||
id: flow
|
@ -2,7 +2,7 @@
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "https://goauthentik.io/blueprints/schema.json",
|
||||
"type": "object",
|
||||
"title": "authentik 2024.8.2 Blueprint schema",
|
||||
"title": "authentik 2024.8.3 Blueprint schema",
|
||||
"required": [
|
||||
"version",
|
||||
"entries"
|
||||
@ -5117,6 +5117,12 @@
|
||||
"title": "Authorization flow",
|
||||
"description": "Flow used when authorizing this provider."
|
||||
},
|
||||
"invalidation_flow": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"title": "Invalidation flow",
|
||||
"description": "Flow used ending the session from a provider."
|
||||
},
|
||||
"property_mappings": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -5287,6 +5293,12 @@
|
||||
"title": "Authorization flow",
|
||||
"description": "Flow used when authorizing this provider."
|
||||
},
|
||||
"invalidation_flow": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"title": "Invalidation flow",
|
||||
"description": "Flow used ending the session from a provider."
|
||||
},
|
||||
"property_mappings": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -5428,6 +5440,12 @@
|
||||
"title": "Authorization flow",
|
||||
"description": "Flow used when authorizing this provider."
|
||||
},
|
||||
"invalidation_flow": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"title": "Invalidation flow",
|
||||
"description": "Flow used ending the session from a provider."
|
||||
},
|
||||
"property_mappings": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -5563,6 +5581,12 @@
|
||||
"title": "Authorization flow",
|
||||
"description": "Flow used when authorizing this provider."
|
||||
},
|
||||
"invalidation_flow": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"title": "Invalidation flow",
|
||||
"description": "Flow used ending the session from a provider."
|
||||
},
|
||||
"property_mappings": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -5688,6 +5712,12 @@
|
||||
"title": "Authorization flow",
|
||||
"description": "Flow used when authorizing this provider."
|
||||
},
|
||||
"invalidation_flow": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"title": "Invalidation flow",
|
||||
"description": "Flow used ending the session from a provider."
|
||||
},
|
||||
"property_mappings": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -5926,6 +5956,10 @@
|
||||
"title": "Url",
|
||||
"description": "Base URL to SCIM requests, usually ends in /v2"
|
||||
},
|
||||
"verify_certificates": {
|
||||
"type": "boolean",
|
||||
"title": "Verify certificates"
|
||||
},
|
||||
"token": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
@ -7567,7 +7601,7 @@
|
||||
"enum": [
|
||||
"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
|
||||
"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
|
||||
"urn:oasis:names:tc:SAML:2.0:nameid-format:X509SubjectName",
|
||||
"urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName",
|
||||
"urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName",
|
||||
"urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
|
||||
],
|
||||
@ -12761,6 +12795,12 @@
|
||||
"title": "Authorization flow",
|
||||
"description": "Flow used when authorizing this provider."
|
||||
},
|
||||
"invalidation_flow": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"title": "Invalidation flow",
|
||||
"description": "Flow used ending the session from a provider."
|
||||
},
|
||||
"property_mappings": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
@ -31,7 +31,7 @@ services:
|
||||
volumes:
|
||||
- redis:/data
|
||||
server:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.8.2}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.8.3}
|
||||
restart: unless-stopped
|
||||
command: server
|
||||
environment:
|
||||
@ -52,7 +52,7 @@ services:
|
||||
- postgresql
|
||||
- redis
|
||||
worker:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.8.2}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.8.3}
|
||||
restart: unless-stopped
|
||||
command: worker
|
||||
environment:
|
||||
|
4
go.mod
4
go.mod
@ -21,7 +21,7 @@ require (
|
||||
github.com/jellydator/ttlcache/v3 v3.3.0
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
|
||||
github.com/pires/go-proxyproto v0.7.0
|
||||
github.com/pires/go-proxyproto v0.8.0
|
||||
github.com/prometheus/client_golang v1.20.4
|
||||
github.com/redis/go-redis/v9 v9.6.1
|
||||
github.com/sethvargo/go-envconfig v1.1.0
|
||||
@ -29,7 +29,7 @@ require (
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/wwt/guac v1.3.2
|
||||
goauthentik.io/api/v3 v3.2024082.1
|
||||
goauthentik.io/api/v3 v3.2024083.5
|
||||
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
||||
golang.org/x/oauth2 v0.23.0
|
||||
golang.org/x/sync v0.8.0
|
||||
|
8
go.sum
8
go.sum
@ -233,8 +233,8 @@ github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+
|
||||
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
||||
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
||||
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
||||
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
||||
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
||||
github.com/pires/go-proxyproto v0.8.0 h1:5unRmEAPbHXHuLjDg01CxJWf91cw3lKHc/0xzKpXEe0=
|
||||
github.com/pires/go-proxyproto v0.8.0/go.mod h1:iknsfgnH8EkjrMeMyvfKByp9TiBZCKZM0jx2xmKqnVY=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
@ -299,8 +299,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
|
||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
goauthentik.io/api/v3 v3.2024082.1 h1:V/3tq3rGK8Fse6xqnVQ8epzzytjXRI93y+jNHen2zMQ=
|
||||
goauthentik.io/api/v3 v3.2024082.1/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
||||
goauthentik.io/api/v3 v3.2024083.5 h1:qXJ4VRPP8ZBvCFrOH252JhEbURbu4MK5b0KZBGq4z1w=
|
||||
goauthentik.io/api/v3 v3.2024083.5/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=
|
||||
|
@ -29,4 +29,4 @@ func UserAgent() string {
|
||||
return fmt.Sprintf("authentik@%s", FullVersion())
|
||||
}
|
||||
|
||||
const VERSION = "2024.8.2"
|
||||
const VERSION = "2024.8.3"
|
||||
|
@ -8,11 +8,17 @@ import (
|
||||
)
|
||||
|
||||
func (db *DirectBinder) Unbind(username string, req *bind.Request) (ldap.LDAPResultCode, error) {
|
||||
flowSlug := db.si.GetInvalidationFlowSlug()
|
||||
if flowSlug == nil {
|
||||
req.Log().Debug("Provider does not have a logout flow configured")
|
||||
db.si.SetFlags(req.BindDN, nil)
|
||||
return ldap.LDAPResultSuccess, nil
|
||||
}
|
||||
flags := db.si.GetFlags(req.BindDN)
|
||||
if flags == nil || flags.Session == nil {
|
||||
return ldap.LDAPResultSuccess, nil
|
||||
}
|
||||
fe := flow.NewFlowExecutor(req.Context(), db.si.GetInvalidationFlowSlug(), db.si.GetAPIClient().GetConfig(), log.Fields{
|
||||
fe := flow.NewFlowExecutor(req.Context(), *flowSlug, db.si.GetAPIClient().GetConfig(), log.Fields{
|
||||
"boundDN": req.BindDN,
|
||||
"client": req.RemoteAddr(),
|
||||
"requestId": req.ID(),
|
||||
@ -22,7 +28,7 @@ func (db *DirectBinder) Unbind(username string, req *bind.Request) (ldap.LDAPRes
|
||||
fe.Params.Add("goauthentik.io/outpost/ldap", "true")
|
||||
_, err := fe.Execute()
|
||||
if err != nil {
|
||||
db.log.WithError(err).Warning("failed to logout user")
|
||||
req.Log().WithError(err).Warning("failed to logout user")
|
||||
}
|
||||
db.si.SetFlags(req.BindDN, nil)
|
||||
return ldap.LDAPResultSuccess, nil
|
||||
|
@ -26,7 +26,7 @@ type ProviderInstance struct {
|
||||
|
||||
appSlug string
|
||||
authenticationFlowSlug string
|
||||
invalidationFlowSlug string
|
||||
invalidationFlowSlug *string
|
||||
s *LDAPServer
|
||||
log *log.Entry
|
||||
|
||||
@ -99,7 +99,7 @@ func (pi *ProviderInstance) GetAuthenticationFlowSlug() string {
|
||||
return pi.authenticationFlowSlug
|
||||
}
|
||||
|
||||
func (pi *ProviderInstance) GetInvalidationFlowSlug() string {
|
||||
func (pi *ProviderInstance) GetInvalidationFlowSlug() *string {
|
||||
return pi.invalidationFlowSlug
|
||||
}
|
||||
|
||||
|
@ -29,16 +29,6 @@ func (ls *LDAPServer) getCurrentProvider(pk int32) *ProviderInstance {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ls *LDAPServer) getInvalidationFlow() string {
|
||||
req, _, err := ls.ac.Client.CoreApi.CoreBrandsCurrentRetrieve(context.Background()).Execute()
|
||||
if err != nil {
|
||||
ls.log.WithError(err).Warning("failed to fetch brand config")
|
||||
return ""
|
||||
}
|
||||
flow := req.GetFlowInvalidation()
|
||||
return flow
|
||||
}
|
||||
|
||||
func (ls *LDAPServer) Refresh() error {
|
||||
apiProviders, err := ak.Paginator(ls.ac.Client.OutpostsApi.OutpostsLdapList(context.Background()), ak.PaginatorOptions{
|
||||
PageSize: 100,
|
||||
@ -51,7 +41,6 @@ func (ls *LDAPServer) Refresh() error {
|
||||
return errors.New("no ldap provider defined")
|
||||
}
|
||||
providers := make([]*ProviderInstance, len(apiProviders))
|
||||
invalidationFlow := ls.getInvalidationFlow()
|
||||
for idx, provider := range apiProviders {
|
||||
userDN := strings.ToLower(fmt.Sprintf("ou=%s,%s", constants.OUUsers, *provider.BaseDn))
|
||||
groupDN := strings.ToLower(fmt.Sprintf("ou=%s,%s", constants.OUGroups, *provider.BaseDn))
|
||||
@ -75,7 +64,7 @@ func (ls *LDAPServer) Refresh() error {
|
||||
UserDN: userDN,
|
||||
appSlug: provider.ApplicationSlug,
|
||||
authenticationFlowSlug: provider.BindFlowSlug,
|
||||
invalidationFlowSlug: invalidationFlow,
|
||||
invalidationFlowSlug: provider.UnbindFlowSlug.Get(),
|
||||
boundUsersMutex: usersMutex,
|
||||
boundUsers: users,
|
||||
s: ls,
|
||||
|
@ -12,7 +12,7 @@ type LDAPServerInstance interface {
|
||||
GetOutpostName() string
|
||||
|
||||
GetAuthenticationFlowSlug() string
|
||||
GetInvalidationFlowSlug() string
|
||||
GetInvalidationFlowSlug() *string
|
||||
GetAppSlug() string
|
||||
GetProviderID() int32
|
||||
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
sentryhttp "github.com/getsentry/sentry-go/http"
|
||||
@ -70,12 +71,20 @@ func NewProxyServer(ac *ak.APIController) *ProxyServer {
|
||||
}
|
||||
|
||||
func (ps *ProxyServer) HandleHost(rw http.ResponseWriter, r *http.Request) bool {
|
||||
// Always handle requests for outpost paths that should answer regardless of hostname
|
||||
if strings.HasPrefix(r.URL.Path, "/outpost.goauthentik.io/ping") ||
|
||||
strings.HasPrefix(r.URL.Path, "/outpost.goauthentik.io/static") {
|
||||
ps.mux.ServeHTTP(rw, r)
|
||||
return true
|
||||
}
|
||||
// lookup app by hostname
|
||||
a, _ := ps.lookupApp(r)
|
||||
if a == nil {
|
||||
return false
|
||||
}
|
||||
// check if the app should handle this URL, or is setup in proxy mode
|
||||
if a.ShouldHandleURL(r) || a.Mode() == api.PROXYMODE_PROXY {
|
||||
a.ServeHTTP(rw, r)
|
||||
ps.mux.ServeHTTP(rw, r)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-09-08 00:09+0000\n"
|
||||
"POT-Creation-Date: 2024-10-12 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"
|
||||
@ -36,8 +36,7 @@ msgid "Blueprint file does not exist"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/blueprints/api.py
|
||||
#, python-brace-format
|
||||
msgid "Failed to validate blueprint: {logs}"
|
||||
msgid "Failed to validate blueprint"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/blueprints/api.py
|
||||
@ -1849,6 +1848,10 @@ msgstr ""
|
||||
msgid "Used recovery-link to authenticate."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/api.py
|
||||
msgid "Only a single LDAP Source with password synchronization is allowed"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "Server URI"
|
||||
msgstr ""
|
||||
|
@ -19,7 +19,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-09-08 00:09+0000\n"
|
||||
"POT-Creation-Date: 2024-10-12 00:08+0000\n"
|
||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||
"Last-Translator: Marc Schmitt, 2024\n"
|
||||
"Language-Team: French (https://app.transifex.com/authentik/teams/119923/fr/)\n"
|
||||
@ -47,9 +47,8 @@ msgid "Blueprint file does not exist"
|
||||
msgstr "Le fichier de plan n'existe pas"
|
||||
|
||||
#: authentik/blueprints/api.py
|
||||
#, python-brace-format
|
||||
msgid "Failed to validate blueprint: {logs}"
|
||||
msgstr "Échec de validation du plan : {logs}"
|
||||
msgid "Failed to validate blueprint"
|
||||
msgstr "Échec de validation du plan"
|
||||
|
||||
#: authentik/blueprints/api.py
|
||||
msgid "Either path or content must be set."
|
||||
@ -2064,6 +2063,11 @@ msgstr ""
|
||||
msgid "Used recovery-link to authenticate."
|
||||
msgstr "Utiliser un lien de récupération pour se connecter."
|
||||
|
||||
#: authentik/sources/ldap/api.py
|
||||
msgid "Only a single LDAP Source with password synchronization is allowed"
|
||||
msgstr ""
|
||||
"Une seule source LDAP avec synchronisation de mot de passe est autorisée"
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "Server URI"
|
||||
msgstr "URI du serveur"
|
||||
|
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-09-08 00:09+0000\n"
|
||||
"POT-Creation-Date: 2024-10-12 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"
|
||||
@ -43,9 +43,8 @@ msgid "Blueprint file does not exist"
|
||||
msgstr "蓝图文件不存在"
|
||||
|
||||
#: authentik/blueprints/api.py
|
||||
#, python-brace-format
|
||||
msgid "Failed to validate blueprint: {logs}"
|
||||
msgstr "验证蓝图失败:{logs}"
|
||||
msgid "Failed to validate blueprint"
|
||||
msgstr "验证蓝图失败"
|
||||
|
||||
#: authentik/blueprints/api.py
|
||||
msgid "Either path or content must be set."
|
||||
@ -1882,6 +1881,10 @@ msgstr "创建一个密钥,可用于恢复对 authentik 的访问权限。"
|
||||
msgid "Used recovery-link to authenticate."
|
||||
msgstr "已使用恢复链接进行身份验证。"
|
||||
|
||||
#: authentik/sources/ldap/api.py
|
||||
msgid "Only a single LDAP Source with password synchronization is allowed"
|
||||
msgstr "仅允许使用密码同步的单个 LDAP 源"
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "Server URI"
|
||||
msgstr "服务器 URI"
|
||||
|
Binary file not shown.
@ -14,7 +14,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-09-08 00:09+0000\n"
|
||||
"POT-Creation-Date: 2024-10-12 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"
|
||||
@ -42,9 +42,8 @@ msgid "Blueprint file does not exist"
|
||||
msgstr "蓝图文件不存在"
|
||||
|
||||
#: authentik/blueprints/api.py
|
||||
#, python-brace-format
|
||||
msgid "Failed to validate blueprint: {logs}"
|
||||
msgstr "验证蓝图失败:{logs}"
|
||||
msgid "Failed to validate blueprint"
|
||||
msgstr "验证蓝图失败"
|
||||
|
||||
#: authentik/blueprints/api.py
|
||||
msgid "Either path or content must be set."
|
||||
@ -1881,6 +1880,10 @@ msgstr "创建一个密钥,可用于恢复对 authentik 的访问权限。"
|
||||
msgid "Used recovery-link to authenticate."
|
||||
msgstr "已使用恢复链接进行身份验证。"
|
||||
|
||||
#: authentik/sources/ldap/api.py
|
||||
msgid "Only a single LDAP Source with password synchronization is allowed"
|
||||
msgstr "仅允许使用密码同步的单个 LDAP 源"
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "Server URI"
|
||||
msgstr "服务器 URI"
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@goauthentik/authentik",
|
||||
"version": "2024.8.2",
|
||||
"version": "2024.8.3",
|
||||
"private": true
|
||||
}
|
||||
|
398
poetry.lock
generated
398
poetry.lock
generated
@ -441,33 +441,33 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "24.8.0"
|
||||
version = "24.10.0"
|
||||
description = "The uncompromising code formatter."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"},
|
||||
{file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"},
|
||||
{file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"},
|
||||
{file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"},
|
||||
{file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"},
|
||||
{file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"},
|
||||
{file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"},
|
||||
{file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"},
|
||||
{file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"},
|
||||
{file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"},
|
||||
{file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"},
|
||||
{file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"},
|
||||
{file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"},
|
||||
{file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"},
|
||||
{file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"},
|
||||
{file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"},
|
||||
{file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"},
|
||||
{file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"},
|
||||
{file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"},
|
||||
{file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"},
|
||||
{file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"},
|
||||
{file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"},
|
||||
{file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"},
|
||||
{file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"},
|
||||
{file = "black-24.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f"},
|
||||
{file = "black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e"},
|
||||
{file = "black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad"},
|
||||
{file = "black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50"},
|
||||
{file = "black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392"},
|
||||
{file = "black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175"},
|
||||
{file = "black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3"},
|
||||
{file = "black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65"},
|
||||
{file = "black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f"},
|
||||
{file = "black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8"},
|
||||
{file = "black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981"},
|
||||
{file = "black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b"},
|
||||
{file = "black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2"},
|
||||
{file = "black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b"},
|
||||
{file = "black-24.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd"},
|
||||
{file = "black-24.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f"},
|
||||
{file = "black-24.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800"},
|
||||
{file = "black-24.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7"},
|
||||
{file = "black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d"},
|
||||
{file = "black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -479,7 +479,7 @@ platformdirs = ">=2"
|
||||
|
||||
[package.extras]
|
||||
colorama = ["colorama (>=0.4.3)"]
|
||||
d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"]
|
||||
d = ["aiohttp (>=3.10)"]
|
||||
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
||||
uvloop = ["uvloop (>=0.15.2)"]
|
||||
|
||||
@ -969,83 +969,73 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.6.1"
|
||||
version = "7.6.3"
|
||||
description = "Code coverage measurement for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"},
|
||||
{file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"},
|
||||
{file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"},
|
||||
{file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"},
|
||||
{file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"},
|
||||
{file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"},
|
||||
{file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"},
|
||||
{file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"},
|
||||
{file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"},
|
||||
{file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"},
|
||||
{file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"},
|
||||
{file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"},
|
||||
{file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"},
|
||||
{file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"},
|
||||
{file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"},
|
||||
{file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"},
|
||||
{file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"},
|
||||
{file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"},
|
||||
{file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"},
|
||||
{file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"},
|
||||
{file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"},
|
||||
{file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"},
|
||||
{file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"},
|
||||
{file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"},
|
||||
{file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"},
|
||||
{file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"},
|
||||
{file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"},
|
||||
{file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"},
|
||||
{file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"},
|
||||
{file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"},
|
||||
{file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"},
|
||||
{file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"},
|
||||
{file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"},
|
||||
{file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"},
|
||||
{file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"},
|
||||
{file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"},
|
||||
{file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"},
|
||||
{file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"},
|
||||
{file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"},
|
||||
{file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"},
|
||||
{file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"},
|
||||
{file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"},
|
||||
{file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"},
|
||||
{file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"},
|
||||
{file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"},
|
||||
{file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"},
|
||||
{file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"},
|
||||
{file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"},
|
||||
{file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"},
|
||||
{file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"},
|
||||
{file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"},
|
||||
{file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"},
|
||||
{file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"},
|
||||
{file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"},
|
||||
{file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"},
|
||||
{file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"},
|
||||
{file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"},
|
||||
{file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"},
|
||||
{file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"},
|
||||
{file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"},
|
||||
{file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"},
|
||||
{file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"},
|
||||
{file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"},
|
||||
{file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"},
|
||||
{file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"},
|
||||
{file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"},
|
||||
{file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"},
|
||||
{file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"},
|
||||
{file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"},
|
||||
{file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"},
|
||||
{file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"},
|
||||
{file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"},
|
||||
{file = "coverage-7.6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6da42bbcec130b188169107ecb6ee7bd7b4c849d24c9370a0c884cf728d8e976"},
|
||||
{file = "coverage-7.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c222958f59b0ae091f4535851cbb24eb57fc0baea07ba675af718fb5302dddb2"},
|
||||
{file = "coverage-7.6.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab84a8b698ad5a6c365b08061920138e7a7dd9a04b6feb09ba1bfae68346ce6d"},
|
||||
{file = "coverage-7.6.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70a6756ce66cd6fe8486c775b30889f0dc4cb20c157aa8c35b45fd7868255c5c"},
|
||||
{file = "coverage-7.6.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c2e6fa98032fec8282f6b27e3f3986c6e05702828380618776ad794e938f53a"},
|
||||
{file = "coverage-7.6.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:921fbe13492caf6a69528f09d5d7c7d518c8d0e7b9f6701b7719715f29a71e6e"},
|
||||
{file = "coverage-7.6.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6d99198203f0b9cb0b5d1c0393859555bc26b548223a769baf7e321a627ed4fc"},
|
||||
{file = "coverage-7.6.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:87cd2e29067ea397a47e352efb13f976eb1b03e18c999270bb50589323294c6e"},
|
||||
{file = "coverage-7.6.3-cp310-cp310-win32.whl", hash = "sha256:a3328c3e64ea4ab12b85999eb0779e6139295bbf5485f69d42cf794309e3d007"},
|
||||
{file = "coverage-7.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:bca4c8abc50d38f9773c1ec80d43f3768df2e8576807d1656016b9d3eeaa96fd"},
|
||||
{file = "coverage-7.6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c51ef82302386d686feea1c44dbeef744585da16fcf97deea2a8d6c1556f519b"},
|
||||
{file = "coverage-7.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0ca37993206402c6c35dc717f90d4c8f53568a8b80f0bf1a1b2b334f4d488fba"},
|
||||
{file = "coverage-7.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c77326300b839c44c3e5a8fe26c15b7e87b2f32dfd2fc9fee1d13604347c9b38"},
|
||||
{file = "coverage-7.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e484e479860e00da1f005cd19d1c5d4a813324e5951319ac3f3eefb497cc549"},
|
||||
{file = "coverage-7.6.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c6c0f4d53ef603397fc894a895b960ecd7d44c727df42a8d500031716d4e8d2"},
|
||||
{file = "coverage-7.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:37be7b5ea3ff5b7c4a9db16074dc94523b5f10dd1f3b362a827af66a55198175"},
|
||||
{file = "coverage-7.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:43b32a06c47539fe275106b376658638b418c7cfdfff0e0259fbf877e845f14b"},
|
||||
{file = "coverage-7.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ee77c7bef0724165e795b6b7bf9c4c22a9b8468a6bdb9c6b4281293c6b22a90f"},
|
||||
{file = "coverage-7.6.3-cp311-cp311-win32.whl", hash = "sha256:43517e1f6b19f610a93d8227e47790722c8bf7422e46b365e0469fc3d3563d97"},
|
||||
{file = "coverage-7.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:04f2189716e85ec9192df307f7c255f90e78b6e9863a03223c3b998d24a3c6c6"},
|
||||
{file = "coverage-7.6.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27bd5f18d8f2879e45724b0ce74f61811639a846ff0e5c0395b7818fae87aec6"},
|
||||
{file = "coverage-7.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d546cfa78844b8b9c1c0533de1851569a13f87449897bbc95d698d1d3cb2a30f"},
|
||||
{file = "coverage-7.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9975442f2e7a5cfcf87299c26b5a45266ab0696348420049b9b94b2ad3d40234"},
|
||||
{file = "coverage-7.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:583049c63106c0555e3ae3931edab5669668bbef84c15861421b94e121878d3f"},
|
||||
{file = "coverage-7.6.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2341a78ae3a5ed454d524206a3fcb3cec408c2a0c7c2752cd78b606a2ff15af4"},
|
||||
{file = "coverage-7.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a4fb91d5f72b7e06a14ff4ae5be625a81cd7e5f869d7a54578fc271d08d58ae3"},
|
||||
{file = "coverage-7.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e279f3db904e3b55f520f11f983cc8dc8a4ce9b65f11692d4718ed021ec58b83"},
|
||||
{file = "coverage-7.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aa23ce39661a3e90eea5f99ec59b763b7d655c2cada10729ed920a38bfc2b167"},
|
||||
{file = "coverage-7.6.3-cp312-cp312-win32.whl", hash = "sha256:52ac29cc72ee7e25ace7807249638f94c9b6a862c56b1df015d2b2e388e51dbd"},
|
||||
{file = "coverage-7.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:40e8b1983080439d4802d80b951f4a93d991ef3261f69e81095a66f86cf3c3c6"},
|
||||
{file = "coverage-7.6.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9134032f5aa445ae591c2ba6991d10136a1f533b1d2fa8f8c21126468c5025c6"},
|
||||
{file = "coverage-7.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:99670790f21a96665a35849990b1df447993880bb6463a0a1d757897f30da929"},
|
||||
{file = "coverage-7.6.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dc7d6b380ca76f5e817ac9eef0c3686e7834c8346bef30b041a4ad286449990"},
|
||||
{file = "coverage-7.6.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f7b26757b22faf88fcf232f5f0e62f6e0fd9e22a8a5d0d5016888cdfe1f6c1c4"},
|
||||
{file = "coverage-7.6.3-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c59d6a4a4633fad297f943c03d0d2569867bd5372eb5684befdff8df8522e39"},
|
||||
{file = "coverage-7.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f263b18692f8ed52c8de7f40a0751e79015983dbd77b16906e5b310a39d3ca21"},
|
||||
{file = "coverage-7.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:79644f68a6ff23b251cae1c82b01a0b51bc40c8468ca9585c6c4b1aeee570e0b"},
|
||||
{file = "coverage-7.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:71967c35828c9ff94e8c7d405469a1fb68257f686bca7c1ed85ed34e7c2529c4"},
|
||||
{file = "coverage-7.6.3-cp313-cp313-win32.whl", hash = "sha256:e266af4da2c1a4cbc6135a570c64577fd3e6eb204607eaff99d8e9b710003c6f"},
|
||||
{file = "coverage-7.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:ea52bd218d4ba260399a8ae4bb6b577d82adfc4518b93566ce1fddd4a49d1dce"},
|
||||
{file = "coverage-7.6.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8d4c6ea0f498c7c79111033a290d060c517853a7bcb2f46516f591dab628ddd3"},
|
||||
{file = "coverage-7.6.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:331b200ad03dbaa44151d74daeb7da2cf382db424ab923574f6ecca7d3b30de3"},
|
||||
{file = "coverage-7.6.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54356a76b67cf8a3085818026bb556545ebb8353951923b88292556dfa9f812d"},
|
||||
{file = "coverage-7.6.3-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ebec65f5068e7df2d49466aab9128510c4867e532e07cb6960075b27658dca38"},
|
||||
{file = "coverage-7.6.3-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d33a785ea8354c480515e781554d3be582a86297e41ccbea627a5c632647f2cd"},
|
||||
{file = "coverage-7.6.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f7ddb920106bbbbcaf2a274d56f46956bf56ecbde210d88061824a95bdd94e92"},
|
||||
{file = "coverage-7.6.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:70d24936ca6c15a3bbc91ee9c7fc661132c6f4c9d42a23b31b6686c05073bde5"},
|
||||
{file = "coverage-7.6.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c30e42ea11badb147f0d2e387115b15e2bd8205a5ad70d6ad79cf37f6ac08c91"},
|
||||
{file = "coverage-7.6.3-cp313-cp313t-win32.whl", hash = "sha256:365defc257c687ce3e7d275f39738dcd230777424117a6c76043459db131dd43"},
|
||||
{file = "coverage-7.6.3-cp313-cp313t-win_amd64.whl", hash = "sha256:23bb63ae3f4c645d2d82fa22697364b0046fbafb6261b258a58587441c5f7bd0"},
|
||||
{file = "coverage-7.6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:da29ceabe3025a1e5a5aeeb331c5b1af686daab4ff0fb4f83df18b1180ea83e2"},
|
||||
{file = "coverage-7.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:df8c05a0f574d480947cba11b947dc41b1265d721c3777881da2fb8d3a1ddfba"},
|
||||
{file = "coverage-7.6.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec1e3b40b82236d100d259854840555469fad4db64f669ab817279eb95cd535c"},
|
||||
{file = "coverage-7.6.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4adeb878a374126f1e5cf03b87f66279f479e01af0e9a654cf6d1509af46c40"},
|
||||
{file = "coverage-7.6.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43d6a66e33b1455b98fc7312b124296dad97a2e191c80320587234a77b1b736e"},
|
||||
{file = "coverage-7.6.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1990b1f4e2c402beb317840030bb9f1b6a363f86e14e21b4212e618acdfce7f6"},
|
||||
{file = "coverage-7.6.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:12f9515d875859faedb4144fd38694a761cd2a61ef9603bf887b13956d0bbfbb"},
|
||||
{file = "coverage-7.6.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:99ded130555c021d99729fabd4ddb91a6f4cc0707df4b1daf912c7850c373b13"},
|
||||
{file = "coverage-7.6.3-cp39-cp39-win32.whl", hash = "sha256:c3a79f56dee9136084cf84a6c7c4341427ef36e05ae6415bf7d787c96ff5eaa3"},
|
||||
{file = "coverage-7.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:aac7501ae73d4a02f4b7ac8fcb9dc55342ca98ffb9ed9f2dfb8a25d53eda0e4d"},
|
||||
{file = "coverage-7.6.3-pp39.pp310-none-any.whl", hash = "sha256:b9853509b4bf57ba7b1f99b9d866c422c9c5248799ab20e652bbb8a184a38181"},
|
||||
{file = "coverage-7.6.3.tar.gz", hash = "sha256:bb7d5fe92bd0dc235f63ebe9f8c6e0884f7360f88f3411bfed1350c872ef2054"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@ -1134,33 +1124,37 @@ tests = ["django", "hypothesis", "pytest", "pytest-asyncio"]
|
||||
|
||||
[[package]]
|
||||
name = "debugpy"
|
||||
version = "1.8.5"
|
||||
version = "1.8.7"
|
||||
description = "An implementation of the Debug Adapter Protocol for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "debugpy-1.8.5-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:7e4d594367d6407a120b76bdaa03886e9eb652c05ba7f87e37418426ad2079f7"},
|
||||
{file = "debugpy-1.8.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4413b7a3ede757dc33a273a17d685ea2b0c09dbd312cc03f5534a0fd4d40750a"},
|
||||
{file = "debugpy-1.8.5-cp310-cp310-win32.whl", hash = "sha256:dd3811bd63632bb25eda6bd73bea8e0521794cda02be41fa3160eb26fc29e7ed"},
|
||||
{file = "debugpy-1.8.5-cp310-cp310-win_amd64.whl", hash = "sha256:b78c1250441ce893cb5035dd6f5fc12db968cc07f91cc06996b2087f7cefdd8e"},
|
||||
{file = "debugpy-1.8.5-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:606bccba19f7188b6ea9579c8a4f5a5364ecd0bf5a0659c8a5d0e10dcee3032a"},
|
||||
{file = "debugpy-1.8.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db9fb642938a7a609a6c865c32ecd0d795d56c1aaa7a7a5722d77855d5e77f2b"},
|
||||
{file = "debugpy-1.8.5-cp311-cp311-win32.whl", hash = "sha256:4fbb3b39ae1aa3e5ad578f37a48a7a303dad9a3d018d369bc9ec629c1cfa7408"},
|
||||
{file = "debugpy-1.8.5-cp311-cp311-win_amd64.whl", hash = "sha256:345d6a0206e81eb68b1493ce2fbffd57c3088e2ce4b46592077a943d2b968ca3"},
|
||||
{file = "debugpy-1.8.5-cp312-cp312-macosx_12_0_universal2.whl", hash = "sha256:5b5c770977c8ec6c40c60d6f58cacc7f7fe5a45960363d6974ddb9b62dbee156"},
|
||||
{file = "debugpy-1.8.5-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0a65b00b7cdd2ee0c2cf4c7335fef31e15f1b7056c7fdbce9e90193e1a8c8cb"},
|
||||
{file = "debugpy-1.8.5-cp312-cp312-win32.whl", hash = "sha256:c9f7c15ea1da18d2fcc2709e9f3d6de98b69a5b0fff1807fb80bc55f906691f7"},
|
||||
{file = "debugpy-1.8.5-cp312-cp312-win_amd64.whl", hash = "sha256:28ced650c974aaf179231668a293ecd5c63c0a671ae6d56b8795ecc5d2f48d3c"},
|
||||
{file = "debugpy-1.8.5-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:3df6692351172a42af7558daa5019651f898fc67450bf091335aa8a18fbf6f3a"},
|
||||
{file = "debugpy-1.8.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cd04a73eb2769eb0bfe43f5bfde1215c5923d6924b9b90f94d15f207a402226"},
|
||||
{file = "debugpy-1.8.5-cp38-cp38-win32.whl", hash = "sha256:8f913ee8e9fcf9d38a751f56e6de12a297ae7832749d35de26d960f14280750a"},
|
||||
{file = "debugpy-1.8.5-cp38-cp38-win_amd64.whl", hash = "sha256:a697beca97dad3780b89a7fb525d5e79f33821a8bc0c06faf1f1289e549743cf"},
|
||||
{file = "debugpy-1.8.5-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:0a1029a2869d01cb777216af8c53cda0476875ef02a2b6ff8b2f2c9a4b04176c"},
|
||||
{file = "debugpy-1.8.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84c276489e141ed0b93b0af648eef891546143d6a48f610945416453a8ad406"},
|
||||
{file = "debugpy-1.8.5-cp39-cp39-win32.whl", hash = "sha256:ad84b7cde7fd96cf6eea34ff6c4a1b7887e0fe2ea46e099e53234856f9d99a34"},
|
||||
{file = "debugpy-1.8.5-cp39-cp39-win_amd64.whl", hash = "sha256:7b0fe36ed9d26cb6836b0a51453653f8f2e347ba7348f2bbfe76bfeb670bfb1c"},
|
||||
{file = "debugpy-1.8.5-py2.py3-none-any.whl", hash = "sha256:55919dce65b471eff25901acf82d328bbd5b833526b6c1364bd5133754777a44"},
|
||||
{file = "debugpy-1.8.5.zip", hash = "sha256:b2112cfeb34b4507399d298fe7023a16656fc553ed5246536060ca7bd0e668d0"},
|
||||
{file = "debugpy-1.8.7-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:95fe04a573b8b22896c404365e03f4eda0ce0ba135b7667a1e57bd079793b96b"},
|
||||
{file = "debugpy-1.8.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:628a11f4b295ffb4141d8242a9bb52b77ad4a63a2ad19217a93be0f77f2c28c9"},
|
||||
{file = "debugpy-1.8.7-cp310-cp310-win32.whl", hash = "sha256:85ce9c1d0eebf622f86cc68618ad64bf66c4fc3197d88f74bb695a416837dd55"},
|
||||
{file = "debugpy-1.8.7-cp310-cp310-win_amd64.whl", hash = "sha256:29e1571c276d643757ea126d014abda081eb5ea4c851628b33de0c2b6245b037"},
|
||||
{file = "debugpy-1.8.7-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:caf528ff9e7308b74a1749c183d6808ffbedbb9fb6af78b033c28974d9b8831f"},
|
||||
{file = "debugpy-1.8.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cba1d078cf2e1e0b8402e6bda528bf8fda7ccd158c3dba6c012b7897747c41a0"},
|
||||
{file = "debugpy-1.8.7-cp311-cp311-win32.whl", hash = "sha256:171899588bcd412151e593bd40d9907133a7622cd6ecdbdb75f89d1551df13c2"},
|
||||
{file = "debugpy-1.8.7-cp311-cp311-win_amd64.whl", hash = "sha256:6e1c4ffb0c79f66e89dfd97944f335880f0d50ad29525dc792785384923e2211"},
|
||||
{file = "debugpy-1.8.7-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:4d27d842311353ede0ad572600c62e4bcd74f458ee01ab0dd3a1a4457e7e3706"},
|
||||
{file = "debugpy-1.8.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:703c1fd62ae0356e194f3e7b7a92acd931f71fe81c4b3be2c17a7b8a4b546ec2"},
|
||||
{file = "debugpy-1.8.7-cp312-cp312-win32.whl", hash = "sha256:2f729228430ef191c1e4df72a75ac94e9bf77413ce5f3f900018712c9da0aaca"},
|
||||
{file = "debugpy-1.8.7-cp312-cp312-win_amd64.whl", hash = "sha256:45c30aaefb3e1975e8a0258f5bbd26cd40cde9bfe71e9e5a7ac82e79bad64e39"},
|
||||
{file = "debugpy-1.8.7-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:d050a1ec7e925f514f0f6594a1e522580317da31fbda1af71d1530d6ea1f2b40"},
|
||||
{file = "debugpy-1.8.7-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f4349a28e3228a42958f8ddaa6333d6f8282d5edaea456070e48609c5983b7"},
|
||||
{file = "debugpy-1.8.7-cp313-cp313-win32.whl", hash = "sha256:11ad72eb9ddb436afb8337891a986302e14944f0f755fd94e90d0d71e9100bba"},
|
||||
{file = "debugpy-1.8.7-cp313-cp313-win_amd64.whl", hash = "sha256:2efb84d6789352d7950b03d7f866e6d180284bc02c7e12cb37b489b7083d81aa"},
|
||||
{file = "debugpy-1.8.7-cp38-cp38-macosx_14_0_x86_64.whl", hash = "sha256:4b908291a1d051ef3331484de8e959ef3e66f12b5e610c203b5b75d2725613a7"},
|
||||
{file = "debugpy-1.8.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da8df5b89a41f1fd31503b179d0a84a5fdb752dddd5b5388dbd1ae23cda31ce9"},
|
||||
{file = "debugpy-1.8.7-cp38-cp38-win32.whl", hash = "sha256:b12515e04720e9e5c2216cc7086d0edadf25d7ab7e3564ec8b4521cf111b4f8c"},
|
||||
{file = "debugpy-1.8.7-cp38-cp38-win_amd64.whl", hash = "sha256:93176e7672551cb5281577cdb62c63aadc87ec036f0c6a486f0ded337c504596"},
|
||||
{file = "debugpy-1.8.7-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:90d93e4f2db442f8222dec5ec55ccfc8005821028982f1968ebf551d32b28907"},
|
||||
{file = "debugpy-1.8.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6db2a370e2700557a976eaadb16243ec9c91bd46f1b3bb15376d7aaa7632c81"},
|
||||
{file = "debugpy-1.8.7-cp39-cp39-win32.whl", hash = "sha256:a6cf2510740e0c0b4a40330640e4b454f928c7b99b0c9dbf48b11efba08a8cda"},
|
||||
{file = "debugpy-1.8.7-cp39-cp39-win_amd64.whl", hash = "sha256:6a9d9d6d31846d8e34f52987ee0f1a904c7baa4912bf4843ab39dadf9b8f3e0d"},
|
||||
{file = "debugpy-1.8.7-py2.py3-none-any.whl", hash = "sha256:57b00de1c8d2c84a61b90880f7e5b6deaf4c312ecbde3a0e8912f2a56c4ac9ae"},
|
||||
{file = "debugpy-1.8.7.zip", hash = "sha256:18b8f731ed3e2e1df8e9cdaa23fb1fc9c24e570cd0081625308ec51c82efe42e"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1771,13 +1765,13 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"]
|
||||
|
||||
[[package]]
|
||||
name = "google-api-python-client"
|
||||
version = "2.146.0"
|
||||
version = "2.149.0"
|
||||
description = "Google API Client Library for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "google_api_python_client-2.146.0-py2.py3-none-any.whl", hash = "sha256:b1e62c9889c5ef6022f11d30d7ef23dc55100300f0e8aaf8aa09e8e92540acad"},
|
||||
{file = "google_api_python_client-2.146.0.tar.gz", hash = "sha256:41f671be10fa077ee5143ee9f0903c14006d39dc644564f4e044ae96b380bf68"},
|
||||
{file = "google_api_python_client-2.149.0-py2.py3-none-any.whl", hash = "sha256:1a5232e9cfed8c201799d9327e4d44dc7ea7daa3c6e1627fca41aa201539c0da"},
|
||||
{file = "google_api_python_client-2.149.0.tar.gz", hash = "sha256:b9d68c6b14ec72580d66001bd33c5816b78e2134b93ccc5cf8f624516b561750"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -2859,13 +2853,13 @@ dev = ["bumpver", "isort", "mypy", "pylint", "pytest", "yapf"]
|
||||
|
||||
[[package]]
|
||||
name = "msgraph-sdk"
|
||||
version = "1.8.0"
|
||||
version = "1.10.0"
|
||||
description = "The Microsoft Graph Python SDK"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "msgraph_sdk-1.8.0-py3-none-any.whl", hash = "sha256:22a8e4a63f989865228f66a54501bef8105909c7156fe0a079ca9b5296339cc2"},
|
||||
{file = "msgraph_sdk-1.8.0.tar.gz", hash = "sha256:1ac84bd47ea288a84f46f6c6d0c89d164ee3453b917615632652344538098314"},
|
||||
{file = "msgraph_sdk-1.10.0-py3-none-any.whl", hash = "sha256:b346013f978d2e23255d044d38751e2715e1eed3159b1b1c3d7cbe831dd121e8"},
|
||||
{file = "msgraph_sdk-1.10.0.tar.gz", hash = "sha256:7b94646fea833d85ad2f793643ff72946de23bc2cc253cfdb694798ae7a60229"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -3215,23 +3209,20 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "pdoc"
|
||||
version = "14.7.0"
|
||||
version = "15.0.0"
|
||||
description = "API Documentation for Python Projects"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "pdoc-14.7.0-py3-none-any.whl", hash = "sha256:72377a907efc6b2c5b3c56b717ef34f11d93621dced3b663f3aede0b844c0ad2"},
|
||||
{file = "pdoc-14.7.0.tar.gz", hash = "sha256:2d28af9c0acc39180744ad0543e4bbc3223ecba0d1302db315ec521c51f71f93"},
|
||||
{file = "pdoc-15.0.0-py3-none-any.whl", hash = "sha256:151b0187a25eaf827099e981d6dbe3a4f68aeb18d0d637c24edcab788d5540f1"},
|
||||
{file = "pdoc-15.0.0.tar.gz", hash = "sha256:b761220d3ba129cd87e6da1bb7b62c8e799973ab9c595de7ba1a514850d86da5"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
Jinja2 = ">=2.11.0"
|
||||
MarkupSafe = "*"
|
||||
MarkupSafe = ">=1.1.1"
|
||||
pygments = ">=2.12.0"
|
||||
|
||||
[package.extras]
|
||||
dev = ["hypothesis", "mypy", "pdoc-pyo3-sample-library (==1.0.11)", "pygments (>=2.14.0)", "pytest", "pytest-cov", "pytest-timeout", "ruff", "tox", "types-pygments"]
|
||||
|
||||
[[package]]
|
||||
name = "pendulum"
|
||||
version = "3.0.0"
|
||||
@ -3448,23 +3439,23 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "psycopg"
|
||||
version = "3.2.2"
|
||||
version = "3.2.3"
|
||||
description = "PostgreSQL database adapter for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "psycopg-3.2.2-py3-none-any.whl", hash = "sha256:babf565d459d8f72fb65da5e211dd0b58a52c51e4e1fa9cadecff42d6b7619b2"},
|
||||
{file = "psycopg-3.2.2.tar.gz", hash = "sha256:8bad2e497ce22d556dac1464738cb948f8d6bab450d965cf1d8a8effd52412e0"},
|
||||
{file = "psycopg-3.2.3-py3-none-any.whl", hash = "sha256:644d3973fe26908c73d4be746074f6e5224b03c1101d302d9a53bf565ad64907"},
|
||||
{file = "psycopg-3.2.3.tar.gz", hash = "sha256:a5764f67c27bec8bfac85764d23c534af2c27b893550377e37ce59c12aac47a2"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
psycopg-c = {version = "3.2.2", optional = true, markers = "implementation_name != \"pypy\" and extra == \"c\""}
|
||||
psycopg-c = {version = "3.2.3", optional = true, markers = "implementation_name != \"pypy\" and extra == \"c\""}
|
||||
typing-extensions = {version = ">=4.6", markers = "python_version < \"3.13\""}
|
||||
tzdata = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
|
||||
[package.extras]
|
||||
binary = ["psycopg-binary (==3.2.2)"]
|
||||
c = ["psycopg-c (==3.2.2)"]
|
||||
binary = ["psycopg-binary (==3.2.3)"]
|
||||
c = ["psycopg-c (==3.2.3)"]
|
||||
dev = ["ast-comments (>=1.1.2)", "black (>=24.1.0)", "codespell (>=2.2)", "dnspython (>=2.1)", "flake8 (>=4.0)", "mypy (>=1.11)", "types-setuptools (>=57.4)", "wheel (>=0.37)"]
|
||||
docs = ["Sphinx (>=5.0)", "furo (==2022.6.21)", "sphinx-autobuild (>=2021.3.14)", "sphinx-autodoc-typehints (>=1.12)"]
|
||||
pool = ["psycopg-pool"]
|
||||
@ -3472,12 +3463,12 @@ test = ["anyio (>=4.0)", "mypy (>=1.11)", "pproxy (>=2.7)", "pytest (>=6.2.5)",
|
||||
|
||||
[[package]]
|
||||
name = "psycopg-c"
|
||||
version = "3.2.2"
|
||||
version = "3.2.3"
|
||||
description = "PostgreSQL database adapter for Python -- C optimisation distribution"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "psycopg_c-3.2.2.tar.gz", hash = "sha256:de8cac75bc6640ef0f54ad9187b81e07c430206a83c566b73d4cca41ecccb7c8"},
|
||||
{file = "psycopg_c-3.2.3.tar.gz", hash = "sha256:06ae7db8eaec1a3845960fa7f997f4ccdb1a7a7ab8dc593a680bcc74e1359671"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4210,29 +4201,29 @@ pyasn1 = ">=0.1.3"
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.6.7"
|
||||
version = "0.6.9"
|
||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "ruff-0.6.7-py3-none-linux_armv6l.whl", hash = "sha256:08277b217534bfdcc2e1377f7f933e1c7957453e8a79764d004e44c40db923f2"},
|
||||
{file = "ruff-0.6.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c6707a32e03b791f4448dc0dce24b636cbcdee4dd5607adc24e5ee73fd86c00a"},
|
||||
{file = "ruff-0.6.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:533d66b7774ef224e7cf91506a7dafcc9e8ec7c059263ec46629e54e7b1f90ab"},
|
||||
{file = "ruff-0.6.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17a86aac6f915932d259f7bec79173e356165518859f94649d8c50b81ff087e9"},
|
||||
{file = "ruff-0.6.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b3f8822defd260ae2460ea3832b24d37d203c3577f48b055590a426a722d50ef"},
|
||||
{file = "ruff-0.6.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9ba4efe5c6dbbb58be58dd83feedb83b5e95c00091bf09987b4baf510fee5c99"},
|
||||
{file = "ruff-0.6.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:525201b77f94d2b54868f0cbe5edc018e64c22563da6c5c2e5c107a4e85c1c0d"},
|
||||
{file = "ruff-0.6.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8854450839f339e1049fdbe15d875384242b8e85d5c6947bb2faad33c651020b"},
|
||||
{file = "ruff-0.6.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f0b62056246234d59cbf2ea66e84812dc9ec4540518e37553513392c171cb18"},
|
||||
{file = "ruff-0.6.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b1462fa56c832dc0cea5b4041cfc9c97813505d11cce74ebc6d1aae068de36b"},
|
||||
{file = "ruff-0.6.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:02b083770e4cdb1495ed313f5694c62808e71764ec6ee5db84eedd82fd32d8f5"},
|
||||
{file = "ruff-0.6.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0c05fd37013de36dfa883a3854fae57b3113aaa8abf5dea79202675991d48624"},
|
||||
{file = "ruff-0.6.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f49c9caa28d9bbfac4a637ae10327b3db00f47d038f3fbb2195c4d682e925b14"},
|
||||
{file = "ruff-0.6.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a0e1655868164e114ba43a908fd2d64a271a23660195017c17691fb6355d59bb"},
|
||||
{file = "ruff-0.6.7-py3-none-win32.whl", hash = "sha256:a939ca435b49f6966a7dd64b765c9df16f1faed0ca3b6f16acdf7731969deb35"},
|
||||
{file = "ruff-0.6.7-py3-none-win_amd64.whl", hash = "sha256:590445eec5653f36248584579c06252ad2e110a5d1f32db5420de35fb0e1c977"},
|
||||
{file = "ruff-0.6.7-py3-none-win_arm64.whl", hash = "sha256:b28f0d5e2f771c1fe3c7a45d3f53916fc74a480698c4b5731f0bea61e52137c8"},
|
||||
{file = "ruff-0.6.7.tar.gz", hash = "sha256:44e52129d82266fa59b587e2cd74def5637b730a69c4542525dfdecfaae38bd5"},
|
||||
{file = "ruff-0.6.9-py3-none-linux_armv6l.whl", hash = "sha256:064df58d84ccc0ac0fcd63bc3090b251d90e2a372558c0f057c3f75ed73e1ccd"},
|
||||
{file = "ruff-0.6.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:140d4b5c9f5fc7a7b074908a78ab8d384dd7f6510402267bc76c37195c02a7ec"},
|
||||
{file = "ruff-0.6.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53fd8ca5e82bdee8da7f506d7b03a261f24cd43d090ea9db9a1dc59d9313914c"},
|
||||
{file = "ruff-0.6.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645d7d8761f915e48a00d4ecc3686969761df69fb561dd914a773c1a8266e14e"},
|
||||
{file = "ruff-0.6.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eae02b700763e3847595b9d2891488989cac00214da7f845f4bcf2989007d577"},
|
||||
{file = "ruff-0.6.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d5ccc9e58112441de8ad4b29dcb7a86dc25c5f770e3c06a9d57e0e5eba48829"},
|
||||
{file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:417b81aa1c9b60b2f8edc463c58363075412866ae4e2b9ab0f690dc1e87ac1b5"},
|
||||
{file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c866b631f5fbce896a74a6e4383407ba7507b815ccc52bcedabb6810fdb3ef7"},
|
||||
{file = "ruff-0.6.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b118afbb3202f5911486ad52da86d1d52305b59e7ef2031cea3425142b97d6f"},
|
||||
{file = "ruff-0.6.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67267654edc23c97335586774790cde402fb6bbdb3c2314f1fc087dee320bfa"},
|
||||
{file = "ruff-0.6.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3ef0cc774b00fec123f635ce5c547dac263f6ee9fb9cc83437c5904183b55ceb"},
|
||||
{file = "ruff-0.6.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:12edd2af0c60fa61ff31cefb90aef4288ac4d372b4962c2864aeea3a1a2460c0"},
|
||||
{file = "ruff-0.6.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:55bb01caeaf3a60b2b2bba07308a02fca6ab56233302406ed5245180a05c5625"},
|
||||
{file = "ruff-0.6.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:925d26471fa24b0ce5a6cdfab1bb526fb4159952385f386bdcc643813d472039"},
|
||||
{file = "ruff-0.6.9-py3-none-win32.whl", hash = "sha256:eb61ec9bdb2506cffd492e05ac40e5bc6284873aceb605503d8494180d6fc84d"},
|
||||
{file = "ruff-0.6.9-py3-none-win_amd64.whl", hash = "sha256:785d31851c1ae91f45b3d8fe23b8ae4b5170089021fbb42402d811135f0b7117"},
|
||||
{file = "ruff-0.6.9-py3-none-win_arm64.whl", hash = "sha256:a9641e31476d601f83cd602608739a0840e348bda93fec9f1ee816f8b6798b93"},
|
||||
{file = "ruff-0.6.9.tar.gz", hash = "sha256:b076ef717a8e5bc819514ee1d602bbdca5b4420ae13a9cf61a0c0a4f53a2baa2"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4290,13 +4281,13 @@ websocket-client = ">=1.8,<2.0"
|
||||
|
||||
[[package]]
|
||||
name = "sentry-sdk"
|
||||
version = "2.14.0"
|
||||
version = "2.16.0"
|
||||
description = "Python client for Sentry (https://sentry.io)"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "sentry_sdk-2.14.0-py2.py3-none-any.whl", hash = "sha256:b8bc3dc51d06590df1291b7519b85c75e2ced4f28d9ea655b6d54033503b5bf4"},
|
||||
{file = "sentry_sdk-2.14.0.tar.gz", hash = "sha256:1e0e2eaf6dad918c7d1e0edac868a7bf20017b177f242cefe2a6bcd47955961d"},
|
||||
{file = "sentry_sdk-2.16.0-py2.py3-none-any.whl", hash = "sha256:49139c31ebcd398f4f6396b18910610a0c1602f6e67083240c33019d1f6aa30c"},
|
||||
{file = "sentry_sdk-2.16.0.tar.gz", hash = "sha256:90f733b32e15dfc1999e6b7aca67a38688a567329de4d6e184154a73f96c6892"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -4319,6 +4310,7 @@ falcon = ["falcon (>=1.4)"]
|
||||
fastapi = ["fastapi (>=0.79.0)"]
|
||||
flask = ["blinker (>=1.1)", "flask (>=0.11)", "markupsafe"]
|
||||
grpcio = ["grpcio (>=1.21.1)", "protobuf (>=3.8.0)"]
|
||||
http2 = ["httpcore[http2] (==1.*)"]
|
||||
httpx = ["httpx (>=0.16.0)"]
|
||||
huey = ["huey (>=2)"]
|
||||
huggingface-hub = ["huggingface-hub (>=0.22)"]
|
||||
@ -4667,13 +4659,13 @@ wsproto = ">=0.14"
|
||||
|
||||
[[package]]
|
||||
name = "twilio"
|
||||
version = "9.3.1"
|
||||
version = "9.3.3"
|
||||
description = "Twilio API client and TwiML generator"
|
||||
optional = false
|
||||
python-versions = ">=3.7.0"
|
||||
files = [
|
||||
{file = "twilio-9.3.1-py2.py3-none-any.whl", hash = "sha256:48c714e5a1340a3d9001d6c9ccd107434c340ff94a5bc1bfb2473d3dc33f5051"},
|
||||
{file = "twilio-9.3.1.tar.gz", hash = "sha256:bd754371353855438a8cf0c38f54580d474afe9655af2d81edb6dbabd8d785c4"},
|
||||
{file = "twilio-9.3.3-py2.py3-none-any.whl", hash = "sha256:716a38a96867d4e233cf540ee9b79eb8b2f839ee72ccbec0331829d20beccdcd"},
|
||||
{file = "twilio-9.3.3.tar.gz", hash = "sha256:4750f7b512258fa1cf61f6666f3f93ddbf850449745cbbc3beec6ea59a813153"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -4802,13 +4794,13 @@ zstd = ["zstandard (>=0.18.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.30.6"
|
||||
version = "0.31.1"
|
||||
description = "The lightning-fast ASGI server."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "uvicorn-0.30.6-py3-none-any.whl", hash = "sha256:65fd46fe3fda5bdc1b03b94eb634923ff18cd35b2f084813ea79d1f103f711b5"},
|
||||
{file = "uvicorn-0.30.6.tar.gz", hash = "sha256:4b15decdda1e72be08209e860a1e10e92439ad5b97cf44cc945fcbee66fc5788"},
|
||||
{file = "uvicorn-0.31.1-py3-none-any.whl", hash = "sha256:adc42d9cac80cf3e51af97c1851648066841e7cfb6993a4ca8de29ac1548ed41"},
|
||||
{file = "uvicorn-0.31.1.tar.gz", hash = "sha256:f5167919867b161b7bcaf32646c6a94cdbd4c3aa2eb5c17d36bb9aa5cfd8c493"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -4882,41 +4874,41 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "watchdog"
|
||||
version = "5.0.2"
|
||||
version = "5.0.3"
|
||||
description = "Filesystem events monitoring"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "watchdog-5.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d961f4123bb3c447d9fcdcb67e1530c366f10ab3a0c7d1c0c9943050936d4877"},
|
||||
{file = "watchdog-5.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72990192cb63872c47d5e5fefe230a401b87fd59d257ee577d61c9e5564c62e5"},
|
||||
{file = "watchdog-5.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6bec703ad90b35a848e05e1b40bf0050da7ca28ead7ac4be724ae5ac2653a1a0"},
|
||||
{file = "watchdog-5.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dae7a1879918f6544201d33666909b040a46421054a50e0f773e0d870ed7438d"},
|
||||
{file = "watchdog-5.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c4a440f725f3b99133de610bfec93d570b13826f89616377715b9cd60424db6e"},
|
||||
{file = "watchdog-5.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8b2918c19e0d48f5f20df458c84692e2a054f02d9df25e6c3c930063eca64c1"},
|
||||
{file = "watchdog-5.0.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:aa9cd6e24126d4afb3752a3e70fce39f92d0e1a58a236ddf6ee823ff7dba28ee"},
|
||||
{file = "watchdog-5.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f627c5bf5759fdd90195b0c0431f99cff4867d212a67b384442c51136a098ed7"},
|
||||
{file = "watchdog-5.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d7594a6d32cda2b49df3fd9abf9b37c8d2f3eab5df45c24056b4a671ac661619"},
|
||||
{file = "watchdog-5.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba32efcccfe2c58f4d01115440d1672b4eb26cdd6fc5b5818f1fb41f7c3e1889"},
|
||||
{file = "watchdog-5.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:963f7c4c91e3f51c998eeff1b3fb24a52a8a34da4f956e470f4b068bb47b78ee"},
|
||||
{file = "watchdog-5.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8c47150aa12f775e22efff1eee9f0f6beee542a7aa1a985c271b1997d340184f"},
|
||||
{file = "watchdog-5.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:14dd4ed023d79d1f670aa659f449bcd2733c33a35c8ffd88689d9d243885198b"},
|
||||
{file = "watchdog-5.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b84bff0391ad4abe25c2740c7aec0e3de316fdf7764007f41e248422a7760a7f"},
|
||||
{file = "watchdog-5.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e8d5ff39f0a9968952cce548e8e08f849141a4fcc1290b1c17c032ba697b9d7"},
|
||||
{file = "watchdog-5.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fb223456db6e5f7bd9bbd5cd969f05aae82ae21acc00643b60d81c770abd402b"},
|
||||
{file = "watchdog-5.0.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9814adb768c23727a27792c77812cf4e2fd9853cd280eafa2bcfa62a99e8bd6e"},
|
||||
{file = "watchdog-5.0.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:901ee48c23f70193d1a7bc2d9ee297df66081dd5f46f0ca011be4f70dec80dab"},
|
||||
{file = "watchdog-5.0.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:638bcca3d5b1885c6ec47be67bf712b00a9ab3d4b22ec0881f4889ad870bc7e8"},
|
||||
{file = "watchdog-5.0.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:5597c051587f8757798216f2485e85eac583c3b343e9aa09127a3a6f82c65ee8"},
|
||||
{file = "watchdog-5.0.2-py3-none-manylinux2014_armv7l.whl", hash = "sha256:53ed1bf71fcb8475dd0ef4912ab139c294c87b903724b6f4a8bd98e026862e6d"},
|
||||
{file = "watchdog-5.0.2-py3-none-manylinux2014_i686.whl", hash = "sha256:29e4a2607bd407d9552c502d38b45a05ec26a8e40cc7e94db9bb48f861fa5abc"},
|
||||
{file = "watchdog-5.0.2-py3-none-manylinux2014_ppc64.whl", hash = "sha256:b6dc8f1d770a8280997e4beae7b9a75a33b268c59e033e72c8a10990097e5fde"},
|
||||
{file = "watchdog-5.0.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:d2ab34adc9bf1489452965cdb16a924e97d4452fcf88a50b21859068b50b5c3b"},
|
||||
{file = "watchdog-5.0.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:7d1aa7e4bb0f0c65a1a91ba37c10e19dabf7eaaa282c5787e51371f090748f4b"},
|
||||
{file = "watchdog-5.0.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:726eef8f8c634ac6584f86c9c53353a010d9f311f6c15a034f3800a7a891d941"},
|
||||
{file = "watchdog-5.0.2-py3-none-win32.whl", hash = "sha256:bda40c57115684d0216556671875e008279dea2dc00fcd3dde126ac8e0d7a2fb"},
|
||||
{file = "watchdog-5.0.2-py3-none-win_amd64.whl", hash = "sha256:d010be060c996db725fbce7e3ef14687cdcc76f4ca0e4339a68cc4532c382a73"},
|
||||
{file = "watchdog-5.0.2-py3-none-win_ia64.whl", hash = "sha256:3960136b2b619510569b90f0cd96408591d6c251a75c97690f4553ca88889769"},
|
||||
{file = "watchdog-5.0.2.tar.gz", hash = "sha256:dcebf7e475001d2cdeb020be630dc5b687e9acdd60d16fea6bb4508e7b94cf76"},
|
||||
{file = "watchdog-5.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:85527b882f3facda0579bce9d743ff7f10c3e1e0db0a0d0e28170a7d0e5ce2ea"},
|
||||
{file = "watchdog-5.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:53adf73dcdc0ef04f7735066b4a57a4cd3e49ef135daae41d77395f0b5b692cb"},
|
||||
{file = "watchdog-5.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e25adddab85f674acac303cf1f5835951345a56c5f7f582987d266679979c75b"},
|
||||
{file = "watchdog-5.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f01f4a3565a387080dc49bdd1fefe4ecc77f894991b88ef927edbfa45eb10818"},
|
||||
{file = "watchdog-5.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:91b522adc25614cdeaf91f7897800b82c13b4b8ac68a42ca959f992f6990c490"},
|
||||
{file = "watchdog-5.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d52db5beb5e476e6853da2e2d24dbbbed6797b449c8bf7ea118a4ee0d2c9040e"},
|
||||
{file = "watchdog-5.0.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:94d11b07c64f63f49876e0ab8042ae034674c8653bfcdaa8c4b32e71cfff87e8"},
|
||||
{file = "watchdog-5.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:349c9488e1d85d0a58e8cb14222d2c51cbc801ce11ac3936ab4c3af986536926"},
|
||||
{file = "watchdog-5.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:53a3f10b62c2d569e260f96e8d966463dec1a50fa4f1b22aec69e3f91025060e"},
|
||||
{file = "watchdog-5.0.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:950f531ec6e03696a2414b6308f5c6ff9dab7821a768c9d5788b1314e9a46ca7"},
|
||||
{file = "watchdog-5.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae6deb336cba5d71476caa029ceb6e88047fc1dc74b62b7c4012639c0b563906"},
|
||||
{file = "watchdog-5.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1021223c08ba8d2d38d71ec1704496471ffd7be42cfb26b87cd5059323a389a1"},
|
||||
{file = "watchdog-5.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:752fb40efc7cc8d88ebc332b8f4bcbe2b5cc7e881bccfeb8e25054c00c994ee3"},
|
||||
{file = "watchdog-5.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a2e8f3f955d68471fa37b0e3add18500790d129cc7efe89971b8a4cc6fdeb0b2"},
|
||||
{file = "watchdog-5.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b8ca4d854adcf480bdfd80f46fdd6fb49f91dd020ae11c89b3a79e19454ec627"},
|
||||
{file = "watchdog-5.0.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:90a67d7857adb1d985aca232cc9905dd5bc4803ed85cfcdcfcf707e52049eda7"},
|
||||
{file = "watchdog-5.0.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:720ef9d3a4f9ca575a780af283c8fd3a0674b307651c1976714745090da5a9e8"},
|
||||
{file = "watchdog-5.0.3-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:223160bb359281bb8e31c8f1068bf71a6b16a8ad3d9524ca6f523ac666bb6a1e"},
|
||||
{file = "watchdog-5.0.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:560135542c91eaa74247a2e8430cf83c4342b29e8ad4f520ae14f0c8a19cfb5b"},
|
||||
{file = "watchdog-5.0.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:dd021efa85970bd4824acacbb922066159d0f9e546389a4743d56919b6758b91"},
|
||||
{file = "watchdog-5.0.3-py3-none-manylinux2014_armv7l.whl", hash = "sha256:78864cc8f23dbee55be34cc1494632a7ba30263951b5b2e8fc8286b95845f82c"},
|
||||
{file = "watchdog-5.0.3-py3-none-manylinux2014_i686.whl", hash = "sha256:1e9679245e3ea6498494b3028b90c7b25dbb2abe65c7d07423ecfc2d6218ff7c"},
|
||||
{file = "watchdog-5.0.3-py3-none-manylinux2014_ppc64.whl", hash = "sha256:9413384f26b5d050b6978e6fcd0c1e7f0539be7a4f1a885061473c5deaa57221"},
|
||||
{file = "watchdog-5.0.3-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:294b7a598974b8e2c6123d19ef15de9abcd282b0fbbdbc4d23dfa812959a9e05"},
|
||||
{file = "watchdog-5.0.3-py3-none-manylinux2014_s390x.whl", hash = "sha256:26dd201857d702bdf9d78c273cafcab5871dd29343748524695cecffa44a8d97"},
|
||||
{file = "watchdog-5.0.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:0f9332243355643d567697c3e3fa07330a1d1abf981611654a1f2bf2175612b7"},
|
||||
{file = "watchdog-5.0.3-py3-none-win32.whl", hash = "sha256:c66f80ee5b602a9c7ab66e3c9f36026590a0902db3aea414d59a2f55188c1f49"},
|
||||
{file = "watchdog-5.0.3-py3-none-win_amd64.whl", hash = "sha256:f00b4cf737f568be9665563347a910f8bdc76f88c2970121c86243c8cfdf90e9"},
|
||||
{file = "watchdog-5.0.3-py3-none-win_ia64.whl", hash = "sha256:49f4d36cb315c25ea0d946e018c01bb028048023b9e103d3d3943f58e109dd45"},
|
||||
{file = "watchdog-5.0.3.tar.gz", hash = "sha256:108f42a7f0345042a854d4d0ad0834b741d421330d5f575b81cb27b883500176"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
|
@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "authentik"
|
||||
version = "2024.8.2"
|
||||
version = "2024.8.3"
|
||||
description = ""
|
||||
authors = ["authentik Team <hello@goauthentik.io>"]
|
||||
|
||||
|
158
schema.yml
158
schema.yml
@ -1,7 +1,7 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: authentik
|
||||
version: 2024.8.2
|
||||
version: 2024.8.3
|
||||
description: Making authentication simple.
|
||||
contact:
|
||||
email: hello@goauthentik.io
|
||||
@ -20755,6 +20755,11 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
- in: query
|
||||
name: invalidation_flow
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
- in: query
|
||||
name: is_backchannel
|
||||
schema:
|
||||
@ -26141,9 +26146,9 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName
|
||||
- urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
|
||||
- urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName
|
||||
- urn:oasis:names:tc:SAML:2.0:nameid-format:X509SubjectName
|
||||
- urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
|
||||
- urn:oasis:names:tc:SAML:2.0:nameid-format:transient
|
||||
description: |+
|
||||
@ -37547,6 +37552,7 @@ components:
|
||||
- $ref: '#/components/schemas/PlexAuthenticationChallenge'
|
||||
- $ref: '#/components/schemas/PromptChallenge'
|
||||
- $ref: '#/components/schemas/RedirectChallenge'
|
||||
- $ref: '#/components/schemas/SessionEndChallenge'
|
||||
- $ref: '#/components/schemas/ShellChallenge'
|
||||
- $ref: '#/components/schemas/UserLoginChallenge'
|
||||
discriminator:
|
||||
@ -37573,6 +37579,7 @@ components:
|
||||
ak-source-plex: '#/components/schemas/PlexAuthenticationChallenge'
|
||||
ak-stage-prompt: '#/components/schemas/PromptChallenge'
|
||||
xak-flow-redirect: '#/components/schemas/RedirectChallenge'
|
||||
ak-stage-session-end: '#/components/schemas/SessionEndChallenge'
|
||||
xak-flow-shell: '#/components/schemas/ShellChallenge'
|
||||
ak-stage-user-login: '#/components/schemas/UserLoginChallenge'
|
||||
ClientTypeEnum:
|
||||
@ -40923,6 +40930,11 @@ components:
|
||||
description: DN under which objects are accessible.
|
||||
bind_flow_slug:
|
||||
type: string
|
||||
unbind_flow_slug:
|
||||
type: string
|
||||
nullable: true
|
||||
description: Get slug for unbind flow, defaulting to brand's default flow.
|
||||
readOnly: true
|
||||
application_slug:
|
||||
type: string
|
||||
description: Prioritise backchannel slug over direct application slug
|
||||
@ -40964,6 +40976,7 @@ components:
|
||||
- bind_flow_slug
|
||||
- name
|
||||
- pk
|
||||
- unbind_flow_slug
|
||||
LDAPProvider:
|
||||
type: object
|
||||
description: LDAPProvider Serializer
|
||||
@ -40984,6 +40997,10 @@ components:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used when authorizing this provider.
|
||||
invalidation_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used ending the session from a provider.
|
||||
property_mappings:
|
||||
type: array
|
||||
items:
|
||||
@ -41068,6 +41085,7 @@ components:
|
||||
- assigned_backchannel_application_slug
|
||||
- authorization_flow
|
||||
- component
|
||||
- invalidation_flow
|
||||
- meta_model_name
|
||||
- name
|
||||
- outpost_set
|
||||
@ -41091,6 +41109,10 @@ components:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used when authorizing this provider.
|
||||
invalidation_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used ending the session from a provider.
|
||||
property_mappings:
|
||||
type: array
|
||||
items:
|
||||
@ -41134,6 +41156,7 @@ components:
|
||||
if it contains a semicolon.
|
||||
required:
|
||||
- authorization_flow
|
||||
- invalidation_flow
|
||||
- name
|
||||
LDAPSource:
|
||||
type: object
|
||||
@ -41620,14 +41643,14 @@ components:
|
||||
LoginChallengeTypes:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/RedirectChallenge'
|
||||
- $ref: '#/components/schemas/PlexAuthenticationChallenge'
|
||||
- $ref: '#/components/schemas/AppleLoginChallenge'
|
||||
- $ref: '#/components/schemas/PlexAuthenticationChallenge'
|
||||
discriminator:
|
||||
propertyName: component
|
||||
mapping:
|
||||
xak-flow-redirect: '#/components/schemas/RedirectChallenge'
|
||||
ak-source-plex: '#/components/schemas/PlexAuthenticationChallenge'
|
||||
ak-source-oauth-apple: '#/components/schemas/AppleLoginChallenge'
|
||||
ak-source-plex: '#/components/schemas/PlexAuthenticationChallenge'
|
||||
LoginMetrics:
|
||||
type: object
|
||||
description: Login Metrics per 1h
|
||||
@ -42041,7 +42064,7 @@ components:
|
||||
enum:
|
||||
- urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
|
||||
- urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
|
||||
- urn:oasis:names:tc:SAML:2.0:nameid-format:X509SubjectName
|
||||
- urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName
|
||||
- urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName
|
||||
- urn:oasis:names:tc:SAML:2.0:nameid-format:transient
|
||||
type: string
|
||||
@ -42282,6 +42305,10 @@ components:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used when authorizing this provider.
|
||||
invalidation_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used ending the session from a provider.
|
||||
property_mappings:
|
||||
type: array
|
||||
items:
|
||||
@ -42379,6 +42406,7 @@ components:
|
||||
- assigned_backchannel_application_slug
|
||||
- authorization_flow
|
||||
- component
|
||||
- invalidation_flow
|
||||
- meta_model_name
|
||||
- name
|
||||
- pk
|
||||
@ -42401,6 +42429,10 @@ components:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used when authorizing this provider.
|
||||
invalidation_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used ending the session from a provider.
|
||||
property_mappings:
|
||||
type: array
|
||||
items:
|
||||
@ -42465,6 +42497,7 @@ components:
|
||||
title: Any JWT signed by the JWK of the selected source can be used to authenticate.
|
||||
required:
|
||||
- authorization_flow
|
||||
- invalidation_flow
|
||||
- name
|
||||
OAuth2ProviderSetupURLs:
|
||||
type: object
|
||||
@ -45857,6 +45890,10 @@ components:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used when authorizing this provider.
|
||||
invalidation_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used ending the session from a provider.
|
||||
property_mappings:
|
||||
type: array
|
||||
items:
|
||||
@ -46177,6 +46214,10 @@ components:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used when authorizing this provider.
|
||||
invalidation_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used ending the session from a provider.
|
||||
property_mappings:
|
||||
type: array
|
||||
items:
|
||||
@ -46701,6 +46742,10 @@ components:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used when authorizing this provider.
|
||||
invalidation_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used ending the session from a provider.
|
||||
property_mappings:
|
||||
type: array
|
||||
items:
|
||||
@ -46806,6 +46851,10 @@ components:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used when authorizing this provider.
|
||||
invalidation_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used ending the session from a provider.
|
||||
property_mappings:
|
||||
type: array
|
||||
items:
|
||||
@ -46856,6 +46905,10 @@ components:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used when authorizing this provider.
|
||||
invalidation_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used ending the session from a provider.
|
||||
property_mappings:
|
||||
type: array
|
||||
items:
|
||||
@ -46947,6 +47000,10 @@ components:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used when authorizing this provider.
|
||||
invalidation_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used ending the session from a provider.
|
||||
property_mappings:
|
||||
type: array
|
||||
items:
|
||||
@ -47200,6 +47257,8 @@ components:
|
||||
type: string
|
||||
minLength: 1
|
||||
description: Base URL to SCIM requests, usually ends in /v2
|
||||
verify_certificates:
|
||||
type: boolean
|
||||
token:
|
||||
type: string
|
||||
minLength: 1
|
||||
@ -48465,6 +48524,10 @@ components:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used when authorizing this provider.
|
||||
invalidation_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used ending the session from a provider.
|
||||
property_mappings:
|
||||
type: array
|
||||
items:
|
||||
@ -48509,6 +48572,7 @@ components:
|
||||
- assigned_backchannel_application_slug
|
||||
- authorization_flow
|
||||
- component
|
||||
- invalidation_flow
|
||||
- meta_model_name
|
||||
- name
|
||||
- pk
|
||||
@ -48548,6 +48612,10 @@ components:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used when authorizing this provider.
|
||||
invalidation_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used ending the session from a provider.
|
||||
property_mappings:
|
||||
type: array
|
||||
items:
|
||||
@ -48555,6 +48623,7 @@ components:
|
||||
format: uuid
|
||||
required:
|
||||
- authorization_flow
|
||||
- invalidation_flow
|
||||
- name
|
||||
ProviderTypeEnum:
|
||||
enum:
|
||||
@ -48695,6 +48764,10 @@ components:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used when authorizing this provider.
|
||||
invalidation_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used ending the session from a provider.
|
||||
property_mappings:
|
||||
type: array
|
||||
items:
|
||||
@ -48811,6 +48884,7 @@ components:
|
||||
- client_id
|
||||
- component
|
||||
- external_host
|
||||
- invalidation_flow
|
||||
- meta_model_name
|
||||
- name
|
||||
- outpost_set
|
||||
@ -48835,6 +48909,10 @@ components:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used when authorizing this provider.
|
||||
invalidation_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used ending the session from a provider.
|
||||
property_mappings:
|
||||
type: array
|
||||
items:
|
||||
@ -48905,6 +48983,7 @@ components:
|
||||
required:
|
||||
- authorization_flow
|
||||
- external_host
|
||||
- invalidation_flow
|
||||
- name
|
||||
RACPropertyMapping:
|
||||
type: object
|
||||
@ -48998,6 +49077,10 @@ components:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used when authorizing this provider.
|
||||
invalidation_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used ending the session from a provider.
|
||||
property_mappings:
|
||||
type: array
|
||||
items:
|
||||
@ -49055,6 +49138,7 @@ components:
|
||||
- assigned_backchannel_application_slug
|
||||
- authorization_flow
|
||||
- component
|
||||
- invalidation_flow
|
||||
- meta_model_name
|
||||
- name
|
||||
- outpost_set
|
||||
@ -49078,6 +49162,10 @@ components:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used when authorizing this provider.
|
||||
invalidation_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used ending the session from a provider.
|
||||
property_mappings:
|
||||
type: array
|
||||
items:
|
||||
@ -49094,6 +49182,7 @@ components:
|
||||
description: When set to true, connection tokens will be deleted upon disconnect.
|
||||
required:
|
||||
- authorization_flow
|
||||
- invalidation_flow
|
||||
- name
|
||||
RadiusCheckAccess:
|
||||
type: object
|
||||
@ -49159,6 +49248,10 @@ components:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used when authorizing this provider.
|
||||
invalidation_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used ending the session from a provider.
|
||||
property_mappings:
|
||||
type: array
|
||||
items:
|
||||
@ -49223,6 +49316,7 @@ components:
|
||||
- assigned_backchannel_application_slug
|
||||
- authorization_flow
|
||||
- component
|
||||
- invalidation_flow
|
||||
- meta_model_name
|
||||
- name
|
||||
- outpost_set
|
||||
@ -49313,6 +49407,10 @@ components:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used when authorizing this provider.
|
||||
invalidation_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used ending the session from a provider.
|
||||
property_mappings:
|
||||
type: array
|
||||
items:
|
||||
@ -49337,6 +49435,7 @@ components:
|
||||
if it contains a semicolon.
|
||||
required:
|
||||
- authorization_flow
|
||||
- invalidation_flow
|
||||
- name
|
||||
RedirectChallenge:
|
||||
type: object
|
||||
@ -49647,6 +49746,10 @@ components:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used when authorizing this provider.
|
||||
invalidation_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used ending the session from a provider.
|
||||
property_mappings:
|
||||
type: array
|
||||
items:
|
||||
@ -49785,6 +49888,7 @@ components:
|
||||
- assigned_backchannel_application_slug
|
||||
- authorization_flow
|
||||
- component
|
||||
- invalidation_flow
|
||||
- meta_model_name
|
||||
- name
|
||||
- pk
|
||||
@ -49806,12 +49910,16 @@ components:
|
||||
authorization_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
invalidation_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
file:
|
||||
type: string
|
||||
format: binary
|
||||
required:
|
||||
- authorization_flow
|
||||
- file
|
||||
- invalidation_flow
|
||||
- name
|
||||
SAMLProviderRequest:
|
||||
type: object
|
||||
@ -49830,6 +49938,10 @@ components:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used when authorizing this provider.
|
||||
invalidation_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Flow used ending the session from a provider.
|
||||
property_mappings:
|
||||
type: array
|
||||
items:
|
||||
@ -49912,6 +50024,7 @@ components:
|
||||
required:
|
||||
- acs_url
|
||||
- authorization_flow
|
||||
- invalidation_flow
|
||||
- name
|
||||
SAMLSource:
|
||||
type: object
|
||||
@ -50368,6 +50481,8 @@ components:
|
||||
url:
|
||||
type: string
|
||||
description: Base URL to SCIM requests, usually ends in /v2
|
||||
verify_certificates:
|
||||
type: boolean
|
||||
token:
|
||||
type: string
|
||||
description: Authentication token
|
||||
@ -50451,6 +50566,8 @@ components:
|
||||
type: string
|
||||
minLength: 1
|
||||
description: Base URL to SCIM requests, usually ends in /v2
|
||||
verify_certificates:
|
||||
type: boolean
|
||||
token:
|
||||
type: string
|
||||
minLength: 1
|
||||
@ -50952,6 +51069,37 @@ components:
|
||||
required:
|
||||
- healthy
|
||||
- version
|
||||
SessionEndChallenge:
|
||||
type: object
|
||||
description: Challenge for ending a session
|
||||
properties:
|
||||
flow_info:
|
||||
$ref: '#/components/schemas/ContextualFlowInfo'
|
||||
component:
|
||||
type: string
|
||||
default: ak-stage-session-end
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ErrorDetail'
|
||||
pending_user:
|
||||
type: string
|
||||
pending_user_avatar:
|
||||
type: string
|
||||
application_name:
|
||||
type: string
|
||||
application_launch_url:
|
||||
type: string
|
||||
invalidation_flow_url:
|
||||
type: string
|
||||
brand_name:
|
||||
type: string
|
||||
required:
|
||||
- brand_name
|
||||
- pending_user
|
||||
- pending_user_avatar
|
||||
SessionUser:
|
||||
type: object
|
||||
description: |-
|
||||
|
@ -4,7 +4,7 @@ This package provides a generated API Client for [authentik](https://goauthentik
|
||||
|
||||
### Building
|
||||
|
||||
See https://goauthentik.io/developer-docs/making-schema-changes
|
||||
See https://docs.goauthentik.io/docs/developer-docs/making-schema-changes
|
||||
|
||||
### Consuming
|
||||
|
||||
|
@ -181,9 +181,15 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
||||
@apply_blueprint(
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
"default/default-brand.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
"default/flow-default-provider-invalidation.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"system/providers-oauth2.yaml",
|
||||
)
|
||||
@apply_blueprint("default/flow-default-provider-authorization-implicit-consent.yaml")
|
||||
@apply_blueprint("system/providers-oauth2.yaml")
|
||||
@reconcile_app("authentik_crypto")
|
||||
def test_authorization_logout(self):
|
||||
"""test OpenID Provider flow with logout"""
|
||||
@ -192,6 +198,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
||||
authorization_flow = Flow.objects.get(
|
||||
slug="default-provider-authorization-implicit-consent"
|
||||
)
|
||||
invalidation_flow = Flow.objects.get(slug="default-provider-invalidation-flow")
|
||||
provider = OAuth2Provider.objects.create(
|
||||
name=generate_id(),
|
||||
client_type=ClientTypes.CONFIDENTIAL,
|
||||
@ -200,6 +207,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
||||
signing_key=create_test_cert(),
|
||||
redirect_uris="http://localhost:3000/login/generic_oauth",
|
||||
authorization_flow=authorization_flow,
|
||||
invalidation_flow=invalidation_flow,
|
||||
)
|
||||
provider.property_mappings.set(
|
||||
ScopeMapping.objects.filter(
|
||||
@ -242,11 +250,13 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
||||
self.driver.get("http://localhost:3000/logout")
|
||||
self.wait_for_url(
|
||||
self.url(
|
||||
"authentik_core:if-session-end",
|
||||
application_slug=self.app_slug,
|
||||
"authentik_core:if-flow",
|
||||
flow_slug=invalidation_flow.slug,
|
||||
)
|
||||
)
|
||||
self.driver.find_element(By.ID, "logout").click()
|
||||
flow_executor = self.get_shadow_root("ak-flow-executor")
|
||||
session_end_stage = self.get_shadow_root("ak-stage-session-end", flow_executor)
|
||||
session_end_stage.find_element(By.ID, "logout").click()
|
||||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
|
@ -65,6 +65,7 @@ class TestProviderProxy(SeleniumTestCase):
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
"default/flow-default-provider-invalidation.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"system/providers-oauth2.yaml",
|
||||
@ -82,6 +83,7 @@ class TestProviderProxy(SeleniumTestCase):
|
||||
authorization_flow=Flow.objects.get(
|
||||
slug="default-provider-authorization-implicit-consent"
|
||||
),
|
||||
invalidation_flow=Flow.objects.get(slug="default-provider-invalidation-flow"),
|
||||
internal_host=f"http://{self.host}",
|
||||
external_host="http://localhost:9000",
|
||||
)
|
||||
@ -120,8 +122,10 @@ class TestProviderProxy(SeleniumTestCase):
|
||||
|
||||
self.driver.get("http://localhost:9000/outpost.goauthentik.io/sign_out")
|
||||
sleep(2)
|
||||
full_body_text = self.driver.find_element(By.CSS_SELECTOR, ".pf-c-title.pf-m-3xl").text
|
||||
self.assertIn("You've logged out of", full_body_text)
|
||||
flow_executor = self.get_shadow_root("ak-flow-executor")
|
||||
session_end_stage = self.get_shadow_root("ak-stage-session-end", flow_executor)
|
||||
title = session_end_stage.find_element(By.CSS_SELECTOR, ".pf-c-title.pf-m-3xl").text
|
||||
self.assertIn("You've logged out of", title)
|
||||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
@ -130,6 +134,7 @@ class TestProviderProxy(SeleniumTestCase):
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
"default/flow-default-provider-invalidation.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"system/providers-oauth2.yaml",
|
||||
@ -149,6 +154,7 @@ class TestProviderProxy(SeleniumTestCase):
|
||||
authorization_flow=Flow.objects.get(
|
||||
slug="default-provider-authorization-implicit-consent"
|
||||
),
|
||||
invalidation_flow=Flow.objects.get(slug="default-provider-invalidation-flow"),
|
||||
internal_host=f"http://{self.host}",
|
||||
external_host="http://localhost:9000",
|
||||
basic_auth_enabled=True,
|
||||
@ -191,8 +197,10 @@ class TestProviderProxy(SeleniumTestCase):
|
||||
|
||||
self.driver.get("http://localhost:9000/outpost.goauthentik.io/sign_out")
|
||||
sleep(2)
|
||||
full_body_text = self.driver.find_element(By.CSS_SELECTOR, ".pf-c-title.pf-m-3xl").text
|
||||
self.assertIn("You've logged out of", full_body_text)
|
||||
flow_executor = self.get_shadow_root("ak-flow-executor")
|
||||
session_end_stage = self.get_shadow_root("ak-stage-session-end", flow_executor)
|
||||
title = session_end_stage.find_element(By.CSS_SELECTOR, ".pf-c-title.pf-m-3xl").text
|
||||
self.assertIn("You've logged out of", title)
|
||||
|
||||
|
||||
# TODO: Fix flaky test
|
||||
|
@ -414,6 +414,7 @@ class TestProviderSAML(SeleniumTestCase):
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
"default/flow-default-provider-invalidation.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"system/providers-saml.yaml",
|
||||
@ -425,6 +426,7 @@ class TestProviderSAML(SeleniumTestCase):
|
||||
authorization_flow = Flow.objects.get(
|
||||
slug="default-provider-authorization-implicit-consent"
|
||||
)
|
||||
invalidation_flow = Flow.objects.get(slug="default-provider-invalidation-flow")
|
||||
provider: SAMLProvider = SAMLProvider.objects.create(
|
||||
name="saml-test",
|
||||
acs_url="http://localhost:9009/saml/acs",
|
||||
@ -432,11 +434,12 @@ class TestProviderSAML(SeleniumTestCase):
|
||||
issuer="authentik-e2e",
|
||||
sp_binding=SAMLBindings.POST,
|
||||
authorization_flow=authorization_flow,
|
||||
invalidation_flow=invalidation_flow,
|
||||
signing_kp=create_test_cert(),
|
||||
)
|
||||
provider.property_mappings.set(SAMLPropertyMapping.objects.all())
|
||||
provider.save()
|
||||
app = Application.objects.create(
|
||||
Application.objects.create(
|
||||
name="SAML",
|
||||
slug="authentik-saml",
|
||||
provider=provider,
|
||||
@ -447,9 +450,11 @@ class TestProviderSAML(SeleniumTestCase):
|
||||
self.wait_for_url("http://localhost:9009/")
|
||||
|
||||
self.driver.get("http://localhost:9009/saml/logout")
|
||||
self.wait_for_url(
|
||||
self.url(
|
||||
"authentik_core:if-session-end",
|
||||
application_slug=app.slug,
|
||||
)
|
||||
should_url = self.url(
|
||||
"authentik_core:if-flow",
|
||||
flow_slug=invalidation_flow.slug,
|
||||
)
|
||||
self.wait.until(
|
||||
lambda driver: driver.current_url.startswith(should_url),
|
||||
f"URL {self.driver.current_url} doesn't match expected URL {should_url}",
|
||||
)
|
||||
|
109
tests/wdio/.gitignore
vendored
109
tests/wdio/.gitignore
vendored
@ -1,109 +0,0 @@
|
||||
|
||||
# Created by https://www.gitignore.io/api/node
|
||||
# Edit at https://www.gitignore.io/?templates=node
|
||||
|
||||
### Node ###
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# nuxt.js build output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Uncomment the public line if your project uses Gatsby
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# https://create-react-app.dev/docs/using-the-public-folder/#docsNav
|
||||
# public
|
||||
|
||||
# Storybook build outputs
|
||||
.out
|
||||
.storybook-out
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# Temporary folders
|
||||
tmp/
|
||||
temp/
|
||||
|
||||
# End of https://www.gitignore.io/api/node
|
@ -1,7 +0,0 @@
|
||||
# don't ever lint node_modules
|
||||
node_modules
|
||||
# don't lint nyc coverage output
|
||||
coverage
|
||||
# Prettier breaks the tsconfig file
|
||||
tsconfig.json
|
||||
.eslintrc.json
|
@ -1,22 +0,0 @@
|
||||
{
|
||||
"arrowParens": "always",
|
||||
"bracketSpacing": true,
|
||||
"embeddedLanguageFormatting": "auto",
|
||||
"htmlWhitespaceSensitivity": "css",
|
||||
"insertPragma": false,
|
||||
"jsxSingleQuote": false,
|
||||
"printWidth": 100,
|
||||
"proseWrap": "preserve",
|
||||
"quoteProps": "consistent",
|
||||
"requirePragma": false,
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 4,
|
||||
"trailingComma": "all",
|
||||
"useTabs": false,
|
||||
"vueIndentScriptAndStyle": false,
|
||||
"plugins": ["@trivago/prettier-plugin-sort-imports"],
|
||||
"importOrderSeparation": true,
|
||||
"importOrderSortSpecifiers": true,
|
||||
"importOrderParserPlugins": ["typescript", "classProperties", "decorators-legacy"]
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
.PHONY: help precommit admin-user test-good-login test-bad-login
|
||||
|
||||
help: ## Show this help
|
||||
@echo "\nSpecify a command. The choices are:\n"
|
||||
@grep -E '^[0-9a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
|
||||
awk 'BEGIN {FS = ":.*?## "}; {printf " \033[0;36m%-20s\033[m %s\n", $$1, $$2}'
|
||||
@echo ""
|
||||
|
||||
ROOT := $(shell git rev-parse --show-toplevel 2> /dev/null)
|
||||
WDIO = npm run wdio
|
||||
SPEC = $(WDIO) -- --logLevel warn --spec ./test/specs
|
||||
|
||||
LOCAL_BLUEPRINTS=$(ROOT)/blueprints/local
|
||||
|
||||
node_modules: ## Runs `npm install` to prepare this feature
|
||||
npm ci
|
||||
|
||||
precommit: node_modules ## Run the precommit: spell check all comments, eslint with sonarJS, prettier-write
|
||||
npm run precommit
|
||||
|
||||
# Actual tests are down below:
|
||||
|
||||
$(ROOT)/blueprints/local/admin-user.yaml:
|
||||
mkdir -p $(LOCAL_BLUEPRINTS)
|
||||
cp ./blueprints/admin-user.yaml $(LOCAL_BLUEPRINTS)
|
||||
cd $(ROOT) && ak apply_blueprint local/admin-user.yaml
|
||||
|
||||
|
||||
admin-user: $(ROOT)/blueprints/local/admin-user.yaml
|
||||
|
||||
|
||||
test-good-login: node_modules admin-user ## Test that we can log into the server. Requires a running instance of the server.
|
||||
$(SPEC)/good-login.ts
|
||||
|
||||
|
||||
test-bad-login: node_modules admin-user ## Test that bad usernames and passwords create appropriate error messages
|
||||
$(SPEC)/bad-logins.ts
|
||||
|
||||
|
||||
test-application-wizard: node_modules admin-user ## Test that the application wizard works as expected
|
||||
$(SPEC)/new-application-by-wizard.ts
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user