Compare commits
	
		
			67 Commits
		
	
	
		
			web/bug/fi
			...
			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 | 
							
								
								
									
										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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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",
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
        )
 | 
			
		||||
@ -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(
 | 
			
		||||
 | 
			
		||||
@ -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")
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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
 | 
			
		||||
@ -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": {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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.2024083.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.2024083.1 h1:OPo2VejMkS5WjYw5zIjfuxR9XUbTKs4m+sACrPKcm9U=
 | 
			
		||||
goauthentik.io/api/v3 v3.2024083.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=
 | 
			
		||||
 | 
			
		||||
@ -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-25 00:08+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
 | 
			
		||||
 | 
			
		||||
@ -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-25 00:08+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."
 | 
			
		||||
 | 
			
		||||
										
											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-25 00:08+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."
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										320
									
								
								poetry.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										320
									
								
								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.6"
 | 
			
		||||
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.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:30f467c5345d9dfdcc0afdb10e018e47f092e383447500f125b4e013236bf14b"},
 | 
			
		||||
    {file = "debugpy-1.8.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d73d8c52614432f4215d0fe79a7e595d0dd162b5c15233762565be2f014803b"},
 | 
			
		||||
    {file = "debugpy-1.8.6-cp310-cp310-win32.whl", hash = "sha256:e3e182cd98eac20ee23a00653503315085b29ab44ed66269482349d307b08df9"},
 | 
			
		||||
    {file = "debugpy-1.8.6-cp310-cp310-win_amd64.whl", hash = "sha256:e3a82da039cfe717b6fb1886cbbe5c4a3f15d7df4765af857f4307585121c2dd"},
 | 
			
		||||
    {file = "debugpy-1.8.6-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:67479a94cf5fd2c2d88f9615e087fcb4fec169ec780464a3f2ba4a9a2bb79955"},
 | 
			
		||||
    {file = "debugpy-1.8.6-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fb8653f6cbf1dd0a305ac1aa66ec246002145074ea57933978346ea5afdf70b"},
 | 
			
		||||
    {file = "debugpy-1.8.6-cp311-cp311-win32.whl", hash = "sha256:cdaf0b9691879da2d13fa39b61c01887c34558d1ff6e5c30e2eb698f5384cd43"},
 | 
			
		||||
    {file = "debugpy-1.8.6-cp311-cp311-win_amd64.whl", hash = "sha256:43996632bee7435583952155c06881074b9a742a86cee74e701d87ca532fe833"},
 | 
			
		||||
    {file = "debugpy-1.8.6-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:db891b141fc6ee4b5fc6d1cc8035ec329cabc64bdd2ae672b4550c87d4ecb128"},
 | 
			
		||||
    {file = "debugpy-1.8.6-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:567419081ff67da766c898ccf21e79f1adad0e321381b0dfc7a9c8f7a9347972"},
 | 
			
		||||
    {file = "debugpy-1.8.6-cp312-cp312-win32.whl", hash = "sha256:c9834dfd701a1f6bf0f7f0b8b1573970ae99ebbeee68314116e0ccc5c78eea3c"},
 | 
			
		||||
    {file = "debugpy-1.8.6-cp312-cp312-win_amd64.whl", hash = "sha256:e4ce0570aa4aca87137890d23b86faeadf184924ad892d20c54237bcaab75d8f"},
 | 
			
		||||
    {file = "debugpy-1.8.6-cp38-cp38-macosx_14_0_x86_64.whl", hash = "sha256:df5dc9eb4ca050273b8e374a4cd967c43be1327eeb42bfe2f58b3cdfe7c68dcb"},
 | 
			
		||||
    {file = "debugpy-1.8.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a85707c6a84b0c5b3db92a2df685b5230dd8fb8c108298ba4f11dba157a615a"},
 | 
			
		||||
    {file = "debugpy-1.8.6-cp38-cp38-win32.whl", hash = "sha256:538c6cdcdcdad310bbefd96d7850be1cd46e703079cc9e67d42a9ca776cdc8a8"},
 | 
			
		||||
    {file = "debugpy-1.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:22140bc02c66cda6053b6eb56dfe01bbe22a4447846581ba1dd6df2c9f97982d"},
 | 
			
		||||
    {file = "debugpy-1.8.6-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:c1cef65cffbc96e7b392d9178dbfd524ab0750da6c0023c027ddcac968fd1caa"},
 | 
			
		||||
    {file = "debugpy-1.8.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1e60bd06bb3cc5c0e957df748d1fab501e01416c43a7bdc756d2a992ea1b881"},
 | 
			
		||||
    {file = "debugpy-1.8.6-cp39-cp39-win32.whl", hash = "sha256:f7158252803d0752ed5398d291dee4c553bb12d14547c0e1843ab74ee9c31123"},
 | 
			
		||||
    {file = "debugpy-1.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:3358aa619a073b620cd0d51d8a6176590af24abcc3fe2e479929a154bf591b51"},
 | 
			
		||||
    {file = "debugpy-1.8.6-py2.py3-none-any.whl", hash = "sha256:b48892df4d810eff21d3ef37274f4c60d32cdcafc462ad5647239036b0f0649f"},
 | 
			
		||||
    {file = "debugpy-1.8.6.zip", hash = "sha256:c931a9371a86784cee25dec8d65bc2dc7a21f3f1552e3833d9ef8f919d22280a"},
 | 
			
		||||
    {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.147.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.147.0-py2.py3-none-any.whl", hash = "sha256:c6ecfa193c695baa41e84562d8f8f244fcd164419eca3fc9fd7565646668f9b2"},
 | 
			
		||||
    {file = "google_api_python_client-2.147.0.tar.gz", hash = "sha256:e864c2cf61d34c00f05278b8bdb72b93b6fa34f0de9ead51d20435f3b65f91be"},
 | 
			
		||||
    {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"
 | 
			
		||||
@ -4210,29 +4201,29 @@ pyasn1 = ">=0.1.3"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "ruff"
 | 
			
		||||
version = "0.6.8"
 | 
			
		||||
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.8-py3-none-linux_armv6l.whl", hash = "sha256:77944bca110ff0a43b768f05a529fecd0706aac7bcce36d7f1eeb4cbfca5f0f2"},
 | 
			
		||||
    {file = "ruff-0.6.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:27b87e1801e786cd6ede4ada3faa5e254ce774de835e6723fd94551464c56b8c"},
 | 
			
		||||
    {file = "ruff-0.6.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cd48f945da2a6334f1793d7f701725a76ba93bf3d73c36f6b21fb04d5338dcf5"},
 | 
			
		||||
    {file = "ruff-0.6.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:677e03c00f37c66cea033274295a983c7c546edea5043d0c798833adf4cf4c6f"},
 | 
			
		||||
    {file = "ruff-0.6.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9f1476236b3eacfacfc0f66aa9e6cd39f2a624cb73ea99189556015f27c0bdeb"},
 | 
			
		||||
    {file = "ruff-0.6.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f5a2f17c7d32991169195d52a04c95b256378bbf0de8cb98478351eb70d526f"},
 | 
			
		||||
    {file = "ruff-0.6.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5fd0d4b7b1457c49e435ee1e437900ced9b35cb8dc5178921dfb7d98d65a08d0"},
 | 
			
		||||
    {file = "ruff-0.6.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8034b19b993e9601f2ddf2c517451e17a6ab5cdb1c13fdff50c1442a7171d87"},
 | 
			
		||||
    {file = "ruff-0.6.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6cfb227b932ba8ef6e56c9f875d987973cd5e35bc5d05f5abf045af78ad8e098"},
 | 
			
		||||
    {file = "ruff-0.6.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef0411eccfc3909269fed47c61ffebdcb84a04504bafa6b6df9b85c27e813b0"},
 | 
			
		||||
    {file = "ruff-0.6.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:007dee844738c3d2e6c24ab5bc7d43c99ba3e1943bd2d95d598582e9c1b27750"},
 | 
			
		||||
    {file = "ruff-0.6.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ce60058d3cdd8490e5e5471ef086b3f1e90ab872b548814e35930e21d848c9ce"},
 | 
			
		||||
    {file = "ruff-0.6.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1085c455d1b3fdb8021ad534379c60353b81ba079712bce7a900e834859182fa"},
 | 
			
		||||
    {file = "ruff-0.6.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:70edf6a93b19481affd287d696d9e311388d808671bc209fb8907b46a8c3af44"},
 | 
			
		||||
    {file = "ruff-0.6.8-py3-none-win32.whl", hash = "sha256:792213f7be25316f9b46b854df80a77e0da87ec66691e8f012f887b4a671ab5a"},
 | 
			
		||||
    {file = "ruff-0.6.8-py3-none-win_amd64.whl", hash = "sha256:ec0517dc0f37cad14a5319ba7bba6e7e339d03fbf967a6d69b0907d61be7a263"},
 | 
			
		||||
    {file = "ruff-0.6.8-py3-none-win_arm64.whl", hash = "sha256:8d3bb2e3fbb9875172119021a13eed38849e762499e3cfde9588e4b4d70968dc"},
 | 
			
		||||
    {file = "ruff-0.6.8.tar.gz", hash = "sha256:a5bf44b1aa0adaf6d9d20f86162b34f7c593bfedabc51239953e446aefc8ce18"},
 | 
			
		||||
    {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.2"
 | 
			
		||||
version = "9.3.3"
 | 
			
		||||
description = "Twilio API client and TwiML generator"
 | 
			
		||||
optional = false
 | 
			
		||||
python-versions = ">=3.7.0"
 | 
			
		||||
files = [
 | 
			
		||||
    {file = "twilio-9.3.2-py2.py3-none-any.whl", hash = "sha256:7fcb2da241d2264b17fbab9ac0ca829c0f0abe23ce6db15d4bb0d4d2d583f953"},
 | 
			
		||||
    {file = "twilio-9.3.2.tar.gz", hash = "sha256:250fc6ce6960aa97a2e2ee7e718e3bc0e73d69731b97fe160ed2097f3cbeb5a8"},
 | 
			
		||||
    {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.31.0"
 | 
			
		||||
version = "0.31.1"
 | 
			
		||||
description = "The lightning-fast ASGI server."
 | 
			
		||||
optional = false
 | 
			
		||||
python-versions = ">=3.8"
 | 
			
		||||
files = [
 | 
			
		||||
    {file = "uvicorn-0.31.0-py3-none-any.whl", hash = "sha256:cac7be4dd4d891c363cd942160a7b02e69150dcbc7a36be04d5f4af4b17c8ced"},
 | 
			
		||||
    {file = "uvicorn-0.31.0.tar.gz", hash = "sha256:13bc21373d103859f68fe739608e2eb054a816dea79189bc3ca08ea89a275906"},
 | 
			
		||||
    {file = "uvicorn-0.31.1-py3-none-any.whl", hash = "sha256:adc42d9cac80cf3e51af97c1851648066841e7cfb6993a4ca8de29ac1548ed41"},
 | 
			
		||||
    {file = "uvicorn-0.31.1.tar.gz", hash = "sha256:f5167919867b161b7bcaf32646c6a94cdbd4c3aa2eb5c17d36bb9aa5cfd8c493"},
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[package.dependencies]
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										156
									
								
								schema.yml
									
									
									
									
									
								
							
							
						
						
									
										156
									
								
								schema.yml
									
									
									
									
									
								
							@ -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
 | 
			
		||||
@ -1,16 +0,0 @@
 | 
			
		||||
version: 1
 | 
			
		||||
entries:
 | 
			
		||||
    - attrs:
 | 
			
		||||
          email: test-admin@goauthentik.io
 | 
			
		||||
          is_active: true
 | 
			
		||||
          name: authentik Default Admin
 | 
			
		||||
          password: test-runner
 | 
			
		||||
          path: users
 | 
			
		||||
          type: internal
 | 
			
		||||
          groups:
 | 
			
		||||
              - !Find [authentik_core.group, [name, "authentik Admins"]]
 | 
			
		||||
          conditions: []
 | 
			
		||||
      identifiers:
 | 
			
		||||
          username: akadmin
 | 
			
		||||
      model: authentik_core.user
 | 
			
		||||
      state: present
 | 
			
		||||
@ -1,84 +0,0 @@
 | 
			
		||||
import eslint from "@eslint/js";
 | 
			
		||||
import tsparser from "@typescript-eslint/parser";
 | 
			
		||||
import litconf from "eslint-plugin-lit";
 | 
			
		||||
import wcconf from "eslint-plugin-wc";
 | 
			
		||||
import globals from "globals";
 | 
			
		||||
import tseslint from "typescript-eslint";
 | 
			
		||||
 | 
			
		||||
export default [
 | 
			
		||||
    // You would not believe how much this change has frustrated users: ["if an ignores key is used
 | 
			
		||||
    // without any other keys in the configuration object, then the patterns act as global
 | 
			
		||||
    // ignores"](https://eslint.org/docs/latest/use/configure/ignore)
 | 
			
		||||
    {
 | 
			
		||||
        ignores: [
 | 
			
		||||
            "dist/",
 | 
			
		||||
            // don't lint the cache
 | 
			
		||||
            ".wireit/",
 | 
			
		||||
            // let packages have their own configurations
 | 
			
		||||
            "packages/",
 | 
			
		||||
            // don't ever lint node_modules
 | 
			
		||||
            "node_modules/",
 | 
			
		||||
            ".storybook/*",
 | 
			
		||||
            // don't lint build output (make sure it's set to your correct build folder name)
 | 
			
		||||
            // don't lint nyc coverage output
 | 
			
		||||
            "coverage/",
 | 
			
		||||
            "src/locale-codes.ts",
 | 
			
		||||
            "storybook-static/",
 | 
			
		||||
            "src/locales/",
 | 
			
		||||
        ],
 | 
			
		||||
    },
 | 
			
		||||
    eslint.configs.recommended,
 | 
			
		||||
    wcconf.configs["flat/recommended"],
 | 
			
		||||
    litconf.configs["flat/recommended"],
 | 
			
		||||
    ...tseslint.configs.recommended,
 | 
			
		||||
    {
 | 
			
		||||
        languageOptions: {
 | 
			
		||||
            parser: tsparser,
 | 
			
		||||
            parserOptions: {
 | 
			
		||||
                ecmaVersion: 12,
 | 
			
		||||
                sourceType: "module",
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        files: ["src/**"],
 | 
			
		||||
        rules: {
 | 
			
		||||
            "no-unused-vars": "off",
 | 
			
		||||
            "no-console": ["error", { allow: ["debug", "warn", "error"] }],
 | 
			
		||||
            "@typescript-eslint/ban-ts-comment": "off",
 | 
			
		||||
            "@typescript-eslint/no-unused-vars": [
 | 
			
		||||
                "error",
 | 
			
		||||
                {
 | 
			
		||||
                    argsIgnorePattern: "^_",
 | 
			
		||||
                    varsIgnorePattern: "^_",
 | 
			
		||||
                    caughtErrorsIgnorePattern: "^_",
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        languageOptions: {
 | 
			
		||||
            parser: tsparser,
 | 
			
		||||
            parserOptions: {
 | 
			
		||||
                ecmaVersion: 12,
 | 
			
		||||
                sourceType: "module",
 | 
			
		||||
            },
 | 
			
		||||
            globals: {
 | 
			
		||||
                ...globals.nodeBuiltin,
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        files: ["scripts/*.mjs", "*.ts", "*.mjs"],
 | 
			
		||||
        rules: {
 | 
			
		||||
            "no-unused-vars": "off",
 | 
			
		||||
            // We WANT our scripts to output to the console!
 | 
			
		||||
            "no-console": "off",
 | 
			
		||||
            "@typescript-eslint/ban-ts-comment": "off",
 | 
			
		||||
            "@typescript-eslint/no-unused-vars": [
 | 
			
		||||
                "error",
 | 
			
		||||
                {
 | 
			
		||||
                    argsIgnorePattern: "^_",
 | 
			
		||||
                    varsIgnorePattern: "^_",
 | 
			
		||||
                    caughtErrorsIgnorePattern: "^_",
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
							
								
								
									
										15683
									
								
								tests/wdio/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										15683
									
								
								tests/wdio/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -1,44 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "@goauthentik/web-tests",
 | 
			
		||||
    "dependencies": {
 | 
			
		||||
        "chromedriver": "^129.0.1",
 | 
			
		||||
        "lockfile-lint": "^4.14.0",
 | 
			
		||||
        "syncpack": "^13.0.0"
 | 
			
		||||
    },
 | 
			
		||||
    "devDependencies": {
 | 
			
		||||
        "@eslint/js": "^9.11.1",
 | 
			
		||||
        "@trivago/prettier-plugin-sort-imports": "^4.3.0",
 | 
			
		||||
        "@types/mocha": "^10.0.8",
 | 
			
		||||
        "@typescript-eslint/eslint-plugin": "^8.7.0",
 | 
			
		||||
        "@typescript-eslint/parser": "^8.7.0",
 | 
			
		||||
        "@wdio/cli": "^9.1.2",
 | 
			
		||||
        "@wdio/local-runner": "^9.1.2",
 | 
			
		||||
        "@wdio/mocha-framework": "^9.1.2",
 | 
			
		||||
        "@wdio/spec-reporter": "^9.1.2",
 | 
			
		||||
        "eslint-plugin-lit": "^1.14.0",
 | 
			
		||||
        "eslint-plugin-sonarjs": "^2.0.2",
 | 
			
		||||
        "eslint-plugin-wc": "^2.1.0",
 | 
			
		||||
        "eslint": "^9.11.1",
 | 
			
		||||
        "npm-run-all": "^4.1.5",
 | 
			
		||||
        "prettier": "^3.3.3",
 | 
			
		||||
        "typescript-eslint": "^8.7.0",
 | 
			
		||||
        "typescript": "^5.6.2",
 | 
			
		||||
        "wdio-wait-for": "^3.0.11"
 | 
			
		||||
    },
 | 
			
		||||
    "engines": {
 | 
			
		||||
        "node": ">=20"
 | 
			
		||||
    },
 | 
			
		||||
    "private": true,
 | 
			
		||||
    "scripts": {
 | 
			
		||||
        "lint": "eslint . --max-warnings 0 --fix",
 | 
			
		||||
        "lint:lockfile": "lockfile-lint --path package.json --type npm --allowed-hosts npm --validate-https",
 | 
			
		||||
        "lint:package": "syncpack format -i '    '",
 | 
			
		||||
        "lint:precommit": "eslint --max-warnings 0 --config ./.eslintrc.precommit.json $(git status --porcelain . | grep '^[AM?][M?]' | cut -d'/' -f3- | grep -E '\\.(ts|js|tsx|jsx)$')",
 | 
			
		||||
        "lint:spelling": "codespell -D - -D $(git rev-parse --show-toplevel 2> /dev/null)/.github/codespell-dictionary.txt -I $(git rev-parse --show-toplevel 2> /dev/null)/.github/codespell-words.txt ./test -s",
 | 
			
		||||
        "precommit": "run-s lint:precommit lint:spelling prettier",
 | 
			
		||||
        "prettier": "prettier --write .",
 | 
			
		||||
        "prettier-check": "prettier --check .",
 | 
			
		||||
        "wdio": "wdio run ./wdio.conf.ts"
 | 
			
		||||
    },
 | 
			
		||||
    "type": "module"
 | 
			
		||||
}
 | 
			
		||||
@ -8,6 +8,9 @@
 | 
			
		||||
// and we'll have one unified way of doing this.  I can only hope.
 | 
			
		||||
 | 
			
		||||
const rawCssImportMaps = [
 | 
			
		||||
    'import AKGlobal from "../../../common/styles/authentik.css";',
 | 
			
		||||
    'import AKGlobal from "../../common/styles/authentik.css";',
 | 
			
		||||
    'import AKGlobal from "../common/styles/authentik.css";',
 | 
			
		||||
    'import AKGlobal from "@goauthentik/common/styles/authentik.css";',
 | 
			
		||||
    'import PFAlert from "@patternfly/patternfly/components/Alert/alert.css";',
 | 
			
		||||
    'import PFAlertGroup from "@patternfly/patternfly/components/AlertGroup/alert-group.css";',
 | 
			
		||||
@ -50,6 +53,7 @@ const rawCssImportMaps = [
 | 
			
		||||
    'import PFNotificationDrawer from "@patternfly/patternfly/components/NotificationDrawer/notification-drawer.css";',
 | 
			
		||||
    'import PFPage from "@patternfly/patternfly/components/Page/page.css";',
 | 
			
		||||
    'import PFPagination from "@patternfly/patternfly/components/Pagination/pagination.css";',
 | 
			
		||||
    'import PFProgress from "@patternfly/patternfly/components/Progress/progress.css";',
 | 
			
		||||
    'import PFProgressStepper from "@patternfly/patternfly/components/ProgressStepper/progress-stepper.css";',
 | 
			
		||||
    'import PFRadio from "@patternfly/patternfly/components/Radio/radio.css";',
 | 
			
		||||
    'import PFSelect from "@patternfly/patternfly/components/Select/select.css";',
 | 
			
		||||
@ -62,7 +66,6 @@ const rawCssImportMaps = [
 | 
			
		||||
    'import PFSwitch from "@patternfly/patternfly/components/Switch/switch.css";',
 | 
			
		||||
    'import PFTable from "@patternfly/patternfly/components/Table/table.css";',
 | 
			
		||||
    'import PFTabs from "@patternfly/patternfly/components/Tabs/tabs.css";',
 | 
			
		||||
    'import PFText from "@patternfly/patternfly/utilities/Text/text.css";',
 | 
			
		||||
    'import PFTitle from "@patternfly/patternfly/components/Title/title.css";',
 | 
			
		||||
    'import PFToggleGroup from "@patternfly/patternfly/components/ToggleGroup/toggle-group.css";',
 | 
			
		||||
    'import PFToolbar from "@patternfly/patternfly/components/Toolbar/toolbar.css";',
 | 
			
		||||
 | 
			
		||||
@ -16,7 +16,6 @@ const config: StorybookConfig = {
 | 
			
		||||
        "@storybook/addon-controls",
 | 
			
		||||
        "@storybook/addon-links",
 | 
			
		||||
        "@storybook/addon-essentials",
 | 
			
		||||
        "@jeysal/storybook-addon-css-user-preferences",
 | 
			
		||||
        "storybook-addon-mock",
 | 
			
		||||
    ],
 | 
			
		||||
    staticDirs: [
 | 
			
		||||
 | 
			
		||||
@ -41,6 +41,10 @@ export default [
 | 
			
		||||
        },
 | 
			
		||||
        files: ["src/**"],
 | 
			
		||||
        rules: {
 | 
			
		||||
            // "lit/attribute-names": "error",
 | 
			
		||||
            "lit/no-private-properties": "error",
 | 
			
		||||
            // "lit/prefer-nothing": "warn",
 | 
			
		||||
            "lit/no-template-bind": "error",
 | 
			
		||||
            "no-unused-vars": "off",
 | 
			
		||||
            "no-console": ["error", { allow: ["debug", "warn", "error"] }],
 | 
			
		||||
            "@typescript-eslint/ban-ts-comment": "off",
 | 
			
		||||
@ -63,6 +67,7 @@ export default [
 | 
			
		||||
            },
 | 
			
		||||
            globals: {
 | 
			
		||||
                ...globals.nodeBuiltin,
 | 
			
		||||
                ...globals.node,
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        files: ["scripts/*.mjs", "*.ts", "*.mjs"],
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										17192
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										17192
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -11,7 +11,7 @@
 | 
			
		||||
        "@floating-ui/dom": "^1.6.11",
 | 
			
		||||
        "@formatjs/intl-listformat": "^7.5.7",
 | 
			
		||||
        "@fortawesome/fontawesome-free": "^6.6.0",
 | 
			
		||||
        "@goauthentik/api": "^2024.8.3-1727449099",
 | 
			
		||||
        "@goauthentik/api": "^2024.8.3-1728918276",
 | 
			
		||||
        "@lit/context": "^1.1.2",
 | 
			
		||||
        "@lit/localize": "^0.12.2",
 | 
			
		||||
        "@lit/reactive-element": "^2.0.4",
 | 
			
		||||
@ -20,6 +20,7 @@
 | 
			
		||||
        "@patternfly/elements": "^4.0.2",
 | 
			
		||||
        "@patternfly/patternfly": "^4.224.2",
 | 
			
		||||
        "@sentry/browser": "^8.32.0",
 | 
			
		||||
        "@spotlightjs/spotlight": "^2.4.2",
 | 
			
		||||
        "@webcomponents/webcomponentsjs": "^2.8.0",
 | 
			
		||||
        "base64-js": "^1.5.1",
 | 
			
		||||
        "chart.js": "^4.4.4",
 | 
			
		||||
@ -41,75 +42,56 @@
 | 
			
		||||
        "yaml": "^2.5.1"
 | 
			
		||||
    },
 | 
			
		||||
    "devDependencies": {
 | 
			
		||||
        "@babel/core": "^7.25.2",
 | 
			
		||||
        "@babel/plugin-proposal-class-properties": "^7.18.6",
 | 
			
		||||
        "@babel/plugin-proposal-decorators": "^7.24.7",
 | 
			
		||||
        "@babel/plugin-transform-private-methods": "^7.25.4",
 | 
			
		||||
        "@babel/plugin-transform-private-property-in-object": "^7.24.7",
 | 
			
		||||
        "@babel/plugin-transform-runtime": "^7.25.4",
 | 
			
		||||
        "@babel/preset-env": "^7.25.4",
 | 
			
		||||
        "@babel/preset-typescript": "^7.24.7",
 | 
			
		||||
        "@changesets/cli": "^2.27.8",
 | 
			
		||||
        "@custom-elements-manifest/analyzer": "^0.10.2",
 | 
			
		||||
        "@eslint/js": "^9.11.1",
 | 
			
		||||
        "@genesiscommunitysuccess/custom-elements-lsp": "^5.0.3",
 | 
			
		||||
        "@hcaptcha/types": "^1.0.4",
 | 
			
		||||
        "@jeysal/storybook-addon-css-user-preferences": "^0.2.0",
 | 
			
		||||
        "@lit/localize-tools": "^0.8.0",
 | 
			
		||||
        "@rollup/plugin-replace": "^6.0.1",
 | 
			
		||||
        "@spotlightjs/spotlight": "^2.4.1",
 | 
			
		||||
        "@storybook/addon-essentials": "^8.3.4",
 | 
			
		||||
        "@storybook/addon-links": "^8.3.4",
 | 
			
		||||
        "@storybook/api": "^7.6.17",
 | 
			
		||||
        "@storybook/blocks": "^8.0.8",
 | 
			
		||||
        "@storybook/blocks": "^8.3.4",
 | 
			
		||||
        "@storybook/builder-vite": "^8.3.4",
 | 
			
		||||
        "@storybook/manager-api": "^8.3.4",
 | 
			
		||||
        "@storybook/web-components": "^8.3.4",
 | 
			
		||||
        "@storybook/web-components-vite": "^8.3.4",
 | 
			
		||||
        "@trivago/prettier-plugin-sort-imports": "^4.3.0",
 | 
			
		||||
        "@types/chart.js": "^2.9.41",
 | 
			
		||||
        "@types/codemirror": "5.60.15",
 | 
			
		||||
        "@types/codemirror": "^5.60.15",
 | 
			
		||||
        "@types/eslint__js": "^8.42.3",
 | 
			
		||||
        "@types/grecaptcha": "^3.0.9",
 | 
			
		||||
        "@types/guacamole-common-js": "1.5.2",
 | 
			
		||||
        "@types/guacamole-common-js": "^1.5.2",
 | 
			
		||||
        "@types/mocha": "^10.0.8",
 | 
			
		||||
        "@types/node": "^22.7.4",
 | 
			
		||||
        "@types/showdown": "^2.0.6",
 | 
			
		||||
        "@typescript-eslint/eslint-plugin": "^8.7.0",
 | 
			
		||||
        "@typescript-eslint/parser": "^8.7.0",
 | 
			
		||||
        "@wdio/browser-runner": "^8.40.5",
 | 
			
		||||
        "@wdio/cli": "^8.40.5",
 | 
			
		||||
        "@wdio/mocha-framework": "^9.1.0",
 | 
			
		||||
        "@wdio/spec-reporter": "^9.1.0",
 | 
			
		||||
        "babel-plugin-macros": "^3.1.0",
 | 
			
		||||
        "babel-plugin-tsconfig-paths": "^1.0.3",
 | 
			
		||||
        "@typescript-eslint/eslint-plugin": "^8.8.0",
 | 
			
		||||
        "@typescript-eslint/parser": "^8.8.0",
 | 
			
		||||
        "@wdio/browser-runner": "^9.1.2",
 | 
			
		||||
        "@wdio/cli": "^9.1.2",
 | 
			
		||||
        "@wdio/spec-reporter": "^9.1.2",
 | 
			
		||||
        "chokidar": "^4.0.1",
 | 
			
		||||
        "cross-env": "^7.0.3",
 | 
			
		||||
        "chromedriver": "^129.0.2",
 | 
			
		||||
        "esbuild": "^0.24.0",
 | 
			
		||||
        "eslint": "^9.11.1",
 | 
			
		||||
        "eslint-plugin-lit": "^1.14.0",
 | 
			
		||||
        "eslint-plugin-wc": "^2.1.0",
 | 
			
		||||
        "eslint-plugin-lit": "^1.15.0",
 | 
			
		||||
        "eslint-plugin-wc": "^2.1.1",
 | 
			
		||||
        "github-slugger": "^2.0.0",
 | 
			
		||||
        "glob": "^11.0.0",
 | 
			
		||||
        "globals": "^15.9.0",
 | 
			
		||||
        "globals": "^15.10.0",
 | 
			
		||||
        "knip": "^5.30.6",
 | 
			
		||||
        "lit-analyzer": "^2.0.3",
 | 
			
		||||
        "npm-run-all": "^4.1.5",
 | 
			
		||||
        "prettier": "^3.3.3",
 | 
			
		||||
        "pseudolocale": "^2.1.0",
 | 
			
		||||
        "react": "^18.2.0",
 | 
			
		||||
        "react-dom": "^18.3.1",
 | 
			
		||||
        "rollup-plugin-modify": "^3.0.0",
 | 
			
		||||
        "rollup-plugin-postcss-lit": "^2.1.0",
 | 
			
		||||
        "storybook": "^8.1.11",
 | 
			
		||||
        "storybook": "^8.3.4",
 | 
			
		||||
        "storybook-addon-mock": "^5.0.0",
 | 
			
		||||
        "syncpack": "^13.0.0",
 | 
			
		||||
        "ts-lit-plugin": "^2.0.2",
 | 
			
		||||
        "ts-node": "^10.9.2",
 | 
			
		||||
        "tslib": "^2.7.0",
 | 
			
		||||
        "turnstile-types": "^1.2.3",
 | 
			
		||||
        "typescript": "^5.6.2",
 | 
			
		||||
        "typescript-eslint": "^8.7.0",
 | 
			
		||||
        "typescript-eslint": "^8.8.0",
 | 
			
		||||
        "vite-plugin-lit-css": "^2.0.0",
 | 
			
		||||
        "vite-tsconfig-paths": "^5.0.1",
 | 
			
		||||
        "wdio-wait-for": "^3.0.11",
 | 
			
		||||
        "wireit": "^0.14.9"
 | 
			
		||||
    },
 | 
			
		||||
    "engines": {
 | 
			
		||||
@ -120,9 +102,9 @@
 | 
			
		||||
        "@esbuild/darwin-arm64": "^0.24.0",
 | 
			
		||||
        "@esbuild/linux-amd64": "^0.18.11",
 | 
			
		||||
        "@esbuild/linux-arm64": "^0.24.0",
 | 
			
		||||
        "@rollup/rollup-darwin-arm64": "4.22.5",
 | 
			
		||||
        "@rollup/rollup-linux-arm64-gnu": "4.22.5",
 | 
			
		||||
        "@rollup/rollup-linux-x64-gnu": "4.22.5"
 | 
			
		||||
        "@rollup/rollup-darwin-arm64": "4.23.0",
 | 
			
		||||
        "@rollup/rollup-linux-arm64-gnu": "4.23.0",
 | 
			
		||||
        "@rollup/rollup-linux-x64-gnu": "4.23.0"
 | 
			
		||||
    },
 | 
			
		||||
    "private": true,
 | 
			
		||||
    "scripts": {
 | 
			
		||||
@ -150,7 +132,8 @@
 | 
			
		||||
        "storybook:build": "wireit",
 | 
			
		||||
        "storybook:build-import-map": "wireit",
 | 
			
		||||
        "test": "wireit",
 | 
			
		||||
        "test-watch": "wireit",
 | 
			
		||||
        "test:e2e:watch": "wireit",
 | 
			
		||||
        "test:watch": "wireit",
 | 
			
		||||
        "tsc": "wireit",
 | 
			
		||||
        "watch": "run-s build-locales esbuild:watch"
 | 
			
		||||
    },
 | 
			
		||||
@ -253,16 +236,20 @@
 | 
			
		||||
        "lint:imports": {
 | 
			
		||||
            "command": "knip --config scripts/knip.config.ts"
 | 
			
		||||
        },
 | 
			
		||||
        "lint:types:tests": {
 | 
			
		||||
            "command": "tsc --noEmit -p ./tests"
 | 
			
		||||
        },
 | 
			
		||||
        "lint:types": {
 | 
			
		||||
            "command": "tsc --noEmit -p .",
 | 
			
		||||
            "dependencies": [
 | 
			
		||||
                "build-locales"
 | 
			
		||||
                "build-locales",
 | 
			
		||||
                "lint:types:tests"
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
        "lint:lockfile": {
 | 
			
		||||
            "__comment": "The lockfile-lint package does not have an option to ensure resolved hashes are set everywhere",
 | 
			
		||||
            "shell": true,
 | 
			
		||||
            "command": "[ -z \"$(jq -r '.packages | to_entries[] | select((.key | contains(\"node_modules\")) and (.value | has(\"resolved\") | not)) | .key' < package-lock.json)\" ]"
 | 
			
		||||
            "command": "sh ./scripts/lint-lockfile.sh package-lock.json"
 | 
			
		||||
        },
 | 
			
		||||
        "lint:lockfiles": {
 | 
			
		||||
            "dependencies": [
 | 
			
		||||
@ -302,6 +289,7 @@
 | 
			
		||||
                "lint:types",
 | 
			
		||||
                "lint:components",
 | 
			
		||||
                "lint:spelling",
 | 
			
		||||
                "lint:package",
 | 
			
		||||
                "lint:lockfile",
 | 
			
		||||
                "lint:lockfiles",
 | 
			
		||||
                "lint:precommit",
 | 
			
		||||
@ -329,14 +317,20 @@
 | 
			
		||||
            "command": "node scripts/build-storybook-import-maps.mjs"
 | 
			
		||||
        },
 | 
			
		||||
        "test": {
 | 
			
		||||
            "command": "wdio ./wdio.conf.ts --logLevel=info",
 | 
			
		||||
            "command": "wdio ./wdio.conf.ts --logLevel=warn",
 | 
			
		||||
            "env": {
 | 
			
		||||
                "CI": "true",
 | 
			
		||||
                "TS_NODE_PROJECT": "tsconfig.test.json"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "test-watch": {
 | 
			
		||||
            "command": "wdio ./wdio.conf.ts",
 | 
			
		||||
        "test:e2e:watch": {
 | 
			
		||||
            "command": "wdio run ./tests/wdio.conf.ts",
 | 
			
		||||
            "env": {
 | 
			
		||||
                "TS_NODE_PROJECT": "./tests/tsconfig.test.json"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "test:watch": {
 | 
			
		||||
            "command": "wdio run ./wdio.conf.ts",
 | 
			
		||||
            "env": {
 | 
			
		||||
                "TS_NODE_PROJECT": "tsconfig.test.json"
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -19,7 +19,7 @@
 | 
			
		||||
        "@types/jquery": "^3.5.31",
 | 
			
		||||
        "lockfile-lint": "^4.14.0",
 | 
			
		||||
        "prettier": "^3.3.2",
 | 
			
		||||
        "rollup": "^4.22.5",
 | 
			
		||||
        "rollup": "^4.23.0",
 | 
			
		||||
        "rollup-plugin-copy": "^3.5.0",
 | 
			
		||||
        "wireit": "^0.14.9"
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
@ -9,9 +9,6 @@ const MAX_DEPTH = 4;
 | 
			
		||||
const MAX_NESTED_CALLBACKS = 4;
 | 
			
		||||
const MAX_PARAMS = 5;
 | 
			
		||||
 | 
			
		||||
// Waiting for SonarJS to be compatible
 | 
			
		||||
// const MAX_COGNITIVE_COMPLEXITY = 9;
 | 
			
		||||
 | 
			
		||||
const rules = {
 | 
			
		||||
    "accessor-pairs": "error",
 | 
			
		||||
    "array-callback-return": "error",
 | 
			
		||||
@ -129,11 +126,6 @@ const rules = {
 | 
			
		||||
 | 
			
		||||
    "no-unused-vars": "off",
 | 
			
		||||
    "no-console": ["error", { allow: ["debug", "warn", "error"] }],
 | 
			
		||||
    // SonarJS is not yet compatible with ESLint 9.  Commenting these out
 | 
			
		||||
    // until it is.
 | 
			
		||||
    //    "sonarjs/cognitive-complexity": ["off", MAX_COGNITIVE_COMPLEXITY],
 | 
			
		||||
    //    "sonarjs/no-duplicate-string": "off",
 | 
			
		||||
    //    "sonarjs/no-nested-template-literals": "off",
 | 
			
		||||
    "@typescript-eslint/ban-ts-comment": "off",
 | 
			
		||||
    "@typescript-eslint/no-unused-vars": [
 | 
			
		||||
        "error",
 | 
			
		||||
@ -170,7 +162,6 @@ export default [
 | 
			
		||||
    wcconf.configs["flat/recommended"],
 | 
			
		||||
    litconf.configs["flat/recommended"],
 | 
			
		||||
    ...tseslint.configs.recommended,
 | 
			
		||||
    //     sonar.configs.recommended,
 | 
			
		||||
    {
 | 
			
		||||
        languageOptions: {
 | 
			
		||||
            parser: tsparser,
 | 
			
		||||
 | 
			
		||||
@ -29,7 +29,6 @@ export default [
 | 
			
		||||
    wcconf.configs["flat/recommended"],
 | 
			
		||||
    litconf.configs["flat/recommended"],
 | 
			
		||||
    ...tseslint.configs.recommended,
 | 
			
		||||
    //    sonar.configs.recommended,
 | 
			
		||||
    {
 | 
			
		||||
        languageOptions: {
 | 
			
		||||
            parser: tsparser,
 | 
			
		||||
@ -42,11 +41,6 @@ export default [
 | 
			
		||||
        rules: {
 | 
			
		||||
            "no-unused-vars": "off",
 | 
			
		||||
            "no-console": ["error", { allow: ["debug", "warn", "error"] }],
 | 
			
		||||
            // SonarJS is not yet compatible with ESLint 9.  Commenting these out
 | 
			
		||||
            // until it is.
 | 
			
		||||
            //    "sonarjs/cognitive-complexity": ["off", 9],
 | 
			
		||||
            //    "sonarjs/no-duplicate-string": "off",
 | 
			
		||||
            //    "sonarjs/no-nested-template-literals": "off",
 | 
			
		||||
            "@typescript-eslint/ban-ts-comment": "off",
 | 
			
		||||
            "@typescript-eslint/no-unused-vars": [
 | 
			
		||||
                "error",
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										21
									
								
								web/scripts/lint-lockfile.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										21
									
								
								web/scripts/lint-lockfile.sh
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
 | 
			
		||||
if ! command -v jq  >/dev/null 2>&1 ; then
 | 
			
		||||
    echo "This check requires the jq program be installed."
 | 
			
		||||
    echo "To install jq, visit"
 | 
			
		||||
    echo "    https://jqlang.github.io/jq/"
 | 
			
		||||
    exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
CMD=$(jq -r '.packages | to_entries[] | select((.key | contains("node_modules")) and (.value | has("resolved") | not)) | .key' < "$1")
 | 
			
		||||
 | 
			
		||||
if [ -n "$CMD" ]; then
 | 
			
		||||
    echo "ERROR package-lock.json entries missing 'resolved' field:"
 | 
			
		||||
    echo ""
 | 
			
		||||
    # Shellcheck erroneously believes that shell string substitution can be used here, but that
 | 
			
		||||
    # feature lacks a "start of line" discriminator.
 | 
			
		||||
    # shellcheck disable=SC2001
 | 
			
		||||
    echo "$CMD" | sed 's/^/    /g'
 | 
			
		||||
    echo ""
 | 
			
		||||
    exit 1
 | 
			
		||||
fi    
 | 
			
		||||
							
								
								
									
										2637
									
								
								web/sfe/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2637
									
								
								web/sfe/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -1,28 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "@goauthentik/web-sfe",
 | 
			
		||||
    "version": "0.0.0",
 | 
			
		||||
    "private": true,
 | 
			
		||||
    "license": "MIT",
 | 
			
		||||
    "dependencies": {
 | 
			
		||||
        "@goauthentik/api": "^2024.8.3-1727449099",
 | 
			
		||||
        "base64-js": "^1.5.1",
 | 
			
		||||
        "bootstrap": "^4.6.1",
 | 
			
		||||
        "formdata-polyfill": "^4.0.10",
 | 
			
		||||
        "jquery": "^3.7.1",
 | 
			
		||||
        "weakmap-polyfill": "^2.0.4"
 | 
			
		||||
    },
 | 
			
		||||
    "scripts": {
 | 
			
		||||
        "build": "rollup -c rollup.config.js --bundleConfigAsCjs",
 | 
			
		||||
        "watch": "rollup -w -c rollup.config.js --bundleConfigAsCjs"
 | 
			
		||||
    },
 | 
			
		||||
    "devDependencies": {
 | 
			
		||||
        "@rollup/plugin-commonjs": "^28.0.0",
 | 
			
		||||
        "@rollup/plugin-node-resolve": "^15.3.0",
 | 
			
		||||
        "@rollup/plugin-swc": "^0.4.0",
 | 
			
		||||
        "@swc/cli": "^0.4.0",
 | 
			
		||||
        "@swc/core": "^1.7.28",
 | 
			
		||||
        "@types/jquery": "^3.5.31",
 | 
			
		||||
        "rollup": "^4.22.5",
 | 
			
		||||
        "rollup-plugin-copy": "^3.5.0"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,8 +1,7 @@
 | 
			
		||||
import "@goauthentik/admin/applications/ApplicationForm";
 | 
			
		||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
 | 
			
		||||
import { PFSize } from "@goauthentik/common/enums.js";
 | 
			
		||||
import "@goauthentik/components/ak-app-icon";
 | 
			
		||||
import MDApplication from "@goauthentik/docs/applications/index.md";
 | 
			
		||||
import MDApplication from "@goauthentik/docs/add-secure-apps/applications/index.md";
 | 
			
		||||
import "@goauthentik/elements/AppIcon.js";
 | 
			
		||||
import "@goauthentik/elements/Markdown";
 | 
			
		||||
import "@goauthentik/elements/buttons/SpinnerButton";
 | 
			
		||||
import "@goauthentik/elements/forms/DeleteBulkForm";
 | 
			
		||||
@ -16,6 +15,7 @@ import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
 | 
			
		||||
import { msg } from "@lit/localize";
 | 
			
		||||
import { CSSResult, TemplateResult, css, html } from "lit";
 | 
			
		||||
import { customElement, property } from "lit/decorators.js";
 | 
			
		||||
import { ifDefined } from "lit/directives/if-defined.js";
 | 
			
		||||
 | 
			
		||||
import PFCard from "@patternfly/patternfly/components/Card/card.css";
 | 
			
		||||
 | 
			
		||||
@ -122,7 +122,10 @@ export class ApplicationListPage extends TablePage<Application> {
 | 
			
		||||
 | 
			
		||||
    row(item: Application): TemplateResult[] {
 | 
			
		||||
        return [
 | 
			
		||||
            html`<ak-app-icon size=${PFSize.Medium} .app=${item}></ak-app-icon>`,
 | 
			
		||||
            html`<ak-app-icon
 | 
			
		||||
                name=${item.name}
 | 
			
		||||
                icon=${ifDefined(item.metaIcon || undefined)}
 | 
			
		||||
            ></ak-app-icon>`,
 | 
			
		||||
            html`<a href="#/core/applications/${item.slug}">
 | 
			
		||||
                <div>${item.name}</div>
 | 
			
		||||
                ${item.metaPublisher ? html`<small>${item.metaPublisher}</small>` : html``}
 | 
			
		||||
 | 
			
		||||
@ -5,8 +5,8 @@ import "@goauthentik/admin/policies/BoundPoliciesList";
 | 
			
		||||
import "@goauthentik/admin/rbac/ObjectPermissionsPage";
 | 
			
		||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
 | 
			
		||||
import { PFSize } from "@goauthentik/common/enums.js";
 | 
			
		||||
import "@goauthentik/components/ak-app-icon";
 | 
			
		||||
import "@goauthentik/components/events/ObjectChangelog";
 | 
			
		||||
import "@goauthentik/elements/AppIcon";
 | 
			
		||||
import { AKElement } from "@goauthentik/elements/Base";
 | 
			
		||||
import "@goauthentik/elements/EmptyState";
 | 
			
		||||
import "@goauthentik/elements/PageHeader";
 | 
			
		||||
@ -102,8 +102,9 @@ export class ApplicationViewPage extends AKElement {
 | 
			
		||||
            >
 | 
			
		||||
                <ak-app-icon
 | 
			
		||||
                    size=${PFSize.Medium}
 | 
			
		||||
                    name=${ifDefined(this.application?.name || undefined)}
 | 
			
		||||
                    icon=${ifDefined(this.application?.metaIcon || undefined)}
 | 
			
		||||
                    slot="icon"
 | 
			
		||||
                    .app=${this.application}
 | 
			
		||||
                ></ak-app-icon>
 | 
			
		||||
            </ak-page-header>
 | 
			
		||||
            ${this.renderApp()}`;
 | 
			
		||||
 | 
			
		||||
@ -101,6 +101,21 @@ export class ApplicationWizardAuthenticationByOauth extends BaseProviderPanel {
 | 
			
		||||
                        ${msg("Flow used when authorizing this provider.")}
 | 
			
		||||
                    </p>
 | 
			
		||||
                </ak-form-element-horizontal>
 | 
			
		||||
                <ak-form-element-horizontal
 | 
			
		||||
                    name="invalidationFlow"
 | 
			
		||||
                    label=${msg("Authorization flow")}
 | 
			
		||||
                    .errorMessages=${errors?.invalidationFlow ?? []}
 | 
			
		||||
                    ?required=${true}
 | 
			
		||||
                >
 | 
			
		||||
                    <ak-flow-search
 | 
			
		||||
                        flowType=${FlowsInstancesListDesignationEnum.Invalidation}
 | 
			
		||||
                        .currentFlow=${provider?.invalidationFlow}
 | 
			
		||||
                        required
 | 
			
		||||
                    ></ak-flow-search>
 | 
			
		||||
                    <p class="pf-c-form__helper-text">
 | 
			
		||||
                        ${msg("Flow used when logging out of this provider.")}
 | 
			
		||||
                    </p>
 | 
			
		||||
                </ak-form-element-horizontal>
 | 
			
		||||
 | 
			
		||||
                <ak-form-group .expanded=${true}>
 | 
			
		||||
                    <span slot="header"> ${msg("Protocol settings")} </span>
 | 
			
		||||
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user