Compare commits
20 Commits
docs-read-
...
web/update
Author | SHA1 | Date | |
---|---|---|---|
76b9add6b4 | |||
d76e3c8023 | |||
fb40ee72a5 | |||
24f52252ba | |||
26ceb3d6c9 | |||
f756be2ece | |||
4bfd06e034 | |||
107dff39af | |||
401850c5e2 | |||
11bc9b8041 | |||
807e2a9fb0 | |||
5bd7cedaba | |||
c0814ad279 | |||
99af95b10c | |||
a36cc820bd | |||
6ff260df01 | |||
e497dbc314 | |||
f9f849574b | |||
4439b298bd | |||
4af6ecf629 |
@ -30,5 +30,3 @@ optional_value = final
|
||||
[bumpversion:file:internal/constants/constants.go]
|
||||
|
||||
[bumpversion:file:web/src/common/constants.ts]
|
||||
|
||||
[bumpversion:file:website/docs/install-config/install/aws/template.yaml]
|
||||
|
@ -11,9 +11,9 @@ inputs:
|
||||
description: "Docker image arch"
|
||||
|
||||
outputs:
|
||||
shouldPush:
|
||||
description: "Whether to push the image or not"
|
||||
value: ${{ steps.ev.outputs.shouldPush }}
|
||||
shouldBuild:
|
||||
description: "Whether to build image or not"
|
||||
value: ${{ steps.ev.outputs.shouldBuild }}
|
||||
|
||||
sha:
|
||||
description: "sha"
|
||||
|
@ -7,14 +7,7 @@ from time import time
|
||||
parser = configparser.ConfigParser()
|
||||
parser.read(".bumpversion.cfg")
|
||||
|
||||
# Decide if we should push the image or not
|
||||
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
|
||||
should_build = str(len(os.environ.get("DOCKER_USERNAME", "")) > 0).lower()
|
||||
|
||||
branch_name = os.environ["GITHUB_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:
|
||||
print(f"shouldPush={str(should_push).lower()}", file=_output)
|
||||
print(f"shouldBuild={should_build}", file=_output)
|
||||
print(f"sha={sha}", file=_output)
|
||||
print(f"version={version}", file=_output)
|
||||
print(f"prerelease={prerelease}", file=_output)
|
||||
|
1
.github/workflows/api-py-publish.yml
vendored
1
.github/workflows/api-py-publish.yml
vendored
@ -7,7 +7,6 @@ on:
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
build:
|
||||
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
|
1
.github/workflows/api-ts-publish.yml
vendored
1
.github/workflows/api-ts-publish.yml
vendored
@ -7,7 +7,6 @@ on:
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
build:
|
||||
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- id: generate_token
|
||||
|
43
.github/workflows/ci-aws-cfn.yml
vendored
43
.github/workflows/ci-aws-cfn.yml
vendored
@ -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
|
10
.github/workflows/ci-main.yml
vendored
10
.github/workflows/ci-main.yml
vendored
@ -252,7 +252,7 @@ jobs:
|
||||
image-name: ghcr.io/goauthentik/dev-server
|
||||
image-arch: ${{ matrix.arch }}
|
||||
- name: Login to Container Registry
|
||||
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
|
||||
if: ${{ steps.ev.outputs.shouldBuild == 'true' }}
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
@ -269,15 +269,15 @@ jobs:
|
||||
GEOIPUPDATE_ACCOUNT_ID=${{ secrets.GEOIPUPDATE_ACCOUNT_ID }}
|
||||
GEOIPUPDATE_LICENSE_KEY=${{ secrets.GEOIPUPDATE_LICENSE_KEY }}
|
||||
tags: ${{ steps.ev.outputs.imageTags }}
|
||||
push: ${{ steps.ev.outputs.shouldPush == 'true' }}
|
||||
push: ${{ steps.ev.outputs.shouldBuild == 'true' }}
|
||||
build-args: |
|
||||
GIT_BUILD_HASH=${{ steps.ev.outputs.sha }}
|
||||
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 }}
|
||||
- uses: actions/attest-build-provenance@v1
|
||||
id: attest
|
||||
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
|
||||
if: ${{ steps.ev.outputs.shouldBuild == 'true' }}
|
||||
with:
|
||||
subject-name: ${{ steps.ev.outputs.attestImageNames }}
|
||||
subject-digest: ${{ steps.push.outputs.digest }}
|
||||
@ -303,7 +303,7 @@ jobs:
|
||||
with:
|
||||
image-name: ghcr.io/goauthentik/dev-server
|
||||
- name: Comment on PR
|
||||
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
|
||||
if: ${{ steps.ev.outputs.shouldBuild == 'true' }}
|
||||
uses: ./.github/actions/comment-pr-instructions
|
||||
with:
|
||||
tag: ${{ steps.ev.outputs.imageMainTag }}
|
||||
|
8
.github/workflows/ci-outpost.yml
vendored
8
.github/workflows/ci-outpost.yml
vendored
@ -90,7 +90,7 @@ jobs:
|
||||
with:
|
||||
image-name: ghcr.io/goauthentik/dev-${{ matrix.type }}
|
||||
- name: Login to Container Registry
|
||||
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
|
||||
if: ${{ steps.ev.outputs.shouldBuild == 'true' }}
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
@ -104,16 +104,16 @@ jobs:
|
||||
with:
|
||||
tags: ${{ steps.ev.outputs.imageTags }}
|
||||
file: ${{ matrix.type }}.Dockerfile
|
||||
push: ${{ steps.ev.outputs.shouldPush == 'true' }}
|
||||
push: ${{ steps.ev.outputs.shouldBuild == 'true' }}
|
||||
build-args: |
|
||||
GIT_BUILD_HASH=${{ steps.ev.outputs.sha }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
context: .
|
||||
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
|
||||
id: attest
|
||||
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
|
||||
if: ${{ steps.ev.outputs.shouldBuild == 'true' }}
|
||||
with:
|
||||
subject-name: ${{ steps.ev.outputs.attestImageNames }}
|
||||
subject-digest: ${{ steps.push.outputs.digest }}
|
||||
|
@ -11,7 +11,6 @@ env:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- id: generate_token
|
||||
|
1
.github/workflows/ghcr-retention.yml
vendored
1
.github/workflows/ghcr-retention.yml
vendored
@ -7,7 +7,6 @@ on:
|
||||
|
||||
jobs:
|
||||
clean-ghcr:
|
||||
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
|
||||
name: Delete old unused container images
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
1
.github/workflows/publish-source-docs.yml
vendored
1
.github/workflows/publish-source-docs.yml
vendored
@ -12,7 +12,6 @@ env:
|
||||
|
||||
jobs:
|
||||
publish-source-docs:
|
||||
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
|
1
.github/workflows/release-next-branch.yml
vendored
1
.github/workflows/release-next-branch.yml
vendored
@ -11,7 +11,6 @@ permissions:
|
||||
|
||||
jobs:
|
||||
update-next:
|
||||
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
|
||||
runs-on: ubuntu-latest
|
||||
environment: internal-production
|
||||
steps:
|
||||
|
21
.github/workflows/release-publish.yml
vendored
21
.github/workflows/release-publish.yml
vendored
@ -169,27 +169,6 @@ jobs:
|
||||
file: ./authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{ matrix.goarch }}
|
||||
asset_name: authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{ matrix.goarch }}
|
||||
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:
|
||||
needs:
|
||||
- build-server
|
||||
|
21
.github/workflows/repo-mirror.yml
vendored
21
.github/workflows/repo-mirror.yml
vendored
@ -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 }}
|
1
.github/workflows/repo-stale.yml
vendored
1
.github/workflows/repo-stale.yml
vendored
@ -11,7 +11,6 @@ permissions:
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- id: generate_token
|
||||
|
@ -1 +1 @@
|
||||
website/docs/developer-docs/index.md
|
||||
website/developer-docs/index.md
|
5
Makefile
5
Makefile
@ -5,7 +5,7 @@ PWD = $(shell pwd)
|
||||
UID = $(shell id -u)
|
||||
GID = $(shell id -g)
|
||||
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"
|
||||
|
||||
GEN_API_TS = "gen-ts-api"
|
||||
@ -252,9 +252,6 @@ website-build:
|
||||
website-watch: ## Build and watch the documentation website, updating automatically
|
||||
cd website && npm run watch
|
||||
|
||||
aws-cfn:
|
||||
cd website && npm run aws-cfn
|
||||
|
||||
#########################
|
||||
## Docker
|
||||
#########################
|
||||
|
@ -65,12 +65,7 @@ from authentik.lib.utils.reflection import get_apps
|
||||
from authentik.outposts.models import OutpostServiceConnection
|
||||
from authentik.policies.models import Policy, PolicyBindingModel
|
||||
from authentik.policies.reputation.models import Reputation
|
||||
from authentik.providers.oauth2.models import (
|
||||
AccessToken,
|
||||
AuthorizationCode,
|
||||
DeviceToken,
|
||||
RefreshToken,
|
||||
)
|
||||
from authentik.providers.oauth2.models import AccessToken, AuthorizationCode, RefreshToken
|
||||
from authentik.providers.scim.models import SCIMProviderGroup, SCIMProviderUser
|
||||
from authentik.rbac.models import Role
|
||||
from authentik.sources.scim.models import SCIMSourceGroup, SCIMSourceUser
|
||||
@ -130,7 +125,6 @@ def excluded_models() -> list[type[Model]]:
|
||||
MicrosoftEntraProviderGroup,
|
||||
EndpointDevice,
|
||||
EndpointDeviceConnection,
|
||||
DeviceToken,
|
||||
)
|
||||
|
||||
|
||||
|
@ -159,7 +159,7 @@ def blueprints_discovery(self: SystemTask, path: str | None = None):
|
||||
check_blueprint_v1_file(blueprint)
|
||||
count += 1
|
||||
self.set_status(
|
||||
TaskStatus.SUCCESSFUL, _("Successfully imported {count} files.".format(count=count))
|
||||
TaskStatus.SUCCESSFUL, _("Successfully imported %(count)d files." % {"count": count})
|
||||
)
|
||||
|
||||
|
||||
|
@ -84,8 +84,8 @@ class CurrentBrandSerializer(PassiveSerializer):
|
||||
|
||||
matched_domain = CharField(source="domain")
|
||||
branding_title = CharField()
|
||||
branding_logo = CharField(source="branding_logo_url")
|
||||
branding_favicon = CharField(source="branding_favicon_url")
|
||||
branding_logo = CharField()
|
||||
branding_favicon = CharField()
|
||||
ui_footer_links = ListField(
|
||||
child=FooterLinkSerializer(),
|
||||
read_only=True,
|
||||
|
@ -10,7 +10,6 @@ from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.lib.models import SerializerModel
|
||||
|
||||
LOGGER = get_logger()
|
||||
@ -72,18 +71,6 @@ class Brand(SerializerModel):
|
||||
)
|
||||
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
|
||||
def serializer(self) -> Serializer:
|
||||
from authentik.brands.api import BrandSerializer
|
||||
|
@ -9,9 +9,6 @@
|
||||
versionFamily: "{{ version_family }}",
|
||||
versionSubdomain: "{{ version_subdomain }}",
|
||||
build: "{{ build }}",
|
||||
api: {
|
||||
base: "{{ base_url }}",
|
||||
},
|
||||
};
|
||||
window.addEventListener("DOMContentLoaded", function () {
|
||||
{% for message in messages %}
|
||||
|
@ -9,8 +9,8 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<title>{% block title %}{% trans title|default:brand.branding_title %}{% endblock %}</title>
|
||||
<link rel="icon" href="{{ brand.branding_favicon_url }}">
|
||||
<link rel="shortcut icon" href="{{ brand.branding_favicon_url }}">
|
||||
<link rel="icon" href="{{ brand.branding_favicon }}">
|
||||
<link rel="shortcut icon" href="{{ brand.branding_favicon }}">
|
||||
{% block head_before %}
|
||||
{% endblock %}
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}">
|
||||
|
@ -4,7 +4,7 @@
|
||||
{% load i18n %}
|
||||
|
||||
{% 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/theme-dark.css' %}" media="(prefers-color-scheme: dark)">
|
||||
{% include "base/header_js.html" %}
|
||||
@ -13,7 +13,7 @@
|
||||
{% block head %}
|
||||
<style>
|
||||
: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-2x: var(--ak-flow-background);
|
||||
--pf-c-background-image--BackgroundImage--sm: var(--ak-flow-background);
|
||||
@ -50,7 +50,7 @@
|
||||
<div class="ak-login-container">
|
||||
<main class="pf-c-login__main">
|
||||
<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>
|
||||
<header class="pf-c-login__main-header">
|
||||
<h1 class="pf-c-title pf-m-3xl">
|
||||
|
@ -16,7 +16,6 @@ from authentik.api.v3.config import ConfigView
|
||||
from authentik.brands.api import CurrentBrandSerializer
|
||||
from authentik.brands.models import Brand
|
||||
from authentik.core.models import UserTypes
|
||||
from authentik.lib.config import CONFIG
|
||||
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["build"] = get_build_hash()
|
||||
kwargs["url_kwargs"] = self.kwargs
|
||||
kwargs["base_url"] = self.request.build_absolute_uri(CONFIG.get("web.path", "/"))
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
|
@ -85,5 +85,5 @@ def certificate_discovery(self: SystemTask):
|
||||
if dirty:
|
||||
cert.save()
|
||||
self.set_status(
|
||||
TaskStatus.SUCCESSFUL, _("Successfully imported {count} files.".format(count=discovered))
|
||||
TaskStatus.SUCCESSFUL, _("Successfully imported %(count)d files." % {"count": discovered})
|
||||
)
|
||||
|
@ -6,8 +6,8 @@
|
||||
<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="#ffffff" media="(prefers-color-scheme: light)">
|
||||
<link rel="icon" href="{{ tenant.branding_favicon_url }}">
|
||||
<link rel="shortcut icon" href="{{ tenant.branding_favicon_url }}">
|
||||
<link rel="icon" href="{{ tenant.branding_favicon }}">
|
||||
<link rel="shortcut icon" href="{{ tenant.branding_favicon }}">
|
||||
{% include "base/header_js.html" %}
|
||||
{% endblock %}
|
||||
|
||||
|
@ -14,7 +14,6 @@ from structlog.stdlib import get_logger
|
||||
from authentik.core.models import Token
|
||||
from authentik.core.types import UserSettingSerializer
|
||||
from authentik.flows.challenge import FlowLayout
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.lib.models import InheritanceForeignKey, SerializerModel
|
||||
from authentik.lib.utils.reflection import class_to_path
|
||||
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
|
||||
it is returned as-is"""
|
||||
if not self.background:
|
||||
return (
|
||||
CONFIG.get("web.path", "/")[:-1] + "/static/dist/assets/images/flow_background.jpg"
|
||||
)
|
||||
if self.background.name.startswith("http"):
|
||||
return "/static/dist/assets/images/flow_background.jpg"
|
||||
if self.background.name.startswith("http") or self.background.name.startswith("/static"):
|
||||
return self.background.name
|
||||
if self.background.name.startswith("/static"):
|
||||
return CONFIG.get("web.path", "/")[:-1] + self.background.name
|
||||
return self.background.url
|
||||
|
||||
stages = models.ManyToManyField(Stage, through="FlowStageBinding", blank=True)
|
||||
|
@ -9,8 +9,8 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<title>{% block title %}{% trans title|default:brand.branding_title %}{% endblock %}</title>
|
||||
<link rel="icon" href="{{ brand.branding_favicon_url }}">
|
||||
<link rel="shortcut icon" href="{{ brand.branding_favicon_url }}">
|
||||
<link rel="icon" href="{{ brand.branding_favicon }}">
|
||||
<link rel="shortcut icon" href="{{ brand.branding_favicon }}">
|
||||
{% block head_before %}
|
||||
{% endblock %}
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'dist/sfe/bootstrap.min.css' %}">
|
||||
|
@ -135,7 +135,6 @@ web:
|
||||
# No default here as it's set dynamically
|
||||
# workers: 2
|
||||
threads: 4
|
||||
path: /
|
||||
|
||||
worker:
|
||||
concurrency: 2
|
||||
|
@ -36,7 +36,6 @@ from authentik.lib.utils.http import authentik_user_agent
|
||||
from authentik.lib.utils.reflection import get_env
|
||||
|
||||
LOGGER = get_logger()
|
||||
_root_path = CONFIG.get("web.path", "/")
|
||||
|
||||
|
||||
class SentryIgnoredException(Exception):
|
||||
@ -91,7 +90,7 @@ def traces_sampler(sampling_context: dict) -> float:
|
||||
path = sampling_context.get("asgi_scope", {}).get("path", "")
|
||||
_type = sampling_context.get("asgi_scope", {}).get("type", "")
|
||||
# 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
|
||||
if _type == "websocket":
|
||||
return 0
|
||||
|
@ -82,7 +82,7 @@ class SyncTasks:
|
||||
return
|
||||
try:
|
||||
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(
|
||||
args=(class_to_path(User), page, provider_pk),
|
||||
time_limit=PAGE_TIMEOUT,
|
||||
@ -90,7 +90,7 @@ class SyncTasks:
|
||||
).get():
|
||||
messages.append(LogEvent(**msg))
|
||||
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(
|
||||
args=(class_to_path(Group), page, provider_pk),
|
||||
time_limit=PAGE_TIMEOUT,
|
||||
|
@ -43,9 +43,8 @@ class PasswordExpiryPolicy(Policy):
|
||||
request.user.set_unusable_password()
|
||||
request.user.save()
|
||||
message = _(
|
||||
"Password expired {days} days ago. Please update your password.".format(
|
||||
days=days_since_expiry
|
||||
)
|
||||
"Password expired %(days)d days ago. Please update your password."
|
||||
% {"days": days_since_expiry}
|
||||
)
|
||||
return PolicyResult(False, message)
|
||||
return PolicyResult(False, _("Password has expired."))
|
||||
|
@ -135,7 +135,7 @@ class PasswordPolicy(Policy):
|
||||
LOGGER.debug("got hibp result", count=final_count, hash=pw_hash[:5])
|
||||
if final_count > self.hibp_allowed_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(True)
|
||||
|
||||
|
@ -73,8 +73,7 @@ class OAuth2ProviderSerializer(ProviderSerializer):
|
||||
"sub_mode",
|
||||
"property_mappings",
|
||||
"issuer_mode",
|
||||
"jwt_federation_sources",
|
||||
"jwt_federation_providers",
|
||||
"jwks_sources",
|
||||
]
|
||||
extra_kwargs = ProviderSerializer.Meta.extra_kwargs
|
||||
|
||||
|
@ -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"
|
||||
),
|
||||
),
|
||||
]
|
@ -244,7 +244,7 @@ class OAuth2Provider(WebfingerProvider, Provider):
|
||||
related_name="oauth2provider_encryption_key_set",
|
||||
)
|
||||
|
||||
jwt_federation_sources = models.ManyToManyField(
|
||||
jwks_sources = models.ManyToManyField(
|
||||
OAuthSource,
|
||||
verbose_name=_(
|
||||
"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,
|
||||
blank=True,
|
||||
)
|
||||
jwt_federation_providers = models.ManyToManyField("OAuth2Provider", blank=True, default=None)
|
||||
|
||||
@cached_property
|
||||
def jwt_key(self) -> tuple[str | PrivateKeyTypes, str]:
|
||||
|
@ -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)
|
@ -37,16 +37,9 @@ class TestTokenClientCredentialsJWTSource(OAuthTestCase):
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
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()
|
||||
|
||||
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(
|
||||
name=generate_id(),
|
||||
slug=generate_id(),
|
||||
@ -69,7 +62,7 @@ class TestTokenClientCredentialsJWTSource(OAuthTestCase):
|
||||
redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://testserver")],
|
||||
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.app = Application.objects.create(name="test", slug="test", provider=self.provider)
|
||||
|
||||
@ -107,7 +100,7 @@ class TestTokenClientCredentialsJWTSource(OAuthTestCase):
|
||||
|
||||
def test_invalid_signature(self):
|
||||
"""test invalid JWT"""
|
||||
token = self.helper_provider.encode(
|
||||
token = self.provider.encode(
|
||||
{
|
||||
"sub": "foo",
|
||||
"exp": datetime.now() + timedelta(hours=2),
|
||||
@ -129,7 +122,7 @@ class TestTokenClientCredentialsJWTSource(OAuthTestCase):
|
||||
|
||||
def test_invalid_expired(self):
|
||||
"""test invalid JWT"""
|
||||
token = self.helper_provider.encode(
|
||||
token = self.provider.encode(
|
||||
{
|
||||
"sub": "foo",
|
||||
"exp": datetime.now() - timedelta(hours=2),
|
||||
@ -153,7 +146,7 @@ class TestTokenClientCredentialsJWTSource(OAuthTestCase):
|
||||
"""test invalid JWT"""
|
||||
self.app.provider = None
|
||||
self.app.save()
|
||||
token = self.helper_provider.encode(
|
||||
token = self.provider.encode(
|
||||
{
|
||||
"sub": "foo",
|
||||
"exp": datetime.now() + timedelta(hours=2),
|
||||
@ -181,7 +174,7 @@ class TestTokenClientCredentialsJWTSource(OAuthTestCase):
|
||||
target=self.app,
|
||||
order=0,
|
||||
)
|
||||
token = self.helper_provider.encode(
|
||||
token = self.provider.encode(
|
||||
{
|
||||
"sub": "foo",
|
||||
"exp": datetime.now() + timedelta(hours=2),
|
||||
@ -203,7 +196,7 @@ class TestTokenClientCredentialsJWTSource(OAuthTestCase):
|
||||
|
||||
def test_successful(self):
|
||||
"""test successful"""
|
||||
token = self.helper_provider.encode(
|
||||
token = self.provider.encode(
|
||||
{
|
||||
"sub": "foo",
|
||||
"exp": datetime.now() + timedelta(hours=2),
|
||||
|
@ -137,7 +137,7 @@ class OAuthDeviceCodeChallengeResponse(ChallengeResponse):
|
||||
|
||||
|
||||
class OAuthDeviceCodeStage(ChallengeStageView):
|
||||
"""Flow challenge for users to enter device code"""
|
||||
"""Flow challenge for users to enter device codes"""
|
||||
|
||||
response_class = OAuthDeviceCodeChallengeResponse
|
||||
|
||||
|
@ -362,9 +362,23 @@ class TokenParams:
|
||||
},
|
||||
).from_http(request, user=user)
|
||||
|
||||
def __validate_jwt_from_source(
|
||||
self, assertion: str
|
||||
) -> tuple[dict, OAuthSource] | tuple[None, None]:
|
||||
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")
|
||||
|
||||
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
|
||||
# the header.
|
||||
# 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)
|
||||
raise TokenError("invalid_grant") from None
|
||||
expected_kid = decode_unvalidated["header"]["kid"]
|
||||
fallback_alg = decode_unvalidated["header"]["alg"]
|
||||
token = source = None
|
||||
for source in self.provider.jwt_federation_sources.filter(
|
||||
for source in self.provider.jwks_sources.filter(
|
||||
oidc_jwks__keys__contains=[{"kid": expected_kid}]
|
||||
):
|
||||
LOGGER.debug("verifying JWT with source", source=source.slug)
|
||||
keys = source.oidc_jwks.get("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"))
|
||||
try:
|
||||
parsed_key = PyJWK.from_dict(key).key
|
||||
parsed_key = PyJWK.from_dict(key)
|
||||
token = decode(
|
||||
assertion,
|
||||
parsed_key,
|
||||
algorithms=[key.get("alg")] if "alg" in key else [fallback_alg],
|
||||
parsed_key.key,
|
||||
algorithms=[key.get("alg")],
|
||||
options={
|
||||
"verify_aud": False,
|
||||
},
|
||||
@ -404,61 +414,13 @@ class TokenParams:
|
||||
# and not a public key
|
||||
except (PyJWTError, ValueError, TypeError, AttributeError) as exc:
|
||||
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:
|
||||
LOGGER.warning("No token could be verified")
|
||||
raise TokenError("invalid_grant")
|
||||
|
||||
LOGGER.info("successfully verified JWT with source", source=source.slug)
|
||||
|
||||
if "exp" in token:
|
||||
exp = datetime.fromtimestamp(token["exp"])
|
||||
# Non-timezone aware check since we assume `exp` is in UTC
|
||||
@ -472,16 +434,15 @@ class TokenParams:
|
||||
raise TokenError("invalid_grant")
|
||||
|
||||
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 = {
|
||||
"jwt": token,
|
||||
}
|
||||
if source:
|
||||
method_args["source"] = source
|
||||
if provider:
|
||||
method_args["provider"] = provider
|
||||
if parsed_key:
|
||||
method_args["jwk_id"] = parsed_key.key_id
|
||||
Event.new(
|
||||
action=EventAction.LOGIN,
|
||||
**{
|
||||
|
@ -94,8 +94,7 @@ class ProxyProviderSerializer(ProviderSerializer):
|
||||
"intercept_header_auth",
|
||||
"redirect_uris",
|
||||
"cookie_domain",
|
||||
"jwt_federation_sources",
|
||||
"jwt_federation_providers",
|
||||
"jwks_sources",
|
||||
"access_token_validity",
|
||||
"refresh_token_validity",
|
||||
"outpost_set",
|
||||
|
@ -32,8 +32,6 @@ LOGIN_URL = "authentik_flows:default-authentication"
|
||||
# Custom user model
|
||||
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_HEADER_NAME = "HTTP_X_AUTHENTIK_CSRF"
|
||||
LANGUAGE_COOKIE_NAME = "authentik_language"
|
||||
@ -306,12 +304,10 @@ DATABASES = {
|
||||
"USER": CONFIG.get("postgresql.user"),
|
||||
"PASSWORD": CONFIG.get("postgresql.password"),
|
||||
"PORT": CONFIG.get("postgresql.port"),
|
||||
"OPTIONS": {
|
||||
"sslmode": CONFIG.get("postgresql.sslmode"),
|
||||
"sslrootcert": CONFIG.get("postgresql.sslrootcert"),
|
||||
"sslcert": CONFIG.get("postgresql.sslcert"),
|
||||
"sslkey": CONFIG.get("postgresql.sslkey"),
|
||||
},
|
||||
"SSLMODE": CONFIG.get("postgresql.sslmode"),
|
||||
"SSLROOTCERT": CONFIG.get("postgresql.sslrootcert"),
|
||||
"SSLCERT": CONFIG.get("postgresql.sslcert"),
|
||||
"SSLKEY": CONFIG.get("postgresql.sslkey"),
|
||||
"TEST": {
|
||||
"NAME": CONFIG.get("postgresql.test.name"),
|
||||
},
|
||||
@ -429,7 +425,7 @@ if _ERROR_REPORTING:
|
||||
# https://docs.djangoproject.com/en/2.1/howto/static-files/
|
||||
|
||||
STATICFILES_DIRS = [BASE_DIR / Path("web")]
|
||||
STATIC_URL = CONFIG.get("web.path", "/") + "static/"
|
||||
STATIC_URL = "/static/"
|
||||
|
||||
STORAGES = {
|
||||
"staticfiles": {
|
||||
|
@ -4,7 +4,6 @@ from django.urls import include, path
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.core.views import error
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.lib.utils.reflection import get_apps
|
||||
from authentik.root.monitoring import LiveView, MetricsView, ReadyView
|
||||
|
||||
@ -15,7 +14,7 @@ handler403 = error.ForbiddenView.as_view()
|
||||
handler404 = error.NotFoundView.as_view()
|
||||
handler500 = error.ServerErrorView.as_view()
|
||||
|
||||
_urlpatterns = []
|
||||
urlpatterns = []
|
||||
|
||||
for _authentik_app in get_apps():
|
||||
mountpoints = None
|
||||
@ -36,7 +35,7 @@ for _authentik_app in get_apps():
|
||||
namespace=namespace,
|
||||
),
|
||||
)
|
||||
_urlpatterns.append(_path)
|
||||
urlpatterns.append(_path)
|
||||
LOGGER.debug(
|
||||
"Mounted URLs",
|
||||
app_name=_authentik_app.name,
|
||||
@ -44,10 +43,8 @@ for _authentik_app in get_apps():
|
||||
namespace=namespace,
|
||||
)
|
||||
|
||||
_urlpatterns += [
|
||||
urlpatterns += [
|
||||
path("-/metrics/", MetricsView.as_view(), name="metrics"),
|
||||
path("-/health/live/", LiveView.as_view(), name="health-live"),
|
||||
path("-/health/ready/", ReadyView.as_view(), name="health-ready"),
|
||||
]
|
||||
|
||||
urlpatterns = [path(CONFIG.get("web.path", "/")[1:], include(_urlpatterns))]
|
||||
|
@ -2,16 +2,13 @@
|
||||
|
||||
from importlib import import_module
|
||||
|
||||
from channels.routing import URLRouter
|
||||
from django.urls import path
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.lib.utils.reflection import get_apps
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
_websocket_urlpatterns = []
|
||||
websocket_urlpatterns = []
|
||||
for _authentik_app in get_apps():
|
||||
try:
|
||||
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"):
|
||||
continue
|
||||
urls: list = api_urls.websocket_urlpatterns
|
||||
_websocket_urlpatterns.extend(urls)
|
||||
websocket_urlpatterns.extend(urls)
|
||||
LOGGER.debug(
|
||||
"Mounted Websocket URLs",
|
||||
app_name=_authentik_app.name,
|
||||
)
|
||||
|
||||
websocket_urlpatterns = [
|
||||
path(
|
||||
CONFIG.get("web.path", "/")[1:],
|
||||
URLRouter(_websocket_urlpatterns),
|
||||
),
|
||||
]
|
||||
|
@ -127,19 +127,19 @@
|
||||
"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="
|
||||
},
|
||||
"b35a26b2-8f6e-4697-ab1d-d44db4da28c6":{
|
||||
"name": "Zoho Vault",
|
||||
"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="
|
||||
},
|
||||
"b35a26b2-8f6e-4697-ab1d-d44db4da28c6":{
|
||||
"name": "Zoho Vault",
|
||||
"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="
|
||||
},
|
||||
"b78a0a55-6ef8-d246-a042-ba0f6d55050c": {
|
||||
"name": "LastPass",
|
||||
"icon_dark": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIHZpZXdCb3g9IjAgMCA1MCA1MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjUwIiBoZWlnaHQ9IjUwIiByeD0iNy44NDc4MiIgZmlsbD0idXJsKCNwYWludDBfbGluZWFyXzI4NF81NzQzKSIvPgo8cGF0aCBkPSJNMzkuMzUxOCAxNy42MzgzQzM4LjkwNSAxNy42MzgzIDM4LjU2NjggMTcuOTc0NyAzOC41NjY4IDE4LjQyODdWMzEuNTYwNEMzOC41NjY4IDMyLjAxMDggMzguOTAxNCAzMi4zNTA5IDM5LjM1MTggMzIuMzUwOUMzOS44MDIyIDMyLjM1MDkgNDAuMTM2OCAzMi4wMTQ0IDQwLjEzNjggMzEuNTYwNFYxOC40Mjg3QzQwLjEzNjggMTcuOTc4NCAzOS44MDIyIDE3LjYzODMgMzkuMzUxOCAxNy42MzgzWiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTIxLjg5MTggMjIuNTQ0OUMyMC4zODE0IDIyLjU0NDkgMTkuMDk1NCAyMy43ODU3IDE5LjA5NTQgMjUuMzA2OUMxOS4wOTU0IDI2LjgyODEgMjAuMzI3MiAyOC4xMjMyIDIxLjgzNzUgMjguMTIzMkMyMy4zNDc4IDI4LjEyMzIgMjQuNjMzOSAyNi44ODI0IDI0LjYzMzkgMjUuMzYxMkMyNC42MzM5IDIzLjg0IDIzLjQwMjEgMjIuNTQ0OSAyMS44OTE4IDIyLjU0NDlaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMzEuMDY5NCAyMi41NDQ5QzI5LjU1OTEgMjIuNTQ0OSAyOC4yNzMxIDIzLjc4NTcgMjguMjczMSAyNS4zMDY5QzI4LjI3MzEgMjYuODI4MSAyOS41MDQ4IDI4LjEyMzIgMzEuMDE1MiAyOC4xMjMyQzMyLjUyNTUgMjguMTIzMiAzMy44MTE1IDI2Ljg4MjQgMzMuODExNSAyNS4zNjEyQzMzLjg2NTggMjMuODQgMzIuNjM0IDIyLjU0NDkgMzEuMDY5NCAyMi41NDQ5WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTEyLjY1OTYgMjIuNTQ0OUMxMS4xNDkzIDIyLjU0NDkgOS44NjMyOCAyMy43ODU3IDkuODYzMjggMjUuMzA2OUM5Ljg2MzI4IDI2LjgyODEgMTEuMDk1MSAyOC4xMjMyIDEyLjYwNTQgMjguMTIzMkMxNC4xMTU3IDI4LjEyMzIgMTUuNDAxNyAyNi44ODI0IDE1LjQwMTcgMjUuMzYxMkMxNS40MDE3IDIzLjg0IDE0LjE3IDIyLjU0NDkgMTIuNjU5NiAyMi41NDQ5WiIgZmlsbD0id2hpdGUiLz4KPGRlZnM+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQwX2xpbmVhcl8yODRfNTc0MyIgeDE9IjI1IiB5MT0iMCIgeDI9IjI1IiB5Mj0iNTAiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iIzNDM0QzRCIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMyNjI2MjYiLz4KPC9saW5lYXJHcmFkaWVudD4KPC9kZWZzPgo8L3N2Zz4K",
|
||||
"icon_light": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIHZpZXdCb3g9IjAgMCA1MCA1MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjUwIiBoZWlnaHQ9IjUwIiByeD0iNy44NDc4MiIgZmlsbD0idXJsKCNwYWludDBfbGluZWFyXzI4NF81Nzc0KSIvPgo8cGF0aCBkPSJNMzkuMzUxNyAxNy42Mzg0QzM4LjkwNDkgMTcuNjM4NCAzOC41NjY3IDE3Ljk3NDkgMzguNTY2NyAxOC40Mjg5VjMxLjU2MDVDMzguNTY2NyAzMi4wMTA4IDM4LjkwMTMgMzIuMzUwOSAzOS4zNTE3IDMyLjM1MDlDMzkuODAyMSAzMi4zNTA5IDQwLjEzNjcgMzIuMDE0NSA0MC4xMzY3IDMxLjU2MDVWMTguNDI4OUM0MC4xMzY3IDE3Ljk3ODUgMzkuODAyMSAxNy42Mzg0IDM5LjM1MTcgMTcuNjM4NFoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0yMS44OTE4IDIyLjU0NUMyMC4zODE0IDIyLjU0NSAxOS4wOTU0IDIzLjc4NTkgMTkuMDk1NCAyNS4zMDdDMTkuMDk1NCAyNi44MjgyIDIwLjMyNzIgMjguMTIzMyAyMS44Mzc1IDI4LjEyMzNDMjMuMzQ3OCAyOC4xMjMzIDI0LjYzMzggMjYuODgyNSAyNC42MzM4IDI1LjM2MTNDMjQuNjMzOCAyMy44NDAxIDIzLjQwMjEgMjIuNTQ1IDIxLjg5MTggMjIuNTQ1WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTMxLjA2OTQgMjIuNTQ1QzI5LjU1OTEgMjIuNTQ1IDI4LjI3MyAyMy43ODU5IDI4LjI3MyAyNS4zMDdDMjguMjczIDI2LjgyODIgMjkuNTA0OCAyOC4xMjMzIDMxLjAxNTEgMjguMTIzM0MzMi41MjU0IDI4LjEyMzMgMzMuODExNSAyNi44ODI1IDMzLjgxMTUgMjUuMzYxM0MzMy44NjU3IDIzLjg0MDEgMzIuNjM0IDIyLjU0NSAzMS4wNjk0IDIyLjU0NVoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0xMi42NTk3IDIyLjU0NUMxMS4xNDk0IDIyLjU0NSA5Ljg2MzM5IDIzLjc4NTkgOS44NjMzOSAyNS4zMDdDOS44NjMzOSAyNi44MjgyIDExLjA5NTIgMjguMTIzMyAxMi42MDU1IDI4LjEyMzNDMTQuMTE1OCAyOC4xMjMzIDE1LjQwMTggMjYuODgyNSAxNS40MDE4IDI1LjM2MTNDMTUuNDAxOCAyMy44NDAxIDE0LjE3IDIyLjU0NSAxMi42NTk3IDIyLjU0NVoiIGZpbGw9IndoaXRlIi8+CjxkZWZzPgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50MF9saW5lYXJfMjg0XzU3NzQiIHgxPSIyNSIgeTE9IjAiIHgyPSIyNSIgeTI9IjUwIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiNFRDE5NEEiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjQzMwODMzIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg=="
|
||||
},
|
||||
"de503f9c-21a4-4f76-b4b7-558eb55c6f89": {
|
||||
"name": "Devolutions",
|
||||
"icon_dark": "data:image/svg+xml;base64,CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iNzJweCIgaGVpZ2h0PSI3MnB4IiB2aWV3Qm94PSIwIDAgNzIgNzIiPgk8ZGVmcz4KICAgICAgICA8ZmlsdGVyIGlkPSJhIiB3aWR0aD0iMjAwJSIgaGVpZ2h0PSIyMDAlIj4KICAgICAgICAgICAgPGZlT2Zmc2V0IHJlc3VsdD0ib2ZmT3V0IiBpbj0iU291cmNlQWxwaGEiIGR5PSIyLjIiLz4KICAgICAgICAgICAgPGZlR2F1c3NpYW5CbHVyIHJlc3VsdD0iYmx1ck91dCIgaW49Im9mZk91dCIgc3RkRGV2aWF0aW9uPSIxLjUiLz4KICAgICAgICAgICAgPGZlQ29sb3JNYXRyaXggdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwLjQgMCIvPgogICAgICAgICAgICA8ZmVNZXJnZT4KICAgICAgICAgICAgICAgIDxmZU1lcmdlTm9kZS8+CiAgICAgICAgICAgICAgICA8ZmVNZXJnZU5vZGUgaW49IlNvdXJjZUdyYXBoaWMiLz4KICAgICAgICAgICAgPC9mZU1lcmdlPgogICAgICAgIDwvZmlsdGVyPgogICAgPC9kZWZzPgo8cGF0aCBmaWxsPSIjZmZmZmZmIiBmaWx0ZXI9InVybCgjYSkiIGQ9Ik0zMS4wNTksNS4zOTVjMTYuODktMi43MjcsMzIuODE3LDguNzcyLDM1LjU0NSwyNS42NjJjMi43MjcsMTYuODktOC43NzIsMzIuODE3LTI1LjY2MiwzNS41NDUKCUMyNC4wNTEsNjkuMzI5LDguMTI0LDU3LjgzLDUuMzk3LDQwLjk0QzIuNjcsMjQuMDQ5LDE0LjE2OSw4LjEyMiwzMS4wNTksNS4zOTV6Ii8+CjxwYXRoIGZpbGw9IiMwMDY4YzMiIGQ9Ik01NS4zNjQsMTcuMjAyYy01LjA0Ny01LjE5Ny0xMS44MDItOC4xMDktMTkuMDItOC4yYy03LjE5MS0wLjA5LTEzLjk4NSwyLjY2OS0xOS4xNDksNy43NjgKCUMxMS45MSwyMS45ODksOSwyOC45NTUsOSwzNi4zOTJsMC4wNDQsMS4yNmMwLDEuMTgxLDAuOTYxLDIuMTQyLDIuMTQyLDIuMTQyczIuMTQxLTAuOTYsMi4xNDItMi4xNGwwLjAxLTAuOTEzCgljMC05Ljk0NSw4LjQ1My0yMC41OTMsMjEuMDM1LTIwLjU5M2MxMy4xMzIsMCwyMS4yNjEsMTAuNzEyLDIxLjI2MSwyMC42MzdjMCw1LjE3My0yLjA2Myw5LjkxOS01LjgwOCwxMy4zNjMKCUM0Ni4xNyw1My41MDksNDEuMjYsNTUuMzYsMzYsNTUuMzZjLTMuMTMsMC02LjIxOS0wLjc1NS04Ljk1OC0yLjE4NmwxOC44Ny04LjY4NmMxLjI2Ny0wLjU4MywyLjExNS0xLjgxLDIuMjEzLTMuMjAxCglzLTAuNTY5LTIuNzI1LTEuNzU0LTMuNDg3bC0xNS43MDYtOC40MTVjLTAuNTU0LTAuMzU3LTEuMjEzLTAuNDc3LTEuODU4LTAuMzM3Yy0wLjY0NCwwLjEzOS0xLjE5NSwwLjUyMS0xLjU1MiwxLjA3NQoJYy0wLjczNywxLjE0NC0wLjQwNiwyLjY3MywwLjcyMSwzLjM5N2w4LjQ1NCw2LjkyM2wtMTguNDE5LDguNDc4Yy0xLjQyNywwLjY1Ny0yLjI5OCwyLjEtMi4yMTgsMy42NzUKCWMwLjA0OCwwLjk0OSwwLjQ5MywxLjg4NCwxLjI1MywyLjYzNEMyMi4xNTgsNjAuMjY5LDI4Ljg0LDYzLDM1Ljk4NSw2M2MwLjQ0NSwwLDAuODkyLTAuMDExLDEuMzQxLTAuMDMyCgljMTQuMTU2LTAuNjczLDI1LjQzMi0xMi4zMTUsMjUuNjcxLTI2LjUwNUM2My4xMTgsMjkuMjM2LDYwLjQwNywyMi4zOTYsNTUuMzY0LDE3LjIwMnoiLz4KPC9zdmc+Cg==",
|
||||
"icon_light": "data:image/svg+xml;base64,CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iNzJweCIgaGVpZ2h0PSI3MnB4IiB2aWV3Qm94PSIwIDAgNzIgNzIiPgk8ZGVmcz4KICAgICAgICA8ZmlsdGVyIGlkPSJhIiB3aWR0aD0iMjAwJSIgaGVpZ2h0PSIyMDAlIj4KICAgICAgICAgICAgPGZlT2Zmc2V0IHJlc3VsdD0ib2ZmT3V0IiBpbj0iU291cmNlQWxwaGEiIGR5PSIyLjIiLz4KICAgICAgICAgICAgPGZlR2F1c3NpYW5CbHVyIHJlc3VsdD0iYmx1ck91dCIgaW49Im9mZk91dCIgc3RkRGV2aWF0aW9uPSIxLjUiLz4KICAgICAgICAgICAgPGZlQ29sb3JNYXRyaXggdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwLjQgMCIvPgogICAgICAgICAgICA8ZmVNZXJnZT4KICAgICAgICAgICAgICAgIDxmZU1lcmdlTm9kZS8+CiAgICAgICAgICAgICAgICA8ZmVNZXJnZU5vZGUgaW49IlNvdXJjZUdyYXBoaWMiLz4KICAgICAgICAgICAgPC9mZU1lcmdlPgogICAgICAgIDwvZmlsdGVyPgogICAgPC9kZWZzPgo8cGF0aCBmaWxsPSIjZmZmZmZmIiBmaWx0ZXI9InVybCgjYSkiIGQ9Ik0zMS4wNTksNS4zOTVjMTYuODktMi43MjcsMzIuODE3LDguNzcyLDM1LjU0NSwyNS42NjJjMi43MjcsMTYuODktOC43NzIsMzIuODE3LTI1LjY2MiwzNS41NDUKCUMyNC4wNTEsNjkuMzI5LDguMTI0LDU3LjgzLDUuMzk3LDQwLjk0QzIuNjcsMjQuMDQ5LDE0LjE2OSw4LjEyMiwzMS4wNTksNS4zOTV6Ii8+CjxwYXRoIGZpbGw9IiMwMDY4YzMiIGQ9Ik01NS4zNjQsMTcuMjAyYy01LjA0Ny01LjE5Ny0xMS44MDItOC4xMDktMTkuMDItOC4yYy03LjE5MS0wLjA5LTEzLjk4NSwyLjY2OS0xOS4xNDksNy43NjgKCUMxMS45MSwyMS45ODksOSwyOC45NTUsOSwzNi4zOTJsMC4wNDQsMS4yNmMwLDEuMTgxLDAuOTYxLDIuMTQyLDIuMTQyLDIuMTQyczIuMTQxLTAuOTYsMi4xNDItMi4xNGwwLjAxLTAuOTEzCgljMC05Ljk0NSw4LjQ1My0yMC41OTMsMjEuMDM1LTIwLjU5M2MxMy4xMzIsMCwyMS4yNjEsMTAuNzEyLDIxLjI2MSwyMC42MzdjMCw1LjE3My0yLjA2Myw5LjkxOS01LjgwOCwxMy4zNjMKCUM0Ni4xNyw1My41MDksNDEuMjYsNTUuMzYsMzYsNTUuMzZjLTMuMTMsMC02LjIxOS0wLjc1NS04Ljk1OC0yLjE4NmwxOC44Ny04LjY4NmMxLjI2Ny0wLjU4MywyLjExNS0xLjgxLDIuMjEzLTMuMjAxCglzLTAuNTY5LTIuNzI1LTEuNzU0LTMuNDg3bC0xNS43MDYtOC40MTVjLTAuNTU0LTAuMzU3LTEuMjEzLTAuNDc3LTEuODU4LTAuMzM3Yy0wLjY0NCwwLjEzOS0xLjE5NSwwLjUyMS0xLjU1MiwxLjA3NQoJYy0wLjczNywxLjE0NC0wLjQwNiwyLjY3MywwLjcyMSwzLjM5N2w4LjQ1NCw2LjkyM2wtMTguNDE5LDguNDc4Yy0xLjQyNywwLjY1Ny0yLjI5OCwyLjEtMi4yMTgsMy42NzUKCWMwLjA0OCwwLjk0OSwwLjQ5MywxLjg4NCwxLjI1MywyLjYzNEMyMi4xNTgsNjAuMjY5LDI4Ljg0LDYzLDM1Ljk4NSw2M2MwLjQ0NSwwLDAuODkyLTAuMDExLDEuMzQxLTAuMDMyCgljMTQuMTU2LTAuNjczLDI1LjQzMi0xMi4zMTUsMjUuNjcxLTI2LjUwNUM2My4xMTgsMjkuMjM2LDYwLjQwNywyMi4zOTYsNTUuMzY0LDE3LjIwMnoiLz4KPC9zdmc+Cg=="
|
||||
}
|
||||
"de503f9c-21a4-4f76-b4b7-558eb55c6f89": {
|
||||
"name": "Devolutions",
|
||||
"icon_dark": "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
@ -5617,20 +5617,13 @@
|
||||
"title": "Issuer mode",
|
||||
"description": "Configure how the issuer field of the ID Token should be filled."
|
||||
},
|
||||
"jwt_federation_sources": {
|
||||
"jwks_sources": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"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."
|
||||
},
|
||||
"jwt_federation_providers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"title": "Jwt federation providers"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
@ -5753,7 +5746,7 @@
|
||||
"type": "string",
|
||||
"title": "Cookie domain"
|
||||
},
|
||||
"jwt_federation_sources": {
|
||||
"jwks_sources": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
@ -5761,13 +5754,6 @@
|
||||
},
|
||||
"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": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
|
@ -47,7 +47,7 @@ func checkServer() int {
|
||||
h := &http.Client{
|
||||
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)
|
||||
if err != nil {
|
||||
log.WithError(err).Warning("failed to send healthcheck request")
|
||||
|
@ -61,7 +61,7 @@ var rootCmd = &cobra.Command{
|
||||
ex := common.Init()
|
||||
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 {
|
||||
panic(err)
|
||||
}
|
||||
|
2
go.mod
2
go.mod
@ -27,7 +27,7 @@ require (
|
||||
github.com/sethvargo/go-envconfig v1.1.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
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
|
||||
goauthentik.io/api/v3 v3.2024104.1
|
||||
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
||||
|
4
go.sum
4
go.sum
@ -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.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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
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/go.mod h1:eKm+NrnK7A88l4UBEcYNpZQGMpZRryYKoz4D/0/n1C0=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
|
@ -14,7 +14,6 @@ type Config struct {
|
||||
// Config for both core and outposts
|
||||
Debug bool `yaml:"debug" env:"AUTHENTIK_DEBUG, overwrite"`
|
||||
Listen ListenConfig `yaml:"listen" env:", prefix=AUTHENTIK_LISTEN__"`
|
||||
Web WebConfig `yaml:"web" env:", prefix=AUTHENTIK_WEB__"`
|
||||
|
||||
// Outpost specific config
|
||||
// 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"`
|
||||
DisableEmbeddedOutpost bool `yaml:"disable_embedded_outpost" env:"DISABLE_EMBEDDED_OUTPOST, overwrite"`
|
||||
}
|
||||
|
||||
type WebConfig struct {
|
||||
Path string `yaml:"path" env:"PATH, overwrite"`
|
||||
}
|
||||
|
@ -56,10 +56,10 @@ type APIController struct {
|
||||
func NewAPIController(akURL url.URL, token string) *APIController {
|
||||
rsp := sentry.StartSpan(context.Background(), "authentik.outposts.init")
|
||||
|
||||
apiConfig := api.NewConfiguration()
|
||||
apiConfig.Host = akURL.Host
|
||||
apiConfig.Scheme = akURL.Scheme
|
||||
apiConfig.HTTPClient = &http.Client{
|
||||
config := api.NewConfiguration()
|
||||
config.Host = akURL.Host
|
||||
config.Scheme = akURL.Scheme
|
||||
config.HTTPClient = &http.Client{
|
||||
Transport: web.NewUserAgentTransport(
|
||||
constants.OutpostUserAgent(),
|
||||
web.NewTracingTransport(
|
||||
@ -68,15 +68,10 @@ func NewAPIController(akURL url.URL, token string) *APIController {
|
||||
),
|
||||
),
|
||||
}
|
||||
apiConfig.Servers = api.ServerConfigurations{
|
||||
{
|
||||
URL: fmt.Sprintf("%sapi/v3", akURL.Path),
|
||||
},
|
||||
}
|
||||
apiConfig.AddDefaultHeader("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
config.AddDefaultHeader("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
|
||||
// create the API client, with the transport
|
||||
apiClient := api.NewAPIClient(apiConfig)
|
||||
apiClient := api.NewAPIClient(config)
|
||||
|
||||
log := log.WithField("logger", "authentik.outpost.ak-api-controller")
|
||||
|
||||
|
@ -17,7 +17,7 @@ import (
|
||||
)
|
||||
|
||||
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.Set("instance_uuid", ac.instanceUUID.String())
|
||||
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 {
|
||||
ac.logger.WithError(err).Warning("failed to connect websocket")
|
||||
return err
|
||||
@ -83,7 +83,6 @@ func (ac *APIController) reconnectWS() {
|
||||
u := url.URL{
|
||||
Host: ac.Client.GetConfig().Host,
|
||||
Scheme: ac.Client.GetConfig().Scheme,
|
||||
Path: strings.ReplaceAll(ac.Client.GetConfig().Servers[0].URL, "api/v3", ""),
|
||||
}
|
||||
attempt := 1
|
||||
for {
|
||||
|
@ -46,7 +46,7 @@ func (ws *WebServer) runMetricsServer() {
|
||||
).ServeHTTP(rw, r)
|
||||
|
||||
// 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 {
|
||||
l.WithError(err).Warning("failed to get upstream metrics")
|
||||
return
|
||||
|
@ -9,15 +9,14 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"goauthentik.io/internal/config"
|
||||
"goauthentik.io/internal/utils/sentry"
|
||||
)
|
||||
|
||||
func (ws *WebServer) configureProxy() {
|
||||
// Reverse proxy to the application server
|
||||
director := func(req *http.Request) {
|
||||
req.URL.Scheme = ws.upstreamURL.Scheme
|
||||
req.URL.Host = ws.upstreamURL.Host
|
||||
req.URL.Scheme = ws.ul.Scheme
|
||||
req.URL.Host = ws.ul.Host
|
||||
if _, ok := req.Header["User-Agent"]; !ok {
|
||||
// explicitly disable User-Agent so it's not set to default value
|
||||
req.Header.Set("User-Agent", "")
|
||||
@ -33,10 +32,7 @@ func (ws *WebServer) configureProxy() {
|
||||
}
|
||||
rp.ErrorHandler = ws.proxyErrorHandler
|
||||
rp.ModifyResponse = ws.proxyModifyResponse
|
||||
ws.mainRouter.PathPrefix(config.Get().Web.Path).Path("/-/health/live/").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) {
|
||||
ws.m.PathPrefix("/").HandlerFunc(sentry.SentryNoSample(func(rw http.ResponseWriter, r *http.Request) {
|
||||
if !ws.g.IsRunning() {
|
||||
ws.proxyErrorHandler(rw, r, errors.New("authentik starting"))
|
||||
return
|
||||
|
@ -14,74 +14,46 @@ import (
|
||||
)
|
||||
|
||||
func (ws *WebServer) configureStatic() {
|
||||
// Setup routers
|
||||
staticRouter := ws.loggingRouter.NewRoute().Subrouter()
|
||||
staticRouter := ws.lh.NewRoute().Subrouter()
|
||||
staticRouter.Use(ws.staticHeaderMiddleware)
|
||||
indexLessRouter := staticRouter.NewRoute().Subrouter()
|
||||
// Specifically disable index
|
||||
indexLessRouter.Use(web.DisableIndex)
|
||||
staticRouter.Use(web.DisableIndex)
|
||||
|
||||
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 {
|
||||
h := handler
|
||||
for _, path := range paths {
|
||||
h = http.StripPrefix(path, h)
|
||||
}
|
||||
return h
|
||||
}
|
||||
// Root file paths, from which they should be accessed
|
||||
staticRouter.PathPrefix("/static/dist/").Handler(http.StripPrefix("/static/dist/", distFs))
|
||||
staticRouter.PathPrefix("/static/authentik/").Handler(authentikHandler)
|
||||
|
||||
helpHandler := http.FileServer(http.Dir("./website/help/"))
|
||||
|
||||
indexLessRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/static/dist/").Handler(pathStripper(
|
||||
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) {
|
||||
// Also serve assets folder in specific interfaces since fonts in patternfly are imported
|
||||
// with a relative path
|
||||
staticRouter.PathPrefix("/if/flow/{flow_slug}/assets").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
|
||||
pathStripper(
|
||||
distFs,
|
||||
"if/flow/"+vars["flow_slug"],
|
||||
config.Get().Web.Path,
|
||||
).ServeHTTP(rw, r)
|
||||
web.DisableIndex(http.StripPrefix(fmt.Sprintf("/if/flow/%s", vars["flow_slug"]), distFs)).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))
|
||||
indexLessRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/if/user/assets").Handler(http.StripPrefix(fmt.Sprintf("%sif/user", config.Get().Web.Path), distFs))
|
||||
indexLessRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/if/rac/{app_slug}/assets").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
staticRouter.PathPrefix("/if/admin/assets").Handler(http.StripPrefix("/if/admin", distFs))
|
||||
staticRouter.PathPrefix("/if/user/assets").Handler(http.StripPrefix("/if/user", distFs))
|
||||
staticRouter.PathPrefix("/if/rac/{app_slug}/assets").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
|
||||
pathStripper(
|
||||
distFs,
|
||||
"if/rac/"+vars["app_slug"],
|
||||
config.Get().Web.Path,
|
||||
).ServeHTTP(rw, r)
|
||||
web.DisableIndex(http.StripPrefix(fmt.Sprintf("/if/rac/%s", vars["app_slug"]), distFs)).ServeHTTP(rw, r)
|
||||
})
|
||||
|
||||
// Media files, if backend is file
|
||||
if config.Get().Storage.Media.Backend == "file" {
|
||||
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) {
|
||||
w.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox")
|
||||
fsMedia.ServeHTTP(w, r)
|
||||
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")
|
||||
fsMedia.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
staticRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/if/help/").Handler(pathStripper(
|
||||
helpHandler,
|
||||
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("/if/help/").Handler(http.StripPrefix("/if/help/", http.FileServer(http.Dir("./website/help/"))))
|
||||
staticRouter.PathPrefix("/help").Handler(http.RedirectHandler("/if/help/", 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.WriteHeader(200)
|
||||
_, err := rw.Write(staticWeb.RobotsTxt)
|
||||
@ -89,7 +61,7 @@ func (ws *WebServer) configureStatic() {
|
||||
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.WriteHeader(200)
|
||||
_, err := rw.Write(staticWeb.SecurityTxt)
|
||||
|
@ -33,13 +33,13 @@ type WebServer struct {
|
||||
ProxyServer *proxyv2.ProxyServer
|
||||
BrandTLS *brand_tls.Watcher
|
||||
|
||||
g *gounicorn.GoUnicorn
|
||||
gunicornReady bool
|
||||
mainRouter *mux.Router
|
||||
loggingRouter *mux.Router
|
||||
log *log.Entry
|
||||
upstreamClient *http.Client
|
||||
upstreamURL *url.URL
|
||||
g *gounicorn.GoUnicorn
|
||||
gr bool
|
||||
m *mux.Router
|
||||
lh *mux.Router
|
||||
log *log.Entry
|
||||
uc *http.Client
|
||||
ul *url.URL
|
||||
}
|
||||
|
||||
const UnixSocketName = "authentik-core.sock"
|
||||
@ -73,22 +73,17 @@ func NewWebServer() *WebServer {
|
||||
u, _ := url.Parse("http://localhost:8000")
|
||||
|
||||
ws := &WebServer{
|
||||
mainRouter: mainHandler,
|
||||
loggingRouter: loggingHandler,
|
||||
log: l,
|
||||
gunicornReady: true,
|
||||
upstreamClient: upstreamClient,
|
||||
upstreamURL: u,
|
||||
m: mainHandler,
|
||||
lh: loggingHandler,
|
||||
log: l,
|
||||
gr: true,
|
||||
uc: upstreamClient,
|
||||
ul: u,
|
||||
}
|
||||
ws.configureStatic()
|
||||
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 {
|
||||
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 {
|
||||
ws.log.WithError(err).Warning("failed to create request for healthcheck")
|
||||
return false
|
||||
@ -112,7 +107,7 @@ func (ws *WebServer) Start() {
|
||||
|
||||
func (ws *WebServer) attemptStartBackend() {
|
||||
for {
|
||||
if !ws.gunicornReady {
|
||||
if !ws.gr {
|
||||
return
|
||||
}
|
||||
err := ws.g.Start()
|
||||
@ -140,7 +135,7 @@ func (ws *WebServer) Core() *gounicorn.GoUnicorn {
|
||||
}
|
||||
|
||||
func (ws *WebServer) upstreamHttpClient() *http.Client {
|
||||
return ws.upstreamClient
|
||||
return ws.uc
|
||||
}
|
||||
|
||||
func (ws *WebServer) Shutdown() {
|
||||
@ -165,7 +160,7 @@ func (ws *WebServer) listenPlain() {
|
||||
|
||||
func (ws *WebServer) serve(listener net.Listener) {
|
||||
srv := &http.Server{
|
||||
Handler: ws.mainRouter,
|
||||
Handler: ws.m,
|
||||
}
|
||||
|
||||
// See https://golang.org/pkg/net/http/#Server.Shutdown
|
||||
|
Binary file not shown.
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -73,8 +73,8 @@ msgid "authentik Export - {date}"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/blueprints/v1/tasks.py authentik/crypto/tasks.py
|
||||
#, python-brace-format
|
||||
msgid "Successfully imported {count} files."
|
||||
#, python-format
|
||||
msgid "Successfully imported %(count)d files."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/brands/models.py
|
||||
@ -856,13 +856,13 @@ msgid "Starting full provider sync"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/lib/sync/outgoing/tasks.py
|
||||
#, python-brace-format
|
||||
msgid "Syncing page {page} of users"
|
||||
#, python-format
|
||||
msgid "Syncing page %(page)d of users"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/lib/sync/outgoing/tasks.py
|
||||
#, python-brace-format
|
||||
msgid "Syncing page {page} of groups"
|
||||
#, python-format
|
||||
msgid "Syncing page %(page)d of groups"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/lib/sync/outgoing/tasks.py
|
||||
@ -1012,8 +1012,8 @@ msgid "Event Matcher Policies"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/expiry/models.py
|
||||
#, python-brace-format
|
||||
msgid "Password expired {days} days ago. Please update your password."
|
||||
#, python-format
|
||||
msgid "Password expired %(days)d days ago. Please update your password."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/expiry/models.py
|
||||
@ -1140,8 +1140,8 @@ msgid "Invalid password."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
#, python-brace-format
|
||||
msgid "Password exists on {count} online lists."
|
||||
#, python-format
|
||||
msgid "Password exists on %(count)d online lists."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
@ -1252,11 +1252,6 @@ msgstr ""
|
||||
msgid "Search full LDAP directory"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/api/providers.py
|
||||
#, python-brace-format
|
||||
msgid "Invalid Regex Pattern: {url}"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/id_token.py
|
||||
msgid "Based on the Hashed User ID"
|
||||
msgstr ""
|
||||
@ -1299,14 +1294,6 @@ msgstr ""
|
||||
msgid "Each provider has a different issuer, based on the application slug."
|
||||
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
|
||||
msgid "code (Authorization Code Flow)"
|
||||
msgstr ""
|
||||
@ -1383,6 +1370,10 @@ msgstr ""
|
||||
msgid "Redirect URIs"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/models.py
|
||||
msgid "Enter each URI on a new line."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/models.py
|
||||
msgid "Include claims in id_token"
|
||||
msgstr ""
|
||||
|
@ -11,7 +11,7 @@
|
||||
# Mordecai, 2023
|
||||
# Charles Leclerc, 2024
|
||||
# nerdinator <florian.dupret@gmail.com>, 2024
|
||||
# Tina, 2024
|
||||
# Titouan Petit, 2024
|
||||
# Marc Schmitt, 2024
|
||||
#
|
||||
#, fuzzy
|
||||
@ -19,7 +19,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: Marc Schmitt, 2024\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}"
|
||||
|
||||
#: authentik/blueprints/v1/tasks.py authentik/crypto/tasks.py
|
||||
#, python-brace-format
|
||||
msgid "Successfully imported {count} files."
|
||||
msgstr "{count} fichiers importés avec succès."
|
||||
#, python-format
|
||||
msgid "Successfully imported %(count)d files."
|
||||
msgstr " %(count)d fichiers importés avec succès."
|
||||
|
||||
#: authentik/brands/models.py
|
||||
msgid ""
|
||||
@ -121,10 +121,6 @@ msgstr "Marque"
|
||||
msgid "Brands"
|
||||
msgstr "Marques"
|
||||
|
||||
#: authentik/core/api/devices.py
|
||||
msgid "Extra description not available"
|
||||
msgstr "Description supplémentaire indisponible"
|
||||
|
||||
#: authentik/core/api/providers.py
|
||||
msgid ""
|
||||
"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 "
|
||||
"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
|
||||
msgid "No leading or trailing slashes allowed."
|
||||
msgstr ""
|
||||
@ -942,14 +933,14 @@ msgid "Starting full provider sync"
|
||||
msgstr "Démarrage d'une synchronisation complète du fournisseur"
|
||||
|
||||
#: authentik/lib/sync/outgoing/tasks.py
|
||||
#, python-brace-format
|
||||
msgid "Syncing page {page} of users"
|
||||
msgstr "Synchronisation de la page {page} d'utilisateurs"
|
||||
#, python-format
|
||||
msgid "Syncing page %(page)d of users"
|
||||
msgstr "Synchronisation de la page %(page)d d'utilisateurs"
|
||||
|
||||
#: authentik/lib/sync/outgoing/tasks.py
|
||||
#, python-brace-format
|
||||
msgid "Syncing page {page} of groups"
|
||||
msgstr "Synchronisation de la page {page} de groupes"
|
||||
#, python-format
|
||||
msgid "Syncing page %(page)d of groups"
|
||||
msgstr "Synchronisation de la page %(page)d de groupes"
|
||||
|
||||
#: authentik/lib/sync/outgoing/tasks.py
|
||||
#, python-brace-format
|
||||
@ -1122,11 +1113,11 @@ msgid "Event Matcher Policies"
|
||||
msgstr "Politiques d'association d'évènements"
|
||||
|
||||
#: authentik/policies/expiry/models.py
|
||||
#, python-brace-format
|
||||
msgid "Password expired {days} days ago. Please update your password."
|
||||
#, python-format
|
||||
msgid "Password expired %(days)d days ago. Please update your password."
|
||||
msgstr ""
|
||||
"Mot de passe expiré il y a {days} jours. Merci de mettre à jour votre mot de"
|
||||
" passe."
|
||||
"Mot de passe expiré il y a %(days)d jours. Merci de mettre à jour votre mot "
|
||||
"de passe."
|
||||
|
||||
#: authentik/policies/expiry/models.py
|
||||
msgid "Password has expired."
|
||||
@ -1258,13 +1249,9 @@ msgid "Password not set in context"
|
||||
msgstr "Mot de passe non défini dans le contexte"
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Invalid password."
|
||||
msgstr "Mot de passe invalide."
|
||||
|
||||
#: 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."
|
||||
#, python-format
|
||||
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
|
||||
msgid "Password is too weak."
|
||||
@ -1391,11 +1378,6 @@ msgstr "Fournisseurs LDAP"
|
||||
msgid "Search full LDAP directory"
|
||||
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
|
||||
msgid "Based on the Hashed User ID"
|
||||
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 "
|
||||
"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
|
||||
msgid "code (Authorization Code Flow)"
|
||||
msgstr "code (Authorization Code Flow)"
|
||||
@ -1533,6 +1507,10 @@ msgstr "Secret du client"
|
||||
msgid "Redirect URIs"
|
||||
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
|
||||
msgid "Include claims in id_token"
|
||||
msgstr "Include les demandes utilisateurs dans id_token"
|
||||
@ -2911,8 +2889,13 @@ msgid "Captcha Stages"
|
||||
msgstr "Étapes de Captcha"
|
||||
|
||||
#: authentik/stages/captcha/stage.py
|
||||
msgid "Invalid captcha response. Retrying may solve this issue."
|
||||
msgstr "Réponse captcha invalide. Réessayer peut résoudre ce problème."
|
||||
msgid "Unknown error"
|
||||
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
|
||||
msgid "Invalid captcha response"
|
||||
@ -3579,11 +3562,6 @@ msgstr ""
|
||||
msgid "Globally enable/disable impersonation."
|
||||
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
|
||||
msgid "Default token duration"
|
||||
msgstr "Durée par défaut des jetons"
|
||||
|
Binary file not shown.
@ -15,7 +15,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-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"
|
||||
"Last-Translator: deluxghost, 2024\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}"
|
||||
|
||||
#: authentik/blueprints/v1/tasks.py authentik/crypto/tasks.py
|
||||
#, python-brace-format
|
||||
msgid "Successfully imported {count} files."
|
||||
msgstr "已成功导入 {count} 个文件。"
|
||||
#, python-format
|
||||
msgid "Successfully imported %(count)d files."
|
||||
msgstr "已成功导入 %(count)d 个文件。"
|
||||
|
||||
#: authentik/brands/models.py
|
||||
msgid ""
|
||||
@ -868,14 +868,14 @@ msgid "Starting full provider sync"
|
||||
msgstr "开始全量提供程序同步"
|
||||
|
||||
#: authentik/lib/sync/outgoing/tasks.py
|
||||
#, python-brace-format
|
||||
msgid "Syncing page {page} of users"
|
||||
msgstr "正在同步用户页面 {page}"
|
||||
#, python-format
|
||||
msgid "Syncing page %(page)d of users"
|
||||
msgstr "正在同步用户页面 %(page)d"
|
||||
|
||||
#: authentik/lib/sync/outgoing/tasks.py
|
||||
#, python-brace-format
|
||||
msgid "Syncing page {page} of groups"
|
||||
msgstr "正在同步群组页面 {page}"
|
||||
#, python-format
|
||||
msgid "Syncing page %(page)d of groups"
|
||||
msgstr "正在同步群组页面 %(page)d"
|
||||
|
||||
#: authentik/lib/sync/outgoing/tasks.py
|
||||
#, python-brace-format
|
||||
@ -1026,9 +1026,9 @@ msgid "Event Matcher Policies"
|
||||
msgstr "事件匹配策略"
|
||||
|
||||
#: authentik/policies/expiry/models.py
|
||||
#, python-brace-format
|
||||
msgid "Password expired {days} days ago. Please update your password."
|
||||
msgstr "密码在 {days} 天前过期。请更新您的密码。"
|
||||
#, python-format
|
||||
msgid "Password expired %(days)d days ago. Please update your password."
|
||||
msgstr "密码在 %(days)d 天前过期。请更新您的密码。"
|
||||
|
||||
#: authentik/policies/expiry/models.py
|
||||
msgid "Password has expired."
|
||||
@ -1154,9 +1154,9 @@ msgid "Invalid password."
|
||||
msgstr "无效密码。"
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
#, python-brace-format
|
||||
msgid "Password exists on {count} online lists."
|
||||
msgstr "{count} 个在线列表中存在密码。"
|
||||
#, python-format
|
||||
msgid "Password exists on %(count)d online lists."
|
||||
msgstr "%(count)d 个在线列表中存在密码。"
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Password is too weak."
|
||||
@ -1275,11 +1275,6 @@ msgstr "LDAP 提供程序"
|
||||
msgid "Search full LDAP directory"
|
||||
msgstr "搜索完整 LDAP 目录"
|
||||
|
||||
#: authentik/providers/oauth2/api/providers.py
|
||||
#, python-brace-format
|
||||
msgid "Invalid Regex Pattern: {url}"
|
||||
msgstr "无效的正则表达式模式:{url}"
|
||||
|
||||
#: authentik/providers/oauth2/id_token.py
|
||||
msgid "Based on the Hashed User ID"
|
||||
msgstr "基于经过哈希处理的用户 ID"
|
||||
@ -1322,14 +1317,6 @@ msgstr "所有提供程序都使用相同的标识符"
|
||||
msgid "Each provider has a different issuer, based on the application 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
|
||||
msgid "code (Authorization Code Flow)"
|
||||
msgstr "code(授权码流程)"
|
||||
@ -1406,6 +1393,10 @@ msgstr "客户端密钥"
|
||||
msgid "Redirect URIs"
|
||||
msgstr "重定向 URI"
|
||||
|
||||
#: authentik/providers/oauth2/models.py
|
||||
msgid "Enter each URI on a new line."
|
||||
msgstr "每行输入一个 URI。"
|
||||
|
||||
#: authentik/providers/oauth2/models.py
|
||||
msgid "Include claims in id_token"
|
||||
msgstr "在 id_token 中包含声明"
|
||||
|
@ -14,7 +14,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: deluxghost, 2024\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}"
|
||||
|
||||
#: authentik/blueprints/v1/tasks.py authentik/crypto/tasks.py
|
||||
#, python-brace-format
|
||||
msgid "Successfully imported {count} files."
|
||||
msgstr "已成功导入 {count} 个文件。"
|
||||
#, python-format
|
||||
msgid "Successfully imported %(count)d files."
|
||||
msgstr "已成功导入 %(count)d 个文件。"
|
||||
|
||||
#: authentik/brands/models.py
|
||||
msgid ""
|
||||
@ -867,14 +867,14 @@ msgid "Starting full provider sync"
|
||||
msgstr "开始全量提供程序同步"
|
||||
|
||||
#: authentik/lib/sync/outgoing/tasks.py
|
||||
#, python-brace-format
|
||||
msgid "Syncing page {page} of users"
|
||||
msgstr "正在同步用户页面 {page}"
|
||||
#, python-format
|
||||
msgid "Syncing page %(page)d of users"
|
||||
msgstr "正在同步用户页面 %(page)d"
|
||||
|
||||
#: authentik/lib/sync/outgoing/tasks.py
|
||||
#, python-brace-format
|
||||
msgid "Syncing page {page} of groups"
|
||||
msgstr "正在同步群组页面 {page}"
|
||||
#, python-format
|
||||
msgid "Syncing page %(page)d of groups"
|
||||
msgstr "正在同步群组页面 %(page)d"
|
||||
|
||||
#: authentik/lib/sync/outgoing/tasks.py
|
||||
#, python-brace-format
|
||||
@ -1025,9 +1025,9 @@ msgid "Event Matcher Policies"
|
||||
msgstr "事件匹配策略"
|
||||
|
||||
#: authentik/policies/expiry/models.py
|
||||
#, python-brace-format
|
||||
msgid "Password expired {days} days ago. Please update your password."
|
||||
msgstr "密码在 {days} 天前过期。请更新您的密码。"
|
||||
#, python-format
|
||||
msgid "Password expired %(days)d days ago. Please update your password."
|
||||
msgstr "密码在 %(days)d 天前过期。请更新您的密码。"
|
||||
|
||||
#: authentik/policies/expiry/models.py
|
||||
msgid "Password has expired."
|
||||
@ -1153,9 +1153,9 @@ msgid "Invalid password."
|
||||
msgstr "无效密码。"
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
#, python-brace-format
|
||||
msgid "Password exists on {count} online lists."
|
||||
msgstr "{count} 个在线列表中存在密码。"
|
||||
#, python-format
|
||||
msgid "Password exists on %(count)d online lists."
|
||||
msgstr "%(count)d 个在线列表中存在密码。"
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Password is too weak."
|
||||
@ -1274,11 +1274,6 @@ msgstr "LDAP 提供程序"
|
||||
msgid "Search full LDAP directory"
|
||||
msgstr "搜索完整 LDAP 目录"
|
||||
|
||||
#: authentik/providers/oauth2/api/providers.py
|
||||
#, python-brace-format
|
||||
msgid "Invalid Regex Pattern: {url}"
|
||||
msgstr "无效的正则表达式模式:{url}"
|
||||
|
||||
#: authentik/providers/oauth2/id_token.py
|
||||
msgid "Based on the Hashed User ID"
|
||||
msgstr "基于经过哈希处理的用户 ID"
|
||||
@ -1321,14 +1316,6 @@ msgstr "所有提供程序都使用相同的标识符"
|
||||
msgid "Each provider has a different issuer, based on the application 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
|
||||
msgid "code (Authorization Code Flow)"
|
||||
msgstr "code(授权码流程)"
|
||||
@ -1405,6 +1392,10 @@ msgstr "客户端密钥"
|
||||
msgid "Redirect URIs"
|
||||
msgstr "重定向 URI"
|
||||
|
||||
#: authentik/providers/oauth2/models.py
|
||||
msgid "Enter each URI on a new line."
|
||||
msgstr "每行输入一个 URI。"
|
||||
|
||||
#: authentik/providers/oauth2/models.py
|
||||
msgid "Include claims in id_token"
|
||||
msgstr "在 id_token 中包含声明"
|
||||
|
600
poetry.lock
generated
600
poetry.lock
generated
@ -342,91 +342,6 @@ six = "*"
|
||||
[package.extras]
|
||||
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]]
|
||||
name = "azure-core"
|
||||
version = "1.30.2"
|
||||
@ -466,13 +381,13 @@ typing-extensions = ">=4.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "bandit"
|
||||
version = "1.8.0"
|
||||
version = "1.7.10"
|
||||
description = "Security oriented static analyser for python code."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "bandit-1.8.0-py3-none-any.whl", hash = "sha256:b1a61d829c0968aed625381e426aa378904b996529d048f8d908fa28f6b13e38"},
|
||||
{file = "bandit-1.8.0.tar.gz", hash = "sha256:b5bfe55a095abd9fe20099178a7c6c060f844bfd4fe4c76d28e35e4c52b9d31e"},
|
||||
{file = "bandit-1.7.10-py3-none-any.whl", hash = "sha256:665721d7bebbb4485a339c55161ac0eedde27d51e638000d91c8c2d68343ad02"},
|
||||
{file = "bandit-1.7.10.tar.gz", hash = "sha256:59ed5caf5d92b6ada4bf65bc6437feea4a9da1093384445fed4d472acc6cff7b"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -643,30 +558,6 @@ files = [
|
||||
{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]]
|
||||
name = "cbor2"
|
||||
version = "5.6.5"
|
||||
@ -1098,91 +989,75 @@ files = [
|
||||
{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]]
|
||||
name = "coverage"
|
||||
version = "7.6.8"
|
||||
version = "7.6.7"
|
||||
description = "Code coverage measurement for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "coverage-7.6.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b39e6011cd06822eb964d038d5dff5da5d98652b81f5ecd439277b32361a3a50"},
|
||||
{file = "coverage-7.6.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:63c19702db10ad79151a059d2d6336fe0c470f2e18d0d4d1a57f7f9713875dcf"},
|
||||
{file = "coverage-7.6.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3985b9be361d8fb6b2d1adc9924d01dec575a1d7453a14cccd73225cb79243ee"},
|
||||
{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.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.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e19122296822deafce89a0c5e8685704c067ae65d45e79718c92df7b3ec3d331"},
|
||||
{file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:13618bed0c38acc418896005732e565b317aa9e98d855a0e9f211a7ffc2d6638"},
|
||||
{file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:193e3bffca48ad74b8c764fb4492dd875038a2f9925530cb094db92bb5e47bed"},
|
||||
{file = "coverage-7.6.8-cp310-cp310-win32.whl", hash = "sha256:3988665ee376abce49613701336544041f2117de7b7fbfe91b93d8ff8b151c8e"},
|
||||
{file = "coverage-7.6.8-cp310-cp310-win_amd64.whl", hash = "sha256:f56f49b2553d7dd85fd86e029515a221e5c1f8cb3d9c38b470bc38bde7b8445a"},
|
||||
{file = "coverage-7.6.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:86cffe9c6dfcfe22e28027069725c7f57f4b868a3f86e81d1c62462764dc46d4"},
|
||||
{file = "coverage-7.6.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d82ab6816c3277dc962cfcdc85b1efa0e5f50fb2c449432deaf2398a2928ab94"},
|
||||
{file = "coverage-7.6.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13690e923a3932e4fad4c0ebfb9cb5988e03d9dcb4c5150b5fcbf58fd8bddfc4"},
|
||||
{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.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.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:768939f7c4353c0fac2f7c37897e10b1414b571fd85dd9fc49e6a87e37a2e0d8"},
|
||||
{file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e44961e36cb13c495806d4cac67640ac2866cb99044e210895b506c26ee63d3a"},
|
||||
{file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ea8bb1ab9558374c0ab591783808511d135a833c3ca64a18ec927f20c4030f0"},
|
||||
{file = "coverage-7.6.8-cp311-cp311-win32.whl", hash = "sha256:629a1ba2115dce8bf75a5cce9f2486ae483cb89c0145795603d6554bdc83e801"},
|
||||
{file = "coverage-7.6.8-cp311-cp311-win_amd64.whl", hash = "sha256:fb9fc32399dca861584d96eccd6c980b69bbcd7c228d06fb74fe53e007aa8ef9"},
|
||||
{file = "coverage-7.6.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e683e6ecc587643f8cde8f5da6768e9d165cd31edf39ee90ed7034f9ca0eefee"},
|
||||
{file = "coverage-7.6.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1defe91d41ce1bd44b40fabf071e6a01a5aa14de4a31b986aa9dfd1b3e3e414a"},
|
||||
{file = "coverage-7.6.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7ad66e8e50225ebf4236368cc43c37f59d5e6728f15f6e258c8639fa0dd8e6d"},
|
||||
{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.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.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4674f0daa1823c295845b6a740d98a840d7a1c11df00d1fd62614545c1583787"},
|
||||
{file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:74610105ebd6f33d7c10f8907afed696e79c59e3043c5f20eaa3a46fddf33b4c"},
|
||||
{file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37cda8712145917105e07aab96388ae76e787270ec04bcb9d5cc786d7cbb8443"},
|
||||
{file = "coverage-7.6.8-cp312-cp312-win32.whl", hash = "sha256:9e89d5c8509fbd6c03d0dd1972925b22f50db0792ce06324ba069f10787429ad"},
|
||||
{file = "coverage-7.6.8-cp312-cp312-win_amd64.whl", hash = "sha256:379c111d3558272a2cae3d8e57e6b6e6f4fe652905692d54bad5ea0ca37c5ad4"},
|
||||
{file = "coverage-7.6.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b0c69f4f724c64dfbfe79f5dfb503b42fe6127b8d479b2677f2b227478db2eb"},
|
||||
{file = "coverage-7.6.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c15b32a7aca8038ed7644f854bf17b663bc38e1671b5d6f43f9a2b2bd0c46f63"},
|
||||
{file = "coverage-7.6.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63068a11171e4276f6ece913bde059e77c713b48c3a848814a6537f35afb8365"},
|
||||
{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.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.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9ebfb2507751f7196995142f057d1324afdab56db1d9743aab7f50289abd022"},
|
||||
{file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c1b4474beee02ede1eef86c25ad4600a424fe36cff01a6103cb4533c6bf0169e"},
|
||||
{file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d9fd2547e6decdbf985d579cf3fc78e4c1d662b9b0ff7cc7862baaab71c9cc5b"},
|
||||
{file = "coverage-7.6.8-cp313-cp313-win32.whl", hash = "sha256:8aae5aea53cbfe024919715eca696b1a3201886ce83790537d1c3668459c7146"},
|
||||
{file = "coverage-7.6.8-cp313-cp313-win_amd64.whl", hash = "sha256:ae270e79f7e169ccfe23284ff5ea2d52a6f401dc01b337efb54b3783e2ce3f28"},
|
||||
{file = "coverage-7.6.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de38add67a0af869b0d79c525d3e4588ac1ffa92f39116dbe0ed9753f26eba7d"},
|
||||
{file = "coverage-7.6.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b07c25d52b1c16ce5de088046cd2432b30f9ad5e224ff17c8f496d9cb7d1d451"},
|
||||
{file = "coverage-7.6.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62a66ff235e4c2e37ed3b6104d8b478d767ff73838d1222132a7a026aa548764"},
|
||||
{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.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.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a7b8ac36fd688c8361cbc7bf1cb5866977ece6e0b17c34aa0df58bda4fa18a4"},
|
||||
{file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:38c51297b35b3ed91670e1e4efb702b790002e3245a28c76e627478aa3c10d83"},
|
||||
{file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2e4e0f60cb4bd7396108823548e82fdab72d4d8a65e58e2c19bbbc2f1e2bfa4b"},
|
||||
{file = "coverage-7.6.8-cp313-cp313t-win32.whl", hash = "sha256:6535d996f6537ecb298b4e287a855f37deaf64ff007162ec0afb9ab8ba3b8b71"},
|
||||
{file = "coverage-7.6.8-cp313-cp313t-win_amd64.whl", hash = "sha256:c79c0685f142ca53256722a384540832420dff4ab15fec1863d7e5bc8691bdcc"},
|
||||
{file = "coverage-7.6.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ac47fa29d8d41059ea3df65bd3ade92f97ee4910ed638e87075b8e8ce69599e"},
|
||||
{file = "coverage-7.6.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:24eda3a24a38157eee639ca9afe45eefa8d2420d49468819ac5f88b10de84f4c"},
|
||||
{file = "coverage-7.6.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4c81ed2820b9023a9a90717020315e63b17b18c274a332e3b6437d7ff70abe0"},
|
||||
{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.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.8-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:716a78a342679cd1177bc8c2fe957e0ab91405bd43a17094324845200b2fddf4"},
|
||||
{file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:177f01eeaa3aee4a5ffb0d1439c5952b53d5010f86e9d2667963e632e30082cc"},
|
||||
{file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:912e95017ff51dc3d7b6e2be158dedc889d9a5cc3382445589ce554f1a34c0ea"},
|
||||
{file = "coverage-7.6.8-cp39-cp39-win32.whl", hash = "sha256:4db3ed6a907b555e57cc2e6f14dc3a4c2458cdad8919e40b5357ab9b6db6c43e"},
|
||||
{file = "coverage-7.6.8-cp39-cp39-win_amd64.whl", hash = "sha256:428ac484592f780e8cd7b6b14eb568f7c85460c92e2a37cb0c0e5186e1a0d076"},
|
||||
{file = "coverage-7.6.8-pp39.pp310-none-any.whl", hash = "sha256:5c52a036535d12590c32c49209e79cabaad9f9ad8aa4cbd875b68c4d67a9cbce"},
|
||||
{file = "coverage-7.6.8.tar.gz", hash = "sha256:8b2b8503edb06822c86d82fa64a4a5cb0760bb8f31f26e138ec743f422f37cfc"},
|
||||
{file = "coverage-7.6.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:108bb458827765d538abcbf8288599fee07d2743357bdd9b9dad456c287e121e"},
|
||||
{file = "coverage-7.6.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c973b2fe4dc445cb865ab369df7521df9c27bf40715c837a113edaa2aa9faf45"},
|
||||
{file = "coverage-7.6.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c6b24007c4bcd0b19fac25763a7cac5035c735ae017e9a349b927cfc88f31c1"},
|
||||
{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.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.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:34d23e28ccb26236718a3a78ba72744212aa383141961dd6825f6595005c8b06"},
|
||||
{file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e25bacb53a8c7325e34d45dddd2f2fbae0dbc230d0e2642e264a64e17322a777"},
|
||||
{file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af05bbba896c4472a29408455fe31b3797b4d8648ed0a2ccac03e074a77e2314"},
|
||||
{file = "coverage-7.6.7-cp310-cp310-win32.whl", hash = "sha256:796c9b107d11d2d69e1849b2dfe41730134b526a49d3acb98ca02f4985eeff7a"},
|
||||
{file = "coverage-7.6.7-cp310-cp310-win_amd64.whl", hash = "sha256:987a8e3da7da4eed10a20491cf790589a8e5e07656b6dc22d3814c4d88faf163"},
|
||||
{file = "coverage-7.6.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7e61b0e77ff4dddebb35a0e8bb5a68bf0f8b872407d8d9f0c726b65dfabe2469"},
|
||||
{file = "coverage-7.6.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a5407a75ca4abc20d6252efeb238377a71ce7bda849c26c7a9bece8680a5d99"},
|
||||
{file = "coverage-7.6.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df002e59f2d29e889c37abd0b9ee0d0e6e38c24f5f55d71ff0e09e3412a340ec"},
|
||||
{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.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.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:60dcf7605c50ea72a14490d0756daffef77a5be15ed1b9fea468b1c7bda1bc3b"},
|
||||
{file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9c2eb378bebb2c8f65befcb5147877fc1c9fbc640fc0aad3add759b5df79d55d"},
|
||||
{file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c0317288f032221d35fa4cbc35d9f4923ff0dfd176c79c9b356e8ef8ef2dff4"},
|
||||
{file = "coverage-7.6.7-cp311-cp311-win32.whl", hash = "sha256:951aade8297358f3618a6e0660dc74f6b52233c42089d28525749fc8267dccd2"},
|
||||
{file = "coverage-7.6.7-cp311-cp311-win_amd64.whl", hash = "sha256:5e444b8e88339a2a67ce07d41faabb1d60d1004820cee5a2c2b54e2d8e429a0f"},
|
||||
{file = "coverage-7.6.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f07ff574986bc3edb80e2c36391678a271d555f91fd1d332a1e0f4b5ea4b6ea9"},
|
||||
{file = "coverage-7.6.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:49ed5ee4109258973630c1f9d099c7e72c5c36605029f3a91fe9982c6076c82b"},
|
||||
{file = "coverage-7.6.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3e8796434a8106b3ac025fd15417315d7a58ee3e600ad4dbcfddc3f4b14342c"},
|
||||
{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.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.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0266b62cbea568bd5e93a4da364d05de422110cbed5056d69339bd5af5685433"},
|
||||
{file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e5f2a0f161d126ccc7038f1f3029184dbdf8f018230af17ef6fd6a707a5b881f"},
|
||||
{file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c132b5a22821f9b143f87446805e13580b67c670a548b96da945a8f6b4f2efbb"},
|
||||
{file = "coverage-7.6.7-cp312-cp312-win32.whl", hash = "sha256:7c07de0d2a110f02af30883cd7dddbe704887617d5c27cf373362667445a4c76"},
|
||||
{file = "coverage-7.6.7-cp312-cp312-win_amd64.whl", hash = "sha256:fd49c01e5057a451c30c9b892948976f5d38f2cbd04dc556a82743ba8e27ed8c"},
|
||||
{file = "coverage-7.6.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:46f21663e358beae6b368429ffadf14ed0a329996248a847a4322fb2e35d64d3"},
|
||||
{file = "coverage-7.6.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:40cca284c7c310d622a1677f105e8507441d1bb7c226f41978ba7c86979609ab"},
|
||||
{file = "coverage-7.6.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77256ad2345c29fe59ae861aa11cfc74579c88d4e8dbf121cbe46b8e32aec808"},
|
||||
{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.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.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdd94501d65adc5c24f8a1a0eda110452ba62b3f4aeaba01e021c1ed9cb8f34a"},
|
||||
{file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:82c809a62e953867cf57e0548c2b8464207f5f3a6ff0e1e961683e79b89f2c55"},
|
||||
{file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bb684694e99d0b791a43e9fc0fa58efc15ec357ac48d25b619f207c41f2fd384"},
|
||||
{file = "coverage-7.6.7-cp313-cp313-win32.whl", hash = "sha256:963e4a08cbb0af6623e61492c0ec4c0ec5c5cf74db5f6564f98248d27ee57d30"},
|
||||
{file = "coverage-7.6.7-cp313-cp313-win_amd64.whl", hash = "sha256:14045b8bfd5909196a90da145a37f9d335a5d988a83db34e80f41e965fb7cb42"},
|
||||
{file = "coverage-7.6.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f2c7a045eef561e9544359a0bf5784b44e55cefc7261a20e730baa9220c83413"},
|
||||
{file = "coverage-7.6.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dd4e4a49d9c72a38d18d641135d2fb0bdf7b726ca60a103836b3d00a1182acd"},
|
||||
{file = "coverage-7.6.7-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c95e0fa3d1547cb6f021ab72f5c23402da2358beec0a8e6d19a368bd7b0fb37"},
|
||||
{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.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.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0573f5cbf39114270842d01872952d301027d2d6e2d84013f30966313cadb529"},
|
||||
{file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:e2c8e3384c12dfa19fa9a52f23eb091a8fad93b5b81a41b14c17c78e23dd1d8b"},
|
||||
{file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:70a56a2ec1869e6e9fa69ef6b76b1a8a7ef709972b9cc473f9ce9d26b5997ce3"},
|
||||
{file = "coverage-7.6.7-cp313-cp313t-win32.whl", hash = "sha256:dbba8210f5067398b2c4d96b4e64d8fb943644d5eb70be0d989067c8ca40c0f8"},
|
||||
{file = "coverage-7.6.7-cp313-cp313t-win_amd64.whl", hash = "sha256:dfd14bcae0c94004baba5184d1c935ae0d1231b8409eb6c103a5fd75e8ecdc56"},
|
||||
{file = "coverage-7.6.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37a15573f988b67f7348916077c6d8ad43adb75e478d0910957394df397d2874"},
|
||||
{file = "coverage-7.6.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b6cce5c76985f81da3769c52203ee94722cd5d5889731cd70d31fee939b74bf0"},
|
||||
{file = "coverage-7.6.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ab9763d291a17b527ac6fd11d1a9a9c358280adb320e9c2672a97af346ac2c"},
|
||||
{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.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.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2bc3e45c16564cc72de09e37413262b9f99167803e5e48c6156bccdfb22c8327"},
|
||||
{file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:623e6965dcf4e28a3debaa6fcf4b99ee06d27218f46d43befe4db1c70841551c"},
|
||||
{file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:850cfd2d6fc26f8346f422920ac204e1d28814e32e3a58c19c91980fa74d8289"},
|
||||
{file = "coverage-7.6.7-cp39-cp39-win32.whl", hash = "sha256:c296263093f099da4f51b3dff1eff5d4959b527d4f2f419e16508c5da9e15e8c"},
|
||||
{file = "coverage-7.6.7-cp39-cp39-win_amd64.whl", hash = "sha256:90746521206c88bdb305a4bf3342b1b7316ab80f804d40c536fc7d329301ee13"},
|
||||
{file = "coverage-7.6.7-pp39.pp310-none-any.whl", hash = "sha256:0ddcb70b3a3a57581b450571b31cb774f23eb9519c2aaa6176d3a84c9fc57671"},
|
||||
{file = "coverage-7.6.7.tar.gz", hash = "sha256:d79d4826e41441c9a118ff045e4bccb9fdbdcb1d02413e7ea6eb5c87b5439d24"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@ -1190,53 +1065,51 @@ toml = ["tomli"]
|
||||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "44.0.0"
|
||||
version = "43.0.3"
|
||||
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
|
||||
optional = false
|
||||
python-versions = "!=3.9.0,!=3.9.1,>=3.7"
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "cryptography-44.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123"},
|
||||
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092"},
|
||||
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f"},
|
||||
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb"},
|
||||
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b"},
|
||||
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543"},
|
||||
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:60eb32934076fa07e4316b7b2742fa52cbb190b42c2df2863dbc4230a0a9b385"},
|
||||
{file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e"},
|
||||
{file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e"},
|
||||
{file = "cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053"},
|
||||
{file = "cryptography-44.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd"},
|
||||
{file = "cryptography-44.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591"},
|
||||
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7"},
|
||||
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc"},
|
||||
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289"},
|
||||
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7"},
|
||||
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c"},
|
||||
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9abcc2e083cbe8dde89124a47e5e53ec38751f0d7dfd36801008f316a127d7ba"},
|
||||
{file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64"},
|
||||
{file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285"},
|
||||
{file = "cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417"},
|
||||
{file = "cryptography-44.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede"},
|
||||
{file = "cryptography-44.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:37d76e6863da3774cd9db5b409a9ecfd2c71c981c38788d3fcfaf177f447b731"},
|
||||
{file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:f677e1268c4e23420c3acade68fac427fffcb8d19d7df95ed7ad17cdef8404f4"},
|
||||
{file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f5e7cb1e5e56ca0933b4873c0220a78b773b24d40d186b6738080b73d3d0a756"},
|
||||
{file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:8b3e6eae66cf54701ee7d9c83c30ac0a1e3fa17be486033000f2a73a12ab507c"},
|
||||
{file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:be4ce505894d15d5c5037167ffb7f0ae90b7be6f2a98f9a5c3442395501c32fa"},
|
||||
{file = "cryptography-44.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:62901fb618f74d7d81bf408c8719e9ec14d863086efe4185afd07c352aee1d2c"},
|
||||
{file = "cryptography-44.0.0.tar.gz", hash = "sha256:cd4e834f340b4293430701e772ec543b0fbe6c2dea510a5286fe0acabe153a02"},
|
||||
{file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"},
|
||||
{file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"},
|
||||
{file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f"},
|
||||
{file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6"},
|
||||
{file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18"},
|
||||
{file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd"},
|
||||
{file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73"},
|
||||
{file = "cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2"},
|
||||
{file = "cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd"},
|
||||
{file = "cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984"},
|
||||
{file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5"},
|
||||
{file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4"},
|
||||
{file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7"},
|
||||
{file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405"},
|
||||
{file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16"},
|
||||
{file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73"},
|
||||
{file = "cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995"},
|
||||
{file = "cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362"},
|
||||
{file = "cryptography-43.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c"},
|
||||
{file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3"},
|
||||
{file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83"},
|
||||
{file = "cryptography-43.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7"},
|
||||
{file = "cryptography-43.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664"},
|
||||
{file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08"},
|
||||
{file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa"},
|
||||
{file = "cryptography-43.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff"},
|
||||
{file = "cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""}
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0)"]
|
||||
docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"]
|
||||
nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"]
|
||||
pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"]
|
||||
sdist = ["build (>=1.0.0)"]
|
||||
docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"]
|
||||
docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"]
|
||||
nox = ["nox"]
|
||||
pep8test = ["check-sdist", "click", "mypy", "ruff"]
|
||||
sdist = ["build"]
|
||||
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"]
|
||||
|
||||
[[package]]
|
||||
@ -1670,13 +1543,13 @@ orjson = ">=3.3.0"
|
||||
|
||||
[[package]]
|
||||
name = "drf-spectacular"
|
||||
version = "0.28.0"
|
||||
version = "0.27.2"
|
||||
description = "Sane and flexible OpenAPI 3 schema generation for Django REST framework"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "drf_spectacular-0.28.0-py3-none-any.whl", hash = "sha256:856e7edf1056e49a4245e87a61e8da4baff46c83dbc25be1da2df77f354c7cb4"},
|
||||
{file = "drf_spectacular-0.28.0.tar.gz", hash = "sha256:2c778a47a40ab2f5078a7c42e82baba07397bb35b074ae4680721b2805943061"},
|
||||
{file = "drf-spectacular-0.27.2.tar.gz", hash = "sha256:a199492f2163c4101055075ebdbb037d59c6e0030692fc83a1a8c0fc65929981"},
|
||||
{file = "drf_spectacular-0.27.2-py3-none-any.whl", hash = "sha256:b1c04bf8b2fbbeaf6f59414b4ea448c8787aba4d32f76055c3b13335cf7ec37b"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -1747,13 +1620,13 @@ idna = ">=2.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "fido2"
|
||||
version = "1.2.0"
|
||||
version = "1.1.3"
|
||||
description = "FIDO2/WebAuthn library for implementing clients and servers."
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.8"
|
||||
python-versions = ">=3.8,<4.0"
|
||||
files = [
|
||||
{file = "fido2-1.2.0-py3-none-any.whl", hash = "sha256:f7c8ee62e359aa980a45773f9493965bb29ede1b237a9218169dbfe60c80e130"},
|
||||
{file = "fido2-1.2.0.tar.gz", hash = "sha256:e39f95920122d64283fda5e5581d95a206e704fa42846bfa4662f86aa0d3333b"},
|
||||
{file = "fido2-1.1.3-py3-none-any.whl", hash = "sha256:6be34c0b9fe85e4911fd2d103cce7ae8ce2f064384a7a2a3bd970b3ef7702931"},
|
||||
{file = "fido2-1.1.3.tar.gz", hash = "sha256:26100f226d12ced621ca6198528ce17edf67b78df4287aee1285fee3cd5aa9fc"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -2360,26 +2233,6 @@ files = [
|
||||
{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]]
|
||||
name = "jsonpatch"
|
||||
version = "1.33"
|
||||
@ -3109,13 +2962,13 @@ dev = ["bumpver", "isort", "mypy", "pylint", "pytest", "yapf"]
|
||||
|
||||
[[package]]
|
||||
name = "msgraph-sdk"
|
||||
version = "1.14.0"
|
||||
version = "1.12.0"
|
||||
description = "The Microsoft Graph Python SDK"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "msgraph_sdk-1.14.0-py3-none-any.whl", hash = "sha256:1a2f327dc8fbe5a5e6d0d84cf71d605e7b118b3066b1e16f011ccd8fd927bb03"},
|
||||
{file = "msgraph_sdk-1.14.0.tar.gz", hash = "sha256:5bbda80941c5d1794682753b8b291bd2ebed719a43d6de949fd0cd613b6dfbbd"},
|
||||
{file = "msgraph_sdk-1.12.0-py3-none-any.whl", hash = "sha256:ac298b546b240391b0e407379d039db32862a56d6fe15cf7c5f7e77631fc6771"},
|
||||
{file = "msgraph_sdk-1.12.0.tar.gz", hash = "sha256:fbb5a8a9f6eed89b496f207eb35b6b4cfc7fefa75608aeef07477a3b2276d4fa"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -3834,17 +3687,6 @@ files = [
|
||||
{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]]
|
||||
name = "pyasn1"
|
||||
version = "0.6.0"
|
||||
@ -3883,13 +3725,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.10.2"
|
||||
version = "2.10.1"
|
||||
description = "Data validation using Python type hints"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pydantic-2.10.2-py3-none-any.whl", hash = "sha256:cfb96e45951117c3024e6b67b25cdc33a3cb7b2fa62e239f7af1378358a1d99e"},
|
||||
{file = "pydantic-2.10.2.tar.gz", hash = "sha256:2bc2d7f17232e0841cbba4641e65ba1eb6fafb3a08de3a091ff3ce14a197c4fa"},
|
||||
{file = "pydantic-2.10.1-py3-none-any.whl", hash = "sha256:a8d20db84de64cf4a7d59e899c2caf0fe9d660c7cfc482528e7020d7dd189a7e"},
|
||||
{file = "pydantic-2.10.1.tar.gz", hash = "sha256:a4daca2dc0aa429555e0656d6bf94873a7dc5f54ee42b1f5873d666fb3f35560"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -4047,13 +3889,13 @@ windows-terminal = ["colorama (>=0.4.6)"]
|
||||
|
||||
[[package]]
|
||||
name = "pyjwt"
|
||||
version = "2.10.1"
|
||||
version = "2.10.0"
|
||||
description = "JSON Web Token implementation in Python"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"},
|
||||
{file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"},
|
||||
{file = "PyJWT-2.10.0-py3-none-any.whl", hash = "sha256:543b77207db656de204372350926bed5a86201c4cbff159f623f79c7bb487a15"},
|
||||
{file = "pyjwt-2.10.0.tar.gz", hash = "sha256:7628a7eb7938959ac1b26e819a1df0fd3259505627b575e4bad6d08f76db695c"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -4093,20 +3935,20 @@ tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "pyopenssl"
|
||||
version = "24.3.0"
|
||||
version = "24.2.1"
|
||||
description = "Python wrapper module around the OpenSSL library"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pyOpenSSL-24.3.0-py3-none-any.whl", hash = "sha256:e474f5a473cd7f92221cc04976e48f4d11502804657a08a989fb3be5514c904a"},
|
||||
{file = "pyopenssl-24.3.0.tar.gz", hash = "sha256:49f7a019577d834746bc55c5fce6ecbcec0f2b4ec5ce1cf43a9a173b8138bb36"},
|
||||
{file = "pyOpenSSL-24.2.1-py3-none-any.whl", hash = "sha256:967d5719b12b243588573f39b0c677637145c7a1ffedcd495a487e58177fbb8d"},
|
||||
{file = "pyopenssl-24.2.1.tar.gz", hash = "sha256:4247f0dbe3748d560dcbb2ff3ea01af0f9a1a001ef5f7c4c647956ed8cbf0e95"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
cryptography = ">=41.0.5,<45"
|
||||
cryptography = ">=41.0.5,<44"
|
||||
|
||||
[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"]
|
||||
|
||||
[[package]]
|
||||
@ -4152,13 +3994,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "8.3.4"
|
||||
version = "8.3.3"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"},
|
||||
{file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"},
|
||||
{file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"},
|
||||
{file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -4260,48 +4102,48 @@ cli = ["click (>=5.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "python-kadmin-rs"
|
||||
version = "0.3.0"
|
||||
version = "0.2.0"
|
||||
description = "Python interface to the Kerberos administration interface (kadm5)"
|
||||
optional = false
|
||||
python-versions = "<3.14,>=3.9"
|
||||
files = [
|
||||
{file = "python_kadmin_rs-0.3.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:7b40e2e62ba884182a955c8dbf64f9322cd80b5ea904a269c7099abc6a6536de"},
|
||||
{file = "python_kadmin_rs-0.3.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:055da2fe5e99a0139b85770a182070ee318c1c8a5a2ddbd719aad2eef323d7d3"},
|
||||
{file = "python_kadmin_rs-0.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:387732550dffa699069412e1181d6c066e4421b47a11287d8c42a4f2fcf3669a"},
|
||||
{file = "python_kadmin_rs-0.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:4f57335b57e8cf2a6aecd21c27928294d3c408cf82c4f905fc88709126116165"},
|
||||
{file = "python_kadmin_rs-0.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:800516f5a8b8241adb5e95e11ba2a06b8c99a3219b64b4f28a154fa9abf5f702"},
|
||||
{file = "python_kadmin_rs-0.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b26cf8994e291f8a4d09e367af8c26de8a4fdf092e094a3ca0200c0300b163a1"},
|
||||
{file = "python_kadmin_rs-0.3.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:672866243de0e6e6483450cad2a63b27a6676e64338a9565a472abda5281443c"},
|
||||
{file = "python_kadmin_rs-0.3.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:5d436b5662a6e1e4b946c4842d469d86c4ada67f709f5e4dcd2d99222510dea1"},
|
||||
{file = "python_kadmin_rs-0.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:64003fc8e840d859f343874989d5fee984623324767a954b4c70372e4884970e"},
|
||||
{file = "python_kadmin_rs-0.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:946868516536ce57b282f038d7a168da9c8e494baae82d735f2713c35d2c4ad9"},
|
||||
{file = "python_kadmin_rs-0.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:03a39b6482b02f0909341baadbacf8d939a24f79f0c10747ad9ec45728f88ab6"},
|
||||
{file = "python_kadmin_rs-0.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc75e1bf4df853d361a5b80405d43ffa111f3f1a9e0a680cf341a8e6a5227c49"},
|
||||
{file = "python_kadmin_rs-0.3.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:c074a424d51edc74c5d4263ccf546b6f0c3d9bd458ec9ac6a550bfc75d34da1b"},
|
||||
{file = "python_kadmin_rs-0.3.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:096fd009beff684691e4b1d9df99f31bef951ecaec29f4cfac01e82e609cd34d"},
|
||||
{file = "python_kadmin_rs-0.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:340155c21e6be971b558d032f5201058f39dafa34665a32bdcd9d8d4b8e8e2af"},
|
||||
{file = "python_kadmin_rs-0.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9ea5758fa11e387fab993c1f6af3921386c91c47f487c820a842762b602cee43"},
|
||||
{file = "python_kadmin_rs-0.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e13d369e1f70cd536d799ab2f653a2c65a6574e671cdd7cbfca863391d8ff903"},
|
||||
{file = "python_kadmin_rs-0.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5dc201513e40052d0eab24eb48996776a16a4a9d7c124014d48c09bd66468410"},
|
||||
{file = "python_kadmin_rs-0.3.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:d58ba4e7a2b80fa5483139a008645a317689727a0c24a5b080ef531318f0ef4e"},
|
||||
{file = "python_kadmin_rs-0.3.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:426328d2656f8595df9e1463da08521021a73d423ff6b24fdc6d32fd44d5a3a7"},
|
||||
{file = "python_kadmin_rs-0.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:0760b03075ecdb1ec8f19919c256b4a1e3cb15f451d1face8ceafb1b88d0449f"},
|
||||
{file = "python_kadmin_rs-0.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1c670aa445da67f5168be71a440c6f5e4de5392c123ff863a60fc2ea55e56b7b"},
|
||||
{file = "python_kadmin_rs-0.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:63e5fbc0d1104da050ffedb29a947222fedfc07d762719fd34355feb8d4bc64b"},
|
||||
{file = "python_kadmin_rs-0.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b76bc37d51ecf1dfaa58e9f0134c2c49a75827e5035ab7bcbb228c8188049a24"},
|
||||
{file = "python_kadmin_rs-0.3.0-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:098fc8d748974682c89888708affcca6b438504e268869c13aa62e67cb4b0414"},
|
||||
{file = "python_kadmin_rs-0.3.0-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:cb4561a105f7cfe66d53d580baf45461bcfe682ef39c601aecbf608d57bc417c"},
|
||||
{file = "python_kadmin_rs-0.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:a3632d9bcbaf36fcdb7cf89c5233c05185f51befa617dc74064901c3b4897cd0"},
|
||||
{file = "python_kadmin_rs-0.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:2243eb7482e6f2f3f15caeae42f13e2ca842e78d52a0b3d60fb72c488d2b8143"},
|
||||
{file = "python_kadmin_rs-0.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e6c588c673ecf99437acdbe3d8ee0771be354ce857515f96a32c401174144029"},
|
||||
{file = "python_kadmin_rs-0.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:bb7e497bfe8fdc39383c303050eadaf7a891832ed83545b0b7cfba71099bdddf"},
|
||||
{file = "python_kadmin_rs-0.3.0-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:4b6489498e6cd08b84771891678a9dba05a4917104e3ef1f6d7170e7a6fea0a3"},
|
||||
{file = "python_kadmin_rs-0.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ae0bb5f5d4527cdf163efae7726579b0018849087bf79ca7225f0a4bb753fbbf"},
|
||||
{file = "python_kadmin_rs-0.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3a3c0cfbe8569b2cf40df0bbbeca125de47f35c98ea16620a34fb5eaac958794"},
|
||||
{file = "python_kadmin_rs-0.3.0-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:aba9b60842dbf7bc82a653e89132118f663d7c4239238e6e8419c1983a01e32e"},
|
||||
{file = "python_kadmin_rs-0.3.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2760ff7ff68868155c94bfb62bd7972f8d0604e066eca2a69b82311b2807f5b5"},
|
||||
{file = "python_kadmin_rs-0.3.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:53e74326e05f8651917fab28d56f54e8e25644c87a8068b5c9af45e202fe619f"},
|
||||
{file = "python_kadmin_rs-0.3.0.tar.gz", hash = "sha256:2e39a9069ade69166e92968871dc84a9990d1c1bf8af11cc146ac38dac65f4e8"},
|
||||
{file = "python_kadmin_rs-0.2.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:1fd29e57c165b0b677b5fd1fac6dba829f077125bd081dd061ed2d9431f0c14f"},
|
||||
{file = "python_kadmin_rs-0.2.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:aa1d4ddb2a12fb852a650091e1924c57639b376538c5b23bdd2b00b93472d7a4"},
|
||||
{file = "python_kadmin_rs-0.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:2bdc6011906f074368311bb5be300b0636671549f3d6e918c5275189ee300659"},
|
||||
{file = "python_kadmin_rs-0.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:eb6b967246debe6090293cea35aa8ef159f04c99cb78acfa260b55c00f96cd76"},
|
||||
{file = "python_kadmin_rs-0.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:95aeb75ff0921f01e9d47b02651994421619eaa7fb7f29c9552465134f638377"},
|
||||
{file = "python_kadmin_rs-0.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6285c951f74de5749b9bd43561439e6fe9fe3db5234837ad7d2f0bcd3725887a"},
|
||||
{file = "python_kadmin_rs-0.2.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:1857a02b3f5f63fc4ec7fdaab87951e20517116e8075fedc25fcd235b93e58ce"},
|
||||
{file = "python_kadmin_rs-0.2.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:910ad697934bf5502d810d2e8f8d7231c367c2644f135c7e5fa77afd50f11fae"},
|
||||
{file = "python_kadmin_rs-0.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:d2938d701553433a84148c5679c6285d4b6e30644f9275e93c49b3c7afd75244"},
|
||||
{file = "python_kadmin_rs-0.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:03a28a1522ada645a5a88f96b821d70b141266f4d524779868c82a8bc22e11dc"},
|
||||
{file = "python_kadmin_rs-0.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:532406f48aa1567f8b374f7d6bb3a5ab46b8d4c6047e38fd8336dbbf37aa2a2b"},
|
||||
{file = "python_kadmin_rs-0.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:36786ecc1015ca52f7f9b9b6000d1f9dab21e37f05ddce7cb9b674eb6922270c"},
|
||||
{file = "python_kadmin_rs-0.2.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:be901980ded6077ae17b82af24ef4b7f78fae361aafb3c59cb95a81757c38765"},
|
||||
{file = "python_kadmin_rs-0.2.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:fbce43213bda7e3071725432cbece677ded29e4c0f2227b63227aff8c828ae8b"},
|
||||
{file = "python_kadmin_rs-0.2.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:ce1ca8f6dcbd92ff8d7fd42ab83d7d25ff5e5432ae76ab605df2623cf4d52944"},
|
||||
{file = "python_kadmin_rs-0.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:e97ccf97054399b383b7ad1314b064191e16657cd320e2f4a06f0993e60cd946"},
|
||||
{file = "python_kadmin_rs-0.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:77a017bdb8bf70cbbe599129c5efdb014255dd4a68e4c65825eadb210748ae3f"},
|
||||
{file = "python_kadmin_rs-0.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bdc00198fa573261ad2b60bbaac3d9f0b67adcfa8bb0b49685dedc0f5843c1da"},
|
||||
{file = "python_kadmin_rs-0.2.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e63d95ab5bc4ff12be5614d1928386bbee6d54141533b955f65bc6df9144bf97"},
|
||||
{file = "python_kadmin_rs-0.2.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d266974af2faf4d44b9573f9d2c4b5c815dfb227223e9f019422936e06029224"},
|
||||
{file = "python_kadmin_rs-0.2.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:619a751b283d399edb6a8fa1957d6bc44dc404a9c0f16ed61517a2c288980341"},
|
||||
{file = "python_kadmin_rs-0.2.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6a918aa7df7ae60d31cfefe9de7c05b2acba049af6e6a63fe541f2e43c3e9298"},
|
||||
{file = "python_kadmin_rs-0.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eeebccec2f3d38366c7406418c112ae72ac074581c378f22dcf458703a0e0f65"},
|
||||
{file = "python_kadmin_rs-0.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:81cce25d8257774761c0e7d264ce8f43d90a752f6ebfdb87e17ed42d7199222e"},
|
||||
{file = "python_kadmin_rs-0.2.0-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:9d208448e6c5bb694e3e75f80a46df4a8d5d457da4d50fa8fbdc6039f15bd5f6"},
|
||||
{file = "python_kadmin_rs-0.2.0-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:6fa29bb5873b909852b3f8ec3a79b7d9b79c2367ae1d1b80a2239dbc2d600388"},
|
||||
{file = "python_kadmin_rs-0.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:685dfd62df8da0ce0023af0ff27a18859d5f9b98e4494bd18d6cd792aa10dd19"},
|
||||
{file = "python_kadmin_rs-0.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:4cad0165ac4444d2f38ba9e320039cdc33e47e6cb0f4ed0f5f1da880d6d6f979"},
|
||||
{file = "python_kadmin_rs-0.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:cffe60c6f03612b27da2b0f63514190aadd7c97ae37a60cd3fe558f167c0ceff"},
|
||||
{file = "python_kadmin_rs-0.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7d5a5dc9a38eea43e5579a20564fc071cd8e7b04be7ea6d73e08625087c4f52a"},
|
||||
{file = "python_kadmin_rs-0.2.0-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:8861f7ebf4921f6ef01fac3a44c18b8777703e1bb34c5f90eab9d9edae254e0f"},
|
||||
{file = "python_kadmin_rs-0.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:0c075a82da8bb6d36ba50a0c227816068acb54e08edfaaffda2959a643e1015a"},
|
||||
{file = "python_kadmin_rs-0.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:00c33345be4871e031ed642a3a26fbf20b5133307dd932a8a7a4af2b8b3a4e11"},
|
||||
{file = "python_kadmin_rs-0.2.0-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:798ceca4480cff3b82becb9ce6c8f6fda0daac41e90efc458872b5208608b506"},
|
||||
{file = "python_kadmin_rs-0.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3b9b5a40457649271bf49670a91cd697e831b4515c9f59e923a68bfacb6be884"},
|
||||
{file = "python_kadmin_rs-0.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b74f4b76d1a67fa81615936a945794ea8cfe92ca0718389f6a6e5f89d19a2284"},
|
||||
{file = "python_kadmin_rs-0.2.0.tar.gz", hash = "sha256:9a3e49b7a27f39e6b533a13eb0a0d23f2ecc186d8448d531b858a1dab3226f8a"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4632,29 +4474,29 @@ pyasn1 = ">=0.1.3"
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.8.1"
|
||||
version = "0.7.4"
|
||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "ruff-0.8.1-py3-none-linux_armv6l.whl", hash = "sha256:fae0805bd514066f20309f6742f6ee7904a773eb9e6c17c45d6b1600ca65c9b5"},
|
||||
{file = "ruff-0.8.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8a4f7385c2285c30f34b200ca5511fcc865f17578383db154e098150ce0a087"},
|
||||
{file = "ruff-0.8.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cd054486da0c53e41e0086e1730eb77d1f698154f910e0cd9e0d64274979a209"},
|
||||
{file = "ruff-0.8.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2029b8c22da147c50ae577e621a5bfbc5d1fed75d86af53643d7a7aee1d23871"},
|
||||
{file = "ruff-0.8.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2666520828dee7dfc7e47ee4ea0d928f40de72056d929a7c5292d95071d881d1"},
|
||||
{file = "ruff-0.8.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:333c57013ef8c97a53892aa56042831c372e0bb1785ab7026187b7abd0135ad5"},
|
||||
{file = "ruff-0.8.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:288326162804f34088ac007139488dcb43de590a5ccfec3166396530b58fb89d"},
|
||||
{file = "ruff-0.8.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b12c39b9448632284561cbf4191aa1b005882acbc81900ffa9f9f471c8ff7e26"},
|
||||
{file = "ruff-0.8.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:364e6674450cbac8e998f7b30639040c99d81dfb5bbc6dfad69bc7a8f916b3d1"},
|
||||
{file = "ruff-0.8.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b22346f845fec132aa39cd29acb94451d030c10874408dbf776af3aaeb53284c"},
|
||||
{file = "ruff-0.8.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b2f2f7a7e7648a2bfe6ead4e0a16745db956da0e3a231ad443d2a66a105c04fa"},
|
||||
{file = "ruff-0.8.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:adf314fc458374c25c5c4a4a9270c3e8a6a807b1bec018cfa2813d6546215540"},
|
||||
{file = "ruff-0.8.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a885d68342a231b5ba4d30b8c6e1b1ee3a65cf37e3d29b3c74069cdf1ee1e3c9"},
|
||||
{file = "ruff-0.8.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d2c16e3508c8cc73e96aa5127d0df8913d2290098f776416a4b157657bee44c5"},
|
||||
{file = "ruff-0.8.1-py3-none-win32.whl", hash = "sha256:93335cd7c0eaedb44882d75a7acb7df4b77cd7cd0d2255c93b28791716e81790"},
|
||||
{file = "ruff-0.8.1-py3-none-win_amd64.whl", hash = "sha256:2954cdbe8dfd8ab359d4a30cd971b589d335a44d444b6ca2cb3d1da21b75e4b6"},
|
||||
{file = "ruff-0.8.1-py3-none-win_arm64.whl", hash = "sha256:55873cc1a473e5ac129d15eccb3c008c096b94809d693fc7053f588b67822737"},
|
||||
{file = "ruff-0.8.1.tar.gz", hash = "sha256:3583db9a6450364ed5ca3f3b4225958b24f78178908d5c4bc0f46251ccca898f"},
|
||||
{file = "ruff-0.7.4-py3-none-linux_armv6l.whl", hash = "sha256:a4919925e7684a3f18e18243cd6bea7cfb8e968a6eaa8437971f681b7ec51478"},
|
||||
{file = "ruff-0.7.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cfb365c135b830778dda8c04fb7d4280ed0b984e1aec27f574445231e20d6c63"},
|
||||
{file = "ruff-0.7.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:63a569b36bc66fbadec5beaa539dd81e0527cb258b94e29e0531ce41bacc1f20"},
|
||||
{file = "ruff-0.7.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d06218747d361d06fd2fdac734e7fa92df36df93035db3dc2ad7aa9852cb109"},
|
||||
{file = "ruff-0.7.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e0cea28d0944f74ebc33e9f934238f15c758841f9f5edd180b5315c203293452"},
|
||||
{file = "ruff-0.7.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80094ecd4793c68b2571b128f91754d60f692d64bc0d7272ec9197fdd09bf9ea"},
|
||||
{file = "ruff-0.7.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:997512325c6620d1c4c2b15db49ef59543ef9cd0f4aa8065ec2ae5103cedc7e7"},
|
||||
{file = "ruff-0.7.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00b4cf3a6b5fad6d1a66e7574d78956bbd09abfd6c8a997798f01f5da3d46a05"},
|
||||
{file = "ruff-0.7.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7dbdc7d8274e1422722933d1edddfdc65b4336abf0b16dfcb9dedd6e6a517d06"},
|
||||
{file = "ruff-0.7.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e92dfb5f00eaedb1501b2f906ccabfd67b2355bdf117fea9719fc99ac2145bc"},
|
||||
{file = "ruff-0.7.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3bd726099f277d735dc38900b6a8d6cf070f80828877941983a57bca1cd92172"},
|
||||
{file = "ruff-0.7.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2e32829c429dd081ee5ba39aef436603e5b22335c3d3fff013cd585806a6486a"},
|
||||
{file = "ruff-0.7.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:662a63b4971807623f6f90c1fb664613f67cc182dc4d991471c23c541fee62dd"},
|
||||
{file = "ruff-0.7.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:876f5e09eaae3eb76814c1d3b68879891d6fde4824c015d48e7a7da4cf066a3a"},
|
||||
{file = "ruff-0.7.4-py3-none-win32.whl", hash = "sha256:75c53f54904be42dd52a548728a5b572344b50d9b2873d13a3f8c5e3b91f5cac"},
|
||||
{file = "ruff-0.7.4-py3-none-win_amd64.whl", hash = "sha256:745775c7b39f914238ed1f1b0bebed0b9155a17cd8bc0b08d3c87e4703b990d6"},
|
||||
{file = "ruff-0.7.4-py3-none-win_arm64.whl", hash = "sha256:11bff065102c3ae9d3ea4dc9ecdfe5a5171349cdd0787c1fc64761212fc9cf1f"},
|
||||
{file = "ruff-0.7.4.tar.gz", hash = "sha256:cd12e35031f5af6b9b93715d8c4f40360070b2041f81273d0527683d5708fce2"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4693,13 +4535,13 @@ django-query = ["django (>=3.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "selenium"
|
||||
version = "4.27.1"
|
||||
version = "4.26.1"
|
||||
description = "Official Python bindings for Selenium WebDriver"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "selenium-4.27.1-py3-none-any.whl", hash = "sha256:b89b1f62b5cfe8025868556fe82360d6b649d464f75d2655cb966c8f8447ea18"},
|
||||
{file = "selenium-4.27.1.tar.gz", hash = "sha256:5296c425a75ff1b44d0d5199042b36a6d1ef76c04fb775b97b40be739a9caae2"},
|
||||
{file = "selenium-4.26.1-py3-none-any.whl", hash = "sha256:1db3f3a0cd5bb07624fa8a3905a6fdde1595a42185a0617077c361dc53d104fb"},
|
||||
{file = "selenium-4.26.1.tar.gz", hash = "sha256:7640f3f08ae7f4e450f895678e8a10a55eb4e4ca18311ed675ecc4684b96b683"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -5035,22 +4877,22 @@ celery = "*"
|
||||
|
||||
[[package]]
|
||||
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."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "tornado-6.4.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e828cce1123e9e44ae2a50a9de3055497ab1d0aeb440c5ac23064d9e44880da1"},
|
||||
{file = "tornado-6.4.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:072ce12ada169c5b00b7d92a99ba089447ccc993ea2143c9ede887e0937aa803"},
|
||||
{file = "tornado-6.4.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a017d239bd1bb0919f72af256a970624241f070496635784d9bf0db640d3fec"},
|
||||
{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.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.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:304463bd0772442ff4d0f5149c6f1c2135a1fae045adf070821c6cdc76980634"},
|
||||
{file = "tornado-6.4.2-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:c82c46813ba483a385ab2a99caeaedf92585a1f90defb5693351fa7e4ea0bf73"},
|
||||
{file = "tornado-6.4.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:932d195ca9015956fa502c6b56af9eb06106140d844a335590c1ec7f5277d10c"},
|
||||
{file = "tornado-6.4.2-cp38-abi3-win32.whl", hash = "sha256:2876cef82e6c5978fde1e0d5b1f919d756968d5b4282418f3146b79b58556482"},
|
||||
{file = "tornado-6.4.2-cp38-abi3-win_amd64.whl", hash = "sha256:908b71bf3ff37d81073356a5fadcc660eb10c1476ee6e2725588626ce7e5ca38"},
|
||||
{file = "tornado-6.4.2.tar.gz", hash = "sha256:92bad5b4746e9879fd7bf1eb21dce4e3fc5128d71601f80005afa39237ad620b"},
|
||||
{file = "tornado-6.4.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:163b0aafc8e23d8cdc3c9dfb24c5368af84a81e3364745ccb4427669bf84aec8"},
|
||||
{file = "tornado-6.4.1-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6d5ce3437e18a2b66fbadb183c1d3364fb03f2be71299e7d10dbeeb69f4b2a14"},
|
||||
{file = "tornado-6.4.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e20b9113cd7293f164dc46fffb13535266e713cdb87bd2d15ddb336e96cfc4"},
|
||||
{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.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.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25486eb223babe3eed4b8aecbac33b37e3dd6d776bc730ca14e1bf93888b979f"},
|
||||
{file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:454db8a7ecfcf2ff6042dde58404164d969b6f5d58b926da15e6b23817950fc4"},
|
||||
{file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a02a08cc7a9314b006f653ce40483b9b3c12cda222d6a46d4ac63bb6c9057698"},
|
||||
{file = "tornado-6.4.1-cp38-abi3-win32.whl", hash = "sha256:d9a566c40b89757c9aa8e6f032bcdb8ca8795d7c1a9762910c722b1635c9de4d"},
|
||||
{file = "tornado-6.4.1-cp38-abi3-win_amd64.whl", hash = "sha256:b24b8982ed444378d7f21d563f4180a2de31ced9d8d84443907a0a64da2072e7"},
|
||||
{file = "tornado-6.4.1.tar.gz", hash = "sha256:92d3ab53183d8c50f8204a51e6f91d18a15d5ef261e84d452800d4ff6fc504e9"},
|
||||
]
|
||||
|
||||
[[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"]
|
||||
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]]
|
||||
name = "typing-extensions"
|
||||
version = "4.12.2"
|
||||
@ -5197,36 +5024,15 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "ua-parser"
|
||||
version = "1.0.0"
|
||||
version = "0.18.0"
|
||||
description = "Python port of Browserscope's user agent parser"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "ua_parser-1.0.0-py3-none-any.whl", hash = "sha256:5b31133606a781f56692caa11a9671a9f330c22604b3c4957a7ba18c152212d0"},
|
||||
{file = "ua_parser-1.0.0.tar.gz", hash = "sha256:a9740f53f4fbb72b7a03d304cae32a2785cafc55e8207efb74877bba17c35324"},
|
||||
{file = "ua-parser-0.18.0.tar.gz", hash = "sha256:db51f1b59bfaa82ed9e2a1d99a54d3e4153dddf99ac1435d51828165422e624e"},
|
||||
{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]]
|
||||
name = "uritemplate"
|
||||
version = "4.1.1"
|
||||
@ -5936,4 +5742,4 @@ files = [
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "~3.12"
|
||||
content-hash = "b4eeab86e953e9c6d7e9336003e258668e43f58aeb360b15f56f8d4874fea5c3"
|
||||
content-hash = "2a7712ca513371eb16ff0b87fc80218e982a5a734bba0bb130b8123e807eb16f"
|
||||
|
@ -131,7 +131,7 @@ pydantic-scim = "*"
|
||||
pyjwt = "*"
|
||||
pyrad = "*"
|
||||
python = "~3.12"
|
||||
python-kadmin-rs = "0.3.0"
|
||||
python-kadmin-rs = "0.2.0"
|
||||
pyyaml = "*"
|
||||
requests-oauthlib = "*"
|
||||
scim2-filter-parser = "*"
|
||||
@ -153,14 +153,12 @@ xmlsec = "*"
|
||||
zxcvbn = "*"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
aws-cdk-lib = "*"
|
||||
bandit = "*"
|
||||
black = "*"
|
||||
bump2version = "*"
|
||||
channels = { version = "*", extras = ["daphne"] }
|
||||
codespell = "*"
|
||||
colorama = "*"
|
||||
constructs = "*"
|
||||
coverage = { extras = ["toml"], version = "*" }
|
||||
debugpy = "*"
|
||||
drf-jsonschema-serializer = "*"
|
||||
|
36
schema.yml
36
schema.yml
@ -44785,7 +44785,7 @@ components:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/IssuerModeEnum'
|
||||
description: Configure how the issuer field of the ID Token should be filled.
|
||||
jwt_federation_sources:
|
||||
jwks_sources:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
@ -44793,10 +44793,6 @@ components:
|
||||
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:
|
||||
- assigned_application_name
|
||||
- assigned_application_slug
|
||||
@ -44892,7 +44888,7 @@ components:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/IssuerModeEnum'
|
||||
description: Configure how the issuer field of the ID Token should be filled.
|
||||
jwt_federation_sources:
|
||||
jwks_sources:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
@ -44900,10 +44896,6 @@ components:
|
||||
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:
|
||||
- authorization_flow
|
||||
- invalidation_flow
|
||||
@ -48919,7 +48911,7 @@ components:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/IssuerModeEnum'
|
||||
description: Configure how the issuer field of the ID Token should be filled.
|
||||
jwt_federation_sources:
|
||||
jwks_sources:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
@ -48927,10 +48919,6 @@ components:
|
||||
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:
|
||||
type: object
|
||||
description: OAuthSourcePropertyMapping Serializer
|
||||
@ -49446,7 +49434,7 @@ components:
|
||||
header and authenticate requests based on its value.
|
||||
cookie_domain:
|
||||
type: string
|
||||
jwt_federation_sources:
|
||||
jwks_sources:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
@ -49454,10 +49442,6 @@ components:
|
||||
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:
|
||||
type: string
|
||||
minLength: 1
|
||||
@ -51520,7 +51504,7 @@ components:
|
||||
readOnly: true
|
||||
cookie_domain:
|
||||
type: string
|
||||
jwt_federation_sources:
|
||||
jwks_sources:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
@ -51528,10 +51512,6 @@ components:
|
||||
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:
|
||||
type: string
|
||||
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.
|
||||
cookie_domain:
|
||||
type: string
|
||||
jwt_federation_sources:
|
||||
jwks_sources:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
@ -51640,10 +51620,6 @@ components:
|
||||
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:
|
||||
type: string
|
||||
minLength: 1
|
||||
|
@ -4,4 +4,4 @@ from time import time
|
||||
|
||||
from authentik import __version__
|
||||
|
||||
print(f"{__version__}-{int(time())}")
|
||||
print("%s-%d" % (__version__, time()))
|
||||
|
@ -3,7 +3,6 @@
|
||||
from json import loads
|
||||
from pathlib import Path
|
||||
from time import sleep
|
||||
from unittest import skip
|
||||
|
||||
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
|
||||
self.assertIn("You've logged out of", title)
|
||||
|
||||
@skip("Flaky test")
|
||||
@retry()
|
||||
def test_nginx(self):
|
||||
"""Test nginx"""
|
||||
|
1636
web/package-lock.json
generated
1636
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -11,7 +11,7 @@
|
||||
"@floating-ui/dom": "^1.6.11",
|
||||
"@formatjs/intl-listformat": "^7.5.7",
|
||||
"@fortawesome/fontawesome-free": "^6.6.0",
|
||||
"@goauthentik/api": "^2024.10.4-1733219849",
|
||||
"@goauthentik/api": "^2024.10.4-1732236707",
|
||||
"@lit-labs/ssr": "^3.2.2",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@lit/localize": "^0.12.2",
|
||||
@ -72,7 +72,7 @@
|
||||
"@wdio/cli": "^9.1.2",
|
||||
"@wdio/spec-reporter": "^9.1.2",
|
||||
"chokidar": "^4.0.1",
|
||||
"chromedriver": "^131.0.1",
|
||||
"chromedriver": "^130.0.4",
|
||||
"esbuild": "^0.24.0",
|
||||
"eslint": "^9.11.1",
|
||||
"eslint-plugin-lit": "^1.15.0",
|
||||
@ -135,6 +135,7 @@
|
||||
"storybook:build": "wireit",
|
||||
"storybook:build-import-map": "wireit",
|
||||
"test": "wireit",
|
||||
"test:e2e": "wireit",
|
||||
"test:e2e:watch": "wireit",
|
||||
"test:watch": "wireit",
|
||||
"tsc": "wireit",
|
||||
@ -321,11 +322,24 @@
|
||||
},
|
||||
"test": {
|
||||
"command": "wdio ./wdio.conf.ts --logLevel=warn",
|
||||
"dependencies": [
|
||||
"build"
|
||||
],
|
||||
"env": {
|
||||
"CI": "true",
|
||||
"TS_NODE_PROJECT": "tsconfig.test.json"
|
||||
}
|
||||
},
|
||||
"test:e2e": {
|
||||
"command": "wdio run ./tests/wdio.conf.ts",
|
||||
"dependencies": [
|
||||
"build"
|
||||
],
|
||||
"env": {
|
||||
"CI": "true",
|
||||
"TS_NODE_PROJECT": "./tests/tsconfig.test.json"
|
||||
}
|
||||
},
|
||||
"test:e2e:watch": {
|
||||
"command": "wdio run ./tests/wdio.conf.ts",
|
||||
"dependencies": [
|
||||
|
@ -20,9 +20,6 @@ interface GlobalAuthentik {
|
||||
brand: {
|
||||
branding_logo: string;
|
||||
};
|
||||
api: {
|
||||
base: string;
|
||||
};
|
||||
}
|
||||
|
||||
function ak(): GlobalAuthentik {
|
||||
@ -44,7 +41,7 @@ class SimpleFlowExecutor {
|
||||
}
|
||||
|
||||
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() {
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { EVENT_SIDEBAR_TOGGLE, VERSION } from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { me } from "@goauthentik/common/users";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import {
|
||||
@ -113,7 +112,7 @@ export class AkAdminSidebar extends WithCapabilitiesConfig(AKElement) {
|
||||
|
||||
// prettier-ignore
|
||||
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 }, [
|
||||
["/administration/overview", msg("Overview")],
|
||||
["/administration/dashboard/users", msg("User Statistics")],
|
||||
|
@ -29,7 +29,7 @@ export class ApplicationWizardPageBase
|
||||
return AwadStyles;
|
||||
}
|
||||
|
||||
@consume({ context: applicationWizardContext })
|
||||
@consume({ context: applicationWizardContext, subscribe: true })
|
||||
public wizard!: ApplicationWizardState;
|
||||
|
||||
@query("form")
|
||||
|
@ -1,7 +1,12 @@
|
||||
import { createContext } from "@lit/context";
|
||||
|
||||
import { LocalTypeCreate } from "./auth-method-choice/ak-application-wizard-authentication-method-choice.choices.js";
|
||||
import { ApplicationWizardState } from "./types";
|
||||
|
||||
export const applicationWizardContext = createContext<ApplicationWizardState>(
|
||||
Symbol("ak-application-wizard-state-context"),
|
||||
);
|
||||
|
||||
export const applicationWizardProvidersContext = createContext<LocalTypeCreate[]>(
|
||||
Symbol("ak-application-wizard-providers-context"),
|
||||
);
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { AkWizard } from "@goauthentik/components/ak-wizard-main/AkWizard";
|
||||
import { CustomListenerElement } from "@goauthentik/elements/utils/eventEmitter";
|
||||
|
||||
@ -5,7 +6,10 @@ import { ContextProvider } from "@lit/context";
|
||||
import { msg } from "@lit/localize";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
|
||||
import { applicationWizardContext } from "./ContextIdentity";
|
||||
import { ProvidersApi, ProxyMode } from "@goauthentik/api";
|
||||
|
||||
import { applicationWizardContext, applicationWizardProvidersContext } from "./ContextIdentity";
|
||||
import { providerTypeRenderers } from "./auth-method-choice/ak-application-wizard-authentication-method-choice.choices.js";
|
||||
import { newSteps } from "./steps";
|
||||
import {
|
||||
ApplicationStep,
|
||||
@ -19,6 +23,7 @@ const freshWizardState = (): ApplicationWizardState => ({
|
||||
app: {},
|
||||
provider: {},
|
||||
errors: {},
|
||||
proxyMode: ProxyMode.Proxy,
|
||||
});
|
||||
|
||||
@customElement("ak-application-wizard")
|
||||
@ -46,6 +51,11 @@ export class ApplicationWizard extends CustomListenerElement(
|
||||
initialValue: this.wizardState,
|
||||
});
|
||||
|
||||
wizardProviderProvider = new ContextProvider(this, {
|
||||
context: applicationWizardProvidersContext,
|
||||
initialValue: [],
|
||||
});
|
||||
|
||||
/**
|
||||
* One of our steps has multiple display variants, one for each type of service provider. We
|
||||
* want to *preserve* a customer's decisions about different providers; never make someone "go
|
||||
@ -56,6 +66,21 @@ export class ApplicationWizard extends CustomListenerElement(
|
||||
*/
|
||||
providerCache: Map<string, OneOfProvider> = new Map();
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
new ProvidersApi(DEFAULT_CONFIG).providersAllTypesList().then((providerTypes) => {
|
||||
const wizardReadyProviders = Object.keys(providerTypeRenderers);
|
||||
this.wizardProviderProvider.setValue(
|
||||
providerTypes
|
||||
.filter((providerType) => wizardReadyProviders.includes(providerType.modelName))
|
||||
.map((providerType) => ({
|
||||
...providerType,
|
||||
renderer: providerTypeRenderers[providerType.modelName],
|
||||
})),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// And this is where all the special cases go...
|
||||
handleUpdate(detail: ApplicationWizardStateUpdate) {
|
||||
if (detail.status === "submitted") {
|
||||
|
@ -1,176 +1,28 @@
|
||||
import "@goauthentik/admin/common/ak-license-notice";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { TemplateResult, html } from "lit";
|
||||
|
||||
import type { ProviderModelEnum as ProviderModelEnumType, TypeCreate } from "@goauthentik/api";
|
||||
import { ProviderModelEnum, ProxyMode } from "@goauthentik/api";
|
||||
import type {
|
||||
LDAPProviderRequest,
|
||||
ModelRequest,
|
||||
OAuth2ProviderRequest,
|
||||
ProxyProviderRequest,
|
||||
RACProviderRequest,
|
||||
RadiusProviderRequest,
|
||||
SAMLProviderRequest,
|
||||
SCIMProviderRequest,
|
||||
} from "@goauthentik/api";
|
||||
|
||||
import { OneOfProvider } from "../types";
|
||||
import type { TypeCreate } from "@goauthentik/api";
|
||||
|
||||
type ProviderRenderer = () => TemplateResult;
|
||||
|
||||
type ModelConverter = (provider: OneOfProvider) => ModelRequest;
|
||||
|
||||
type ProviderNoteProvider = () => TemplateResult | undefined;
|
||||
type ProviderNote = ProviderNoteProvider | undefined;
|
||||
|
||||
export type LocalTypeCreate = TypeCreate & {
|
||||
formName: string;
|
||||
modelName: ProviderModelEnumType;
|
||||
converter: ModelConverter;
|
||||
note?: ProviderNote;
|
||||
renderer: ProviderRenderer;
|
||||
};
|
||||
|
||||
export const providerModelsList: LocalTypeCreate[] = [
|
||||
{
|
||||
formName: "oauth2provider",
|
||||
name: msg("OAuth2/OIDC (Open Authorization/OpenID Connect)"),
|
||||
description: msg("Modern applications, APIs and Single-page applications."),
|
||||
renderer: () =>
|
||||
html`<ak-application-wizard-authentication-by-oauth></ak-application-wizard-authentication-by-oauth>`,
|
||||
modelName: ProviderModelEnum.Oauth2Oauth2provider,
|
||||
converter: (provider: OneOfProvider) => ({
|
||||
providerModel: ProviderModelEnum.Oauth2Oauth2provider,
|
||||
...(provider as OAuth2ProviderRequest),
|
||||
}),
|
||||
component: "",
|
||||
iconUrl: "/static/authentik/sources/openidconnect.svg",
|
||||
},
|
||||
{
|
||||
formName: "ldapprovider",
|
||||
name: msg("LDAP (Lightweight Directory Access Protocol)"),
|
||||
description: msg(
|
||||
"Provide an LDAP interface for applications and users to authenticate against.",
|
||||
),
|
||||
renderer: () =>
|
||||
html`<ak-application-wizard-authentication-by-ldap></ak-application-wizard-authentication-by-ldap>`,
|
||||
modelName: ProviderModelEnum.LdapLdapprovider,
|
||||
converter: (provider: OneOfProvider) => ({
|
||||
providerModel: ProviderModelEnum.LdapLdapprovider,
|
||||
...(provider as LDAPProviderRequest),
|
||||
}),
|
||||
component: "",
|
||||
iconUrl: "/static/authentik/sources/ldap.png",
|
||||
},
|
||||
{
|
||||
formName: "proxyprovider-proxy",
|
||||
name: msg("Transparent Reverse Proxy"),
|
||||
description: msg("For transparent reverse proxies with required authentication"),
|
||||
renderer: () =>
|
||||
html`<ak-application-wizard-authentication-for-reverse-proxy></ak-application-wizard-authentication-for-reverse-proxy>`,
|
||||
modelName: ProviderModelEnum.ProxyProxyprovider,
|
||||
converter: (provider: OneOfProvider) => ({
|
||||
providerModel: ProviderModelEnum.ProxyProxyprovider,
|
||||
...(provider as ProxyProviderRequest),
|
||||
mode: ProxyMode.Proxy,
|
||||
}),
|
||||
component: "",
|
||||
iconUrl: "/static/authentik/sources/proxy.svg",
|
||||
},
|
||||
{
|
||||
formName: "proxyprovider-forwardsingle",
|
||||
name: msg("Forward Auth (Single Application)"),
|
||||
description: msg("For nginx's auth_request or traefik's forwardAuth"),
|
||||
renderer: () =>
|
||||
html`<ak-application-wizard-authentication-for-single-forward-proxy></ak-application-wizard-authentication-for-single-forward-proxy>`,
|
||||
modelName: ProviderModelEnum.ProxyProxyprovider,
|
||||
converter: (provider: OneOfProvider) => ({
|
||||
providerModel: ProviderModelEnum.ProxyProxyprovider,
|
||||
...(provider as ProxyProviderRequest),
|
||||
mode: ProxyMode.ForwardSingle,
|
||||
}),
|
||||
component: "",
|
||||
iconUrl: "/static/authentik/sources/proxy.svg",
|
||||
},
|
||||
{
|
||||
formName: "proxyprovider-forwarddomain",
|
||||
name: msg("Forward Auth (Domain Level)"),
|
||||
description: msg("For nginx's auth_request or traefik's forwardAuth per root domain"),
|
||||
renderer: () =>
|
||||
html`<ak-application-wizard-authentication-for-forward-proxy-domain></ak-application-wizard-authentication-for-forward-proxy-domain>`,
|
||||
modelName: ProviderModelEnum.ProxyProxyprovider,
|
||||
converter: (provider: OneOfProvider) => ({
|
||||
providerModel: ProviderModelEnum.ProxyProxyprovider,
|
||||
...(provider as ProxyProviderRequest),
|
||||
mode: ProxyMode.ForwardDomain,
|
||||
}),
|
||||
component: "",
|
||||
iconUrl: "/static/authentik/sources/proxy.svg",
|
||||
},
|
||||
{
|
||||
formName: "racprovider",
|
||||
name: msg("Remote Access Provider"),
|
||||
description: msg("Remotely access computers/servers via RDP/SSH/VNC"),
|
||||
renderer: () =>
|
||||
html`<ak-application-wizard-authentication-for-rac></ak-application-wizard-authentication-for-rac>`,
|
||||
modelName: ProviderModelEnum.RacRacprovider,
|
||||
converter: (provider: OneOfProvider) => ({
|
||||
providerModel: ProviderModelEnum.RacRacprovider,
|
||||
...(provider as RACProviderRequest),
|
||||
}),
|
||||
note: () => html`<ak-license-notice></ak-license-notice>`,
|
||||
requiresEnterprise: true,
|
||||
component: "",
|
||||
iconUrl: "/static/authentik/sources/rac.svg",
|
||||
},
|
||||
{
|
||||
formName: "samlprovider",
|
||||
name: msg("SAML (Security Assertion Markup Language)"),
|
||||
description: msg("Configure SAML provider manually"),
|
||||
renderer: () =>
|
||||
html`<ak-application-wizard-authentication-by-saml-configuration></ak-application-wizard-authentication-by-saml-configuration>`,
|
||||
modelName: ProviderModelEnum.SamlSamlprovider,
|
||||
converter: (provider: OneOfProvider) => ({
|
||||
providerModel: ProviderModelEnum.SamlSamlprovider,
|
||||
...(provider as SAMLProviderRequest),
|
||||
}),
|
||||
component: "",
|
||||
iconUrl: "/static/authentik/sources/saml.png",
|
||||
},
|
||||
{
|
||||
formName: "radiusprovider",
|
||||
name: msg("RADIUS (Remote Authentication Dial-In User Service)"),
|
||||
description: msg("Configure RADIUS provider manually"),
|
||||
renderer: () =>
|
||||
html`<ak-application-wizard-authentication-by-radius></ak-application-wizard-authentication-by-radius>`,
|
||||
modelName: ProviderModelEnum.RadiusRadiusprovider,
|
||||
converter: (provider: OneOfProvider) => ({
|
||||
providerModel: ProviderModelEnum.RadiusRadiusprovider,
|
||||
...(provider as RadiusProviderRequest),
|
||||
}),
|
||||
component: "",
|
||||
iconUrl: "/static/authentik/sources/radius.svg",
|
||||
},
|
||||
{
|
||||
formName: "scimprovider",
|
||||
name: msg("SCIM (System for Cross-domain Identity Management)"),
|
||||
description: msg("Configure SCIM provider manually"),
|
||||
renderer: () =>
|
||||
html`<ak-application-wizard-authentication-by-scim></ak-application-wizard-authentication-by-scim>`,
|
||||
modelName: ProviderModelEnum.ScimScimprovider,
|
||||
converter: (provider: OneOfProvider) => ({
|
||||
providerModel: ProviderModelEnum.ScimScimprovider,
|
||||
...(provider as SCIMProviderRequest),
|
||||
}),
|
||||
component: "",
|
||||
iconUrl: "/static/authentik/sources/scim.png",
|
||||
},
|
||||
];
|
||||
|
||||
export const providerRendererList = new Map<string, ProviderRenderer>(
|
||||
providerModelsList.map((tc) => [tc.formName, tc.renderer]),
|
||||
);
|
||||
|
||||
export default providerModelsList;
|
||||
export const providerTypeRenderers: Record<string, () => TemplateResult> = {
|
||||
oauth2provider: () =>
|
||||
html`<ak-application-wizard-authentication-by-oauth></ak-application-wizard-authentication-by-oauth>`,
|
||||
ldapprovider: () =>
|
||||
html`<ak-application-wizard-authentication-by-ldap></ak-application-wizard-authentication-by-ldap>`,
|
||||
proxyprovider: () =>
|
||||
html`<ak-application-wizard-authentication-for-reverse-proxy></ak-application-wizard-authentication-for-reverse-proxy>`,
|
||||
racprovider: () =>
|
||||
html`<ak-application-wizard-authentication-for-rac></ak-application-wizard-authentication-for-rac>`,
|
||||
samlprovider: () =>
|
||||
html`<ak-application-wizard-authentication-by-saml-configuration></ak-application-wizard-authentication-by-saml-configuration>`,
|
||||
radiusprovider: () =>
|
||||
html`<ak-application-wizard-authentication-by-radius></ak-application-wizard-authentication-by-radius>`,
|
||||
scimprovider: () =>
|
||||
html`<ak-application-wizard-authentication-by-scim></ak-application-wizard-authentication-by-scim>`,
|
||||
};
|
||||
|
@ -7,41 +7,37 @@ import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import "@goauthentik/elements/wizard/TypeCreateWizardPage";
|
||||
import { TypeCreateWizardPageLayouts } from "@goauthentik/elements/wizard/TypeCreateWizardPage";
|
||||
|
||||
import { consume } from "@lit/context";
|
||||
import { msg } from "@lit/localize";
|
||||
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
|
||||
import { html } from "lit";
|
||||
|
||||
import BasePanel from "../BasePanel";
|
||||
import { applicationWizardProvidersContext } from "../ContextIdentity";
|
||||
import type { LocalTypeCreate } from "./ak-application-wizard-authentication-method-choice.choices";
|
||||
import providerModelsList from "./ak-application-wizard-authentication-method-choice.choices";
|
||||
|
||||
@customElement("ak-application-wizard-authentication-method-choice")
|
||||
export class ApplicationWizardAuthenticationMethodChoice extends WithLicenseSummary(BasePanel) {
|
||||
@consume({ context: applicationWizardProvidersContext })
|
||||
public providerModelsList!: LocalTypeCreate[];
|
||||
|
||||
render() {
|
||||
const selectedTypes = providerModelsList.filter(
|
||||
(t) => t.formName === this.wizard.providerModel,
|
||||
const selectedTypes = this.providerModelsList.filter(
|
||||
(t) => t.modelName === this.wizard.providerModel,
|
||||
);
|
||||
|
||||
// As a hack, the Application wizard has separate provider paths for our three types of
|
||||
// proxy providers. This patch swaps the form we want to be directed to on page 3 from the
|
||||
// modelName to the formName, so we get the right one. This information isn't modified
|
||||
// or forwarded, so the proxy-plus-subtype is correctly mapped on submission.
|
||||
const typesForWizard = providerModelsList.map((provider) => ({
|
||||
...provider,
|
||||
modelName: provider.formName,
|
||||
}));
|
||||
|
||||
return providerModelsList.length > 0
|
||||
return this.providerModelsList.length > 0
|
||||
? html`<form class="pf-c-form pf-m-horizontal">
|
||||
<ak-wizard-page-type-create
|
||||
.types=${typesForWizard}
|
||||
.types=${this.providerModelsList}
|
||||
name="selectProviderType"
|
||||
layout=${TypeCreateWizardPageLayouts.grid}
|
||||
.selectedType=${selectedTypes.length > 0 ? selectedTypes[0] : undefined}
|
||||
@select=${(ev: CustomEvent<LocalTypeCreate>) => {
|
||||
this.dispatchWizardUpdate({
|
||||
update: {
|
||||
...this.wizard,
|
||||
providerModel: ev.detail.formName,
|
||||
providerModel: ev.detail.modelName,
|
||||
errors: {},
|
||||
},
|
||||
status: this.valid ? "valid" : "invalid",
|
||||
|
@ -22,6 +22,9 @@ import {
|
||||
type ApplicationRequest,
|
||||
CoreApi,
|
||||
type ModelRequest,
|
||||
ProviderModelEnum,
|
||||
ProxyMode,
|
||||
type ProxyProviderRequest,
|
||||
type TransactionApplicationRequest,
|
||||
type TransactionApplicationResponse,
|
||||
ValidationError,
|
||||
@ -29,7 +32,6 @@ import {
|
||||
} from "@goauthentik/api";
|
||||
|
||||
import BasePanel from "../BasePanel";
|
||||
import providerModelsList from "../auth-method-choice/ak-application-wizard-authentication-method-choice.choices";
|
||||
|
||||
function cleanApplication(app: Partial<ApplicationRequest>): ApplicationRequest {
|
||||
return {
|
||||
@ -39,14 +41,19 @@ function cleanApplication(app: Partial<ApplicationRequest>): ApplicationRequest
|
||||
};
|
||||
}
|
||||
|
||||
type ProviderModelType = Exclude<ModelRequest["providerModel"], "11184809">;
|
||||
|
||||
type State = {
|
||||
state: "idle" | "running" | "error" | "success";
|
||||
label: string | TemplateResult;
|
||||
icon: string[];
|
||||
};
|
||||
|
||||
const providerMap: Map<string, string> = Object.values(ProviderModelEnum)
|
||||
.filter((value) => /^authentik_providers_/.test(value) && /provider$/.test(value))
|
||||
.reduce((acc: Map<string, string>, value) => {
|
||||
acc.set(value.split(".")[1], value);
|
||||
return acc;
|
||||
}, new Map());
|
||||
|
||||
const idleState: State = {
|
||||
state: "idle",
|
||||
label: "",
|
||||
@ -70,6 +77,7 @@ const successState: State = {
|
||||
icon: ["fa-check-circle", "pf-m-success"],
|
||||
};
|
||||
|
||||
type StrictProviderModelEnum = Exclude<ProviderModelEnum, "11184809">;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const isValidationError = (v: any): v is ValidationError => instanceOfValidationError(v);
|
||||
|
||||
@ -102,19 +110,28 @@ export class ApplicationWizardCommitApplication extends BasePanel {
|
||||
if (this.commitState === idleState) {
|
||||
this.response = undefined;
|
||||
this.commitState = runningState;
|
||||
const providerModel = providerModelsList.find(
|
||||
({ formName }) => formName === this.wizard.providerModel,
|
||||
);
|
||||
if (!providerModel) {
|
||||
throw new Error(
|
||||
`Could not determine provider model from user request: ${JSON.stringify(this.wizard, null, 2)}`,
|
||||
);
|
||||
|
||||
// Stringly-based API. Not the best, but it works. Just be aware that it is
|
||||
// stringly-based.
|
||||
|
||||
const providerModel = providerMap.get(
|
||||
this.wizard.providerModel,
|
||||
) as StrictProviderModelEnum;
|
||||
const provider = this.wizard.provider as ModelRequest;
|
||||
provider.providerModel = providerModel;
|
||||
|
||||
// Special case for the Proxy provider.
|
||||
if (this.wizard.providerModel === "proxyprovider") {
|
||||
(provider as ProxyProviderRequest).mode = this.wizard.proxyMode;
|
||||
if ((provider as ProxyProviderRequest).mode !== ProxyMode.ForwardDomain) {
|
||||
(provider as ProxyProviderRequest).cookieDomain = "";
|
||||
}
|
||||
}
|
||||
|
||||
const request: TransactionApplicationRequest = {
|
||||
providerModel: providerModel.modelName as ProviderModelType,
|
||||
app: cleanApplication(this.wizard.app),
|
||||
provider: providerModel.converter(this.wizard.provider),
|
||||
providerModel,
|
||||
provider,
|
||||
};
|
||||
|
||||
this.send(request);
|
||||
@ -125,6 +142,7 @@ export class ApplicationWizardCommitApplication extends BasePanel {
|
||||
data: TransactionApplicationRequest,
|
||||
): Promise<TransactionApplicationResponse | void> {
|
||||
this.errors = undefined;
|
||||
this.commitState = idleState;
|
||||
new CoreApi(DEFAULT_CONFIG)
|
||||
.coreTransactionalApplicationsUpdate({
|
||||
transactionApplicationRequest: data,
|
||||
@ -138,6 +156,7 @@ export class ApplicationWizardCommitApplication extends BasePanel {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
.catch(async (resolution: any) => {
|
||||
const errors = await parseAPIError(resolution);
|
||||
console.log(errors);
|
||||
|
||||
// THIS is a really gross special case; if the user is duplicating the name of an
|
||||
// existing provider, the error appears on the `app` (!) error object. We have to
|
||||
@ -164,11 +183,7 @@ export class ApplicationWizardCommitApplication extends BasePanel {
|
||||
});
|
||||
}
|
||||
|
||||
renderErrors(errors?: ValidationError) {
|
||||
if (!errors) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
renderErrors(errors: ValidationError) {
|
||||
const navTo = (step: number) => () =>
|
||||
this.dispatchCustomEvent("ak-wizard-nav", {
|
||||
command: "goto",
|
||||
@ -219,7 +234,9 @@ export class ApplicationWizardCommitApplication extends BasePanel {
|
||||
>
|
||||
${this.commitState.label}
|
||||
</h1>
|
||||
${this.renderErrors(this.errors)}
|
||||
${this.commitState === errorState
|
||||
? this.renderErrors(this.errors ?? {})
|
||||
: nothing}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { consume } from "@lit/context";
|
||||
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
|
||||
|
||||
import BasePanel from "../BasePanel";
|
||||
import { providerRendererList } from "../auth-method-choice/ak-application-wizard-authentication-method-choice.choices";
|
||||
import { applicationWizardProvidersContext } from "../ContextIdentity";
|
||||
import type { LocalTypeCreate } from "../auth-method-choice/ak-application-wizard-authentication-method-choice.choices";
|
||||
import "./ldap/ak-application-wizard-authentication-by-ldap";
|
||||
import "./oauth/ak-application-wizard-authentication-by-oauth";
|
||||
import "./proxy/ak-application-wizard-authentication-for-forward-domain-proxy";
|
||||
import "./proxy/ak-application-wizard-authentication-for-reverse-proxy";
|
||||
import "./proxy/ak-application-wizard-authentication-for-single-forward-proxy";
|
||||
import "./rac/ak-application-wizard-authentication-for-rac";
|
||||
import "./radius/ak-application-wizard-authentication-by-radius";
|
||||
import "./saml/ak-application-wizard-authentication-by-saml-configuration";
|
||||
@ -14,14 +14,19 @@ import "./scim/ak-application-wizard-authentication-by-scim";
|
||||
|
||||
@customElement("ak-application-wizard-authentication-method")
|
||||
export class ApplicationWizardApplicationDetails extends BasePanel {
|
||||
@consume({ context: applicationWizardProvidersContext })
|
||||
public providerModelsList!: LocalTypeCreate[];
|
||||
|
||||
render() {
|
||||
const handler = providerRendererList.get(this.wizard.providerModel);
|
||||
const handler: LocalTypeCreate | undefined = this.providerModelsList.find(
|
||||
({ modelName }) => modelName === this.wizard.providerModel,
|
||||
);
|
||||
if (!handler) {
|
||||
throw new Error(
|
||||
"Unrecognized authentication method in ak-application-wizard-authentication-method",
|
||||
);
|
||||
}
|
||||
return handler();
|
||||
return handler.renderer();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,33 +1,14 @@
|
||||
import "@goauthentik/admin/applications/wizard/ak-wizard-title";
|
||||
import "@goauthentik/admin/common/ak-crypto-certificate-search";
|
||||
import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
|
||||
import { first } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/components/ak-number-input";
|
||||
import "@goauthentik/components/ak-radio-input";
|
||||
import "@goauthentik/components/ak-switch-input";
|
||||
import "@goauthentik/components/ak-text-input";
|
||||
import { renderForm } from "@goauthentik/admin/providers/ldap/LDAPProviderFormForm.js";
|
||||
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
|
||||
import { html, nothing } from "lit";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
import { html } from "lit";
|
||||
|
||||
import { FlowsInstancesListDesignationEnum } from "@goauthentik/api";
|
||||
import type { LDAPProvider } from "@goauthentik/api";
|
||||
|
||||
import BaseProviderPanel from "../BaseProviderPanel";
|
||||
import {
|
||||
bindModeOptions,
|
||||
cryptoCertificateHelp,
|
||||
gidStartNumberHelp,
|
||||
mfaSupportHelp,
|
||||
searchModeOptions,
|
||||
tlsServerNameHelp,
|
||||
uidStartNumberHelp,
|
||||
} from "./LDAPOptionsAndHelp";
|
||||
|
||||
@customElement("ak-application-wizard-authentication-by-ldap")
|
||||
export class ApplicationWizardApplicationDetails extends WithBrandConfig(BaseProviderPanel) {
|
||||
@ -37,129 +18,7 @@ export class ApplicationWizardApplicationDetails extends WithBrandConfig(BasePro
|
||||
|
||||
return html` <ak-wizard-title>${msg("Configure LDAP Provider")}</ak-wizard-title>
|
||||
<form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
|
||||
<ak-text-input
|
||||
name="name"
|
||||
value=${ifDefined(provider?.name)}
|
||||
label=${msg("Name")}
|
||||
.errorMessages=${errors?.name ?? []}
|
||||
required
|
||||
help=${msg("Method's display Name.")}
|
||||
></ak-text-input>
|
||||
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Bind flow")}
|
||||
?required=${true}
|
||||
name="authorizationFlow"
|
||||
.errorMessages=${errors?.authorizationFlow ?? []}
|
||||
>
|
||||
<ak-branded-flow-search
|
||||
flowType=${FlowsInstancesListDesignationEnum.Authentication}
|
||||
.currentFlow=${provider?.authorizationFlow}
|
||||
.brandFlow=${this.brand.flowAuthentication}
|
||||
required
|
||||
></ak-branded-flow-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Flow used for users to authenticate.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Unbind flow")}
|
||||
name="invalidationFlow"
|
||||
required
|
||||
>
|
||||
<ak-branded-flow-search
|
||||
flowType=${FlowsInstancesListDesignationEnum.Invalidation}
|
||||
.currentFlow=${provider?.invalidationFlow}
|
||||
.brandFlow=${this.brand.flowInvalidation}
|
||||
defaultFlowSlug="default-invalidation-flow"
|
||||
required
|
||||
></ak-branded-flow-search>
|
||||
<p class="pf-c-form__helper-text">${msg("Flow used for unbinding users.")}</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-radio-input
|
||||
label=${msg("Bind mode")}
|
||||
name="bindMode"
|
||||
.options=${bindModeOptions}
|
||||
.value=${provider?.bindMode}
|
||||
help=${msg("Configure how the outpost authenticates requests.")}
|
||||
>
|
||||
</ak-radio-input>
|
||||
|
||||
<ak-radio-input
|
||||
label=${msg("Search mode")}
|
||||
name="searchMode"
|
||||
.options=${searchModeOptions}
|
||||
.value=${provider?.searchMode}
|
||||
help=${msg(
|
||||
"Configure how the outpost queries the core authentik server's users.",
|
||||
)}
|
||||
>
|
||||
</ak-radio-input>
|
||||
|
||||
<ak-switch-input
|
||||
name="mfaSupport"
|
||||
label=${msg("Code-based MFA Support")}
|
||||
?checked=${provider?.mfaSupport ?? true}
|
||||
help=${mfaSupportHelp}
|
||||
>
|
||||
</ak-switch-input>
|
||||
|
||||
<ak-form-group .expanded=${true}>
|
||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-text-input
|
||||
name="baseDn"
|
||||
label=${msg("Base DN")}
|
||||
required
|
||||
value="${first(provider?.baseDn, "DC=ldap,DC=goauthentik,DC=io")}"
|
||||
.errorMessages=${errors?.baseDn ?? []}
|
||||
help=${msg(
|
||||
"LDAP DN under which bind requests and search requests can be made.",
|
||||
)}
|
||||
>
|
||||
</ak-text-input>
|
||||
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Certificate")}
|
||||
name="certificate"
|
||||
.errorMessages=${errors?.certificate ?? []}
|
||||
>
|
||||
<ak-crypto-certificate-search
|
||||
certificate=${ifDefined(provider?.certificate ?? nothing)}
|
||||
name="certificate"
|
||||
>
|
||||
</ak-crypto-certificate-search>
|
||||
<p class="pf-c-form__helper-text">${cryptoCertificateHelp}</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-text-input
|
||||
label=${msg("TLS Server name")}
|
||||
name="tlsServerName"
|
||||
value="${first(provider?.tlsServerName, "")}"
|
||||
.errorMessages=${errors?.tlsServerName ?? []}
|
||||
help=${tlsServerNameHelp}
|
||||
></ak-text-input>
|
||||
|
||||
<ak-number-input
|
||||
label=${msg("UID start number")}
|
||||
required
|
||||
name="uidStartNumber"
|
||||
value="${first(provider?.uidStartNumber, 2000)}"
|
||||
.errorMessages=${errors?.uidStartNumber ?? []}
|
||||
help=${uidStartNumberHelp}
|
||||
></ak-number-input>
|
||||
|
||||
<ak-number-input
|
||||
label=${msg("GID start number")}
|
||||
required
|
||||
name="gidStartNumber"
|
||||
value="${first(provider?.gidStartNumber, 4000)}"
|
||||
.errorMessages=${errors?.gidStartNumber ?? []}
|
||||
help=${gidStartNumberHelp}
|
||||
></ak-number-input>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
${renderForm(provider, errors, this.brand)}
|
||||
</form>`;
|
||||
}
|
||||
}
|
||||
|
@ -1,47 +1,11 @@
|
||||
import "@goauthentik/admin/applications/wizard/ak-wizard-title";
|
||||
import "@goauthentik/admin/common/ak-crypto-certificate-search";
|
||||
import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
|
||||
import {
|
||||
clientTypeOptions,
|
||||
issuerModeOptions,
|
||||
redirectUriHelp,
|
||||
subjectModeOptions,
|
||||
} from "@goauthentik/admin/providers/oauth2/OAuth2ProviderForm";
|
||||
import {
|
||||
propertyMappingsProvider,
|
||||
propertyMappingsSelector,
|
||||
} from "@goauthentik/admin/providers/oauth2/OAuth2ProviderFormHelpers.js";
|
||||
import {
|
||||
IRedirectURIInput,
|
||||
akOAuthRedirectURIInput,
|
||||
} from "@goauthentik/admin/providers/oauth2/OAuth2ProviderRedirectURI";
|
||||
import {
|
||||
oauth2SourcesProvider,
|
||||
oauth2SourcesSelector,
|
||||
} from "@goauthentik/admin/providers/oauth2/OAuth2Sources.js";
|
||||
import { renderForm } from "@goauthentik/admin/providers/oauth2/OAuth2ProviderFormForm.js";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { ascii_letters, digits, first, randomString } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/components/ak-number-input";
|
||||
import "@goauthentik/components/ak-radio-input";
|
||||
import "@goauthentik/components/ak-switch-input";
|
||||
import "@goauthentik/components/ak-text-input";
|
||||
import "@goauthentik/components/ak-textarea-input";
|
||||
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { customElement, state } from "@lit/reactive-element/decorators.js";
|
||||
import { html, nothing } from "lit";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
import { html } from "lit";
|
||||
|
||||
import {
|
||||
ClientTypeEnum,
|
||||
FlowsInstancesListDesignationEnum,
|
||||
MatchingModeEnum,
|
||||
RedirectURI,
|
||||
SourcesApi,
|
||||
} from "@goauthentik/api";
|
||||
import { SourcesApi } from "@goauthentik/api";
|
||||
import { type OAuth2Provider, type PaginatedOAuthSourceList } from "@goauthentik/api";
|
||||
|
||||
import BaseProviderPanel from "../BaseProviderPanel";
|
||||
@ -69,256 +33,17 @@ export class ApplicationWizardAuthenticationByOauth extends BaseProviderPanel {
|
||||
render() {
|
||||
const provider = this.wizard.provider as OAuth2Provider | undefined;
|
||||
const errors = this.wizard.errors.provider;
|
||||
|
||||
return html`<ak-wizard-title>${msg("Configure OAuth2/OpenId Provider")}</ak-wizard-title>
|
||||
const showClientSecretCallback = (show: boolean) => {
|
||||
this.showClientSecret = show;
|
||||
};
|
||||
return html` <ak-wizard-title>${msg("Configure OAuth2 Provider")}</ak-wizard-title>
|
||||
<form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
|
||||
<ak-text-input
|
||||
name="name"
|
||||
label=${msg("Name")}
|
||||
value=${ifDefined(provider?.name)}
|
||||
.errorMessages=${errors?.name ?? []}
|
||||
required
|
||||
></ak-text-input>
|
||||
|
||||
<ak-form-element-horizontal
|
||||
name="authorizationFlow"
|
||||
label=${msg("Authorization flow")}
|
||||
.errorMessages=${errors?.authorizationFlow ?? []}
|
||||
?required=${true}
|
||||
>
|
||||
<ak-flow-search
|
||||
flowType=${FlowsInstancesListDesignationEnum.Authorization}
|
||||
.currentFlow=${provider?.authorizationFlow}
|
||||
required
|
||||
></ak-flow-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Flow used when authorizing this provider.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-form-group .expanded=${true}>
|
||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-radio-input
|
||||
name="clientType"
|
||||
label=${msg("Client type")}
|
||||
.value=${provider?.clientType}
|
||||
required
|
||||
@change=${(ev: CustomEvent<{ value: ClientTypeEnum }>) => {
|
||||
this.showClientSecret = ev.detail.value !== ClientTypeEnum.Public;
|
||||
}}
|
||||
.options=${clientTypeOptions}
|
||||
>
|
||||
</ak-radio-input>
|
||||
|
||||
<ak-text-input
|
||||
name="clientId"
|
||||
label=${msg("Client ID")}
|
||||
value=${provider?.clientId ?? randomString(40, ascii_letters + digits)}
|
||||
.errorMessages=${errors?.clientId ?? []}
|
||||
required
|
||||
>
|
||||
</ak-text-input>
|
||||
|
||||
<ak-text-input
|
||||
name="clientSecret"
|
||||
label=${msg("Client Secret")}
|
||||
value=${provider?.clientSecret ??
|
||||
randomString(128, ascii_letters + digits)}
|
||||
.errorMessages=${errors?.clientSecret ?? []}
|
||||
?hidden=${!this.showClientSecret}
|
||||
>
|
||||
</ak-text-input>
|
||||
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Redirect URIs/Origins")}
|
||||
required
|
||||
name="redirectUris"
|
||||
>
|
||||
<ak-array-input
|
||||
.items=${[]}
|
||||
.newItem=${() => ({
|
||||
matchingMode: MatchingModeEnum.Strict,
|
||||
url: "",
|
||||
})}
|
||||
.row=${(f?: RedirectURI) =>
|
||||
akOAuthRedirectURIInput({
|
||||
".redirectURI": f,
|
||||
"style": "width: 100%",
|
||||
"name": "oauth2-redirect-uri",
|
||||
} as unknown as IRedirectURIInput)}
|
||||
>
|
||||
</ak-array-input>
|
||||
${redirectUriHelp}
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Signing Key")}
|
||||
name="signingKey"
|
||||
.errorMessages=${errors?.signingKey ?? []}
|
||||
>
|
||||
<ak-crypto-certificate-search
|
||||
certificate=${ifDefined(provider?.signingKey ?? nothing)}
|
||||
name="certificate"
|
||||
singleton
|
||||
>
|
||||
</ak-crypto-certificate-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Key used to sign the tokens.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Advanced flow settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
name="authenticationFlow"
|
||||
label=${msg("Authentication flow")}
|
||||
>
|
||||
<ak-flow-search
|
||||
flowType=${FlowsInstancesListDesignationEnum.Authentication}
|
||||
.currentFlow=${provider?.authenticationFlow}
|
||||
></ak-flow-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"Flow used when a user access this provider and is not authenticated.",
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Invalidation flow")}
|
||||
name="invalidationFlow"
|
||||
required
|
||||
>
|
||||
<ak-flow-search
|
||||
flowType=${FlowsInstancesListDesignationEnum.Invalidation}
|
||||
.currentFlow=${provider?.invalidationFlow}
|
||||
defaultFlowSlug="default-provider-invalidation-flow"
|
||||
required
|
||||
></ak-flow-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Flow used when logging out of this provider.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Advanced protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-text-input
|
||||
name="accessCodeValidity"
|
||||
label=${msg("Access code validity")}
|
||||
required
|
||||
value="${first(provider?.accessCodeValidity, "minutes=1")}"
|
||||
.errorMessages=${errors?.accessCodeValidity ?? []}
|
||||
.bighelp=${html`<p class="pf-c-form__helper-text">
|
||||
${msg("Configure how long access codes are valid for.")}
|
||||
</p>
|
||||
<ak-utils-time-delta-help></ak-utils-time-delta-help>`}
|
||||
>
|
||||
</ak-text-input>
|
||||
|
||||
<ak-text-input
|
||||
name="accessTokenValidity"
|
||||
label=${msg("Access Token validity")}
|
||||
value="${first(provider?.accessTokenValidity, "minutes=5")}"
|
||||
required
|
||||
.errorMessages=${errors?.accessTokenValidity ?? []}
|
||||
.bighelp=${html` <p class="pf-c-form__helper-text">
|
||||
${msg("Configure how long access tokens are valid for.")}
|
||||
</p>
|
||||
<ak-utils-time-delta-help></ak-utils-time-delta-help>`}
|
||||
>
|
||||
</ak-text-input>
|
||||
|
||||
<ak-text-input
|
||||
name="refreshTokenValidity"
|
||||
label=${msg("Refresh Token validity")}
|
||||
value="${first(provider?.refreshTokenValidity, "days=30")}"
|
||||
.errorMessages=${errors?.refreshTokenValidity ?? []}
|
||||
?required=${true}
|
||||
.bighelp=${html` <p class="pf-c-form__helper-text">
|
||||
${msg("Configure how long refresh tokens are valid for.")}
|
||||
</p>
|
||||
<ak-utils-time-delta-help></ak-utils-time-delta-help>`}
|
||||
>
|
||||
</ak-text-input>
|
||||
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Scopes")}
|
||||
name="propertyMappings"
|
||||
.errorMessages=${errors?.propertyMappings ?? []}
|
||||
>
|
||||
<ak-dual-select-dynamic-selected
|
||||
.provider=${propertyMappingsProvider}
|
||||
.selector=${propertyMappingsSelector(provider?.propertyMappings)}
|
||||
available-label=${msg("Available Scopes")}
|
||||
selected-label=${msg("Selected Scopes")}
|
||||
></ak-dual-select-dynamic-selected>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"Select which scopes can be used by the client. The client still has to specify the scope to access the data.",
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-radio-input
|
||||
name="subMode"
|
||||
label=${msg("Subject mode")}
|
||||
required
|
||||
.options=${subjectModeOptions}
|
||||
.value=${provider?.subMode}
|
||||
help=${msg(
|
||||
"Configure what data should be used as unique User Identifier. For most cases, the default should be fine.",
|
||||
)}
|
||||
>
|
||||
</ak-radio-input>
|
||||
<ak-switch-input
|
||||
name="includeClaimsInIdToken"
|
||||
label=${msg("Include claims in id_token")}
|
||||
?checked=${first(provider?.includeClaimsInIdToken, true)}
|
||||
help=${msg(
|
||||
"Include User claims from scopes in the id_token, for applications that don't access the userinfo endpoint.",
|
||||
)}
|
||||
></ak-switch-input>
|
||||
<ak-radio-input
|
||||
name="issuerMode"
|
||||
label=${msg("Issuer mode")}
|
||||
required
|
||||
.options=${issuerModeOptions}
|
||||
.value=${provider?.issuerMode}
|
||||
help=${msg(
|
||||
"Configure how the issuer field of the ID Token should be filled.",
|
||||
)}
|
||||
>
|
||||
</ak-radio-input>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
||||
<ak-form-group>
|
||||
<span slot="header">${msg("Machine-to-Machine authentication settings")}</span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Trusted OIDC Sources")}
|
||||
name="jwksSources"
|
||||
.errorMessages=${errors?.jwksSources ?? []}
|
||||
>
|
||||
<ak-dual-select-dynamic-selected
|
||||
.provider=${oauth2SourcesProvider}
|
||||
.selector=${oauth2SourcesSelector(provider?.jwtFederationSources)}
|
||||
available-label=${msg("Available Sources")}
|
||||
selected-label=${msg("Selected Sources")}
|
||||
></ak-dual-select-dynamic-selected>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"JWTs signed by certificates configured in the selected sources can be used to authenticate to this provider.",
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
${renderForm(
|
||||
provider ?? {},
|
||||
errors,
|
||||
this.showClientSecret,
|
||||
showClientSecretCallback,
|
||||
)}
|
||||
</form>`;
|
||||
}
|
||||
}
|
||||
|
@ -1,269 +0,0 @@
|
||||
import "@goauthentik/admin/applications/wizard/ak-wizard-title";
|
||||
import {
|
||||
oauth2SourcesProvider,
|
||||
oauth2SourcesSelector,
|
||||
} from "@goauthentik/admin/providers/oauth2/OAuth2Sources.js";
|
||||
import {
|
||||
propertyMappingsProvider,
|
||||
propertyMappingsSelector,
|
||||
} from "@goauthentik/admin/providers/proxy/ProxyProviderFormHelpers.js";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { first } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/components/ak-switch-input";
|
||||
import "@goauthentik/components/ak-text-input";
|
||||
import "@goauthentik/components/ak-textarea-input";
|
||||
import "@goauthentik/components/ak-toggle-group";
|
||||
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { state } from "@lit/reactive-element/decorators.js";
|
||||
import { TemplateResult, html, nothing } from "lit";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import {
|
||||
FlowsInstancesListDesignationEnum,
|
||||
PaginatedOAuthSourceList,
|
||||
PaginatedScopeMappingList,
|
||||
ProxyMode,
|
||||
ProxyProvider,
|
||||
SourcesApi,
|
||||
} from "@goauthentik/api";
|
||||
|
||||
import BaseProviderPanel from "../BaseProviderPanel";
|
||||
|
||||
type MaybeTemplateResult = TemplateResult | typeof nothing;
|
||||
|
||||
export class AkTypeProxyApplicationWizardPage extends BaseProviderPanel {
|
||||
constructor() {
|
||||
super();
|
||||
new SourcesApi(DEFAULT_CONFIG)
|
||||
.sourcesOauthList({
|
||||
ordering: "name",
|
||||
hasJwks: true,
|
||||
})
|
||||
.then((oauthSources: PaginatedOAuthSourceList) => {
|
||||
this.oauthSources = oauthSources;
|
||||
});
|
||||
}
|
||||
|
||||
propertyMappings?: PaginatedScopeMappingList;
|
||||
oauthSources?: PaginatedOAuthSourceList;
|
||||
|
||||
@state()
|
||||
showHttpBasic = true;
|
||||
|
||||
@state()
|
||||
mode: ProxyMode = ProxyMode.Proxy;
|
||||
|
||||
get instance(): ProxyProvider | undefined {
|
||||
return this.wizard.provider as ProxyProvider;
|
||||
}
|
||||
|
||||
renderModeDescription(): MaybeTemplateResult {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
renderProxyMode(): TemplateResult {
|
||||
throw new Error("Must be implemented in a child class.");
|
||||
}
|
||||
|
||||
renderHttpBasic() {
|
||||
return html`<ak-text-input
|
||||
name="basicAuthUserAttribute"
|
||||
label=${msg("HTTP-Basic Username Key")}
|
||||
value="${ifDefined(this.instance?.basicAuthUserAttribute)}"
|
||||
help=${msg(
|
||||
"User/Group Attribute used for the user part of the HTTP-Basic Header. If not set, the user's Email address is used.",
|
||||
)}
|
||||
>
|
||||
</ak-text-input>
|
||||
|
||||
<ak-text-input
|
||||
name="basicAuthPasswordAttribute"
|
||||
label=${msg("HTTP-Basic Password Key")}
|
||||
value="${ifDefined(this.instance?.basicAuthPasswordAttribute)}"
|
||||
help=${msg(
|
||||
"User/Group Attribute used for the password part of the HTTP-Basic Header.",
|
||||
)}
|
||||
>
|
||||
</ak-text-input>`;
|
||||
}
|
||||
|
||||
render() {
|
||||
const errors = this.wizard.errors.provider;
|
||||
|
||||
return html` <ak-wizard-title>${msg("Configure Proxy Provider")}</ak-wizard-title>
|
||||
<form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
|
||||
${this.renderModeDescription()}
|
||||
<ak-text-input
|
||||
name="name"
|
||||
value=${ifDefined(this.instance?.name)}
|
||||
required
|
||||
.errorMessages=${errors?.name ?? []}
|
||||
label=${msg("Name")}
|
||||
></ak-text-input>
|
||||
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Authorization flow")}
|
||||
required
|
||||
name="authorizationFlow"
|
||||
.errorMessages=${errors?.authorizationFlow ?? []}
|
||||
>
|
||||
<ak-flow-search
|
||||
flowType=${FlowsInstancesListDesignationEnum.Authorization}
|
||||
.currentFlow=${this.instance?.authorizationFlow}
|
||||
required
|
||||
></ak-flow-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Flow used when authorizing this provider.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
${this.renderProxyMode()}
|
||||
|
||||
<ak-text-input
|
||||
name="accessTokenValidity"
|
||||
value=${first(this.instance?.accessTokenValidity, "hours=24")}
|
||||
label=${msg("Token validity")}
|
||||
help=${msg("Configure how long tokens are valid for.")}
|
||||
.errorMessages=${errors?.accessTokenValidity ?? []}
|
||||
></ak-text-input>
|
||||
|
||||
<ak-form-group>
|
||||
<span slot="header">${msg("Advanced protocol settings")}</span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Certificate")}
|
||||
name="certificate"
|
||||
.errorMessages=${errors?.certificate ?? []}
|
||||
>
|
||||
<ak-crypto-certificate-search
|
||||
certificate=${ifDefined(this.instance?.certificate ?? undefined)}
|
||||
></ak-crypto-certificate-search>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Additional scopes")}
|
||||
name="propertyMappings"
|
||||
>
|
||||
<ak-dual-select-dynamic-selected
|
||||
.provider=${propertyMappingsProvider}
|
||||
.selector=${propertyMappingsSelector(
|
||||
this.instance?.propertyMappings,
|
||||
)}
|
||||
available-label="${msg("Available Scopes")}"
|
||||
selected-label="${msg("Selected Scopes")}"
|
||||
></ak-dual-select-dynamic-selected>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Additional scope mappings, which are passed to the proxy.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-textarea-input
|
||||
name="skipPathRegex"
|
||||
label=${this.mode === ProxyMode.ForwardDomain
|
||||
? msg("Unauthenticated URLs")
|
||||
: msg("Unauthenticated Paths")}
|
||||
value=${ifDefined(this.instance?.skipPathRegex)}
|
||||
.errorMessages=${errors?.skipPathRegex ?? []}
|
||||
.bighelp=${html` <p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"Regular expressions for which authentication is not required. Each new line is interpreted as a new expression.",
|
||||
)}
|
||||
</p>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"When using proxy or forward auth (single application) mode, the requested URL Path is checked against the regular expressions. When using forward auth (domain mode), the full requested URL including scheme and host is matched against the regular expressions.",
|
||||
)}
|
||||
</p>`}
|
||||
>
|
||||
</ak-textarea-input>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Advanced flow settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
name="authenticationFlow"
|
||||
label=${msg("Authentication flow")}
|
||||
>
|
||||
<ak-flow-search
|
||||
flowType=${FlowsInstancesListDesignationEnum.Authentication}
|
||||
.currentFlow=${this.instance?.authenticationFlow}
|
||||
></ak-flow-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"Flow used when a user access this provider and is not authenticated.",
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Invalidation flow")}
|
||||
name="invalidationFlow"
|
||||
required
|
||||
>
|
||||
<ak-flow-search
|
||||
flowType=${FlowsInstancesListDesignationEnum.Invalidation}
|
||||
.currentFlow=${this.instance?.invalidationFlow}
|
||||
defaultFlowSlug="default-provider-invalidation-flow"
|
||||
required
|
||||
></ak-flow-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Flow used when logging out of this provider.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group>
|
||||
<span slot="header">${msg("Authentication settings")}</span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-switch-input
|
||||
name="interceptHeaderAuth"
|
||||
?checked=${first(this.instance?.interceptHeaderAuth, true)}
|
||||
label=${msg("Intercept header authentication")}
|
||||
help=${msg(
|
||||
"When enabled, authentik will intercept the Authorization header to authenticate the request.",
|
||||
)}
|
||||
></ak-switch-input>
|
||||
|
||||
<ak-switch-input
|
||||
name="basicAuthEnabled"
|
||||
?checked=${first(this.instance?.basicAuthEnabled, false)}
|
||||
@change=${(ev: Event) => {
|
||||
const el = ev.target as HTMLInputElement;
|
||||
this.showHttpBasic = el.checked;
|
||||
}}
|
||||
label=${msg("Send HTTP-Basic Authentication")}
|
||||
help=${msg(
|
||||
"Send a custom HTTP-Basic Authentication header based on values from authentik.",
|
||||
)}
|
||||
></ak-switch-input>
|
||||
|
||||
${this.showHttpBasic ? this.renderHttpBasic() : html``}
|
||||
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Trusted OIDC Sources")}
|
||||
name="jwksSources"
|
||||
.errorMessages=${errors?.jwksSources ?? []}
|
||||
>
|
||||
<ak-dual-select-dynamic-selected
|
||||
.provider=${oauth2SourcesProvider}
|
||||
.selector=${oauth2SourcesSelector(
|
||||
this.instance?.jwtFederationSources,
|
||||
)}
|
||||
available-label=${msg("Available Sources")}
|
||||
selected-label=${msg("Selected Sources")}
|
||||
></ak-dual-select-dynamic-selected>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"JWTs signed by certificates configured in the selected sources can be used to authenticate to this provider.",
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
</form>`;
|
||||
}
|
||||
}
|
||||
|
||||
export default AkTypeProxyApplicationWizardPage;
|
@ -1,74 +0,0 @@
|
||||
import "@goauthentik/components/ak-text-input";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { customElement } from "@lit/reactive-element/decorators.js";
|
||||
import { html } from "lit";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import PFList from "@patternfly/patternfly/components/List/list.css";
|
||||
|
||||
import { ProxyProvider } from "@goauthentik/api";
|
||||
|
||||
import AkTypeProxyApplicationWizardPage from "./AuthenticationByProxyPage";
|
||||
|
||||
@customElement("ak-application-wizard-authentication-for-forward-proxy-domain")
|
||||
export class AkForwardDomainProxyApplicationWizardPage extends AkTypeProxyApplicationWizardPage {
|
||||
static get styles() {
|
||||
return super.styles.concat(PFList);
|
||||
}
|
||||
|
||||
renderModeDescription() {
|
||||
return html`<p>
|
||||
${msg(
|
||||
"Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application.",
|
||||
)}
|
||||
</p>
|
||||
<div>
|
||||
${msg("An example setup can look like this:")}
|
||||
<ul class="pf-c-list">
|
||||
<li>${msg("authentik running on auth.example.com")}</li>
|
||||
<li>${msg("app1 running on app1.example.com")}</li>
|
||||
</ul>
|
||||
${msg(
|
||||
"In this case, you'd set the Authentication URL to auth.example.com and Cookie domain to example.com.",
|
||||
)}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
renderProxyMode() {
|
||||
const provider = this.wizard.provider as ProxyProvider | undefined;
|
||||
const errors = this.wizard.errors.provider;
|
||||
|
||||
return html`
|
||||
<ak-text-input
|
||||
name="externalHost"
|
||||
label=${msg("External host")}
|
||||
value=${ifDefined(provider?.externalHost)}
|
||||
.errorMessages=${errors?.externalHost ?? []}
|
||||
required
|
||||
help=${msg(
|
||||
"The external URL you'll authenticate at. The authentik core server should be reachable under this URL.",
|
||||
)}
|
||||
>
|
||||
</ak-text-input>
|
||||
<ak-text-input
|
||||
name="cookieDomain"
|
||||
label=${msg("Cookie domain")}
|
||||
value="${ifDefined(provider?.cookieDomain)}"
|
||||
.errorMessages=${errors?.cookieDomain ?? []}
|
||||
required
|
||||
help=${msg(
|
||||
"Set this to the domain you wish the authentication to be valid for. Must be a parent domain of the URL above. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'.",
|
||||
)}
|
||||
></ak-text-input>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
export default AkForwardDomainProxyApplicationWizardPage;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-application-wizard-authentication-for-forward-proxy-domain": AkForwardDomainProxyApplicationWizardPage;
|
||||
}
|
||||
}
|
@ -1,55 +1,50 @@
|
||||
import { first } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/components/ak-switch-input";
|
||||
import "@goauthentik/components/ak-text-input";
|
||||
import {
|
||||
ProxyModeValue,
|
||||
type SetMode,
|
||||
type SetShowHttpBasic,
|
||||
renderForm,
|
||||
} from "@goauthentik/admin/providers/proxy/ProxyProviderFormForm.js";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { customElement } from "@lit/reactive-element/decorators.js";
|
||||
import { customElement, state } from "@lit/reactive-element/decorators.js";
|
||||
import { html } from "lit";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import { ProxyProvider } from "@goauthentik/api";
|
||||
import { ProxyMode } from "@goauthentik/api";
|
||||
|
||||
import AkTypeProxyApplicationWizardPage from "./AuthenticationByProxyPage";
|
||||
import BaseProviderPanel from "../BaseProviderPanel.js";
|
||||
|
||||
@customElement("ak-application-wizard-authentication-for-reverse-proxy")
|
||||
export class AkReverseProxyApplicationWizardPage extends AkTypeProxyApplicationWizardPage {
|
||||
renderModeDescription() {
|
||||
return html`<p class="pf-u-mb-xl">
|
||||
${msg(
|
||||
"This provider will behave like a transparent reverse-proxy, except requests must be authenticated. If your upstream application uses HTTPS, make sure to connect to the outpost using HTTPS as well.",
|
||||
)}
|
||||
</p>`;
|
||||
}
|
||||
export class AkReverseProxyApplicationWizardPage extends BaseProviderPanel {
|
||||
@state()
|
||||
showHttpBasic = true;
|
||||
|
||||
renderProxyMode() {
|
||||
const provider = this.wizard.provider as ProxyProvider | undefined;
|
||||
const errors = this.wizard.errors.provider;
|
||||
render() {
|
||||
const onSetMode: SetMode = (ev: CustomEvent<ProxyModeValue>) => {
|
||||
this.dispatchWizardUpdate({
|
||||
update: {
|
||||
...this.wizard,
|
||||
proxyMode: ev.detail.value,
|
||||
},
|
||||
});
|
||||
// We deliberately chose not to make the forms "controlled," but we do need this form to
|
||||
// respond immediately to a state change in the wizard.
|
||||
window.setTimeout(() => this.requestUpdate(), 0);
|
||||
};
|
||||
|
||||
return html` <ak-text-input
|
||||
name="externalHost"
|
||||
value=${ifDefined(provider?.externalHost)}
|
||||
required
|
||||
label=${msg("External host")}
|
||||
.errorMessages=${errors?.externalHost ?? []}
|
||||
help=${msg(
|
||||
"The external URL you'll access the application at. Include any non-standard port.",
|
||||
)}
|
||||
></ak-text-input>
|
||||
<ak-text-input
|
||||
name="internalHost"
|
||||
value=${ifDefined(provider?.internalHost)}
|
||||
.errorMessages=${errors?.internalHost ?? []}
|
||||
required
|
||||
label=${msg("Internal host")}
|
||||
help=${msg("Upstream host that the requests are forwarded to.")}
|
||||
></ak-text-input>
|
||||
<ak-switch-input
|
||||
name="internalHostSslValidation"
|
||||
?checked=${first(provider?.internalHostSslValidation, true)}
|
||||
label=${msg("Internal host SSL Validation")}
|
||||
help=${msg("Validate SSL Certificates of upstream servers.")}
|
||||
>
|
||||
</ak-switch-input>`;
|
||||
const onSetShowHttpBasic: SetShowHttpBasic = (ev: Event) => {
|
||||
const el = ev.target as HTMLInputElement;
|
||||
this.showHttpBasic = el.checked;
|
||||
};
|
||||
|
||||
return html` <ak-wizard-title>${msg("Configure Proxy Provider")}</ak-wizard-title>
|
||||
<form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
|
||||
${renderForm(this.wizard.provider ?? {}, this.wizard.errors.provider ?? [], {
|
||||
mode: this.wizard.proxyMode ?? ProxyMode.Proxy,
|
||||
onSetMode,
|
||||
showHttpBasic: this.showHttpBasic,
|
||||
onSetShowHttpBasic,
|
||||
})}
|
||||
</form>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,48 +0,0 @@
|
||||
import "@goauthentik/components/ak-text-input";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { customElement } from "@lit/reactive-element/decorators.js";
|
||||
import { html } from "lit";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import { ProxyProvider } from "@goauthentik/api";
|
||||
|
||||
import AkTypeProxyApplicationWizardPage from "./AuthenticationByProxyPage";
|
||||
|
||||
@customElement("ak-application-wizard-authentication-for-single-forward-proxy")
|
||||
export class AkForwardSingleProxyApplicationWizardPage extends AkTypeProxyApplicationWizardPage {
|
||||
renderModeDescription() {
|
||||
return html`<p class="pf-u-mb-xl">
|
||||
${msg(
|
||||
html`Use this provider with nginx's <code>auth_request</code> or traefik's
|
||||
<code>forwardAuth</code>. Each application/domain needs its own provider.
|
||||
Additionally, on each domain, <code>/outpost.goauthentik.io</code> must be
|
||||
routed to the outpost (when using a managed outpost, this is done for you).`,
|
||||
)}
|
||||
</p>`;
|
||||
}
|
||||
|
||||
renderProxyMode() {
|
||||
const provider = this.wizard.provider as ProxyProvider | undefined;
|
||||
const errors = this.wizard.errors.provider;
|
||||
|
||||
return html`<ak-text-input
|
||||
name="externalHost"
|
||||
value=${ifDefined(provider?.externalHost)}
|
||||
required
|
||||
label=${msg("External host")}
|
||||
.errorMessages=${errors?.externalHost ?? []}
|
||||
help=${msg(
|
||||
"The external URL you'll access the application at. Include any non-standard port.",
|
||||
)}
|
||||
></ak-text-input>`;
|
||||
}
|
||||
}
|
||||
|
||||
export default AkForwardSingleProxyApplicationWizardPage;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-application-wizard-authentication-for-single-forward-proxy": AkForwardSingleProxyApplicationWizardPage;
|
||||
}
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
import "@goauthentik/admin/applications/wizard/ak-wizard-title";
|
||||
import "@goauthentik/admin/common/ak-flow-search/ak-flow-search";
|
||||
import {
|
||||
propertyMappingsProvider,
|
||||
propertyMappingsSelector,
|
||||
} from "@goauthentik/admin/providers/rac/RACProviderFormHelpers.js";
|
||||
makeRACPropertyMappingsSelector,
|
||||
racPropertyMappingsProvider,
|
||||
} from "@goauthentik/admin/providers/rac/RACPropertyMappings.js";
|
||||
import "@goauthentik/components/ak-text-input";
|
||||
import "@goauthentik/elements/CodeMirror";
|
||||
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
|
||||
@ -70,8 +70,10 @@ export class ApplicationWizardAuthenticationByRAC extends BaseProviderPanel {
|
||||
name="propertyMappings"
|
||||
>
|
||||
<ak-dual-select-dynamic-selected
|
||||
.provider=${propertyMappingsProvider}
|
||||
.selector=${propertyMappingsSelector(provider?.propertyMappings)}
|
||||
.provider=${racPropertyMappingsProvider}
|
||||
.selector=${makeRACPropertyMappingsSelector(
|
||||
provider?.propertyMappings,
|
||||
)}
|
||||
available-label="${msg("Available Property Mappings")}"
|
||||
selected-label="${msg("Selected Property Mappings")}"
|
||||
></ak-dual-select-dynamic-selected>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import "@goauthentik/admin/applications/wizard/ak-wizard-title";
|
||||
import "@goauthentik/admin/common/ak-crypto-certificate-search";
|
||||
import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
|
||||
import { ascii_letters, digits, first, randomString } from "@goauthentik/common/utils";
|
||||
import { renderForm } from "@goauthentik/admin/providers/radius/RadiusProviderFormForm.js";
|
||||
import "@goauthentik/components/ak-text-input";
|
||||
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
@ -10,91 +10,21 @@ import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import { msg } from "@lit/localize";
|
||||
import { customElement } from "@lit/reactive-element/decorators.js";
|
||||
import { html } from "lit";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import { FlowsInstancesListDesignationEnum, RadiusProvider } from "@goauthentik/api";
|
||||
import { RadiusProvider } from "@goauthentik/api";
|
||||
|
||||
import BaseProviderPanel from "../BaseProviderPanel";
|
||||
|
||||
@customElement("ak-application-wizard-authentication-by-radius")
|
||||
export class ApplicationWizardAuthenticationByRadius extends WithBrandConfig(BaseProviderPanel) {
|
||||
render() {
|
||||
const provider = this.wizard.provider as RadiusProvider | undefined;
|
||||
const errors = this.wizard.errors.provider;
|
||||
|
||||
return html`<ak-wizard-title>${msg("Configure Radius Provider")}</ak-wizard-title>
|
||||
<form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
|
||||
<ak-text-input
|
||||
name="name"
|
||||
label=${msg("Name")}
|
||||
value=${ifDefined(provider?.name)}
|
||||
.errorMessages=${errors?.name ?? []}
|
||||
required
|
||||
>
|
||||
</ak-text-input>
|
||||
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Authentication flow")}
|
||||
?required=${true}
|
||||
name="authorizationFlow"
|
||||
.errorMessages=${errors?.authorizationFlow ?? []}
|
||||
>
|
||||
<ak-branded-flow-search
|
||||
flowType=${FlowsInstancesListDesignationEnum.Authentication}
|
||||
.currentFlow=${provider?.authorizationFlow}
|
||||
.brandFlow=${this.brand.flowAuthentication}
|
||||
required
|
||||
></ak-branded-flow-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Flow used for users to authenticate.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-text-input
|
||||
name="sharedSecret"
|
||||
label=${msg("Shared secret")}
|
||||
.errorMessages=${errors?.sharedSecret ?? []}
|
||||
value=${first(
|
||||
provider?.sharedSecret,
|
||||
randomString(128, ascii_letters + digits),
|
||||
)}
|
||||
required
|
||||
></ak-text-input>
|
||||
<ak-text-input
|
||||
name="clientNetworks"
|
||||
label=${msg("Client Networks")}
|
||||
value=${first(provider?.clientNetworks, "0.0.0.0/0, ::/0")}
|
||||
.errorMessages=${errors?.clientNetworks ?? []}
|
||||
required
|
||||
help=${msg(`List of CIDRs (comma-seperated) that clients can connect from. A more specific
|
||||
CIDR will match before a looser one. Clients connecting from a non-specified CIDR
|
||||
will be dropped.`)}
|
||||
></ak-text-input>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Advanced flow settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Invalidation flow")}
|
||||
name="invalidationFlow"
|
||||
required
|
||||
>
|
||||
<ak-flow-search
|
||||
flowType=${FlowsInstancesListDesignationEnum.Invalidation}
|
||||
.currentFlow=${provider?.invalidationFlow}
|
||||
defaultFlowSlug="default-invalidation-flow"
|
||||
required
|
||||
></ak-flow-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Flow used when logging out of this provider.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
</div></ak-form-group
|
||||
>
|
||||
${renderForm(
|
||||
this.wizard.provider as RadiusProvider | undefined,
|
||||
this.wizard.errors.provider,
|
||||
this.brand,
|
||||
)}
|
||||
</form>`;
|
||||
}
|
||||
}
|
||||
|
@ -1,356 +1,35 @@
|
||||
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 AkCryptoCertificateSearch from "@goauthentik/admin/common/ak-crypto-certificate-search";
|
||||
import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { first } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/components/ak-multi-select";
|
||||
import "@goauthentik/components/ak-number-input";
|
||||
import "@goauthentik/components/ak-radio-input";
|
||||
import "@goauthentik/components/ak-switch-input";
|
||||
import "@goauthentik/components/ak-text-input";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import { renderForm } from "@goauthentik/admin/providers/saml/SAMLProviderFormForm.js";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { customElement, state } from "@lit/reactive-element/decorators.js";
|
||||
import { html, nothing } from "lit";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
import { html } from "lit";
|
||||
|
||||
import {
|
||||
FlowsInstancesListDesignationEnum,
|
||||
PaginatedSAMLPropertyMappingList,
|
||||
PropertymappingsApi,
|
||||
SAMLProvider,
|
||||
} from "@goauthentik/api";
|
||||
import { SAMLProvider } from "@goauthentik/api";
|
||||
|
||||
import BaseProviderPanel from "../BaseProviderPanel";
|
||||
import {
|
||||
digestAlgorithmOptions,
|
||||
signatureAlgorithmOptions,
|
||||
spBindingOptions,
|
||||
} from "./SamlProviderOptions";
|
||||
import "./saml-property-mappings-search";
|
||||
|
||||
@customElement("ak-application-wizard-authentication-by-saml-configuration")
|
||||
export class ApplicationWizardProviderSamlConfiguration extends BaseProviderPanel {
|
||||
@state()
|
||||
propertyMappings?: PaginatedSAMLPropertyMappingList;
|
||||
|
||||
@state()
|
||||
hasSigningKp = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
new PropertymappingsApi(DEFAULT_CONFIG)
|
||||
.propertymappingsProviderSamlList({
|
||||
ordering: "saml_name",
|
||||
})
|
||||
.then((propertyMappings: PaginatedSAMLPropertyMappingList) => {
|
||||
this.propertyMappings = propertyMappings;
|
||||
});
|
||||
}
|
||||
|
||||
propertyMappingConfiguration(provider?: SAMLProvider) {
|
||||
const propertyMappings = this.propertyMappings?.results ?? [];
|
||||
|
||||
const configuredMappings = (providerMappings: string[]) =>
|
||||
propertyMappings.map((pm) => pm.pk).filter((pmpk) => providerMappings.includes(pmpk));
|
||||
|
||||
const managedMappings = () =>
|
||||
propertyMappings
|
||||
.filter((pm) => (pm?.managed ?? "").startsWith("goauthentik.io/providers/saml"))
|
||||
.map((pm) => pm.pk);
|
||||
|
||||
const pmValues = provider?.propertyMappings
|
||||
? configuredMappings(provider?.propertyMappings ?? [])
|
||||
: managedMappings();
|
||||
|
||||
const propertyPairs = propertyMappings.map((pm) => [pm.pk, pm.name]);
|
||||
|
||||
return { pmValues, propertyPairs };
|
||||
}
|
||||
|
||||
render() {
|
||||
const provider = this.wizard.provider as SAMLProvider | undefined;
|
||||
const errors = this.wizard.errors.provider;
|
||||
|
||||
const { pmValues, propertyPairs } = this.propertyMappingConfiguration(provider);
|
||||
const setHasSigningKp = (ev: InputEvent) => {
|
||||
const target = ev.target as AkCryptoCertificateSearch;
|
||||
if (!target) return;
|
||||
this.hasSigningKp = !!target.selectedKeypair;
|
||||
};
|
||||
|
||||
return html` <ak-wizard-title>${msg("Configure SAML Provider")}</ak-wizard-title>
|
||||
<form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
|
||||
<ak-text-input
|
||||
name="name"
|
||||
value=${ifDefined(provider?.name)}
|
||||
required
|
||||
label=${msg("Name")}
|
||||
.errorMessages=${errors?.name ?? []}
|
||||
></ak-text-input>
|
||||
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Authorization flow")}
|
||||
?required=${true}
|
||||
name="authorizationFlow"
|
||||
.errorMessages=${errors?.authorizationFlow ?? []}
|
||||
>
|
||||
<ak-flow-search
|
||||
flowType=${FlowsInstancesListDesignationEnum.Authorization}
|
||||
.currentFlow=${provider?.authorizationFlow}
|
||||
required
|
||||
></ak-flow-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Flow used when authorizing this provider.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-form-group .expanded=${true}>
|
||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-text-input
|
||||
name="acsUrl"
|
||||
value=${ifDefined(provider?.acsUrl)}
|
||||
required
|
||||
label=${msg("ACS URL")}
|
||||
.errorMessages=${errors?.acsUrl ?? []}
|
||||
></ak-text-input>
|
||||
|
||||
<ak-text-input
|
||||
name="issuer"
|
||||
value=${provider?.issuer || "authentik"}
|
||||
required
|
||||
label=${msg("Issuer")}
|
||||
help=${msg("Also known as EntityID.")}
|
||||
.errorMessages=${errors?.issuer ?? []}
|
||||
></ak-text-input>
|
||||
|
||||
<ak-radio-input
|
||||
name="spBinding"
|
||||
label=${msg("Service Provider Binding")}
|
||||
required
|
||||
.options=${spBindingOptions}
|
||||
.value=${provider?.spBinding}
|
||||
help=${msg(
|
||||
"Determines how authentik sends the response back to the Service Provider.",
|
||||
)}
|
||||
>
|
||||
</ak-radio-input>
|
||||
|
||||
<ak-text-input
|
||||
name="audience"
|
||||
value=${ifDefined(provider?.audience)}
|
||||
label=${msg("Audience")}
|
||||
.errorMessages=${errors?.audience ?? []}
|
||||
></ak-text-input>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Advanced flow settings")}</span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
name="authenticationFlow"
|
||||
label=${msg("Authentication flow")}
|
||||
>
|
||||
<ak-flow-search
|
||||
flowType=${FlowsInstancesListDesignationEnum.Authentication}
|
||||
.currentFlow=${provider?.authenticationFlow}
|
||||
></ak-flow-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"Flow used when a user access this provider and is not authenticated.",
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Invalidation flow")}
|
||||
name="invalidationFlow"
|
||||
required
|
||||
>
|
||||
<ak-flow-search
|
||||
flowType=${FlowsInstancesListDesignationEnum.Invalidation}
|
||||
.currentFlow=${provider?.invalidationFlow}
|
||||
defaultFlowSlug="default-provider-invalidation-flow"
|
||||
required
|
||||
></ak-flow-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Flow used when logging out of this provider.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Advanced protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Signing Certificate")}
|
||||
name="signingKp"
|
||||
>
|
||||
<ak-crypto-certificate-search
|
||||
certificate=${ifDefined(provider?.signingKp ?? undefined)}
|
||||
@input=${(ev: InputEvent) => {
|
||||
const target = ev.target as AkCryptoCertificateSearch;
|
||||
if (!target) return;
|
||||
this.hasSigningKp = !!target.selectedKeypair;
|
||||
}}
|
||||
></ak-crypto-certificate-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"Certificate used to sign outgoing Responses going to the Service Provider.",
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
${this.hasSigningKp
|
||||
? html` <ak-form-element-horizontal name="signAssertion">
|
||||
<label class="pf-c-switch">
|
||||
<input
|
||||
class="pf-c-switch__input"
|
||||
type="checkbox"
|
||||
?checked=${first(provider?.signAssertion, true)}
|
||||
/>
|
||||
<span class="pf-c-switch__toggle">
|
||||
<span class="pf-c-switch__toggle-icon">
|
||||
<i class="fas fa-check" aria-hidden="true"></i>
|
||||
</span>
|
||||
</span>
|
||||
<span class="pf-c-switch__label"
|
||||
>${msg("Sign assertions")}</span
|
||||
>
|
||||
</label>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"When enabled, the assertion element of the SAML response will be signed.",
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal name="signResponse">
|
||||
<label class="pf-c-switch">
|
||||
<input
|
||||
class="pf-c-switch__input"
|
||||
type="checkbox"
|
||||
?checked=${first(provider?.signResponse, false)}
|
||||
/>
|
||||
<span class="pf-c-switch__toggle">
|
||||
<span class="pf-c-switch__toggle-icon">
|
||||
<i class="fas fa-check" aria-hidden="true"></i>
|
||||
</span>
|
||||
</span>
|
||||
<span class="pf-c-switch__label"
|
||||
>${msg("Sign responses")}</span
|
||||
>
|
||||
</label>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"When enabled, the assertion element of the SAML response will be signed.",
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>`
|
||||
: nothing}
|
||||
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Verification Certificate")}
|
||||
name="verificationKp"
|
||||
>
|
||||
<ak-crypto-certificate-search
|
||||
certificate=${ifDefined(provider?.verificationKp ?? undefined)}
|
||||
nokey
|
||||
></ak-crypto-certificate-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"When selected, incoming assertion's Signatures will be validated against this certificate. To allow unsigned Requests, leave on default.",
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Encryption Certificate")}
|
||||
name="encryptionKp"
|
||||
>
|
||||
<ak-crypto-certificate-search
|
||||
certificate=${ifDefined(provider?.encryptionKp ?? undefined)}
|
||||
></ak-crypto-certificate-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"When selected, encrypted assertions will be decrypted using this keypair.",
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-multi-select
|
||||
label=${msg("Property Mappings")}
|
||||
name="propertyMappings"
|
||||
.options=${propertyPairs}
|
||||
.values=${pmValues}
|
||||
.richhelp=${html` <p class="pf-c-form__helper-text">
|
||||
${msg("Property mappings used for user mapping.")}
|
||||
</p>`}
|
||||
></ak-multi-select>
|
||||
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("NameID Property Mapping")}
|
||||
name="nameIdMapping"
|
||||
>
|
||||
<ak-saml-property-mapping-search
|
||||
name="nameIdMapping"
|
||||
propertymapping=${ifDefined(provider?.nameIdMapping ?? undefined)}
|
||||
></ak-saml-property-mapping-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected.",
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-text-input
|
||||
name="assertionValidNotBefore"
|
||||
value=${provider?.assertionValidNotBefore || "minutes=-5"}
|
||||
required
|
||||
label=${msg("Assertion valid not before")}
|
||||
help=${msg(
|
||||
"Configure the maximum allowed time drift for an assertion.",
|
||||
)}
|
||||
.errorMessages=${errors?.assertionValidNotBefore ?? []}
|
||||
></ak-text-input>
|
||||
|
||||
<ak-text-input
|
||||
name="assertionValidNotOnOrAfter"
|
||||
value=${provider?.assertionValidNotOnOrAfter || "minutes=5"}
|
||||
required
|
||||
label=${msg("Assertion valid not on or after")}
|
||||
help=${msg(
|
||||
"Assertion not valid on or after current time + this value.",
|
||||
)}
|
||||
.errorMessages=${errors?.assertionValidNotOnOrAfter ?? []}
|
||||
></ak-text-input>
|
||||
|
||||
<ak-text-input
|
||||
name="sessionValidNotOnOrAfter"
|
||||
value=${provider?.sessionValidNotOnOrAfter || "minutes=86400"}
|
||||
required
|
||||
label=${msg("Session valid not on or after")}
|
||||
help=${msg("Session not valid on or after current time + this value.")}
|
||||
.errorMessages=${errors?.sessionValidNotOnOrAfter ?? []}
|
||||
></ak-text-input>
|
||||
|
||||
<ak-radio-input
|
||||
name="digestAlgorithm"
|
||||
label=${msg("Digest algorithm")}
|
||||
required
|
||||
.options=${digestAlgorithmOptions}
|
||||
.value=${provider?.digestAlgorithm}
|
||||
>
|
||||
</ak-radio-input>
|
||||
|
||||
<ak-radio-input
|
||||
name="signatureAlgorithm"
|
||||
label=${msg("Signature algorithm")}
|
||||
required
|
||||
.options=${signatureAlgorithmOptions}
|
||||
.value=${provider?.signatureAlgorithm}
|
||||
>
|
||||
</ak-radio-input>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
${renderForm(
|
||||
(this.wizard.provider as SAMLProvider) ?? {},
|
||||
this.wizard.errors.provider,
|
||||
setHasSigningKp,
|
||||
this.hasSigningKp,
|
||||
)}
|
||||
</form>`;
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,10 @@
|
||||
import "@goauthentik/admin/applications/wizard/ak-wizard-title";
|
||||
import "@goauthentik/admin/common/ak-core-group-search";
|
||||
import "@goauthentik/admin/common/ak-crypto-certificate-search";
|
||||
import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { first } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/components/ak-multi-select";
|
||||
import "@goauthentik/components/ak-switch-input";
|
||||
import "@goauthentik/components/ak-text-input";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import { renderForm } from "@goauthentik/admin/providers/scim/SCIMProviderFormForm.js";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { customElement, state } from "@lit/reactive-element/decorators.js";
|
||||
import { html } from "lit";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import { PaginatedSCIMMappingList, PropertymappingsApi, type SCIMProvider } from "@goauthentik/api";
|
||||
import { PaginatedSCIMMappingList, type SCIMProvider } from "@goauthentik/api";
|
||||
|
||||
import BaseProviderPanel from "../BaseProviderPanel";
|
||||
|
||||
@ -26,125 +15,15 @@ export class ApplicationWizardAuthenticationBySCIM extends BaseProviderPanel {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
new PropertymappingsApi(DEFAULT_CONFIG)
|
||||
.propertymappingsProviderScimList({
|
||||
ordering: "managed",
|
||||
})
|
||||
.then((propertyMappings: PaginatedSCIMMappingList) => {
|
||||
this.propertyMappings = propertyMappings;
|
||||
});
|
||||
}
|
||||
|
||||
propertyMappingConfiguration(provider?: SCIMProvider) {
|
||||
const propertyMappings = this.propertyMappings?.results ?? [];
|
||||
|
||||
const configuredMappings = (providerMappings: string[]) =>
|
||||
propertyMappings.map((pm) => pm.pk).filter((pmpk) => providerMappings.includes(pmpk));
|
||||
|
||||
const managedMappings = (key: string) =>
|
||||
propertyMappings
|
||||
.filter((pm) => pm.managed === `goauthentik.io/providers/scim/${key}`)
|
||||
.map((pm) => pm.pk);
|
||||
|
||||
const pmUserValues = provider?.propertyMappings
|
||||
? configuredMappings(provider?.propertyMappings ?? [])
|
||||
: managedMappings("user");
|
||||
|
||||
const pmGroupValues = provider?.propertyMappingsGroup
|
||||
? configuredMappings(provider?.propertyMappingsGroup ?? [])
|
||||
: managedMappings("group");
|
||||
|
||||
const propertyPairs = propertyMappings.map((pm) => [pm.pk, pm.name]);
|
||||
|
||||
return { pmUserValues, pmGroupValues, propertyPairs };
|
||||
}
|
||||
|
||||
render() {
|
||||
const provider = this.wizard.provider as SCIMProvider | undefined;
|
||||
const errors = this.wizard.errors.provider;
|
||||
|
||||
const { pmUserValues, pmGroupValues, propertyPairs } =
|
||||
this.propertyMappingConfiguration(provider);
|
||||
|
||||
return html`<ak-wizard-title>${msg("Configure SCIM Provider")}</ak-wizard-title>
|
||||
<form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
|
||||
<ak-text-input
|
||||
name="name"
|
||||
label=${msg("Name")}
|
||||
value=${ifDefined(provider?.name)}
|
||||
.errorMessages=${errors?.name ?? []}
|
||||
required
|
||||
></ak-text-input>
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-text-input
|
||||
name="url"
|
||||
label=${msg("URL")}
|
||||
value="${first(provider?.url, "")}"
|
||||
required
|
||||
help=${msg("SCIM base url, usually ends in /v2.")}
|
||||
.errorMessages=${errors?.url ?? []}
|
||||
>
|
||||
</ak-text-input>
|
||||
<ak-text-input
|
||||
name="token"
|
||||
label=${msg("Token")}
|
||||
value="${first(provider?.token, "")}"
|
||||
.errorMessages=${errors?.token ?? []}
|
||||
required
|
||||
help=${msg(
|
||||
"Token to authenticate with. Currently only bearer authentication is supported.",
|
||||
)}
|
||||
>
|
||||
</ak-text-input>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group expanded>
|
||||
<span slot="header">${msg("User filtering")}</span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-switch-input
|
||||
name="excludeUsersServiceAccount"
|
||||
?checked=${first(provider?.excludeUsersServiceAccount, true)}
|
||||
label=${msg("Exclude service accounts")}
|
||||
></ak-switch-input>
|
||||
<ak-form-element-horizontal label=${msg("Group")} name="filterGroup">
|
||||
<ak-core-group-search
|
||||
.group=${provider?.filterGroup}
|
||||
></ak-core-group-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Only sync users within the selected group.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group ?expanded=${true}>
|
||||
<span slot="header"> ${msg("Attribute mapping")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-multi-select
|
||||
label=${msg("User Property Mappings")}
|
||||
name="propertyMappings"
|
||||
.options=${propertyPairs}
|
||||
.values=${pmUserValues}
|
||||
.richhelp=${html`
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Property mappings used for user mapping.")}
|
||||
</p>
|
||||
`}
|
||||
></ak-multi-select>
|
||||
<ak-multi-select
|
||||
label=${msg("Group Property Mappings")}
|
||||
name="propertyMappingsGroup"
|
||||
.options=${propertyPairs}
|
||||
.values=${pmGroupValues}
|
||||
.richhelp=${html`
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Property mappings used for group creation.")}
|
||||
</p>
|
||||
`}
|
||||
></ak-multi-select>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
${renderForm(
|
||||
(this.wizard.provider as SCIMProvider) ?? {},
|
||||
this.wizard.errors.provider,
|
||||
)}
|
||||
</form>`;
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
type LDAPProviderRequest,
|
||||
type OAuth2ProviderRequest,
|
||||
type ProvidersSamlImportMetadataCreateRequest,
|
||||
ProxyMode,
|
||||
type ProxyProviderRequest,
|
||||
type RACProviderRequest,
|
||||
type RadiusProviderRequest,
|
||||
@ -27,6 +28,7 @@ export interface ApplicationWizardState {
|
||||
providerModel: string;
|
||||
app: Partial<ApplicationRequest>;
|
||||
provider: OneOfProvider;
|
||||
proxyMode?: ProxyMode;
|
||||
errors: ValidationError;
|
||||
}
|
||||
|
||||
|
@ -5,8 +5,8 @@ import "@goauthentik/elements/forms/SearchSelect";
|
||||
import { CustomListenerElement } from "@goauthentik/elements/utils/eventEmitter";
|
||||
|
||||
import { html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { property, query } from "lit/decorators.js";
|
||||
import { customElement, property, query } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import {
|
||||
CertificateKeyPair,
|
||||
@ -114,6 +114,7 @@ export class AkCryptoCertificateSearch extends CustomListenerElement(AKElement)
|
||||
render() {
|
||||
return html`
|
||||
<ak-search-select
|
||||
name=${ifDefined(this.name ?? undefined)}
|
||||
.fetchObjects=${this.fetchObjects}
|
||||
.renderElement=${renderElement}
|
||||
.value=${renderValue}
|
||||
|
@ -7,6 +7,7 @@ import { CustomListenerElement } from "@goauthentik/elements/utils/eventEmitter"
|
||||
|
||||
import { html } from "lit";
|
||||
import { property, query } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import { FlowsApi, FlowsInstancesListDesignationEnum } from "@goauthentik/api";
|
||||
import type { Flow, FlowsInstancesListRequest } from "@goauthentik/api";
|
||||
@ -133,7 +134,7 @@ export class FlowSearch<T extends Flow> extends CustomListenerElement(AKElement)
|
||||
.renderElement=${renderElement}
|
||||
.renderDescription=${renderDescription}
|
||||
.value=${getFlowValue}
|
||||
.name=${this.name}
|
||||
name=${ifDefined(this.name ?? undefined)}
|
||||
@ak-change=${this.handleSearchUpdate}
|
||||
?blankable=${!this.required}
|
||||
>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { severityToLabel } from "@goauthentik/common/labels";
|
||||
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 { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||
import "@goauthentik/elements/forms/Radio";
|
||||
@ -17,12 +18,32 @@ import {
|
||||
EventsApi,
|
||||
Group,
|
||||
NotificationRule,
|
||||
NotificationTransport,
|
||||
PaginatedNotificationTransportList,
|
||||
SeverityEnum,
|
||||
} 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")
|
||||
export class RuleForm extends ModelForm<NotificationRule, string> {
|
||||
eventTransports?: PaginatedNotificationTransportList;
|
||||
@ -105,7 +126,7 @@ export class RuleForm extends ModelForm<NotificationRule, string> {
|
||||
>
|
||||
<ak-dual-select-dynamic-selected
|
||||
.provider=${eventTransportsProvider}
|
||||
.selector=${eventTransportsSelector(this.instance?.transports)}
|
||||
.selector=${makeTransportSelector(this.instance?.transports)}
|
||||
available-label="${msg("Available Transports")}"
|
||||
selected-label="${msg("Selected Transports")}"
|
||||
></ak-dual-select-dynamic-selected>
|
||||
|
@ -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);
|
||||
};
|
||||
}
|
@ -43,8 +43,11 @@ export class ProviderWizard extends AKElement {
|
||||
@query("ak-wizard")
|
||||
wizard?: Wizard;
|
||||
|
||||
async firstUpdated(): Promise<void> {
|
||||
this.providerTypes = await new ProvidersApi(DEFAULT_CONFIG).providersAllTypesList();
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
new ProvidersApi(DEFAULT_CONFIG).providersAllTypesList().then((providerTypes) => {
|
||||
this.providerTypes = providerTypes;
|
||||
});
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
@ -58,6 +61,7 @@ export class ProviderWizard extends AKElement {
|
||||
}}
|
||||
>
|
||||
<ak-wizard-page-type-create
|
||||
name="selectProviderType"
|
||||
slot="initial"
|
||||
layout=${TypeCreateWizardPageLayouts.grid}
|
||||
.types=${this.providerTypes}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm";
|
||||
import {
|
||||
propertyMappingsProvider,
|
||||
propertyMappingsSelector,
|
||||
} from "@goauthentik/admin/providers/google_workspace/GoogleWorkspaceProviderFormHelpers.js";
|
||||
googleWorkspacePropertyMappingsProvider,
|
||||
makeGoogleWorkspacePropertyMappingsSelector,
|
||||
} from "@goauthentik/admin/providers/google_workspace/GoogleWorkspaceProviderPropertyMappings";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { first } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/elements/CodeMirror";
|
||||
@ -224,8 +224,8 @@ export class GoogleWorkspaceProviderFormPage extends BaseProviderForm<GoogleWork
|
||||
name="propertyMappings"
|
||||
>
|
||||
<ak-dual-select-dynamic-selected
|
||||
.provider=${propertyMappingsProvider}
|
||||
.selector=${propertyMappingsSelector(
|
||||
.provider=${googleWorkspacePropertyMappingsProvider}
|
||||
.selector=${makeGoogleWorkspacePropertyMappingsSelector(
|
||||
this.instance?.propertyMappings,
|
||||
"goauthentik.io/providers/google_workspace/user",
|
||||
)}
|
||||
@ -241,8 +241,8 @@ export class GoogleWorkspaceProviderFormPage extends BaseProviderForm<GoogleWork
|
||||
name="propertyMappingsGroup"
|
||||
>
|
||||
<ak-dual-select-dynamic-selected
|
||||
.provider=${propertyMappingsProvider}
|
||||
.selector=${propertyMappingsSelector(
|
||||
.provider=${googleWorkspacePropertyMappingsProvider}
|
||||
.selector=${makeGoogleWorkspacePropertyMappingsSelector(
|
||||
this.instance?.propertyMappingsGroup,
|
||||
"goauthentik.io/providers/google_workspace/group",
|
||||
)}
|
||||
|
@ -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);
|
||||
};
|
||||
}
|
@ -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;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user