Compare commits

..

22 Commits

Author SHA1 Message Date
dbf090b85e fixed my own typo 2024-12-16 19:09:54 -06:00
46a3d4f07e tweak to bump 2024-12-16 19:09:01 -06:00
e465502e4b ran make website and tweaked wording 2024-12-16 18:56:18 -06:00
a9a81cf7e7 Update website/integrations/services/hoarder/index.md
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Miguel Palau <mpalauzarza@gmail.com>
2024-12-03 17:14:26 -06:00
357c58a82b Update website/integrations/services/hoarder/index.md
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Miguel Palau <mpalauzarza@gmail.com>
2024-12-03 17:14:01 -06:00
d1a01fef94 Update website/integrations/services/hoarder/index.md
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Miguel Palau <mpalauzarza@gmail.com>
2024-12-03 17:13:52 -06:00
258ac3284d Update index.md
Co-authored-by: 4d62 <github-user@sdko.org>
Signed-off-by: Miguel Palau <mpalauzarza@gmail.com>
2024-11-24 21:49:28 -06:00
8aa064f3cf Update index.md
Co-authored-by: 4d62 <github-user@sdko.org>
Signed-off-by: Miguel Palau <mpalauzarza@gmail.com>
2024-11-24 21:49:22 -06:00
ce984e6c80 Update index.md
Co-authored-by: 4d62 <github-user@sdko.org>
Signed-off-by: Miguel Palau <mpalauzarza@gmail.com>
2024-11-24 21:49:14 -06:00
215de6e4f0 Update index.md
Co-authored-by: 4d62 <github-user@sdko.org>
Signed-off-by: Miguel Palau <mpalauzarza@gmail.com>
2024-11-24 21:49:07 -06:00
64a3ef2557 Update index.md
Co-authored-by: 4d62 <github-user@sdko.org>
Signed-off-by: Miguel Palau <mpalauzarza@gmail.com>
2024-11-24 21:48:58 -06:00
9f0e9513c2 more detailed way of using an application 2024-11-24 16:37:04 -06:00
83a2ca0481 hoarder sorted alphabetically 2024-11-24 16:34:37 -06:00
756071cc0a Update website/integrations/services/hoarder/index.md
Co-authored-by: 4d62 <github-user@sdko.org>
Signed-off-by: Miguel Palau <mpalauzarza@gmail.com>
2024-11-24 16:30:29 -06:00
99109f7c05 Update website/integrations/services/hoarder/index.md
Co-authored-by: 4d62 <github-user@sdko.org>
Signed-off-by: Miguel Palau <mpalauzarza@gmail.com>
2024-11-24 16:30:19 -06:00
5bddfe1148 Update website/integrations/services/hoarder/index.md
Co-authored-by: 4d62 <github-user@sdko.org>
Signed-off-by: Miguel Palau <mpalauzarza@gmail.com>
2024-11-24 16:30:01 -06:00
2ea574219f Update website/integrations/services/hoarder/index.md
Co-authored-by: 4d62 <github-user@sdko.org>
Signed-off-by: Miguel Palau <mpalauzarza@gmail.com>
2024-11-24 16:29:53 -06:00
ba225da0b9 Update website/integrations/services/hoarder/index.md
Co-authored-by: 4d62 <github-user@sdko.org>
Signed-off-by: Miguel Palau <mpalauzarza@gmail.com>
2024-11-24 16:29:45 -06:00
5a83cd916f Update website/integrations/services/hoarder/index.md
Co-authored-by: 4d62 <github-user@sdko.org>
Signed-off-by: Miguel Palau <mpalauzarza@gmail.com>
2024-11-24 16:29:37 -06:00
2b27fca1ee Update website/integrations/services/hoarder/index.md
Co-authored-by: 4d62 <github-user@sdko.org>
Signed-off-by: Miguel Palau <mpalauzarza@gmail.com>
2024-11-24 16:28:20 -06:00
f6908115d3 Update website/integrations/services/hoarder/index.md
Co-authored-by: 4d62 <github-user@sdko.org>
Signed-off-by: Miguel Palau <mpalauzarza@gmail.com>
2024-11-24 16:28:11 -06:00
b64e6f6e7f add Hoarder integration 2024-11-22 11:28:31 -06:00
438 changed files with 23866 additions and 27204 deletions

View File

@ -30,5 +30,3 @@ optional_value = final
[bumpversion:file:internal/constants/constants.go] [bumpversion:file:internal/constants/constants.go]
[bumpversion:file:web/src/common/constants.ts] [bumpversion:file:web/src/common/constants.ts]
[bumpversion:file:website/docs/install-config/install/aws/template.yaml]

View File

@ -11,9 +11,9 @@ inputs:
description: "Docker image arch" description: "Docker image arch"
outputs: outputs:
shouldPush: shouldBuild:
description: "Whether to push the image or not" description: "Whether to build image or not"
value: ${{ steps.ev.outputs.shouldPush }} value: ${{ steps.ev.outputs.shouldBuild }}
sha: sha:
description: "sha" description: "sha"

View File

@ -7,14 +7,7 @@ from time import time
parser = configparser.ConfigParser() parser = configparser.ConfigParser()
parser.read(".bumpversion.cfg") parser.read(".bumpversion.cfg")
# Decide if we should push the image or not should_build = str(len(os.environ.get("DOCKER_USERNAME", "")) > 0).lower()
should_push = True
if len(os.environ.get("DOCKER_USERNAME", "")) < 1:
# Don't push if we don't have DOCKER_USERNAME, i.e. no secrets are available
should_push = False
if os.environ.get("GITHUB_REPOSITORY").lower() == "goauthentik/authentik-internal":
# Don't push on the internal repo
should_push = False
branch_name = os.environ["GITHUB_REF"] branch_name = os.environ["GITHUB_REF"]
if os.environ.get("GITHUB_HEAD_REF", "") != "": if os.environ.get("GITHUB_HEAD_REF", "") != "":
@ -71,7 +64,7 @@ def get_attest_image_names(image_with_tags: list[str]):
with open(os.environ["GITHUB_OUTPUT"], "a+", encoding="utf-8") as _output: with open(os.environ["GITHUB_OUTPUT"], "a+", encoding="utf-8") as _output:
print(f"shouldPush={str(should_push).lower()}", file=_output) print(f"shouldBuild={should_build}", file=_output)
print(f"sha={sha}", file=_output) print(f"sha={sha}", file=_output)
print(f"version={version}", file=_output) print(f"version={version}", file=_output)
print(f"prerelease={prerelease}", file=_output) print(f"prerelease={prerelease}", file=_output)

View File

@ -7,7 +7,6 @@ on:
workflow_dispatch: workflow_dispatch:
jobs: jobs:
build: build:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
id-token: write id-token: write

View File

@ -7,7 +7,6 @@ on:
workflow_dispatch: workflow_dispatch:
jobs: jobs:
build: build:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- id: generate_token - id: generate_token

View File

@ -1,43 +0,0 @@
name: authentik-ci-aws-cfn
on:
push:
branches:
- main
- next
- version-*
pull_request:
branches:
- main
- version-*
env:
POSTGRES_DB: authentik
POSTGRES_USER: authentik
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
jobs:
check-changes-applied:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup authentik env
uses: ./.github/actions/setup
- uses: actions/setup-node@v4
with:
node-version-file: website/package.json
cache: "npm"
cache-dependency-path: website/package-lock.json
- working-directory: website/
run: |
npm ci
- name: Check changes have been applied
run: |
poetry run make aws-cfn
git diff --exit-code
ci-aws-cfn-mark:
needs:
- check-changes-applied
runs-on: ubuntu-latest
steps:
- run: echo mark

View File

@ -252,7 +252,7 @@ jobs:
image-name: ghcr.io/goauthentik/dev-server image-name: ghcr.io/goauthentik/dev-server
image-arch: ${{ matrix.arch }} image-arch: ${{ matrix.arch }}
- name: Login to Container Registry - name: Login to Container Registry
if: ${{ steps.ev.outputs.shouldPush == 'true' }} if: ${{ steps.ev.outputs.shouldBuild == 'true' }}
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
registry: ghcr.io registry: ghcr.io
@ -269,15 +269,15 @@ jobs:
GEOIPUPDATE_ACCOUNT_ID=${{ secrets.GEOIPUPDATE_ACCOUNT_ID }} GEOIPUPDATE_ACCOUNT_ID=${{ secrets.GEOIPUPDATE_ACCOUNT_ID }}
GEOIPUPDATE_LICENSE_KEY=${{ secrets.GEOIPUPDATE_LICENSE_KEY }} GEOIPUPDATE_LICENSE_KEY=${{ secrets.GEOIPUPDATE_LICENSE_KEY }}
tags: ${{ steps.ev.outputs.imageTags }} tags: ${{ steps.ev.outputs.imageTags }}
push: ${{ steps.ev.outputs.shouldPush == 'true' }} push: ${{ steps.ev.outputs.shouldBuild == 'true' }}
build-args: | build-args: |
GIT_BUILD_HASH=${{ steps.ev.outputs.sha }} GIT_BUILD_HASH=${{ steps.ev.outputs.sha }}
cache-from: type=registry,ref=ghcr.io/goauthentik/dev-server:buildcache cache-from: type=registry,ref=ghcr.io/goauthentik/dev-server:buildcache
cache-to: ${{ steps.ev.outputs.shouldPush == 'true' && 'type=registry,ref=ghcr.io/goauthentik/dev-server:buildcache,mode=max' || '' }} cache-to: ${{ steps.ev.outputs.shouldBuild == 'true' && 'type=registry,ref=ghcr.io/goauthentik/dev-server:buildcache,mode=max' || '' }}
platforms: linux/${{ matrix.arch }} platforms: linux/${{ matrix.arch }}
- uses: actions/attest-build-provenance@v1 - uses: actions/attest-build-provenance@v1
id: attest id: attest
if: ${{ steps.ev.outputs.shouldPush == 'true' }} if: ${{ steps.ev.outputs.shouldBuild == 'true' }}
with: with:
subject-name: ${{ steps.ev.outputs.attestImageNames }} subject-name: ${{ steps.ev.outputs.attestImageNames }}
subject-digest: ${{ steps.push.outputs.digest }} subject-digest: ${{ steps.push.outputs.digest }}
@ -303,7 +303,7 @@ jobs:
with: with:
image-name: ghcr.io/goauthentik/dev-server image-name: ghcr.io/goauthentik/dev-server
- name: Comment on PR - name: Comment on PR
if: ${{ steps.ev.outputs.shouldPush == 'true' }} if: ${{ steps.ev.outputs.shouldBuild == 'true' }}
uses: ./.github/actions/comment-pr-instructions uses: ./.github/actions/comment-pr-instructions
with: with:
tag: ${{ steps.ev.outputs.imageMainTag }} tag: ${{ steps.ev.outputs.imageMainTag }}

View File

@ -90,7 +90,7 @@ jobs:
with: with:
image-name: ghcr.io/goauthentik/dev-${{ matrix.type }} image-name: ghcr.io/goauthentik/dev-${{ matrix.type }}
- name: Login to Container Registry - name: Login to Container Registry
if: ${{ steps.ev.outputs.shouldPush == 'true' }} if: ${{ steps.ev.outputs.shouldBuild == 'true' }}
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
registry: ghcr.io registry: ghcr.io
@ -104,16 +104,16 @@ jobs:
with: with:
tags: ${{ steps.ev.outputs.imageTags }} tags: ${{ steps.ev.outputs.imageTags }}
file: ${{ matrix.type }}.Dockerfile file: ${{ matrix.type }}.Dockerfile
push: ${{ steps.ev.outputs.shouldPush == 'true' }} push: ${{ steps.ev.outputs.shouldBuild == 'true' }}
build-args: | build-args: |
GIT_BUILD_HASH=${{ steps.ev.outputs.sha }} GIT_BUILD_HASH=${{ steps.ev.outputs.sha }}
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
context: . context: .
cache-from: type=registry,ref=ghcr.io/goauthentik/dev-${{ matrix.type }}:buildcache cache-from: type=registry,ref=ghcr.io/goauthentik/dev-${{ matrix.type }}:buildcache
cache-to: ${{ steps.ev.outputs.shouldPush == 'true' && format('type=registry,ref=ghcr.io/goauthentik/dev-{0}:buildcache,mode=max', matrix.type) || '' }} cache-to: ${{ steps.ev.outputs.shouldBuild == 'true' && format('type=registry,ref=ghcr.io/goauthentik/dev-{0}:buildcache,mode=max', matrix.type) || '' }}
- uses: actions/attest-build-provenance@v1 - uses: actions/attest-build-provenance@v1
id: attest id: attest
if: ${{ steps.ev.outputs.shouldPush == 'true' }} if: ${{ steps.ev.outputs.shouldBuild == 'true' }}
with: with:
subject-name: ${{ steps.ev.outputs.attestImageNames }} subject-name: ${{ steps.ev.outputs.attestImageNames }}
subject-digest: ${{ steps.push.outputs.digest }} subject-digest: ${{ steps.push.outputs.digest }}

View File

@ -11,7 +11,6 @@ env:
jobs: jobs:
build: build:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- id: generate_token - id: generate_token

View File

@ -7,7 +7,6 @@ on:
jobs: jobs:
clean-ghcr: clean-ghcr:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
name: Delete old unused container images name: Delete old unused container images
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:

View File

@ -12,7 +12,6 @@ env:
jobs: jobs:
publish-source-docs: publish-source-docs:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 120 timeout-minutes: 120
steps: steps:

View File

@ -11,7 +11,6 @@ permissions:
jobs: jobs:
update-next: update-next:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
environment: internal-production environment: internal-production
steps: steps:

View File

@ -169,27 +169,6 @@ jobs:
file: ./authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{ matrix.goarch }} file: ./authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{ matrix.goarch }}
asset_name: authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{ matrix.goarch }} asset_name: authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{ matrix.goarch }}
tag: ${{ github.ref }} tag: ${{ github.ref }}
upload-aws-cfn-template:
permissions:
# Needed for AWS login
id-token: write
contents: read
needs:
- build-server
- build-outpost
env:
AWS_REGION: eu-central-1
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: "arn:aws:iam::016170277896:role/github_goauthentik_authentik"
aws-region: ${{ env.AWS_REGION }}
- name: Upload template
run: |
aws s3 cp website/docs/install-config/install/aws/template.yaml s3://authentik-cloudformation-templates/authentik.ecs.${{ github.ref }}.yaml
aws s3 cp website/docs/install-config/install/aws/template.yaml s3://authentik-cloudformation-templates/authentik.ecs.latest.yaml
test-release: test-release:
needs: needs:
- build-server - build-server

View File

@ -1,21 +0,0 @@
name: "authentik-repo-mirror"
on: [push, delete]
jobs:
to_internal:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- if: ${{ env.MIRROR_KEY != '' }}
uses: pixta-dev/repository-mirroring-action@v1
with:
target_repo_url:
git@github.com:goauthentik/authentik-internal.git
ssh_private_key:
${{ secrets.GH_MIRROR_KEY }}
env:
MIRROR_KEY: ${{ secrets.GH_MIRROR_KEY }}

View File

@ -11,7 +11,6 @@ permissions:
jobs: jobs:
stale: stale:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- id: generate_token - id: generate_token

View File

@ -1 +1 @@
website/docs/developer-docs/index.md website/developer-docs/index.md

View File

@ -5,7 +5,7 @@ PWD = $(shell pwd)
UID = $(shell id -u) UID = $(shell id -u)
GID = $(shell id -g) GID = $(shell id -g)
NPM_VERSION = $(shell python -m scripts.npm_version) NPM_VERSION = $(shell python -m scripts.npm_version)
PY_SOURCES = authentik tests scripts lifecycle .github website/docs/install-config/install/aws PY_SOURCES = authentik tests scripts lifecycle .github
DOCKER_IMAGE ?= "authentik:test" DOCKER_IMAGE ?= "authentik:test"
GEN_API_TS = "gen-ts-api" GEN_API_TS = "gen-ts-api"
@ -252,9 +252,6 @@ website-build:
website-watch: ## Build and watch the documentation website, updating automatically website-watch: ## Build and watch the documentation website, updating automatically
cd website && npm run watch cd website && npm run watch
aws-cfn:
cd website && npm run aws-cfn
######################### #########################
## Docker ## Docker
######################### #########################

View File

@ -65,12 +65,7 @@ from authentik.lib.utils.reflection import get_apps
from authentik.outposts.models import OutpostServiceConnection from authentik.outposts.models import OutpostServiceConnection
from authentik.policies.models import Policy, PolicyBindingModel from authentik.policies.models import Policy, PolicyBindingModel
from authentik.policies.reputation.models import Reputation from authentik.policies.reputation.models import Reputation
from authentik.providers.oauth2.models import ( from authentik.providers.oauth2.models import AccessToken, AuthorizationCode, RefreshToken
AccessToken,
AuthorizationCode,
DeviceToken,
RefreshToken,
)
from authentik.providers.scim.models import SCIMProviderGroup, SCIMProviderUser from authentik.providers.scim.models import SCIMProviderGroup, SCIMProviderUser
from authentik.rbac.models import Role from authentik.rbac.models import Role
from authentik.sources.scim.models import SCIMSourceGroup, SCIMSourceUser from authentik.sources.scim.models import SCIMSourceGroup, SCIMSourceUser
@ -130,7 +125,6 @@ def excluded_models() -> list[type[Model]]:
MicrosoftEntraProviderGroup, MicrosoftEntraProviderGroup,
EndpointDevice, EndpointDevice,
EndpointDeviceConnection, EndpointDeviceConnection,
DeviceToken,
) )

View File

@ -159,7 +159,7 @@ def blueprints_discovery(self: SystemTask, path: str | None = None):
check_blueprint_v1_file(blueprint) check_blueprint_v1_file(blueprint)
count += 1 count += 1
self.set_status( self.set_status(
TaskStatus.SUCCESSFUL, _("Successfully imported {count} files.".format(count=count)) TaskStatus.SUCCESSFUL, _("Successfully imported %(count)d files." % {"count": count})
) )

View File

@ -84,8 +84,8 @@ class CurrentBrandSerializer(PassiveSerializer):
matched_domain = CharField(source="domain") matched_domain = CharField(source="domain")
branding_title = CharField() branding_title = CharField()
branding_logo = CharField(source="branding_logo_url") branding_logo = CharField()
branding_favicon = CharField(source="branding_favicon_url") branding_favicon = CharField()
ui_footer_links = ListField( ui_footer_links = ListField(
child=FooterLinkSerializer(), child=FooterLinkSerializer(),
read_only=True, read_only=True,

View File

@ -10,7 +10,6 @@ from structlog.stdlib import get_logger
from authentik.crypto.models import CertificateKeyPair from authentik.crypto.models import CertificateKeyPair
from authentik.flows.models import Flow from authentik.flows.models import Flow
from authentik.lib.config import CONFIG
from authentik.lib.models import SerializerModel from authentik.lib.models import SerializerModel
LOGGER = get_logger() LOGGER = get_logger()
@ -72,18 +71,6 @@ class Brand(SerializerModel):
) )
attributes = models.JSONField(default=dict, blank=True) attributes = models.JSONField(default=dict, blank=True)
def branding_logo_url(self) -> str:
"""Get branding_logo with the correct prefix"""
if self.branding_logo.startswith("/static"):
return CONFIG.get("web.path", "/")[:-1] + self.branding_logo
return self.branding_logo
def branding_favicon_url(self) -> str:
"""Get branding_favicon with the correct prefix"""
if self.branding_favicon.startswith("/static"):
return CONFIG.get("web.path", "/")[:-1] + self.branding_favicon
return self.branding_favicon
@property @property
def serializer(self) -> Serializer: def serializer(self) -> Serializer:
from authentik.brands.api import BrandSerializer from authentik.brands.api import BrandSerializer

View File

@ -9,9 +9,6 @@
versionFamily: "{{ version_family }}", versionFamily: "{{ version_family }}",
versionSubdomain: "{{ version_subdomain }}", versionSubdomain: "{{ version_subdomain }}",
build: "{{ build }}", build: "{{ build }}",
api: {
base: "{{ base_url }}",
},
}; };
window.addEventListener("DOMContentLoaded", function () { window.addEventListener("DOMContentLoaded", function () {
{% for message in messages %} {% for message in messages %}

View File

@ -9,8 +9,8 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>{% block title %}{% trans title|default:brand.branding_title %}{% endblock %}</title> <title>{% block title %}{% trans title|default:brand.branding_title %}{% endblock %}</title>
<link rel="icon" href="{{ brand.branding_favicon_url }}"> <link rel="icon" href="{{ brand.branding_favicon }}">
<link rel="shortcut icon" href="{{ brand.branding_favicon_url }}"> <link rel="shortcut icon" href="{{ brand.branding_favicon }}">
{% block head_before %} {% block head_before %}
{% endblock %} {% endblock %}
<link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}"> <link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}">

View File

@ -4,7 +4,7 @@
{% load i18n %} {% load i18n %}
{% block head_before %} {% block head_before %}
<link rel="prefetch" href="{% static 'dist/assets/images/flow_background.jpg' %}" /> <link rel="prefetch" href="/static/dist/assets/images/flow_background.jpg" />
<link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly.min.css' %}"> <link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly.min.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/theme-dark.css' %}" media="(prefers-color-scheme: dark)"> <link rel="stylesheet" type="text/css" href="{% static 'dist/theme-dark.css' %}" media="(prefers-color-scheme: dark)">
{% include "base/header_js.html" %} {% include "base/header_js.html" %}
@ -13,7 +13,7 @@
{% block head %} {% block head %}
<style> <style>
:root { :root {
--ak-flow-background: url("{% static 'dist/assets/images/flow_background.jpg' %}"); --ak-flow-background: url("/static/dist/assets/images/flow_background.jpg");
--pf-c-background-image--BackgroundImage: var(--ak-flow-background); --pf-c-background-image--BackgroundImage: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage-2x: var(--ak-flow-background); --pf-c-background-image--BackgroundImage-2x: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage--sm: var(--ak-flow-background); --pf-c-background-image--BackgroundImage--sm: var(--ak-flow-background);
@ -50,7 +50,7 @@
<div class="ak-login-container"> <div class="ak-login-container">
<main class="pf-c-login__main"> <main class="pf-c-login__main">
<div class="pf-c-login__main-header pf-c-brand ak-brand"> <div class="pf-c-login__main-header pf-c-brand ak-brand">
<img src="{{ brand.branding_logo_url }}" alt="authentik Logo" /> <img src="{{ brand.branding_logo }}" alt="authentik Logo" />
</div> </div>
<header class="pf-c-login__main-header"> <header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl"> <h1 class="pf-c-title pf-m-3xl">

View File

@ -16,7 +16,6 @@ from authentik.api.v3.config import ConfigView
from authentik.brands.api import CurrentBrandSerializer from authentik.brands.api import CurrentBrandSerializer
from authentik.brands.models import Brand from authentik.brands.models import Brand
from authentik.core.models import UserTypes from authentik.core.models import UserTypes
from authentik.lib.config import CONFIG
from authentik.policies.denied import AccessDeniedResponse from authentik.policies.denied import AccessDeniedResponse
@ -52,7 +51,6 @@ class InterfaceView(TemplateView):
kwargs["version_subdomain"] = f"version-{LOCAL_VERSION.major}-{LOCAL_VERSION.minor}" kwargs["version_subdomain"] = f"version-{LOCAL_VERSION.major}-{LOCAL_VERSION.minor}"
kwargs["build"] = get_build_hash() kwargs["build"] = get_build_hash()
kwargs["url_kwargs"] = self.kwargs kwargs["url_kwargs"] = self.kwargs
kwargs["base_url"] = self.request.build_absolute_uri(CONFIG.get("web.path", "/"))
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)

View File

@ -85,5 +85,5 @@ def certificate_discovery(self: SystemTask):
if dirty: if dirty:
cert.save() cert.save()
self.set_status( self.set_status(
TaskStatus.SUCCESSFUL, _("Successfully imported {count} files.".format(count=discovered)) TaskStatus.SUCCESSFUL, _("Successfully imported %(count)d files." % {"count": discovered})
) )

View File

@ -6,8 +6,8 @@
<script src="{% versioned_script 'dist/enterprise/rac/index-%v.js' %}" type="module"></script> <script src="{% versioned_script 'dist/enterprise/rac/index-%v.js' %}" type="module"></script>
<meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)"> <meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)">
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)"> <meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
<link rel="icon" href="{{ tenant.branding_favicon_url }}"> <link rel="icon" href="{{ tenant.branding_favicon }}">
<link rel="shortcut icon" href="{{ tenant.branding_favicon_url }}"> <link rel="shortcut icon" href="{{ tenant.branding_favicon }}">
{% include "base/header_js.html" %} {% include "base/header_js.html" %}
{% endblock %} {% endblock %}

View File

@ -14,7 +14,6 @@ from structlog.stdlib import get_logger
from authentik.core.models import Token from authentik.core.models import Token
from authentik.core.types import UserSettingSerializer from authentik.core.types import UserSettingSerializer
from authentik.flows.challenge import FlowLayout from authentik.flows.challenge import FlowLayout
from authentik.lib.config import CONFIG
from authentik.lib.models import InheritanceForeignKey, SerializerModel from authentik.lib.models import InheritanceForeignKey, SerializerModel
from authentik.lib.utils.reflection import class_to_path from authentik.lib.utils.reflection import class_to_path
from authentik.policies.models import PolicyBindingModel from authentik.policies.models import PolicyBindingModel
@ -178,13 +177,9 @@ class Flow(SerializerModel, PolicyBindingModel):
"""Get the URL to the background image. If the name is /static or starts with http """Get the URL to the background image. If the name is /static or starts with http
it is returned as-is""" it is returned as-is"""
if not self.background: if not self.background:
return ( return "/static/dist/assets/images/flow_background.jpg"
CONFIG.get("web.path", "/")[:-1] + "/static/dist/assets/images/flow_background.jpg" if self.background.name.startswith("http") or self.background.name.startswith("/static"):
)
if self.background.name.startswith("http"):
return self.background.name return self.background.name
if self.background.name.startswith("/static"):
return CONFIG.get("web.path", "/")[:-1] + self.background.name
return self.background.url return self.background.url
stages = models.ManyToManyField(Stage, through="FlowStageBinding", blank=True) stages = models.ManyToManyField(Stage, through="FlowStageBinding", blank=True)

View File

@ -9,8 +9,8 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>{% block title %}{% trans title|default:brand.branding_title %}{% endblock %}</title> <title>{% block title %}{% trans title|default:brand.branding_title %}{% endblock %}</title>
<link rel="icon" href="{{ brand.branding_favicon_url }}"> <link rel="icon" href="{{ brand.branding_favicon }}">
<link rel="shortcut icon" href="{{ brand.branding_favicon_url }}"> <link rel="shortcut icon" href="{{ brand.branding_favicon }}">
{% block head_before %} {% block head_before %}
{% endblock %} {% endblock %}
<link rel="stylesheet" type="text/css" href="{% static 'dist/sfe/bootstrap.min.css' %}"> <link rel="stylesheet" type="text/css" href="{% static 'dist/sfe/bootstrap.min.css' %}">

View File

@ -135,7 +135,6 @@ web:
# No default here as it's set dynamically # No default here as it's set dynamically
# workers: 2 # workers: 2
threads: 4 threads: 4
path: /
worker: worker:
concurrency: 2 concurrency: 2

View File

@ -36,7 +36,6 @@ from authentik.lib.utils.http import authentik_user_agent
from authentik.lib.utils.reflection import get_env from authentik.lib.utils.reflection import get_env
LOGGER = get_logger() LOGGER = get_logger()
_root_path = CONFIG.get("web.path", "/")
class SentryIgnoredException(Exception): class SentryIgnoredException(Exception):
@ -91,7 +90,7 @@ def traces_sampler(sampling_context: dict) -> float:
path = sampling_context.get("asgi_scope", {}).get("path", "") path = sampling_context.get("asgi_scope", {}).get("path", "")
_type = sampling_context.get("asgi_scope", {}).get("type", "") _type = sampling_context.get("asgi_scope", {}).get("type", "")
# Ignore all healthcheck routes # Ignore all healthcheck routes
if path.startswith(f"{_root_path}-/health") or path.startswith(f"{_root_path}-/metrics"): if path.startswith("/-/health") or path.startswith("/-/metrics"):
return 0 return 0
if _type == "websocket": if _type == "websocket":
return 0 return 0

View File

@ -82,7 +82,7 @@ class SyncTasks:
return return
try: try:
for page in users_paginator.page_range: for page in users_paginator.page_range:
messages.append(_("Syncing page {page} of users".format(page=page))) messages.append(_("Syncing page %(page)d of users" % {"page": page}))
for msg in sync_objects.apply_async( for msg in sync_objects.apply_async(
args=(class_to_path(User), page, provider_pk), args=(class_to_path(User), page, provider_pk),
time_limit=PAGE_TIMEOUT, time_limit=PAGE_TIMEOUT,
@ -90,7 +90,7 @@ class SyncTasks:
).get(): ).get():
messages.append(LogEvent(**msg)) messages.append(LogEvent(**msg))
for page in groups_paginator.page_range: for page in groups_paginator.page_range:
messages.append(_("Syncing page {page} of groups".format(page=page))) messages.append(_("Syncing page %(page)d of groups" % {"page": page}))
for msg in sync_objects.apply_async( for msg in sync_objects.apply_async(
args=(class_to_path(Group), page, provider_pk), args=(class_to_path(Group), page, provider_pk),
time_limit=PAGE_TIMEOUT, time_limit=PAGE_TIMEOUT,

View File

@ -43,9 +43,8 @@ class PasswordExpiryPolicy(Policy):
request.user.set_unusable_password() request.user.set_unusable_password()
request.user.save() request.user.save()
message = _( message = _(
"Password expired {days} days ago. Please update your password.".format( "Password expired %(days)d days ago. Please update your password."
days=days_since_expiry % {"days": days_since_expiry}
)
) )
return PolicyResult(False, message) return PolicyResult(False, message)
return PolicyResult(False, _("Password has expired.")) return PolicyResult(False, _("Password has expired."))

View File

@ -135,7 +135,7 @@ class PasswordPolicy(Policy):
LOGGER.debug("got hibp result", count=final_count, hash=pw_hash[:5]) LOGGER.debug("got hibp result", count=final_count, hash=pw_hash[:5])
if final_count > self.hibp_allowed_count: if final_count > self.hibp_allowed_count:
LOGGER.debug("password failed", check="hibp", count=final_count) LOGGER.debug("password failed", check="hibp", count=final_count)
message = _("Password exists on {count} online lists.".format(count=final_count)) message = _("Password exists on %(count)d online lists." % {"count": final_count})
return PolicyResult(False, message) return PolicyResult(False, message)
return PolicyResult(True) return PolicyResult(True)

View File

@ -73,8 +73,7 @@ class OAuth2ProviderSerializer(ProviderSerializer):
"sub_mode", "sub_mode",
"property_mappings", "property_mappings",
"issuer_mode", "issuer_mode",
"jwt_federation_sources", "jwks_sources",
"jwt_federation_providers",
] ]
extra_kwargs = ProviderSerializer.Meta.extra_kwargs extra_kwargs = ProviderSerializer.Meta.extra_kwargs

View File

@ -1,25 +0,0 @@
# Generated by Django 5.0.9 on 2024-11-22 14:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_providers_oauth2", "0024_remove_oauth2provider_redirect_uris_and_more"),
]
operations = [
migrations.RenameField(
model_name="oauth2provider",
old_name="jwks_sources",
new_name="jwt_federation_sources",
),
migrations.AddField(
model_name="oauth2provider",
name="jwt_federation_providers",
field=models.ManyToManyField(
blank=True, default=None, to="authentik_providers_oauth2.oauth2provider"
),
),
]

View File

@ -244,7 +244,7 @@ class OAuth2Provider(WebfingerProvider, Provider):
related_name="oauth2provider_encryption_key_set", related_name="oauth2provider_encryption_key_set",
) )
jwt_federation_sources = models.ManyToManyField( jwks_sources = models.ManyToManyField(
OAuthSource, OAuthSource,
verbose_name=_( verbose_name=_(
"Any JWT signed by the JWK of the selected source can be used to authenticate." "Any JWT signed by the JWK of the selected source can be used to authenticate."
@ -253,7 +253,6 @@ class OAuth2Provider(WebfingerProvider, Provider):
default=None, default=None,
blank=True, blank=True,
) )
jwt_federation_providers = models.ManyToManyField("OAuth2Provider", blank=True, default=None)
@cached_property @cached_property
def jwt_key(self) -> tuple[str | PrivateKeyTypes, str]: def jwt_key(self) -> tuple[str | PrivateKeyTypes, str]:

View File

@ -1,228 +0,0 @@
"""Test token view"""
from datetime import datetime, timedelta
from json import loads
from django.test import RequestFactory
from django.urls import reverse
from django.utils.timezone import now
from jwt import decode
from authentik.blueprints.tests import apply_blueprint
from authentik.core.models import Application, Group
from authentik.core.tests.utils import create_test_cert, create_test_flow, create_test_user
from authentik.lib.generators import generate_id
from authentik.policies.models import PolicyBinding
from authentik.providers.oauth2.constants import (
GRANT_TYPE_CLIENT_CREDENTIALS,
SCOPE_OPENID,
SCOPE_OPENID_EMAIL,
SCOPE_OPENID_PROFILE,
TOKEN_TYPE,
)
from authentik.providers.oauth2.models import (
AccessToken,
OAuth2Provider,
RedirectURI,
RedirectURIMatchingMode,
ScopeMapping,
)
from authentik.providers.oauth2.tests.utils import OAuthTestCase
class TestTokenClientCredentialsJWTProvider(OAuthTestCase):
"""Test token (client_credentials, with JWT) view"""
@apply_blueprint("system/providers-oauth2.yaml")
def setUp(self) -> None:
super().setUp()
self.factory = RequestFactory()
self.other_cert = create_test_cert()
self.cert = create_test_cert()
self.other_provider = OAuth2Provider.objects.create(
name=generate_id(),
authorization_flow=create_test_flow(),
signing_key=self.other_cert,
)
self.other_provider.property_mappings.set(ScopeMapping.objects.all())
self.app = Application.objects.create(
name=generate_id(), slug=generate_id(), provider=self.other_provider
)
self.provider: OAuth2Provider = OAuth2Provider.objects.create(
name="test",
authorization_flow=create_test_flow(),
redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://testserver")],
signing_key=self.cert,
)
self.provider.jwt_federation_providers.add(self.other_provider)
self.provider.property_mappings.set(ScopeMapping.objects.all())
self.app = Application.objects.create(name="test", slug="test", provider=self.provider)
def test_invalid_type(self):
"""test invalid type"""
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
{
"grant_type": GRANT_TYPE_CLIENT_CREDENTIALS,
"scope": f"{SCOPE_OPENID} {SCOPE_OPENID_EMAIL} {SCOPE_OPENID_PROFILE}",
"client_id": self.provider.client_id,
"client_assertion_type": "foo",
"client_assertion": "foo.bar",
},
)
self.assertEqual(response.status_code, 400)
body = loads(response.content.decode())
self.assertEqual(body["error"], "invalid_grant")
def test_invalid_jwt(self):
"""test invalid JWT"""
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
{
"grant_type": GRANT_TYPE_CLIENT_CREDENTIALS,
"scope": f"{SCOPE_OPENID} {SCOPE_OPENID_EMAIL} {SCOPE_OPENID_PROFILE}",
"client_id": self.provider.client_id,
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
"client_assertion": "foo.bar",
},
)
self.assertEqual(response.status_code, 400)
body = loads(response.content.decode())
self.assertEqual(body["error"], "invalid_grant")
def test_invalid_signature(self):
"""test invalid JWT"""
token = self.provider.encode(
{
"sub": "foo",
"exp": datetime.now() + timedelta(hours=2),
}
)
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
{
"grant_type": GRANT_TYPE_CLIENT_CREDENTIALS,
"scope": f"{SCOPE_OPENID} {SCOPE_OPENID_EMAIL} {SCOPE_OPENID_PROFILE}",
"client_id": self.provider.client_id,
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
"client_assertion": token + "foo",
},
)
self.assertEqual(response.status_code, 400)
body = loads(response.content.decode())
self.assertEqual(body["error"], "invalid_grant")
def test_invalid_expired(self):
"""test invalid JWT"""
token = self.provider.encode(
{
"sub": "foo",
"exp": datetime.now() - timedelta(hours=2),
}
)
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
{
"grant_type": GRANT_TYPE_CLIENT_CREDENTIALS,
"scope": f"{SCOPE_OPENID} {SCOPE_OPENID_EMAIL} {SCOPE_OPENID_PROFILE}",
"client_id": self.provider.client_id,
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
"client_assertion": token,
},
)
self.assertEqual(response.status_code, 400)
body = loads(response.content.decode())
self.assertEqual(body["error"], "invalid_grant")
def test_invalid_no_app(self):
"""test invalid JWT"""
self.app.provider = None
self.app.save()
token = self.provider.encode(
{
"sub": "foo",
"exp": datetime.now() + timedelta(hours=2),
}
)
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
{
"grant_type": GRANT_TYPE_CLIENT_CREDENTIALS,
"scope": f"{SCOPE_OPENID} {SCOPE_OPENID_EMAIL} {SCOPE_OPENID_PROFILE}",
"client_id": self.provider.client_id,
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
"client_assertion": token,
},
)
self.assertEqual(response.status_code, 400)
body = loads(response.content.decode())
self.assertEqual(body["error"], "invalid_grant")
def test_invalid_access_denied(self):
"""test invalid JWT"""
group = Group.objects.create(name="foo")
PolicyBinding.objects.create(
group=group,
target=self.app,
order=0,
)
token = self.provider.encode(
{
"sub": "foo",
"exp": datetime.now() + timedelta(hours=2),
}
)
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
{
"grant_type": GRANT_TYPE_CLIENT_CREDENTIALS,
"scope": f"{SCOPE_OPENID} {SCOPE_OPENID_EMAIL} {SCOPE_OPENID_PROFILE}",
"client_id": self.provider.client_id,
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
"client_assertion": token,
},
)
self.assertEqual(response.status_code, 400)
body = loads(response.content.decode())
self.assertEqual(body["error"], "invalid_grant")
def test_successful(self):
"""test successful"""
user = create_test_user()
token = self.other_provider.encode(
{
"sub": "foo",
"exp": datetime.now() + timedelta(hours=2),
}
)
AccessToken.objects.create(
provider=self.other_provider,
token=token,
user=user,
auth_time=now(),
)
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
{
"grant_type": GRANT_TYPE_CLIENT_CREDENTIALS,
"scope": f"{SCOPE_OPENID} {SCOPE_OPENID_EMAIL} {SCOPE_OPENID_PROFILE}",
"client_id": self.provider.client_id,
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
"client_assertion": token,
},
)
self.assertEqual(response.status_code, 200)
body = loads(response.content.decode())
self.assertEqual(body["token_type"], TOKEN_TYPE)
_, alg = self.provider.jwt_key
jwt = decode(
body["access_token"],
key=self.provider.signing_key.public_key,
algorithms=[alg],
audience=self.provider.client_id,
)
self.assertEqual(jwt["given_name"], user.name)
self.assertEqual(jwt["preferred_username"], user.username)

View File

@ -37,16 +37,9 @@ class TestTokenClientCredentialsJWTSource(OAuthTestCase):
def setUp(self) -> None: def setUp(self) -> None:
super().setUp() super().setUp()
self.factory = RequestFactory() self.factory = RequestFactory()
self.other_cert = create_test_cert()
# Provider used as a helper to sign JWTs with the same key as the OAuth source has
self.helper_provider = OAuth2Provider.objects.create(
name=generate_id(),
authorization_flow=create_test_flow(),
signing_key=self.other_cert,
)
self.cert = create_test_cert() self.cert = create_test_cert()
jwk = JWKSView().get_jwk_for_key(self.other_cert, "sig") jwk = JWKSView().get_jwk_for_key(self.cert, "sig")
self.source: OAuthSource = OAuthSource.objects.create( self.source: OAuthSource = OAuthSource.objects.create(
name=generate_id(), name=generate_id(),
slug=generate_id(), slug=generate_id(),
@ -69,7 +62,7 @@ class TestTokenClientCredentialsJWTSource(OAuthTestCase):
redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://testserver")], redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://testserver")],
signing_key=self.cert, signing_key=self.cert,
) )
self.provider.jwt_federation_sources.add(self.source) self.provider.jwks_sources.add(self.source)
self.provider.property_mappings.set(ScopeMapping.objects.all()) self.provider.property_mappings.set(ScopeMapping.objects.all())
self.app = Application.objects.create(name="test", slug="test", provider=self.provider) self.app = Application.objects.create(name="test", slug="test", provider=self.provider)
@ -107,7 +100,7 @@ class TestTokenClientCredentialsJWTSource(OAuthTestCase):
def test_invalid_signature(self): def test_invalid_signature(self):
"""test invalid JWT""" """test invalid JWT"""
token = self.helper_provider.encode( token = self.provider.encode(
{ {
"sub": "foo", "sub": "foo",
"exp": datetime.now() + timedelta(hours=2), "exp": datetime.now() + timedelta(hours=2),
@ -129,7 +122,7 @@ class TestTokenClientCredentialsJWTSource(OAuthTestCase):
def test_invalid_expired(self): def test_invalid_expired(self):
"""test invalid JWT""" """test invalid JWT"""
token = self.helper_provider.encode( token = self.provider.encode(
{ {
"sub": "foo", "sub": "foo",
"exp": datetime.now() - timedelta(hours=2), "exp": datetime.now() - timedelta(hours=2),
@ -153,7 +146,7 @@ class TestTokenClientCredentialsJWTSource(OAuthTestCase):
"""test invalid JWT""" """test invalid JWT"""
self.app.provider = None self.app.provider = None
self.app.save() self.app.save()
token = self.helper_provider.encode( token = self.provider.encode(
{ {
"sub": "foo", "sub": "foo",
"exp": datetime.now() + timedelta(hours=2), "exp": datetime.now() + timedelta(hours=2),
@ -181,7 +174,7 @@ class TestTokenClientCredentialsJWTSource(OAuthTestCase):
target=self.app, target=self.app,
order=0, order=0,
) )
token = self.helper_provider.encode( token = self.provider.encode(
{ {
"sub": "foo", "sub": "foo",
"exp": datetime.now() + timedelta(hours=2), "exp": datetime.now() + timedelta(hours=2),
@ -203,7 +196,7 @@ class TestTokenClientCredentialsJWTSource(OAuthTestCase):
def test_successful(self): def test_successful(self):
"""test successful""" """test successful"""
token = self.helper_provider.encode( token = self.provider.encode(
{ {
"sub": "foo", "sub": "foo",
"exp": datetime.now() + timedelta(hours=2), "exp": datetime.now() + timedelta(hours=2),

View File

@ -137,7 +137,7 @@ class OAuthDeviceCodeChallengeResponse(ChallengeResponse):
class OAuthDeviceCodeStage(ChallengeStageView): class OAuthDeviceCodeStage(ChallengeStageView):
"""Flow challenge for users to enter device code""" """Flow challenge for users to enter device codes"""
response_class = OAuthDeviceCodeChallengeResponse response_class = OAuthDeviceCodeChallengeResponse

View File

@ -362,9 +362,23 @@ class TokenParams:
}, },
).from_http(request, user=user) ).from_http(request, user=user)
def __validate_jwt_from_source( def __post_init_client_credentials_jwt(self, request: HttpRequest):
self, assertion: str assertion_type = request.POST.get(CLIENT_ASSERTION_TYPE, "")
) -> tuple[dict, OAuthSource] | tuple[None, None]: if assertion_type != CLIENT_ASSERTION_TYPE_JWT:
LOGGER.warning("Invalid assertion type", assertion_type=assertion_type)
raise TokenError("invalid_grant")
client_secret = request.POST.get("client_secret", None)
assertion = request.POST.get(CLIENT_ASSERTION, client_secret)
if not assertion:
LOGGER.warning("Missing client assertion")
raise TokenError("invalid_grant")
token = None
source: OAuthSource | None = None
parsed_key: PyJWK | None = None
# Fully decode the JWT without verifying the signature, so we can get access to # Fully decode the JWT without verifying the signature, so we can get access to
# the header. # the header.
# Get the Key ID from the header, and use that to optimise our source query to only find # Get the Key ID from the header, and use that to optimise our source query to only find
@ -379,23 +393,19 @@ class TokenParams:
LOGGER.warning("failed to parse JWT for kid lookup", exc=exc) LOGGER.warning("failed to parse JWT for kid lookup", exc=exc)
raise TokenError("invalid_grant") from None raise TokenError("invalid_grant") from None
expected_kid = decode_unvalidated["header"]["kid"] expected_kid = decode_unvalidated["header"]["kid"]
fallback_alg = decode_unvalidated["header"]["alg"] for source in self.provider.jwks_sources.filter(
token = source = None
for source in self.provider.jwt_federation_sources.filter(
oidc_jwks__keys__contains=[{"kid": expected_kid}] oidc_jwks__keys__contains=[{"kid": expected_kid}]
): ):
LOGGER.debug("verifying JWT with source", source=source.slug) LOGGER.debug("verifying JWT with source", source=source.slug)
keys = source.oidc_jwks.get("keys", []) keys = source.oidc_jwks.get("keys", [])
for key in keys: for key in keys:
if key.get("kid") and key.get("kid") != expected_kid:
continue
LOGGER.debug("verifying JWT with key", source=source.slug, key=key.get("kid")) LOGGER.debug("verifying JWT with key", source=source.slug, key=key.get("kid"))
try: try:
parsed_key = PyJWK.from_dict(key).key parsed_key = PyJWK.from_dict(key)
token = decode( token = decode(
assertion, assertion,
parsed_key, parsed_key.key,
algorithms=[key.get("alg")] if "alg" in key else [fallback_alg], algorithms=[key.get("alg")],
options={ options={
"verify_aud": False, "verify_aud": False,
}, },
@ -404,61 +414,13 @@ class TokenParams:
# and not a public key # and not a public key
except (PyJWTError, ValueError, TypeError, AttributeError) as exc: except (PyJWTError, ValueError, TypeError, AttributeError) as exc:
LOGGER.warning("failed to verify JWT", exc=exc, source=source.slug) LOGGER.warning("failed to verify JWT", exc=exc, source=source.slug)
if token:
LOGGER.info("successfully verified JWT with source", source=source.slug)
return token, source
def __validate_jwt_from_provider(
self, assertion: str
) -> tuple[dict, OAuth2Provider] | tuple[None, None]:
token = provider = _key = None
federated_token = AccessToken.objects.filter(
token=assertion, provider__in=self.provider.jwt_federation_providers.all()
).first()
if federated_token:
_key, _alg = federated_token.provider.jwt_key
try:
token = decode(
assertion,
_key.public_key(),
algorithms=[_alg],
options={
"verify_aud": False,
},
)
provider = federated_token.provider
self.user = federated_token.user
except (PyJWTError, ValueError, TypeError, AttributeError) as exc:
LOGGER.warning(
"failed to verify JWT", exc=exc, provider=federated_token.provider.name
)
if token:
LOGGER.info("successfully verified JWT with provider", provider=provider.name)
return token, provider
def __post_init_client_credentials_jwt(self, request: HttpRequest):
assertion_type = request.POST.get(CLIENT_ASSERTION_TYPE, "")
if assertion_type != CLIENT_ASSERTION_TYPE_JWT:
LOGGER.warning("Invalid assertion type", assertion_type=assertion_type)
raise TokenError("invalid_grant")
client_secret = request.POST.get("client_secret", None)
assertion = request.POST.get(CLIENT_ASSERTION, client_secret)
if not assertion:
LOGGER.warning("Missing client assertion")
raise TokenError("invalid_grant")
source = provider = None
token, source = self.__validate_jwt_from_source(assertion)
if not token:
token, provider = self.__validate_jwt_from_provider(assertion)
if not token: if not token:
LOGGER.warning("No token could be verified") LOGGER.warning("No token could be verified")
raise TokenError("invalid_grant") raise TokenError("invalid_grant")
LOGGER.info("successfully verified JWT with source", source=source.slug)
if "exp" in token: if "exp" in token:
exp = datetime.fromtimestamp(token["exp"]) exp = datetime.fromtimestamp(token["exp"])
# Non-timezone aware check since we assume `exp` is in UTC # Non-timezone aware check since we assume `exp` is in UTC
@ -472,16 +434,15 @@ class TokenParams:
raise TokenError("invalid_grant") raise TokenError("invalid_grant")
self.__check_policy_access(app, request, oauth_jwt=token) self.__check_policy_access(app, request, oauth_jwt=token)
if not provider: self.__create_user_from_jwt(token, app, source)
self.__create_user_from_jwt(token, app, source)
method_args = { method_args = {
"jwt": token, "jwt": token,
} }
if source: if source:
method_args["source"] = source method_args["source"] = source
if provider: if parsed_key:
method_args["provider"] = provider method_args["jwk_id"] = parsed_key.key_id
Event.new( Event.new(
action=EventAction.LOGIN, action=EventAction.LOGIN,
**{ **{

View File

@ -94,8 +94,7 @@ class ProxyProviderSerializer(ProviderSerializer):
"intercept_header_auth", "intercept_header_auth",
"redirect_uris", "redirect_uris",
"cookie_domain", "cookie_domain",
"jwt_federation_sources", "jwks_sources",
"jwt_federation_providers",
"access_token_validity", "access_token_validity",
"refresh_token_validity", "refresh_token_validity",
"outpost_set", "outpost_set",

View File

@ -32,8 +32,6 @@ LOGIN_URL = "authentik_flows:default-authentication"
# Custom user model # Custom user model
AUTH_USER_MODEL = "authentik_core.User" AUTH_USER_MODEL = "authentik_core.User"
CSRF_COOKIE_PATH = LANGUAGE_COOKIE_PATH = SESSION_COOKIE_PATH = CONFIG.get("web.path", "/")
CSRF_COOKIE_NAME = "authentik_csrf" CSRF_COOKIE_NAME = "authentik_csrf"
CSRF_HEADER_NAME = "HTTP_X_AUTHENTIK_CSRF" CSRF_HEADER_NAME = "HTTP_X_AUTHENTIK_CSRF"
LANGUAGE_COOKIE_NAME = "authentik_language" LANGUAGE_COOKIE_NAME = "authentik_language"
@ -306,12 +304,10 @@ DATABASES = {
"USER": CONFIG.get("postgresql.user"), "USER": CONFIG.get("postgresql.user"),
"PASSWORD": CONFIG.get("postgresql.password"), "PASSWORD": CONFIG.get("postgresql.password"),
"PORT": CONFIG.get("postgresql.port"), "PORT": CONFIG.get("postgresql.port"),
"OPTIONS": { "SSLMODE": CONFIG.get("postgresql.sslmode"),
"sslmode": CONFIG.get("postgresql.sslmode"), "SSLROOTCERT": CONFIG.get("postgresql.sslrootcert"),
"sslrootcert": CONFIG.get("postgresql.sslrootcert"), "SSLCERT": CONFIG.get("postgresql.sslcert"),
"sslcert": CONFIG.get("postgresql.sslcert"), "SSLKEY": CONFIG.get("postgresql.sslkey"),
"sslkey": CONFIG.get("postgresql.sslkey"),
},
"TEST": { "TEST": {
"NAME": CONFIG.get("postgresql.test.name"), "NAME": CONFIG.get("postgresql.test.name"),
}, },
@ -429,7 +425,7 @@ if _ERROR_REPORTING:
# https://docs.djangoproject.com/en/2.1/howto/static-files/ # https://docs.djangoproject.com/en/2.1/howto/static-files/
STATICFILES_DIRS = [BASE_DIR / Path("web")] STATICFILES_DIRS = [BASE_DIR / Path("web")]
STATIC_URL = CONFIG.get("web.path", "/") + "static/" STATIC_URL = "/static/"
STORAGES = { STORAGES = {
"staticfiles": { "staticfiles": {

View File

@ -4,7 +4,6 @@ from django.urls import include, path
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.core.views import error from authentik.core.views import error
from authentik.lib.config import CONFIG
from authentik.lib.utils.reflection import get_apps from authentik.lib.utils.reflection import get_apps
from authentik.root.monitoring import LiveView, MetricsView, ReadyView from authentik.root.monitoring import LiveView, MetricsView, ReadyView
@ -15,7 +14,7 @@ handler403 = error.ForbiddenView.as_view()
handler404 = error.NotFoundView.as_view() handler404 = error.NotFoundView.as_view()
handler500 = error.ServerErrorView.as_view() handler500 = error.ServerErrorView.as_view()
_urlpatterns = [] urlpatterns = []
for _authentik_app in get_apps(): for _authentik_app in get_apps():
mountpoints = None mountpoints = None
@ -36,7 +35,7 @@ for _authentik_app in get_apps():
namespace=namespace, namespace=namespace,
), ),
) )
_urlpatterns.append(_path) urlpatterns.append(_path)
LOGGER.debug( LOGGER.debug(
"Mounted URLs", "Mounted URLs",
app_name=_authentik_app.name, app_name=_authentik_app.name,
@ -44,10 +43,8 @@ for _authentik_app in get_apps():
namespace=namespace, namespace=namespace,
) )
_urlpatterns += [ urlpatterns += [
path("-/metrics/", MetricsView.as_view(), name="metrics"), path("-/metrics/", MetricsView.as_view(), name="metrics"),
path("-/health/live/", LiveView.as_view(), name="health-live"), path("-/health/live/", LiveView.as_view(), name="health-live"),
path("-/health/ready/", ReadyView.as_view(), name="health-ready"), path("-/health/ready/", ReadyView.as_view(), name="health-ready"),
] ]
urlpatterns = [path(CONFIG.get("web.path", "/")[1:], include(_urlpatterns))]

View File

@ -2,16 +2,13 @@
from importlib import import_module from importlib import import_module
from channels.routing import URLRouter
from django.urls import path
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.lib.config import CONFIG
from authentik.lib.utils.reflection import get_apps from authentik.lib.utils.reflection import get_apps
LOGGER = get_logger() LOGGER = get_logger()
_websocket_urlpatterns = [] websocket_urlpatterns = []
for _authentik_app in get_apps(): for _authentik_app in get_apps():
try: try:
api_urls = import_module(f"{_authentik_app.name}.urls") api_urls = import_module(f"{_authentik_app.name}.urls")
@ -20,15 +17,8 @@ for _authentik_app in get_apps():
if not hasattr(api_urls, "websocket_urlpatterns"): if not hasattr(api_urls, "websocket_urlpatterns"):
continue continue
urls: list = api_urls.websocket_urlpatterns urls: list = api_urls.websocket_urlpatterns
_websocket_urlpatterns.extend(urls) websocket_urlpatterns.extend(urls)
LOGGER.debug( LOGGER.debug(
"Mounted Websocket URLs", "Mounted Websocket URLs",
app_name=_authentik_app.name, app_name=_authentik_app.name,
) )
websocket_urlpatterns = [
path(
CONFIG.get("web.path", "/")[1:],
URLRouter(_websocket_urlpatterns),
),
]

View File

@ -127,19 +127,19 @@
"icon_dark":"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+Cjxzdmcgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEwODAgMTA4MCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWw6c3BhY2U9InByZXNlcnZlIiB4bWxuczpzZXJpZj0iaHR0cDovL3d3dy5zZXJpZi5jb20vIiBzdHlsZT0iZmlsbC1ydWxlOmV2ZW5vZGQ7Y2xpcC1ydWxlOmV2ZW5vZGQ7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjI7Ij4KICAgIDxnIHRyYW5zZm9ybT0ibWF0cml4KDAuOTgzODI3LDAsMCwwLjk4MzgyNywxMy42NjEsLTAuMjg0Njk5KSI+CiAgICAgICAgPHBhdGggZD0iTTcyMy4yNTMsNDkzLjcwNEM3NTYuMjg4LDQ5NS4yMiA3ODIuNjQ0LDUyMi41MiA3ODIuNjQ0LDU1NS45MjdMNzgyLjY0NCw4MzQuMzA0Qzc4Mi42NDQsODY4LjY4MyA3NTQuNzMzLDg5Ni41OTMgNzIwLjM1NSw4OTYuNTkzTDM1MS4zOTMsODk2LjU5M0MzMTcuMDE1LDg5Ni41OTMgMjg5LjEwNCw4NjguNjgzIDI4OS4xMDQsODM0LjMwNEwyODkuMTA0LDU1NS45MjdDMjg5LjEwNCw1MjMuMTE3IDMxNC41MjYsNDk2LjE5OCAzNDYuNzMsNDkzLjgxTDM0Ni43MywzOTAuMTE5QzM0Ni43MywyODYuMjE1IDQzMS4wODgsMjAxLjg1OCA1MzQuOTkyLDIwMS44NThDNjM4Ljg5NiwyMDEuODU4IDcyMy4yNTMsMjg2LjIxNSA3MjMuMjUzLDM5MC4xMTlMNzIzLjI1Myw0OTMuNzA0Wk00MzMuMzI4LDQ5MC45NjZMNjM2LjY4Niw0OTIuMTE2QzYzNi42ODYsNDkyLjExNiA2MzcuMTQyLDM4NS4xMzIgNjM3LjA4NiwzODQuNDg5QzYzMS44NDksMzI0LjE2MyA1NzkuMTE4LDI4OC4wNzkgNTM0Ljk5MiwyODguNTM1QzQ5My4wMTcsMjg4Ljk2OSA0MzUuOTIsMzE4LjEgNDMzLjY1NiwzODMuNzk3QzQzMy40NzYsMzg5LjAxNyA0MzMuMzI4LDQ5MC45NjYgNDMzLjMyOCw0OTAuOTY2Wk01MDMuMjk2LDcxNC4zODJMNDkyLjQzMyw3ODQuNDU1QzQ5Mi40MzMsNzg0LjQ1NSA0OTAuMzc2LDc5OC4yODQgNDkyLjQxNiw4MDIuNjY0QzQ5Ni43OCw4MTIuMDMxIDUwMy44MjIsODExLjExMSA1MDMuODIyLDgxMS4xMTFMNTY1LjYsODEwLjgzOEM1NjUuNiw4MTAuODM4IDU3Mi44NjMsODExLjQ3MiA1NzcuNjM3LDgwMi4xNTVDNTc5Ljg4LDc5Ny43NzUgNTc3LjMyMyw3ODMuNzQgNTc3LjMyMyw3ODMuNzRMNTY2LjY0OSw3MTQuNDAxQzU5MC43NDMsNzAyLjY0OSA2MDcuMzU5LDY3Ny45MDggNjA3LjM1OSw2NDkuMzE4QzYwNy4zNTksNjA5LjM3NyA1NzQuOTMyLDU3Ni45NSA1MzQuOTkyLDU3Ni45NUM0OTUuMDUxLDU3Ni45NSA0NjIuNjI0LDYwOS4zNzcgNDYyLjYyNCw2NDkuMzE4QzQ2Mi42MjQsNjc3Ljg5MyA0NzkuMjIzLDcwMi42MjIgNTAzLjI5Niw3MTQuMzgyWiIgc3R5bGU9ImZpbGw6cmdiKDAsMTUyLDI0OCk7Ii8+CiAgICA8L2c+Cjwvc3ZnPgo=", "icon_dark":"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+Cjxzdmcgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEwODAgMTA4MCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWw6c3BhY2U9InByZXNlcnZlIiB4bWxuczpzZXJpZj0iaHR0cDovL3d3dy5zZXJpZi5jb20vIiBzdHlsZT0iZmlsbC1ydWxlOmV2ZW5vZGQ7Y2xpcC1ydWxlOmV2ZW5vZGQ7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjI7Ij4KICAgIDxnIHRyYW5zZm9ybT0ibWF0cml4KDAuOTgzODI3LDAsMCwwLjk4MzgyNywxMy42NjEsLTAuMjg0Njk5KSI+CiAgICAgICAgPHBhdGggZD0iTTcyMy4yNTMsNDkzLjcwNEM3NTYuMjg4LDQ5NS4yMiA3ODIuNjQ0LDUyMi41MiA3ODIuNjQ0LDU1NS45MjdMNzgyLjY0NCw4MzQuMzA0Qzc4Mi42NDQsODY4LjY4MyA3NTQuNzMzLDg5Ni41OTMgNzIwLjM1NSw4OTYuNTkzTDM1MS4zOTMsODk2LjU5M0MzMTcuMDE1LDg5Ni41OTMgMjg5LjEwNCw4NjguNjgzIDI4OS4xMDQsODM0LjMwNEwyODkuMTA0LDU1NS45MjdDMjg5LjEwNCw1MjMuMTE3IDMxNC41MjYsNDk2LjE5OCAzNDYuNzMsNDkzLjgxTDM0Ni43MywzOTAuMTE5QzM0Ni43MywyODYuMjE1IDQzMS4wODgsMjAxLjg1OCA1MzQuOTkyLDIwMS44NThDNjM4Ljg5NiwyMDEuODU4IDcyMy4yNTMsMjg2LjIxNSA3MjMuMjUzLDM5MC4xMTlMNzIzLjI1Myw0OTMuNzA0Wk00MzMuMzI4LDQ5MC45NjZMNjM2LjY4Niw0OTIuMTE2QzYzNi42ODYsNDkyLjExNiA2MzcuMTQyLDM4NS4xMzIgNjM3LjA4NiwzODQuNDg5QzYzMS44NDksMzI0LjE2MyA1NzkuMTE4LDI4OC4wNzkgNTM0Ljk5MiwyODguNTM1QzQ5My4wMTcsMjg4Ljk2OSA0MzUuOTIsMzE4LjEgNDMzLjY1NiwzODMuNzk3QzQzMy40NzYsMzg5LjAxNyA0MzMuMzI4LDQ5MC45NjYgNDMzLjMyOCw0OTAuOTY2Wk01MDMuMjk2LDcxNC4zODJMNDkyLjQzMyw3ODQuNDU1QzQ5Mi40MzMsNzg0LjQ1NSA0OTAuMzc2LDc5OC4yODQgNDkyLjQxNiw4MDIuNjY0QzQ5Ni43OCw4MTIuMDMxIDUwMy44MjIsODExLjExMSA1MDMuODIyLDgxMS4xMTFMNTY1LjYsODEwLjgzOEM1NjUuNiw4MTAuODM4IDU3Mi44NjMsODExLjQ3MiA1NzcuNjM3LDgwMi4xNTVDNTc5Ljg4LDc5Ny43NzUgNTc3LjMyMyw3ODMuNzQgNTc3LjMyMyw3ODMuNzRMNTY2LjY0OSw3MTQuNDAxQzU5MC43NDMsNzAyLjY0OSA2MDcuMzU5LDY3Ny45MDggNjA3LjM1OSw2NDkuMzE4QzYwNy4zNTksNjA5LjM3NyA1NzQuOTMyLDU3Ni45NSA1MzQuOTkyLDU3Ni45NUM0OTUuMDUxLDU3Ni45NSA0NjIuNjI0LDYwOS4zNzcgNDYyLjYyNCw2NDkuMzE4QzQ2Mi42MjQsNjc3Ljg5MyA0NzkuMjIzLDcwMi42MjIgNTAzLjI5Niw3MTQuMzgyWiIgc3R5bGU9ImZpbGw6cmdiKDAsMTUyLDI0OCk7Ii8+CiAgICA8L2c+Cjwvc3ZnPgo=",
"icon_light":"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+Cjxzdmcgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEwODAgMTA4MCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWw6c3BhY2U9InByZXNlcnZlIiB4bWxuczpzZXJpZj0iaHR0cDovL3d3dy5zZXJpZi5jb20vIiBzdHlsZT0iZmlsbC1ydWxlOmV2ZW5vZGQ7Y2xpcC1ydWxlOmV2ZW5vZGQ7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjI7Ij4KICAgIDxnIHRyYW5zZm9ybT0ibWF0cml4KDAuOTgzODI3LDAsMCwwLjk4MzgyNywxMy42NjEsLTAuMjg0Njk5KSI+CiAgICAgICAgPHBhdGggZD0iTTcyMy4yNTMsNDkzLjcwNEM3NTYuMjg4LDQ5NS4yMiA3ODIuNjQ0LDUyMi41MiA3ODIuNjQ0LDU1NS45MjdMNzgyLjY0NCw4MzQuMzA0Qzc4Mi42NDQsODY4LjY4MyA3NTQuNzMzLDg5Ni41OTMgNzIwLjM1NSw4OTYuNTkzTDM1MS4zOTMsODk2LjU5M0MzMTcuMDE1LDg5Ni41OTMgMjg5LjEwNCw4NjguNjgzIDI4OS4xMDQsODM0LjMwNEwyODkuMTA0LDU1NS45MjdDMjg5LjEwNCw1MjMuMTE3IDMxNC41MjYsNDk2LjE5OCAzNDYuNzMsNDkzLjgxTDM0Ni43MywzOTAuMTE5QzM0Ni43MywyODYuMjE1IDQzMS4wODgsMjAxLjg1OCA1MzQuOTkyLDIwMS44NThDNjM4Ljg5NiwyMDEuODU4IDcyMy4yNTMsMjg2LjIxNSA3MjMuMjUzLDM5MC4xMTlMNzIzLjI1Myw0OTMuNzA0Wk00MzMuMzI4LDQ5MC45NjZMNjM2LjY4Niw0OTIuMTE2QzYzNi42ODYsNDkyLjExNiA2MzcuMTQyLDM4NS4xMzIgNjM3LjA4NiwzODQuNDg5QzYzMS44NDksMzI0LjE2MyA1NzkuMTE4LDI4OC4wNzkgNTM0Ljk5MiwyODguNTM1QzQ5My4wMTcsMjg4Ljk2OSA0MzUuOTIsMzE4LjEgNDMzLjY1NiwzODMuNzk3QzQzMy40NzYsMzg5LjAxNyA0MzMuMzI4LDQ5MC45NjYgNDMzLjMyOCw0OTAuOTY2Wk01MDMuMjk2LDcxNC4zODJMNDkyLjQzMyw3ODQuNDU1QzQ5Mi40MzMsNzg0LjQ1NSA0OTAuMzc2LDc5OC4yODQgNDkyLjQxNiw4MDIuNjY0QzQ5Ni43OCw4MTIuMDMxIDUwMy44MjIsODExLjExMSA1MDMuODIyLDgxMS4xMTFMNTY1LjYsODEwLjgzOEM1NjUuNiw4MTAuODM4IDU3Mi44NjMsODExLjQ3MiA1NzcuNjM3LDgwMi4xNTVDNTc5Ljg4LDc5Ny43NzUgNTc3LjMyMyw3ODMuNzQgNTc3LjMyMyw3ODMuNzRMNTY2LjY0OSw3MTQuNDAxQzU5MC43NDMsNzAyLjY0OSA2MDcuMzU5LDY3Ny45MDggNjA3LjM1OSw2NDkuMzE4QzYwNy4zNTksNjA5LjM3NyA1NzQuOTMyLDU3Ni45NSA1MzQuOTkyLDU3Ni45NUM0OTUuMDUxLDU3Ni45NSA0NjIuNjI0LDYwOS4zNzcgNDYyLjYyNCw2NDkuMzE4QzQ2Mi42MjQsNjc3Ljg5MyA0NzkuMjIzLDcwMi42MjIgNTAzLjI5Niw3MTQuMzgyWiIgc3R5bGU9ImZpbGw6cmdiKDAsMTUyLDI0OCk7Ii8+CiAgICA8L2c+Cjwvc3ZnPgo=" "icon_light":"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+Cjxzdmcgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEwODAgMTA4MCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWw6c3BhY2U9InByZXNlcnZlIiB4bWxuczpzZXJpZj0iaHR0cDovL3d3dy5zZXJpZi5jb20vIiBzdHlsZT0iZmlsbC1ydWxlOmV2ZW5vZGQ7Y2xpcC1ydWxlOmV2ZW5vZGQ7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjI7Ij4KICAgIDxnIHRyYW5zZm9ybT0ibWF0cml4KDAuOTgzODI3LDAsMCwwLjk4MzgyNywxMy42NjEsLTAuMjg0Njk5KSI+CiAgICAgICAgPHBhdGggZD0iTTcyMy4yNTMsNDkzLjcwNEM3NTYuMjg4LDQ5NS4yMiA3ODIuNjQ0LDUyMi41MiA3ODIuNjQ0LDU1NS45MjdMNzgyLjY0NCw4MzQuMzA0Qzc4Mi42NDQsODY4LjY4MyA3NTQuNzMzLDg5Ni41OTMgNzIwLjM1NSw4OTYuNTkzTDM1MS4zOTMsODk2LjU5M0MzMTcuMDE1LDg5Ni41OTMgMjg5LjEwNCw4NjguNjgzIDI4OS4xMDQsODM0LjMwNEwyODkuMTA0LDU1NS45MjdDMjg5LjEwNCw1MjMuMTE3IDMxNC41MjYsNDk2LjE5OCAzNDYuNzMsNDkzLjgxTDM0Ni43MywzOTAuMTE5QzM0Ni43MywyODYuMjE1IDQzMS4wODgsMjAxLjg1OCA1MzQuOTkyLDIwMS44NThDNjM4Ljg5NiwyMDEuODU4IDcyMy4yNTMsMjg2LjIxNSA3MjMuMjUzLDM5MC4xMTlMNzIzLjI1Myw0OTMuNzA0Wk00MzMuMzI4LDQ5MC45NjZMNjM2LjY4Niw0OTIuMTE2QzYzNi42ODYsNDkyLjExNiA2MzcuMTQyLDM4NS4xMzIgNjM3LjA4NiwzODQuNDg5QzYzMS44NDksMzI0LjE2MyA1NzkuMTE4LDI4OC4wNzkgNTM0Ljk5MiwyODguNTM1QzQ5My4wMTcsMjg4Ljk2OSA0MzUuOTIsMzE4LjEgNDMzLjY1NiwzODMuNzk3QzQzMy40NzYsMzg5LjAxNyA0MzMuMzI4LDQ5MC45NjYgNDMzLjMyOCw0OTAuOTY2Wk01MDMuMjk2LDcxNC4zODJMNDkyLjQzMyw3ODQuNDU1QzQ5Mi40MzMsNzg0LjQ1NSA0OTAuMzc2LDc5OC4yODQgNDkyLjQxNiw4MDIuNjY0QzQ5Ni43OCw4MTIuMDMxIDUwMy44MjIsODExLjExMSA1MDMuODIyLDgxMS4xMTFMNTY1LjYsODEwLjgzOEM1NjUuNiw4MTAuODM4IDU3Mi44NjMsODExLjQ3MiA1NzcuNjM3LDgwMi4xNTVDNTc5Ljg4LDc5Ny43NzUgNTc3LjMyMyw3ODMuNzQgNTc3LjMyMyw3ODMuNzRMNTY2LjY0OSw3MTQuNDAxQzU5MC43NDMsNzAyLjY0OSA2MDcuMzU5LDY3Ny45MDggNjA3LjM1OSw2NDkuMzE4QzYwNy4zNTksNjA5LjM3NyA1NzQuOTMyLDU3Ni45NSA1MzQuOTkyLDU3Ni45NUM0OTUuMDUxLDU3Ni45NSA0NjIuNjI0LDYwOS4zNzcgNDYyLjYyNCw2NDkuMzE4QzQ2Mi42MjQsNjc3Ljg5MyA0NzkuMjIzLDcwMi42MjIgNTAzLjI5Niw3MTQuMzgyWiIgc3R5bGU9ImZpbGw6cmdiKDAsMTUyLDI0OCk7Ii8+CiAgICA8L2c+Cjwvc3ZnPgo="
}, },
"b35a26b2-8f6e-4697-ab1d-d44db4da28c6":{ "b35a26b2-8f6e-4697-ab1d-d44db4da28c6":{
"name": "Zoho Vault", "name": "Zoho Vault",
"icon_dark": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMzIgMzIiPgogIDxkZWZzPgogICAgPHN0eWxlPgogICAgICAuY2xzLTEgewogICAgICAgIGZpbGw6ICMyMjZlYjM7CiAgICAgIH0KCiAgICAgIC5jbHMtMiB7CiAgICAgICAgZmlsbDogI2ZmZjsKICAgICAgfQoKICAgICAgLmNscy0zIHsKICAgICAgICBvcGFjaXR5OiAuOTsKICAgICAgfQoKICAgICAgLmNscy00IHsKICAgICAgICBmaWxsOiAjZTQyNTI4OwogICAgICB9CiAgICA8L3N0eWxlPgogIDwvZGVmcz4KICA8cGF0aCBjbGFzcz0iY2xzLTIiIGQ9Ik0yNi4xLDMuNjJINS45Yy0xLjI0LDAtMi4yNSwxLjAxLTIuMjUsMi4yNXYxNi45NGMwLDEuMjQsMS4wMSwyLjI1LDIuMjUsMi4yNWguMWMuMy4wNy42Ny4yOS42Ny45MXYyLjExYzAsLjE1LjEyLjI3LjI3LjI3aDIuNzVjLjE1LDAsLjI3LS4xMi4yNy0uMjd2LTEuMjNzLjA3LTEuNTYsMS43NS0xLjc5aDguNThjMS42OC4yMywxLjc1LDEuNzksMS43NSwxLjc5djEuMjNjMCwuMTUuMTIuMjcuMjcuMjdoMi43NWMuMTUsMCwuMjctLjEyLjI3LS4yN3YtMi4xMWMwLS42Mi4zNy0uODMuNjctLjkxaC4wOWMxLjI0LDAsMi4yNS0xLjAxLDIuMjUtMi4yNVY1Ljg3YzAtMS4yNC0xLjAxLTIuMjUtMi4yNS0yLjI1WiIvPgogIDxnPgogICAgPGcgY2xhc3M9ImNscy0zIj4KICAgICAgPHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMjUuMDYsMzBoLTIuNzVjLTEuMDYsMC0xLjkyLS44Ni0xLjkyLTEuOTJ2LTEuMTNjMC0uMTUtLjEyLS4yNy0uMjctLjI3aC04LjI0Yy0uMTUsMC0uMjcuMTItLjI3LjI3djEuMTNjMCwxLjA2LS44NiwxLjkyLTEuOTIsMS45MmgtMi43NWMtMS4wNiwwLTEuOTItLjg2LTEuOTItMS45MnYtMS40OWMtMS43Mi0uMzgtMy4wMi0xLjkyLTMuMDItMy43NlY1Ljg0YzAtMi4xMiwxLjcyLTMuODQsMy44NC0zLjg0aDIwLjMxYzIuMTIsMCwzLjg0LDEuNzIsMy44NCwzLjg0djE2Ljk5YzAsMS44NC0xLjMsMy4zOC0zLjAyLDMuNzZ2MS40OWMwLDEuMDYtLjg2LDEuOTItMS45MiwxLjkyWk0xMS44OCwyNS4wM2g4LjI0YzEuMDYsMCwxLjkyLjg2LDEuOTIsMS45MnYxLjEzYzAsLjE1LjEyLjI3LjI3LjI3aDIuNzVjLjE1LDAsLjI3LS4xMi4yNy0uMjd2LTIuMjJjMC0uNDYuMzctLjgyLjgyLS44MiwxLjIxLDAsMi4yLS45OSwyLjItMi4yVjUuODRjMC0xLjIxLS45OS0yLjItMi4yLTIuMkg1Ljg0Yy0xLjIxLDAtMi4yLjk5LTIuMiwyLjJ2MTYuOTljMCwxLjIxLjk5LDIuMiwyLjIsMi4yLjQ2LDAsLjgyLjM3LjgyLjgydjIuMjJjMCwuMTUuMTIuMjcuMjcuMjdoMi43NWMuMTUsMCwuMjctLjEyLjI3LS4yN3YtMS4xM2MwLTEuMDYuODYtMS45MiwxLjkyLTEuOTJaIi8+CiAgICA8L2c+CiAgICA8ZyBjbGFzcz0iY2xzLTMiPgogICAgICA8cGF0aCBjbGFzcz0iY2xzLTQiIGQ9Ik0xMC43NywxOS4yNWMtLjE3LDAtLjM0LS4wNS0uNDgtLjE2LS4zNy0uMjctLjQ1LS43OC0uMTgtMS4xNWwyLjY3LTMuNjhjLjI3LS4zNy43OC0uNDUsMS4xNS0uMTguMzcuMjcuNDUuNzguMTgsMS4xNWwtMi42NywzLjY4Yy0uMTYuMjItLjQxLjM0LS42Ny4zNFoiLz4KICAgIDwvZz4KICAgIDxnIGNsYXNzPSJjbHMtMyI+CiAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNCIgZD0iTTE2LjEyLDE5LjI1Yy0uMjYsMC0uNTEtLjEyLS42Ny0uMzRsLTIuNjctMy42OGMtLjI3LS4zNy0uMTktLjg4LjE4LTEuMTUuMzctLjI3Ljg4LS4xOSwxLjE1LjE4bDIuNjcsMy42OGMuMjcuMzcuMTkuODgtLjE4LDEuMTUtLjE1LjExLS4zMi4xNi0uNDguMTZaIi8+CiAgICA8L2c+CiAgICA8ZyBjbGFzcz0iY2xzLTMiPgogICAgICA8cGF0aCBjbGFzcz0iY2xzLTQiIGQ9Ik0xMy40NCwxNS41N2MtLjQ2LDAtLjgyLS4zNy0uODItLjgydi00LjUxYzAtLjQ2LjM3LS44Mi44Mi0uODJzLjgyLjM3LjgyLjgydjQuNTFjMCwuNDYtLjM3LjgyLS44Mi44MloiLz4KICAgIDwvZz4KICAgIDxnIGNsYXNzPSJjbHMtMyI+CiAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNCIgZD0iTTEzLjQ0LDE1LjU4Yy0uMzUsMC0uNjctLjIyLS43OC0uNTctLjE0LS40My4xLS45LjUzLTEuMDRsNC4zMi0xLjM5Yy40My0uMTQuOS4xLDEuMDQuNTMuMTQuNDMtLjEuOS0uNTMsMS4wNGwtNC4zMiwxLjM5Yy0uMDkuMDMtLjE3LjA0LS4yNi4wNFoiLz4KICAgIDwvZz4KICAgIDxnIGNsYXNzPSJjbHMtMyI+CiAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNCIgZD0iTTEzLjQ0LDE1LjU4Yy0uMDksMC0uMTctLjAxLS4yNS0uMDRsLTQuMzItMS4zOWMtLjQzLS4xNC0uNjctLjYtLjUzLTEuMDQuMTQtLjQzLjYtLjY3LDEuMDQtLjUzbDQuMzIsMS4zOWMuNDMuMTQuNjcuNi41MywxLjA0LS4xMS4zNS0uNDMuNTctLjc4LjU3WiIvPgogICAgPC9nPgogICAgPGcgY2xhc3M9ImNscy0zIj4KICAgICAgPHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMjIuODUsMTkuMjNjLS40NiwwLS44Mi0uMzctLjgyLS44MnYtOC4xNWMwLS40Ni4zNy0uODIuODItLjgycy44Mi4zNy44Mi44MnY4LjE1YzAsLjQ2LS4zNy44Mi0uODIuODJaIi8+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4=", "icon_dark": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMzIgMzIiPgogIDxkZWZzPgogICAgPHN0eWxlPgogICAgICAuY2xzLTEgewogICAgICAgIGZpbGw6ICMyMjZlYjM7CiAgICAgIH0KCiAgICAgIC5jbHMtMiB7CiAgICAgICAgZmlsbDogI2ZmZjsKICAgICAgfQoKICAgICAgLmNscy0zIHsKICAgICAgICBvcGFjaXR5OiAuOTsKICAgICAgfQoKICAgICAgLmNscy00IHsKICAgICAgICBmaWxsOiAjZTQyNTI4OwogICAgICB9CiAgICA8L3N0eWxlPgogIDwvZGVmcz4KICA8cGF0aCBjbGFzcz0iY2xzLTIiIGQ9Ik0yNi4xLDMuNjJINS45Yy0xLjI0LDAtMi4yNSwxLjAxLTIuMjUsMi4yNXYxNi45NGMwLDEuMjQsMS4wMSwyLjI1LDIuMjUsMi4yNWguMWMuMy4wNy42Ny4yOS42Ny45MXYyLjExYzAsLjE1LjEyLjI3LjI3LjI3aDIuNzVjLjE1LDAsLjI3LS4xMi4yNy0uMjd2LTEuMjNzLjA3LTEuNTYsMS43NS0xLjc5aDguNThjMS42OC4yMywxLjc1LDEuNzksMS43NSwxLjc5djEuMjNjMCwuMTUuMTIuMjcuMjcuMjdoMi43NWMuMTUsMCwuMjctLjEyLjI3LS4yN3YtMi4xMWMwLS42Mi4zNy0uODMuNjctLjkxaC4wOWMxLjI0LDAsMi4yNS0xLjAxLDIuMjUtMi4yNVY1Ljg3YzAtMS4yNC0xLjAxLTIuMjUtMi4yNS0yLjI1WiIvPgogIDxnPgogICAgPGcgY2xhc3M9ImNscy0zIj4KICAgICAgPHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMjUuMDYsMzBoLTIuNzVjLTEuMDYsMC0xLjkyLS44Ni0xLjkyLTEuOTJ2LTEuMTNjMC0uMTUtLjEyLS4yNy0uMjctLjI3aC04LjI0Yy0uMTUsMC0uMjcuMTItLjI3LjI3djEuMTNjMCwxLjA2LS44NiwxLjkyLTEuOTIsMS45MmgtMi43NWMtMS4wNiwwLTEuOTItLjg2LTEuOTItMS45MnYtMS40OWMtMS43Mi0uMzgtMy4wMi0xLjkyLTMuMDItMy43NlY1Ljg0YzAtMi4xMiwxLjcyLTMuODQsMy44NC0zLjg0aDIwLjMxYzIuMTIsMCwzLjg0LDEuNzIsMy44NCwzLjg0djE2Ljk5YzAsMS44NC0xLjMsMy4zOC0zLjAyLDMuNzZ2MS40OWMwLDEuMDYtLjg2LDEuOTItMS45MiwxLjkyWk0xMS44OCwyNS4wM2g4LjI0YzEuMDYsMCwxLjkyLjg2LDEuOTIsMS45MnYxLjEzYzAsLjE1LjEyLjI3LjI3LjI3aDIuNzVjLjE1LDAsLjI3LS4xMi4yNy0uMjd2LTIuMjJjMC0uNDYuMzctLjgyLjgyLS44MiwxLjIxLDAsMi4yLS45OSwyLjItMi4yVjUuODRjMC0xLjIxLS45OS0yLjItMi4yLTIuMkg1Ljg0Yy0xLjIxLDAtMi4yLjk5LTIuMiwyLjJ2MTYuOTljMCwxLjIxLjk5LDIuMiwyLjIsMi4yLjQ2LDAsLjgyLjM3LjgyLjgydjIuMjJjMCwuMTUuMTIuMjcuMjcuMjdoMi43NWMuMTUsMCwuMjctLjEyLjI3LS4yN3YtMS4xM2MwLTEuMDYuODYtMS45MiwxLjkyLTEuOTJaIi8+CiAgICA8L2c+CiAgICA8ZyBjbGFzcz0iY2xzLTMiPgogICAgICA8cGF0aCBjbGFzcz0iY2xzLTQiIGQ9Ik0xMC43NywxOS4yNWMtLjE3LDAtLjM0LS4wNS0uNDgtLjE2LS4zNy0uMjctLjQ1LS43OC0uMTgtMS4xNWwyLjY3LTMuNjhjLjI3LS4zNy43OC0uNDUsMS4xNS0uMTguMzcuMjcuNDUuNzguMTgsMS4xNWwtMi42NywzLjY4Yy0uMTYuMjItLjQxLjM0LS42Ny4zNFoiLz4KICAgIDwvZz4KICAgIDxnIGNsYXNzPSJjbHMtMyI+CiAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNCIgZD0iTTE2LjEyLDE5LjI1Yy0uMjYsMC0uNTEtLjEyLS42Ny0uMzRsLTIuNjctMy42OGMtLjI3LS4zNy0uMTktLjg4LjE4LTEuMTUuMzctLjI3Ljg4LS4xOSwxLjE1LjE4bDIuNjcsMy42OGMuMjcuMzcuMTkuODgtLjE4LDEuMTUtLjE1LjExLS4zMi4xNi0uNDguMTZaIi8+CiAgICA8L2c+CiAgICA8ZyBjbGFzcz0iY2xzLTMiPgogICAgICA8cGF0aCBjbGFzcz0iY2xzLTQiIGQ9Ik0xMy40NCwxNS41N2MtLjQ2LDAtLjgyLS4zNy0uODItLjgydi00LjUxYzAtLjQ2LjM3LS44Mi44Mi0uODJzLjgyLjM3LjgyLjgydjQuNTFjMCwuNDYtLjM3LjgyLS44Mi44MloiLz4KICAgIDwvZz4KICAgIDxnIGNsYXNzPSJjbHMtMyI+CiAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNCIgZD0iTTEzLjQ0LDE1LjU4Yy0uMzUsMC0uNjctLjIyLS43OC0uNTctLjE0LS40My4xLS45LjUzLTEuMDRsNC4zMi0xLjM5Yy40My0uMTQuOS4xLDEuMDQuNTMuMTQuNDMtLjEuOS0uNTMsMS4wNGwtNC4zMiwxLjM5Yy0uMDkuMDMtLjE3LjA0LS4yNi4wNFoiLz4KICAgIDwvZz4KICAgIDxnIGNsYXNzPSJjbHMtMyI+CiAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNCIgZD0iTTEzLjQ0LDE1LjU4Yy0uMDksMC0uMTctLjAxLS4yNS0uMDRsLTQuMzItMS4zOWMtLjQzLS4xNC0uNjctLjYtLjUzLTEuMDQuMTQtLjQzLjYtLjY3LDEuMDQtLjUzbDQuMzIsMS4zOWMuNDMuMTQuNjcuNi41MywxLjA0LS4xMS4zNS0uNDMuNTctLjc4LjU3WiIvPgogICAgPC9nPgogICAgPGcgY2xhc3M9ImNscy0zIj4KICAgICAgPHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMjIuODUsMTkuMjNjLS40NiwwLS44Mi0uMzctLjgyLS44MnYtOC4xNWMwLS40Ni4zNy0uODIuODItLjgycy44Mi4zNy44Mi44MnY4LjE1YzAsLjQ2LS4zNy44Mi0uODIuODJaIi8+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4=",
"icon_light": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMzIgMzIiPgogIDxkZWZzPgogICAgPHN0eWxlPgogICAgICAuY2xzLTEgewogICAgICAgIGZpbGw6ICMyMjZlYjM7CiAgICAgIH0KCiAgICAgIC5jbHMtMiB7CiAgICAgICAgZmlsbDogI2ZmZjsKICAgICAgfQoKICAgICAgLmNscy0zIHsKICAgICAgICBvcGFjaXR5OiAuOTsKICAgICAgfQoKICAgICAgLmNscy00IHsKICAgICAgICBmaWxsOiAjZTQyNTI4OwogICAgICB9CiAgICA8L3N0eWxlPgogIDwvZGVmcz4KICA8cGF0aCBjbGFzcz0iY2xzLTIiIGQ9Ik0yNi4xLDMuNjJINS45Yy0xLjI0LDAtMi4yNSwxLjAxLTIuMjUsMi4yNXYxNi45NGMwLDEuMjQsMS4wMSwyLjI1LDIuMjUsMi4yNWguMWMuMy4wNy42Ny4yOS42Ny45MXYyLjExYzAsLjE1LjEyLjI3LjI3LjI3aDIuNzVjLjE1LDAsLjI3LS4xMi4yNy0uMjd2LTEuMjNzLjA3LTEuNTYsMS43NS0xLjc5aDguNThjMS42OC4yMywxLjc1LDEuNzksMS43NSwxLjc5djEuMjNjMCwuMTUuMTIuMjcuMjcuMjdoMi43NWMuMTUsMCwuMjctLjEyLjI3LS4yN3YtMi4xMWMwLS42Mi4zNy0uODMuNjctLjkxaC4wOWMxLjI0LDAsMi4yNS0xLjAxLDIuMjUtMi4yNVY1Ljg3YzAtMS4yNC0xLjAxLTIuMjUtMi4yNS0yLjI1WiIvPgogIDxnPgogICAgPGcgY2xhc3M9ImNscy0zIj4KICAgICAgPHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMjUuMDYsMzBoLTIuNzVjLTEuMDYsMC0xLjkyLS44Ni0xLjkyLTEuOTJ2LTEuMTNjMC0uMTUtLjEyLS4yNy0uMjctLjI3aC04LjI0Yy0uMTUsMC0uMjcuMTItLjI3LjI3djEuMTNjMCwxLjA2LS44NiwxLjkyLTEuOTIsMS45MmgtMi43NWMtMS4wNiwwLTEuOTItLjg2LTEuOTItMS45MnYtMS40OWMtMS43Mi0uMzgtMy4wMi0xLjkyLTMuMDItMy43NlY1Ljg0YzAtMi4xMiwxLjcyLTMuODQsMy44NC0zLjg0aDIwLjMxYzIuMTIsMCwzLjg0LDEuNzIsMy44NCwzLjg0djE2Ljk5YzAsMS44NC0xLjMsMy4zOC0zLjAyLDMuNzZ2MS40OWMwLDEuMDYtLjg2LDEuOTItMS45MiwxLjkyWk0xMS44OCwyNS4wM2g4LjI0YzEuMDYsMCwxLjkyLjg2LDEuOTIsMS45MnYxLjEzYzAsLjE1LjEyLjI3LjI3LjI3aDIuNzVjLjE1LDAsLjI3LS4xMi4yNy0uMjd2LTIuMjJjMC0uNDYuMzctLjgyLjgyLS44MiwxLjIxLDAsMi4yLS45OSwyLjItMi4yVjUuODRjMC0xLjIxLS45OS0yLjItMi4yLTIuMkg1Ljg0Yy0xLjIxLDAtMi4yLjk5LTIuMiwyLjJ2MTYuOTljMCwxLjIxLjk5LDIuMiwyLjIsMi4yLjQ2LDAsLjgyLjM3LjgyLjgydjIuMjJjMCwuMTUuMTIuMjcuMjcuMjdoMi43NWMuMTUsMCwuMjctLjEyLjI3LS4yN3YtMS4xM2MwLTEuMDYuODYtMS45MiwxLjkyLTEuOTJaIi8+CiAgICA8L2c+CiAgICA8ZyBjbGFzcz0iY2xzLTMiPgogICAgICA8cGF0aCBjbGFzcz0iY2xzLTQiIGQ9Ik0xMC43NywxOS4yNWMtLjE3LDAtLjM0LS4wNS0uNDgtLjE2LS4zNy0uMjctLjQ1LS43OC0uMTgtMS4xNWwyLjY3LTMuNjhjLjI3LS4zNy43OC0uNDUsMS4xNS0uMTguMzcuMjcuNDUuNzguMTgsMS4xNWwtMi42NywzLjY4Yy0uMTYuMjItLjQxLjM0LS42Ny4zNFoiLz4KICAgIDwvZz4KICAgIDxnIGNsYXNzPSJjbHMtMyI+CiAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNCIgZD0iTTE2LjEyLDE5LjI1Yy0uMjYsMC0uNTEtLjEyLS42Ny0uMzRsLTIuNjctMy42OGMtLjI3LS4zNy0uMTktLjg4LjE4LTEuMTUuMzctLjI3Ljg4LS4xOSwxLjE1LjE4bDIuNjcsMy42OGMuMjcuMzcuMTkuODgtLjE4LDEuMTUtLjE1LjExLS4zMi4xNi0uNDguMTZaIi8+CiAgICA8L2c+CiAgICA8ZyBjbGFzcz0iY2xzLTMiPgogICAgICA8cGF0aCBjbGFzcz0iY2xzLTQiIGQ9Ik0xMy40NCwxNS41N2MtLjQ2LDAtLjgyLS4zNy0uODItLjgydi00LjUxYzAtLjQ2LjM3LS44Mi44Mi0uODJzLjgyLjM3LjgyLjgydjQuNTFjMCwuNDYtLjM3LjgyLS44Mi44MloiLz4KICAgIDwvZz4KICAgIDxnIGNsYXNzPSJjbHMtMyI+CiAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNCIgZD0iTTEzLjQ0LDE1LjU4Yy0uMzUsMC0uNjctLjIyLS43OC0uNTctLjE0LS40My4xLS45LjUzLTEuMDRsNC4zMi0xLjM5Yy40My0uMTQuOS4xLDEuMDQuNTMuMTQuNDMtLjEuOS0uNTMsMS4wNGwtNC4zMiwxLjM5Yy0uMDkuMDMtLjE3LjA0LS4yNi4wNFoiLz4KICAgIDwvZz4KICAgIDxnIGNsYXNzPSJjbHMtMyI+CiAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNCIgZD0iTTEzLjQ0LDE1LjU4Yy0uMDksMC0uMTctLjAxLS4yNS0uMDRsLTQuMzItMS4zOWMtLjQzLS4xNC0uNjctLjYtLjUzLTEuMDQuMTQtLjQzLjYtLjY3LDEuMDQtLjUzbDQuMzIsMS4zOWMuNDMuMTQuNjcuNi41MywxLjA0LS4xMS4zNS0uNDMuNTctLjc4LjU3WiIvPgogICAgPC9nPgogICAgPGcgY2xhc3M9ImNscy0zIj4KICAgICAgPHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMjIuODUsMTkuMjNjLS40NiwwLS44Mi0uMzctLjgyLS44MnYtOC4xNWMwLS40Ni4zNy0uODIuODItLjgycy44Mi4zNy44Mi44MnY4LjE1YzAsLjQ2LS4zNy44Mi0uODIuODJaIi8+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4=" "icon_light": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMzIgMzIiPgogIDxkZWZzPgogICAgPHN0eWxlPgogICAgICAuY2xzLTEgewogICAgICAgIGZpbGw6ICMyMjZlYjM7CiAgICAgIH0KCiAgICAgIC5jbHMtMiB7CiAgICAgICAgZmlsbDogI2ZmZjsKICAgICAgfQoKICAgICAgLmNscy0zIHsKICAgICAgICBvcGFjaXR5OiAuOTsKICAgICAgfQoKICAgICAgLmNscy00IHsKICAgICAgICBmaWxsOiAjZTQyNTI4OwogICAgICB9CiAgICA8L3N0eWxlPgogIDwvZGVmcz4KICA8cGF0aCBjbGFzcz0iY2xzLTIiIGQ9Ik0yNi4xLDMuNjJINS45Yy0xLjI0LDAtMi4yNSwxLjAxLTIuMjUsMi4yNXYxNi45NGMwLDEuMjQsMS4wMSwyLjI1LDIuMjUsMi4yNWguMWMuMy4wNy42Ny4yOS42Ny45MXYyLjExYzAsLjE1LjEyLjI3LjI3LjI3aDIuNzVjLjE1LDAsLjI3LS4xMi4yNy0uMjd2LTEuMjNzLjA3LTEuNTYsMS43NS0xLjc5aDguNThjMS42OC4yMywxLjc1LDEuNzksMS43NSwxLjc5djEuMjNjMCwuMTUuMTIuMjcuMjcuMjdoMi43NWMuMTUsMCwuMjctLjEyLjI3LS4yN3YtMi4xMWMwLS42Mi4zNy0uODMuNjctLjkxaC4wOWMxLjI0LDAsMi4yNS0xLjAxLDIuMjUtMi4yNVY1Ljg3YzAtMS4yNC0xLjAxLTIuMjUtMi4yNS0yLjI1WiIvPgogIDxnPgogICAgPGcgY2xhc3M9ImNscy0zIj4KICAgICAgPHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMjUuMDYsMzBoLTIuNzVjLTEuMDYsMC0xLjkyLS44Ni0xLjkyLTEuOTJ2LTEuMTNjMC0uMTUtLjEyLS4yNy0uMjctLjI3aC04LjI0Yy0uMTUsMC0uMjcuMTItLjI3LjI3djEuMTNjMCwxLjA2LS44NiwxLjkyLTEuOTIsMS45MmgtMi43NWMtMS4wNiwwLTEuOTItLjg2LTEuOTItMS45MnYtMS40OWMtMS43Mi0uMzgtMy4wMi0xLjkyLTMuMDItMy43NlY1Ljg0YzAtMi4xMiwxLjcyLTMuODQsMy44NC0zLjg0aDIwLjMxYzIuMTIsMCwzLjg0LDEuNzIsMy44NCwzLjg0djE2Ljk5YzAsMS44NC0xLjMsMy4zOC0zLjAyLDMuNzZ2MS40OWMwLDEuMDYtLjg2LDEuOTItMS45MiwxLjkyWk0xMS44OCwyNS4wM2g4LjI0YzEuMDYsMCwxLjkyLjg2LDEuOTIsMS45MnYxLjEzYzAsLjE1LjEyLjI3LjI3LjI3aDIuNzVjLjE1LDAsLjI3LS4xMi4yNy0uMjd2LTIuMjJjMC0uNDYuMzctLjgyLjgyLS44MiwxLjIxLDAsMi4yLS45OSwyLjItMi4yVjUuODRjMC0xLjIxLS45OS0yLjItMi4yLTIuMkg1Ljg0Yy0xLjIxLDAtMi4yLjk5LTIuMiwyLjJ2MTYuOTljMCwxLjIxLjk5LDIuMiwyLjIsMi4yLjQ2LDAsLjgyLjM3LjgyLjgydjIuMjJjMCwuMTUuMTIuMjcuMjcuMjdoMi43NWMuMTUsMCwuMjctLjEyLjI3LS4yN3YtMS4xM2MwLTEuMDYuODYtMS45MiwxLjkyLTEuOTJaIi8+CiAgICA8L2c+CiAgICA8ZyBjbGFzcz0iY2xzLTMiPgogICAgICA8cGF0aCBjbGFzcz0iY2xzLTQiIGQ9Ik0xMC43NywxOS4yNWMtLjE3LDAtLjM0LS4wNS0uNDgtLjE2LS4zNy0uMjctLjQ1LS43OC0uMTgtMS4xNWwyLjY3LTMuNjhjLjI3LS4zNy43OC0uNDUsMS4xNS0uMTguMzcuMjcuNDUuNzguMTgsMS4xNWwtMi42NywzLjY4Yy0uMTYuMjItLjQxLjM0LS42Ny4zNFoiLz4KICAgIDwvZz4KICAgIDxnIGNsYXNzPSJjbHMtMyI+CiAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNCIgZD0iTTE2LjEyLDE5LjI1Yy0uMjYsMC0uNTEtLjEyLS42Ny0uMzRsLTIuNjctMy42OGMtLjI3LS4zNy0uMTktLjg4LjE4LTEuMTUuMzctLjI3Ljg4LS4xOSwxLjE1LjE4bDIuNjcsMy42OGMuMjcuMzcuMTkuODgtLjE4LDEuMTUtLjE1LjExLS4zMi4xNi0uNDguMTZaIi8+CiAgICA8L2c+CiAgICA8ZyBjbGFzcz0iY2xzLTMiPgogICAgICA8cGF0aCBjbGFzcz0iY2xzLTQiIGQ9Ik0xMy40NCwxNS41N2MtLjQ2LDAtLjgyLS4zNy0uODItLjgydi00LjUxYzAtLjQ2LjM3LS44Mi44Mi0uODJzLjgyLjM3LjgyLjgydjQuNTFjMCwuNDYtLjM3LjgyLS44Mi44MloiLz4KICAgIDwvZz4KICAgIDxnIGNsYXNzPSJjbHMtMyI+CiAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNCIgZD0iTTEzLjQ0LDE1LjU4Yy0uMzUsMC0uNjctLjIyLS43OC0uNTctLjE0LS40My4xLS45LjUzLTEuMDRsNC4zMi0xLjM5Yy40My0uMTQuOS4xLDEuMDQuNTMuMTQuNDMtLjEuOS0uNTMsMS4wNGwtNC4zMiwxLjM5Yy0uMDkuMDMtLjE3LjA0LS4yNi4wNFoiLz4KICAgIDwvZz4KICAgIDxnIGNsYXNzPSJjbHMtMyI+CiAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNCIgZD0iTTEzLjQ0LDE1LjU4Yy0uMDksMC0uMTctLjAxLS4yNS0uMDRsLTQuMzItMS4zOWMtLjQzLS4xNC0uNjctLjYtLjUzLTEuMDQuMTQtLjQzLjYtLjY3LDEuMDQtLjUzbDQuMzIsMS4zOWMuNDMuMTQuNjcuNi41MywxLjA0LS4xMS4zNS0uNDMuNTctLjc4LjU3WiIvPgogICAgPC9nPgogICAgPGcgY2xhc3M9ImNscy0zIj4KICAgICAgPHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMjIuODUsMTkuMjNjLS40NiwwLS44Mi0uMzctLjgyLS44MnYtOC4xNWMwLS40Ni4zNy0uODIuODItLjgycy44Mi4zNy44Mi44MnY4LjE1YzAsLjQ2LS4zNy44Mi0uODIuODJaIi8+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4="
}, },
"b78a0a55-6ef8-d246-a042-ba0f6d55050c": { "b78a0a55-6ef8-d246-a042-ba0f6d55050c": {
"name": "LastPass", "name": "LastPass",
"icon_dark": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIHZpZXdCb3g9IjAgMCA1MCA1MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjUwIiBoZWlnaHQ9IjUwIiByeD0iNy44NDc4MiIgZmlsbD0idXJsKCNwYWludDBfbGluZWFyXzI4NF81NzQzKSIvPgo8cGF0aCBkPSJNMzkuMzUxOCAxNy42MzgzQzM4LjkwNSAxNy42MzgzIDM4LjU2NjggMTcuOTc0NyAzOC41NjY4IDE4LjQyODdWMzEuNTYwNEMzOC41NjY4IDMyLjAxMDggMzguOTAxNCAzMi4zNTA5IDM5LjM1MTggMzIuMzUwOUMzOS44MDIyIDMyLjM1MDkgNDAuMTM2OCAzMi4wMTQ0IDQwLjEzNjggMzEuNTYwNFYxOC40Mjg3QzQwLjEzNjggMTcuOTc4NCAzOS44MDIyIDE3LjYzODMgMzkuMzUxOCAxNy42MzgzWiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTIxLjg5MTggMjIuNTQ0OUMyMC4zODE0IDIyLjU0NDkgMTkuMDk1NCAyMy43ODU3IDE5LjA5NTQgMjUuMzA2OUMxOS4wOTU0IDI2LjgyODEgMjAuMzI3MiAyOC4xMjMyIDIxLjgzNzUgMjguMTIzMkMyMy4zNDc4IDI4LjEyMzIgMjQuNjMzOSAyNi44ODI0IDI0LjYzMzkgMjUuMzYxMkMyNC42MzM5IDIzLjg0IDIzLjQwMjEgMjIuNTQ0OSAyMS44OTE4IDIyLjU0NDlaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMzEuMDY5NCAyMi41NDQ5QzI5LjU1OTEgMjIuNTQ0OSAyOC4yNzMxIDIzLjc4NTcgMjguMjczMSAyNS4zMDY5QzI4LjI3MzEgMjYuODI4MSAyOS41MDQ4IDI4LjEyMzIgMzEuMDE1MiAyOC4xMjMyQzMyLjUyNTUgMjguMTIzMiAzMy44MTE1IDI2Ljg4MjQgMzMuODExNSAyNS4zNjEyQzMzLjg2NTggMjMuODQgMzIuNjM0IDIyLjU0NDkgMzEuMDY5NCAyMi41NDQ5WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTEyLjY1OTYgMjIuNTQ0OUMxMS4xNDkzIDIyLjU0NDkgOS44NjMyOCAyMy43ODU3IDkuODYzMjggMjUuMzA2OUM5Ljg2MzI4IDI2LjgyODEgMTEuMDk1MSAyOC4xMjMyIDEyLjYwNTQgMjguMTIzMkMxNC4xMTU3IDI4LjEyMzIgMTUuNDAxNyAyNi44ODI0IDE1LjQwMTcgMjUuMzYxMkMxNS40MDE3IDIzLjg0IDE0LjE3IDIyLjU0NDkgMTIuNjU5NiAyMi41NDQ5WiIgZmlsbD0id2hpdGUiLz4KPGRlZnM+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQwX2xpbmVhcl8yODRfNTc0MyIgeDE9IjI1IiB5MT0iMCIgeDI9IjI1IiB5Mj0iNTAiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iIzNDM0QzRCIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMyNjI2MjYiLz4KPC9saW5lYXJHcmFkaWVudD4KPC9kZWZzPgo8L3N2Zz4K", "icon_dark": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIHZpZXdCb3g9IjAgMCA1MCA1MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjUwIiBoZWlnaHQ9IjUwIiByeD0iNy44NDc4MiIgZmlsbD0idXJsKCNwYWludDBfbGluZWFyXzI4NF81NzQzKSIvPgo8cGF0aCBkPSJNMzkuMzUxOCAxNy42MzgzQzM4LjkwNSAxNy42MzgzIDM4LjU2NjggMTcuOTc0NyAzOC41NjY4IDE4LjQyODdWMzEuNTYwNEMzOC41NjY4IDMyLjAxMDggMzguOTAxNCAzMi4zNTA5IDM5LjM1MTggMzIuMzUwOUMzOS44MDIyIDMyLjM1MDkgNDAuMTM2OCAzMi4wMTQ0IDQwLjEzNjggMzEuNTYwNFYxOC40Mjg3QzQwLjEzNjggMTcuOTc4NCAzOS44MDIyIDE3LjYzODMgMzkuMzUxOCAxNy42MzgzWiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTIxLjg5MTggMjIuNTQ0OUMyMC4zODE0IDIyLjU0NDkgMTkuMDk1NCAyMy43ODU3IDE5LjA5NTQgMjUuMzA2OUMxOS4wOTU0IDI2LjgyODEgMjAuMzI3MiAyOC4xMjMyIDIxLjgzNzUgMjguMTIzMkMyMy4zNDc4IDI4LjEyMzIgMjQuNjMzOSAyNi44ODI0IDI0LjYzMzkgMjUuMzYxMkMyNC42MzM5IDIzLjg0IDIzLjQwMjEgMjIuNTQ0OSAyMS44OTE4IDIyLjU0NDlaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMzEuMDY5NCAyMi41NDQ5QzI5LjU1OTEgMjIuNTQ0OSAyOC4yNzMxIDIzLjc4NTcgMjguMjczMSAyNS4zMDY5QzI4LjI3MzEgMjYuODI4MSAyOS41MDQ4IDI4LjEyMzIgMzEuMDE1MiAyOC4xMjMyQzMyLjUyNTUgMjguMTIzMiAzMy44MTE1IDI2Ljg4MjQgMzMuODExNSAyNS4zNjEyQzMzLjg2NTggMjMuODQgMzIuNjM0IDIyLjU0NDkgMzEuMDY5NCAyMi41NDQ5WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTEyLjY1OTYgMjIuNTQ0OUMxMS4xNDkzIDIyLjU0NDkgOS44NjMyOCAyMy43ODU3IDkuODYzMjggMjUuMzA2OUM5Ljg2MzI4IDI2LjgyODEgMTEuMDk1MSAyOC4xMjMyIDEyLjYwNTQgMjguMTIzMkMxNC4xMTU3IDI4LjEyMzIgMTUuNDAxNyAyNi44ODI0IDE1LjQwMTcgMjUuMzYxMkMxNS40MDE3IDIzLjg0IDE0LjE3IDIyLjU0NDkgMTIuNjU5NiAyMi41NDQ5WiIgZmlsbD0id2hpdGUiLz4KPGRlZnM+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQwX2xpbmVhcl8yODRfNTc0MyIgeDE9IjI1IiB5MT0iMCIgeDI9IjI1IiB5Mj0iNTAiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iIzNDM0QzRCIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMyNjI2MjYiLz4KPC9saW5lYXJHcmFkaWVudD4KPC9kZWZzPgo8L3N2Zz4K",
"icon_light": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIHZpZXdCb3g9IjAgMCA1MCA1MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjUwIiBoZWlnaHQ9IjUwIiByeD0iNy44NDc4MiIgZmlsbD0idXJsKCNwYWludDBfbGluZWFyXzI4NF81Nzc0KSIvPgo8cGF0aCBkPSJNMzkuMzUxNyAxNy42Mzg0QzM4LjkwNDkgMTcuNjM4NCAzOC41NjY3IDE3Ljk3NDkgMzguNTY2NyAxOC40Mjg5VjMxLjU2MDVDMzguNTY2NyAzMi4wMTA4IDM4LjkwMTMgMzIuMzUwOSAzOS4zNTE3IDMyLjM1MDlDMzkuODAyMSAzMi4zNTA5IDQwLjEzNjcgMzIuMDE0NSA0MC4xMzY3IDMxLjU2MDVWMTguNDI4OUM0MC4xMzY3IDE3Ljk3ODUgMzkuODAyMSAxNy42Mzg0IDM5LjM1MTcgMTcuNjM4NFoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0yMS44OTE4IDIyLjU0NUMyMC4zODE0IDIyLjU0NSAxOS4wOTU0IDIzLjc4NTkgMTkuMDk1NCAyNS4zMDdDMTkuMDk1NCAyNi44MjgyIDIwLjMyNzIgMjguMTIzMyAyMS44Mzc1IDI4LjEyMzNDMjMuMzQ3OCAyOC4xMjMzIDI0LjYzMzggMjYuODgyNSAyNC42MzM4IDI1LjM2MTNDMjQuNjMzOCAyMy44NDAxIDIzLjQwMjEgMjIuNTQ1IDIxLjg5MTggMjIuNTQ1WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTMxLjA2OTQgMjIuNTQ1QzI5LjU1OTEgMjIuNTQ1IDI4LjI3MyAyMy43ODU5IDI4LjI3MyAyNS4zMDdDMjguMjczIDI2LjgyODIgMjkuNTA0OCAyOC4xMjMzIDMxLjAxNTEgMjguMTIzM0MzMi41MjU0IDI4LjEyMzMgMzMuODExNSAyNi44ODI1IDMzLjgxMTUgMjUuMzYxM0MzMy44NjU3IDIzLjg0MDEgMzIuNjM0IDIyLjU0NSAzMS4wNjk0IDIyLjU0NVoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0xMi42NTk3IDIyLjU0NUMxMS4xNDk0IDIyLjU0NSA5Ljg2MzM5IDIzLjc4NTkgOS44NjMzOSAyNS4zMDdDOS44NjMzOSAyNi44MjgyIDExLjA5NTIgMjguMTIzMyAxMi42MDU1IDI4LjEyMzNDMTQuMTE1OCAyOC4xMjMzIDE1LjQwMTggMjYuODgyNSAxNS40MDE4IDI1LjM2MTNDMTUuNDAxOCAyMy44NDAxIDE0LjE3IDIyLjU0NSAxMi42NTk3IDIyLjU0NVoiIGZpbGw9IndoaXRlIi8+CjxkZWZzPgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50MF9saW5lYXJfMjg0XzU3NzQiIHgxPSIyNSIgeTE9IjAiIHgyPSIyNSIgeTI9IjUwIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiNFRDE5NEEiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjQzMwODMzIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg==" "icon_light": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIHZpZXdCb3g9IjAgMCA1MCA1MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjUwIiBoZWlnaHQ9IjUwIiByeD0iNy44NDc4MiIgZmlsbD0idXJsKCNwYWludDBfbGluZWFyXzI4NF81Nzc0KSIvPgo8cGF0aCBkPSJNMzkuMzUxNyAxNy42Mzg0QzM4LjkwNDkgMTcuNjM4NCAzOC41NjY3IDE3Ljk3NDkgMzguNTY2NyAxOC40Mjg5VjMxLjU2MDVDMzguNTY2NyAzMi4wMTA4IDM4LjkwMTMgMzIuMzUwOSAzOS4zNTE3IDMyLjM1MDlDMzkuODAyMSAzMi4zNTA5IDQwLjEzNjcgMzIuMDE0NSA0MC4xMzY3IDMxLjU2MDVWMTguNDI4OUM0MC4xMzY3IDE3Ljk3ODUgMzkuODAyMSAxNy42Mzg0IDM5LjM1MTcgMTcuNjM4NFoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0yMS44OTE4IDIyLjU0NUMyMC4zODE0IDIyLjU0NSAxOS4wOTU0IDIzLjc4NTkgMTkuMDk1NCAyNS4zMDdDMTkuMDk1NCAyNi44MjgyIDIwLjMyNzIgMjguMTIzMyAyMS44Mzc1IDI4LjEyMzNDMjMuMzQ3OCAyOC4xMjMzIDI0LjYzMzggMjYuODgyNSAyNC42MzM4IDI1LjM2MTNDMjQuNjMzOCAyMy44NDAxIDIzLjQwMjEgMjIuNTQ1IDIxLjg5MTggMjIuNTQ1WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTMxLjA2OTQgMjIuNTQ1QzI5LjU1OTEgMjIuNTQ1IDI4LjI3MyAyMy43ODU5IDI4LjI3MyAyNS4zMDdDMjguMjczIDI2LjgyODIgMjkuNTA0OCAyOC4xMjMzIDMxLjAxNTEgMjguMTIzM0MzMi41MjU0IDI4LjEyMzMgMzMuODExNSAyNi44ODI1IDMzLjgxMTUgMjUuMzYxM0MzMy44NjU3IDIzLjg0MDEgMzIuNjM0IDIyLjU0NSAzMS4wNjk0IDIyLjU0NVoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0xMi42NTk3IDIyLjU0NUMxMS4xNDk0IDIyLjU0NSA5Ljg2MzM5IDIzLjc4NTkgOS44NjMzOSAyNS4zMDdDOS44NjMzOSAyNi44MjgyIDExLjA5NTIgMjguMTIzMyAxMi42MDU1IDI4LjEyMzNDMTQuMTE1OCAyOC4xMjMzIDE1LjQwMTggMjYuODgyNSAxNS40MDE4IDI1LjM2MTNDMTUuNDAxOCAyMy44NDAxIDE0LjE3IDIyLjU0NSAxMi42NTk3IDIyLjU0NVoiIGZpbGw9IndoaXRlIi8+CjxkZWZzPgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50MF9saW5lYXJfMjg0XzU3NzQiIHgxPSIyNSIgeTE9IjAiIHgyPSIyNSIgeTI9IjUwIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiNFRDE5NEEiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjQzMwODMzIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg=="
}, },
"de503f9c-21a4-4f76-b4b7-558eb55c6f89": { "de503f9c-21a4-4f76-b4b7-558eb55c6f89": {
"name": "Devolutions", "name": "Devolutions",
"icon_dark": "data:image/svg+xml;base64,CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iNzJweCIgaGVpZ2h0PSI3MnB4IiB2aWV3Qm94PSIwIDAgNzIgNzIiPgk8ZGVmcz4KICAgICAgICA8ZmlsdGVyIGlkPSJhIiB3aWR0aD0iMjAwJSIgaGVpZ2h0PSIyMDAlIj4KICAgICAgICAgICAgPGZlT2Zmc2V0IHJlc3VsdD0ib2ZmT3V0IiBpbj0iU291cmNlQWxwaGEiIGR5PSIyLjIiLz4KICAgICAgICAgICAgPGZlR2F1c3NpYW5CbHVyIHJlc3VsdD0iYmx1ck91dCIgaW49Im9mZk91dCIgc3RkRGV2aWF0aW9uPSIxLjUiLz4KICAgICAgICAgICAgPGZlQ29sb3JNYXRyaXggdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwLjQgMCIvPgogICAgICAgICAgICA8ZmVNZXJnZT4KICAgICAgICAgICAgICAgIDxmZU1lcmdlTm9kZS8+CiAgICAgICAgICAgICAgICA8ZmVNZXJnZU5vZGUgaW49IlNvdXJjZUdyYXBoaWMiLz4KICAgICAgICAgICAgPC9mZU1lcmdlPgogICAgICAgIDwvZmlsdGVyPgogICAgPC9kZWZzPgo8cGF0aCBmaWxsPSIjZmZmZmZmIiBmaWx0ZXI9InVybCgjYSkiIGQ9Ik0zMS4wNTksNS4zOTVjMTYuODktMi43MjcsMzIuODE3LDguNzcyLDM1LjU0NSwyNS42NjJjMi43MjcsMTYuODktOC43NzIsMzIuODE3LTI1LjY2MiwzNS41NDUKCUMyNC4wNTEsNjkuMzI5LDguMTI0LDU3LjgzLDUuMzk3LDQwLjk0QzIuNjcsMjQuMDQ5LDE0LjE2OSw4LjEyMiwzMS4wNTksNS4zOTV6Ii8+CjxwYXRoIGZpbGw9IiMwMDY4YzMiIGQ9Ik01NS4zNjQsMTcuMjAyYy01LjA0Ny01LjE5Ny0xMS44MDItOC4xMDktMTkuMDItOC4yYy03LjE5MS0wLjA5LTEzLjk4NSwyLjY2OS0xOS4xNDksNy43NjgKCUMxMS45MSwyMS45ODksOSwyOC45NTUsOSwzNi4zOTJsMC4wNDQsMS4yNmMwLDEuMTgxLDAuOTYxLDIuMTQyLDIuMTQyLDIuMTQyczIuMTQxLTAuOTYsMi4xNDItMi4xNGwwLjAxLTAuOTEzCgljMC05Ljk0NSw4LjQ1My0yMC41OTMsMjEuMDM1LTIwLjU5M2MxMy4xMzIsMCwyMS4yNjEsMTAuNzEyLDIxLjI2MSwyMC42MzdjMCw1LjE3My0yLjA2Myw5LjkxOS01LjgwOCwxMy4zNjMKCUM0Ni4xNyw1My41MDksNDEuMjYsNTUuMzYsMzYsNTUuMzZjLTMuMTMsMC02LjIxOS0wLjc1NS04Ljk1OC0yLjE4NmwxOC44Ny04LjY4NmMxLjI2Ny0wLjU4MywyLjExNS0xLjgxLDIuMjEzLTMuMjAxCglzLTAuNTY5LTIuNzI1LTEuNzU0LTMuNDg3bC0xNS43MDYtOC40MTVjLTAuNTU0LTAuMzU3LTEuMjEzLTAuNDc3LTEuODU4LTAuMzM3Yy0wLjY0NCwwLjEzOS0xLjE5NSwwLjUyMS0xLjU1MiwxLjA3NQoJYy0wLjczNywxLjE0NC0wLjQwNiwyLjY3MywwLjcyMSwzLjM5N2w4LjQ1NCw2LjkyM2wtMTguNDE5LDguNDc4Yy0xLjQyNywwLjY1Ny0yLjI5OCwyLjEtMi4yMTgsMy42NzUKCWMwLjA0OCwwLjk0OSwwLjQ5MywxLjg4NCwxLjI1MywyLjYzNEMyMi4xNTgsNjAuMjY5LDI4Ljg0LDYzLDM1Ljk4NSw2M2MwLjQ0NSwwLDAuODkyLTAuMDExLDEuMzQxLTAuMDMyCgljMTQuMTU2LTAuNjczLDI1LjQzMi0xMi4zMTUsMjUuNjcxLTI2LjUwNUM2My4xMTgsMjkuMjM2LDYwLjQwNywyMi4zOTYsNTUuMzY0LDE3LjIwMnoiLz4KPC9zdmc+Cg==", "icon_dark": "data:image/svg+xml;base64,CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iNzJweCIgaGVpZ2h0PSI3MnB4IiB2aWV3Qm94PSIwIDAgNzIgNzIiPgk8ZGVmcz4KICAgICAgICA8ZmlsdGVyIGlkPSJhIiB3aWR0aD0iMjAwJSIgaGVpZ2h0PSIyMDAlIj4KICAgICAgICAgICAgPGZlT2Zmc2V0IHJlc3VsdD0ib2ZmT3V0IiBpbj0iU291cmNlQWxwaGEiIGR5PSIyLjIiLz4KICAgICAgICAgICAgPGZlR2F1c3NpYW5CbHVyIHJlc3VsdD0iYmx1ck91dCIgaW49Im9mZk91dCIgc3RkRGV2aWF0aW9uPSIxLjUiLz4KICAgICAgICAgICAgPGZlQ29sb3JNYXRyaXggdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwLjQgMCIvPgogICAgICAgICAgICA8ZmVNZXJnZT4KICAgICAgICAgICAgICAgIDxmZU1lcmdlTm9kZS8+CiAgICAgICAgICAgICAgICA8ZmVNZXJnZU5vZGUgaW49IlNvdXJjZUdyYXBoaWMiLz4KICAgICAgICAgICAgPC9mZU1lcmdlPgogICAgICAgIDwvZmlsdGVyPgogICAgPC9kZWZzPgo8cGF0aCBmaWxsPSIjZmZmZmZmIiBmaWx0ZXI9InVybCgjYSkiIGQ9Ik0zMS4wNTksNS4zOTVjMTYuODktMi43MjcsMzIuODE3LDguNzcyLDM1LjU0NSwyNS42NjJjMi43MjcsMTYuODktOC43NzIsMzIuODE3LTI1LjY2MiwzNS41NDUKCUMyNC4wNTEsNjkuMzI5LDguMTI0LDU3LjgzLDUuMzk3LDQwLjk0QzIuNjcsMjQuMDQ5LDE0LjE2OSw4LjEyMiwzMS4wNTksNS4zOTV6Ii8+CjxwYXRoIGZpbGw9IiMwMDY4YzMiIGQ9Ik01NS4zNjQsMTcuMjAyYy01LjA0Ny01LjE5Ny0xMS44MDItOC4xMDktMTkuMDItOC4yYy03LjE5MS0wLjA5LTEzLjk4NSwyLjY2OS0xOS4xNDksNy43NjgKCUMxMS45MSwyMS45ODksOSwyOC45NTUsOSwzNi4zOTJsMC4wNDQsMS4yNmMwLDEuMTgxLDAuOTYxLDIuMTQyLDIuMTQyLDIuMTQyczIuMTQxLTAuOTYsMi4xNDItMi4xNGwwLjAxLTAuOTEzCgljMC05Ljk0NSw4LjQ1My0yMC41OTMsMjEuMDM1LTIwLjU5M2MxMy4xMzIsMCwyMS4yNjEsMTAuNzEyLDIxLjI2MSwyMC42MzdjMCw1LjE3My0yLjA2Myw5LjkxOS01LjgwOCwxMy4zNjMKCUM0Ni4xNyw1My41MDksNDEuMjYsNTUuMzYsMzYsNTUuMzZjLTMuMTMsMC02LjIxOS0wLjc1NS04Ljk1OC0yLjE4NmwxOC44Ny04LjY4NmMxLjI2Ny0wLjU4MywyLjExNS0xLjgxLDIuMjEzLTMuMjAxCglzLTAuNTY5LTIuNzI1LTEuNzU0LTMuNDg3bC0xNS43MDYtOC40MTVjLTAuNTU0LTAuMzU3LTEuMjEzLTAuNDc3LTEuODU4LTAuMzM3Yy0wLjY0NCwwLjEzOS0xLjE5NSwwLjUyMS0xLjU1MiwxLjA3NQoJYy0wLjczNywxLjE0NC0wLjQwNiwyLjY3MywwLjcyMSwzLjM5N2w4LjQ1NCw2LjkyM2wtMTguNDE5LDguNDc4Yy0xLjQyNywwLjY1Ny0yLjI5OCwyLjEtMi4yMTgsMy42NzUKCWMwLjA0OCwwLjk0OSwwLjQ5MywxLjg4NCwxLjI1MywyLjYzNEMyMi4xNTgsNjAuMjY5LDI4Ljg0LDYzLDM1Ljk4NSw2M2MwLjQ0NSwwLDAuODkyLTAuMDExLDEuMzQxLTAuMDMyCgljMTQuMTU2LTAuNjczLDI1LjQzMi0xMi4zMTUsMjUuNjcxLTI2LjUwNUM2My4xMTgsMjkuMjM2LDYwLjQwNywyMi4zOTYsNTUuMzY0LDE3LjIwMnoiLz4KPC9zdmc+Cg==",
"icon_light": "data:image/svg+xml;base64,CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iNzJweCIgaGVpZ2h0PSI3MnB4IiB2aWV3Qm94PSIwIDAgNzIgNzIiPgk8ZGVmcz4KICAgICAgICA8ZmlsdGVyIGlkPSJhIiB3aWR0aD0iMjAwJSIgaGVpZ2h0PSIyMDAlIj4KICAgICAgICAgICAgPGZlT2Zmc2V0IHJlc3VsdD0ib2ZmT3V0IiBpbj0iU291cmNlQWxwaGEiIGR5PSIyLjIiLz4KICAgICAgICAgICAgPGZlR2F1c3NpYW5CbHVyIHJlc3VsdD0iYmx1ck91dCIgaW49Im9mZk91dCIgc3RkRGV2aWF0aW9uPSIxLjUiLz4KICAgICAgICAgICAgPGZlQ29sb3JNYXRyaXggdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwLjQgMCIvPgogICAgICAgICAgICA8ZmVNZXJnZT4KICAgICAgICAgICAgICAgIDxmZU1lcmdlTm9kZS8+CiAgICAgICAgICAgICAgICA8ZmVNZXJnZU5vZGUgaW49IlNvdXJjZUdyYXBoaWMiLz4KICAgICAgICAgICAgPC9mZU1lcmdlPgogICAgICAgIDwvZmlsdGVyPgogICAgPC9kZWZzPgo8cGF0aCBmaWxsPSIjZmZmZmZmIiBmaWx0ZXI9InVybCgjYSkiIGQ9Ik0zMS4wNTksNS4zOTVjMTYuODktMi43MjcsMzIuODE3LDguNzcyLDM1LjU0NSwyNS42NjJjMi43MjcsMTYuODktOC43NzIsMzIuODE3LTI1LjY2MiwzNS41NDUKCUMyNC4wNTEsNjkuMzI5LDguMTI0LDU3LjgzLDUuMzk3LDQwLjk0QzIuNjcsMjQuMDQ5LDE0LjE2OSw4LjEyMiwzMS4wNTksNS4zOTV6Ii8+CjxwYXRoIGZpbGw9IiMwMDY4YzMiIGQ9Ik01NS4zNjQsMTcuMjAyYy01LjA0Ny01LjE5Ny0xMS44MDItOC4xMDktMTkuMDItOC4yYy03LjE5MS0wLjA5LTEzLjk4NSwyLjY2OS0xOS4xNDksNy43NjgKCUMxMS45MSwyMS45ODksOSwyOC45NTUsOSwzNi4zOTJsMC4wNDQsMS4yNmMwLDEuMTgxLDAuOTYxLDIuMTQyLDIuMTQyLDIuMTQyczIuMTQxLTAuOTYsMi4xNDItMi4xNGwwLjAxLTAuOTEzCgljMC05Ljk0NSw4LjQ1My0yMC41OTMsMjEuMDM1LTIwLjU5M2MxMy4xMzIsMCwyMS4yNjEsMTAuNzEyLDIxLjI2MSwyMC42MzdjMCw1LjE3My0yLjA2Myw5LjkxOS01LjgwOCwxMy4zNjMKCUM0Ni4xNyw1My41MDksNDEuMjYsNTUuMzYsMzYsNTUuMzZjLTMuMTMsMC02LjIxOS0wLjc1NS04Ljk1OC0yLjE4NmwxOC44Ny04LjY4NmMxLjI2Ny0wLjU4MywyLjExNS0xLjgxLDIuMjEzLTMuMjAxCglzLTAuNTY5LTIuNzI1LTEuNzU0LTMuNDg3bC0xNS43MDYtOC40MTVjLTAuNTU0LTAuMzU3LTEuMjEzLTAuNDc3LTEuODU4LTAuMzM3Yy0wLjY0NCwwLjEzOS0xLjE5NSwwLjUyMS0xLjU1MiwxLjA3NQoJYy0wLjczNywxLjE0NC0wLjQwNiwyLjY3MywwLjcyMSwzLjM5N2w4LjQ1NCw2LjkyM2wtMTguNDE5LDguNDc4Yy0xLjQyNywwLjY1Ny0yLjI5OCwyLjEtMi4yMTgsMy42NzUKCWMwLjA0OCwwLjk0OSwwLjQ5MywxLjg4NCwxLjI1MywyLjYzNEMyMi4xNTgsNjAuMjY5LDI4Ljg0LDYzLDM1Ljk4NSw2M2MwLjQ0NSwwLDAuODkyLTAuMDExLDEuMzQxLTAuMDMyCgljMTQuMTU2LTAuNjczLDI1LjQzMi0xMi4zMTUsMjUuNjcxLTI2LjUwNUM2My4xMTgsMjkuMjM2LDYwLjQwNywyMi4zOTYsNTUuMzY0LDE3LjIwMnoiLz4KPC9zdmc+Cg==" "icon_light": "data:image/svg+xml;base64,CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iNzJweCIgaGVpZ2h0PSI3MnB4IiB2aWV3Qm94PSIwIDAgNzIgNzIiPgk8ZGVmcz4KICAgICAgICA8ZmlsdGVyIGlkPSJhIiB3aWR0aD0iMjAwJSIgaGVpZ2h0PSIyMDAlIj4KICAgICAgICAgICAgPGZlT2Zmc2V0IHJlc3VsdD0ib2ZmT3V0IiBpbj0iU291cmNlQWxwaGEiIGR5PSIyLjIiLz4KICAgICAgICAgICAgPGZlR2F1c3NpYW5CbHVyIHJlc3VsdD0iYmx1ck91dCIgaW49Im9mZk91dCIgc3RkRGV2aWF0aW9uPSIxLjUiLz4KICAgICAgICAgICAgPGZlQ29sb3JNYXRyaXggdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwLjQgMCIvPgogICAgICAgICAgICA8ZmVNZXJnZT4KICAgICAgICAgICAgICAgIDxmZU1lcmdlTm9kZS8+CiAgICAgICAgICAgICAgICA8ZmVNZXJnZU5vZGUgaW49IlNvdXJjZUdyYXBoaWMiLz4KICAgICAgICAgICAgPC9mZU1lcmdlPgogICAgICAgIDwvZmlsdGVyPgogICAgPC9kZWZzPgo8cGF0aCBmaWxsPSIjZmZmZmZmIiBmaWx0ZXI9InVybCgjYSkiIGQ9Ik0zMS4wNTksNS4zOTVjMTYuODktMi43MjcsMzIuODE3LDguNzcyLDM1LjU0NSwyNS42NjJjMi43MjcsMTYuODktOC43NzIsMzIuODE3LTI1LjY2MiwzNS41NDUKCUMyNC4wNTEsNjkuMzI5LDguMTI0LDU3LjgzLDUuMzk3LDQwLjk0QzIuNjcsMjQuMDQ5LDE0LjE2OSw4LjEyMiwzMS4wNTksNS4zOTV6Ii8+CjxwYXRoIGZpbGw9IiMwMDY4YzMiIGQ9Ik01NS4zNjQsMTcuMjAyYy01LjA0Ny01LjE5Ny0xMS44MDItOC4xMDktMTkuMDItOC4yYy03LjE5MS0wLjA5LTEzLjk4NSwyLjY2OS0xOS4xNDksNy43NjgKCUMxMS45MSwyMS45ODksOSwyOC45NTUsOSwzNi4zOTJsMC4wNDQsMS4yNmMwLDEuMTgxLDAuOTYxLDIuMTQyLDIuMTQyLDIuMTQyczIuMTQxLTAuOTYsMi4xNDItMi4xNGwwLjAxLTAuOTEzCgljMC05Ljk0NSw4LjQ1My0yMC41OTMsMjEuMDM1LTIwLjU5M2MxMy4xMzIsMCwyMS4yNjEsMTAuNzEyLDIxLjI2MSwyMC42MzdjMCw1LjE3My0yLjA2Myw5LjkxOS01LjgwOCwxMy4zNjMKCUM0Ni4xNyw1My41MDksNDEuMjYsNTUuMzYsMzYsNTUuMzZjLTMuMTMsMC02LjIxOS0wLjc1NS04Ljk1OC0yLjE4NmwxOC44Ny04LjY4NmMxLjI2Ny0wLjU4MywyLjExNS0xLjgxLDIuMjEzLTMuMjAxCglzLTAuNTY5LTIuNzI1LTEuNzU0LTMuNDg3bC0xNS43MDYtOC40MTVjLTAuNTU0LTAuMzU3LTEuMjEzLTAuNDc3LTEuODU4LTAuMzM3Yy0wLjY0NCwwLjEzOS0xLjE5NSwwLjUyMS0xLjU1MiwxLjA3NQoJYy0wLjczNywxLjE0NC0wLjQwNiwyLjY3MywwLjcyMSwzLjM5N2w4LjQ1NCw2LjkyM2wtMTguNDE5LDguNDc4Yy0xLjQyNywwLjY1Ny0yLjI5OCwyLjEtMi4yMTgsMy42NzUKCWMwLjA0OCwwLjk0OSwwLjQ5MywxLjg4NCwxLjI1MywyLjYzNEMyMi4xNTgsNjAuMjY5LDI4Ljg0LDYzLDM1Ljk4NSw2M2MwLjQ0NSwwLDAuODkyLTAuMDExLDEuMzQxLTAuMDMyCgljMTQuMTU2LTAuNjczLDI1LjQzMi0xMi4zMTUsMjUuNjcxLTI2LjUwNUM2My4xMTgsMjkuMjM2LDYwLjQwNywyMi4zOTYsNTUuMzY0LDE3LjIwMnoiLz4KPC9zdmc+Cg=="
} }
} }

File diff suppressed because one or more lines are too long

View File

@ -5617,20 +5617,13 @@
"title": "Issuer mode", "title": "Issuer mode",
"description": "Configure how the issuer field of the ID Token should be filled." "description": "Configure how the issuer field of the ID Token should be filled."
}, },
"jwt_federation_sources": { "jwks_sources": {
"type": "array", "type": "array",
"items": { "items": {
"type": "integer", "type": "integer",
"title": "Any JWT signed by the JWK of the selected source can be used to authenticate." "title": "Any JWT signed by the JWK of the selected source can be used to authenticate."
}, },
"title": "Any JWT signed by the JWK of the selected source can be used to authenticate." "title": "Any JWT signed by the JWK of the selected source can be used to authenticate."
},
"jwt_federation_providers": {
"type": "array",
"items": {
"type": "integer"
},
"title": "Jwt federation providers"
} }
}, },
"required": [] "required": []
@ -5753,7 +5746,7 @@
"type": "string", "type": "string",
"title": "Cookie domain" "title": "Cookie domain"
}, },
"jwt_federation_sources": { "jwks_sources": {
"type": "array", "type": "array",
"items": { "items": {
"type": "integer", "type": "integer",
@ -5761,13 +5754,6 @@
}, },
"title": "Any JWT signed by the JWK of the selected source can be used to authenticate." "title": "Any JWT signed by the JWK of the selected source can be used to authenticate."
}, },
"jwt_federation_providers": {
"type": "array",
"items": {
"type": "integer"
},
"title": "Jwt federation providers"
},
"access_token_validity": { "access_token_validity": {
"type": "string", "type": "string",
"minLength": 1, "minLength": 1,

View File

@ -47,7 +47,7 @@ func checkServer() int {
h := &http.Client{ h := &http.Client{
Transport: web.NewUserAgentTransport("goauthentik.io/healthcheck", http.DefaultTransport), Transport: web.NewUserAgentTransport("goauthentik.io/healthcheck", http.DefaultTransport),
} }
url := fmt.Sprintf("http://%s%s-/health/live/", config.Get().Listen.HTTP, config.Get().Web.Path) url := fmt.Sprintf("http://%s/-/health/live/", config.Get().Listen.HTTP)
res, err := h.Head(url) res, err := h.Head(url)
if err != nil { if err != nil {
log.WithError(err).Warning("failed to send healthcheck request") log.WithError(err).Warning("failed to send healthcheck request")

View File

@ -61,7 +61,7 @@ var rootCmd = &cobra.Command{
ex := common.Init() ex := common.Init()
defer common.Defer() defer common.Defer()
u, err := url.Parse(fmt.Sprintf("http://%s%s", config.Get().Listen.HTTP, config.Get().Web.Path)) u, err := url.Parse(fmt.Sprintf("http://%s", config.Get().Listen.HTTP))
if err != nil { if err != nil {
panic(err) panic(err)
} }

2
go.mod
View File

@ -27,7 +27,7 @@ require (
github.com/sethvargo/go-envconfig v1.1.0 github.com/sethvargo/go-envconfig v1.1.0
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.1 github.com/spf13/cobra v1.8.1
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.9.0
github.com/wwt/guac v1.3.2 github.com/wwt/guac v1.3.2
goauthentik.io/api/v3 v3.2024104.1 goauthentik.io/api/v3 v3.2024104.1
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab

4
go.sum
View File

@ -274,8 +274,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/wwt/guac v1.3.2 h1:sH6OFGa/1tBs7ieWBVlZe7t6F5JAOWBry/tqQL/Vup4= github.com/wwt/guac v1.3.2 h1:sH6OFGa/1tBs7ieWBVlZe7t6F5JAOWBry/tqQL/Vup4=
github.com/wwt/guac v1.3.2/go.mod h1:eKm+NrnK7A88l4UBEcYNpZQGMpZRryYKoz4D/0/n1C0= github.com/wwt/guac v1.3.2/go.mod h1:eKm+NrnK7A88l4UBEcYNpZQGMpZRryYKoz4D/0/n1C0=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=

View File

@ -14,7 +14,6 @@ type Config struct {
// Config for both core and outposts // Config for both core and outposts
Debug bool `yaml:"debug" env:"AUTHENTIK_DEBUG, overwrite"` Debug bool `yaml:"debug" env:"AUTHENTIK_DEBUG, overwrite"`
Listen ListenConfig `yaml:"listen" env:", prefix=AUTHENTIK_LISTEN__"` Listen ListenConfig `yaml:"listen" env:", prefix=AUTHENTIK_LISTEN__"`
Web WebConfig `yaml:"web" env:", prefix=AUTHENTIK_WEB__"`
// Outpost specific config // Outpost specific config
// These are only relevant for proxy/ldap outposts, and cannot be set via YAML // These are only relevant for proxy/ldap outposts, and cannot be set via YAML
@ -73,7 +72,3 @@ type OutpostConfig struct {
Discover bool `yaml:"discover" env:"DISCOVER, overwrite"` Discover bool `yaml:"discover" env:"DISCOVER, overwrite"`
DisableEmbeddedOutpost bool `yaml:"disable_embedded_outpost" env:"DISABLE_EMBEDDED_OUTPOST, overwrite"` DisableEmbeddedOutpost bool `yaml:"disable_embedded_outpost" env:"DISABLE_EMBEDDED_OUTPOST, overwrite"`
} }
type WebConfig struct {
Path string `yaml:"path" env:"PATH, overwrite"`
}

View File

@ -56,10 +56,10 @@ type APIController struct {
func NewAPIController(akURL url.URL, token string) *APIController { func NewAPIController(akURL url.URL, token string) *APIController {
rsp := sentry.StartSpan(context.Background(), "authentik.outposts.init") rsp := sentry.StartSpan(context.Background(), "authentik.outposts.init")
apiConfig := api.NewConfiguration() config := api.NewConfiguration()
apiConfig.Host = akURL.Host config.Host = akURL.Host
apiConfig.Scheme = akURL.Scheme config.Scheme = akURL.Scheme
apiConfig.HTTPClient = &http.Client{ config.HTTPClient = &http.Client{
Transport: web.NewUserAgentTransport( Transport: web.NewUserAgentTransport(
constants.OutpostUserAgent(), constants.OutpostUserAgent(),
web.NewTracingTransport( web.NewTracingTransport(
@ -68,15 +68,10 @@ func NewAPIController(akURL url.URL, token string) *APIController {
), ),
), ),
} }
apiConfig.Servers = api.ServerConfigurations{ config.AddDefaultHeader("Authorization", fmt.Sprintf("Bearer %s", token))
{
URL: fmt.Sprintf("%sapi/v3", akURL.Path),
},
}
apiConfig.AddDefaultHeader("Authorization", fmt.Sprintf("Bearer %s", token))
// create the API client, with the transport // create the API client, with the transport
apiClient := api.NewAPIClient(apiConfig) apiClient := api.NewAPIClient(config)
log := log.WithField("logger", "authentik.outpost.ak-api-controller") log := log.WithField("logger", "authentik.outpost.ak-api-controller")

View File

@ -17,7 +17,7 @@ import (
) )
func (ac *APIController) initWS(akURL url.URL, outpostUUID string) error { func (ac *APIController) initWS(akURL url.URL, outpostUUID string) error {
pathTemplate := "%s://%s%sws/outpost/%s/?%s" pathTemplate := "%s://%s/ws/outpost/%s/?%s"
query := akURL.Query() query := akURL.Query()
query.Set("instance_uuid", ac.instanceUUID.String()) query.Set("instance_uuid", ac.instanceUUID.String())
scheme := strings.ReplaceAll(akURL.Scheme, "http", "ws") scheme := strings.ReplaceAll(akURL.Scheme, "http", "ws")
@ -37,7 +37,7 @@ func (ac *APIController) initWS(akURL url.URL, outpostUUID string) error {
}, },
} }
ws, _, err := dialer.Dial(fmt.Sprintf(pathTemplate, scheme, akURL.Host, akURL.Path, outpostUUID, akURL.Query().Encode()), header) ws, _, err := dialer.Dial(fmt.Sprintf(pathTemplate, scheme, akURL.Host, outpostUUID, akURL.Query().Encode()), header)
if err != nil { if err != nil {
ac.logger.WithError(err).Warning("failed to connect websocket") ac.logger.WithError(err).Warning("failed to connect websocket")
return err return err
@ -83,7 +83,6 @@ func (ac *APIController) reconnectWS() {
u := url.URL{ u := url.URL{
Host: ac.Client.GetConfig().Host, Host: ac.Client.GetConfig().Host,
Scheme: ac.Client.GetConfig().Scheme, Scheme: ac.Client.GetConfig().Scheme,
Path: strings.ReplaceAll(ac.Client.GetConfig().Servers[0].URL, "api/v3", ""),
} }
attempt := 1 attempt := 1
for { for {

View File

@ -46,7 +46,7 @@ func (ws *WebServer) runMetricsServer() {
).ServeHTTP(rw, r) ).ServeHTTP(rw, r)
// Get upstream metrics // Get upstream metrics
re, err := http.NewRequest("GET", fmt.Sprintf("%s%s-/metrics/", ws.upstreamURL.String(), config.Get().Web.Path), nil) re, err := http.NewRequest("GET", fmt.Sprintf("%s/-/metrics/", ws.ul.String()), nil)
if err != nil { if err != nil {
l.WithError(err).Warning("failed to get upstream metrics") l.WithError(err).Warning("failed to get upstream metrics")
return return

View File

@ -9,15 +9,14 @@ import (
"time" "time"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"goauthentik.io/internal/config"
"goauthentik.io/internal/utils/sentry" "goauthentik.io/internal/utils/sentry"
) )
func (ws *WebServer) configureProxy() { func (ws *WebServer) configureProxy() {
// Reverse proxy to the application server // Reverse proxy to the application server
director := func(req *http.Request) { director := func(req *http.Request) {
req.URL.Scheme = ws.upstreamURL.Scheme req.URL.Scheme = ws.ul.Scheme
req.URL.Host = ws.upstreamURL.Host req.URL.Host = ws.ul.Host
if _, ok := req.Header["User-Agent"]; !ok { if _, ok := req.Header["User-Agent"]; !ok {
// explicitly disable User-Agent so it's not set to default value // explicitly disable User-Agent so it's not set to default value
req.Header.Set("User-Agent", "") req.Header.Set("User-Agent", "")
@ -33,10 +32,7 @@ func (ws *WebServer) configureProxy() {
} }
rp.ErrorHandler = ws.proxyErrorHandler rp.ErrorHandler = ws.proxyErrorHandler
rp.ModifyResponse = ws.proxyModifyResponse rp.ModifyResponse = ws.proxyModifyResponse
ws.mainRouter.PathPrefix(config.Get().Web.Path).Path("/-/health/live/").HandlerFunc(sentry.SentryNoSample(func(rw http.ResponseWriter, r *http.Request) { ws.m.PathPrefix("/").HandlerFunc(sentry.SentryNoSample(func(rw http.ResponseWriter, r *http.Request) {
rw.WriteHeader(200)
}))
ws.mainRouter.PathPrefix(config.Get().Web.Path).HandlerFunc(sentry.SentryNoSample(func(rw http.ResponseWriter, r *http.Request) {
if !ws.g.IsRunning() { if !ws.g.IsRunning() {
ws.proxyErrorHandler(rw, r, errors.New("authentik starting")) ws.proxyErrorHandler(rw, r, errors.New("authentik starting"))
return return

View File

@ -14,74 +14,46 @@ import (
) )
func (ws *WebServer) configureStatic() { func (ws *WebServer) configureStatic() {
// Setup routers staticRouter := ws.lh.NewRoute().Subrouter()
staticRouter := ws.loggingRouter.NewRoute().Subrouter()
staticRouter.Use(ws.staticHeaderMiddleware) staticRouter.Use(ws.staticHeaderMiddleware)
indexLessRouter := staticRouter.NewRoute().Subrouter() staticRouter.Use(web.DisableIndex)
// Specifically disable index
indexLessRouter.Use(web.DisableIndex)
distFs := http.FileServer(http.Dir("./web/dist")) distFs := http.FileServer(http.Dir("./web/dist"))
authentikHandler := http.StripPrefix("/static/authentik/", http.FileServer(http.Dir("./web/authentik")))
pathStripper := func(handler http.Handler, paths ...string) http.Handler { // Root file paths, from which they should be accessed
h := handler staticRouter.PathPrefix("/static/dist/").Handler(http.StripPrefix("/static/dist/", distFs))
for _, path := range paths { staticRouter.PathPrefix("/static/authentik/").Handler(authentikHandler)
h = http.StripPrefix(path, h)
}
return h
}
helpHandler := http.FileServer(http.Dir("./website/help/")) // Also serve assets folder in specific interfaces since fonts in patternfly are imported
// with a relative path
indexLessRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/static/dist/").Handler(pathStripper( staticRouter.PathPrefix("/if/flow/{flow_slug}/assets").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
distFs,
"static/dist/",
config.Get().Web.Path,
))
indexLessRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/static/authentik/").Handler(pathStripper(
http.FileServer(http.Dir("./web/authentik")),
"static/authentik/",
config.Get().Web.Path,
))
indexLessRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/if/flow/{flow_slug}/assets").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
pathStripper( web.DisableIndex(http.StripPrefix(fmt.Sprintf("/if/flow/%s", vars["flow_slug"]), distFs)).ServeHTTP(rw, r)
distFs,
"if/flow/"+vars["flow_slug"],
config.Get().Web.Path,
).ServeHTTP(rw, r)
}) })
indexLessRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/if/admin/assets").Handler(http.StripPrefix(fmt.Sprintf("%sif/admin", config.Get().Web.Path), distFs)) staticRouter.PathPrefix("/if/admin/assets").Handler(http.StripPrefix("/if/admin", distFs))
indexLessRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/if/user/assets").Handler(http.StripPrefix(fmt.Sprintf("%sif/user", config.Get().Web.Path), distFs)) staticRouter.PathPrefix("/if/user/assets").Handler(http.StripPrefix("/if/user", distFs))
indexLessRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/if/rac/{app_slug}/assets").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { staticRouter.PathPrefix("/if/rac/{app_slug}/assets").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
pathStripper( web.DisableIndex(http.StripPrefix(fmt.Sprintf("/if/rac/%s", vars["app_slug"]), distFs)).ServeHTTP(rw, r)
distFs,
"if/rac/"+vars["app_slug"],
config.Get().Web.Path,
).ServeHTTP(rw, r)
}) })
// Media files, if backend is file // Media files, if backend is file
if config.Get().Storage.Media.Backend == "file" { if config.Get().Storage.Media.Backend == "file" {
fsMedia := http.StripPrefix("/media", http.FileServer(http.Dir(config.Get().Storage.Media.File.Path))) fsMedia := http.StripPrefix("/media", http.FileServer(http.Dir(config.Get().Storage.Media.File.Path)))
indexLessRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/media/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { staticRouter.PathPrefix("/media/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox") w.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox")
fsMedia.ServeHTTP(w, r) fsMedia.ServeHTTP(w, r)
}) })
} }
staticRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/if/help/").Handler(pathStripper( staticRouter.PathPrefix("/if/help/").Handler(http.StripPrefix("/if/help/", http.FileServer(http.Dir("./website/help/"))))
helpHandler, staticRouter.PathPrefix("/help").Handler(http.RedirectHandler("/if/help/", http.StatusMovedPermanently))
config.Get().Web.Path,
"/if/help/",
))
staticRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/help").Handler(http.RedirectHandler(fmt.Sprintf("%sif/help/", config.Get().Web.Path), http.StatusMovedPermanently))
staticRouter.PathPrefix(config.Get().Web.Path).Path("/robots.txt").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { // Static misc files
ws.lh.Path("/robots.txt").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
rw.Header()["Content-Type"] = []string{"text/plain"} rw.Header()["Content-Type"] = []string{"text/plain"}
rw.WriteHeader(200) rw.WriteHeader(200)
_, err := rw.Write(staticWeb.RobotsTxt) _, err := rw.Write(staticWeb.RobotsTxt)
@ -89,7 +61,7 @@ func (ws *WebServer) configureStatic() {
ws.log.WithError(err).Warning("failed to write response") ws.log.WithError(err).Warning("failed to write response")
} }
}) })
staticRouter.PathPrefix(config.Get().Web.Path).Path("/.well-known/security.txt").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { ws.lh.Path("/.well-known/security.txt").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
rw.Header()["Content-Type"] = []string{"text/plain"} rw.Header()["Content-Type"] = []string{"text/plain"}
rw.WriteHeader(200) rw.WriteHeader(200)
_, err := rw.Write(staticWeb.SecurityTxt) _, err := rw.Write(staticWeb.SecurityTxt)

View File

@ -33,13 +33,13 @@ type WebServer struct {
ProxyServer *proxyv2.ProxyServer ProxyServer *proxyv2.ProxyServer
BrandTLS *brand_tls.Watcher BrandTLS *brand_tls.Watcher
g *gounicorn.GoUnicorn g *gounicorn.GoUnicorn
gunicornReady bool gr bool
mainRouter *mux.Router m *mux.Router
loggingRouter *mux.Router lh *mux.Router
log *log.Entry log *log.Entry
upstreamClient *http.Client uc *http.Client
upstreamURL *url.URL ul *url.URL
} }
const UnixSocketName = "authentik-core.sock" const UnixSocketName = "authentik-core.sock"
@ -73,22 +73,17 @@ func NewWebServer() *WebServer {
u, _ := url.Parse("http://localhost:8000") u, _ := url.Parse("http://localhost:8000")
ws := &WebServer{ ws := &WebServer{
mainRouter: mainHandler, m: mainHandler,
loggingRouter: loggingHandler, lh: loggingHandler,
log: l, log: l,
gunicornReady: true, gr: true,
upstreamClient: upstreamClient, uc: upstreamClient,
upstreamURL: u, ul: u,
} }
ws.configureStatic() ws.configureStatic()
ws.configureProxy() ws.configureProxy()
// Redirect for sub-folder
if sp := config.Get().Web.Path; sp != "/" {
ws.mainRouter.Path("/").Handler(http.RedirectHandler(sp, http.StatusFound))
}
hcUrl := fmt.Sprintf("%s%s-/health/live/", ws.upstreamURL.String(), config.Get().Web.Path)
ws.g = gounicorn.New(func() bool { ws.g = gounicorn.New(func() bool {
req, err := http.NewRequest(http.MethodGet, hcUrl, nil) req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/-/health/live/", ws.ul.String()), nil)
if err != nil { if err != nil {
ws.log.WithError(err).Warning("failed to create request for healthcheck") ws.log.WithError(err).Warning("failed to create request for healthcheck")
return false return false
@ -112,7 +107,7 @@ func (ws *WebServer) Start() {
func (ws *WebServer) attemptStartBackend() { func (ws *WebServer) attemptStartBackend() {
for { for {
if !ws.gunicornReady { if !ws.gr {
return return
} }
err := ws.g.Start() err := ws.g.Start()
@ -140,7 +135,7 @@ func (ws *WebServer) Core() *gounicorn.GoUnicorn {
} }
func (ws *WebServer) upstreamHttpClient() *http.Client { func (ws *WebServer) upstreamHttpClient() *http.Client {
return ws.upstreamClient return ws.uc
} }
func (ws *WebServer) Shutdown() { func (ws *WebServer) Shutdown() {
@ -165,7 +160,7 @@ func (ws *WebServer) listenPlain() {
func (ws *WebServer) serve(listener net.Listener) { func (ws *WebServer) serve(listener net.Listener) {
srv := &http.Server{ srv := &http.Server{
Handler: ws.mainRouter, Handler: ws.m,
} }
// See https://golang.org/pkg/net/http/#Server.Shutdown // See https://golang.org/pkg/net/http/#Server.Shutdown

Binary file not shown.

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-11-26 00:09+0000\n" "POT-Creation-Date: 2024-11-18 00:09+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -73,8 +73,8 @@ msgid "authentik Export - {date}"
msgstr "" msgstr ""
#: authentik/blueprints/v1/tasks.py authentik/crypto/tasks.py #: authentik/blueprints/v1/tasks.py authentik/crypto/tasks.py
#, python-brace-format #, python-format
msgid "Successfully imported {count} files." msgid "Successfully imported %(count)d files."
msgstr "" msgstr ""
#: authentik/brands/models.py #: authentik/brands/models.py
@ -856,13 +856,13 @@ msgid "Starting full provider sync"
msgstr "" msgstr ""
#: authentik/lib/sync/outgoing/tasks.py #: authentik/lib/sync/outgoing/tasks.py
#, python-brace-format #, python-format
msgid "Syncing page {page} of users" msgid "Syncing page %(page)d of users"
msgstr "" msgstr ""
#: authentik/lib/sync/outgoing/tasks.py #: authentik/lib/sync/outgoing/tasks.py
#, python-brace-format #, python-format
msgid "Syncing page {page} of groups" msgid "Syncing page %(page)d of groups"
msgstr "" msgstr ""
#: authentik/lib/sync/outgoing/tasks.py #: authentik/lib/sync/outgoing/tasks.py
@ -1012,8 +1012,8 @@ msgid "Event Matcher Policies"
msgstr "" msgstr ""
#: authentik/policies/expiry/models.py #: authentik/policies/expiry/models.py
#, python-brace-format #, python-format
msgid "Password expired {days} days ago. Please update your password." msgid "Password expired %(days)d days ago. Please update your password."
msgstr "" msgstr ""
#: authentik/policies/expiry/models.py #: authentik/policies/expiry/models.py
@ -1140,8 +1140,8 @@ msgid "Invalid password."
msgstr "" msgstr ""
#: authentik/policies/password/models.py #: authentik/policies/password/models.py
#, python-brace-format #, python-format
msgid "Password exists on {count} online lists." msgid "Password exists on %(count)d online lists."
msgstr "" msgstr ""
#: authentik/policies/password/models.py #: authentik/policies/password/models.py
@ -1252,11 +1252,6 @@ msgstr ""
msgid "Search full LDAP directory" msgid "Search full LDAP directory"
msgstr "" msgstr ""
#: authentik/providers/oauth2/api/providers.py
#, python-brace-format
msgid "Invalid Regex Pattern: {url}"
msgstr ""
#: authentik/providers/oauth2/id_token.py #: authentik/providers/oauth2/id_token.py
msgid "Based on the Hashed User ID" msgid "Based on the Hashed User ID"
msgstr "" msgstr ""
@ -1299,14 +1294,6 @@ msgstr ""
msgid "Each provider has a different issuer, based on the application slug." msgid "Each provider has a different issuer, based on the application slug."
msgstr "" msgstr ""
#: authentik/providers/oauth2/models.py
msgid "Strict URL comparison"
msgstr ""
#: authentik/providers/oauth2/models.py
msgid "Regular Expression URL matching"
msgstr ""
#: authentik/providers/oauth2/models.py #: authentik/providers/oauth2/models.py
msgid "code (Authorization Code Flow)" msgid "code (Authorization Code Flow)"
msgstr "" msgstr ""
@ -1383,6 +1370,10 @@ msgstr ""
msgid "Redirect URIs" msgid "Redirect URIs"
msgstr "" msgstr ""
#: authentik/providers/oauth2/models.py
msgid "Enter each URI on a new line."
msgstr ""
#: authentik/providers/oauth2/models.py #: authentik/providers/oauth2/models.py
msgid "Include claims in id_token" msgid "Include claims in id_token"
msgstr "" msgstr ""

View File

@ -11,7 +11,7 @@
# Mordecai, 2023 # Mordecai, 2023
# Charles Leclerc, 2024 # Charles Leclerc, 2024
# nerdinator <florian.dupret@gmail.com>, 2024 # nerdinator <florian.dupret@gmail.com>, 2024
# Tina, 2024 # Titouan Petit, 2024
# Marc Schmitt, 2024 # Marc Schmitt, 2024
# #
#, fuzzy #, fuzzy
@ -19,7 +19,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-11-26 00:09+0000\n" "POT-Creation-Date: 2024-10-23 16:39+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n" "PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Marc Schmitt, 2024\n" "Last-Translator: Marc Schmitt, 2024\n"
"Language-Team: French (https://app.transifex.com/authentik/teams/119923/fr/)\n" "Language-Team: French (https://app.transifex.com/authentik/teams/119923/fr/)\n"
@ -89,9 +89,9 @@ msgid "authentik Export - {date}"
msgstr "Export authentik - {date}" msgstr "Export authentik - {date}"
#: authentik/blueprints/v1/tasks.py authentik/crypto/tasks.py #: authentik/blueprints/v1/tasks.py authentik/crypto/tasks.py
#, python-brace-format #, python-format
msgid "Successfully imported {count} files." msgid "Successfully imported %(count)d files."
msgstr "{count} fichiers importés avec succès." msgstr " %(count)d fichiers importés avec succès."
#: authentik/brands/models.py #: authentik/brands/models.py
msgid "" msgid ""
@ -121,10 +121,6 @@ msgstr "Marque"
msgid "Brands" msgid "Brands"
msgstr "Marques" msgstr "Marques"
#: authentik/core/api/devices.py
msgid "Extra description not available"
msgstr "Description supplémentaire indisponible"
#: authentik/core/api/providers.py #: authentik/core/api/providers.py
msgid "" msgid ""
"When not set all providers are returned. When set to true, only backchannel " "When not set all providers are returned. When set to true, only backchannel "
@ -135,11 +131,6 @@ msgstr ""
"fournisseurs backchannels sont retournés. Si faux, les fournisseurs " "fournisseurs backchannels sont retournés. Si faux, les fournisseurs "
"backchannels sont exclus" "backchannels sont exclus"
#: authentik/core/api/transactional_applications.py
#, python-brace-format
msgid "User lacks permission to create {model}"
msgstr "L'utilisateur manque de permission pour créer {model}"
#: authentik/core/api/users.py #: authentik/core/api/users.py
msgid "No leading or trailing slashes allowed." msgid "No leading or trailing slashes allowed."
msgstr "" msgstr ""
@ -942,14 +933,14 @@ msgid "Starting full provider sync"
msgstr "Démarrage d'une synchronisation complète du fournisseur" msgstr "Démarrage d'une synchronisation complète du fournisseur"
#: authentik/lib/sync/outgoing/tasks.py #: authentik/lib/sync/outgoing/tasks.py
#, python-brace-format #, python-format
msgid "Syncing page {page} of users" msgid "Syncing page %(page)d of users"
msgstr "Synchronisation de la page {page} d'utilisateurs" msgstr "Synchronisation de la page %(page)d d'utilisateurs"
#: authentik/lib/sync/outgoing/tasks.py #: authentik/lib/sync/outgoing/tasks.py
#, python-brace-format #, python-format
msgid "Syncing page {page} of groups" msgid "Syncing page %(page)d of groups"
msgstr "Synchronisation de la page {page} de groupes" msgstr "Synchronisation de la page %(page)d de groupes"
#: authentik/lib/sync/outgoing/tasks.py #: authentik/lib/sync/outgoing/tasks.py
#, python-brace-format #, python-brace-format
@ -1122,11 +1113,11 @@ msgid "Event Matcher Policies"
msgstr "Politiques d'association d'évènements" msgstr "Politiques d'association d'évènements"
#: authentik/policies/expiry/models.py #: authentik/policies/expiry/models.py
#, python-brace-format #, python-format
msgid "Password expired {days} days ago. Please update your password." msgid "Password expired %(days)d days ago. Please update your password."
msgstr "" msgstr ""
"Mot de passe expiré il y a {days} jours. Merci de mettre à jour votre mot de" "Mot de passe expiré il y a %(days)d jours. Merci de mettre à jour votre mot "
" passe." "de passe."
#: authentik/policies/expiry/models.py #: authentik/policies/expiry/models.py
msgid "Password has expired." msgid "Password has expired."
@ -1258,13 +1249,9 @@ msgid "Password not set in context"
msgstr "Mot de passe non défini dans le contexte" msgstr "Mot de passe non défini dans le contexte"
#: authentik/policies/password/models.py #: authentik/policies/password/models.py
msgid "Invalid password." #, python-format
msgstr "Mot de passe invalide." msgid "Password exists on %(count)d online lists."
msgstr "Le mot de passe existe sur %(count)d liste en ligne."
#: authentik/policies/password/models.py
#, python-brace-format
msgid "Password exists on {count} online lists."
msgstr "Le mot de passe existe sur {count} listes en ligne."
#: authentik/policies/password/models.py #: authentik/policies/password/models.py
msgid "Password is too weak." msgid "Password is too weak."
@ -1391,11 +1378,6 @@ msgstr "Fournisseurs LDAP"
msgid "Search full LDAP directory" msgid "Search full LDAP directory"
msgstr "Rechercher dans l'annuaire LDAP complet" msgstr "Rechercher dans l'annuaire LDAP complet"
#: authentik/providers/oauth2/api/providers.py
#, python-brace-format
msgid "Invalid Regex Pattern: {url}"
msgstr "Pattern de regex invalide : {url}"
#: authentik/providers/oauth2/id_token.py #: authentik/providers/oauth2/id_token.py
msgid "Based on the Hashed User ID" msgid "Based on the Hashed User ID"
msgstr "Basé sur le hash de l'ID utilisateur" msgstr "Basé sur le hash de l'ID utilisateur"
@ -1445,14 +1427,6 @@ msgstr ""
"Chaque fournisseur a un émetteur différent, basé sur le slug de " "Chaque fournisseur a un émetteur différent, basé sur le slug de "
"l'application." "l'application."
#: authentik/providers/oauth2/models.py
msgid "Strict URL comparison"
msgstr "Comparaison stricte d'URL"
#: authentik/providers/oauth2/models.py
msgid "Regular Expression URL matching"
msgstr "Correspondance d'URL par expression régulière"
#: authentik/providers/oauth2/models.py #: authentik/providers/oauth2/models.py
msgid "code (Authorization Code Flow)" msgid "code (Authorization Code Flow)"
msgstr "code (Authorization Code Flow)" msgstr "code (Authorization Code Flow)"
@ -1533,6 +1507,10 @@ msgstr "Secret du client"
msgid "Redirect URIs" msgid "Redirect URIs"
msgstr "URIs de redirection" msgstr "URIs de redirection"
#: authentik/providers/oauth2/models.py
msgid "Enter each URI on a new line."
msgstr "Entrez chaque URI sur une nouvelle ligne."
#: authentik/providers/oauth2/models.py #: authentik/providers/oauth2/models.py
msgid "Include claims in id_token" msgid "Include claims in id_token"
msgstr "Include les demandes utilisateurs dans id_token" msgstr "Include les demandes utilisateurs dans id_token"
@ -2911,8 +2889,13 @@ msgid "Captcha Stages"
msgstr "Étapes de Captcha" msgstr "Étapes de Captcha"
#: authentik/stages/captcha/stage.py #: authentik/stages/captcha/stage.py
msgid "Invalid captcha response. Retrying may solve this issue." msgid "Unknown error"
msgstr "Réponse captcha invalide. Réessayer peut résoudre ce problème." msgstr "Erreur inconnue"
#: authentik/stages/captcha/stage.py
#, python-brace-format
msgid "Failed to validate token: {error}"
msgstr "Échec de validation du jeton : {error}"
#: authentik/stages/captcha/stage.py #: authentik/stages/captcha/stage.py
msgid "Invalid captcha response" msgid "Invalid captcha response"
@ -3579,11 +3562,6 @@ msgstr ""
msgid "Globally enable/disable impersonation." msgid "Globally enable/disable impersonation."
msgstr "Activer/désactiver l'appropriation utilisateur de manière globale." msgstr "Activer/désactiver l'appropriation utilisateur de manière globale."
#: authentik/tenants/models.py
msgid "Require administrators to provide a reason for impersonating a user."
msgstr ""
"Forcer les administrateurs à fournir une raison d'appropriation utilisateur."
#: authentik/tenants/models.py #: authentik/tenants/models.py
msgid "Default token duration" msgid "Default token duration"
msgstr "Durée par défaut des jetons" msgstr "Durée par défaut des jetons"

Binary file not shown.

View File

@ -15,7 +15,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-11-26 00:09+0000\n" "POT-Creation-Date: 2024-11-18 00:09+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n" "PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: deluxghost, 2024\n" "Last-Translator: deluxghost, 2024\n"
"Language-Team: Chinese Simplified (https://app.transifex.com/authentik/teams/119923/zh-Hans/)\n" "Language-Team: Chinese Simplified (https://app.transifex.com/authentik/teams/119923/zh-Hans/)\n"
@ -82,9 +82,9 @@ msgid "authentik Export - {date}"
msgstr "authentik 导出 - {date}" msgstr "authentik 导出 - {date}"
#: authentik/blueprints/v1/tasks.py authentik/crypto/tasks.py #: authentik/blueprints/v1/tasks.py authentik/crypto/tasks.py
#, python-brace-format #, python-format
msgid "Successfully imported {count} files." msgid "Successfully imported %(count)d files."
msgstr "已成功导入 {count} 个文件。" msgstr "已成功导入 %(count)d 个文件。"
#: authentik/brands/models.py #: authentik/brands/models.py
msgid "" msgid ""
@ -868,14 +868,14 @@ msgid "Starting full provider sync"
msgstr "开始全量提供程序同步" msgstr "开始全量提供程序同步"
#: authentik/lib/sync/outgoing/tasks.py #: authentik/lib/sync/outgoing/tasks.py
#, python-brace-format #, python-format
msgid "Syncing page {page} of users" msgid "Syncing page %(page)d of users"
msgstr "正在同步用户页面 {page}" msgstr "正在同步用户页面 %(page)d"
#: authentik/lib/sync/outgoing/tasks.py #: authentik/lib/sync/outgoing/tasks.py
#, python-brace-format #, python-format
msgid "Syncing page {page} of groups" msgid "Syncing page %(page)d of groups"
msgstr "正在同步群组页面 {page}" msgstr "正在同步群组页面 %(page)d"
#: authentik/lib/sync/outgoing/tasks.py #: authentik/lib/sync/outgoing/tasks.py
#, python-brace-format #, python-brace-format
@ -1026,9 +1026,9 @@ msgid "Event Matcher Policies"
msgstr "事件匹配策略" msgstr "事件匹配策略"
#: authentik/policies/expiry/models.py #: authentik/policies/expiry/models.py
#, python-brace-format #, python-format
msgid "Password expired {days} days ago. Please update your password." msgid "Password expired %(days)d days ago. Please update your password."
msgstr "密码在 {days} 天前过期。请更新您的密码。" msgstr "密码在 %(days)d 天前过期。请更新您的密码。"
#: authentik/policies/expiry/models.py #: authentik/policies/expiry/models.py
msgid "Password has expired." msgid "Password has expired."
@ -1154,9 +1154,9 @@ msgid "Invalid password."
msgstr "无效密码。" msgstr "无效密码。"
#: authentik/policies/password/models.py #: authentik/policies/password/models.py
#, python-brace-format #, python-format
msgid "Password exists on {count} online lists." msgid "Password exists on %(count)d online lists."
msgstr "{count} 个在线列表中存在密码。" msgstr "%(count)d 个在线列表中存在密码。"
#: authentik/policies/password/models.py #: authentik/policies/password/models.py
msgid "Password is too weak." msgid "Password is too weak."
@ -1275,11 +1275,6 @@ msgstr "LDAP 提供程序"
msgid "Search full LDAP directory" msgid "Search full LDAP directory"
msgstr "搜索完整 LDAP 目录" msgstr "搜索完整 LDAP 目录"
#: authentik/providers/oauth2/api/providers.py
#, python-brace-format
msgid "Invalid Regex Pattern: {url}"
msgstr "无效的正则表达式模式:{url}"
#: authentik/providers/oauth2/id_token.py #: authentik/providers/oauth2/id_token.py
msgid "Based on the Hashed User ID" msgid "Based on the Hashed User ID"
msgstr "基于经过哈希处理的用户 ID" msgstr "基于经过哈希处理的用户 ID"
@ -1322,14 +1317,6 @@ msgstr "所有提供程序都使用相同的标识符"
msgid "Each provider has a different issuer, based on the application slug." msgid "Each provider has a different issuer, based on the application slug."
msgstr "根据应用程序 Slug每个提供程序都有不同的颁发者。" msgstr "根据应用程序 Slug每个提供程序都有不同的颁发者。"
#: authentik/providers/oauth2/models.py
msgid "Strict URL comparison"
msgstr "严格 URL 比较"
#: authentik/providers/oauth2/models.py
msgid "Regular Expression URL matching"
msgstr "正则表达式 URL 匹配"
#: authentik/providers/oauth2/models.py #: authentik/providers/oauth2/models.py
msgid "code (Authorization Code Flow)" msgid "code (Authorization Code Flow)"
msgstr "code授权码流程" msgstr "code授权码流程"
@ -1406,6 +1393,10 @@ msgstr "客户端密钥"
msgid "Redirect URIs" msgid "Redirect URIs"
msgstr "重定向 URI" msgstr "重定向 URI"
#: authentik/providers/oauth2/models.py
msgid "Enter each URI on a new line."
msgstr "每行输入一个 URI。"
#: authentik/providers/oauth2/models.py #: authentik/providers/oauth2/models.py
msgid "Include claims in id_token" msgid "Include claims in id_token"
msgstr "在 id_token 中包含声明" msgstr "在 id_token 中包含声明"

View File

@ -14,7 +14,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-11-26 00:09+0000\n" "POT-Creation-Date: 2024-11-18 00:09+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n" "PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: deluxghost, 2024\n" "Last-Translator: deluxghost, 2024\n"
"Language-Team: Chinese (China) (https://app.transifex.com/authentik/teams/119923/zh_CN/)\n" "Language-Team: Chinese (China) (https://app.transifex.com/authentik/teams/119923/zh_CN/)\n"
@ -81,9 +81,9 @@ msgid "authentik Export - {date}"
msgstr "authentik 导出 - {date}" msgstr "authentik 导出 - {date}"
#: authentik/blueprints/v1/tasks.py authentik/crypto/tasks.py #: authentik/blueprints/v1/tasks.py authentik/crypto/tasks.py
#, python-brace-format #, python-format
msgid "Successfully imported {count} files." msgid "Successfully imported %(count)d files."
msgstr "已成功导入 {count} 个文件。" msgstr "已成功导入 %(count)d 个文件。"
#: authentik/brands/models.py #: authentik/brands/models.py
msgid "" msgid ""
@ -867,14 +867,14 @@ msgid "Starting full provider sync"
msgstr "开始全量提供程序同步" msgstr "开始全量提供程序同步"
#: authentik/lib/sync/outgoing/tasks.py #: authentik/lib/sync/outgoing/tasks.py
#, python-brace-format #, python-format
msgid "Syncing page {page} of users" msgid "Syncing page %(page)d of users"
msgstr "正在同步用户页面 {page}" msgstr "正在同步用户页面 %(page)d"
#: authentik/lib/sync/outgoing/tasks.py #: authentik/lib/sync/outgoing/tasks.py
#, python-brace-format #, python-format
msgid "Syncing page {page} of groups" msgid "Syncing page %(page)d of groups"
msgstr "正在同步群组页面 {page}" msgstr "正在同步群组页面 %(page)d"
#: authentik/lib/sync/outgoing/tasks.py #: authentik/lib/sync/outgoing/tasks.py
#, python-brace-format #, python-brace-format
@ -1025,9 +1025,9 @@ msgid "Event Matcher Policies"
msgstr "事件匹配策略" msgstr "事件匹配策略"
#: authentik/policies/expiry/models.py #: authentik/policies/expiry/models.py
#, python-brace-format #, python-format
msgid "Password expired {days} days ago. Please update your password." msgid "Password expired %(days)d days ago. Please update your password."
msgstr "密码在 {days} 天前过期。请更新您的密码。" msgstr "密码在 %(days)d 天前过期。请更新您的密码。"
#: authentik/policies/expiry/models.py #: authentik/policies/expiry/models.py
msgid "Password has expired." msgid "Password has expired."
@ -1153,9 +1153,9 @@ msgid "Invalid password."
msgstr "无效密码。" msgstr "无效密码。"
#: authentik/policies/password/models.py #: authentik/policies/password/models.py
#, python-brace-format #, python-format
msgid "Password exists on {count} online lists." msgid "Password exists on %(count)d online lists."
msgstr "{count} 个在线列表中存在密码。" msgstr "%(count)d 个在线列表中存在密码。"
#: authentik/policies/password/models.py #: authentik/policies/password/models.py
msgid "Password is too weak." msgid "Password is too weak."
@ -1274,11 +1274,6 @@ msgstr "LDAP 提供程序"
msgid "Search full LDAP directory" msgid "Search full LDAP directory"
msgstr "搜索完整 LDAP 目录" msgstr "搜索完整 LDAP 目录"
#: authentik/providers/oauth2/api/providers.py
#, python-brace-format
msgid "Invalid Regex Pattern: {url}"
msgstr "无效的正则表达式模式:{url}"
#: authentik/providers/oauth2/id_token.py #: authentik/providers/oauth2/id_token.py
msgid "Based on the Hashed User ID" msgid "Based on the Hashed User ID"
msgstr "基于经过哈希处理的用户 ID" msgstr "基于经过哈希处理的用户 ID"
@ -1321,14 +1316,6 @@ msgstr "所有提供程序都使用相同的标识符"
msgid "Each provider has a different issuer, based on the application slug." msgid "Each provider has a different issuer, based on the application slug."
msgstr "根据应用程序 Slug每个提供程序都有不同的颁发者。" msgstr "根据应用程序 Slug每个提供程序都有不同的颁发者。"
#: authentik/providers/oauth2/models.py
msgid "Strict URL comparison"
msgstr "严格 URL 比较"
#: authentik/providers/oauth2/models.py
msgid "Regular Expression URL matching"
msgstr "正则表达式 URL 匹配"
#: authentik/providers/oauth2/models.py #: authentik/providers/oauth2/models.py
msgid "code (Authorization Code Flow)" msgid "code (Authorization Code Flow)"
msgstr "code授权码流程" msgstr "code授权码流程"
@ -1405,6 +1392,10 @@ msgstr "客户端密钥"
msgid "Redirect URIs" msgid "Redirect URIs"
msgstr "重定向 URI" msgstr "重定向 URI"
#: authentik/providers/oauth2/models.py
msgid "Enter each URI on a new line."
msgstr "每行输入一个 URI。"
#: authentik/providers/oauth2/models.py #: authentik/providers/oauth2/models.py
msgid "Include claims in id_token" msgid "Include claims in id_token"
msgstr "在 id_token 中包含声明" msgstr "在 id_token 中包含声明"

600
poetry.lock generated
View File

@ -342,91 +342,6 @@ six = "*"
[package.extras] [package.extras]
visualize = ["Twisted (>=16.1.1)", "graphviz (>0.5.1)"] visualize = ["Twisted (>=16.1.1)", "graphviz (>0.5.1)"]
[[package]]
name = "aws-cdk-asset-awscli-v1"
version = "2.2.212"
description = "A library that contains the AWS CLI for use in Lambda Layers"
optional = false
python-versions = "~=3.8"
files = [
{file = "aws_cdk.asset_awscli_v1-2.2.212-py3-none-any.whl", hash = "sha256:12161e2d528698957bc2c0f53d2f5e81de54f8ad0e4b94316634bdc1db50f539"},
{file = "aws_cdk_asset_awscli_v1-2.2.212.tar.gz", hash = "sha256:3a4374562f37c9cd3f59cb45173a18ef0f781c0f1df187773662a1dd14cc18fd"},
]
[package.dependencies]
jsii = ">=1.105.0,<2.0.0"
publication = ">=0.0.3"
typeguard = ">=2.13.3,<4.3.0"
[[package]]
name = "aws-cdk-asset-kubectl-v20"
version = "2.1.3"
description = "A Lambda Layer that contains kubectl v1.20"
optional = false
python-versions = "~=3.8"
files = [
{file = "aws_cdk.asset_kubectl_v20-2.1.3-py3-none-any.whl", hash = "sha256:d5612e5bd03c215a28ce53193b1144ecf4e93b3b6779563c046a8a74d83a3979"},
{file = "aws_cdk_asset_kubectl_v20-2.1.3.tar.gz", hash = "sha256:237cd8530d9e8be0bbc7159af927dbb6b7f91bf3f4099c8ef4d9a213b34264be"},
]
[package.dependencies]
jsii = ">=1.103.1,<2.0.0"
publication = ">=0.0.3"
typeguard = ">=2.13.3,<5.0.0"
[[package]]
name = "aws-cdk-asset-node-proxy-agent-v6"
version = "2.1.0"
description = "@aws-cdk/asset-node-proxy-agent-v6"
optional = false
python-versions = "~=3.8"
files = [
{file = "aws_cdk.asset_node_proxy_agent_v6-2.1.0-py3-none-any.whl", hash = "sha256:24a388b69a44d03bae6dbf864c4e25ba650d4b61c008b4568b94ffbb9a69e40e"},
{file = "aws_cdk_asset_node_proxy_agent_v6-2.1.0.tar.gz", hash = "sha256:1f292c0631f86708ba4ee328b3a2b229f7e46ea1c79fbde567ee9eb119c2b0e2"},
]
[package.dependencies]
jsii = ">=1.103.1,<2.0.0"
publication = ">=0.0.3"
typeguard = ">=2.13.3,<5.0.0"
[[package]]
name = "aws-cdk-cloud-assembly-schema"
version = "38.0.1"
description = "Cloud Assembly Schema"
optional = false
python-versions = "~=3.8"
files = [
{file = "aws_cdk.cloud_assembly_schema-38.0.1-py3-none-any.whl", hash = "sha256:92613b46213b460681e9424f09b77f06ff059eb1c773092540364ef82fcecf55"},
{file = "aws_cdk_cloud_assembly_schema-38.0.1.tar.gz", hash = "sha256:7c75861adc41f7b959910d4b3b191ea242815402e599dbfa31934892838ae25e"},
]
[package.dependencies]
jsii = ">=1.103.1,<2.0.0"
publication = ">=0.0.3"
typeguard = ">=2.13.3,<5.0.0"
[[package]]
name = "aws-cdk-lib"
version = "2.171.1"
description = "Version 2 of the AWS Cloud Development Kit library"
optional = false
python-versions = "~=3.8"
files = [
{file = "aws_cdk_lib-2.171.1-py3-none-any.whl", hash = "sha256:05da3f0b776db3c083421fd235e6f139441d31e7858e66683fdd6e360b88f949"},
{file = "aws_cdk_lib-2.171.1.tar.gz", hash = "sha256:2b329b926976b03d55bfdfe01ab09886c9f19a337e343686dfe8d23a439f880f"},
]
[package.dependencies]
"aws-cdk.asset-awscli-v1" = ">=2.2.208,<3.0.0"
"aws-cdk.asset-kubectl-v20" = ">=2.1.3,<3.0.0"
"aws-cdk.asset-node-proxy-agent-v6" = ">=2.1.0,<3.0.0"
"aws-cdk.cloud-assembly-schema" = ">=38.0.1,<39.0.0"
constructs = ">=10.0.0,<11.0.0"
jsii = ">=1.104.0,<2.0.0"
publication = ">=0.0.3"
typeguard = ">=2.13.3,<4.3.0"
[[package]] [[package]]
name = "azure-core" name = "azure-core"
version = "1.30.2" version = "1.30.2"
@ -466,13 +381,13 @@ typing-extensions = ">=4.0.0"
[[package]] [[package]]
name = "bandit" name = "bandit"
version = "1.8.0" version = "1.7.10"
description = "Security oriented static analyser for python code." description = "Security oriented static analyser for python code."
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.8"
files = [ files = [
{file = "bandit-1.8.0-py3-none-any.whl", hash = "sha256:b1a61d829c0968aed625381e426aa378904b996529d048f8d908fa28f6b13e38"}, {file = "bandit-1.7.10-py3-none-any.whl", hash = "sha256:665721d7bebbb4485a339c55161ac0eedde27d51e638000d91c8c2d68343ad02"},
{file = "bandit-1.8.0.tar.gz", hash = "sha256:b5bfe55a095abd9fe20099178a7c6c060f844bfd4fe4c76d28e35e4c52b9d31e"}, {file = "bandit-1.7.10.tar.gz", hash = "sha256:59ed5caf5d92b6ada4bf65bc6437feea4a9da1093384445fed4d472acc6cff7b"},
] ]
[package.dependencies] [package.dependencies]
@ -643,30 +558,6 @@ files = [
{file = "cachetools-5.4.0.tar.gz", hash = "sha256:b8adc2e7c07f105ced7bc56dbb6dfbe7c4a00acce20e2227b3f355be89bc6827"}, {file = "cachetools-5.4.0.tar.gz", hash = "sha256:b8adc2e7c07f105ced7bc56dbb6dfbe7c4a00acce20e2227b3f355be89bc6827"},
] ]
[[package]]
name = "cattrs"
version = "24.1.2"
description = "Composable complex class support for attrs and dataclasses."
optional = false
python-versions = ">=3.8"
files = [
{file = "cattrs-24.1.2-py3-none-any.whl", hash = "sha256:67c7495b760168d931a10233f979b28dc04daf853b30752246f4f8471c6d68d0"},
{file = "cattrs-24.1.2.tar.gz", hash = "sha256:8028cfe1ff5382df59dd36474a86e02d817b06eaf8af84555441bac915d2ef85"},
]
[package.dependencies]
attrs = ">=23.1.0"
[package.extras]
bson = ["pymongo (>=4.4.0)"]
cbor2 = ["cbor2 (>=5.4.6)"]
msgpack = ["msgpack (>=1.0.5)"]
msgspec = ["msgspec (>=0.18.5)"]
orjson = ["orjson (>=3.9.2)"]
pyyaml = ["pyyaml (>=6.0)"]
tomlkit = ["tomlkit (>=0.11.8)"]
ujson = ["ujson (>=5.7.0)"]
[[package]] [[package]]
name = "cbor2" name = "cbor2"
version = "5.6.5" version = "5.6.5"
@ -1098,91 +989,75 @@ files = [
{file = "constantly-23.10.4.tar.gz", hash = "sha256:aa92b70a33e2ac0bb33cd745eb61776594dc48764b06c35e0efd050b7f1c7cbd"}, {file = "constantly-23.10.4.tar.gz", hash = "sha256:aa92b70a33e2ac0bb33cd745eb61776594dc48764b06c35e0efd050b7f1c7cbd"},
] ]
[[package]]
name = "constructs"
version = "10.4.2"
description = "A programming model for software-defined state"
optional = false
python-versions = "~=3.8"
files = [
{file = "constructs-10.4.2-py3-none-any.whl", hash = "sha256:1f0f59b004edebfde0f826340698b8c34611f57848139b7954904c61645f13c1"},
{file = "constructs-10.4.2.tar.gz", hash = "sha256:ce54724360fffe10bab27d8a081844eb81f5ace7d7c62c84b719c49f164d5307"},
]
[package.dependencies]
jsii = ">=1.102.0,<2.0.0"
publication = ">=0.0.3"
typeguard = ">=2.13.3,<2.14.0"
[[package]] [[package]]
name = "coverage" name = "coverage"
version = "7.6.8" version = "7.6.7"
description = "Code coverage measurement for Python" description = "Code coverage measurement for Python"
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
files = [ files = [
{file = "coverage-7.6.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b39e6011cd06822eb964d038d5dff5da5d98652b81f5ecd439277b32361a3a50"}, {file = "coverage-7.6.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:108bb458827765d538abcbf8288599fee07d2743357bdd9b9dad456c287e121e"},
{file = "coverage-7.6.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:63c19702db10ad79151a059d2d6336fe0c470f2e18d0d4d1a57f7f9713875dcf"}, {file = "coverage-7.6.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c973b2fe4dc445cb865ab369df7521df9c27bf40715c837a113edaa2aa9faf45"},
{file = "coverage-7.6.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3985b9be361d8fb6b2d1adc9924d01dec575a1d7453a14cccd73225cb79243ee"}, {file = "coverage-7.6.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c6b24007c4bcd0b19fac25763a7cac5035c735ae017e9a349b927cfc88f31c1"},
{file = "coverage-7.6.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:644ec81edec0f4ad17d51c838a7d01e42811054543b76d4ba2c5d6af741ce2a6"}, {file = "coverage-7.6.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acbb8af78f8f91b3b51f58f288c0994ba63c646bc1a8a22ad072e4e7e0a49f1c"},
{file = "coverage-7.6.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f188a2402f8359cf0c4b1fe89eea40dc13b52e7b4fd4812450da9fcd210181d"}, {file = "coverage-7.6.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad32a981bcdedb8d2ace03b05e4fd8dace8901eec64a532b00b15217d3677dd2"},
{file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e19122296822deafce89a0c5e8685704c067ae65d45e79718c92df7b3ec3d331"}, {file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:34d23e28ccb26236718a3a78ba72744212aa383141961dd6825f6595005c8b06"},
{file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:13618bed0c38acc418896005732e565b317aa9e98d855a0e9f211a7ffc2d6638"}, {file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e25bacb53a8c7325e34d45dddd2f2fbae0dbc230d0e2642e264a64e17322a777"},
{file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:193e3bffca48ad74b8c764fb4492dd875038a2f9925530cb094db92bb5e47bed"}, {file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af05bbba896c4472a29408455fe31b3797b4d8648ed0a2ccac03e074a77e2314"},
{file = "coverage-7.6.8-cp310-cp310-win32.whl", hash = "sha256:3988665ee376abce49613701336544041f2117de7b7fbfe91b93d8ff8b151c8e"}, {file = "coverage-7.6.7-cp310-cp310-win32.whl", hash = "sha256:796c9b107d11d2d69e1849b2dfe41730134b526a49d3acb98ca02f4985eeff7a"},
{file = "coverage-7.6.8-cp310-cp310-win_amd64.whl", hash = "sha256:f56f49b2553d7dd85fd86e029515a221e5c1f8cb3d9c38b470bc38bde7b8445a"}, {file = "coverage-7.6.7-cp310-cp310-win_amd64.whl", hash = "sha256:987a8e3da7da4eed10a20491cf790589a8e5e07656b6dc22d3814c4d88faf163"},
{file = "coverage-7.6.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:86cffe9c6dfcfe22e28027069725c7f57f4b868a3f86e81d1c62462764dc46d4"}, {file = "coverage-7.6.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7e61b0e77ff4dddebb35a0e8bb5a68bf0f8b872407d8d9f0c726b65dfabe2469"},
{file = "coverage-7.6.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d82ab6816c3277dc962cfcdc85b1efa0e5f50fb2c449432deaf2398a2928ab94"}, {file = "coverage-7.6.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a5407a75ca4abc20d6252efeb238377a71ce7bda849c26c7a9bece8680a5d99"},
{file = "coverage-7.6.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13690e923a3932e4fad4c0ebfb9cb5988e03d9dcb4c5150b5fcbf58fd8bddfc4"}, {file = "coverage-7.6.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df002e59f2d29e889c37abd0b9ee0d0e6e38c24f5f55d71ff0e09e3412a340ec"},
{file = "coverage-7.6.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4be32da0c3827ac9132bb488d331cb32e8d9638dd41a0557c5569d57cf22c9c1"}, {file = "coverage-7.6.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:673184b3156cba06154825f25af33baa2671ddae6343f23175764e65a8c4c30b"},
{file = "coverage-7.6.8-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44e6c85bbdc809383b509d732b06419fb4544dca29ebe18480379633623baafb"}, {file = "coverage-7.6.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e69ad502f1a2243f739f5bd60565d14a278be58be4c137d90799f2c263e7049a"},
{file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:768939f7c4353c0fac2f7c37897e10b1414b571fd85dd9fc49e6a87e37a2e0d8"}, {file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:60dcf7605c50ea72a14490d0756daffef77a5be15ed1b9fea468b1c7bda1bc3b"},
{file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e44961e36cb13c495806d4cac67640ac2866cb99044e210895b506c26ee63d3a"}, {file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9c2eb378bebb2c8f65befcb5147877fc1c9fbc640fc0aad3add759b5df79d55d"},
{file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ea8bb1ab9558374c0ab591783808511d135a833c3ca64a18ec927f20c4030f0"}, {file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c0317288f032221d35fa4cbc35d9f4923ff0dfd176c79c9b356e8ef8ef2dff4"},
{file = "coverage-7.6.8-cp311-cp311-win32.whl", hash = "sha256:629a1ba2115dce8bf75a5cce9f2486ae483cb89c0145795603d6554bdc83e801"}, {file = "coverage-7.6.7-cp311-cp311-win32.whl", hash = "sha256:951aade8297358f3618a6e0660dc74f6b52233c42089d28525749fc8267dccd2"},
{file = "coverage-7.6.8-cp311-cp311-win_amd64.whl", hash = "sha256:fb9fc32399dca861584d96eccd6c980b69bbcd7c228d06fb74fe53e007aa8ef9"}, {file = "coverage-7.6.7-cp311-cp311-win_amd64.whl", hash = "sha256:5e444b8e88339a2a67ce07d41faabb1d60d1004820cee5a2c2b54e2d8e429a0f"},
{file = "coverage-7.6.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e683e6ecc587643f8cde8f5da6768e9d165cd31edf39ee90ed7034f9ca0eefee"}, {file = "coverage-7.6.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f07ff574986bc3edb80e2c36391678a271d555f91fd1d332a1e0f4b5ea4b6ea9"},
{file = "coverage-7.6.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1defe91d41ce1bd44b40fabf071e6a01a5aa14de4a31b986aa9dfd1b3e3e414a"}, {file = "coverage-7.6.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:49ed5ee4109258973630c1f9d099c7e72c5c36605029f3a91fe9982c6076c82b"},
{file = "coverage-7.6.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7ad66e8e50225ebf4236368cc43c37f59d5e6728f15f6e258c8639fa0dd8e6d"}, {file = "coverage-7.6.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3e8796434a8106b3ac025fd15417315d7a58ee3e600ad4dbcfddc3f4b14342c"},
{file = "coverage-7.6.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fe47da3e4fda5f1abb5709c156eca207eacf8007304ce3019eb001e7a7204cb"}, {file = "coverage-7.6.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3b925300484a3294d1c70f6b2b810d6526f2929de954e5b6be2bf8caa1f12c1"},
{file = "coverage-7.6.8-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:202a2d645c5a46b84992f55b0a3affe4f0ba6b4c611abec32ee88358db4bb649"}, {file = "coverage-7.6.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c42ec2c522e3ddd683dec5cdce8e62817afb648caedad9da725001fa530d354"},
{file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4674f0daa1823c295845b6a740d98a840d7a1c11df00d1fd62614545c1583787"}, {file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0266b62cbea568bd5e93a4da364d05de422110cbed5056d69339bd5af5685433"},
{file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:74610105ebd6f33d7c10f8907afed696e79c59e3043c5f20eaa3a46fddf33b4c"}, {file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e5f2a0f161d126ccc7038f1f3029184dbdf8f018230af17ef6fd6a707a5b881f"},
{file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37cda8712145917105e07aab96388ae76e787270ec04bcb9d5cc786d7cbb8443"}, {file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c132b5a22821f9b143f87446805e13580b67c670a548b96da945a8f6b4f2efbb"},
{file = "coverage-7.6.8-cp312-cp312-win32.whl", hash = "sha256:9e89d5c8509fbd6c03d0dd1972925b22f50db0792ce06324ba069f10787429ad"}, {file = "coverage-7.6.7-cp312-cp312-win32.whl", hash = "sha256:7c07de0d2a110f02af30883cd7dddbe704887617d5c27cf373362667445a4c76"},
{file = "coverage-7.6.8-cp312-cp312-win_amd64.whl", hash = "sha256:379c111d3558272a2cae3d8e57e6b6e6f4fe652905692d54bad5ea0ca37c5ad4"}, {file = "coverage-7.6.7-cp312-cp312-win_amd64.whl", hash = "sha256:fd49c01e5057a451c30c9b892948976f5d38f2cbd04dc556a82743ba8e27ed8c"},
{file = "coverage-7.6.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b0c69f4f724c64dfbfe79f5dfb503b42fe6127b8d479b2677f2b227478db2eb"}, {file = "coverage-7.6.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:46f21663e358beae6b368429ffadf14ed0a329996248a847a4322fb2e35d64d3"},
{file = "coverage-7.6.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c15b32a7aca8038ed7644f854bf17b663bc38e1671b5d6f43f9a2b2bd0c46f63"}, {file = "coverage-7.6.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:40cca284c7c310d622a1677f105e8507441d1bb7c226f41978ba7c86979609ab"},
{file = "coverage-7.6.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63068a11171e4276f6ece913bde059e77c713b48c3a848814a6537f35afb8365"}, {file = "coverage-7.6.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77256ad2345c29fe59ae861aa11cfc74579c88d4e8dbf121cbe46b8e32aec808"},
{file = "coverage-7.6.8-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f4548c5ead23ad13fb7a2c8ea541357474ec13c2b736feb02e19a3085fac002"}, {file = "coverage-7.6.7-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87ea64b9fa52bf395272e54020537990a28078478167ade6c61da7ac04dc14bc"},
{file = "coverage-7.6.8-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b4b4299dd0d2c67caaaf286d58aef5e75b125b95615dda4542561a5a566a1e3"}, {file = "coverage-7.6.7-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d608a7808793e3615e54e9267519351c3ae204a6d85764d8337bd95993581a8"},
{file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9ebfb2507751f7196995142f057d1324afdab56db1d9743aab7f50289abd022"}, {file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdd94501d65adc5c24f8a1a0eda110452ba62b3f4aeaba01e021c1ed9cb8f34a"},
{file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c1b4474beee02ede1eef86c25ad4600a424fe36cff01a6103cb4533c6bf0169e"}, {file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:82c809a62e953867cf57e0548c2b8464207f5f3a6ff0e1e961683e79b89f2c55"},
{file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d9fd2547e6decdbf985d579cf3fc78e4c1d662b9b0ff7cc7862baaab71c9cc5b"}, {file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bb684694e99d0b791a43e9fc0fa58efc15ec357ac48d25b619f207c41f2fd384"},
{file = "coverage-7.6.8-cp313-cp313-win32.whl", hash = "sha256:8aae5aea53cbfe024919715eca696b1a3201886ce83790537d1c3668459c7146"}, {file = "coverage-7.6.7-cp313-cp313-win32.whl", hash = "sha256:963e4a08cbb0af6623e61492c0ec4c0ec5c5cf74db5f6564f98248d27ee57d30"},
{file = "coverage-7.6.8-cp313-cp313-win_amd64.whl", hash = "sha256:ae270e79f7e169ccfe23284ff5ea2d52a6f401dc01b337efb54b3783e2ce3f28"}, {file = "coverage-7.6.7-cp313-cp313-win_amd64.whl", hash = "sha256:14045b8bfd5909196a90da145a37f9d335a5d988a83db34e80f41e965fb7cb42"},
{file = "coverage-7.6.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de38add67a0af869b0d79c525d3e4588ac1ffa92f39116dbe0ed9753f26eba7d"}, {file = "coverage-7.6.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f2c7a045eef561e9544359a0bf5784b44e55cefc7261a20e730baa9220c83413"},
{file = "coverage-7.6.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b07c25d52b1c16ce5de088046cd2432b30f9ad5e224ff17c8f496d9cb7d1d451"}, {file = "coverage-7.6.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dd4e4a49d9c72a38d18d641135d2fb0bdf7b726ca60a103836b3d00a1182acd"},
{file = "coverage-7.6.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62a66ff235e4c2e37ed3b6104d8b478d767ff73838d1222132a7a026aa548764"}, {file = "coverage-7.6.7-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c95e0fa3d1547cb6f021ab72f5c23402da2358beec0a8e6d19a368bd7b0fb37"},
{file = "coverage-7.6.8-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09b9f848b28081e7b975a3626e9081574a7b9196cde26604540582da60235fdf"}, {file = "coverage-7.6.7-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f63e21ed474edd23f7501f89b53280014436e383a14b9bd77a648366c81dce7b"},
{file = "coverage-7.6.8-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:093896e530c38c8e9c996901858ac63f3d4171268db2c9c8b373a228f459bbc5"}, {file = "coverage-7.6.7-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead9b9605c54d15be228687552916c89c9683c215370c4a44f1f217d2adcc34d"},
{file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a7b8ac36fd688c8361cbc7bf1cb5866977ece6e0b17c34aa0df58bda4fa18a4"}, {file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0573f5cbf39114270842d01872952d301027d2d6e2d84013f30966313cadb529"},
{file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:38c51297b35b3ed91670e1e4efb702b790002e3245a28c76e627478aa3c10d83"}, {file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:e2c8e3384c12dfa19fa9a52f23eb091a8fad93b5b81a41b14c17c78e23dd1d8b"},
{file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2e4e0f60cb4bd7396108823548e82fdab72d4d8a65e58e2c19bbbc2f1e2bfa4b"}, {file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:70a56a2ec1869e6e9fa69ef6b76b1a8a7ef709972b9cc473f9ce9d26b5997ce3"},
{file = "coverage-7.6.8-cp313-cp313t-win32.whl", hash = "sha256:6535d996f6537ecb298b4e287a855f37deaf64ff007162ec0afb9ab8ba3b8b71"}, {file = "coverage-7.6.7-cp313-cp313t-win32.whl", hash = "sha256:dbba8210f5067398b2c4d96b4e64d8fb943644d5eb70be0d989067c8ca40c0f8"},
{file = "coverage-7.6.8-cp313-cp313t-win_amd64.whl", hash = "sha256:c79c0685f142ca53256722a384540832420dff4ab15fec1863d7e5bc8691bdcc"}, {file = "coverage-7.6.7-cp313-cp313t-win_amd64.whl", hash = "sha256:dfd14bcae0c94004baba5184d1c935ae0d1231b8409eb6c103a5fd75e8ecdc56"},
{file = "coverage-7.6.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ac47fa29d8d41059ea3df65bd3ade92f97ee4910ed638e87075b8e8ce69599e"}, {file = "coverage-7.6.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37a15573f988b67f7348916077c6d8ad43adb75e478d0910957394df397d2874"},
{file = "coverage-7.6.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:24eda3a24a38157eee639ca9afe45eefa8d2420d49468819ac5f88b10de84f4c"}, {file = "coverage-7.6.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b6cce5c76985f81da3769c52203ee94722cd5d5889731cd70d31fee939b74bf0"},
{file = "coverage-7.6.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4c81ed2820b9023a9a90717020315e63b17b18c274a332e3b6437d7ff70abe0"}, {file = "coverage-7.6.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ab9763d291a17b527ac6fd11d1a9a9c358280adb320e9c2672a97af346ac2c"},
{file = "coverage-7.6.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd55f8fc8fa494958772a2a7302b0354ab16e0b9272b3c3d83cdb5bec5bd1779"}, {file = "coverage-7.6.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6cf96ceaa275f071f1bea3067f8fd43bec184a25a962c754024c973af871e1b7"},
{file = "coverage-7.6.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f39e2f3530ed1626c66e7493be7a8423b023ca852aacdc91fb30162c350d2a92"}, {file = "coverage-7.6.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aee9cf6b0134d6f932d219ce253ef0e624f4fa588ee64830fcba193269e4daa3"},
{file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:716a78a342679cd1177bc8c2fe957e0ab91405bd43a17094324845200b2fddf4"}, {file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2bc3e45c16564cc72de09e37413262b9f99167803e5e48c6156bccdfb22c8327"},
{file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:177f01eeaa3aee4a5ffb0d1439c5952b53d5010f86e9d2667963e632e30082cc"}, {file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:623e6965dcf4e28a3debaa6fcf4b99ee06d27218f46d43befe4db1c70841551c"},
{file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:912e95017ff51dc3d7b6e2be158dedc889d9a5cc3382445589ce554f1a34c0ea"}, {file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:850cfd2d6fc26f8346f422920ac204e1d28814e32e3a58c19c91980fa74d8289"},
{file = "coverage-7.6.8-cp39-cp39-win32.whl", hash = "sha256:4db3ed6a907b555e57cc2e6f14dc3a4c2458cdad8919e40b5357ab9b6db6c43e"}, {file = "coverage-7.6.7-cp39-cp39-win32.whl", hash = "sha256:c296263093f099da4f51b3dff1eff5d4959b527d4f2f419e16508c5da9e15e8c"},
{file = "coverage-7.6.8-cp39-cp39-win_amd64.whl", hash = "sha256:428ac484592f780e8cd7b6b14eb568f7c85460c92e2a37cb0c0e5186e1a0d076"}, {file = "coverage-7.6.7-cp39-cp39-win_amd64.whl", hash = "sha256:90746521206c88bdb305a4bf3342b1b7316ab80f804d40c536fc7d329301ee13"},
{file = "coverage-7.6.8-pp39.pp310-none-any.whl", hash = "sha256:5c52a036535d12590c32c49209e79cabaad9f9ad8aa4cbd875b68c4d67a9cbce"}, {file = "coverage-7.6.7-pp39.pp310-none-any.whl", hash = "sha256:0ddcb70b3a3a57581b450571b31cb774f23eb9519c2aaa6176d3a84c9fc57671"},
{file = "coverage-7.6.8.tar.gz", hash = "sha256:8b2b8503edb06822c86d82fa64a4a5cb0760bb8f31f26e138ec743f422f37cfc"}, {file = "coverage-7.6.7.tar.gz", hash = "sha256:d79d4826e41441c9a118ff045e4bccb9fdbdcb1d02413e7ea6eb5c87b5439d24"},
] ]
[package.extras] [package.extras]
@ -1190,53 +1065,51 @@ toml = ["tomli"]
[[package]] [[package]]
name = "cryptography" name = "cryptography"
version = "44.0.0" version = "43.0.3"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
optional = false optional = false
python-versions = "!=3.9.0,!=3.9.1,>=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "cryptography-44.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123"}, {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"},
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"},
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f"},
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6"},
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18"},
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543"}, {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd"},
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:60eb32934076fa07e4316b7b2742fa52cbb190b42c2df2863dbc4230a0a9b385"}, {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73"},
{file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e"}, {file = "cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2"},
{file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e"}, {file = "cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd"},
{file = "cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053"}, {file = "cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984"},
{file = "cryptography-44.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd"}, {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5"},
{file = "cryptography-44.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591"}, {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4"},
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7"}, {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7"},
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc"}, {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405"},
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289"}, {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16"},
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7"}, {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73"},
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c"}, {file = "cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995"},
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9abcc2e083cbe8dde89124a47e5e53ec38751f0d7dfd36801008f316a127d7ba"}, {file = "cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362"},
{file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64"}, {file = "cryptography-43.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c"},
{file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285"}, {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3"},
{file = "cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417"}, {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83"},
{file = "cryptography-44.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede"}, {file = "cryptography-43.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7"},
{file = "cryptography-44.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:37d76e6863da3774cd9db5b409a9ecfd2c71c981c38788d3fcfaf177f447b731"}, {file = "cryptography-43.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664"},
{file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:f677e1268c4e23420c3acade68fac427fffcb8d19d7df95ed7ad17cdef8404f4"}, {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08"},
{file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f5e7cb1e5e56ca0933b4873c0220a78b773b24d40d186b6738080b73d3d0a756"}, {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa"},
{file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:8b3e6eae66cf54701ee7d9c83c30ac0a1e3fa17be486033000f2a73a12ab507c"}, {file = "cryptography-43.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff"},
{file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:be4ce505894d15d5c5037167ffb7f0ae90b7be6f2a98f9a5c3442395501c32fa"}, {file = "cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805"},
{file = "cryptography-44.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:62901fb618f74d7d81bf408c8719e9ec14d863086efe4185afd07c352aee1d2c"},
{file = "cryptography-44.0.0.tar.gz", hash = "sha256:cd4e834f340b4293430701e772ec543b0fbe6c2dea510a5286fe0acabe153a02"},
] ]
[package.dependencies] [package.dependencies]
cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""}
[package.extras] [package.extras]
docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0)"] docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"]
docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"]
nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"] nox = ["nox"]
pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] pep8test = ["check-sdist", "click", "mypy", "ruff"]
sdist = ["build (>=1.0.0)"] sdist = ["build"]
ssh = ["bcrypt (>=3.1.5)"] ssh = ["bcrypt (>=3.1.5)"]
test = ["certifi (>=2024)", "cryptography-vectors (==44.0.0)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] test = ["certifi", "cryptography-vectors (==43.0.3)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"]
test-randomorder = ["pytest-randomly"] test-randomorder = ["pytest-randomly"]
[[package]] [[package]]
@ -1670,13 +1543,13 @@ orjson = ">=3.3.0"
[[package]] [[package]]
name = "drf-spectacular" name = "drf-spectacular"
version = "0.28.0" version = "0.27.2"
description = "Sane and flexible OpenAPI 3 schema generation for Django REST framework" description = "Sane and flexible OpenAPI 3 schema generation for Django REST framework"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "drf_spectacular-0.28.0-py3-none-any.whl", hash = "sha256:856e7edf1056e49a4245e87a61e8da4baff46c83dbc25be1da2df77f354c7cb4"}, {file = "drf-spectacular-0.27.2.tar.gz", hash = "sha256:a199492f2163c4101055075ebdbb037d59c6e0030692fc83a1a8c0fc65929981"},
{file = "drf_spectacular-0.28.0.tar.gz", hash = "sha256:2c778a47a40ab2f5078a7c42e82baba07397bb35b074ae4680721b2805943061"}, {file = "drf_spectacular-0.27.2-py3-none-any.whl", hash = "sha256:b1c04bf8b2fbbeaf6f59414b4ea448c8787aba4d32f76055c3b13335cf7ec37b"},
] ]
[package.dependencies] [package.dependencies]
@ -1747,13 +1620,13 @@ idna = ">=2.0.0"
[[package]] [[package]]
name = "fido2" name = "fido2"
version = "1.2.0" version = "1.1.3"
description = "FIDO2/WebAuthn library for implementing clients and servers." description = "FIDO2/WebAuthn library for implementing clients and servers."
optional = false optional = false
python-versions = "<4.0,>=3.8" python-versions = ">=3.8,<4.0"
files = [ files = [
{file = "fido2-1.2.0-py3-none-any.whl", hash = "sha256:f7c8ee62e359aa980a45773f9493965bb29ede1b237a9218169dbfe60c80e130"}, {file = "fido2-1.1.3-py3-none-any.whl", hash = "sha256:6be34c0b9fe85e4911fd2d103cce7ae8ce2f064384a7a2a3bd970b3ef7702931"},
{file = "fido2-1.2.0.tar.gz", hash = "sha256:e39f95920122d64283fda5e5581d95a206e704fa42846bfa4662f86aa0d3333b"}, {file = "fido2-1.1.3.tar.gz", hash = "sha256:26100f226d12ced621ca6198528ce17edf67b78df4287aee1285fee3cd5aa9fc"},
] ]
[package.dependencies] [package.dependencies]
@ -2360,26 +2233,6 @@ files = [
{file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"},
] ]
[[package]]
name = "jsii"
version = "1.105.0"
description = "Python client for jsii runtime"
optional = false
python-versions = "~=3.8"
files = [
{file = "jsii-1.105.0-py3-none-any.whl", hash = "sha256:8888088479b449db6d8e3a7df25434ec4580bf4fc13f4f952e9db5f2a3fc0c0f"},
{file = "jsii-1.105.0.tar.gz", hash = "sha256:435682d509e628e6f8a765b017102e6fcd553f4d0f6b3417b3f7eb295c2e0d1f"},
]
[package.dependencies]
attrs = ">=21.2,<25.0"
cattrs = ">=1.8,<24.2"
importlib-resources = ">=5.2.0"
publication = ">=0.0.3"
python-dateutil = "*"
typeguard = ">=2.13.3,<4.5.0"
typing-extensions = ">=3.8,<5.0"
[[package]] [[package]]
name = "jsonpatch" name = "jsonpatch"
version = "1.33" version = "1.33"
@ -3109,13 +2962,13 @@ dev = ["bumpver", "isort", "mypy", "pylint", "pytest", "yapf"]
[[package]] [[package]]
name = "msgraph-sdk" name = "msgraph-sdk"
version = "1.14.0" version = "1.12.0"
description = "The Microsoft Graph Python SDK" description = "The Microsoft Graph Python SDK"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "msgraph_sdk-1.14.0-py3-none-any.whl", hash = "sha256:1a2f327dc8fbe5a5e6d0d84cf71d605e7b118b3066b1e16f011ccd8fd927bb03"}, {file = "msgraph_sdk-1.12.0-py3-none-any.whl", hash = "sha256:ac298b546b240391b0e407379d039db32862a56d6fe15cf7c5f7e77631fc6771"},
{file = "msgraph_sdk-1.14.0.tar.gz", hash = "sha256:5bbda80941c5d1794682753b8b291bd2ebed719a43d6de949fd0cd613b6dfbbd"}, {file = "msgraph_sdk-1.12.0.tar.gz", hash = "sha256:fbb5a8a9f6eed89b496f207eb35b6b4cfc7fefa75608aeef07477a3b2276d4fa"},
] ]
[package.dependencies] [package.dependencies]
@ -3834,17 +3687,6 @@ files = [
{file = "psycopg_c-3.2.3.tar.gz", hash = "sha256:06ae7db8eaec1a3845960fa7f997f4ccdb1a7a7ab8dc593a680bcc74e1359671"}, {file = "psycopg_c-3.2.3.tar.gz", hash = "sha256:06ae7db8eaec1a3845960fa7f997f4ccdb1a7a7ab8dc593a680bcc74e1359671"},
] ]
[[package]]
name = "publication"
version = "0.0.3"
description = "Publication helps you maintain public-api-friendly modules by preventing unintentional access to private implementation details via introspection."
optional = false
python-versions = "*"
files = [
{file = "publication-0.0.3-py2.py3-none-any.whl", hash = "sha256:0248885351febc11d8a1098d5c8e3ab2dabcf3e8c0c96db1e17ecd12b53afbe6"},
{file = "publication-0.0.3.tar.gz", hash = "sha256:68416a0de76dddcdd2930d1c8ef853a743cc96c82416c4e4d3b5d901c6276dc4"},
]
[[package]] [[package]]
name = "pyasn1" name = "pyasn1"
version = "0.6.0" version = "0.6.0"
@ -3883,13 +3725,13 @@ files = [
[[package]] [[package]]
name = "pydantic" name = "pydantic"
version = "2.10.2" version = "2.10.1"
description = "Data validation using Python type hints" description = "Data validation using Python type hints"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "pydantic-2.10.2-py3-none-any.whl", hash = "sha256:cfb96e45951117c3024e6b67b25cdc33a3cb7b2fa62e239f7af1378358a1d99e"}, {file = "pydantic-2.10.1-py3-none-any.whl", hash = "sha256:a8d20db84de64cf4a7d59e899c2caf0fe9d660c7cfc482528e7020d7dd189a7e"},
{file = "pydantic-2.10.2.tar.gz", hash = "sha256:2bc2d7f17232e0841cbba4641e65ba1eb6fafb3a08de3a091ff3ce14a197c4fa"}, {file = "pydantic-2.10.1.tar.gz", hash = "sha256:a4daca2dc0aa429555e0656d6bf94873a7dc5f54ee42b1f5873d666fb3f35560"},
] ]
[package.dependencies] [package.dependencies]
@ -4047,13 +3889,13 @@ windows-terminal = ["colorama (>=0.4.6)"]
[[package]] [[package]]
name = "pyjwt" name = "pyjwt"
version = "2.10.1" version = "2.10.0"
description = "JSON Web Token implementation in Python" description = "JSON Web Token implementation in Python"
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
files = [ files = [
{file = "PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"}, {file = "PyJWT-2.10.0-py3-none-any.whl", hash = "sha256:543b77207db656de204372350926bed5a86201c4cbff159f623f79c7bb487a15"},
{file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"}, {file = "pyjwt-2.10.0.tar.gz", hash = "sha256:7628a7eb7938959ac1b26e819a1df0fd3259505627b575e4bad6d08f76db695c"},
] ]
[package.dependencies] [package.dependencies]
@ -4093,20 +3935,20 @@ tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"]
[[package]] [[package]]
name = "pyopenssl" name = "pyopenssl"
version = "24.3.0" version = "24.2.1"
description = "Python wrapper module around the OpenSSL library" description = "Python wrapper module around the OpenSSL library"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "pyOpenSSL-24.3.0-py3-none-any.whl", hash = "sha256:e474f5a473cd7f92221cc04976e48f4d11502804657a08a989fb3be5514c904a"}, {file = "pyOpenSSL-24.2.1-py3-none-any.whl", hash = "sha256:967d5719b12b243588573f39b0c677637145c7a1ffedcd495a487e58177fbb8d"},
{file = "pyopenssl-24.3.0.tar.gz", hash = "sha256:49f7a019577d834746bc55c5fce6ecbcec0f2b4ec5ce1cf43a9a173b8138bb36"}, {file = "pyopenssl-24.2.1.tar.gz", hash = "sha256:4247f0dbe3748d560dcbb2ff3ea01af0f9a1a001ef5f7c4c647956ed8cbf0e95"},
] ]
[package.dependencies] [package.dependencies]
cryptography = ">=41.0.5,<45" cryptography = ">=41.0.5,<44"
[package.extras] [package.extras]
docs = ["sphinx (!=5.2.0,!=5.2.0.post0,!=7.2.5)", "sphinx_rtd_theme"] docs = ["sphinx (!=5.2.0,!=5.2.0.post0,!=7.2.5)", "sphinx-rtd-theme"]
test = ["pretend", "pytest (>=3.0.1)", "pytest-rerunfailures"] test = ["pretend", "pytest (>=3.0.1)", "pytest-rerunfailures"]
[[package]] [[package]]
@ -4152,13 +3994,13 @@ files = [
[[package]] [[package]]
name = "pytest" name = "pytest"
version = "8.3.4" version = "8.3.3"
description = "pytest: simple powerful testing with Python" description = "pytest: simple powerful testing with Python"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"},
{file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"},
] ]
[package.dependencies] [package.dependencies]
@ -4260,48 +4102,48 @@ cli = ["click (>=5.0)"]
[[package]] [[package]]
name = "python-kadmin-rs" name = "python-kadmin-rs"
version = "0.3.0" version = "0.2.0"
description = "Python interface to the Kerberos administration interface (kadm5)" description = "Python interface to the Kerberos administration interface (kadm5)"
optional = false optional = false
python-versions = "<3.14,>=3.9" python-versions = "<3.14,>=3.9"
files = [ files = [
{file = "python_kadmin_rs-0.3.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:7b40e2e62ba884182a955c8dbf64f9322cd80b5ea904a269c7099abc6a6536de"}, {file = "python_kadmin_rs-0.2.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:1fd29e57c165b0b677b5fd1fac6dba829f077125bd081dd061ed2d9431f0c14f"},
{file = "python_kadmin_rs-0.3.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:055da2fe5e99a0139b85770a182070ee318c1c8a5a2ddbd719aad2eef323d7d3"}, {file = "python_kadmin_rs-0.2.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:aa1d4ddb2a12fb852a650091e1924c57639b376538c5b23bdd2b00b93472d7a4"},
{file = "python_kadmin_rs-0.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:387732550dffa699069412e1181d6c066e4421b47a11287d8c42a4f2fcf3669a"}, {file = "python_kadmin_rs-0.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:2bdc6011906f074368311bb5be300b0636671549f3d6e918c5275189ee300659"},
{file = "python_kadmin_rs-0.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:4f57335b57e8cf2a6aecd21c27928294d3c408cf82c4f905fc88709126116165"}, {file = "python_kadmin_rs-0.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:eb6b967246debe6090293cea35aa8ef159f04c99cb78acfa260b55c00f96cd76"},
{file = "python_kadmin_rs-0.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:800516f5a8b8241adb5e95e11ba2a06b8c99a3219b64b4f28a154fa9abf5f702"}, {file = "python_kadmin_rs-0.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:95aeb75ff0921f01e9d47b02651994421619eaa7fb7f29c9552465134f638377"},
{file = "python_kadmin_rs-0.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b26cf8994e291f8a4d09e367af8c26de8a4fdf092e094a3ca0200c0300b163a1"}, {file = "python_kadmin_rs-0.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6285c951f74de5749b9bd43561439e6fe9fe3db5234837ad7d2f0bcd3725887a"},
{file = "python_kadmin_rs-0.3.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:672866243de0e6e6483450cad2a63b27a6676e64338a9565a472abda5281443c"}, {file = "python_kadmin_rs-0.2.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:1857a02b3f5f63fc4ec7fdaab87951e20517116e8075fedc25fcd235b93e58ce"},
{file = "python_kadmin_rs-0.3.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:5d436b5662a6e1e4b946c4842d469d86c4ada67f709f5e4dcd2d99222510dea1"}, {file = "python_kadmin_rs-0.2.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:910ad697934bf5502d810d2e8f8d7231c367c2644f135c7e5fa77afd50f11fae"},
{file = "python_kadmin_rs-0.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:64003fc8e840d859f343874989d5fee984623324767a954b4c70372e4884970e"}, {file = "python_kadmin_rs-0.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:d2938d701553433a84148c5679c6285d4b6e30644f9275e93c49b3c7afd75244"},
{file = "python_kadmin_rs-0.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:946868516536ce57b282f038d7a168da9c8e494baae82d735f2713c35d2c4ad9"}, {file = "python_kadmin_rs-0.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:03a28a1522ada645a5a88f96b821d70b141266f4d524779868c82a8bc22e11dc"},
{file = "python_kadmin_rs-0.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:03a39b6482b02f0909341baadbacf8d939a24f79f0c10747ad9ec45728f88ab6"}, {file = "python_kadmin_rs-0.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:532406f48aa1567f8b374f7d6bb3a5ab46b8d4c6047e38fd8336dbbf37aa2a2b"},
{file = "python_kadmin_rs-0.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc75e1bf4df853d361a5b80405d43ffa111f3f1a9e0a680cf341a8e6a5227c49"}, {file = "python_kadmin_rs-0.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:36786ecc1015ca52f7f9b9b6000d1f9dab21e37f05ddce7cb9b674eb6922270c"},
{file = "python_kadmin_rs-0.3.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:c074a424d51edc74c5d4263ccf546b6f0c3d9bd458ec9ac6a550bfc75d34da1b"}, {file = "python_kadmin_rs-0.2.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:be901980ded6077ae17b82af24ef4b7f78fae361aafb3c59cb95a81757c38765"},
{file = "python_kadmin_rs-0.3.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:096fd009beff684691e4b1d9df99f31bef951ecaec29f4cfac01e82e609cd34d"}, {file = "python_kadmin_rs-0.2.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:fbce43213bda7e3071725432cbece677ded29e4c0f2227b63227aff8c828ae8b"},
{file = "python_kadmin_rs-0.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:340155c21e6be971b558d032f5201058f39dafa34665a32bdcd9d8d4b8e8e2af"}, {file = "python_kadmin_rs-0.2.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:ce1ca8f6dcbd92ff8d7fd42ab83d7d25ff5e5432ae76ab605df2623cf4d52944"},
{file = "python_kadmin_rs-0.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9ea5758fa11e387fab993c1f6af3921386c91c47f487c820a842762b602cee43"}, {file = "python_kadmin_rs-0.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:e97ccf97054399b383b7ad1314b064191e16657cd320e2f4a06f0993e60cd946"},
{file = "python_kadmin_rs-0.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e13d369e1f70cd536d799ab2f653a2c65a6574e671cdd7cbfca863391d8ff903"}, {file = "python_kadmin_rs-0.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:77a017bdb8bf70cbbe599129c5efdb014255dd4a68e4c65825eadb210748ae3f"},
{file = "python_kadmin_rs-0.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5dc201513e40052d0eab24eb48996776a16a4a9d7c124014d48c09bd66468410"}, {file = "python_kadmin_rs-0.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bdc00198fa573261ad2b60bbaac3d9f0b67adcfa8bb0b49685dedc0f5843c1da"},
{file = "python_kadmin_rs-0.3.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:d58ba4e7a2b80fa5483139a008645a317689727a0c24a5b080ef531318f0ef4e"}, {file = "python_kadmin_rs-0.2.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e63d95ab5bc4ff12be5614d1928386bbee6d54141533b955f65bc6df9144bf97"},
{file = "python_kadmin_rs-0.3.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:426328d2656f8595df9e1463da08521021a73d423ff6b24fdc6d32fd44d5a3a7"}, {file = "python_kadmin_rs-0.2.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d266974af2faf4d44b9573f9d2c4b5c815dfb227223e9f019422936e06029224"},
{file = "python_kadmin_rs-0.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:0760b03075ecdb1ec8f19919c256b4a1e3cb15f451d1face8ceafb1b88d0449f"}, {file = "python_kadmin_rs-0.2.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:619a751b283d399edb6a8fa1957d6bc44dc404a9c0f16ed61517a2c288980341"},
{file = "python_kadmin_rs-0.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1c670aa445da67f5168be71a440c6f5e4de5392c123ff863a60fc2ea55e56b7b"}, {file = "python_kadmin_rs-0.2.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6a918aa7df7ae60d31cfefe9de7c05b2acba049af6e6a63fe541f2e43c3e9298"},
{file = "python_kadmin_rs-0.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:63e5fbc0d1104da050ffedb29a947222fedfc07d762719fd34355feb8d4bc64b"}, {file = "python_kadmin_rs-0.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eeebccec2f3d38366c7406418c112ae72ac074581c378f22dcf458703a0e0f65"},
{file = "python_kadmin_rs-0.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b76bc37d51ecf1dfaa58e9f0134c2c49a75827e5035ab7bcbb228c8188049a24"}, {file = "python_kadmin_rs-0.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:81cce25d8257774761c0e7d264ce8f43d90a752f6ebfdb87e17ed42d7199222e"},
{file = "python_kadmin_rs-0.3.0-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:098fc8d748974682c89888708affcca6b438504e268869c13aa62e67cb4b0414"}, {file = "python_kadmin_rs-0.2.0-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:9d208448e6c5bb694e3e75f80a46df4a8d5d457da4d50fa8fbdc6039f15bd5f6"},
{file = "python_kadmin_rs-0.3.0-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:cb4561a105f7cfe66d53d580baf45461bcfe682ef39c601aecbf608d57bc417c"}, {file = "python_kadmin_rs-0.2.0-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:6fa29bb5873b909852b3f8ec3a79b7d9b79c2367ae1d1b80a2239dbc2d600388"},
{file = "python_kadmin_rs-0.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:a3632d9bcbaf36fcdb7cf89c5233c05185f51befa617dc74064901c3b4897cd0"}, {file = "python_kadmin_rs-0.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:685dfd62df8da0ce0023af0ff27a18859d5f9b98e4494bd18d6cd792aa10dd19"},
{file = "python_kadmin_rs-0.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:2243eb7482e6f2f3f15caeae42f13e2ca842e78d52a0b3d60fb72c488d2b8143"}, {file = "python_kadmin_rs-0.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:4cad0165ac4444d2f38ba9e320039cdc33e47e6cb0f4ed0f5f1da880d6d6f979"},
{file = "python_kadmin_rs-0.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e6c588c673ecf99437acdbe3d8ee0771be354ce857515f96a32c401174144029"}, {file = "python_kadmin_rs-0.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:cffe60c6f03612b27da2b0f63514190aadd7c97ae37a60cd3fe558f167c0ceff"},
{file = "python_kadmin_rs-0.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:bb7e497bfe8fdc39383c303050eadaf7a891832ed83545b0b7cfba71099bdddf"}, {file = "python_kadmin_rs-0.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7d5a5dc9a38eea43e5579a20564fc071cd8e7b04be7ea6d73e08625087c4f52a"},
{file = "python_kadmin_rs-0.3.0-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:4b6489498e6cd08b84771891678a9dba05a4917104e3ef1f6d7170e7a6fea0a3"}, {file = "python_kadmin_rs-0.2.0-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:8861f7ebf4921f6ef01fac3a44c18b8777703e1bb34c5f90eab9d9edae254e0f"},
{file = "python_kadmin_rs-0.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ae0bb5f5d4527cdf163efae7726579b0018849087bf79ca7225f0a4bb753fbbf"}, {file = "python_kadmin_rs-0.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:0c075a82da8bb6d36ba50a0c227816068acb54e08edfaaffda2959a643e1015a"},
{file = "python_kadmin_rs-0.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3a3c0cfbe8569b2cf40df0bbbeca125de47f35c98ea16620a34fb5eaac958794"}, {file = "python_kadmin_rs-0.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:00c33345be4871e031ed642a3a26fbf20b5133307dd932a8a7a4af2b8b3a4e11"},
{file = "python_kadmin_rs-0.3.0-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:aba9b60842dbf7bc82a653e89132118f663d7c4239238e6e8419c1983a01e32e"}, {file = "python_kadmin_rs-0.2.0-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:798ceca4480cff3b82becb9ce6c8f6fda0daac41e90efc458872b5208608b506"},
{file = "python_kadmin_rs-0.3.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2760ff7ff68868155c94bfb62bd7972f8d0604e066eca2a69b82311b2807f5b5"}, {file = "python_kadmin_rs-0.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3b9b5a40457649271bf49670a91cd697e831b4515c9f59e923a68bfacb6be884"},
{file = "python_kadmin_rs-0.3.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:53e74326e05f8651917fab28d56f54e8e25644c87a8068b5c9af45e202fe619f"}, {file = "python_kadmin_rs-0.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b74f4b76d1a67fa81615936a945794ea8cfe92ca0718389f6a6e5f89d19a2284"},
{file = "python_kadmin_rs-0.3.0.tar.gz", hash = "sha256:2e39a9069ade69166e92968871dc84a9990d1c1bf8af11cc146ac38dac65f4e8"}, {file = "python_kadmin_rs-0.2.0.tar.gz", hash = "sha256:9a3e49b7a27f39e6b533a13eb0a0d23f2ecc186d8448d531b858a1dab3226f8a"},
] ]
[[package]] [[package]]
@ -4632,29 +4474,29 @@ pyasn1 = ">=0.1.3"
[[package]] [[package]]
name = "ruff" name = "ruff"
version = "0.8.1" version = "0.7.4"
description = "An extremely fast Python linter and code formatter, written in Rust." description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "ruff-0.8.1-py3-none-linux_armv6l.whl", hash = "sha256:fae0805bd514066f20309f6742f6ee7904a773eb9e6c17c45d6b1600ca65c9b5"}, {file = "ruff-0.7.4-py3-none-linux_armv6l.whl", hash = "sha256:a4919925e7684a3f18e18243cd6bea7cfb8e968a6eaa8437971f681b7ec51478"},
{file = "ruff-0.8.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8a4f7385c2285c30f34b200ca5511fcc865f17578383db154e098150ce0a087"}, {file = "ruff-0.7.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cfb365c135b830778dda8c04fb7d4280ed0b984e1aec27f574445231e20d6c63"},
{file = "ruff-0.8.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cd054486da0c53e41e0086e1730eb77d1f698154f910e0cd9e0d64274979a209"}, {file = "ruff-0.7.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:63a569b36bc66fbadec5beaa539dd81e0527cb258b94e29e0531ce41bacc1f20"},
{file = "ruff-0.8.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2029b8c22da147c50ae577e621a5bfbc5d1fed75d86af53643d7a7aee1d23871"}, {file = "ruff-0.7.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d06218747d361d06fd2fdac734e7fa92df36df93035db3dc2ad7aa9852cb109"},
{file = "ruff-0.8.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2666520828dee7dfc7e47ee4ea0d928f40de72056d929a7c5292d95071d881d1"}, {file = "ruff-0.7.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e0cea28d0944f74ebc33e9f934238f15c758841f9f5edd180b5315c203293452"},
{file = "ruff-0.8.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:333c57013ef8c97a53892aa56042831c372e0bb1785ab7026187b7abd0135ad5"}, {file = "ruff-0.7.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80094ecd4793c68b2571b128f91754d60f692d64bc0d7272ec9197fdd09bf9ea"},
{file = "ruff-0.8.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:288326162804f34088ac007139488dcb43de590a5ccfec3166396530b58fb89d"}, {file = "ruff-0.7.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:997512325c6620d1c4c2b15db49ef59543ef9cd0f4aa8065ec2ae5103cedc7e7"},
{file = "ruff-0.8.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b12c39b9448632284561cbf4191aa1b005882acbc81900ffa9f9f471c8ff7e26"}, {file = "ruff-0.7.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00b4cf3a6b5fad6d1a66e7574d78956bbd09abfd6c8a997798f01f5da3d46a05"},
{file = "ruff-0.8.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:364e6674450cbac8e998f7b30639040c99d81dfb5bbc6dfad69bc7a8f916b3d1"}, {file = "ruff-0.7.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7dbdc7d8274e1422722933d1edddfdc65b4336abf0b16dfcb9dedd6e6a517d06"},
{file = "ruff-0.8.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b22346f845fec132aa39cd29acb94451d030c10874408dbf776af3aaeb53284c"}, {file = "ruff-0.7.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e92dfb5f00eaedb1501b2f906ccabfd67b2355bdf117fea9719fc99ac2145bc"},
{file = "ruff-0.8.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b2f2f7a7e7648a2bfe6ead4e0a16745db956da0e3a231ad443d2a66a105c04fa"}, {file = "ruff-0.7.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3bd726099f277d735dc38900b6a8d6cf070f80828877941983a57bca1cd92172"},
{file = "ruff-0.8.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:adf314fc458374c25c5c4a4a9270c3e8a6a807b1bec018cfa2813d6546215540"}, {file = "ruff-0.7.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2e32829c429dd081ee5ba39aef436603e5b22335c3d3fff013cd585806a6486a"},
{file = "ruff-0.8.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a885d68342a231b5ba4d30b8c6e1b1ee3a65cf37e3d29b3c74069cdf1ee1e3c9"}, {file = "ruff-0.7.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:662a63b4971807623f6f90c1fb664613f67cc182dc4d991471c23c541fee62dd"},
{file = "ruff-0.8.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d2c16e3508c8cc73e96aa5127d0df8913d2290098f776416a4b157657bee44c5"}, {file = "ruff-0.7.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:876f5e09eaae3eb76814c1d3b68879891d6fde4824c015d48e7a7da4cf066a3a"},
{file = "ruff-0.8.1-py3-none-win32.whl", hash = "sha256:93335cd7c0eaedb44882d75a7acb7df4b77cd7cd0d2255c93b28791716e81790"}, {file = "ruff-0.7.4-py3-none-win32.whl", hash = "sha256:75c53f54904be42dd52a548728a5b572344b50d9b2873d13a3f8c5e3b91f5cac"},
{file = "ruff-0.8.1-py3-none-win_amd64.whl", hash = "sha256:2954cdbe8dfd8ab359d4a30cd971b589d335a44d444b6ca2cb3d1da21b75e4b6"}, {file = "ruff-0.7.4-py3-none-win_amd64.whl", hash = "sha256:745775c7b39f914238ed1f1b0bebed0b9155a17cd8bc0b08d3c87e4703b990d6"},
{file = "ruff-0.8.1-py3-none-win_arm64.whl", hash = "sha256:55873cc1a473e5ac129d15eccb3c008c096b94809d693fc7053f588b67822737"}, {file = "ruff-0.7.4-py3-none-win_arm64.whl", hash = "sha256:11bff065102c3ae9d3ea4dc9ecdfe5a5171349cdd0787c1fc64761212fc9cf1f"},
{file = "ruff-0.8.1.tar.gz", hash = "sha256:3583db9a6450364ed5ca3f3b4225958b24f78178908d5c4bc0f46251ccca898f"}, {file = "ruff-0.7.4.tar.gz", hash = "sha256:cd12e35031f5af6b9b93715d8c4f40360070b2041f81273d0527683d5708fce2"},
] ]
[[package]] [[package]]
@ -4693,13 +4535,13 @@ django-query = ["django (>=3.2)"]
[[package]] [[package]]
name = "selenium" name = "selenium"
version = "4.27.1" version = "4.26.1"
description = "Official Python bindings for Selenium WebDriver" description = "Official Python bindings for Selenium WebDriver"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "selenium-4.27.1-py3-none-any.whl", hash = "sha256:b89b1f62b5cfe8025868556fe82360d6b649d464f75d2655cb966c8f8447ea18"}, {file = "selenium-4.26.1-py3-none-any.whl", hash = "sha256:1db3f3a0cd5bb07624fa8a3905a6fdde1595a42185a0617077c361dc53d104fb"},
{file = "selenium-4.27.1.tar.gz", hash = "sha256:5296c425a75ff1b44d0d5199042b36a6d1ef76c04fb775b97b40be739a9caae2"}, {file = "selenium-4.26.1.tar.gz", hash = "sha256:7640f3f08ae7f4e450f895678e8a10a55eb4e4ca18311ed675ecc4684b96b683"},
] ]
[package.dependencies] [package.dependencies]
@ -5035,22 +4877,22 @@ celery = "*"
[[package]] [[package]]
name = "tornado" name = "tornado"
version = "6.4.2" version = "6.4.1"
description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "tornado-6.4.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e828cce1123e9e44ae2a50a9de3055497ab1d0aeb440c5ac23064d9e44880da1"}, {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:163b0aafc8e23d8cdc3c9dfb24c5368af84a81e3364745ccb4427669bf84aec8"},
{file = "tornado-6.4.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:072ce12ada169c5b00b7d92a99ba089447ccc993ea2143c9ede887e0937aa803"}, {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6d5ce3437e18a2b66fbadb183c1d3364fb03f2be71299e7d10dbeeb69f4b2a14"},
{file = "tornado-6.4.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a017d239bd1bb0919f72af256a970624241f070496635784d9bf0db640d3fec"}, {file = "tornado-6.4.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e20b9113cd7293f164dc46fffb13535266e713cdb87bd2d15ddb336e96cfc4"},
{file = "tornado-6.4.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c36e62ce8f63409301537222faffcef7dfc5284f27eec227389f2ad11b09d946"}, {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ae50a504a740365267b2a8d1a90c9fbc86b780a39170feca9bcc1787ff80842"},
{file = "tornado-6.4.2-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca9eb02196e789c9cb5c3c7c0f04fb447dc2adffd95265b2c7223a8a615ccbf"}, {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613bf4ddf5c7a95509218b149b555621497a6cc0d46ac341b30bd9ec19eac7f3"},
{file = "tornado-6.4.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:304463bd0772442ff4d0f5149c6f1c2135a1fae045adf070821c6cdc76980634"}, {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25486eb223babe3eed4b8aecbac33b37e3dd6d776bc730ca14e1bf93888b979f"},
{file = "tornado-6.4.2-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:c82c46813ba483a385ab2a99caeaedf92585a1f90defb5693351fa7e4ea0bf73"}, {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:454db8a7ecfcf2ff6042dde58404164d969b6f5d58b926da15e6b23817950fc4"},
{file = "tornado-6.4.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:932d195ca9015956fa502c6b56af9eb06106140d844a335590c1ec7f5277d10c"}, {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a02a08cc7a9314b006f653ce40483b9b3c12cda222d6a46d4ac63bb6c9057698"},
{file = "tornado-6.4.2-cp38-abi3-win32.whl", hash = "sha256:2876cef82e6c5978fde1e0d5b1f919d756968d5b4282418f3146b79b58556482"}, {file = "tornado-6.4.1-cp38-abi3-win32.whl", hash = "sha256:d9a566c40b89757c9aa8e6f032bcdb8ca8795d7c1a9762910c722b1635c9de4d"},
{file = "tornado-6.4.2-cp38-abi3-win_amd64.whl", hash = "sha256:908b71bf3ff37d81073356a5fadcc660eb10c1476ee6e2725588626ce7e5ca38"}, {file = "tornado-6.4.1-cp38-abi3-win_amd64.whl", hash = "sha256:b24b8982ed444378d7f21d563f4180a2de31ced9d8d84443907a0a64da2072e7"},
{file = "tornado-6.4.2.tar.gz", hash = "sha256:92bad5b4746e9879fd7bf1eb21dce4e3fc5128d71601f80005afa39237ad620b"}, {file = "tornado-6.4.1.tar.gz", hash = "sha256:92d3ab53183d8c50f8204a51e6f91d18a15d5ef261e84d452800d4ff6fc504e9"},
] ]
[[package]] [[package]]
@ -5158,21 +5000,6 @@ all = ["twisted (>=20.3.0)", "zope.interface (>=5.2.0)"]
dev = ["pep8 (>=1.6.2)", "pyenchant (>=1.6.6)", "pytest (>=2.6.4)", "pytest-cov (>=1.8.1)", "sphinx (>=1.2.3)", "sphinx-rtd-theme (>=0.1.9)", "sphinxcontrib-spelling (>=2.1.2)", "tox (>=2.1.1)", "tox-gh-actions (>=2.2.0)", "twine (>=1.6.5)", "wheel"] dev = ["pep8 (>=1.6.2)", "pyenchant (>=1.6.6)", "pytest (>=2.6.4)", "pytest-cov (>=1.8.1)", "sphinx (>=1.2.3)", "sphinx-rtd-theme (>=0.1.9)", "sphinxcontrib-spelling (>=2.1.2)", "tox (>=2.1.1)", "tox-gh-actions (>=2.2.0)", "twine (>=1.6.5)", "wheel"]
twisted = ["twisted (>=20.3.0)", "zope.interface (>=5.2.0)"] twisted = ["twisted (>=20.3.0)", "zope.interface (>=5.2.0)"]
[[package]]
name = "typeguard"
version = "2.13.3"
description = "Run-time type checker for Python"
optional = false
python-versions = ">=3.5.3"
files = [
{file = "typeguard-2.13.3-py3-none-any.whl", hash = "sha256:5e3e3be01e887e7eafae5af63d1f36c849aaa94e3a0112097312aabfa16284f1"},
{file = "typeguard-2.13.3.tar.gz", hash = "sha256:00edaa8da3a133674796cf5ea87d9f4b4c367d77476e185e80251cc13dfbb8c4"},
]
[package.extras]
doc = ["sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
test = ["mypy", "pytest", "typing-extensions"]
[[package]] [[package]]
name = "typing-extensions" name = "typing-extensions"
version = "4.12.2" version = "4.12.2"
@ -5197,36 +5024,15 @@ files = [
[[package]] [[package]]
name = "ua-parser" name = "ua-parser"
version = "1.0.0" version = "0.18.0"
description = "Python port of Browserscope's user agent parser" description = "Python port of Browserscope's user agent parser"
optional = false optional = false
python-versions = ">=3.9" python-versions = "*"
files = [ files = [
{file = "ua_parser-1.0.0-py3-none-any.whl", hash = "sha256:5b31133606a781f56692caa11a9671a9f330c22604b3c4957a7ba18c152212d0"}, {file = "ua-parser-0.18.0.tar.gz", hash = "sha256:db51f1b59bfaa82ed9e2a1d99a54d3e4153dddf99ac1435d51828165422e624e"},
{file = "ua_parser-1.0.0.tar.gz", hash = "sha256:a9740f53f4fbb72b7a03d304cae32a2785cafc55e8207efb74877bba17c35324"}, {file = "ua_parser-0.18.0-py2.py3-none-any.whl", hash = "sha256:9d94ac3a80bcb0166823956a779186c746b50ea4c9fd9bf30fdb758553c38950"},
] ]
[package.dependencies]
ua-parser-builtins = "*"
[package.extras]
re2 = ["google-re2"]
regex = ["ua-parser-rs"]
yaml = ["PyYaml"]
[[package]]
name = "ua-parser-builtins"
version = "0.18.0"
description = "Precompiled rules for User Agent Parser"
optional = false
python-versions = ">=3.9"
files = [
{file = "ua_parser_builtins-0.18.0-py3-none-any.whl", hash = "sha256:51cbc3d6ab9c533fc12fc7cededbef503c8d04e465d0aff20d869e15320b5ca9"},
]
[package.dependencies]
ua-parser = "*"
[[package]] [[package]]
name = "uritemplate" name = "uritemplate"
version = "4.1.1" version = "4.1.1"
@ -5936,4 +5742,4 @@ files = [
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "~3.12" python-versions = "~3.12"
content-hash = "b4eeab86e953e9c6d7e9336003e258668e43f58aeb360b15f56f8d4874fea5c3" content-hash = "2a7712ca513371eb16ff0b87fc80218e982a5a734bba0bb130b8123e807eb16f"

View File

@ -131,7 +131,7 @@ pydantic-scim = "*"
pyjwt = "*" pyjwt = "*"
pyrad = "*" pyrad = "*"
python = "~3.12" python = "~3.12"
python-kadmin-rs = "0.3.0" python-kadmin-rs = "0.2.0"
pyyaml = "*" pyyaml = "*"
requests-oauthlib = "*" requests-oauthlib = "*"
scim2-filter-parser = "*" scim2-filter-parser = "*"
@ -153,14 +153,12 @@ xmlsec = "*"
zxcvbn = "*" zxcvbn = "*"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
aws-cdk-lib = "*"
bandit = "*" bandit = "*"
black = "*" black = "*"
bump2version = "*" bump2version = "*"
channels = { version = "*", extras = ["daphne"] } channels = { version = "*", extras = ["daphne"] }
codespell = "*" codespell = "*"
colorama = "*" colorama = "*"
constructs = "*"
coverage = { extras = ["toml"], version = "*" } coverage = { extras = ["toml"], version = "*" }
debugpy = "*" debugpy = "*"
drf-jsonschema-serializer = "*" drf-jsonschema-serializer = "*"

View File

@ -44785,7 +44785,7 @@ components:
allOf: allOf:
- $ref: '#/components/schemas/IssuerModeEnum' - $ref: '#/components/schemas/IssuerModeEnum'
description: Configure how the issuer field of the ID Token should be filled. description: Configure how the issuer field of the ID Token should be filled.
jwt_federation_sources: jwks_sources:
type: array type: array
items: items:
type: string type: string
@ -44793,10 +44793,6 @@ components:
title: Any JWT signed by the JWK of the selected source can be used to title: Any JWT signed by the JWK of the selected source can be used to
authenticate. authenticate.
title: Any JWT signed by the JWK of the selected source can be used to authenticate. title: Any JWT signed by the JWK of the selected source can be used to authenticate.
jwt_federation_providers:
type: array
items:
type: integer
required: required:
- assigned_application_name - assigned_application_name
- assigned_application_slug - assigned_application_slug
@ -44892,7 +44888,7 @@ components:
allOf: allOf:
- $ref: '#/components/schemas/IssuerModeEnum' - $ref: '#/components/schemas/IssuerModeEnum'
description: Configure how the issuer field of the ID Token should be filled. description: Configure how the issuer field of the ID Token should be filled.
jwt_federation_sources: jwks_sources:
type: array type: array
items: items:
type: string type: string
@ -44900,10 +44896,6 @@ components:
title: Any JWT signed by the JWK of the selected source can be used to title: Any JWT signed by the JWK of the selected source can be used to
authenticate. authenticate.
title: Any JWT signed by the JWK of the selected source can be used to authenticate. title: Any JWT signed by the JWK of the selected source can be used to authenticate.
jwt_federation_providers:
type: array
items:
type: integer
required: required:
- authorization_flow - authorization_flow
- invalidation_flow - invalidation_flow
@ -48919,7 +48911,7 @@ components:
allOf: allOf:
- $ref: '#/components/schemas/IssuerModeEnum' - $ref: '#/components/schemas/IssuerModeEnum'
description: Configure how the issuer field of the ID Token should be filled. description: Configure how the issuer field of the ID Token should be filled.
jwt_federation_sources: jwks_sources:
type: array type: array
items: items:
type: string type: string
@ -48927,10 +48919,6 @@ components:
title: Any JWT signed by the JWK of the selected source can be used to title: Any JWT signed by the JWK of the selected source can be used to
authenticate. authenticate.
title: Any JWT signed by the JWK of the selected source can be used to authenticate. title: Any JWT signed by the JWK of the selected source can be used to authenticate.
jwt_federation_providers:
type: array
items:
type: integer
PatchedOAuthSourcePropertyMappingRequest: PatchedOAuthSourcePropertyMappingRequest:
type: object type: object
description: OAuthSourcePropertyMapping Serializer description: OAuthSourcePropertyMapping Serializer
@ -49446,7 +49434,7 @@ components:
header and authenticate requests based on its value. header and authenticate requests based on its value.
cookie_domain: cookie_domain:
type: string type: string
jwt_federation_sources: jwks_sources:
type: array type: array
items: items:
type: string type: string
@ -49454,10 +49442,6 @@ components:
title: Any JWT signed by the JWK of the selected source can be used to title: Any JWT signed by the JWK of the selected source can be used to
authenticate. authenticate.
title: Any JWT signed by the JWK of the selected source can be used to authenticate. title: Any JWT signed by the JWK of the selected source can be used to authenticate.
jwt_federation_providers:
type: array
items:
type: integer
access_token_validity: access_token_validity:
type: string type: string
minLength: 1 minLength: 1
@ -51520,7 +51504,7 @@ components:
readOnly: true readOnly: true
cookie_domain: cookie_domain:
type: string type: string
jwt_federation_sources: jwks_sources:
type: array type: array
items: items:
type: string type: string
@ -51528,10 +51512,6 @@ components:
title: Any JWT signed by the JWK of the selected source can be used to title: Any JWT signed by the JWK of the selected source can be used to
authenticate. authenticate.
title: Any JWT signed by the JWK of the selected source can be used to authenticate. title: Any JWT signed by the JWK of the selected source can be used to authenticate.
jwt_federation_providers:
type: array
items:
type: integer
access_token_validity: access_token_validity:
type: string type: string
description: 'Tokens not valid on or after current time + this value (Format: description: 'Tokens not valid on or after current time + this value (Format:
@ -51632,7 +51612,7 @@ components:
header and authenticate requests based on its value. header and authenticate requests based on its value.
cookie_domain: cookie_domain:
type: string type: string
jwt_federation_sources: jwks_sources:
type: array type: array
items: items:
type: string type: string
@ -51640,10 +51620,6 @@ components:
title: Any JWT signed by the JWK of the selected source can be used to title: Any JWT signed by the JWK of the selected source can be used to
authenticate. authenticate.
title: Any JWT signed by the JWK of the selected source can be used to authenticate. title: Any JWT signed by the JWK of the selected source can be used to authenticate.
jwt_federation_providers:
type: array
items:
type: integer
access_token_validity: access_token_validity:
type: string type: string
minLength: 1 minLength: 1

View File

@ -4,4 +4,4 @@ from time import time
from authentik import __version__ from authentik import __version__
print(f"{__version__}-{int(time())}") print("%s-%d" % (__version__, time()))

View File

@ -3,7 +3,6 @@
from json import loads from json import loads
from pathlib import Path from pathlib import Path
from time import sleep from time import sleep
from unittest import skip
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
@ -124,7 +123,6 @@ class TestProviderProxyForward(SeleniumTestCase):
title = session_end_stage.find_element(By.CSS_SELECTOR, ".pf-c-title.pf-m-3xl").text title = session_end_stage.find_element(By.CSS_SELECTOR, ".pf-c-title.pf-m-3xl").text
self.assertIn("You've logged out of", title) self.assertIn("You've logged out of", title)
@skip("Flaky test")
@retry() @retry()
def test_nginx(self): def test_nginx(self):
"""Test nginx""" """Test nginx"""

16
web/package-lock.json generated
View File

@ -23,7 +23,7 @@
"@floating-ui/dom": "^1.6.11", "@floating-ui/dom": "^1.6.11",
"@formatjs/intl-listformat": "^7.5.7", "@formatjs/intl-listformat": "^7.5.7",
"@fortawesome/fontawesome-free": "^6.6.0", "@fortawesome/fontawesome-free": "^6.6.0",
"@goauthentik/api": "^2024.10.4-1733219849", "@goauthentik/api": "^2024.10.4-1732236707",
"@lit-labs/ssr": "^3.2.2", "@lit-labs/ssr": "^3.2.2",
"@lit/context": "^1.1.2", "@lit/context": "^1.1.2",
"@lit/localize": "^0.12.2", "@lit/localize": "^0.12.2",
@ -84,7 +84,7 @@
"@wdio/cli": "^9.1.2", "@wdio/cli": "^9.1.2",
"@wdio/spec-reporter": "^9.1.2", "@wdio/spec-reporter": "^9.1.2",
"chokidar": "^4.0.1", "chokidar": "^4.0.1",
"chromedriver": "^131.0.1", "chromedriver": "^130.0.4",
"esbuild": "^0.24.0", "esbuild": "^0.24.0",
"eslint": "^9.11.1", "eslint": "^9.11.1",
"eslint-plugin-lit": "^1.15.0", "eslint-plugin-lit": "^1.15.0",
@ -1775,9 +1775,9 @@
} }
}, },
"node_modules/@goauthentik/api": { "node_modules/@goauthentik/api": {
"version": "2024.10.4-1733219849", "version": "2024.10.4-1732236707",
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2024.10.4-1733219849.tgz", "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2024.10.4-1732236707.tgz",
"integrity": "sha512-Bls5D7PjtOytn2NqWUQEzKxeBql+kwwOWvBR+Dvo9fj7j6J5Sj5O/cIwGlKqN9q6p3ynSarstPv5YlLvYvPOGw==" "integrity": "sha512-pzqUpv1Xaf7OpPLB6ZkzGPfA6PAN8xGCkrPWfMZMi04XyHzZj4pVgdeb0d/G0XXEZm1z4OzqEJWEm3w8wdhfVw=="
}, },
"node_modules/@goauthentik/web": { "node_modules/@goauthentik/web": {
"resolved": "", "resolved": "",
@ -8699,9 +8699,9 @@
} }
}, },
"node_modules/chromedriver": { "node_modules/chromedriver": {
"version": "131.0.1", "version": "130.0.4",
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-131.0.1.tgz", "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-130.0.4.tgz",
"integrity": "sha512-LHRh+oaNU1WowJjAkWsviN8pTzQYJDbv/FvJyrQ7XhjKdIzVh/s3GV1iU7IjMTsxIQnBsTjx+9jWjzCWIXC7ug==", "integrity": "sha512-lpR+PWXszij1k4Ig3t338Zvll9HtCTiwoLM7n4pCCswALHxzmgwaaIFBh3rt9+5wRk9D07oFblrazrBxwaYYAQ==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {

View File

@ -11,7 +11,7 @@
"@floating-ui/dom": "^1.6.11", "@floating-ui/dom": "^1.6.11",
"@formatjs/intl-listformat": "^7.5.7", "@formatjs/intl-listformat": "^7.5.7",
"@fortawesome/fontawesome-free": "^6.6.0", "@fortawesome/fontawesome-free": "^6.6.0",
"@goauthentik/api": "^2024.10.4-1733219849", "@goauthentik/api": "^2024.10.4-1732236707",
"@lit-labs/ssr": "^3.2.2", "@lit-labs/ssr": "^3.2.2",
"@lit/context": "^1.1.2", "@lit/context": "^1.1.2",
"@lit/localize": "^0.12.2", "@lit/localize": "^0.12.2",
@ -72,7 +72,7 @@
"@wdio/cli": "^9.1.2", "@wdio/cli": "^9.1.2",
"@wdio/spec-reporter": "^9.1.2", "@wdio/spec-reporter": "^9.1.2",
"chokidar": "^4.0.1", "chokidar": "^4.0.1",
"chromedriver": "^131.0.1", "chromedriver": "^130.0.4",
"esbuild": "^0.24.0", "esbuild": "^0.24.0",
"eslint": "^9.11.1", "eslint": "^9.11.1",
"eslint-plugin-lit": "^1.15.0", "eslint-plugin-lit": "^1.15.0",

View File

@ -20,9 +20,6 @@ interface GlobalAuthentik {
brand: { brand: {
branding_logo: string; branding_logo: string;
}; };
api: {
base: string;
};
} }
function ak(): GlobalAuthentik { function ak(): GlobalAuthentik {
@ -44,7 +41,7 @@ class SimpleFlowExecutor {
} }
get apiURL() { get apiURL() {
return `${ak().api.base}api/v3/flows/executor/${this.flowSlug}/?query=${encodeURIComponent(window.location.search.substring(1))}`; return `/api/v3/flows/executor/${this.flowSlug}/?query=${encodeURIComponent(window.location.search.substring(1))}`;
} }
start() { start() {

View File

@ -1,6 +1,5 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EVENT_SIDEBAR_TOGGLE, VERSION } from "@goauthentik/common/constants"; import { EVENT_SIDEBAR_TOGGLE, VERSION } from "@goauthentik/common/constants";
import { globalAK } from "@goauthentik/common/global";
import { me } from "@goauthentik/common/users"; import { me } from "@goauthentik/common/users";
import { AKElement } from "@goauthentik/elements/Base"; import { AKElement } from "@goauthentik/elements/Base";
import { import {
@ -113,7 +112,7 @@ export class AkAdminSidebar extends WithCapabilitiesConfig(AKElement) {
// prettier-ignore // prettier-ignore
const sidebarContent: SidebarEntry[] = [ const sidebarContent: SidebarEntry[] = [
[`${globalAK().api.base}if/user/`, msg("User interface"), { "?isAbsoluteLink": true, "?highlight": true }], ["/if/user/", msg("User interface"), { "?isAbsoluteLink": true, "?highlight": true }],
[null, msg("Dashboards"), { "?expanded": true }, [ [null, msg("Dashboards"), { "?expanded": true }, [
["/administration/overview", msg("Overview")], ["/administration/overview", msg("Overview")],
["/administration/dashboard/users", msg("User Statistics")], ["/administration/dashboard/users", msg("User Statistics")],

View File

@ -1,23 +1,23 @@
import "@goauthentik/admin/applications/wizard/ak-wizard-title"; import "@goauthentik/admin/applications/wizard/ak-wizard-title";
import "@goauthentik/admin/common/ak-crypto-certificate-search"; import "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search"; import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
import {
makeOAuth2PropertyMappingsSelector,
oauth2PropertyMappingsProvider,
} from "@goauthentik/admin/providers/oauth2/OAuth2PropertyMappings.js";
import { import {
clientTypeOptions, clientTypeOptions,
issuerModeOptions, issuerModeOptions,
redirectUriHelp, redirectUriHelp,
subjectModeOptions, subjectModeOptions,
} from "@goauthentik/admin/providers/oauth2/OAuth2ProviderForm"; } from "@goauthentik/admin/providers/oauth2/OAuth2ProviderForm";
import {
propertyMappingsProvider,
propertyMappingsSelector,
} from "@goauthentik/admin/providers/oauth2/OAuth2ProviderFormHelpers.js";
import { import {
IRedirectURIInput, IRedirectURIInput,
akOAuthRedirectURIInput, akOAuthRedirectURIInput,
} from "@goauthentik/admin/providers/oauth2/OAuth2ProviderRedirectURI"; } from "@goauthentik/admin/providers/oauth2/OAuth2ProviderRedirectURI";
import { import {
makeSourceSelector,
oauth2SourcesProvider, oauth2SourcesProvider,
oauth2SourcesSelector,
} from "@goauthentik/admin/providers/oauth2/OAuth2Sources.js"; } from "@goauthentik/admin/providers/oauth2/OAuth2Sources.js";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { ascii_letters, digits, first, randomString } from "@goauthentik/common/utils"; import { ascii_letters, digits, first, randomString } from "@goauthentik/common/utils";
@ -252,8 +252,10 @@ export class ApplicationWizardAuthenticationByOauth extends BaseProviderPanel {
.errorMessages=${errors?.propertyMappings ?? []} .errorMessages=${errors?.propertyMappings ?? []}
> >
<ak-dual-select-dynamic-selected <ak-dual-select-dynamic-selected
.provider=${propertyMappingsProvider} .provider=${oauth2PropertyMappingsProvider}
.selector=${propertyMappingsSelector(provider?.propertyMappings)} .selector=${makeOAuth2PropertyMappingsSelector(
provider?.propertyMappings,
)}
available-label=${msg("Available Scopes")} available-label=${msg("Available Scopes")}
selected-label=${msg("Selected Scopes")} selected-label=${msg("Selected Scopes")}
></ak-dual-select-dynamic-selected> ></ak-dual-select-dynamic-selected>
@ -307,7 +309,7 @@ export class ApplicationWizardAuthenticationByOauth extends BaseProviderPanel {
> >
<ak-dual-select-dynamic-selected <ak-dual-select-dynamic-selected
.provider=${oauth2SourcesProvider} .provider=${oauth2SourcesProvider}
.selector=${oauth2SourcesSelector(provider?.jwtFederationSources)} .selector=${makeSourceSelector(provider?.jwksSources)}
available-label=${msg("Available Sources")} available-label=${msg("Available Sources")}
selected-label=${msg("Selected Sources")} selected-label=${msg("Selected Sources")}
></ak-dual-select-dynamic-selected> ></ak-dual-select-dynamic-selected>

View File

@ -1,12 +1,12 @@
import "@goauthentik/admin/applications/wizard/ak-wizard-title"; import "@goauthentik/admin/applications/wizard/ak-wizard-title";
import { import {
makeSourceSelector,
oauth2SourcesProvider, oauth2SourcesProvider,
oauth2SourcesSelector,
} from "@goauthentik/admin/providers/oauth2/OAuth2Sources.js"; } from "@goauthentik/admin/providers/oauth2/OAuth2Sources.js";
import { import {
propertyMappingsProvider, makeProxyPropertyMappingsSelector,
propertyMappingsSelector, proxyPropertyMappingsProvider,
} from "@goauthentik/admin/providers/proxy/ProxyProviderFormHelpers.js"; } from "@goauthentik/admin/providers/proxy/ProxyProviderPropertyMappings.js";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils"; import { first } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-switch-input"; import "@goauthentik/components/ak-switch-input";
@ -147,8 +147,8 @@ export class AkTypeProxyApplicationWizardPage extends BaseProviderPanel {
name="propertyMappings" name="propertyMappings"
> >
<ak-dual-select-dynamic-selected <ak-dual-select-dynamic-selected
.provider=${propertyMappingsProvider} .provider=${proxyPropertyMappingsProvider}
.selector=${propertyMappingsSelector( .selector=${makeProxyPropertyMappingsSelector(
this.instance?.propertyMappings, this.instance?.propertyMappings,
)} )}
available-label="${msg("Available Scopes")}" available-label="${msg("Available Scopes")}"
@ -248,9 +248,7 @@ export class AkTypeProxyApplicationWizardPage extends BaseProviderPanel {
> >
<ak-dual-select-dynamic-selected <ak-dual-select-dynamic-selected
.provider=${oauth2SourcesProvider} .provider=${oauth2SourcesProvider}
.selector=${oauth2SourcesSelector( .selector=${makeSourceSelector(this.instance?.jwksSources)}
this.instance?.jwtFederationSources,
)}
available-label=${msg("Available Sources")} available-label=${msg("Available Sources")}
selected-label=${msg("Selected Sources")} selected-label=${msg("Selected Sources")}
></ak-dual-select-dynamic-selected> ></ak-dual-select-dynamic-selected>

View File

@ -1,9 +1,9 @@
import "@goauthentik/admin/applications/wizard/ak-wizard-title"; import "@goauthentik/admin/applications/wizard/ak-wizard-title";
import "@goauthentik/admin/common/ak-flow-search/ak-flow-search"; import "@goauthentik/admin/common/ak-flow-search/ak-flow-search";
import { import {
propertyMappingsProvider, makeRACPropertyMappingsSelector,
propertyMappingsSelector, racPropertyMappingsProvider,
} from "@goauthentik/admin/providers/rac/RACProviderFormHelpers.js"; } from "@goauthentik/admin/providers/rac/RACPropertyMappings.js";
import "@goauthentik/components/ak-text-input"; import "@goauthentik/components/ak-text-input";
import "@goauthentik/elements/CodeMirror"; import "@goauthentik/elements/CodeMirror";
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js"; import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
@ -70,8 +70,10 @@ export class ApplicationWizardAuthenticationByRAC extends BaseProviderPanel {
name="propertyMappings" name="propertyMappings"
> >
<ak-dual-select-dynamic-selected <ak-dual-select-dynamic-selected
.provider=${propertyMappingsProvider} .provider=${racPropertyMappingsProvider}
.selector=${propertyMappingsSelector(provider?.propertyMappings)} .selector=${makeRACPropertyMappingsSelector(
provider?.propertyMappings,
)}
available-label="${msg("Available Property Mappings")}" available-label="${msg("Available Property Mappings")}"
selected-label="${msg("Selected Property Mappings")}" selected-label="${msg("Selected Property Mappings")}"
></ak-dual-select-dynamic-selected> ></ak-dual-select-dynamic-selected>

View File

@ -1,6 +1,7 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { severityToLabel } from "@goauthentik/common/labels"; import { severityToLabel } from "@goauthentik/common/labels";
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js"; import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import "@goauthentik/elements/forms/Radio"; import "@goauthentik/elements/forms/Radio";
@ -17,12 +18,32 @@ import {
EventsApi, EventsApi,
Group, Group,
NotificationRule, NotificationRule,
NotificationTransport,
PaginatedNotificationTransportList, PaginatedNotificationTransportList,
SeverityEnum, SeverityEnum,
} from "@goauthentik/api"; } from "@goauthentik/api";
import { eventTransportsProvider, eventTransportsSelector } from "./RuleFormHelpers.js"; async function eventTransportsProvider(page = 1, search = "") {
const eventTransports = await new EventsApi(DEFAULT_CONFIG).eventsTransportsList({
ordering: "name",
pageSize: 20,
search: search.trim(),
page,
});
return {
pagination: eventTransports.pagination,
options: eventTransports.results.map((transport) => [transport.pk, transport.name]),
};
}
export function makeTransportSelector(instanceTransports: string[] | undefined) {
const localTransports = instanceTransports ? new Set(instanceTransports) : undefined;
return localTransports
? ([pk, _]: DualSelectPair) => localTransports.has(pk)
: ([_0, _1, _2, stage]: DualSelectPair<NotificationTransport>) => stage !== undefined;
}
@customElement("ak-event-rule-form") @customElement("ak-event-rule-form")
export class RuleForm extends ModelForm<NotificationRule, string> { export class RuleForm extends ModelForm<NotificationRule, string> {
eventTransports?: PaginatedNotificationTransportList; eventTransports?: PaginatedNotificationTransportList;
@ -105,7 +126,7 @@ export class RuleForm extends ModelForm<NotificationRule, string> {
> >
<ak-dual-select-dynamic-selected <ak-dual-select-dynamic-selected
.provider=${eventTransportsProvider} .provider=${eventTransportsProvider}
.selector=${eventTransportsSelector(this.instance?.transports)} .selector=${makeTransportSelector(this.instance?.transports)}
available-label="${msg("Available Transports")}" available-label="${msg("Available Transports")}"
selected-label="${msg("Selected Transports")}" selected-label="${msg("Selected Transports")}"
></ak-dual-select-dynamic-selected> ></ak-dual-select-dynamic-selected>

View File

@ -1,42 +0,0 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types";
import { EventsApi, NotificationTransport } from "@goauthentik/api";
const transportToSelect = (transport: NotificationTransport) => [transport.pk, transport.name];
export async function eventTransportsProvider(page = 1, search = "") {
const eventTransports = await new EventsApi(DEFAULT_CONFIG).eventsTransportsList({
ordering: "name",
pageSize: 20,
search: search.trim(),
page,
});
return {
pagination: eventTransports.pagination,
options: eventTransports.results.map(transportToSelect),
};
}
export function eventTransportsSelector(instanceTransports: string[] | undefined) {
if (!instanceTransports) {
return async (transports: DualSelectPair<NotificationTransport>[]) =>
transports.filter(
([_0, _1, _2, stage]: DualSelectPair<NotificationTransport>) => stage !== undefined,
);
}
return async () => {
const transportsApi = new EventsApi(DEFAULT_CONFIG);
const transports = await Promise.allSettled(
instanceTransports.map((instanceId) =>
transportsApi.eventsTransportsRetrieve({ uuid: instanceId }),
),
);
return transports
.filter((s) => s.status === "fulfilled")
.map((s) => s.value)
.map(transportToSelect);
};
}

View File

@ -1,8 +1,8 @@
import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm"; import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm";
import { import {
propertyMappingsProvider, googleWorkspacePropertyMappingsProvider,
propertyMappingsSelector, makeGoogleWorkspacePropertyMappingsSelector,
} from "@goauthentik/admin/providers/google_workspace/GoogleWorkspaceProviderFormHelpers.js"; } from "@goauthentik/admin/providers/google_workspace/GoogleWorkspaceProviderPropertyMappings";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils"; import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/CodeMirror"; import "@goauthentik/elements/CodeMirror";
@ -224,8 +224,8 @@ export class GoogleWorkspaceProviderFormPage extends BaseProviderForm<GoogleWork
name="propertyMappings" name="propertyMappings"
> >
<ak-dual-select-dynamic-selected <ak-dual-select-dynamic-selected
.provider=${propertyMappingsProvider} .provider=${googleWorkspacePropertyMappingsProvider}
.selector=${propertyMappingsSelector( .selector=${makeGoogleWorkspacePropertyMappingsSelector(
this.instance?.propertyMappings, this.instance?.propertyMappings,
"goauthentik.io/providers/google_workspace/user", "goauthentik.io/providers/google_workspace/user",
)} )}
@ -241,8 +241,8 @@ export class GoogleWorkspaceProviderFormPage extends BaseProviderForm<GoogleWork
name="propertyMappingsGroup" name="propertyMappingsGroup"
> >
<ak-dual-select-dynamic-selected <ak-dual-select-dynamic-selected
.provider=${propertyMappingsProvider} .provider=${googleWorkspacePropertyMappingsProvider}
.selector=${propertyMappingsSelector( .selector=${makeGoogleWorkspacePropertyMappingsSelector(
this.instance?.propertyMappingsGroup, this.instance?.propertyMappingsGroup,
"goauthentik.io/providers/google_workspace/group", "goauthentik.io/providers/google_workspace/group",
)} )}

View File

@ -1,48 +0,0 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types.js";
import { GoogleWorkspaceProviderMapping, PropertymappingsApi } from "@goauthentik/api";
const mappingToSelect = (m: GoogleWorkspaceProviderMapping) => [m.pk, m.name, m.name, m];
export async function propertyMappingsProvider(page = 1, search = "") {
const propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsProviderGoogleWorkspaceList({
ordering: "managed",
pageSize: 20,
search: search.trim(),
page,
});
return {
pagination: propertyMappings.pagination,
options: propertyMappings.results.map(mappingToSelect),
};
}
export function propertyMappingsSelector(
instanceMappings: string[] | undefined,
defaultSelection: string,
) {
if (!instanceMappings) {
return async (mappings: DualSelectPair<GoogleWorkspaceProviderMapping>[]) =>
mappings.filter(
([_0, _1, _2, mapping]: DualSelectPair<GoogleWorkspaceProviderMapping>) =>
mapping?.managed === defaultSelection,
);
}
return async () => {
const pm = new PropertymappingsApi(DEFAULT_CONFIG);
const mappings = await Promise.allSettled(
instanceMappings.map((instanceId) =>
pm.propertymappingsProviderGoogleWorkspaceRetrieve({ pmUuid: instanceId }),
),
);
return mappings
.filter((s) => s.status === "fulfilled")
.map((s) => s.value)
.map(mappingToSelect);
};
}

View File

@ -0,0 +1,30 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types.js";
import { PropertymappingsApi, ScopeMapping } from "@goauthentik/api";
export async function googleWorkspacePropertyMappingsProvider(page = 1, search = "") {
const propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsProviderGoogleWorkspaceList({
ordering: "managed",
pageSize: 20,
search: search.trim(),
page,
});
return {
pagination: propertyMappings.pagination,
options: propertyMappings.results.map((scope) => [scope.pk, scope.name, scope.name, scope]),
};
}
export function makeGoogleWorkspacePropertyMappingsSelector(
instanceMappings: string[] | undefined,
defaultSelection: string,
) {
const localMappings = instanceMappings ? new Set(instanceMappings) : undefined;
return localMappings
? ([pk, _]: DualSelectPair) => localMappings.has(pk)
: ([_0, _1, _2, scope]: DualSelectPair<ScopeMapping>) =>
scope?.managed === defaultSelection;
}

View File

@ -1,46 +0,0 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types.js";
import { LDAPSourcePropertyMapping, PropertymappingsApi } from "@goauthentik/api";
const mappingToSelect = (m: LDAPSourcePropertyMapping) => [m.pk, m.name, m.name, m];
export async function propertyMappingsProvider(page = 1, search = "") {
const propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsSourceLdapList({
ordering: "managed",
pageSize: 20,
search: search.trim(),
page,
});
return {
pagination: propertyMappings.pagination,
options: propertyMappings.results.map(mappingToSelect),
};
}
export function propertyMappingsSelector(instanceMappings?: string[]) {
if (!instanceMappings) {
return async (transports: DualSelectPair<LDAPSourcePropertyMapping>[]) =>
transports.filter(
([_0, _1, _2, mapping]: DualSelectPair<LDAPSourcePropertyMapping>) =>
mapping?.managed?.startsWith("goauthentik.io/sources/ldap/default") ||
mapping?.managed?.startsWith("goauthentik.io/sources/ldap/ms"),
);
}
return async () => {
const pm = new PropertymappingsApi(DEFAULT_CONFIG);
const mappings = await Promise.allSettled(
instanceMappings.map((instanceId) =>
pm.propertymappingsSourceLdapRetrieve({ pmUuid: instanceId }),
),
);
return mappings
.filter((s) => s.status === "fulfilled")
.map((s) => s.value)
.map(mappingToSelect);
};
}

View File

@ -1,8 +1,8 @@
import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm"; import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm";
import { import {
propertyMappingsProvider, makeMicrosoftEntraPropertyMappingsSelector,
propertyMappingsSelector, microsoftEntraPropertyMappingsProvider,
} from "@goauthentik/admin/providers/microsoft_entra/MicrosoftEntraProviderFormHelpers.js"; } from "@goauthentik/admin/providers/microsoft_entra/MicrosoftEntraProviderPropertyMappings";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils"; import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js"; import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
@ -213,8 +213,8 @@ export class MicrosoftEntraProviderFormPage extends BaseProviderForm<MicrosoftEn
name="propertyMappings" name="propertyMappings"
> >
<ak-dual-select-dynamic-selected <ak-dual-select-dynamic-selected
.provider=${propertyMappingsProvider} .provider=${microsoftEntraPropertyMappingsProvider}
.selector=${propertyMappingsSelector( .selector=${makeMicrosoftEntraPropertyMappingsSelector(
this.instance?.propertyMappings, this.instance?.propertyMappings,
"goauthentik.io/providers/microsoft_entra/user", "goauthentik.io/providers/microsoft_entra/user",
)} )}
@ -230,8 +230,8 @@ export class MicrosoftEntraProviderFormPage extends BaseProviderForm<MicrosoftEn
name="propertyMappingsGroup" name="propertyMappingsGroup"
> >
<ak-dual-select-dynamic-selected <ak-dual-select-dynamic-selected
.provider=${propertyMappingsProvider} .provider=${microsoftEntraPropertyMappingsProvider}
.selector=${propertyMappingsSelector( .selector=${makeMicrosoftEntraPropertyMappingsSelector(
this.instance?.propertyMappingsGroup, this.instance?.propertyMappingsGroup,
"goauthentik.io/providers/microsoft_entra/group", "goauthentik.io/providers/microsoft_entra/group",
)} )}

View File

@ -1,48 +0,0 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types.js";
import { MicrosoftEntraProviderMapping, PropertymappingsApi } from "@goauthentik/api";
const mappingToSelect = (m: MicrosoftEntraProviderMapping) => [m.pk, m.name, m.name, m];
export async function propertyMappingsProvider(page = 1, search = "") {
const propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsProviderMicrosoftEntraList({
ordering: "managed",
pageSize: 20,
search: search.trim(),
page,
});
return {
pagination: propertyMappings.pagination,
options: propertyMappings.results.map(mappingToSelect),
};
}
export function propertyMappingsSelector(
instanceMappings: string[] | undefined,
defaultSelection: string,
) {
if (!instanceMappings) {
return async (mappings: DualSelectPair<MicrosoftEntraProviderMapping>[]) =>
mappings.filter(
([_0, _1, _2, mapping]: DualSelectPair<MicrosoftEntraProviderMapping>) =>
mapping?.managed === defaultSelection,
);
}
return async () => {
const pm = new PropertymappingsApi(DEFAULT_CONFIG);
const mappings = await Promise.allSettled(
instanceMappings.map((instanceId) =>
pm.propertymappingsProviderMicrosoftEntraRetrieve({ pmUuid: instanceId }),
),
);
return mappings
.filter((s) => s.status === "fulfilled")
.map((s) => s.value)
.map(mappingToSelect);
};
}

View File

@ -0,0 +1,33 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types.js";
import { PropertymappingsApi, ScopeMapping } from "@goauthentik/api";
export const defaultScopes = [
"goauthentik.io/providers/oauth2/scope-openid",
"goauthentik.io/providers/oauth2/scope-email",
"goauthentik.io/providers/oauth2/scope-profile",
];
export async function oauth2PropertyMappingsProvider(page = 1, search = "") {
const propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsProviderScopeList({
ordering: "scope_name",
pageSize: 20,
search: search.trim(),
page,
});
return {
pagination: propertyMappings.pagination,
options: propertyMappings.results.map((scope) => [scope.pk, scope.name, scope.name, scope]),
};
}
export function makeOAuth2PropertyMappingsSelector(instanceMappings: string[] | undefined) {
const localMappings = instanceMappings ? new Set(instanceMappings) : undefined;
return localMappings
? ([pk, _]: DualSelectPair) => localMappings.has(pk)
: ([_0, _1, _2, scope]: DualSelectPair<ScopeMapping>) =>
scope?.managed && defaultScopes.includes(scope?.managed);
}

View File

@ -13,7 +13,6 @@ import "@goauthentik/components/ak-textarea-input";
import "@goauthentik/elements/ak-array-input.js"; import "@goauthentik/elements/ak-array-input.js";
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js"; import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
import "@goauthentik/elements/ak-dual-select/ak-dual-select-provider.js"; import "@goauthentik/elements/ak-dual-select/ak-dual-select-provider.js";
import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types";
import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/Radio"; import "@goauthentik/elements/forms/Radio";
@ -36,8 +35,11 @@ import {
SubModeEnum, SubModeEnum,
} from "@goauthentik/api"; } from "@goauthentik/api";
import { propertyMappingsProvider, propertyMappingsSelector } from "./OAuth2ProviderFormHelpers.js"; import {
import { oauth2SourcesProvider, oauth2SourcesSelector } from "./OAuth2Sources.js"; makeOAuth2PropertyMappingsSelector,
oauth2PropertyMappingsProvider,
} from "./OAuth2PropertyMappings.js";
import { makeSourceSelector, oauth2SourcesProvider } from "./OAuth2Sources.js";
export const clientTypeOptions = [ export const clientTypeOptions = [
{ {
@ -117,45 +119,6 @@ export const redirectUriHelp = html`${redirectUriHelpMessages.map(
(m) => html`<p class="pf-c-form__helper-text">${m}</p>`, (m) => html`<p class="pf-c-form__helper-text">${m}</p>`,
)}`; )}`;
const providerToSelect = (provider: OAuth2Provider) => [provider.pk, provider.name];
export async function oauth2ProvidersProvider(page = 1, search = "") {
const oauthProviders = await new ProvidersApi(DEFAULT_CONFIG).providersOauth2List({
ordering: "name",
pageSize: 20,
search: search.trim(),
page,
});
return {
pagination: oauthProviders.pagination,
options: oauthProviders.results.map((provider) => providerToSelect(provider)),
};
}
export function oauth2ProviderSelector(instanceProviders: number[] | undefined) {
if (!instanceProviders) {
return async (mappings: DualSelectPair<OAuth2Provider>[]) =>
mappings.filter(
([_0, _1, _2, source]: DualSelectPair<OAuth2Provider>) => source !== undefined,
);
}
return async () => {
const oauthSources = new ProvidersApi(DEFAULT_CONFIG);
const mappings = await Promise.allSettled(
instanceProviders.map((instanceId) =>
oauthSources.providersOauth2Retrieve({ id: instanceId }),
),
);
return mappings
.filter((s) => s.status === "fulfilled")
.map((s) => s.value)
.map(providerToSelect);
};
}
/** /**
* Form page for OAuth2 Authentication Method * Form page for OAuth2 Authentication Method
* *
@ -372,8 +335,10 @@ export class OAuth2ProviderFormPage extends BaseProviderForm<OAuth2Provider> {
</ak-text-input> </ak-text-input>
<ak-form-element-horizontal label=${msg("Scopes")} name="propertyMappings"> <ak-form-element-horizontal label=${msg("Scopes")} name="propertyMappings">
<ak-dual-select-dynamic-selected <ak-dual-select-dynamic-selected
.provider=${propertyMappingsProvider} .provider=${oauth2PropertyMappingsProvider}
.selector=${propertyMappingsSelector(provider?.propertyMappings)} .selector=${makeOAuth2PropertyMappingsSelector(
provider?.propertyMappings,
)}
available-label=${msg("Available Scopes")} available-label=${msg("Available Scopes")}
selected-label=${msg("Selected Scopes")} selected-label=${msg("Selected Scopes")}
></ak-dual-select-dynamic-selected> ></ak-dual-select-dynamic-selected>
@ -421,12 +386,12 @@ export class OAuth2ProviderFormPage extends BaseProviderForm<OAuth2Provider> {
<span slot="header">${msg("Machine-to-Machine authentication settings")}</span> <span slot="header">${msg("Machine-to-Machine authentication settings")}</span>
<div slot="body" class="pf-c-form"> <div slot="body" class="pf-c-form">
<ak-form-element-horizontal <ak-form-element-horizontal
label=${msg("Federated OIDC Sources")} label=${msg("Trusted OIDC Sources")}
name="jwtFederationSources" name="jwksSources"
> >
<ak-dual-select-dynamic-selected <ak-dual-select-dynamic-selected
.provider=${oauth2SourcesProvider} .provider=${oauth2SourcesProvider}
.selector=${oauth2SourcesSelector(provider?.jwtFederationSources)} .selector=${makeSourceSelector(provider?.jwksSources)}
available-label=${msg("Available Sources")} available-label=${msg("Available Sources")}
selected-label=${msg("Selected Sources")} selected-label=${msg("Selected Sources")}
></ak-dual-select-dynamic-selected> ></ak-dual-select-dynamic-selected>
@ -436,22 +401,6 @@ export class OAuth2ProviderFormPage extends BaseProviderForm<OAuth2Provider> {
)} )}
</p> </p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Federated OIDC Providers")}
name="jwtFederationProviders"
>
<ak-dual-select-dynamic-selected
.provider=${oauth2ProvidersProvider}
.selector=${oauth2ProviderSelector(provider?.jwtFederationProviders)}
available-label=${msg("Available Providers")}
selected-label=${msg("Selected Providers")}
></ak-dual-select-dynamic-selected>
<p class="pf-c-form__helper-text">
${msg(
"JWTs signed by the selected providers can be used to authenticate to this provider.",
)}
</p>
</ak-form-element-horizontal>
</div> </div>
</ak-form-group>`; </ak-form-group>`;
} }

View File

@ -1,51 +0,0 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types.js";
import { PropertymappingsApi, ScopeMapping } from "@goauthentik/api";
export const defaultScopes = [
"goauthentik.io/providers/oauth2/scope-openid",
"goauthentik.io/providers/oauth2/scope-email",
"goauthentik.io/providers/oauth2/scope-profile",
];
const mappingToSelect = (s: ScopeMapping) => [s.pk, s.name, s.name, s];
export async function propertyMappingsProvider(page = 1, search = "") {
const propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsProviderScopeList({
ordering: "scope_name",
pageSize: 20,
search: search.trim(),
page,
});
return {
pagination: propertyMappings.pagination,
options: propertyMappings.results.map(mappingToSelect),
};
}
export function propertyMappingsSelector(instanceMappings?: string[]) {
if (!instanceMappings) {
return async (mappings: DualSelectPair<ScopeMapping>[]) =>
mappings.filter(
([_0, _1, _2, scope]: DualSelectPair<ScopeMapping>) =>
scope?.managed && defaultScopes.includes(scope?.managed),
);
}
return async () => {
const pm = new PropertymappingsApi(DEFAULT_CONFIG);
const mappings = await Promise.allSettled(
instanceMappings.map((instanceId) =>
pm.propertymappingsProviderScopeRetrieve({ pmUuid: instanceId }),
),
);
return mappings
.filter((s) => s.status === "fulfilled")
.map((s) => s.value)
.map(mappingToSelect);
};
}

View File

@ -3,13 +3,6 @@ import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types";
import { OAuthSource, SourcesApi } from "@goauthentik/api"; import { OAuthSource, SourcesApi } from "@goauthentik/api";
const sourceToSelect = (source: OAuthSource) => [
source.pk,
`${source.name} (${source.slug})`,
source.name,
source,
];
export async function oauth2SourcesProvider(page = 1, search = "") { export async function oauth2SourcesProvider(page = 1, search = "") {
const oauthSources = await new SourcesApi(DEFAULT_CONFIG).sourcesOauthList({ const oauthSources = await new SourcesApi(DEFAULT_CONFIG).sourcesOauthList({
ordering: "name", ordering: "name",
@ -21,29 +14,17 @@ export async function oauth2SourcesProvider(page = 1, search = "") {
return { return {
pagination: oauthSources.pagination, pagination: oauthSources.pagination,
options: oauthSources.results.map(sourceToSelect), options: oauthSources.results.map((source) => [
source.pk,
`${source.name} (${source.slug})`,
]),
}; };
} }
export function oauth2SourcesSelector(instanceMappings?: string[]) { export function makeSourceSelector(instanceSources: string[] | undefined) {
if (!instanceMappings) { const localSources = instanceSources ? new Set(instanceSources) : undefined;
return async (mappings: DualSelectPair<OAuthSource>[]) =>
mappings.filter(
([_0, _1, _2, source]: DualSelectPair<OAuthSource>) => source !== undefined,
);
}
return async () => { return localSources
const oauthSources = new SourcesApi(DEFAULT_CONFIG); ? ([pk, _]: DualSelectPair) => localSources.has(pk)
const mappings = await Promise.allSettled( : ([_0, _1, _2, prompt]: DualSelectPair<OAuthSource>) => prompt !== undefined;
instanceMappings.map((instanceId) =>
oauthSources.sourcesOauthRetrieve({ slug: instanceId }),
),
);
return mappings
.filter((s) => s.status === "fulfilled")
.map((s) => s.value)
.map(sourceToSelect);
};
} }

View File

@ -2,12 +2,8 @@ import "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-flow-search"; import "@goauthentik/admin/common/ak-flow-search/ak-flow-search";
import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm"; import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm";
import { import {
oauth2ProviderSelector, makeSourceSelector,
oauth2ProvidersProvider,
} from "@goauthentik/admin/providers/oauth2/OAuth2ProviderForm";
import {
oauth2SourcesProvider, oauth2SourcesProvider,
oauth2SourcesSelector,
} from "@goauthentik/admin/providers/oauth2/OAuth2Sources.js"; } from "@goauthentik/admin/providers/oauth2/OAuth2Sources.js";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils"; import { first } from "@goauthentik/common/utils";
@ -34,7 +30,10 @@ import {
ProxyProvider, ProxyProvider,
} from "@goauthentik/api"; } from "@goauthentik/api";
import { propertyMappingsProvider, propertyMappingsSelector } from "./ProxyProviderFormHelpers.js"; import {
makeProxyPropertyMappingsSelector,
proxyPropertyMappingsProvider,
} from "./ProxyProviderPropertyMappings.js";
@customElement("ak-provider-proxy-form") @customElement("ak-provider-proxy-form")
export class ProxyProviderFormPage extends BaseProviderForm<ProxyProvider> { export class ProxyProviderFormPage extends BaseProviderForm<ProxyProvider> {
@ -303,8 +302,10 @@ export class ProxyProviderFormPage extends BaseProviderForm<ProxyProvider> {
name="propertyMappings" name="propertyMappings"
> >
<ak-dual-select-dynamic-selected <ak-dual-select-dynamic-selected
.provider=${propertyMappingsProvider} .provider=${proxyPropertyMappingsProvider}
.selector=${propertyMappingsSelector(this.instance?.propertyMappings)} .selector=${makeProxyPropertyMappingsSelector(
this.instance?.propertyMappings,
)}
available-label="${msg("Available Scopes")}" available-label="${msg("Available Scopes")}"
selected-label="${msg("Selected Scopes")}" selected-label="${msg("Selected Scopes")}"
></ak-dual-select-dynamic-selected> ></ak-dual-select-dynamic-selected>
@ -389,11 +390,11 @@ ${this.instance?.skipPathRegex}</textarea
${this.showHttpBasic ? this.renderHttpBasic() : html``} ${this.showHttpBasic ? this.renderHttpBasic() : html``}
<ak-form-element-horizontal <ak-form-element-horizontal
label=${msg("Trusted OIDC Sources")} label=${msg("Trusted OIDC Sources")}
name="jwtFederationSources" name="jwksSources"
> >
<ak-dual-select-dynamic-selected <ak-dual-select-dynamic-selected
.provider=${oauth2SourcesProvider} .provider=${oauth2SourcesProvider}
.selector=${oauth2SourcesSelector(this.instance?.jwtFederationSources)} .selector=${makeSourceSelector(this.instance?.jwksSources)}
available-label=${msg("Available Sources")} available-label=${msg("Available Sources")}
selected-label=${msg("Selected Sources")} selected-label=${msg("Selected Sources")}
></ak-dual-select-dynamic-selected> ></ak-dual-select-dynamic-selected>
@ -403,24 +404,6 @@ ${this.instance?.skipPathRegex}</textarea
)} )}
</p> </p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Federated OIDC Providers")}
name="jwtFederationProviders"
>
<ak-dual-select-dynamic-selected
.provider=${oauth2ProvidersProvider}
.selector=${oauth2ProviderSelector(
this.instance?.jwtFederationProviders,
)}
available-label=${msg("Available Providers")}
selected-label=${msg("Selected Providers")}
></ak-dual-select-dynamic-selected>
<p class="pf-c-form__helper-text">
${msg(
"JWTs signed by the selected providers can be used to authenticate to this provider.",
)}
</p>
</ak-form-element-horizontal>
</div> </div>
</ak-form-group> </ak-form-group>

View File

@ -1,45 +0,0 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types.js";
import { PropertymappingsApi, ScopeMapping } from "@goauthentik/api";
const mappingToSelect = (s: ScopeMapping) => [s.pk, s.name, s.name, s];
export async function propertyMappingsProvider(page = 1, search = "") {
const propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsProviderScopeList({
ordering: "scope_name",
pageSize: 20,
search: search.trim(),
page,
});
return {
pagination: propertyMappings.pagination,
options: propertyMappings.results.map(mappingToSelect),
};
}
export function propertyMappingsSelector(instanceMappings?: string[]) {
if (!instanceMappings) {
return async (mappings: DualSelectPair<ScopeMapping>[]) =>
mappings.filter(
([_0, _1, _2, scope]: DualSelectPair<ScopeMapping>) =>
!(scope?.managed ?? "").startsWith("goauthentik.io/providers"),
);
}
return async () => {
const pm = new PropertymappingsApi(DEFAULT_CONFIG);
const mappings = await Promise.allSettled(
instanceMappings.map((instanceId) =>
pm.propertymappingsProviderScopeRetrieve({ pmUuid: instanceId }),
),
);
return mappings
.filter((s) => s.status === "fulfilled")
.map((s) => s.value)
.map(mappingToSelect);
};
}

View File

@ -0,0 +1,27 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types.js";
import { PropertymappingsApi, ScopeMapping } from "@goauthentik/api";
export async function proxyPropertyMappingsProvider(page = 1, search = "") {
const propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsProviderScopeList({
ordering: "scope_name",
pageSize: 20,
search: search.trim(),
page,
});
return {
pagination: propertyMappings.pagination,
options: propertyMappings.results.map((scope) => [scope.pk, scope.name, scope.name, scope]),
};
}
export function makeProxyPropertyMappingsSelector(mappings?: string[]) {
const localMappings = mappings ? new Set(mappings) : undefined;
return localMappings
? ([pk, _]: DualSelectPair) => localMappings.has(pk)
: ([_0, _1, _2, scope]: DualSelectPair<ScopeMapping>) =>
!(scope?.managed ?? "").startsWith("goauthentik.io/providers");
}

View File

@ -15,7 +15,10 @@ import { ifDefined } from "lit/directives/if-defined.js";
import { AuthModeEnum, Endpoint, ProtocolEnum, RacApi } from "@goauthentik/api"; import { AuthModeEnum, Endpoint, ProtocolEnum, RacApi } from "@goauthentik/api";
import { propertyMappingsProvider, propertyMappingsSelector } from "./RACProviderFormHelpers.js"; import {
makeRACPropertyMappingsSelector,
racPropertyMappingsProvider,
} from "./RACPropertyMappings.js";
@customElement("ak-rac-endpoint-form") @customElement("ak-rac-endpoint-form")
export class EndpointForm extends ModelForm<Endpoint, string> { export class EndpointForm extends ModelForm<Endpoint, string> {
@ -111,8 +114,8 @@ export class EndpointForm extends ModelForm<Endpoint, string> {
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Property mappings")} name="propertyMappings"> <ak-form-element-horizontal label=${msg("Property mappings")} name="propertyMappings">
<ak-dual-select-dynamic-selected <ak-dual-select-dynamic-selected
.provider=${propertyMappingsProvider} .provider=${racPropertyMappingsProvider}
.selector=${propertyMappingsSelector(this.instance?.propertyMappings)} .selector=${makeRACPropertyMappingsSelector(this.instance?.propertyMappings)}
available-label="${msg("Available User Property Mappings")}" available-label="${msg("Available User Property Mappings")}"
selected-label="${msg("Selected User Property Mappings")}" selected-label="${msg("Selected User Property Mappings")}"
></ak-dual-select-dynamic-selected> ></ak-dual-select-dynamic-selected>

View File

@ -0,0 +1,24 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types.js";
import { PropertymappingsApi } from "@goauthentik/api";
export async function racPropertyMappingsProvider(page = 1, search = "") {
const propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsProviderRacList({
ordering: "name",
pageSize: 20,
search: search.trim(),
page,
});
return {
pagination: propertyMappings.pagination,
options: propertyMappings.results.map((mapping) => [mapping.pk, mapping.name]),
};
}
export function makeRACPropertyMappingsSelector(instanceMappings?: string[]) {
const localMappings = new Set(instanceMappings ?? []);
return ([pk, _]: DualSelectPair) => localMappings.has(pk);
}

View File

@ -19,7 +19,10 @@ import { ifDefined } from "lit/directives/if-defined.js";
import { FlowsInstancesListDesignationEnum, ProvidersApi, RACProvider } from "@goauthentik/api"; import { FlowsInstancesListDesignationEnum, ProvidersApi, RACProvider } from "@goauthentik/api";
import { propertyMappingsProvider, propertyMappingsSelector } from "./RACProviderFormHelpers.js"; import {
makeRACPropertyMappingsSelector,
racPropertyMappingsProvider,
} from "./RACPropertyMappings.js";
@customElement("ak-provider-rac-form") @customElement("ak-provider-rac-form")
export class RACProviderFormPage extends ModelForm<RACProvider, number> { export class RACProviderFormPage extends ModelForm<RACProvider, number> {
@ -124,8 +127,10 @@ export class RACProviderFormPage extends ModelForm<RACProvider, number> {
name="propertyMappings" name="propertyMappings"
> >
<ak-dual-select-dynamic-selected <ak-dual-select-dynamic-selected
.provider=${propertyMappingsProvider} .provider=${racPropertyMappingsProvider}
.selector=${propertyMappingsSelector(this.instance?.propertyMappings)} .selector=${makeRACPropertyMappingsSelector(
this.instance?.propertyMappings,
)}
available-label="${msg("Available Property Mappings")}" available-label="${msg("Available Property Mappings")}"
selected-label="${msg("Selected Property Mappings")}" selected-label="${msg("Selected Property Mappings")}"
></ak-dual-select-dynamic-selected> ></ak-dual-select-dynamic-selected>

View File

@ -1,41 +0,0 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types.js";
import { PropertymappingsApi, RACPropertyMapping } from "@goauthentik/api";
const mappingToSelect = (m: RACPropertyMapping) => [m.pk, m.name, m.name, m];
export async function propertyMappingsProvider(page = 1, search = "") {
const propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsProviderRacList({
ordering: "name",
pageSize: 20,
search: search.trim(),
page,
});
return {
pagination: propertyMappings.pagination,
options: propertyMappings.results.map(mappingToSelect),
};
}
export function propertyMappingsSelector(instanceMappings?: string[]) {
if (!instanceMappings) {
return async (_mappings: DualSelectPair<RACPropertyMapping>[]) => [];
}
return async () => {
const pm = new PropertymappingsApi(DEFAULT_CONFIG);
const mappings = await Promise.allSettled(
instanceMappings.map((instanceId) =>
pm.propertymappingsProviderRacRetrieve({ pmUuid: instanceId }),
),
);
return mappings
.filter((s) => s.status === "fulfilled")
.map((s) => s.value)
.map(mappingToSelect);
};
}

View File

@ -4,6 +4,7 @@ import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm"
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { ascii_letters, digits, first, randomString } from "@goauthentik/common/utils"; import { ascii_letters, digits, first, randomString } from "@goauthentik/common/utils";
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider"; import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types";
import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/SearchSelect"; import "@goauthentik/elements/forms/SearchSelect";
@ -13,9 +14,35 @@ import { TemplateResult, html } from "lit";
import { ifDefined } from "lit-html/directives/if-defined.js"; import { ifDefined } from "lit-html/directives/if-defined.js";
import { customElement } from "lit/decorators.js"; import { customElement } from "lit/decorators.js";
import { FlowsInstancesListDesignationEnum, ProvidersApi, RadiusProvider } from "@goauthentik/api"; import {
FlowsInstancesListDesignationEnum,
PropertymappingsApi,
ProvidersApi,
RadiusProvider,
RadiusProviderPropertyMapping,
} from "@goauthentik/api";
import { propertyMappingsProvider, propertyMappingsSelector } from "./RadiusProviderFormHelpers.js"; export async function radiusPropertyMappingsProvider(page = 1, search = "") {
const propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsProviderRadiusList({
ordering: "name",
pageSize: 20,
search: search.trim(),
page,
});
return {
pagination: propertyMappings.pagination,
options: propertyMappings.results.map((m) => [m.pk, m.name, m.name, m]),
};
}
export function makeRadiusPropertyMappingsSelector(instanceMappings?: string[]) {
const localMappings = instanceMappings ? new Set(instanceMappings) : undefined;
return localMappings
? ([pk, _]: DualSelectPair) => localMappings.has(pk)
: ([_0, _1, _2, _]: DualSelectPair<RadiusProviderPropertyMapping>) => [];
}
@customElement("ak-provider-radius-form") @customElement("ak-provider-radius-form")
export class RadiusProviderFormPage extends WithBrandConfig(BaseProviderForm<RadiusProvider>) { export class RadiusProviderFormPage extends WithBrandConfig(BaseProviderForm<RadiusProvider>) {
@ -128,8 +155,10 @@ export class RadiusProviderFormPage extends WithBrandConfig(BaseProviderForm<Rad
name="propertyMappings" name="propertyMappings"
> >
<ak-dual-select-dynamic-selected <ak-dual-select-dynamic-selected
.provider=${propertyMappingsProvider} .provider=${radiusPropertyMappingsProvider}
.selector=${propertyMappingsSelector(this.instance?.propertyMappings)} .selector=${makeRadiusPropertyMappingsSelector(
this.instance?.propertyMappings,
)}
available-label=${msg("Available Property Mappings")} available-label=${msg("Available Property Mappings")}
selected-label=${msg("Selected Property Mappings")} selected-label=${msg("Selected Property Mappings")}
></ak-dual-select-dynamic-selected> ></ak-dual-select-dynamic-selected>

View File

@ -1,41 +0,0 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types.js";
import { PropertymappingsApi, RadiusProviderPropertyMapping } from "@goauthentik/api";
const mappingToSelect = (m: RadiusProviderPropertyMapping) => [m.pk, m.name, m.name, m];
export async function propertyMappingsProvider(page = 1, search = "") {
const propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsProviderRadiusList({
ordering: "name",
pageSize: 20,
search: search.trim(),
page,
});
return {
pagination: propertyMappings.pagination,
options: propertyMappings.results.map(mappingToSelect),
};
}
export function propertyMappingsSelector(instanceMappings?: string[]) {
if (!instanceMappings) {
return async (_mappings: DualSelectPair<RadiusProviderPropertyMapping>[]) => [];
}
return async () => {
const pm = new PropertymappingsApi(DEFAULT_CONFIG);
const mappings = await Promise.allSettled(
instanceMappings.map((instanceId) =>
pm.propertymappingsProviderRadiusRetrieve({ pmUuid: instanceId }),
),
);
return mappings
.filter((s) => s.status === "fulfilled")
.map((s) => s.value)
.map(mappingToSelect);
};
}

View File

@ -9,6 +9,7 @@ import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm"
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils"; import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js"; import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types.js";
import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/Radio"; import "@goauthentik/elements/forms/Radio";
@ -30,7 +31,28 @@ import {
SpBindingEnum, SpBindingEnum,
} from "@goauthentik/api"; } from "@goauthentik/api";
import { propertyMappingsProvider, propertyMappingsSelector } from "./SAMLProviderFormHelpers.js"; export async function samlPropertyMappingsProvider(page = 1, search = "") {
const propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsProviderSamlList({
ordering: "saml_name",
pageSize: 20,
search: search.trim(),
page,
});
return {
pagination: propertyMappings.pagination,
options: propertyMappings.results.map((m) => [m.pk, m.name, m.name, m]),
};
}
export function makeSAMLPropertyMappingsSelector(instanceMappings?: string[]) {
const localMappings = instanceMappings ? new Set(instanceMappings) : undefined;
return localMappings
? ([pk, _]: DualSelectPair) => localMappings.has(pk)
: ([_0, _1, _2, mapping]: DualSelectPair<SAMLPropertyMapping>) =>
mapping?.managed?.startsWith("goauthentik.io/providers/saml");
}
@customElement("ak-provider-saml-form") @customElement("ak-provider-saml-form")
export class SAMLProviderFormPage extends BaseProviderForm<SAMLProvider> { export class SAMLProviderFormPage extends BaseProviderForm<SAMLProvider> {
@ -281,8 +303,10 @@ export class SAMLProviderFormPage extends BaseProviderForm<SAMLProvider> {
name="propertyMappings" name="propertyMappings"
> >
<ak-dual-select-dynamic-selected <ak-dual-select-dynamic-selected
.provider=${propertyMappingsProvider} .provider=${samlPropertyMappingsProvider}
.selector=${propertyMappingsSelector(this.instance?.propertyMappings)} .selector=${makeSAMLPropertyMappingsSelector(
this.instance?.propertyMappings,
)}
available-label=${msg("Available User Property Mappings")} available-label=${msg("Available User Property Mappings")}
selected-label=${msg("Selected User Property Mappings")} selected-label=${msg("Selected User Property Mappings")}
></ak-dual-select-dynamic-selected> ></ak-dual-select-dynamic-selected>

View File

@ -1,44 +0,0 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types.js";
import { PropertymappingsApi, SAMLPropertyMapping } from "@goauthentik/api";
const mappingToSelect = (m: SAMLPropertyMapping) => [m.pk, m.name, m.name, m];
export async function propertyMappingsProvider(page = 1, search = "") {
const propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsProviderSamlList({
ordering: "saml_name",
pageSize: 20,
search: search.trim(),
page,
});
return {
pagination: propertyMappings.pagination,
options: propertyMappings.results.map(mappingToSelect),
};
}
export function propertyMappingsSelector(instanceMappings?: string[]) {
if (!instanceMappings) {
return async (mappings: DualSelectPair<SAMLPropertyMapping>[]) =>
mappings.filter(([_0, _1, _2, mapping]: DualSelectPair<SAMLPropertyMapping>) =>
mapping?.managed?.startsWith("goauthentik.io/providers/saml"),
);
}
return async () => {
const pm = new PropertymappingsApi(DEFAULT_CONFIG);
const mappings = await Promise.allSettled(
instanceMappings.map((instanceId) =>
pm.propertymappingsProviderSamlRetrieve({ pmUuid: instanceId }),
),
);
return mappings
.filter((s) => s.status === "fulfilled")
.map((s) => s.value)
.map(mappingToSelect);
};
}

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