Compare commits
132 Commits
version/20
...
version/20
Author | SHA1 | Date | |
---|---|---|---|
eaad564e23 | |||
511a94975b | |||
015810a2fd | |||
e70e6b84c2 | |||
d0b9c9a26f | |||
3e403fa348 | |||
48f4a971ef | |||
6314be14ad | |||
1a072c6c39 | |||
ef2eed0bdf | |||
91227b1e96 | |||
67d68629da | |||
e875db8f66 | |||
055a76393d | |||
0754821628 | |||
fca88d9896 | |||
dfe0404c51 | |||
fa61696b46 | |||
e5773738f4 | |||
cac8539d79 | |||
cf600f6f26 | |||
49dfb4756e | |||
814758e2aa | |||
5c42dac5e2 | |||
88603fa4f7 | |||
0232c4e162 | |||
11753c1fe1 | |||
f5cc6c67ec | |||
8b8ed3527a | |||
1aa0274e7c | |||
ecd33ca0c1 | |||
e93be0de9a | |||
a5adc4f8ed | |||
a6baed9753 | |||
ceaf832e63 | |||
a6b0b14685 | |||
f679250edd | |||
acc4de2235 | |||
56a8276dbf | |||
6dfe6edbef | |||
6af4bd0d9a | |||
7ee7f6bd6a | |||
f8b8334010 | |||
d4b65dc4b4 | |||
e4bbd3b1c0 | |||
87de5e625d | |||
efbe51673e | |||
a95bea53ea | |||
6021fc0f52 | |||
1415b68ff4 | |||
be6853ac52 | |||
7fd6be5abb | |||
91d6f572a5 | |||
016a9ce34e | |||
8adb95af7f | |||
1dc54775d8 | |||
370ef716b5 | |||
16e56ad9ca | |||
b5b5a9eed3 | |||
8b22e7bcc3 | |||
d48b5b9511 | |||
0eccaa3f1e | |||
67d550a80d | |||
ebb5711c32 | |||
79ec872232 | |||
4284e14ff7 | |||
92a09779d0 | |||
14c621631d | |||
c55f503b9b | |||
a908cad976 | |||
c2586557d8 | |||
01c80a82e2 | |||
0d47654651 | |||
1183095833 | |||
c281b11bdc | |||
61fe45a58c | |||
d43aab479c | |||
7f8383427a | |||
a06d6cf33d | |||
5b7cb205c9 | |||
293a932d20 | |||
fff901ff03 | |||
f47c936295 | |||
88d5aec618 | |||
96ae68cf09 | |||
63b3434b6f | |||
947ecec02b | |||
1c2b452406 | |||
47777529ac | |||
949095c376 | |||
4b112c2799 | |||
291a2516b1 | |||
4dcfd021e2 | |||
ca50848db3 | |||
0bb3e3c558 | |||
e4b25809ab | |||
7bf932f8e2 | |||
99d04528b0 | |||
e48d172036 | |||
c2388137a8 | |||
650e2cbc38 | |||
b32800ea71 | |||
e1c0c0b20c | |||
fe39e39dcd | |||
883f213b03 | |||
538996f617 | |||
2f4c92deb9 | |||
ef335ec083 | |||
07b09df3fe | |||
e70e031a1f | |||
c7ba183dc0 | |||
3ed23a37ea | |||
3d724db0e3 | |||
2997542114 | |||
84b18fff96 | |||
1dce408c72 | |||
e5ff47bf14 | |||
b53bf331c3 | |||
90e9a8b34c | |||
845f842783 | |||
7397849c60 | |||
6dd46b5fc5 | |||
89ca79ed10 | |||
713bef895c | |||
925115e9ce | |||
42f5cf8c93 | |||
82cc1d536a | |||
08af2fd46b | |||
70e3b27a4d | |||
6a411d7960 | |||
33567b56d7 | |||
0c1954aeb7 |
@ -1,5 +1,5 @@
|
||||
[bumpversion]
|
||||
current_version = 2022.1.1
|
||||
current_version = 2022.1.5
|
||||
tag = True
|
||||
commit = True
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*)
|
||||
|
14
.github/workflows/ci-main.yml
vendored
14
.github/workflows/ci-main.yml
vendored
@ -40,7 +40,7 @@ jobs:
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: ~/.cache/pypoetry/virtualenvs
|
||||
key: ${{ runner.os }}-poetry-cache-v3-${{ hashFiles('**/poetry.lock') }}
|
||||
key: ${{ runner.os }}-poetry-cache-v2-${{ hashFiles('**/poetry.lock') }}
|
||||
- name: prepare
|
||||
env:
|
||||
INSTALL: ${{ steps.cache-poetry.outputs.cache-hit }}
|
||||
@ -56,7 +56,7 @@ jobs:
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: ~/.cache/pypoetry/virtualenvs
|
||||
key: ${{ runner.os }}-poetry-cache-v3-${{ hashFiles('**/poetry.lock') }}
|
||||
key: ${{ runner.os }}-poetry-cache-v2-${{ hashFiles('**/poetry.lock') }}
|
||||
- name: prepare
|
||||
env:
|
||||
INSTALL: ${{ steps.cache-poetry.outputs.cache-hit }}
|
||||
@ -79,7 +79,7 @@ jobs:
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: ~/.cache/pypoetry/virtualenvs
|
||||
key: ${{ runner.os }}-poetry-cache-v3-${{ hashFiles('**/poetry.lock') }}
|
||||
key: ${{ runner.os }}-poetry-cache-v2-${{ hashFiles('**/poetry.lock') }}
|
||||
- name: checkout stable
|
||||
run: |
|
||||
# Copy current, latest config to local
|
||||
@ -121,7 +121,7 @@ jobs:
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: ~/.cache/pypoetry/virtualenvs
|
||||
key: ${{ runner.os }}-poetry-cache-v3-${{ hashFiles('**/poetry.lock') }}
|
||||
key: ${{ runner.os }}-poetry-cache-v2-${{ hashFiles('**/poetry.lock') }}
|
||||
- name: prepare
|
||||
env:
|
||||
INSTALL: ${{ steps.cache-poetry.outputs.cache-hit }}
|
||||
@ -148,7 +148,7 @@ jobs:
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: ~/.cache/pypoetry/virtualenvs
|
||||
key: ${{ runner.os }}-poetry-cache-v3-${{ hashFiles('**/poetry.lock') }}
|
||||
key: ${{ runner.os }}-poetry-cache-v2-${{ hashFiles('**/poetry.lock') }}
|
||||
- name: prepare
|
||||
env:
|
||||
INSTALL: ${{ steps.cache-poetry.outputs.cache-hit }}
|
||||
@ -185,7 +185,7 @@ jobs:
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: ~/.cache/pypoetry/virtualenvs
|
||||
key: ${{ runner.os }}-poetry-cache-v3-${{ hashFiles('**/poetry.lock') }}
|
||||
key: ${{ runner.os }}-poetry-cache-v2-${{ hashFiles('**/poetry.lock') }}
|
||||
- name: prepare
|
||||
env:
|
||||
INSTALL: ${{ steps.cache-poetry.outputs.cache-hit }}
|
||||
@ -230,7 +230,7 @@ jobs:
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: ~/.cache/pypoetry/virtualenvs
|
||||
key: ${{ runner.os }}-poetry-cache-v3-${{ hashFiles('**/poetry.lock') }}
|
||||
key: ${{ runner.os }}-poetry-cache-v2-${{ hashFiles('**/poetry.lock') }}
|
||||
- name: prepare
|
||||
env:
|
||||
INSTALL: ${{ steps.cache-poetry.outputs.cache-hit }}
|
||||
|
16
.github/workflows/ci-outpost.yml
vendored
16
.github/workflows/ci-outpost.yml
vendored
@ -30,9 +30,25 @@ jobs:
|
||||
-w /app \
|
||||
golangci/golangci-lint:v1.43 \
|
||||
golangci-lint run -v --timeout 200s
|
||||
test-unittest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: "^1.17"
|
||||
- name: Get dependencies
|
||||
run: |
|
||||
go get github.com/axw/gocov/gocov
|
||||
go get github.com/AlekSi/gocov-xml
|
||||
go get github.com/jstemmer/go-junit-report
|
||||
- name: Go unittests
|
||||
run: |
|
||||
go test -timeout 0 -v -race -coverprofile=coverage.out -covermode=atomic -cover ./... | go-junit-report > junit.xml
|
||||
ci-outpost-mark:
|
||||
needs:
|
||||
- lint-golint
|
||||
- test-unittest
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo mark
|
||||
|
14
.github/workflows/release-publish.yml
vendored
14
.github/workflows/release-publish.yml
vendored
@ -30,14 +30,14 @@ jobs:
|
||||
with:
|
||||
push: ${{ github.event_name == 'release' }}
|
||||
tags: |
|
||||
beryju/authentik:2022.1.1,
|
||||
beryju/authentik:2022.1.5,
|
||||
beryju/authentik:latest,
|
||||
ghcr.io/goauthentik/server:2022.1.1,
|
||||
ghcr.io/goauthentik/server:2022.1.5,
|
||||
ghcr.io/goauthentik/server:latest
|
||||
platforms: linux/amd64,linux/arm64
|
||||
context: .
|
||||
- name: Building Docker Image (stable)
|
||||
if: ${{ github.event_name == 'release' && !contains('2022.1.1', 'rc') }}
|
||||
if: ${{ github.event_name == 'release' && !contains('2022.1.5', 'rc') }}
|
||||
run: |
|
||||
docker pull beryju/authentik:latest
|
||||
docker tag beryju/authentik:latest beryju/authentik:stable
|
||||
@ -78,14 +78,14 @@ jobs:
|
||||
with:
|
||||
push: ${{ github.event_name == 'release' }}
|
||||
tags: |
|
||||
beryju/authentik-${{ matrix.type }}:2022.1.1,
|
||||
beryju/authentik-${{ matrix.type }}:2022.1.5,
|
||||
beryju/authentik-${{ matrix.type }}:latest,
|
||||
ghcr.io/goauthentik/${{ matrix.type }}:2022.1.1,
|
||||
ghcr.io/goauthentik/${{ matrix.type }}:2022.1.5,
|
||||
ghcr.io/goauthentik/${{ matrix.type }}:latest
|
||||
file: ${{ matrix.type }}.Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
- name: Building Docker Image (stable)
|
||||
if: ${{ github.event_name == 'release' && !contains('2022.1.1', 'rc') }}
|
||||
if: ${{ github.event_name == 'release' && !contains('2022.1.5', 'rc') }}
|
||||
run: |
|
||||
docker pull beryju/authentik-${{ matrix.type }}:latest
|
||||
docker tag beryju/authentik-${{ matrix.type }}:latest beryju/authentik-${{ matrix.type }}:stable
|
||||
@ -170,7 +170,7 @@ jobs:
|
||||
SENTRY_PROJECT: authentik
|
||||
SENTRY_URL: https://sentry.beryju.org
|
||||
with:
|
||||
version: authentik@2022.1.1
|
||||
version: authentik@2022.1.5
|
||||
environment: beryjuorg-prod
|
||||
sourcemaps: './web/dist'
|
||||
url_prefix: '~/static/dist'
|
||||
|
2
.github/workflows/translation-compile.yml
vendored
2
.github/workflows/translation-compile.yml
vendored
@ -26,7 +26,7 @@ jobs:
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: ~/.cache/pypoetry/virtualenvs
|
||||
key: ${{ runner.os }}-poetry-cache-v3-${{ hashFiles('**/poetry.lock') }}
|
||||
key: ${{ runner.os }}-poetry-cache-v2-${{ hashFiles('**/poetry.lock') }}
|
||||
- name: prepare
|
||||
env:
|
||||
INSTALL: ${{ steps.cache-poetry.outputs.cache-hit }}
|
||||
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -12,7 +12,8 @@
|
||||
"totp",
|
||||
"webauthn",
|
||||
"traefik",
|
||||
"passwordless"
|
||||
"passwordless",
|
||||
"kubernetes"
|
||||
],
|
||||
"python.linting.pylintEnabled": true,
|
||||
"todo-tree.tree.showCountsInTree": true,
|
||||
|
3
Makefile
3
Makefile
@ -15,6 +15,9 @@ test-e2e-provider:
|
||||
test-e2e-rest:
|
||||
coverage run manage.py test tests/e2e/test_flows* tests/e2e/test_source*
|
||||
|
||||
test-go:
|
||||
go test -timeout 0 -v -race -cover ./...
|
||||
|
||||
test:
|
||||
coverage run manage.py test authentik
|
||||
coverage html
|
||||
|
@ -57,4 +57,4 @@ DigitalOcean provides development and testing resources for authentik.
|
||||
</a>
|
||||
</p>
|
||||
|
||||
Netlify hosts the [goauthentik.io](goauthentik.io) site.
|
||||
Netlify hosts the [goauthentik.io](https://goauthentik.io) site.
|
||||
|
@ -2,7 +2,7 @@
|
||||
from os import environ
|
||||
from typing import Optional
|
||||
|
||||
__version__ = "2022.1.1"
|
||||
__version__ = "2022.1.5"
|
||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||
|
||||
|
||||
|
@ -1,13 +1,16 @@
|
||||
"""Application API Views"""
|
||||
from typing import Optional
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.db.models import QuerySet
|
||||
from django.http.response import HttpResponseBadRequest
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.functional import SimpleLazyObject
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema
|
||||
from guardian.shortcuts import get_objects_for_user
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.fields import ReadOnlyField
|
||||
from rest_framework.fields import ReadOnlyField, SerializerMethodField
|
||||
from rest_framework.parsers import MultiPartParser
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
@ -39,11 +42,22 @@ def user_app_cache_key(user_pk: str) -> str:
|
||||
class ApplicationSerializer(ModelSerializer):
|
||||
"""Application Serializer"""
|
||||
|
||||
launch_url = ReadOnlyField(source="get_launch_url")
|
||||
launch_url = SerializerMethodField()
|
||||
provider_obj = ProviderSerializer(source="get_provider", required=False)
|
||||
|
||||
meta_icon = ReadOnlyField(source="get_meta_icon")
|
||||
|
||||
def get_launch_url(self, app: Application) -> Optional[str]:
|
||||
"""Allow formatting of launch URL"""
|
||||
url = app.get_launch_url()
|
||||
if not url:
|
||||
return url
|
||||
user = self.context["request"].user
|
||||
if isinstance(user, SimpleLazyObject):
|
||||
user._setup()
|
||||
user = user._wrapped
|
||||
return url % user.__dict__
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Application
|
||||
|
@ -3,7 +3,7 @@ from typing import Any
|
||||
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from drf_spectacular.utils import OpenApiResponse, extend_schema
|
||||
from guardian.shortcuts import get_anonymous_user
|
||||
from guardian.shortcuts import assign_perm, get_anonymous_user
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.fields import CharField
|
||||
@ -95,10 +95,12 @@ class TokenViewSet(UsedByMixin, ModelViewSet):
|
||||
|
||||
def perform_create(self, serializer: TokenSerializer):
|
||||
if not self.request.user.is_superuser:
|
||||
return serializer.save(
|
||||
instance = serializer.save(
|
||||
user=self.request.user,
|
||||
expiring=self.request.user.attributes.get(USER_ATTRIBUTE_TOKEN_EXPIRING, True),
|
||||
)
|
||||
assign_perm("authentik_core.view_token_key", self.request.user, instance)
|
||||
return instance
|
||||
return super().perform_create(serializer)
|
||||
|
||||
@permission_required("authentik_core.view_token_key")
|
||||
|
@ -13,7 +13,9 @@ class TestApplicationsAPI(APITestCase):
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.user = create_test_admin_user()
|
||||
self.allowed = Application.objects.create(name="allowed", slug="allowed")
|
||||
self.allowed = Application.objects.create(
|
||||
name="allowed", slug="allowed", meta_launch_url="https://goauthentik.io/%(username)s"
|
||||
)
|
||||
self.denied = Application.objects.create(name="denied", slug="denied")
|
||||
PolicyBinding.objects.create(
|
||||
target=self.denied,
|
||||
@ -64,8 +66,8 @@ class TestApplicationsAPI(APITestCase):
|
||||
"slug": "allowed",
|
||||
"provider": None,
|
||||
"provider_obj": None,
|
||||
"launch_url": None,
|
||||
"meta_launch_url": "",
|
||||
"launch_url": f"https://goauthentik.io/{self.user.username}",
|
||||
"meta_launch_url": "https://goauthentik.io/%(username)s",
|
||||
"meta_icon": None,
|
||||
"meta_description": "",
|
||||
"meta_publisher": "",
|
||||
@ -100,8 +102,8 @@ class TestApplicationsAPI(APITestCase):
|
||||
"slug": "allowed",
|
||||
"provider": None,
|
||||
"provider_obj": None,
|
||||
"launch_url": None,
|
||||
"meta_launch_url": "",
|
||||
"launch_url": f"https://goauthentik.io/{self.user.username}",
|
||||
"meta_launch_url": "https://goauthentik.io/%(username)s",
|
||||
"meta_icon": None,
|
||||
"meta_description": "",
|
||||
"meta_publisher": "",
|
||||
|
@ -30,6 +30,7 @@ class TestTokenAPI(APITestCase):
|
||||
self.assertEqual(token.user, self.user)
|
||||
self.assertEqual(token.intent, TokenIntents.INTENT_API)
|
||||
self.assertEqual(token.expiring, True)
|
||||
self.assertTrue(self.user.has_perm("authentik_core.view_token_key", token))
|
||||
|
||||
def test_token_create_invalid(self):
|
||||
"""Test token creation endpoint (invalid data)"""
|
||||
|
@ -6,7 +6,7 @@ postgresql:
|
||||
port: 5432
|
||||
password: 'env://POSTGRES_PASSWORD'
|
||||
backup:
|
||||
enabled: true
|
||||
enabled: false
|
||||
s3_backup:
|
||||
access_key: ""
|
||||
secret_key: ""
|
||||
|
@ -32,6 +32,7 @@ class BaseEvaluator:
|
||||
self._globals = {
|
||||
"regex_match": BaseEvaluator.expr_regex_match,
|
||||
"regex_replace": BaseEvaluator.expr_regex_replace,
|
||||
"list_flatten": BaseEvaluator.expr_flatten,
|
||||
"ak_is_group_member": BaseEvaluator.expr_is_group_member,
|
||||
"ak_user_by": BaseEvaluator.expr_user_by,
|
||||
"ak_logger": get_logger(),
|
||||
@ -40,6 +41,15 @@ class BaseEvaluator:
|
||||
self._context = {}
|
||||
self._filename = "BaseEvalautor"
|
||||
|
||||
@staticmethod
|
||||
def expr_flatten(value: list[Any] | Any) -> Optional[Any]:
|
||||
"""Flatten `value` if its a list"""
|
||||
if isinstance(value, list):
|
||||
if len(value) < 1:
|
||||
return None
|
||||
return value[0]
|
||||
return value
|
||||
|
||||
@staticmethod
|
||||
def expr_regex_match(value: Any, regex: str) -> bool:
|
||||
"""Expression Filter to run re.search"""
|
||||
|
@ -55,6 +55,10 @@ class OutpostConsumer(AuthJsonConsumer):
|
||||
|
||||
first_msg = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.logger = get_logger()
|
||||
|
||||
def connect(self):
|
||||
super().connect()
|
||||
uuid = self.scope["url_route"]["kwargs"]["pk"]
|
||||
@ -65,7 +69,7 @@ class OutpostConsumer(AuthJsonConsumer):
|
||||
)
|
||||
if not outpost:
|
||||
raise DenyConnection()
|
||||
self.logger = get_logger().bind(outpost=outpost)
|
||||
self.logger = self.logger.bind(outpost=outpost)
|
||||
try:
|
||||
self.accept()
|
||||
except RuntimeError as exc:
|
||||
|
@ -106,9 +106,12 @@ class DockerController(BaseController):
|
||||
).lower()
|
||||
|
||||
def _get_labels(self) -> dict[str, str]:
|
||||
return {
|
||||
labels = {
|
||||
"io.goauthentik.outpost-uuid": self.outpost.pk.hex,
|
||||
}
|
||||
if self.outpost.config.docker_labels:
|
||||
labels.update(self.outpost.config.docker_labels)
|
||||
return labels
|
||||
|
||||
def _get_env(self) -> dict[str, str]:
|
||||
return {
|
||||
|
@ -2,6 +2,7 @@
|
||||
from pathlib import Path
|
||||
|
||||
from kubernetes.client.models.v1_container_port import V1ContainerPort
|
||||
from kubernetes.client.models.v1_service_port import V1ServicePort
|
||||
from kubernetes.config.incluster_config import SERVICE_TOKEN_FILENAME
|
||||
|
||||
from authentik.outposts.controllers.k8s.triggers import NeedsRecreate
|
||||
@ -16,10 +17,31 @@ def get_namespace() -> str:
|
||||
return "default"
|
||||
|
||||
|
||||
def compare_ports(current: list[V1ContainerPort], reference: list[V1ContainerPort]):
|
||||
def compare_port(
|
||||
current: V1ServicePort | V1ContainerPort, reference: V1ServicePort | V1ContainerPort
|
||||
) -> bool:
|
||||
"""Compare a single port"""
|
||||
if current.name != reference.name:
|
||||
return False
|
||||
if current.protocol != reference.protocol:
|
||||
return False
|
||||
if isinstance(current, V1ServicePort) and isinstance(reference, V1ServicePort):
|
||||
# We only care about the target port
|
||||
if current.target_port != reference.target_port:
|
||||
return False
|
||||
if isinstance(current, V1ContainerPort) and isinstance(reference, V1ContainerPort):
|
||||
# We only care about the target port
|
||||
if current.container_port != reference.container_port:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def compare_ports(
|
||||
current: list[V1ServicePort | V1ContainerPort], reference: list[V1ServicePort | V1ContainerPort]
|
||||
):
|
||||
"""Compare ports of a list"""
|
||||
if len(current) != len(reference):
|
||||
raise NeedsRecreate()
|
||||
for port in reference:
|
||||
if port not in current:
|
||||
if not any(compare_port(port, current_port) for current_port in current):
|
||||
raise NeedsRecreate()
|
||||
|
@ -60,6 +60,7 @@ class OutpostConfig:
|
||||
|
||||
docker_network: Optional[str] = field(default=None)
|
||||
docker_map_ports: bool = field(default=True)
|
||||
docker_labels: Optional[dict[str, str]] = field(default=None)
|
||||
|
||||
container_image: Optional[str] = field(default=None)
|
||||
|
||||
|
@ -45,7 +45,7 @@ class HaveIBeenPwendPolicy(Policy):
|
||||
fields=request.context.keys(),
|
||||
)
|
||||
return PolicyResult(False, _("Password not set in context"))
|
||||
password = request.context[self.password_field]
|
||||
password = str(request.context[self.password_field])
|
||||
|
||||
pw_hash = sha1(password.encode("utf-8")).hexdigest() # nosec
|
||||
url = f"https://api.pwnedpasswords.com/range/{pw_hash[:5]}"
|
||||
|
@ -23,10 +23,12 @@ class ProxyDockerController(DockerController):
|
||||
proxy_provider: ProxyProvider
|
||||
external_host_name = urlparse(proxy_provider.external_host)
|
||||
hosts.append(f"`{external_host_name.netloc}`")
|
||||
traefik_name = f"ak-outpost-{self.outpost.pk.hex}"
|
||||
traefik_name = self.name
|
||||
labels = super()._get_labels()
|
||||
labels["traefik.enable"] = "true"
|
||||
labels[f"traefik.http.routers.{traefik_name}-router.rule"] = f"Host({','.join(hosts)})"
|
||||
labels[
|
||||
f"traefik.http.routers.{traefik_name}-router.rule"
|
||||
] = f"Host({','.join(hosts)}) && PathPrefix(`/akprox`)"
|
||||
labels[f"traefik.http.routers.{traefik_name}-router.tls"] = "true"
|
||||
labels[f"traefik.http.routers.{traefik_name}-router.service"] = f"{traefik_name}-service"
|
||||
labels[
|
||||
|
@ -15,6 +15,7 @@ from authentik.providers.saml.processors.request_parser import AuthNRequestParse
|
||||
from authentik.sources.saml.exceptions import MismatchedRequestID
|
||||
from authentik.sources.saml.models import SAMLSource
|
||||
from authentik.sources.saml.processors.constants import (
|
||||
SAML_BINDING_REDIRECT,
|
||||
SAML_NAME_ID_FORMAT_EMAIL,
|
||||
SAML_NAME_ID_FORMAT_UNSPECIFIED,
|
||||
)
|
||||
@ -98,6 +99,9 @@ class TestAuthNRequest(TestCase):
|
||||
|
||||
# First create an AuthNRequest
|
||||
request_proc = RequestProcessor(self.source, http_request, "test_state")
|
||||
auth_n = request_proc.get_auth_n()
|
||||
self.assertEqual(auth_n.attrib["ProtocolBinding"], SAML_BINDING_REDIRECT)
|
||||
|
||||
request = request_proc.build_auth_n()
|
||||
# Now we check the ID and signature
|
||||
parsed_request = AuthNRequestParser(self.provider).parse(
|
||||
|
@ -1,14 +1,4 @@
|
||||
"""
|
||||
Django settings for authentik project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 2.1.3.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/2.1/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/2.1/ref/settings/
|
||||
"""
|
||||
"""root settings for authentik"""
|
||||
|
||||
import importlib
|
||||
import logging
|
||||
@ -18,24 +8,22 @@ from hashlib import sha512
|
||||
from json import dumps
|
||||
from tempfile import gettempdir
|
||||
from time import time
|
||||
from urllib.parse import quote
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
import structlog
|
||||
from celery.schedules import crontab
|
||||
from sentry_sdk import init as sentry_init
|
||||
from sentry_sdk.api import set_tag
|
||||
from sentry_sdk.integrations.boto3 import Boto3Integration
|
||||
from sentry_sdk.integrations.celery import CeleryIntegration
|
||||
from sentry_sdk.integrations.django import DjangoIntegration
|
||||
from sentry_sdk.integrations.redis import RedisIntegration
|
||||
from sentry_sdk.integrations.threading import ThreadingIntegration
|
||||
|
||||
from authentik import ENV_GIT_HASH_KEY, __version__, get_build_hash, get_full_version
|
||||
from authentik import ENV_GIT_HASH_KEY, __version__, get_build_hash
|
||||
from authentik.core.middleware import structlog_add_request_id
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.lib.logging import add_process_id
|
||||
from authentik.lib.sentry import before_send
|
||||
from authentik.lib.utils.http import get_http_session
|
||||
from authentik.lib.utils.reflection import get_env
|
||||
from authentik.stages.password import BACKEND_APP_PASSWORD, BACKEND_INBUILT, BACKEND_LDAP
|
||||
|
||||
@ -220,7 +208,7 @@ if CONFIG.y_bool("redis.tls", False):
|
||||
REDIS_CELERY_TLS_REQUIREMENTS = f"?ssl_cert_reqs={CONFIG.y('redis.tls_reqs')}"
|
||||
_redis_url = (
|
||||
f"{REDIS_PROTOCOL_PREFIX}:"
|
||||
f"{quote(CONFIG.y('redis.password'))}@{quote(CONFIG.y('redis.host'))}:"
|
||||
f"{quote_plus(CONFIG.y('redis.password'))}@{quote_plus(CONFIG.y('redis.host'))}:"
|
||||
f"{int(CONFIG.y('redis.port'))}"
|
||||
)
|
||||
|
||||
@ -347,6 +335,7 @@ LOCALE_PATHS = ["./locale"]
|
||||
# Celery settings
|
||||
# Add a 10 minute timeout to all Celery tasks.
|
||||
CELERY_TASK_SOFT_TIME_LIMIT = 600
|
||||
CELERY_WORKER_MAX_TASKS_PER_CHILD = 50
|
||||
CELERY_BEAT_SCHEDULE = {
|
||||
"clean_expired_models": {
|
||||
"task": "authentik.core.tasks.clean_expired_models",
|
||||
@ -407,7 +396,6 @@ if _ERROR_REPORTING:
|
||||
DjangoIntegration(transaction_style="function_name"),
|
||||
CeleryIntegration(),
|
||||
RedisIntegration(),
|
||||
Boto3Integration(),
|
||||
ThreadingIntegration(propagate_hub=True),
|
||||
],
|
||||
before_send=before_send,
|
||||
@ -424,29 +412,6 @@ if _ERROR_REPORTING:
|
||||
"Error reporting is enabled",
|
||||
env=CONFIG.y("error_reporting.environment", "customer"),
|
||||
)
|
||||
if not CONFIG.y_bool("disable_startup_analytics", False):
|
||||
should_send = env not in ["dev", "ci"]
|
||||
if should_send:
|
||||
try:
|
||||
get_http_session().post(
|
||||
"https://goauthentik.io/api/event",
|
||||
json={
|
||||
"domain": "authentik",
|
||||
"name": "pageview",
|
||||
"referrer": get_full_version(),
|
||||
"url": (
|
||||
f"http://localhost/{env}?utm_source={get_full_version()}&utm_medium={env}"
|
||||
),
|
||||
},
|
||||
headers={
|
||||
"User-Agent": sha512(str(SECRET_KEY).encode("ascii")).hexdigest()[:16],
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
timeout=5,
|
||||
)
|
||||
# pylint: disable=bare-except
|
||||
except: # nosec
|
||||
pass
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/2.1/howto/static-files/
|
||||
|
@ -35,21 +35,21 @@ class LDAPProviderManager(ObjectManager):
|
||||
"goauthentik.io/sources/ldap/ms-userprincipalname",
|
||||
name="authentik default Active Directory Mapping: userPrincipalName",
|
||||
object_field="attributes.upn",
|
||||
expression="return ldap.get('userPrincipalName')",
|
||||
expression="return list_flatten(ldap.get('userPrincipalName'))",
|
||||
),
|
||||
EnsureExists(
|
||||
LDAPPropertyMapping,
|
||||
"goauthentik.io/sources/ldap/ms-givenName",
|
||||
name="authentik default Active Directory Mapping: givenName",
|
||||
object_field="attributes.givenName",
|
||||
expression="return ldap.get('givenName')",
|
||||
expression="return list_flatten(ldap.get('givenName'))",
|
||||
),
|
||||
EnsureExists(
|
||||
LDAPPropertyMapping,
|
||||
"goauthentik.io/sources/ldap/ms-sn",
|
||||
name="authentik default Active Directory Mapping: sn",
|
||||
object_field="attributes.sn",
|
||||
expression="return ldap.get('sn')",
|
||||
expression="return list_flatten(ldap.get('sn'))",
|
||||
),
|
||||
# OpenLDAP specific mappings
|
||||
EnsureExists(
|
||||
|
@ -3,6 +3,7 @@ from ldap3.core.exceptions import LDAPException
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
|
||||
from authentik.lib.utils.errors import exception_to_string
|
||||
from authentik.lib.utils.reflection import class_to_path, path_to_class
|
||||
from authentik.root.celery import CELERY_APP
|
||||
from authentik.sources.ldap.models import LDAPSource
|
||||
@ -52,5 +53,5 @@ def ldap_sync(self: MonitoredTask, source_pk: str, sync_class: str):
|
||||
)
|
||||
except LDAPException as exc:
|
||||
# No explicit event is created here as .set_status with an error will do that
|
||||
LOGGER.debug(exc)
|
||||
LOGGER.warning(exception_to_string(exc))
|
||||
self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))
|
||||
|
@ -18,6 +18,8 @@ from authentik.sources.saml.processors.constants import (
|
||||
RSA_SHA256,
|
||||
RSA_SHA384,
|
||||
RSA_SHA512,
|
||||
SAML_BINDING_POST,
|
||||
SAML_BINDING_REDIRECT,
|
||||
SAML_NAME_ID_FORMAT_EMAIL,
|
||||
SAML_NAME_ID_FORMAT_PERSISTENT,
|
||||
SAML_NAME_ID_FORMAT_TRANSIENT,
|
||||
@ -37,6 +39,15 @@ class SAMLBindingTypes(models.TextChoices):
|
||||
POST = "POST", _("POST Binding")
|
||||
POST_AUTO = "POST_AUTO", _("POST Binding with auto-confirmation")
|
||||
|
||||
@property
|
||||
def uri(self) -> str:
|
||||
"""Convert database field to URI"""
|
||||
return {
|
||||
SAMLBindingTypes.POST: SAML_BINDING_POST,
|
||||
SAMLBindingTypes.POST_AUTO: SAML_BINDING_POST,
|
||||
SAMLBindingTypes.REDIRECT: SAML_BINDING_REDIRECT,
|
||||
}[self]
|
||||
|
||||
|
||||
class SAMLNameIDPolicy(models.TextChoices):
|
||||
"""SAML NameID Policies"""
|
||||
|
@ -10,7 +10,7 @@ from lxml.etree import Element # nosec
|
||||
from authentik.providers.saml.utils import get_random_id
|
||||
from authentik.providers.saml.utils.encoding import deflate_and_base64_encode
|
||||
from authentik.providers.saml.utils.time import get_time_string
|
||||
from authentik.sources.saml.models import SAMLSource
|
||||
from authentik.sources.saml.models import SAMLBindingTypes, SAMLSource
|
||||
from authentik.sources.saml.processors.constants import (
|
||||
DIGEST_ALGORITHM_TRANSLATION_MAP,
|
||||
NS_MAP,
|
||||
@ -62,7 +62,7 @@ class RequestProcessor:
|
||||
auth_n_request.attrib["Destination"] = self.source.sso_url
|
||||
auth_n_request.attrib["ID"] = self.request_id
|
||||
auth_n_request.attrib["IssueInstant"] = self.issue_instant
|
||||
auth_n_request.attrib["ProtocolBinding"] = self.source.binding_type
|
||||
auth_n_request.attrib["ProtocolBinding"] = SAMLBindingTypes(self.source.binding_type).uri
|
||||
auth_n_request.attrib["Version"] = "2.0"
|
||||
# Create issuer object
|
||||
auth_n_request.append(self.get_issuer())
|
||||
|
@ -196,7 +196,10 @@ class AuthenticatorValidateStageView(ChallengeStageView):
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def get_challenge(self) -> AuthenticatorValidationChallenge:
|
||||
challenges = self.request.session["device_challenges"]
|
||||
challenges = self.request.session.get("device_challenges")
|
||||
if not challenges:
|
||||
LOGGER.debug("Authenticator Validation stage ran without challenges")
|
||||
return self.executor.stage_invalid()
|
||||
return AuthenticatorValidationChallenge(
|
||||
data={
|
||||
"type": ChallengeTypes.NATIVE.value,
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"goauthentik.io/internal/common"
|
||||
"goauthentik.io/internal/debug"
|
||||
"goauthentik.io/internal/outpost/ak"
|
||||
"goauthentik.io/internal/outpost/ldap"
|
||||
)
|
||||
@ -27,6 +28,7 @@ func main() {
|
||||
log.FieldKeyTime: "timestamp",
|
||||
},
|
||||
})
|
||||
go debug.EnableDebugServer()
|
||||
akURL, found := os.LookupEnv("AUTHENTIK_HOST")
|
||||
if !found {
|
||||
fmt.Println("env AUTHENTIK_HOST not set!")
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"goauthentik.io/internal/common"
|
||||
"goauthentik.io/internal/debug"
|
||||
"goauthentik.io/internal/outpost/ak"
|
||||
"goauthentik.io/internal/outpost/proxyv2"
|
||||
)
|
||||
@ -32,6 +33,7 @@ func main() {
|
||||
log.FieldKeyTime: "timestamp",
|
||||
},
|
||||
})
|
||||
go debug.EnableDebugServer()
|
||||
akURL, found := os.LookupEnv("AUTHENTIK_HOST")
|
||||
if !found {
|
||||
fmt.Println("env AUTHENTIK_HOST not set!")
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"goauthentik.io/internal/common"
|
||||
"goauthentik.io/internal/config"
|
||||
"goauthentik.io/internal/constants"
|
||||
"goauthentik.io/internal/debug"
|
||||
"goauthentik.io/internal/gounicorn"
|
||||
"goauthentik.io/internal/outpost/ak"
|
||||
"goauthentik.io/internal/outpost/proxyv2"
|
||||
@ -28,6 +29,7 @@ func main() {
|
||||
log.FieldKeyTime: "timestamp",
|
||||
},
|
||||
})
|
||||
go debug.EnableDebugServer()
|
||||
l := log.WithField("logger", "authentik.root")
|
||||
config.DefaultConfig()
|
||||
err := config.LoadConfig("./authentik/lib/default.yml")
|
||||
|
@ -17,7 +17,7 @@ services:
|
||||
image: redis:alpine
|
||||
restart: unless-stopped
|
||||
server:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.1.1}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.1.5}
|
||||
restart: unless-stopped
|
||||
command: server
|
||||
environment:
|
||||
@ -38,7 +38,7 @@ services:
|
||||
- "0.0.0.0:${AUTHENTIK_PORT_HTTP:-9000}:9000"
|
||||
- "0.0.0.0:${AUTHENTIK_PORT_HTTPS:-9443}:9443"
|
||||
worker:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.1.1}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.1.5}
|
||||
restart: unless-stopped
|
||||
command: worker
|
||||
environment:
|
||||
|
6
go.mod
6
go.mod
@ -9,7 +9,7 @@ require (
|
||||
github.com/garyburd/redigo v1.6.2 // indirect
|
||||
github.com/getsentry/sentry-go v0.12.0
|
||||
github.com/go-ldap/ldap/v3 v3.4.1
|
||||
github.com/go-openapi/runtime v0.21.0
|
||||
github.com/go-openapi/runtime v0.22.0
|
||||
github.com/go-openapi/strfmt v0.21.1
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
github.com/google/uuid v1.3.0
|
||||
@ -25,8 +25,10 @@ require (
|
||||
github.com/pires/go-proxyproto v0.6.1
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac // indirect
|
||||
github.com/prometheus/client_golang v1.12.0
|
||||
github.com/prometheus/client_golang v1.12.1
|
||||
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
goauthentik.io/api v0.2021125.1
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
|
10
go.sum
10
go.sum
@ -183,8 +183,8 @@ github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29g
|
||||
github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo=
|
||||
github.com/go-openapi/runtime v0.19.16/go.mod h1:5P9104EJgYcizotuXhEuUrzVc+j1RiSjahULvYmlv98=
|
||||
github.com/go-openapi/runtime v0.19.24/go.mod h1:Lm9YGCeecBnUUkFTxPC4s1+lwrkJ0pthx8YvyjCfkgk=
|
||||
github.com/go-openapi/runtime v0.21.0 h1:giZ8eT26R+/rx6RX2MkYjZPY8vPYVKDhP/mOazrQHzM=
|
||||
github.com/go-openapi/runtime v0.21.0/go.mod h1:aQg+kaIQEn+A2CRSY1TxbM8+sT9g2V3aLc1FbIAnbbs=
|
||||
github.com/go-openapi/runtime v0.22.0 h1:vY2D0u807kkcwidaj0YJuq4zyAWQnjLNDpJcVBrUFNs=
|
||||
github.com/go-openapi/runtime v0.22.0/go.mod h1:aQg+kaIQEn+A2CRSY1TxbM8+sT9g2V3aLc1FbIAnbbs=
|
||||
github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||
github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||
github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
|
||||
@ -471,8 +471,8 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_golang v1.12.0 h1:C+UIj/QWtmqY13Arb8kwMt5j34/0Z2iKamrJ+ryC0Gg=
|
||||
github.com/prometheus/client_golang v1.12.0/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
|
||||
github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
|
||||
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
@ -489,6 +489,8 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
|
||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b h1:aUNXCGgukb4gtY99imuIeoh8Vr0GSwAlYxPAhqZrpFc=
|
||||
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
|
||||
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
|
@ -25,4 +25,4 @@ func OutpostUserAgent() string {
|
||||
return fmt.Sprintf("authentik-outpost@%s", FullVersion())
|
||||
}
|
||||
|
||||
const VERSION = "2022.1.1"
|
||||
const VERSION = "2022.1.5"
|
||||
|
24
internal/debug/debug.go
Normal file
24
internal/debug/debug.go
Normal file
@ -0,0 +1,24 @@
|
||||
package debug
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func EnableDebugServer() {
|
||||
l := log.WithField("logger", "authentik.go_debugger")
|
||||
if deb := os.Getenv("AUTHENTIK_DEBUG"); strings.ToLower(deb) != "true" {
|
||||
l.Info("not enabling debug server, set `AUTHENTIK_DEBUG` to `true` to enable it.")
|
||||
}
|
||||
h := http.NewServeMux()
|
||||
h.HandleFunc("/debug/pprof/", pprof.Index)
|
||||
h.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
||||
h.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||
h.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||
h.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
||||
l.Println(http.ListenAndServe("0.0.0.0:9900", nil))
|
||||
}
|
66
internal/outpost/ak/test.go
Normal file
66
internal/outpost/ak/test.go
Normal file
@ -0,0 +1,66 @@
|
||||
package ak
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/securecookie"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"goauthentik.io/api"
|
||||
)
|
||||
|
||||
func TestSecret() string {
|
||||
return base64.RawURLEncoding.EncodeToString(securecookie.GenerateRandomKey(32))
|
||||
}
|
||||
|
||||
func MockConfig() api.Config {
|
||||
return *api.NewConfig(
|
||||
*api.NewErrorReportingConfig(false, "test", false, 0.0),
|
||||
[]api.CapabilitiesEnum{},
|
||||
100,
|
||||
100,
|
||||
100,
|
||||
100,
|
||||
)
|
||||
}
|
||||
|
||||
func MockAK(outpost api.Outpost, globalConfig api.Config) *APIController {
|
||||
config := api.NewConfiguration()
|
||||
config.HTTPClient = &http.Client{
|
||||
Transport: GetTLSTransport(),
|
||||
}
|
||||
token := TestSecret()
|
||||
config.AddDefaultHeader("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
|
||||
// create the API client, with the transport
|
||||
apiClient := api.NewAPIClient(config)
|
||||
|
||||
log := log.WithField("logger", "authentik.outpost.ak-api-controller")
|
||||
|
||||
log.WithField("name", outpost.Name).Debug("Fetched outpost configuration")
|
||||
|
||||
log.Debug("Fetched global configuration")
|
||||
|
||||
// doGlobalSetup is called by the OnRefresh handler, which ticks on start
|
||||
// doGlobalSetup(outpost, akConfig)
|
||||
|
||||
ac := &APIController{
|
||||
Client: apiClient,
|
||||
GlobalConfig: globalConfig,
|
||||
|
||||
token: token,
|
||||
logger: log,
|
||||
|
||||
reloadOffset: time.Duration(rand.Intn(10)) * time.Second,
|
||||
instanceUUID: uuid.New(),
|
||||
Outpost: outpost,
|
||||
wsBackoffMultiplier: 1,
|
||||
refreshHandlers: make([]func(), 0),
|
||||
}
|
||||
ac.logger.WithField("offset", ac.reloadOffset.String()).Debug("HA Reload offset")
|
||||
return ac
|
||||
}
|
@ -16,6 +16,7 @@ import (
|
||||
"goauthentik.io/internal/outpost/ldap/flags"
|
||||
"goauthentik.io/internal/outpost/ldap/metrics"
|
||||
"goauthentik.io/internal/outpost/ldap/server"
|
||||
"goauthentik.io/internal/outpost/ldap/utils"
|
||||
)
|
||||
|
||||
const ContextUserKey = "ak_user"
|
||||
@ -35,7 +36,7 @@ func NewDirectBinder(si server.LDAPServerInstance) *DirectBinder {
|
||||
}
|
||||
|
||||
func (db *DirectBinder) GetUsername(dn string) (string, error) {
|
||||
if !strings.HasSuffix(strings.ToLower(dn), strings.ToLower(db.si.GetBaseDN())) {
|
||||
if !utils.HasSuffixNoCase(dn, db.si.GetBaseDN()) {
|
||||
return "", errors.New("invalid base DN")
|
||||
}
|
||||
dns, err := goldap.ParseDN(dn)
|
||||
|
@ -140,26 +140,26 @@ func (pi *ProviderInstance) GetNeededObjects(scope int, baseDN string, filterOC
|
||||
// If our requested base DN doesn't match any of the container DNs, then
|
||||
// we're probably loading a user or group. If it does, then make sure our
|
||||
// scope will eventually take us to users or groups.
|
||||
if (baseDN == pi.BaseDN || strings.HasSuffix(baseDN, pi.UserDN)) && utils.IncludeObjectClass(filterOC, ldapConstants.GetUserOCs()) {
|
||||
if (strings.EqualFold(baseDN, pi.BaseDN) || utils.HasSuffixNoCase(baseDN, pi.UserDN)) && utils.IncludeObjectClass(filterOC, ldapConstants.GetUserOCs()) {
|
||||
if baseDN != pi.UserDN && baseDN != pi.BaseDN ||
|
||||
baseDN == pi.BaseDN && scope > 1 ||
|
||||
baseDN == pi.UserDN && scope > 0 {
|
||||
strings.EqualFold(baseDN, pi.BaseDN) && scope > 1 ||
|
||||
strings.EqualFold(baseDN, pi.UserDN) && scope > 0 {
|
||||
needUsers = true
|
||||
}
|
||||
}
|
||||
|
||||
if (baseDN == pi.BaseDN || strings.HasSuffix(baseDN, pi.GroupDN)) && utils.IncludeObjectClass(filterOC, ldapConstants.GetGroupOCs()) {
|
||||
if (strings.EqualFold(baseDN, pi.BaseDN) || utils.HasSuffixNoCase(baseDN, pi.GroupDN)) && utils.IncludeObjectClass(filterOC, ldapConstants.GetGroupOCs()) {
|
||||
if baseDN != pi.GroupDN && baseDN != pi.BaseDN ||
|
||||
baseDN == pi.BaseDN && scope > 1 ||
|
||||
baseDN == pi.GroupDN && scope > 0 {
|
||||
strings.EqualFold(baseDN, pi.BaseDN) && scope > 1 ||
|
||||
strings.EqualFold(baseDN, pi.GroupDN) && scope > 0 {
|
||||
needGroups = true
|
||||
}
|
||||
}
|
||||
|
||||
if (baseDN == pi.BaseDN || strings.HasSuffix(baseDN, pi.VirtualGroupDN)) && utils.IncludeObjectClass(filterOC, ldapConstants.GetVirtualGroupOCs()) {
|
||||
if (strings.EqualFold(baseDN, pi.BaseDN) || utils.HasSuffixNoCase(baseDN, pi.VirtualGroupDN)) && utils.IncludeObjectClass(filterOC, ldapConstants.GetVirtualGroupOCs()) {
|
||||
if baseDN != pi.VirtualGroupDN && baseDN != pi.BaseDN ||
|
||||
baseDN == pi.BaseDN && scope > 1 ||
|
||||
baseDN == pi.VirtualGroupDN && scope > 0 {
|
||||
strings.EqualFold(baseDN, pi.BaseDN) && scope > 1 ||
|
||||
strings.EqualFold(baseDN, pi.VirtualGroupDN) && scope > 0 {
|
||||
needUsers = true
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ func NewDirectSearcher(si server.LDAPServerInstance) *DirectSearcher {
|
||||
|
||||
func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult, error) {
|
||||
accsp := sentry.StartSpan(req.Context(), "authentik.providers.ldap.search.check_access")
|
||||
baseDN := strings.ToLower(ds.si.GetBaseDN())
|
||||
baseDN := ds.si.GetBaseDN()
|
||||
|
||||
filterOC, err := ldap.GetFilterObjectClass(req.Filter)
|
||||
if err != nil {
|
||||
@ -59,7 +59,7 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult,
|
||||
}).Inc()
|
||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Search Error: Anonymous BindDN not allowed %s", req.BindDN)
|
||||
}
|
||||
if !strings.HasSuffix(req.BindDN, ","+baseDN) {
|
||||
if !utils.HasSuffixNoCase(req.BindDN, ","+baseDN) {
|
||||
metrics.RequestsRejected.With(prometheus.Labels{
|
||||
"outpost_name": ds.si.GetOutpostName(),
|
||||
"type": "search",
|
||||
@ -105,7 +105,7 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult,
|
||||
scope := req.SearchRequest.Scope
|
||||
needUsers, needGroups := ds.si.GetNeededObjects(scope, req.BaseDN, filterOC)
|
||||
|
||||
if scope >= 0 && req.BaseDN == baseDN {
|
||||
if scope >= 0 && strings.EqualFold(req.BaseDN, baseDN) {
|
||||
if utils.IncludeObjectClass(filterOC, constants.GetDomainOCs()) {
|
||||
entries = append(entries, ds.si.GetBaseEntry())
|
||||
}
|
||||
@ -209,8 +209,8 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult,
|
||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, err
|
||||
}
|
||||
|
||||
if scope >= 0 && (req.BaseDN == ds.si.GetBaseDN() || strings.HasSuffix(req.BaseDN, ds.si.GetBaseUserDN())) {
|
||||
singleu := strings.HasSuffix(req.BaseDN, ","+ds.si.GetBaseUserDN())
|
||||
if scope >= 0 && (strings.EqualFold(req.BaseDN, ds.si.GetBaseDN()) || utils.HasSuffixNoCase(req.BaseDN, ds.si.GetBaseUserDN())) {
|
||||
singleu := utils.HasSuffixNoCase(req.BaseDN, ","+ds.si.GetBaseUserDN())
|
||||
|
||||
if !singleu && utils.IncludeObjectClass(filterOC, constants.GetContainerOCs()) {
|
||||
entries = append(entries, utils.GetContainerEntry(filterOC, ds.si.GetBaseUserDN(), constants.OUUsers))
|
||||
@ -220,7 +220,7 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult,
|
||||
if scope >= 0 && users != nil && utils.IncludeObjectClass(filterOC, constants.GetUserOCs()) {
|
||||
for _, u := range *users {
|
||||
entry := ds.si.UserEntry(u)
|
||||
if req.BaseDN == entry.DN || !singleu {
|
||||
if strings.EqualFold(req.BaseDN, entry.DN) || !singleu {
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
}
|
||||
@ -229,8 +229,8 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult,
|
||||
scope += 1 // Return the scope to what it was before we descended
|
||||
}
|
||||
|
||||
if scope >= 0 && (req.BaseDN == ds.si.GetBaseDN() || strings.HasSuffix(req.BaseDN, ds.si.GetBaseGroupDN())) {
|
||||
singleg := strings.HasSuffix(req.BaseDN, ","+ds.si.GetBaseGroupDN())
|
||||
if scope >= 0 && (strings.EqualFold(req.BaseDN, ds.si.GetBaseDN()) || utils.HasSuffixNoCase(req.BaseDN, ds.si.GetBaseGroupDN())) {
|
||||
singleg := utils.HasSuffixNoCase(req.BaseDN, ","+ds.si.GetBaseGroupDN())
|
||||
|
||||
if !singleg && utils.IncludeObjectClass(filterOC, constants.GetContainerOCs()) {
|
||||
entries = append(entries, utils.GetContainerEntry(filterOC, ds.si.GetBaseGroupDN(), constants.OUGroups))
|
||||
@ -240,7 +240,7 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult,
|
||||
if scope >= 0 && groups != nil && utils.IncludeObjectClass(filterOC, constants.GetGroupOCs()) {
|
||||
for _, g := range *groups {
|
||||
entry := group.FromAPIGroup(g, ds.si).Entry()
|
||||
if req.BaseDN == entry.DN || !singleg {
|
||||
if strings.EqualFold(req.BaseDN, entry.DN) || !singleg {
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
}
|
||||
@ -249,8 +249,8 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult,
|
||||
scope += 1 // Return the scope to what it was before we descended
|
||||
}
|
||||
|
||||
if scope >= 0 && (req.BaseDN == ds.si.GetBaseDN() || strings.HasSuffix(req.BaseDN, ds.si.GetBaseVirtualGroupDN())) {
|
||||
singlevg := strings.HasSuffix(req.BaseDN, ","+ds.si.GetBaseVirtualGroupDN())
|
||||
if scope >= 0 && (strings.EqualFold(req.BaseDN, ds.si.GetBaseDN()) || utils.HasSuffixNoCase(req.BaseDN, ds.si.GetBaseVirtualGroupDN())) {
|
||||
singlevg := utils.HasSuffixNoCase(req.BaseDN, ","+ds.si.GetBaseVirtualGroupDN())
|
||||
|
||||
if !singlevg || utils.IncludeObjectClass(filterOC, constants.GetContainerOCs()) {
|
||||
entries = append(entries, utils.GetContainerEntry(filterOC, ds.si.GetBaseVirtualGroupDN(), constants.OUVirtualGroups))
|
||||
@ -260,7 +260,7 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult,
|
||||
if scope >= 0 && users != nil && utils.IncludeObjectClass(filterOC, constants.GetVirtualGroupOCs()) {
|
||||
for _, u := range *users {
|
||||
entry := group.FromAPIUser(u, ds.si).Entry()
|
||||
if req.BaseDN == entry.DN || !singlevg {
|
||||
if strings.EqualFold(req.BaseDN, entry.DN) || !singlevg {
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ func NewMemorySearcher(si server.LDAPServerInstance) *MemorySearcher {
|
||||
|
||||
func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult, error) {
|
||||
accsp := sentry.StartSpan(req.Context(), "authentik.providers.ldap.search.check_access")
|
||||
baseDN := strings.ToLower(ms.si.GetBaseDN())
|
||||
baseDN := ms.si.GetBaseDN()
|
||||
|
||||
filterOC, err := ldap.GetFilterObjectClass(req.Filter)
|
||||
if err != nil {
|
||||
@ -62,7 +62,7 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult,
|
||||
}).Inc()
|
||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Search Error: Anonymous BindDN not allowed %s", req.BindDN)
|
||||
}
|
||||
if !strings.HasSuffix(req.BindDN, ","+baseDN) {
|
||||
if !utils.HasSuffixNoCase(req.BindDN, ","+baseDN) {
|
||||
metrics.RequestsRejected.With(prometheus.Labels{
|
||||
"outpost_name": ms.si.GetOutpostName(),
|
||||
"type": "search",
|
||||
@ -92,7 +92,7 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult,
|
||||
scope := req.SearchRequest.Scope
|
||||
needUsers, needGroups := ms.si.GetNeededObjects(scope, req.BaseDN, filterOC)
|
||||
|
||||
if scope >= 0 && req.BaseDN == baseDN {
|
||||
if scope >= 0 && strings.EqualFold(req.BaseDN, baseDN) {
|
||||
if utils.IncludeObjectClass(filterOC, constants.GetDomainOCs()) {
|
||||
entries = append(entries, ms.si.GetBaseEntry())
|
||||
}
|
||||
@ -155,8 +155,8 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult,
|
||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, err
|
||||
}
|
||||
|
||||
if scope >= 0 && (req.BaseDN == ms.si.GetBaseDN() || strings.HasSuffix(req.BaseDN, ms.si.GetBaseUserDN())) {
|
||||
singleu := strings.HasSuffix(req.BaseDN, ","+ms.si.GetBaseUserDN())
|
||||
if scope >= 0 && (strings.EqualFold(req.BaseDN, ms.si.GetBaseDN()) || utils.HasSuffixNoCase(req.BaseDN, ms.si.GetBaseUserDN())) {
|
||||
singleu := utils.HasSuffixNoCase(req.BaseDN, ","+ms.si.GetBaseUserDN())
|
||||
|
||||
if !singleu && utils.IncludeObjectClass(filterOC, constants.GetContainerOCs()) {
|
||||
entries = append(entries, utils.GetContainerEntry(filterOC, ms.si.GetBaseUserDN(), constants.OUUsers))
|
||||
@ -166,7 +166,7 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult,
|
||||
if scope >= 0 && users != nil && utils.IncludeObjectClass(filterOC, constants.GetUserOCs()) {
|
||||
for _, u := range *users {
|
||||
entry := ms.si.UserEntry(u)
|
||||
if req.BaseDN == entry.DN || !singleu {
|
||||
if strings.EqualFold(req.BaseDN, entry.DN) || !singleu {
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
}
|
||||
@ -175,8 +175,8 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult,
|
||||
scope += 1 // Return the scope to what it was before we descended
|
||||
}
|
||||
|
||||
if scope >= 0 && (req.BaseDN == ms.si.GetBaseDN() || strings.HasSuffix(req.BaseDN, ms.si.GetBaseGroupDN())) {
|
||||
singleg := strings.HasSuffix(req.BaseDN, ","+ms.si.GetBaseGroupDN())
|
||||
if scope >= 0 && (strings.EqualFold(req.BaseDN, ms.si.GetBaseDN()) || utils.HasSuffixNoCase(req.BaseDN, ms.si.GetBaseGroupDN())) {
|
||||
singleg := utils.HasSuffixNoCase(req.BaseDN, ","+ms.si.GetBaseGroupDN())
|
||||
|
||||
if !singleg && utils.IncludeObjectClass(filterOC, constants.GetContainerOCs()) {
|
||||
entries = append(entries, utils.GetContainerEntry(filterOC, ms.si.GetBaseGroupDN(), constants.OUGroups))
|
||||
@ -185,7 +185,7 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult,
|
||||
|
||||
if scope >= 0 && groups != nil && utils.IncludeObjectClass(filterOC, constants.GetGroupOCs()) {
|
||||
for _, g := range groups {
|
||||
if req.BaseDN == g.DN || !singleg {
|
||||
if strings.EqualFold(req.BaseDN, g.DN) || !singleg {
|
||||
entries = append(entries, g.Entry())
|
||||
}
|
||||
}
|
||||
@ -194,8 +194,8 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult,
|
||||
scope += 1 // Return the scope to what it was before we descended
|
||||
}
|
||||
|
||||
if scope >= 0 && (req.BaseDN == ms.si.GetBaseDN() || strings.HasSuffix(req.BaseDN, ms.si.GetBaseVirtualGroupDN())) {
|
||||
singlevg := strings.HasSuffix(req.BaseDN, ","+ms.si.GetBaseVirtualGroupDN())
|
||||
if scope >= 0 && (strings.EqualFold(req.BaseDN, ms.si.GetBaseDN()) || utils.HasSuffixNoCase(req.BaseDN, ms.si.GetBaseVirtualGroupDN())) {
|
||||
singlevg := utils.HasSuffixNoCase(req.BaseDN, ","+ms.si.GetBaseVirtualGroupDN())
|
||||
|
||||
if !singlevg && utils.IncludeObjectClass(filterOC, constants.GetContainerOCs()) {
|
||||
entries = append(entries, utils.GetContainerEntry(filterOC, ms.si.GetBaseVirtualGroupDN(), constants.OUVirtualGroups))
|
||||
@ -205,7 +205,7 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult,
|
||||
if scope >= 0 && users != nil && utils.IncludeObjectClass(filterOC, constants.GetVirtualGroupOCs()) {
|
||||
for _, u := range *users {
|
||||
entry := group.FromAPIUser(u, ms.si).Entry()
|
||||
if req.BaseDN == entry.DN || !singlevg {
|
||||
if strings.EqualFold(req.BaseDN, entry.DN) || !singlevg {
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,6 @@ type Request struct {
|
||||
func NewRequest(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (*Request, *sentry.Span) {
|
||||
rid := uuid.New().String()
|
||||
bindDN = strings.ToLower(bindDN)
|
||||
searchReq.BaseDN = strings.ToLower(searchReq.BaseDN)
|
||||
span := sentry.StartSpan(context.TODO(), "authentik.providers.ldap.search", sentry.TransactionName("authentik.providers.ldap.search"))
|
||||
span.Description = fmt.Sprintf("%s (%s)", searchReq.BaseDN, ldap.ScopeMap[searchReq.Scope])
|
||||
span.SetTag("request_uid", rid)
|
||||
|
@ -2,6 +2,7 @@ package utils
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/nmcclain/ldap"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@ -117,3 +118,7 @@ func GetContainerEntry(filterOC string, dn string, ou string) *ldap.Entry {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func HasSuffixNoCase(s1 string, s2 string) bool {
|
||||
return strings.HasSuffix(strings.ToLower(s1), strings.ToLower(s2))
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
goldap "github.com/go-ldap/ldap/v3"
|
||||
ber "github.com/nmcclain/asn1-ber"
|
||||
"github.com/nmcclain/ldap"
|
||||
@ -41,7 +43,7 @@ func parseFilterForGroupSingle(req api.ApiCoreGroupsListRequest, f *ber.Packet)
|
||||
// Switch on type of the value, then check the key
|
||||
switch vv := v.(type) {
|
||||
case string:
|
||||
switch k {
|
||||
switch strings.ToLower(k.(string)) {
|
||||
case "cn":
|
||||
return req.Name(vv), false
|
||||
case "member":
|
||||
@ -54,7 +56,7 @@ func parseFilterForGroupSingle(req api.ApiCoreGroupsListRequest, f *ber.Packet)
|
||||
username := userDN.RDNs[0].Attributes[0].Value
|
||||
// If the DN's first ou is virtual-groups, ignore this filter
|
||||
if len(userDN.RDNs) > 1 {
|
||||
if userDN.RDNs[1].Attributes[0].Value == constants.OUVirtualGroups || userDN.RDNs[1].Attributes[0].Value == constants.OUGroups {
|
||||
if strings.EqualFold(userDN.RDNs[1].Attributes[0].Value, constants.OUVirtualGroups) || strings.EqualFold(userDN.RDNs[1].Attributes[0].Value, constants.OUGroups) {
|
||||
// Since we know we're not filtering anything, skip this request
|
||||
return req, true
|
||||
}
|
||||
|
@ -46,6 +46,7 @@ type Application struct {
|
||||
|
||||
log *log.Entry
|
||||
mux *mux.Router
|
||||
ak *ak.APIController
|
||||
|
||||
errorTemplates *template.Template
|
||||
}
|
||||
@ -93,6 +94,7 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, cs *ak.CryptoStore
|
||||
httpClient: c,
|
||||
mux: mux,
|
||||
errorTemplates: templates.GetTemplates(),
|
||||
ak: ak,
|
||||
}
|
||||
a.sessions = a.getStore(p)
|
||||
mux.Use(web.NewLoggingHandler(muxLogger, func(l *log.Entry, r *http.Request) *log.Entry {
|
||||
|
@ -1,18 +1,19 @@
|
||||
package application
|
||||
|
||||
type ProxyClaims struct {
|
||||
UserAttributes map[string]interface{} `json:"user_attributes"`
|
||||
UserAttributes map[string]interface{} `json:"user_attributes"`
|
||||
BackendOverride string `json:"backend_override"`
|
||||
}
|
||||
|
||||
type Claims struct {
|
||||
Sub string `json:"sub"`
|
||||
Exp int `json:"exp"`
|
||||
Email string `json:"email"`
|
||||
Verified bool `json:"email_verified"`
|
||||
Proxy ProxyClaims `json:"ak_proxy"`
|
||||
Name string `json:"name"`
|
||||
PreferredUsername string `json:"preferred_username"`
|
||||
Groups []string `json:"groups"`
|
||||
Sub string `json:"sub"`
|
||||
Exp int `json:"exp"`
|
||||
Email string `json:"email"`
|
||||
Verified bool `json:"email_verified"`
|
||||
Proxy *ProxyClaims `json:"ak_proxy"`
|
||||
Name string `json:"name"`
|
||||
PreferredUsername string `json:"preferred_username"`
|
||||
Groups []string `json:"groups"`
|
||||
|
||||
RawToken string
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@ -57,7 +59,8 @@ func (a *Application) addHeaders(headers http.Header, c *Claims) {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Application) getTraefikForwardUrl(r *http.Request) *url.URL {
|
||||
// getTraefikForwardUrl See https://doc.traefik.io/traefik/middlewares/forwardauth/
|
||||
func (a *Application) getTraefikForwardUrl(r *http.Request) (*url.URL, error) {
|
||||
u, err := url.Parse(fmt.Sprintf(
|
||||
"%s://%s%s",
|
||||
r.Header.Get("X-Forwarded-Proto"),
|
||||
@ -65,33 +68,63 @@ func (a *Application) getTraefikForwardUrl(r *http.Request) *url.URL {
|
||||
r.Header.Get("X-Forwarded-Uri"),
|
||||
))
|
||||
if err != nil {
|
||||
a.log.WithError(err).Warning("Failed to parse URL from traefik")
|
||||
return r.URL
|
||||
return nil, err
|
||||
}
|
||||
a.log.WithField("url", u.String()).Trace("traefik forwarded url")
|
||||
return u
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (a *Application) IsAllowlisted(r *http.Request) bool {
|
||||
url := r.URL
|
||||
// In Forward auth mode, we can't directly match against the requested URL
|
||||
// Since that would be /akprox/auth/...
|
||||
if a.Mode() == api.PROXYMODE_FORWARD_SINGLE || a.Mode() == api.PROXYMODE_FORWARD_DOMAIN {
|
||||
// For traefik, we can get the Upstream URL from headers
|
||||
// For nginx we can attempt to as well, but it's not guaranteed to work.
|
||||
if strings.HasPrefix(r.URL.Path, "/akprox/auth") {
|
||||
url = a.getTraefikForwardUrl(r)
|
||||
// getNginxForwardUrl See https://github.com/kubernetes/ingress-nginx/blob/main/rootfs/etc/nginx/template/nginx.tmpl
|
||||
func (a *Application) getNginxForwardUrl(r *http.Request) (*url.URL, error) {
|
||||
ou := r.Header.Get("X-Original-URI")
|
||||
if ou != "" {
|
||||
// Turn this full URL into a relative URL
|
||||
u := &url.URL{
|
||||
Host: "",
|
||||
Scheme: "",
|
||||
Path: ou,
|
||||
}
|
||||
a.log.WithField("url", u.String()).Info("building forward URL from X-Original-URI")
|
||||
return u, nil
|
||||
}
|
||||
for _, u := range a.UnauthenticatedRegex {
|
||||
h := r.Header.Get("X-Original-URL")
|
||||
if len(h) < 1 {
|
||||
return nil, errors.New("no forward URL found")
|
||||
}
|
||||
u, err := url.Parse(h)
|
||||
if err != nil {
|
||||
a.log.WithError(err).Warning("failed to parse URL from nginx")
|
||||
return nil, err
|
||||
}
|
||||
a.log.WithField("url", u.String()).Trace("nginx forwarded url")
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (a *Application) ReportMisconfiguration(r *http.Request, msg string, fields map[string]interface{}) {
|
||||
fields["message"] = msg
|
||||
a.log.WithFields(fields).Error("Reporting configuration error")
|
||||
req := api.EventRequest{
|
||||
Action: api.EVENTACTIONS_CONFIGURATION_ERROR,
|
||||
App: "authentik.providers.proxy", // must match python apps.py name
|
||||
ClientIp: *api.NewNullableString(api.PtrString(r.RemoteAddr)),
|
||||
Context: &fields,
|
||||
}
|
||||
_, _, err := a.ak.Client.EventsApi.EventsEventsCreate(context.Background()).EventRequest(req).Execute()
|
||||
if err != nil {
|
||||
a.log.WithError(err).Warning("failed to report configuration error")
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Application) IsAllowlisted(u *url.URL) bool {
|
||||
for _, ur := range a.UnauthenticatedRegex {
|
||||
var testString string
|
||||
if a.Mode() == api.PROXYMODE_PROXY || a.Mode() == api.PROXYMODE_FORWARD_SINGLE {
|
||||
testString = url.Path
|
||||
testString = u.Path
|
||||
} else {
|
||||
testString = url.String()
|
||||
testString = u.String()
|
||||
}
|
||||
a.log.WithField("regex", u.String()).WithField("url", testString).Trace("Matching URL against allow list")
|
||||
if u.MatchString(testString) {
|
||||
if ur.MatchString(testString) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -25,13 +25,27 @@ func (a *Application) configureForward() error {
|
||||
}
|
||||
|
||||
func (a *Application) forwardHandleTraefik(rw http.ResponseWriter, r *http.Request) {
|
||||
a.log.WithField("header", r.Header).Trace("tracing headers for debug")
|
||||
// First check if we've got everything we need
|
||||
fwd, err := a.getTraefikForwardUrl(r)
|
||||
if err != nil {
|
||||
a.ReportMisconfiguration(r, fmt.Sprintf("Outpost %s (Provider %s) failed to detect a forward URL from Traefik", a.outpostName, a.proxyConfig.Name), map[string]interface{}{
|
||||
"provider": a.proxyConfig.Name,
|
||||
"outpost": a.outpostName,
|
||||
"url": r.URL.String(),
|
||||
"headers": cleanseHeaders(r.Header),
|
||||
})
|
||||
http.Error(rw, "configuration error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := a.getClaims(r)
|
||||
if claims != nil && err == nil {
|
||||
a.addHeaders(rw.Header(), claims)
|
||||
rw.Header().Set("User-Agent", r.Header.Get("User-Agent"))
|
||||
a.log.WithField("headers", rw.Header()).Trace("headers written to forward_auth")
|
||||
return
|
||||
} else if claims == nil && a.IsAllowlisted(r) {
|
||||
} else if claims == nil && a.IsAllowlisted(fwd) {
|
||||
a.log.Trace("path can be accessed without authentication")
|
||||
return
|
||||
}
|
||||
@ -45,15 +59,18 @@ func (a *Application) forwardHandleTraefik(rw http.ResponseWriter, r *http.Reque
|
||||
if *a.proxyConfig.Mode == api.PROXYMODE_FORWARD_SINGLE {
|
||||
host = web.GetHost(r)
|
||||
} else if *a.proxyConfig.Mode == api.PROXYMODE_FORWARD_DOMAIN {
|
||||
eh, _ := url.Parse(a.proxyConfig.ExternalHost)
|
||||
host = eh.Host
|
||||
eh, err := url.Parse(a.proxyConfig.ExternalHost)
|
||||
if err != nil {
|
||||
a.log.WithField("host", a.proxyConfig.ExternalHost).WithError(err).Warning("invalid external_host")
|
||||
} else {
|
||||
host = eh.Host
|
||||
}
|
||||
}
|
||||
// set the redirect flag to the current URL we have, since we redirect
|
||||
// to a (possibly) different domain, but we want to be redirected back
|
||||
// to the application
|
||||
// see https://doc.traefik.io/traefik/middlewares/forwardauth/
|
||||
// X-Forwarded-Uri is only the path, so we need to build the entire URL
|
||||
s.Values[constants.SessionRedirect] = a.getTraefikForwardUrl(r).String()
|
||||
s.Values[constants.SessionRedirect] = fwd.String()
|
||||
err = s.Save(r, rw)
|
||||
if err != nil {
|
||||
a.log.WithError(err).Warning("failed to save session before redirect")
|
||||
@ -69,6 +86,19 @@ func (a *Application) forwardHandleTraefik(rw http.ResponseWriter, r *http.Reque
|
||||
}
|
||||
|
||||
func (a *Application) forwardHandleNginx(rw http.ResponseWriter, r *http.Request) {
|
||||
a.log.WithField("header", r.Header).Trace("tracing headers for debug")
|
||||
fwd, err := a.getNginxForwardUrl(r)
|
||||
if err != nil {
|
||||
a.ReportMisconfiguration(r, fmt.Sprintf("Outpost %s (Provider %s) failed to detect a forward URL from nginx", a.outpostName, a.proxyConfig.Name), map[string]interface{}{
|
||||
"provider": a.proxyConfig.Name,
|
||||
"outpost": a.outpostName,
|
||||
"url": r.URL.String(),
|
||||
"headers": cleanseHeaders(r.Header),
|
||||
})
|
||||
http.Error(rw, "configuration error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := a.getClaims(r)
|
||||
if claims != nil && err == nil {
|
||||
a.addHeaders(rw.Header(), claims)
|
||||
@ -76,13 +106,23 @@ func (a *Application) forwardHandleNginx(rw http.ResponseWriter, r *http.Request
|
||||
rw.WriteHeader(200)
|
||||
a.log.WithField("headers", rw.Header()).Trace("headers written to forward_auth")
|
||||
return
|
||||
} else if claims == nil && a.IsAllowlisted(r) {
|
||||
} else if claims == nil && a.IsAllowlisted(fwd) {
|
||||
a.log.Trace("path can be accessed without authentication")
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(a.getTraefikForwardUrl(r).Path, "/akprox") {
|
||||
a.log.WithField("url", r.URL.String()).Trace("path begins with /akprox, allowing access")
|
||||
return
|
||||
|
||||
s, _ := a.sessions.Get(r, constants.SeesionName)
|
||||
s.Values[constants.SessionRedirect] = fwd.String()
|
||||
err = s.Save(r, rw)
|
||||
if err != nil {
|
||||
a.log.WithError(err).Warning("failed to save session before redirect")
|
||||
}
|
||||
|
||||
if fwd.String() != r.URL.String() {
|
||||
if strings.HasPrefix(fwd.Path, "/akprox") {
|
||||
a.log.WithField("url", r.URL.String()).Trace("path begins with /akprox, allowing access")
|
||||
return
|
||||
}
|
||||
}
|
||||
http.Error(rw, "unauthorized request", http.StatusUnauthorized)
|
||||
}
|
||||
|
134
internal/outpost/proxyv2/application/mode_forward_nginx_test.go
Normal file
134
internal/outpost/proxyv2/application/mode_forward_nginx_test.go
Normal file
@ -0,0 +1,134 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"goauthentik.io/api"
|
||||
"goauthentik.io/internal/outpost/proxyv2/constants"
|
||||
)
|
||||
|
||||
func TestForwardHandleNginx_Single_Blank(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
req, _ := http.NewRequest("GET", "/akprox/auth/nginx", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
a.forwardHandleNginx(rr, req)
|
||||
|
||||
assert.Equal(t, http.StatusInternalServerError, rr.Code)
|
||||
}
|
||||
|
||||
func TestForwardHandleNginx_Single_Skip(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
req, _ := http.NewRequest("GET", "/akprox/auth/nginx", nil)
|
||||
req.Header.Set("X-Original-URL", "http://test.goauthentik.io/skip")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
a.forwardHandleNginx(rr, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
}
|
||||
|
||||
func TestForwardHandleNginx_Single_Headers(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
req, _ := http.NewRequest("GET", "/akprox/auth/nginx", nil)
|
||||
req.Header.Set("X-Original-URL", "http://test.goauthentik.io/app")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
a.forwardHandleNginx(rr, req)
|
||||
|
||||
assert.Equal(t, rr.Code, http.StatusUnauthorized)
|
||||
|
||||
s, _ := a.sessions.Get(req, constants.SeesionName)
|
||||
assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect])
|
||||
}
|
||||
|
||||
func TestForwardHandleNginx_Single_URI(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
req, _ := http.NewRequest("GET", "https://foo.bar/akprox/auth/nginx", nil)
|
||||
req.Header.Set("X-Original-URI", "/app")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
a.forwardHandleNginx(rr, req)
|
||||
|
||||
assert.Equal(t, rr.Code, http.StatusUnauthorized)
|
||||
|
||||
s, _ := a.sessions.Get(req, constants.SeesionName)
|
||||
assert.Equal(t, "/app", s.Values[constants.SessionRedirect])
|
||||
}
|
||||
|
||||
func TestForwardHandleNginx_Single_Claims(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
req, _ := http.NewRequest("GET", "/akprox/auth/nginx", nil)
|
||||
req.Header.Set("X-Original-URI", "/")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
a.forwardHandleNginx(rr, req)
|
||||
|
||||
s, _ := a.sessions.Get(req, constants.SeesionName)
|
||||
s.Values[constants.SessionClaims] = Claims{
|
||||
Sub: "foo",
|
||||
Proxy: &ProxyClaims{
|
||||
UserAttributes: map[string]interface{}{
|
||||
"username": "foo",
|
||||
"password": "bar",
|
||||
"additionalHeaders": map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
err := a.sessions.Save(req, rr, s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
rr = httptest.NewRecorder()
|
||||
a.forwardHandleNginx(rr, req)
|
||||
|
||||
h := rr.Result().Header
|
||||
|
||||
assert.Equal(t, []string{"Basic Zm9vOmJhcg=="}, h["Authorization"])
|
||||
assert.Equal(t, []string{"bar"}, h["Foo"])
|
||||
assert.Equal(t, []string{""}, h["User-Agent"])
|
||||
assert.Equal(t, []string{""}, h["X-Authentik-Email"])
|
||||
assert.Equal(t, []string{""}, h["X-Authentik-Groups"])
|
||||
assert.Equal(t, []string{""}, h["X-Authentik-Jwt"])
|
||||
assert.Equal(t, []string{""}, h["X-Authentik-Meta-App"])
|
||||
assert.Equal(t, []string{""}, h["X-Authentik-Meta-Jwks"])
|
||||
assert.Equal(t, []string{""}, h["X-Authentik-Meta-Outpost"])
|
||||
assert.Equal(t, []string{""}, h["X-Authentik-Name"])
|
||||
assert.Equal(t, []string{"foo"}, h["X-Authentik-Uid"])
|
||||
assert.Equal(t, []string{""}, h["X-Authentik-Username"])
|
||||
}
|
||||
|
||||
func TestForwardHandleNginx_Domain_Blank(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr()
|
||||
a.proxyConfig.CookieDomain = api.PtrString("foo")
|
||||
req, _ := http.NewRequest("GET", "/akprox/auth/nginx", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
a.forwardHandleNginx(rr, req)
|
||||
|
||||
assert.Equal(t, http.StatusInternalServerError, rr.Code)
|
||||
}
|
||||
|
||||
func TestForwardHandleNginx_Domain_Header(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr()
|
||||
a.proxyConfig.CookieDomain = api.PtrString("foo")
|
||||
a.proxyConfig.ExternalHost = "http://auth.test.goauthentik.io"
|
||||
req, _ := http.NewRequest("GET", "/akprox/auth/nginx", nil)
|
||||
req.Header.Set("X-Original-URL", "http://test.goauthentik.io/app")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
a.forwardHandleNginx(rr, req)
|
||||
|
||||
assert.Equal(t, http.StatusUnauthorized, rr.Code)
|
||||
|
||||
s, _ := a.sessions.Get(req, constants.SeesionName)
|
||||
assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect])
|
||||
}
|
@ -0,0 +1,132 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"goauthentik.io/api"
|
||||
"goauthentik.io/internal/outpost/proxyv2/constants"
|
||||
)
|
||||
|
||||
func TestForwardHandleTraefik_Single_Blank(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
req, _ := http.NewRequest("GET", "/akprox/auth/traefik", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
a.forwardHandleTraefik(rr, req)
|
||||
|
||||
assert.Equal(t, http.StatusInternalServerError, rr.Code)
|
||||
}
|
||||
|
||||
func TestForwardHandleTraefik_Single_Skip(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
req, _ := http.NewRequest("GET", "/akprox/auth/traefik", nil)
|
||||
req.Header.Set("X-Forwarded-Proto", "http")
|
||||
req.Header.Set("X-Forwarded-Host", "test.goauthentik.io")
|
||||
req.Header.Set("X-Forwarded-Uri", "/skip")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
a.forwardHandleTraefik(rr, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
}
|
||||
|
||||
func TestForwardHandleTraefik_Single_Headers(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
req, _ := http.NewRequest("GET", "/akprox/auth/traefik", nil)
|
||||
req.Header.Set("X-Forwarded-Proto", "http")
|
||||
req.Header.Set("X-Forwarded-Host", "test.goauthentik.io")
|
||||
req.Header.Set("X-Forwarded-Uri", "/app")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
a.forwardHandleTraefik(rr, req)
|
||||
|
||||
assert.Equal(t, rr.Code, http.StatusTemporaryRedirect)
|
||||
loc, _ := rr.Result().Location()
|
||||
assert.Equal(t, loc.String(), "http://test.goauthentik.io/akprox/start")
|
||||
|
||||
s, _ := a.sessions.Get(req, constants.SeesionName)
|
||||
assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect])
|
||||
}
|
||||
|
||||
func TestForwardHandleTraefik_Single_Claims(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
req, _ := http.NewRequest("GET", "/akprox/auth/traefik", nil)
|
||||
req.Header.Set("X-Forwarded-Proto", "http")
|
||||
req.Header.Set("X-Forwarded-Host", "test.goauthentik.io")
|
||||
req.Header.Set("X-Forwarded-Uri", "/app")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
a.forwardHandleTraefik(rr, req)
|
||||
|
||||
s, _ := a.sessions.Get(req, constants.SeesionName)
|
||||
s.Values[constants.SessionClaims] = Claims{
|
||||
Sub: "foo",
|
||||
Proxy: &ProxyClaims{
|
||||
UserAttributes: map[string]interface{}{
|
||||
"username": "foo",
|
||||
"password": "bar",
|
||||
"additionalHeaders": map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
err := a.sessions.Save(req, rr, s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
rr = httptest.NewRecorder()
|
||||
a.forwardHandleTraefik(rr, req)
|
||||
|
||||
h := rr.Result().Header
|
||||
|
||||
assert.Equal(t, []string{"Basic Zm9vOmJhcg=="}, h["Authorization"])
|
||||
assert.Equal(t, []string{"bar"}, h["Foo"])
|
||||
assert.Equal(t, []string{""}, h["User-Agent"])
|
||||
assert.Equal(t, []string{""}, h["X-Authentik-Email"])
|
||||
assert.Equal(t, []string{""}, h["X-Authentik-Groups"])
|
||||
assert.Equal(t, []string{""}, h["X-Authentik-Jwt"])
|
||||
assert.Equal(t, []string{""}, h["X-Authentik-Meta-App"])
|
||||
assert.Equal(t, []string{""}, h["X-Authentik-Meta-Jwks"])
|
||||
assert.Equal(t, []string{""}, h["X-Authentik-Meta-Outpost"])
|
||||
assert.Equal(t, []string{""}, h["X-Authentik-Name"])
|
||||
assert.Equal(t, []string{"foo"}, h["X-Authentik-Uid"])
|
||||
assert.Equal(t, []string{""}, h["X-Authentik-Username"])
|
||||
}
|
||||
|
||||
func TestForwardHandleTraefik_Domain_Blank(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr()
|
||||
a.proxyConfig.CookieDomain = api.PtrString("foo")
|
||||
req, _ := http.NewRequest("GET", "/akprox/auth/traefik", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
a.forwardHandleTraefik(rr, req)
|
||||
|
||||
assert.Equal(t, http.StatusInternalServerError, rr.Code)
|
||||
}
|
||||
|
||||
func TestForwardHandleTraefik_Domain_Header(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr()
|
||||
a.proxyConfig.CookieDomain = api.PtrString("foo")
|
||||
a.proxyConfig.ExternalHost = "http://auth.test.goauthentik.io"
|
||||
req, _ := http.NewRequest("GET", "/akprox/auth/traefik", nil)
|
||||
req.Header.Set("X-Forwarded-Proto", "http")
|
||||
req.Header.Set("X-Forwarded-Host", "test.goauthentik.io")
|
||||
req.Header.Set("X-Forwarded-Uri", "/app")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
a.forwardHandleTraefik(rr, req)
|
||||
|
||||
assert.Equal(t, http.StatusTemporaryRedirect, rr.Code)
|
||||
loc, _ := rr.Result().Location()
|
||||
assert.Equal(t, "http://auth.test.goauthentik.io/akprox/start", loc.String())
|
||||
|
||||
s, _ := a.sessions.Get(req, constants.SeesionName)
|
||||
assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect])
|
||||
}
|
@ -35,7 +35,7 @@ func (a *Application) configureProxy() error {
|
||||
rp.ModifyResponse = a.proxyModifyResponse
|
||||
a.mux.PathPrefix("/").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
claims, err := a.getClaims(r)
|
||||
if claims == nil && a.IsAllowlisted(r) {
|
||||
if claims == nil && a.IsAllowlisted(r.URL) {
|
||||
a.log.Trace("path can be accessed without authentication")
|
||||
} else if claims == nil && err != nil {
|
||||
a.redirectToStart(rw, r)
|
||||
@ -60,7 +60,7 @@ func (a *Application) configureProxy() error {
|
||||
}
|
||||
metrics.UpstreamTiming.With(prometheus.Labels{
|
||||
"outpost_name": a.outpostName,
|
||||
"upstream_host": u.String(),
|
||||
"upstream_host": r.URL.Host,
|
||||
"scheme": r.URL.Scheme,
|
||||
"method": r.Method,
|
||||
"path": r.URL.Path,
|
||||
@ -71,13 +71,27 @@ func (a *Application) configureProxy() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Application) proxyModifyRequest(u *url.URL) func(req *http.Request) {
|
||||
return func(req *http.Request) {
|
||||
req.URL.Scheme = u.Scheme
|
||||
req.URL.Host = u.Host
|
||||
func (a *Application) proxyModifyRequest(ou *url.URL) func(req *http.Request) {
|
||||
return func(r *http.Request) {
|
||||
claims, _ := a.getClaims(r)
|
||||
r.URL.Scheme = ou.Scheme
|
||||
r.URL.Host = ou.Host
|
||||
r.Host = ou.Host
|
||||
if claims != nil && claims.Proxy != nil && claims.Proxy.BackendOverride != "" {
|
||||
u, err := url.Parse(claims.Proxy.BackendOverride)
|
||||
if err != nil {
|
||||
a.log.WithField("backend_override", claims.Proxy.BackendOverride).WithError(err).Warning("failed parse user backend override")
|
||||
} else {
|
||||
r.URL.Scheme = u.Scheme
|
||||
r.URL.Host = u.Host
|
||||
r.Host = u.Host
|
||||
}
|
||||
}
|
||||
a.log.WithField("upstream_url", r.URL.String()).Trace("final upstream url")
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Application) proxyModifyResponse(res *http.Response) error {
|
||||
res.Header.Set("X-Powered-By", "authentik_proxy2")
|
||||
return nil
|
||||
}
|
||||
|
81
internal/outpost/proxyv2/application/mode_proxy_test.go
Normal file
81
internal/outpost/proxyv2/application/mode_proxy_test.go
Normal file
@ -0,0 +1,81 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"goauthentik.io/internal/outpost/proxyv2/constants"
|
||||
)
|
||||
|
||||
func TestProxy_ModifyRequest(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
req, _ := http.NewRequest("GET", "http://frontend/foo", nil)
|
||||
u, err := url.Parse("http://backend:8012")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
a.proxyModifyRequest(u)(req)
|
||||
|
||||
assert.Equal(t, "/foo", req.URL.Path)
|
||||
assert.Equal(t, "backend:8012", req.URL.Host)
|
||||
assert.Equal(t, "backend:8012", req.Host)
|
||||
}
|
||||
|
||||
func TestProxy_ModifyRequest_Claims(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
req, _ := http.NewRequest("GET", "http://frontend/foo", nil)
|
||||
u, err := url.Parse("http://backend:8012")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
s, _ := a.sessions.Get(req, constants.SeesionName)
|
||||
s.Values[constants.SessionClaims] = Claims{
|
||||
Sub: "foo",
|
||||
Proxy: &ProxyClaims{
|
||||
BackendOverride: "http://other-backend:8123",
|
||||
},
|
||||
}
|
||||
err = a.sessions.Save(req, rr, s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
a.proxyModifyRequest(u)(req)
|
||||
|
||||
assert.Equal(t, "/foo", req.URL.Path)
|
||||
assert.Equal(t, "other-backend:8123", req.URL.Host)
|
||||
assert.Equal(t, "other-backend:8123", req.Host)
|
||||
}
|
||||
|
||||
func TestProxy_ModifyRequest_Claims_Invalid(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
req, _ := http.NewRequest("GET", "http://frontend/foo", nil)
|
||||
u, err := url.Parse("http://backend:8012")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
s, _ := a.sessions.Get(req, constants.SeesionName)
|
||||
s.Values[constants.SessionClaims] = Claims{
|
||||
Sub: "foo",
|
||||
Proxy: &ProxyClaims{
|
||||
BackendOverride: ":qewr",
|
||||
},
|
||||
}
|
||||
err = a.sessions.Save(req, rr, s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
a.proxyModifyRequest(u)(req)
|
||||
|
||||
assert.Equal(t, "/foo", req.URL.Path)
|
||||
assert.Equal(t, "backend:8012", req.URL.Host)
|
||||
assert.Equal(t, "backend:8012", req.Host)
|
||||
}
|
40
internal/outpost/proxyv2/application/test.go
Normal file
40
internal/outpost/proxyv2/application/test.go
Normal file
@ -0,0 +1,40 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/quasoft/memstore"
|
||||
"goauthentik.io/api"
|
||||
"goauthentik.io/internal/outpost/ak"
|
||||
)
|
||||
|
||||
func newTestApplication() *Application {
|
||||
a, _ := NewApplication(
|
||||
api.ProxyOutpostConfig{
|
||||
Name: ak.TestSecret(),
|
||||
ClientId: api.PtrString(ak.TestSecret()),
|
||||
ClientSecret: api.PtrString(ak.TestSecret()),
|
||||
CookieSecret: api.PtrString(ak.TestSecret()),
|
||||
CookieDomain: api.PtrString(""),
|
||||
Mode: api.PROXYMODE_FORWARD_SINGLE.Ptr(),
|
||||
SkipPathRegex: api.PtrString("/skip.*"),
|
||||
BasicAuthEnabled: api.PtrBool(true),
|
||||
BasicAuthUserAttribute: api.PtrString("username"),
|
||||
BasicAuthPasswordAttribute: api.PtrString("password"),
|
||||
},
|
||||
http.DefaultClient,
|
||||
nil,
|
||||
ak.MockAK(
|
||||
api.Outpost{
|
||||
Config: map[string]interface{}{
|
||||
"authentik_host": ak.TestSecret(),
|
||||
},
|
||||
},
|
||||
ak.MockConfig(),
|
||||
),
|
||||
)
|
||||
a.sessions = memstore.NewMemStore(
|
||||
[]byte(ak.TestSecret()),
|
||||
)
|
||||
return a
|
||||
}
|
@ -26,21 +26,14 @@ func (a *Application) redirectToStart(rw http.ResponseWriter, r *http.Request) {
|
||||
if err == nil {
|
||||
a.log.WithError(err).Warning("failed to decode session")
|
||||
}
|
||||
redirectUrl := r.URL.String()
|
||||
// simple way to copy the URL
|
||||
u, _ := url.Parse(redirectUrl)
|
||||
// In proxy and forward_single mode we only have one URL that we route on
|
||||
// if we somehow got here without that URL, make sure we're at least redirected back to it
|
||||
if a.Mode() == api.PROXYMODE_PROXY || a.Mode() == api.PROXYMODE_FORWARD_SINGLE {
|
||||
u.Host = a.proxyConfig.ExternalHost
|
||||
}
|
||||
redirectUrl := urlJoin(a.proxyConfig.ExternalHost, r.URL.Path)
|
||||
if a.Mode() == api.PROXYMODE_FORWARD_DOMAIN {
|
||||
dom := strings.TrimPrefix(*a.proxyConfig.CookieDomain, ".")
|
||||
// In forward_domain we only check that the current URL's host
|
||||
// ends with the cookie domain (remove the leading period if set)
|
||||
if !strings.HasSuffix(r.URL.Hostname(), dom) {
|
||||
a.log.WithField("url", r.URL.String()).WithField("cd", dom).Warning("Invalid redirect found")
|
||||
redirectUrl = ""
|
||||
redirectUrl = a.proxyConfig.ExternalHost
|
||||
}
|
||||
}
|
||||
s.Values[constants.SessionRedirect] = redirectUrl
|
||||
@ -94,3 +87,13 @@ func contains(s []string, e string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func cleanseHeaders(headers http.Header) map[string]string {
|
||||
h := make(map[string]string)
|
||||
for hk, hv := range headers {
|
||||
if len(hv) > 0 {
|
||||
h[hk] = hv[0]
|
||||
}
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
81
internal/outpost/proxyv2/application/utils_test.go
Normal file
81
internal/outpost/proxyv2/application/utils_test.go
Normal file
@ -0,0 +1,81 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"goauthentik.io/api"
|
||||
"goauthentik.io/internal/outpost/proxyv2/constants"
|
||||
)
|
||||
|
||||
func TestRedirectToStart_Proxy(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
a.proxyConfig.Mode = api.PROXYMODE_PROXY.Ptr()
|
||||
a.proxyConfig.ExternalHost = "https://test.goauthentik.io"
|
||||
req, _ := http.NewRequest("GET", "/foo/bar/baz", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
a.redirectToStart(rr, req)
|
||||
|
||||
assert.Equal(t, http.StatusFound, rr.Code)
|
||||
loc, _ := rr.Result().Location()
|
||||
assert.Equal(t, "https://test.goauthentik.io/akprox/start", loc.String())
|
||||
|
||||
s, _ := a.sessions.Get(req, constants.SeesionName)
|
||||
assert.Equal(t, "https://test.goauthentik.io/foo/bar/baz", s.Values[constants.SessionRedirect])
|
||||
}
|
||||
|
||||
func TestRedirectToStart_Forward(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
a.proxyConfig.Mode = api.PROXYMODE_FORWARD_SINGLE.Ptr()
|
||||
a.proxyConfig.ExternalHost = "https://test.goauthentik.io"
|
||||
req, _ := http.NewRequest("GET", "/foo/bar/baz", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
a.redirectToStart(rr, req)
|
||||
|
||||
assert.Equal(t, http.StatusFound, rr.Code)
|
||||
loc, _ := rr.Result().Location()
|
||||
assert.Equal(t, "https://test.goauthentik.io/akprox/start", loc.String())
|
||||
|
||||
s, _ := a.sessions.Get(req, constants.SeesionName)
|
||||
assert.Equal(t, "https://test.goauthentik.io/foo/bar/baz", s.Values[constants.SessionRedirect])
|
||||
}
|
||||
|
||||
func TestRedirectToStart_Forward_Domain_Invalid(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
a.proxyConfig.CookieDomain = api.PtrString("foo")
|
||||
a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr()
|
||||
a.proxyConfig.ExternalHost = "https://test.goauthentik.io"
|
||||
req, _ := http.NewRequest("GET", "/foo/bar/baz", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
a.redirectToStart(rr, req)
|
||||
|
||||
assert.Equal(t, http.StatusFound, rr.Code)
|
||||
loc, _ := rr.Result().Location()
|
||||
assert.Equal(t, "https://test.goauthentik.io/akprox/start", loc.String())
|
||||
|
||||
s, _ := a.sessions.Get(req, constants.SeesionName)
|
||||
assert.Equal(t, "https://test.goauthentik.io", s.Values[constants.SessionRedirect])
|
||||
}
|
||||
|
||||
func TestRedirectToStart_Forward_Domain(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
a.proxyConfig.CookieDomain = api.PtrString("goauthentik.io")
|
||||
a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr()
|
||||
a.proxyConfig.ExternalHost = "https://test.goauthentik.io"
|
||||
req, _ := http.NewRequest("GET", "/foo/bar/baz", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
a.redirectToStart(rr, req)
|
||||
|
||||
assert.Equal(t, http.StatusFound, rr.Code)
|
||||
loc, _ := rr.Result().Location()
|
||||
assert.Equal(t, "https://test.goauthentik.io/akprox/start", loc.String())
|
||||
|
||||
s, _ := a.sessions.Get(req, constants.SeesionName)
|
||||
assert.Equal(t, "https://test.goauthentik.io", s.Values[constants.SessionRedirect])
|
||||
}
|
@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"goauthentik.io/api"
|
||||
"goauthentik.io/internal/outpost/proxyv2/application"
|
||||
"goauthentik.io/internal/outpost/proxyv2/metrics"
|
||||
"goauthentik.io/internal/utils/web"
|
||||
@ -58,6 +59,9 @@ func (ps *ProxyServer) lookupApp(r *http.Request) (*application.Application, str
|
||||
var longestMatch *application.Application
|
||||
longestMatchLength := 0
|
||||
for _, app := range ps.apps {
|
||||
if app.Mode() != api.PROXYMODE_FORWARD_DOMAIN {
|
||||
continue
|
||||
}
|
||||
// Check if the cookie domain has a leading period for a wildcard
|
||||
// This will decrease the weight of a wildcard domain, but a request to example.com
|
||||
// with the cookie domain set to example.com will still be routed correctly.
|
||||
@ -70,6 +74,11 @@ func (ps *ProxyServer) lookupApp(r *http.Request) (*application.Application, str
|
||||
}
|
||||
longestMatch = app
|
||||
longestMatchLength = len(cd)
|
||||
// Also for forward_auth_domain, we need to respond on the external domain
|
||||
if app.ProxyConfig().ExternalHost == host {
|
||||
ps.log.WithField("host", host).WithField("app", app.ProxyConfig().Name).Debug("Found app based on external_host")
|
||||
return app, host
|
||||
}
|
||||
}
|
||||
// Check if our longes match is 0, in which case we didn't match, so we
|
||||
// manually return no app
|
||||
|
@ -46,7 +46,7 @@ func NewProxyServer(ac *ak.APIController, portOffset int) *ProxyServer {
|
||||
rootMux.Use(func(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
h.ServeHTTP(rw, r)
|
||||
rw.Header().Set("Server", "authentik_proxy2")
|
||||
rw.Header().Set("X-Powered-By", "authentik_proxy2")
|
||||
})
|
||||
})
|
||||
|
||||
@ -91,7 +91,7 @@ func (ps *ProxyServer) TimerFlowCacheExpiry() {}
|
||||
func (ps *ProxyServer) GetCertificate(serverName string) *tls.Certificate {
|
||||
app, ok := ps.apps[serverName]
|
||||
if !ok {
|
||||
ps.log.WithField("server-name", serverName).Debug("app does not exist")
|
||||
ps.log.WithField("server-name", serverName).Debug("failed to get certificate for ServerName")
|
||||
return nil
|
||||
}
|
||||
if app.Cert == nil {
|
||||
@ -102,7 +102,11 @@ func (ps *ProxyServer) GetCertificate(serverName string) *tls.Certificate {
|
||||
}
|
||||
|
||||
func (ps *ProxyServer) getCertificates(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
appCert := ps.GetCertificate(info.ServerName)
|
||||
sn := info.ServerName
|
||||
if sn == "" {
|
||||
return &ps.defaultCert, nil
|
||||
}
|
||||
appCert := ps.GetCertificate(sn)
|
||||
if appCert == nil {
|
||||
return &ps.defaultCert, nil
|
||||
}
|
||||
@ -151,17 +155,14 @@ func (ps *ProxyServer) Start() error {
|
||||
wg.Add(3)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
ps.log.Debug("Starting HTTP Server...")
|
||||
ps.ServeHTTP()
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
ps.log.Debug("Starting HTTPs Server...")
|
||||
ps.ServeHTTPS()
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
ps.log.Debug("Starting Metrics Server...")
|
||||
metrics.RunServer()
|
||||
}()
|
||||
return nil
|
||||
|
@ -99,14 +99,14 @@ func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
h.handler.ServeHTTP(responseLogger, req)
|
||||
duration := float64(time.Since(t)) / float64(time.Millisecond)
|
||||
h.afterHandler(h.logger.WithFields(log.Fields{
|
||||
"remote": req.RemoteAddr,
|
||||
"host": GetHost(req),
|
||||
"request_protocol": req.Proto,
|
||||
"runtime": fmt.Sprintf("%0.3f", duration),
|
||||
"method": req.Method,
|
||||
"size": responseLogger.Size(),
|
||||
"status": responseLogger.Status(),
|
||||
"upstream": responseLogger.upstream,
|
||||
"request_useragent": req.UserAgent(),
|
||||
"remote": req.RemoteAddr,
|
||||
"host": GetHost(req),
|
||||
"runtime": fmt.Sprintf("%0.3f", duration),
|
||||
"method": req.Method,
|
||||
"scheme": req.URL.Scheme,
|
||||
"size": responseLogger.Size(),
|
||||
"status": responseLogger.Status(),
|
||||
"upstream": responseLogger.upstream,
|
||||
"user_agent": req.UserAgent(),
|
||||
}), req).Info(url.RequestURI())
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
@ -24,6 +25,7 @@ func (ws *WebServer) configureProxy() {
|
||||
if req.TLS != nil {
|
||||
req.Header.Set("X-Forwarded-Proto", "https")
|
||||
}
|
||||
ws.log.WithField("url", req.URL.String()).WithField("headers", req.Header).Trace("tracing request to backend")
|
||||
}
|
||||
rp := &httputil.ReverseProxy{Director: director}
|
||||
rp.ErrorHandler = ws.proxyErrorHandler
|
||||
@ -65,15 +67,27 @@ func (ws *WebServer) configureProxy() {
|
||||
}
|
||||
|
||||
func (ws *WebServer) proxyErrorHandler(rw http.ResponseWriter, req *http.Request, err error) {
|
||||
ws.log.Warning(err.Error())
|
||||
ws.log.WithError(err).Warning("failed to proxy to backend")
|
||||
rw.WriteHeader(http.StatusBadGateway)
|
||||
_, err = rw.Write([]byte("authentik starting..."))
|
||||
em := fmt.Sprintf("failed to connect to authentik backend: %v", err)
|
||||
if !ws.p.IsRunning() {
|
||||
em = "authentik starting..."
|
||||
}
|
||||
// return json if the client asks for json
|
||||
if req.Header.Get("Accept") == "application/json" {
|
||||
eem, _ := json.Marshal(map[string]string{
|
||||
"error": em,
|
||||
})
|
||||
em = string(eem)
|
||||
}
|
||||
_, err = rw.Write([]byte(em))
|
||||
if err != nil {
|
||||
ws.log.WithError(err).Warning("failed to write error message")
|
||||
}
|
||||
}
|
||||
|
||||
func (ws *WebServer) proxyModifyResponse(r *http.Response) error {
|
||||
r.Header.Set("server", "authentik")
|
||||
r.Header.Set("X-Powered-By", "authentik")
|
||||
r.Header.Del("Server")
|
||||
return nil
|
||||
}
|
||||
|
@ -16,6 +16,9 @@ func (ws *WebServer) GetCertificate() func(ch *tls.ClientHelloInfo) (*tls.Certif
|
||||
ws.log.WithError(err).Error("failed to generate default cert")
|
||||
}
|
||||
return func(ch *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
if ch.ServerName == "" {
|
||||
return &cert, nil
|
||||
}
|
||||
if ws.ProxyServer != nil {
|
||||
appCert := ws.ProxyServer.GetCertificate(ch.ServerName)
|
||||
if appCert != nil {
|
||||
|
@ -61,14 +61,15 @@ MODE_FILE="/tmp/authentik-mode"
|
||||
if [[ "$1" == "server" ]]; then
|
||||
wait_for_db
|
||||
echo "server" > $MODE_FILE
|
||||
# We only set prometheus_multiproc_dir for serer, as with the worker it just fills up the disk
|
||||
# We only set PROMETHEUS_MULTIPROC_DIR for serer, as with the worker it just fills up the disk
|
||||
# as one file is created per process
|
||||
#
|
||||
# Set to TMPDIR instead hardcoded path so this can be used outside docker too
|
||||
export prometheus_multiproc_dir=$TMPDIR
|
||||
export PROMETHEUS_MULTIPROC_DIR=$TMPDIR
|
||||
python -m lifecycle.migrate
|
||||
/authentik-proxy
|
||||
elif [[ "$1" == "worker" ]]; then
|
||||
wait_for_db
|
||||
echo "worker" > $MODE_FILE
|
||||
check_if_root "celery -A authentik.root.celery worker -Ofair --max-tasks-per-child=1 --autoscale 3,1 -E -B -s /tmp/celerybeat-schedule -Q authentik,authentik_scheduled,authentik_events"
|
||||
elif [[ "$1" == "flower" ]]; then
|
||||
|
@ -1,13 +1,18 @@
|
||||
"""Gunicorn config"""
|
||||
import os
|
||||
import pwd
|
||||
from hashlib import sha512
|
||||
from multiprocessing import cpu_count
|
||||
|
||||
import structlog
|
||||
from kubernetes.config.incluster_config import SERVICE_HOST_ENV_NAME
|
||||
|
||||
from authentik import get_full_version
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.lib.utils.http import get_http_session
|
||||
from authentik.lib.utils.reflection import get_env
|
||||
|
||||
bind = "127.0.0.1:8000"
|
||||
reload = True
|
||||
|
||||
try:
|
||||
pwd.getpwnam("authentik")
|
||||
@ -23,6 +28,9 @@ if os.path.exists("/dev/shm"): # nosec
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "authentik.root.settings")
|
||||
|
||||
max_requests = 1000
|
||||
max_requests_jitter = 50
|
||||
|
||||
logconfig_dict = {
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
@ -67,3 +75,31 @@ def worker_exit(server, worker):
|
||||
from prometheus_client import multiprocess
|
||||
|
||||
multiprocess.mark_process_dead(worker.pid)
|
||||
|
||||
|
||||
if not CONFIG.y_bool("disable_startup_analytics", False):
|
||||
env = get_env()
|
||||
should_send = env not in ["dev", "ci"]
|
||||
if should_send:
|
||||
try:
|
||||
get_http_session().post(
|
||||
"https://goauthentik.io/api/event",
|
||||
json={
|
||||
"domain": "authentik",
|
||||
"name": "pageview",
|
||||
"referrer": get_full_version(),
|
||||
"url": (
|
||||
f"http://localhost/{env}?utm_source={get_full_version()}&utm_medium={env}"
|
||||
),
|
||||
},
|
||||
headers={
|
||||
"User-Agent": sha512(str(CONFIG.y("secret_key")).encode("ascii")).hexdigest()[
|
||||
:16
|
||||
],
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
timeout=5,
|
||||
)
|
||||
# pylint: disable=bare-except
|
||||
except: # nosec
|
||||
pass
|
||||
|
@ -26,9 +26,13 @@ def j_print(event: str, log_level: str = "info", **kwargs):
|
||||
print(dumps(data), file=stderr)
|
||||
|
||||
|
||||
j_print("Starting authentik bootstrap")
|
||||
|
||||
# Sanity check, ensure SECRET_KEY is set before we even check for database connectivity
|
||||
if CONFIG.y("secret_key") is None or len(CONFIG.y("secret_key")) == 0:
|
||||
j_print("----------------------------------------------------------------------")
|
||||
j_print("Secret key missing, check https://goauthentik.io/docs/installation/.")
|
||||
j_print("----------------------------------------------------------------------")
|
||||
sysexit(1)
|
||||
|
||||
|
||||
@ -45,7 +49,9 @@ while True:
|
||||
break
|
||||
except OperationalError as exc:
|
||||
sleep(1)
|
||||
j_print(f"PostgreSQL Connection failed, retrying... ({exc})")
|
||||
j_print(f"PostgreSQL connection failed, retrying... ({exc})")
|
||||
finally:
|
||||
j_print("PostgreSQL connection successful")
|
||||
|
||||
REDIS_PROTOCOL_PREFIX = "redis://"
|
||||
if CONFIG.y_bool("redis.tls", False):
|
||||
@ -63,3 +69,7 @@ while True:
|
||||
except RedisError as exc:
|
||||
sleep(1)
|
||||
j_print(f"Redis Connection failed, retrying... ({exc})", redis_url=REDIS_URL)
|
||||
finally:
|
||||
j_print("Redis Connection successful")
|
||||
|
||||
j_print("Finished authentik bootstrap")
|
||||
|
BIN
locale/es/LC_MESSAGES/django.mo
Normal file
BIN
locale/es/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
1831
locale/es/LC_MESSAGES/django.po
Normal file
1831
locale/es/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load Diff
457
poetry.lock
generated
457
poetry.lock
generated
@ -54,11 +54,11 @@ vine = "5.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "asgiref"
|
||||
version = "3.4.1"
|
||||
version = "3.5.0"
|
||||
description = "ASGI specs, helper code, and adapters"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.extras]
|
||||
tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"]
|
||||
@ -163,11 +163,11 @@ visualize = ["graphviz (>0.5.1)", "Twisted (>=16.1.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "bandit"
|
||||
version = "1.7.1"
|
||||
version = "1.7.2"
|
||||
description = "Security oriented static analyser for python code."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""}
|
||||
@ -175,6 +175,11 @@ GitPython = ">=1.0.1"
|
||||
PyYAML = ">=5.3.1"
|
||||
stevedore = ">=1.20.0"
|
||||
|
||||
[package.extras]
|
||||
test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)", "toml"]
|
||||
toml = ["toml"]
|
||||
yaml = ["pyyaml"]
|
||||
|
||||
[[package]]
|
||||
name = "bcrypt"
|
||||
version = "3.2.0"
|
||||
@ -201,40 +206,35 @@ python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "21.12b0"
|
||||
version = "22.1.0"
|
||||
description = "The uncompromising code formatter."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6.2"
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=7.1.2"
|
||||
click = ">=8.0.0"
|
||||
mypy-extensions = ">=0.4.3"
|
||||
pathspec = ">=0.9.0,<1"
|
||||
pathspec = ">=0.9.0"
|
||||
platformdirs = ">=2"
|
||||
tomli = ">=0.2.6,<2.0.0"
|
||||
typing-extensions = [
|
||||
{version = ">=3.10.0.0", markers = "python_version < \"3.10\""},
|
||||
{version = "!=3.10.0.1", markers = "python_version >= \"3.10\""},
|
||||
]
|
||||
tomli = ">=1.1.0"
|
||||
|
||||
[package.extras]
|
||||
colorama = ["colorama (>=0.4.3)"]
|
||||
d = ["aiohttp (>=3.7.4)"]
|
||||
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
||||
python2 = ["typed-ast (>=1.4.3)"]
|
||||
uvloop = ["uvloop (>=0.15.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "boto3"
|
||||
version = "1.20.40"
|
||||
version = "1.20.46"
|
||||
description = "The AWS SDK for Python"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">= 3.6"
|
||||
|
||||
[package.dependencies]
|
||||
botocore = ">=1.23.40,<1.24.0"
|
||||
botocore = ">=1.23.46,<1.24.0"
|
||||
jmespath = ">=0.7.1,<1.0.0"
|
||||
s3transfer = ">=0.5.0,<0.6.0"
|
||||
|
||||
@ -243,7 +243,7 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
|
||||
|
||||
[[package]]
|
||||
name = "botocore"
|
||||
version = "1.23.40"
|
||||
version = "1.23.46"
|
||||
description = "Low-level, data-driven core of boto 3."
|
||||
category = "main"
|
||||
optional = false
|
||||
@ -267,11 +267,11 @@ python-versions = ">=3.5"
|
||||
|
||||
[[package]]
|
||||
name = "cachetools"
|
||||
version = "4.2.4"
|
||||
version = "5.0.0"
|
||||
description = "Extensible memoizing collections and decorators"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "~=3.5"
|
||||
python-versions = "~=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "cattrs"
|
||||
@ -490,11 +490,11 @@ python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "6.2"
|
||||
version = "6.3"
|
||||
description = "Code coverage measurement for Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
tomli = {version = "*", optional = true, markers = "extra == \"toml\""}
|
||||
@ -580,7 +580,7 @@ dev = ["tox", "bump2version (<1)", "sphinx (<2)", "importlib-metadata (<3)", "im
|
||||
|
||||
[[package]]
|
||||
name = "django"
|
||||
version = "4.0.1"
|
||||
version = "4.0.2"
|
||||
description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
|
||||
category = "main"
|
||||
optional = false
|
||||
@ -844,14 +844,14 @@ gitdb = ">=4.0.1,<5"
|
||||
|
||||
[[package]]
|
||||
name = "google-auth"
|
||||
version = "2.3.3"
|
||||
version = "2.4.0"
|
||||
description = "Google Authentication Library"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*"
|
||||
|
||||
[package.dependencies]
|
||||
cachetools = ">=2.0.0,<5.0"
|
||||
cachetools = ">=2.0.0,<6.0"
|
||||
pyasn1-modules = ">=0.2.1"
|
||||
rsa = {version = ">=3.1.4,<5", markers = "python_version >= \"3.6\""}
|
||||
six = ">=1.9.0"
|
||||
@ -877,7 +877,7 @@ tornado = ["tornado (>=0.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.12.0"
|
||||
version = "0.13.0"
|
||||
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
|
||||
category = "main"
|
||||
optional = false
|
||||
@ -1122,11 +1122,11 @@ python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "multidict"
|
||||
version = "5.2.0"
|
||||
version = "6.0.1"
|
||||
description = "multidict implementation"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "mypy-extensions"
|
||||
@ -1297,7 +1297,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[[package]]
|
||||
name = "pycryptodome"
|
||||
version = "3.12.0"
|
||||
version = "3.14.0"
|
||||
description = "Cryptographic library for Python"
|
||||
category = "main"
|
||||
optional = false
|
||||
@ -1393,7 +1393,7 @@ test = ["flaky", "pretend", "pytest (>=3.0.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "pyparsing"
|
||||
version = "3.0.6"
|
||||
version = "3.0.7"
|
||||
description = "Python parsing module"
|
||||
category = "main"
|
||||
optional = false
|
||||
@ -1555,7 +1555,7 @@ test = ["fixtures", "mock", "purl", "pytest", "sphinx", "testrepository (>=0.0.1
|
||||
|
||||
[[package]]
|
||||
name = "requests-oauthlib"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
description = "OAuthlib authentication support for Requests."
|
||||
category = "main"
|
||||
optional = false
|
||||
@ -1608,39 +1608,33 @@ urllib3 = {version = ">=1.26,<2.0", extras = ["secure"]}
|
||||
|
||||
[[package]]
|
||||
name = "sentry-sdk"
|
||||
version = "1.5.0"
|
||||
description = ""
|
||||
version = "1.5.4"
|
||||
description = "Python client for Sentry (https://sentry.io)"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
develop = false
|
||||
|
||||
[package.dependencies]
|
||||
certifi = "*"
|
||||
urllib3 = ">=1.10.0"
|
||||
|
||||
[package.extras]
|
||||
flask = ["flask (>=0.11)", "blinker (>=1.1)"]
|
||||
bottle = ["bottle (>=0.12.13)"]
|
||||
falcon = ["falcon (>=1.4)"]
|
||||
django = ["django (>=1.8)"]
|
||||
sanic = ["sanic (>=0.8)"]
|
||||
celery = ["celery (>=3)"]
|
||||
beam = ["apache-beam (>=2.12)"]
|
||||
rq = ["rq (>=0.6)"]
|
||||
aiohttp = ["aiohttp (>=3.5)"]
|
||||
tornado = ["tornado (>=5)"]
|
||||
sqlalchemy = ["sqlalchemy (>=1.2)"]
|
||||
pyspark = ["pyspark (>=2.4.4)"]
|
||||
pure_eval = ["pure-eval", "executing", "asttokens"]
|
||||
beam = ["apache-beam (>=2.12)"]
|
||||
bottle = ["bottle (>=0.12.13)"]
|
||||
celery = ["celery (>=3)"]
|
||||
chalice = ["chalice (>=1.16.0)"]
|
||||
django = ["django (>=1.8)"]
|
||||
falcon = ["falcon (>=1.4)"]
|
||||
flask = ["flask (>=0.11)", "blinker (>=1.1)"]
|
||||
httpx = ["httpx (>=0.16.0)"]
|
||||
|
||||
[package.source]
|
||||
type = "git"
|
||||
url = 'https://github.com/beryju/sentry-python.git'
|
||||
reference = '379aee28b15d3b87b381317746c4efd24b3d7bc3'
|
||||
resolved_reference = "379aee28b15d3b87b381317746c4efd24b3d7bc3"
|
||||
pure_eval = ["pure-eval", "executing", "asttokens"]
|
||||
pyspark = ["pyspark (>=2.4.4)"]
|
||||
quart = ["quart (>=0.16.1)", "blinker (>=1.1)"]
|
||||
rq = ["rq (>=0.6)"]
|
||||
sanic = ["sanic (>=0.8)"]
|
||||
sqlalchemy = ["sqlalchemy (>=1.2)"]
|
||||
tornado = ["tornado (>=5)"]
|
||||
|
||||
[[package]]
|
||||
name = "service-identity"
|
||||
@ -1904,11 +1898,11 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.17.0"
|
||||
version = "0.17.3"
|
||||
description = "The lightning-fast ASGI server."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
asgiref = ">=3.4.0"
|
||||
@ -2069,7 +2063,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "b943f64c1111790d5534be74e941ce6880efb75cdf562993a66f138c082cd80f"
|
||||
content-hash = "d3061d973ae4fd3ddea0bff63f1dbd615322546024b75dc6015ef5faf01d6368"
|
||||
|
||||
[metadata.files]
|
||||
aiohttp = [
|
||||
@ -2159,8 +2153,8 @@ amqp = [
|
||||
{file = "amqp-5.0.9.tar.gz", hash = "sha256:1e5f707424e544078ca196e72ae6a14887ce74e02bd126be54b7c03c971bef18"},
|
||||
]
|
||||
asgiref = [
|
||||
{file = "asgiref-3.4.1-py3-none-any.whl", hash = "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"},
|
||||
{file = "asgiref-3.4.1.tar.gz", hash = "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9"},
|
||||
{file = "asgiref-3.5.0-py3-none-any.whl", hash = "sha256:88d59c13d634dcffe0510be048210188edd79aeccb6a6c9028cdad6f31d730a9"},
|
||||
{file = "asgiref-3.5.0.tar.gz", hash = "sha256:2f8abc20f7248433085eda803936d98992f1343ddb022065779f37c5da0181d0"},
|
||||
]
|
||||
asn1crypto = [
|
||||
{file = "asn1crypto-1.4.0-py2.py3-none-any.whl", hash = "sha256:4bcdf33c861c7d40bdcd74d8e4dd7661aac320fcdf40b9a3f95b4ee12fde2fa8"},
|
||||
@ -2194,8 +2188,8 @@ automat = [
|
||||
{file = "Automat-20.2.0.tar.gz", hash = "sha256:7979803c74610e11ef0c0d68a2942b152df52da55336e0c9d58daf1831cbdf33"},
|
||||
]
|
||||
bandit = [
|
||||
{file = "bandit-1.7.1-py3-none-any.whl", hash = "sha256:f5acd838e59c038a159b5c621cf0f8270b279e884eadd7b782d7491c02add0d4"},
|
||||
{file = "bandit-1.7.1.tar.gz", hash = "sha256:a81b00b5436e6880fa8ad6799bc830e02032047713cbb143a12939ac67eb756c"},
|
||||
{file = "bandit-1.7.2-py3-none-any.whl", hash = "sha256:e20402cadfd126d85b68ed4c8862959663c8c372dbbb1fca8f8e2c9f55a067ec"},
|
||||
{file = "bandit-1.7.2.tar.gz", hash = "sha256:6d11adea0214a43813887bfe71a377b5a9955e4c826c8ffd341b494e3ab25260"},
|
||||
]
|
||||
bcrypt = [
|
||||
{file = "bcrypt-3.2.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:b589229207630484aefe5899122fb938a5b017b0f4349f769b8c13e78d99a8fd"},
|
||||
@ -2214,24 +2208,45 @@ billiard = [
|
||||
{file = "billiard-3.6.4.0.tar.gz", hash = "sha256:299de5a8da28a783d51b197d496bef4f1595dd023a93a4f59dde1886ae905547"},
|
||||
]
|
||||
black = [
|
||||
{file = "black-21.12b0-py3-none-any.whl", hash = "sha256:a615e69ae185e08fdd73e4715e260e2479c861b5740057fde6e8b4e3b7dd589f"},
|
||||
{file = "black-21.12b0.tar.gz", hash = "sha256:77b80f693a569e2e527958459634f18df9b0ba2625ba4e0c2d5da5be42e6f2b3"},
|
||||
{file = "black-22.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6"},
|
||||
{file = "black-22.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866"},
|
||||
{file = "black-22.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e21e1f1efa65a50e3960edd068b6ae6d64ad6235bd8bfea116a03b21836af71"},
|
||||
{file = "black-22.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f69158a7d120fd641d1fa9a921d898e20d52e44a74a6fbbcc570a62a6bc8ab"},
|
||||
{file = "black-22.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:228b5ae2c8e3d6227e4bde5920d2fc66cc3400fde7bcc74f480cb07ef0b570d5"},
|
||||
{file = "black-22.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b1a5ed73ab4c482208d20434f700d514f66ffe2840f63a6252ecc43a9bc77e8a"},
|
||||
{file = "black-22.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35944b7100af4a985abfcaa860b06af15590deb1f392f06c8683b4381e8eeaf0"},
|
||||
{file = "black-22.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7835fee5238fc0a0baf6c9268fb816b5f5cd9b8793423a75e8cd663c48d073ba"},
|
||||
{file = "black-22.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dae63f2dbf82882fa3b2a3c49c32bffe144970a573cd68d247af6560fc493ae1"},
|
||||
{file = "black-22.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fa1db02410b1924b6749c245ab38d30621564e658297484952f3d8a39fce7e8"},
|
||||
{file = "black-22.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c8226f50b8c34a14608b848dc23a46e5d08397d009446353dad45e04af0c8e28"},
|
||||
{file = "black-22.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2d6f331c02f0f40aa51a22e479c8209d37fcd520c77721c034517d44eecf5912"},
|
||||
{file = "black-22.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:742ce9af3086e5bd07e58c8feb09dbb2b047b7f566eb5f5bc63fd455814979f3"},
|
||||
{file = "black-22.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fdb8754b453fb15fad3f72cd9cad3e16776f0964d67cf30ebcbf10327a3777a3"},
|
||||
{file = "black-22.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5660feab44c2e3cb24b2419b998846cbb01c23c7fe645fee45087efa3da2d61"},
|
||||
{file = "black-22.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:6f2f01381f91c1efb1451998bd65a129b3ed6f64f79663a55fe0e9b74a5f81fd"},
|
||||
{file = "black-22.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:efbadd9b52c060a8fc3b9658744091cb33c31f830b3f074422ed27bad2b18e8f"},
|
||||
{file = "black-22.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8871fcb4b447206904932b54b567923e5be802b9b19b744fdff092bd2f3118d0"},
|
||||
{file = "black-22.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccad888050f5393f0d6029deea2a33e5ae371fd182a697313bdbd835d3edaf9c"},
|
||||
{file = "black-22.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07e5c049442d7ca1a2fc273c79d1aecbbf1bc858f62e8184abe1ad175c4f7cc2"},
|
||||
{file = "black-22.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:373922fc66676133ddc3e754e4509196a8c392fec3f5ca4486673e685a421321"},
|
||||
{file = "black-22.1.0-py3-none-any.whl", hash = "sha256:3524739d76b6b3ed1132422bf9d82123cd1705086723bc3e235ca39fd21c667d"},
|
||||
{file = "black-22.1.0.tar.gz", hash = "sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5"},
|
||||
]
|
||||
boto3 = [
|
||||
{file = "boto3-1.20.40-py3-none-any.whl", hash = "sha256:cfe85589e4a0a997c7b9ae7432400b03fa6fa5fea29fdc48db3099a903b76998"},
|
||||
{file = "boto3-1.20.40.tar.gz", hash = "sha256:66aef9a6d8cad393f69166112ba49e14e2c6766f9278c96134101314a9af2992"},
|
||||
{file = "boto3-1.20.46-py3-none-any.whl", hash = "sha256:a2ffce001160d7e7c72a90c3084700d50eb64ea4a3aae8afe21566971d1fd611"},
|
||||
{file = "boto3-1.20.46.tar.gz", hash = "sha256:d7effba509d7298ef49316ba2da7a2ea115f2a7ff691f875f6354666663cf386"},
|
||||
]
|
||||
botocore = [
|
||||
{file = "botocore-1.23.40-py3-none-any.whl", hash = "sha256:88a314fe27cd97a0c731094c5b34db01ebe930801700e5d1b68485ebde746c3c"},
|
||||
{file = "botocore-1.23.40.tar.gz", hash = "sha256:49baa1fca4483b24769f0743fbf72afe4db391f41f1fc12ea34e06036db642a4"},
|
||||
{file = "botocore-1.23.46-py3-none-any.whl", hash = "sha256:354bce55e5adc8e2fe106acfd455ce448f9b920d7b697d06faa8cf200fd6566b"},
|
||||
{file = "botocore-1.23.46.tar.gz", hash = "sha256:38dd4564839f531725b667db360ba7df2125ceb3752b0ba12759c3e918015b95"},
|
||||
]
|
||||
bump2version = [
|
||||
{file = "bump2version-1.0.1-py2.py3-none-any.whl", hash = "sha256:37f927ea17cde7ae2d7baf832f8e80ce3777624554a653006c9144f8017fe410"},
|
||||
{file = "bump2version-1.0.1.tar.gz", hash = "sha256:762cb2bfad61f4ec8e2bdf452c7c267416f8c70dd9ecb1653fd0bbb01fa936e6"},
|
||||
]
|
||||
cachetools = [
|
||||
{file = "cachetools-4.2.4-py3-none-any.whl", hash = "sha256:92971d3cb7d2a97efff7c7bb1657f21a8f5fb309a37530537c71b1774189f2d1"},
|
||||
{file = "cachetools-4.2.4.tar.gz", hash = "sha256:89ea6f1b638d5a73a4f9226be57ac5e4f399d22770b92355f92dcb0f7f001693"},
|
||||
{file = "cachetools-5.0.0-py3-none-any.whl", hash = "sha256:8fecd4203a38af17928be7b90689d8083603073622229ca7077b72d8e5a976e4"},
|
||||
{file = "cachetools-5.0.0.tar.gz", hash = "sha256:486471dfa8799eb7ec503a8059e263db000cdda20075ce5e48903087f79d5fd6"},
|
||||
]
|
||||
cattrs = [
|
||||
{file = "cattrs-1.10.0-py3-none-any.whl", hash = "sha256:35dd9063244263e63bd0bd24ea61e3015b00272cead084b2c40d788b0f857c46"},
|
||||
@ -2353,53 +2368,50 @@ constantly = [
|
||||
{file = "constantly-15.1.0.tar.gz", hash = "sha256:586372eb92059873e29eba4f9dec8381541b4d3834660707faf8ba59146dfc35"},
|
||||
]
|
||||
coverage = [
|
||||
{file = "coverage-6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6dbc1536e105adda7a6312c778f15aaabe583b0e9a0b0a324990334fd458c94b"},
|
||||
{file = "coverage-6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:174cf9b4bef0db2e8244f82059a5a72bd47e1d40e71c68ab055425172b16b7d0"},
|
||||
{file = "coverage-6.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:92b8c845527eae547a2a6617d336adc56394050c3ed8a6918683646328fbb6da"},
|
||||
{file = "coverage-6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c7912d1526299cb04c88288e148c6c87c0df600eca76efd99d84396cfe00ef1d"},
|
||||
{file = "coverage-6.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5d2033d5db1d58ae2d62f095e1aefb6988af65b4b12cb8987af409587cc0739"},
|
||||
{file = "coverage-6.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3feac4084291642165c3a0d9eaebedf19ffa505016c4d3db15bfe235718d4971"},
|
||||
{file = "coverage-6.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:276651978c94a8c5672ea60a2656e95a3cce2a3f31e9fb2d5ebd4c215d095840"},
|
||||
{file = "coverage-6.2-cp310-cp310-win32.whl", hash = "sha256:f506af4f27def639ba45789fa6fde45f9a217da0be05f8910458e4557eed020c"},
|
||||
{file = "coverage-6.2-cp310-cp310-win_amd64.whl", hash = "sha256:3f7c17209eef285c86f819ff04a6d4cbee9b33ef05cbcaae4c0b4e8e06b3ec8f"},
|
||||
{file = "coverage-6.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:13362889b2d46e8d9f97c421539c97c963e34031ab0cb89e8ca83a10cc71ac76"},
|
||||
{file = "coverage-6.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22e60a3ca5acba37d1d4a2ee66e051f5b0e1b9ac950b5b0cf4aa5366eda41d47"},
|
||||
{file = "coverage-6.2-cp311-cp311-win_amd64.whl", hash = "sha256:b637c57fdb8be84e91fac60d9325a66a5981f8086c954ea2772efe28425eaf64"},
|
||||
{file = "coverage-6.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f467bbb837691ab5a8ca359199d3429a11a01e6dfb3d9dcc676dc035ca93c0a9"},
|
||||
{file = "coverage-6.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2641f803ee9f95b1f387f3e8f3bf28d83d9b69a39e9911e5bfee832bea75240d"},
|
||||
{file = "coverage-6.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1219d760ccfafc03c0822ae2e06e3b1248a8e6d1a70928966bafc6838d3c9e48"},
|
||||
{file = "coverage-6.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9a2b5b52be0a8626fcbffd7e689781bf8c2ac01613e77feda93d96184949a98e"},
|
||||
{file = "coverage-6.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8e2c35a4c1f269704e90888e56f794e2d9c0262fb0c1b1c8c4ee44d9b9e77b5d"},
|
||||
{file = "coverage-6.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5d6b09c972ce9200264c35a1d53d43ca55ef61836d9ec60f0d44273a31aa9f17"},
|
||||
{file = "coverage-6.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e3db840a4dee542e37e09f30859f1612da90e1c5239a6a2498c473183a50e781"},
|
||||
{file = "coverage-6.2-cp36-cp36m-win32.whl", hash = "sha256:4e547122ca2d244f7c090fe3f4b5a5861255ff66b7ab6d98f44a0222aaf8671a"},
|
||||
{file = "coverage-6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:01774a2c2c729619760320270e42cd9e797427ecfddd32c2a7b639cdc481f3c0"},
|
||||
{file = "coverage-6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb8b8ee99b3fffe4fd86f4c81b35a6bf7e4462cba019997af2fe679365db0c49"},
|
||||
{file = "coverage-6.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:619346d57c7126ae49ac95b11b0dc8e36c1dd49d148477461bb66c8cf13bb521"},
|
||||
{file = "coverage-6.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0a7726f74ff63f41e95ed3a89fef002916c828bb5fcae83b505b49d81a066884"},
|
||||
{file = "coverage-6.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cfd9386c1d6f13b37e05a91a8583e802f8059bebfccde61a418c5808dea6bbfa"},
|
||||
{file = "coverage-6.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:17e6c11038d4ed6e8af1407d9e89a2904d573be29d51515f14262d7f10ef0a64"},
|
||||
{file = "coverage-6.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c254b03032d5a06de049ce8bca8338a5185f07fb76600afff3c161e053d88617"},
|
||||
{file = "coverage-6.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dca38a21e4423f3edb821292e97cec7ad38086f84313462098568baedf4331f8"},
|
||||
{file = "coverage-6.2-cp37-cp37m-win32.whl", hash = "sha256:600617008aa82032ddeace2535626d1bc212dfff32b43989539deda63b3f36e4"},
|
||||
{file = "coverage-6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:bf154ba7ee2fd613eb541c2bc03d3d9ac667080a737449d1a3fb342740eb1a74"},
|
||||
{file = "coverage-6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9afb5b746781fc2abce26193d1c817b7eb0e11459510fba65d2bd77fe161d9e"},
|
||||
{file = "coverage-6.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edcada2e24ed68f019175c2b2af2a8b481d3d084798b8c20d15d34f5c733fa58"},
|
||||
{file = "coverage-6.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a9c8c4283e17690ff1a7427123ffb428ad6a52ed720d550e299e8291e33184dc"},
|
||||
{file = "coverage-6.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f614fc9956d76d8a88a88bb41ddc12709caa755666f580af3a688899721efecd"},
|
||||
{file = "coverage-6.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9365ed5cce5d0cf2c10afc6add145c5037d3148585b8ae0e77cc1efdd6aa2953"},
|
||||
{file = "coverage-6.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8bdfe9ff3a4ea37d17f172ac0dff1e1c383aec17a636b9b35906babc9f0f5475"},
|
||||
{file = "coverage-6.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:63c424e6f5b4ab1cf1e23a43b12f542b0ec2e54f99ec9f11b75382152981df57"},
|
||||
{file = "coverage-6.2-cp38-cp38-win32.whl", hash = "sha256:49dbff64961bc9bdd2289a2bda6a3a5a331964ba5497f694e2cbd540d656dc1c"},
|
||||
{file = "coverage-6.2-cp38-cp38-win_amd64.whl", hash = "sha256:9a29311bd6429be317c1f3fe4bc06c4c5ee45e2fa61b2a19d4d1d6111cb94af2"},
|
||||
{file = "coverage-6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03b20e52b7d31be571c9c06b74746746d4eb82fc260e594dc662ed48145e9efd"},
|
||||
{file = "coverage-6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:215f8afcc02a24c2d9a10d3790b21054b58d71f4b3c6f055d4bb1b15cecce685"},
|
||||
{file = "coverage-6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a4bdeb0a52d1d04123b41d90a4390b096f3ef38eee35e11f0b22c2d031222c6c"},
|
||||
{file = "coverage-6.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c332d8f8d448ded473b97fefe4a0983265af21917d8b0cdcb8bb06b2afe632c3"},
|
||||
{file = "coverage-6.2-cp39-cp39-win32.whl", hash = "sha256:6e1394d24d5938e561fbeaa0cd3d356207579c28bd1792f25a068743f2d5b282"},
|
||||
{file = "coverage-6.2-cp39-cp39-win_amd64.whl", hash = "sha256:86f2e78b1eff847609b1ca8050c9e1fa3bd44ce755b2ec30e70f2d3ba3844644"},
|
||||
{file = "coverage-6.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:5829192582c0ec8ca4a2532407bc14c2f338d9878a10442f5d03804a95fac9de"},
|
||||
{file = "coverage-6.2.tar.gz", hash = "sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8"},
|
||||
{file = "coverage-6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e8071e7d9ba9f457fc674afc3de054450be2c9b195c470147fbbc082468d8ff7"},
|
||||
{file = "coverage-6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86c91c511853dfda81c2cf2360502cb72783f4b7cebabef27869f00cbe1db07d"},
|
||||
{file = "coverage-6.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c4ce3b647bd1792d4394f5690d9df6dc035b00bcdbc5595099c01282a59ae01"},
|
||||
{file = "coverage-6.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a491e159294d756e7fc8462f98175e2d2225e4dbe062cca7d3e0d5a75ba6260"},
|
||||
{file = "coverage-6.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d008e0f67ac800b0ca04d7914b8501312c8c6c00ad8c7ba17754609fae1231a"},
|
||||
{file = "coverage-6.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4578728c36de2801c1deb1c6b760d31883e62e33f33c7ba8f982e609dc95167d"},
|
||||
{file = "coverage-6.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7ee317486593193e066fc5e98ac0ce712178c21529a85c07b7cb978171f25d53"},
|
||||
{file = "coverage-6.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2bc85664b06ba42d14bb74d6ddf19d8bfc520cb660561d2d9ce5786ae72f71b5"},
|
||||
{file = "coverage-6.3-cp310-cp310-win32.whl", hash = "sha256:27a94db5dc098c25048b0aca155f5fac674f2cf1b1736c5272ba28ead2fc267e"},
|
||||
{file = "coverage-6.3-cp310-cp310-win_amd64.whl", hash = "sha256:bde4aeabc0d1b2e52c4036c54440b1ad05beeca8113f47aceb4998bb7471e2c2"},
|
||||
{file = "coverage-6.3-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:509c68c3e2015022aeda03b003dd68fa19987cdcf64e9d4edc98db41cfc45d30"},
|
||||
{file = "coverage-6.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e4ff163602c5c77e7bb4ea81ba5d3b793b4419f8acd296aae149370902cf4e92"},
|
||||
{file = "coverage-6.3-cp311-cp311-win_amd64.whl", hash = "sha256:d1675db48490e5fa0b300f6329ecb8a9a37c29b9ab64fa9c964d34111788ca2d"},
|
||||
{file = "coverage-6.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7eed8459a2b81848cafb3280b39d7d49950d5f98e403677941c752e7e7ee47cb"},
|
||||
{file = "coverage-6.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b4285fde5286b946835a1a53bba3ad41ef74285ba9e8013e14b5ea93deaeafc"},
|
||||
{file = "coverage-6.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4748349734110fd32d46ff8897b561e6300d8989a494ad5a0a2e4f0ca974fc7"},
|
||||
{file = "coverage-6.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:823f9325283dc9565ba0aa2d240471a93ca8999861779b2b6c7aded45b58ee0f"},
|
||||
{file = "coverage-6.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:fff16a30fdf57b214778eff86391301c4509e327a65b877862f7c929f10a4253"},
|
||||
{file = "coverage-6.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:da1a428bdbe71f9a8c270c7baab29e9552ac9d0e0cba5e7e9a4c9ee6465d258d"},
|
||||
{file = "coverage-6.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7d82c610a2e10372e128023c5baf9ce3d270f3029fe7274ff5bc2897c68f1318"},
|
||||
{file = "coverage-6.3-cp37-cp37m-win32.whl", hash = "sha256:11e61c5548ecf74ea1f8b059730b049871f0e32b74f88bd0d670c20c819ad749"},
|
||||
{file = "coverage-6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:8e0c3525b1a182c8ffc9bca7e56b521e0c2b8b3e82f033c8e16d6d721f1b54d6"},
|
||||
{file = "coverage-6.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a189036c50dcd56100746139a459f0d27540fef95b09aba03e786540b8feaa5f"},
|
||||
{file = "coverage-6.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:32168001f33025fd756884d56d01adebb34e6c8c0b3395ca8584cdcee9c7c9d2"},
|
||||
{file = "coverage-6.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5d79c9af3f410a2b5acad91258b4ae179ee9c83897eb9de69151b179b0227f5"},
|
||||
{file = "coverage-6.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:85c5fc9029043cf8b07f73fbb0a7ab6d3b717510c3b5642b77058ea55d7cacde"},
|
||||
{file = "coverage-6.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7596aa2f2b8fa5604129cfc9a27ad9beec0a96f18078cb424d029fdd707468d"},
|
||||
{file = "coverage-6.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ce443a3e6df90d692c38762f108fc4c88314bf477689f04de76b3f252e7a351c"},
|
||||
{file = "coverage-6.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:012157499ec4f135fc36cd2177e3d1a1840af9b236cbe80e9a5ccfc83d912a69"},
|
||||
{file = "coverage-6.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0a34d313105cdd0d3644c56df2d743fe467270d6ab93b5d4a347eb9fec8924d6"},
|
||||
{file = "coverage-6.3-cp38-cp38-win32.whl", hash = "sha256:6e78b1e25e5c5695dea012be473e442f7094d066925604be20b30713dbd47f89"},
|
||||
{file = "coverage-6.3-cp38-cp38-win_amd64.whl", hash = "sha256:433b99f7b0613bdcdc0b00cc3d39ed6d756797e3b078d2c43f8a38288520aec6"},
|
||||
{file = "coverage-6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9ed3244b415725f08ca3bdf02ed681089fd95e9465099a21c8e2d9c5d6ca2606"},
|
||||
{file = "coverage-6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab4fc4b866b279740e0d917402f0e9a08683e002f43fa408e9655818ed392196"},
|
||||
{file = "coverage-6.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8582e9280f8d0f38114fe95a92ae8d0790b56b099d728cc4f8a2e14b1c4a18c"},
|
||||
{file = "coverage-6.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c72bb4679283c6737f452eeb9b2a0e570acaef2197ad255fb20162adc80bea76"},
|
||||
{file = "coverage-6.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca29c352389ea27a24c79acd117abdd8a865c6eb01576b6f0990cd9a4e9c9f48"},
|
||||
{file = "coverage-6.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:152cc2624381df4e4e604e21bd8e95eb8059535f7b768c1fb8b8ae0b26f47ab0"},
|
||||
{file = "coverage-6.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:51372e24b1f7143ee2df6b45cff6a721f3abe93b1e506196f3ffa4155c2497f7"},
|
||||
{file = "coverage-6.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:72d9d186508325a456475dd05b1756f9a204c7086b07fffb227ef8cee03b1dc2"},
|
||||
{file = "coverage-6.3-cp39-cp39-win32.whl", hash = "sha256:649df3641eb351cdfd0d5533c92fc9df507b6b2bf48a7ef8c71ab63cbc7b5c3c"},
|
||||
{file = "coverage-6.3-cp39-cp39-win_amd64.whl", hash = "sha256:e67ccd53da5958ea1ec833a160b96357f90859c220a00150de011b787c27b98d"},
|
||||
{file = "coverage-6.3-pp36.pp37.pp38-none-any.whl", hash = "sha256:27ac7cb84538e278e07569ceaaa6f807a029dc194b1c819a9820b9bb5dbf63ab"},
|
||||
{file = "coverage-6.3.tar.gz", hash = "sha256:987a84ff98a309994ca77ed3cc4b92424f824278e48e4bf7d1bb79a63cfe2099"},
|
||||
]
|
||||
cryptography = [
|
||||
{file = "cryptography-36.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:73bc2d3f2444bcfeac67dd130ff2ea598ea5f20b40e36d19821b4df8c9c5037b"},
|
||||
@ -2444,8 +2456,8 @@ deprecated = [
|
||||
{file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"},
|
||||
]
|
||||
django = [
|
||||
{file = "Django-4.0.1-py3-none-any.whl", hash = "sha256:7cd8e8a3ed2bc0dfda05ce1e53a9c81b30eefd7aa350e538a18884475e4d4ce2"},
|
||||
{file = "Django-4.0.1.tar.gz", hash = "sha256:2485eea3cc4c3bae13080dee866ebf90ba9f98d1afe8fda89bfb0eb2e218ef86"},
|
||||
{file = "Django-4.0.2-py3-none-any.whl", hash = "sha256:996495c58bff749232426c88726d8cd38d24c94d7c1d80835aafffa9bc52985a"},
|
||||
{file = "Django-4.0.2.tar.gz", hash = "sha256:110fb58fb12eca59e072ad59fc42d771cd642dd7a2f2416582aa9da7a8ef954a"},
|
||||
]
|
||||
django-dbbackup = [
|
||||
{file = "django-dbbackup-4.0.0b0.tar.gz", hash = "sha256:aca81265dab91b3a73c3a730cf168d946621c28002f1321d4c33910ba7c63634"},
|
||||
@ -2581,16 +2593,16 @@ gitpython = [
|
||||
{file = "GitPython-3.1.26.tar.gz", hash = "sha256:fc8868f63a2e6d268fb25f481995ba185a85a66fcad126f039323ff6635669ee"},
|
||||
]
|
||||
google-auth = [
|
||||
{file = "google-auth-2.3.3.tar.gz", hash = "sha256:d83570a664c10b97a1dc6f8df87e5fdfff012f48f62be131e449c20dfc32630e"},
|
||||
{file = "google_auth-2.3.3-py2.py3-none-any.whl", hash = "sha256:a348a50b027679cb7dae98043ac8dbcc1d7951f06d8387496071a1e05a2465c0"},
|
||||
{file = "google-auth-2.4.0.tar.gz", hash = "sha256:ef6f4827f6a3f9c5ff884616e2ba779acb5d690486fb70ca5e3091ed85ad932a"},
|
||||
{file = "google_auth-2.4.0-py2.py3-none-any.whl", hash = "sha256:d1fad279d9d97e7d6b4a09a53e851ab2ee6d36d5c19547354a3f47a8a6ae41b9"},
|
||||
]
|
||||
gunicorn = [
|
||||
{file = "gunicorn-20.1.0-py3-none-any.whl", hash = "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e"},
|
||||
{file = "gunicorn-20.1.0.tar.gz", hash = "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8"},
|
||||
]
|
||||
h11 = [
|
||||
{file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"},
|
||||
{file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"},
|
||||
{file = "h11-0.13.0-py3-none-any.whl", hash = "sha256:8ddd78563b633ca55346c8cd41ec0af27d3c79931828beffb46ce70a379e7442"},
|
||||
{file = "h11-0.13.0.tar.gz", hash = "sha256:70813c1135087a248a4d38cc0e1a0181ffab2188141a93eaf567940c3957ff06"},
|
||||
]
|
||||
hiredis = [
|
||||
{file = "hiredis-2.0.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b4c8b0bc5841e578d5fb32a16e0c305359b987b850a06964bd5a62739d688048"},
|
||||
@ -2861,78 +2873,65 @@ msgpack = [
|
||||
{file = "msgpack-1.0.3.tar.gz", hash = "sha256:51fdc7fb93615286428ee7758cecc2f374d5ff363bdd884c7ea622a7a327a81e"},
|
||||
]
|
||||
multidict = [
|
||||
{file = "multidict-5.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3822c5894c72e3b35aae9909bef66ec83e44522faf767c0ad39e0e2de11d3b55"},
|
||||
{file = "multidict-5.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:28e6d883acd8674887d7edc896b91751dc2d8e87fbdca8359591a13872799e4e"},
|
||||
{file = "multidict-5.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b61f85101ef08cbbc37846ac0e43f027f7844f3fade9b7f6dd087178caedeee7"},
|
||||
{file = "multidict-5.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9b668c065968c5979fe6b6fa6760bb6ab9aeb94b75b73c0a9c1acf6393ac3bf"},
|
||||
{file = "multidict-5.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:517d75522b7b18a3385726b54a081afd425d4f41144a5399e5abd97ccafdf36b"},
|
||||
{file = "multidict-5.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1b4ac3ba7a97b35a5ccf34f41b5a8642a01d1e55454b699e5e8e7a99b5a3acf5"},
|
||||
{file = "multidict-5.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:df23c83398715b26ab09574217ca21e14694917a0c857e356fd39e1c64f8283f"},
|
||||
{file = "multidict-5.2.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e58a9b5cc96e014ddf93c2227cbdeca94b56a7eb77300205d6e4001805391747"},
|
||||
{file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f76440e480c3b2ca7f843ff8a48dc82446b86ed4930552d736c0bac507498a52"},
|
||||
{file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cfde464ca4af42a629648c0b0d79b8f295cf5b695412451716531d6916461628"},
|
||||
{file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0fed465af2e0eb6357ba95795d003ac0bdb546305cc2366b1fc8f0ad67cc3fda"},
|
||||
{file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:b70913cbf2e14275013be98a06ef4b412329fe7b4f83d64eb70dce8269ed1e1a"},
|
||||
{file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5635bcf1b75f0f6ef3c8a1ad07b500104a971e38d3683167b9454cb6465ac86"},
|
||||
{file = "multidict-5.2.0-cp310-cp310-win32.whl", hash = "sha256:77f0fb7200cc7dedda7a60912f2059086e29ff67cefbc58d2506638c1a9132d7"},
|
||||
{file = "multidict-5.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:9416cf11bcd73c861267e88aea71e9fcc35302b3943e45e1dbb4317f91a4b34f"},
|
||||
{file = "multidict-5.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fd77c8f3cba815aa69cb97ee2b2ef385c7c12ada9c734b0f3b32e26bb88bbf1d"},
|
||||
{file = "multidict-5.2.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98ec9aea6223adf46999f22e2c0ab6cf33f5914be604a404f658386a8f1fba37"},
|
||||
{file = "multidict-5.2.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5283c0a00f48e8cafcecadebfa0ed1dac8b39e295c7248c44c665c16dc1138b"},
|
||||
{file = "multidict-5.2.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5f79c19c6420962eb17c7e48878a03053b7ccd7b69f389d5831c0a4a7f1ac0a1"},
|
||||
{file = "multidict-5.2.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e4a67f1080123de76e4e97a18d10350df6a7182e243312426d508712e99988d4"},
|
||||
{file = "multidict-5.2.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:94b117e27efd8e08b4046c57461d5a114d26b40824995a2eb58372b94f9fca02"},
|
||||
{file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:2e77282fd1d677c313ffcaddfec236bf23f273c4fba7cdf198108f5940ae10f5"},
|
||||
{file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:116347c63ba049c1ea56e157fa8aa6edaf5e92925c9b64f3da7769bdfa012858"},
|
||||
{file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:dc3a866cf6c13d59a01878cd806f219340f3e82eed514485e094321f24900677"},
|
||||
{file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ac42181292099d91217a82e3fa3ce0e0ddf3a74fd891b7c2b347a7f5aa0edded"},
|
||||
{file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:f0bb0973f42ffcb5e3537548e0767079420aefd94ba990b61cf7bb8d47f4916d"},
|
||||
{file = "multidict-5.2.0-cp36-cp36m-win32.whl", hash = "sha256:ea21d4d5104b4f840b91d9dc8cbc832aba9612121eaba503e54eaab1ad140eb9"},
|
||||
{file = "multidict-5.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:e6453f3cbeb78440747096f239d282cc57a2997a16b5197c9bc839099e1633d0"},
|
||||
{file = "multidict-5.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d3def943bfd5f1c47d51fd324df1e806d8da1f8e105cc7f1c76a1daf0f7e17b0"},
|
||||
{file = "multidict-5.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35591729668a303a02b06e8dba0eb8140c4a1bfd4c4b3209a436a02a5ac1de11"},
|
||||
{file = "multidict-5.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8cacda0b679ebc25624d5de66c705bc53dcc7c6f02a7fb0f3ca5e227d80422"},
|
||||
{file = "multidict-5.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:baf1856fab8212bf35230c019cde7c641887e3fc08cadd39d32a421a30151ea3"},
|
||||
{file = "multidict-5.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a43616aec0f0d53c411582c451f5d3e1123a68cc7b3475d6f7d97a626f8ff90d"},
|
||||
{file = "multidict-5.2.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:25cbd39a9029b409167aa0a20d8a17f502d43f2efebfe9e3ac019fe6796c59ac"},
|
||||
{file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a2cbcfbea6dc776782a444db819c8b78afe4db597211298dd8b2222f73e9cd0"},
|
||||
{file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3d2d7d1fff8e09d99354c04c3fd5b560fb04639fd45926b34e27cfdec678a704"},
|
||||
{file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a37e9a68349f6abe24130846e2f1d2e38f7ddab30b81b754e5a1fde32f782b23"},
|
||||
{file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:637c1896497ff19e1ee27c1c2c2ddaa9f2d134bbb5e0c52254361ea20486418d"},
|
||||
{file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9815765f9dcda04921ba467957be543423e5ec6a1136135d84f2ae092c50d87b"},
|
||||
{file = "multidict-5.2.0-cp37-cp37m-win32.whl", hash = "sha256:8b911d74acdc1fe2941e59b4f1a278a330e9c34c6c8ca1ee21264c51ec9b67ef"},
|
||||
{file = "multidict-5.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:380b868f55f63d048a25931a1632818f90e4be71d2081c2338fcf656d299949a"},
|
||||
{file = "multidict-5.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e7d81ce5744757d2f05fc41896e3b2ae0458464b14b5a2c1e87a6a9d69aefaa8"},
|
||||
{file = "multidict-5.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d1d55cdf706ddc62822d394d1df53573d32a7a07d4f099470d3cb9323b721b6"},
|
||||
{file = "multidict-5.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4771d0d0ac9d9fe9e24e33bed482a13dfc1256d008d101485fe460359476065"},
|
||||
{file = "multidict-5.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da7d57ea65744d249427793c042094c4016789eb2562576fb831870f9c878d9e"},
|
||||
{file = "multidict-5.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdd68778f96216596218b4e8882944d24a634d984ee1a5a049b300377878fa7c"},
|
||||
{file = "multidict-5.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ecc99bce8ee42dcad15848c7885197d26841cb24fa2ee6e89d23b8993c871c64"},
|
||||
{file = "multidict-5.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:067150fad08e6f2dd91a650c7a49ba65085303fcc3decbd64a57dc13a2733031"},
|
||||
{file = "multidict-5.2.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:78c106b2b506b4d895ddc801ff509f941119394b89c9115580014127414e6c2d"},
|
||||
{file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e6c4fa1ec16e01e292315ba76eb1d012c025b99d22896bd14a66628b245e3e01"},
|
||||
{file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b227345e4186809d31f22087d0265655114af7cda442ecaf72246275865bebe4"},
|
||||
{file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:06560fbdcf22c9387100979e65b26fba0816c162b888cb65b845d3def7a54c9b"},
|
||||
{file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7878b61c867fb2df7a95e44b316f88d5a3742390c99dfba6c557a21b30180cac"},
|
||||
{file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:246145bff76cc4b19310f0ad28bd0769b940c2a49fc601b86bfd150cbd72bb22"},
|
||||
{file = "multidict-5.2.0-cp38-cp38-win32.whl", hash = "sha256:c30ac9f562106cd9e8071c23949a067b10211917fdcb75b4718cf5775356a940"},
|
||||
{file = "multidict-5.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:f19001e790013ed580abfde2a4465388950728861b52f0da73e8e8a9418533c0"},
|
||||
{file = "multidict-5.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c1ff762e2ee126e6f1258650ac641e2b8e1f3d927a925aafcfde943b77a36d24"},
|
||||
{file = "multidict-5.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bd6c9c50bf2ad3f0448edaa1a3b55b2e6866ef8feca5d8dbec10ec7c94371d21"},
|
||||
{file = "multidict-5.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc66d4016f6e50ed36fb39cd287a3878ffcebfa90008535c62e0e90a7ab713ae"},
|
||||
{file = "multidict-5.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9acb76d5f3dd9421874923da2ed1e76041cb51b9337fd7f507edde1d86535d6"},
|
||||
{file = "multidict-5.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dfc924a7e946dd3c6360e50e8f750d51e3ef5395c95dc054bc9eab0f70df4f9c"},
|
||||
{file = "multidict-5.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32fdba7333eb2351fee2596b756d730d62b5827d5e1ab2f84e6cbb287cc67fe0"},
|
||||
{file = "multidict-5.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b9aad49466b8d828b96b9e3630006234879c8d3e2b0a9d99219b3121bc5cdb17"},
|
||||
{file = "multidict-5.2.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:93de39267c4c676c9ebb2057e98a8138bade0d806aad4d864322eee0803140a0"},
|
||||
{file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f9bef5cff994ca3026fcc90680e326d1a19df9841c5e3d224076407cc21471a1"},
|
||||
{file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:5f841c4f14331fd1e36cbf3336ed7be2cb2a8f110ce40ea253e5573387db7621"},
|
||||
{file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:38ba256ee9b310da6a1a0f013ef4e422fca30a685bcbec86a969bd520504e341"},
|
||||
{file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3bc3b1621b979621cee9f7b09f024ec76ec03cc365e638126a056317470bde1b"},
|
||||
{file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6ee908c070020d682e9b42c8f621e8bb10c767d04416e2ebe44e37d0f44d9ad5"},
|
||||
{file = "multidict-5.2.0-cp39-cp39-win32.whl", hash = "sha256:1c7976cd1c157fa7ba5456ae5d31ccdf1479680dc9b8d8aa28afabc370df42b8"},
|
||||
{file = "multidict-5.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:c9631c642e08b9fff1c6255487e62971d8b8e821808ddd013d8ac058087591ac"},
|
||||
{file = "multidict-5.2.0.tar.gz", hash = "sha256:0dd1c93edb444b33ba2274b66f63def8a327d607c6c790772f448a53b6ea59ce"},
|
||||
{file = "multidict-6.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05c3cca447fd39b566615d7cf918f0e83cd92f0549f8182a6cab6e4729c02566"},
|
||||
{file = "multidict-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7b487ad10f594e78e0e61662ff7643e1adc610919ea8a95dd976126bffc0256f"},
|
||||
{file = "multidict-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d65b5bd50a5e1f4c6427ebcabb5acc62540301de733f81fd549cf7633826fccd"},
|
||||
{file = "multidict-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a542ceca143e19c08715defe133475961d43d7bfb48dbdc3ac506548ec168f18"},
|
||||
{file = "multidict-6.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:af441cfeb3003d96de8acdeb402187bc6a34f96e125618b8275bacfd29a6fad7"},
|
||||
{file = "multidict-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3f97f03d7dbc33e20a73428be93974e82762b393579d59053da1257c35bad13b"},
|
||||
{file = "multidict-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c74c7a058a16b7dd24d030f0e8fe846f7d63f41cfeea89a804969cb129182a8"},
|
||||
{file = "multidict-6.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5210e5eca8f0fc0213da31cbc29587d15195b21f40ffd53ff513126c39be0e2"},
|
||||
{file = "multidict-6.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:af1fdc8d25eb9e9998854e996bc982ae3be733590a9e8d6b179b28fd212ddf71"},
|
||||
{file = "multidict-6.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ccf0aaaef2f8a4cf1973ebe17162b0c3016859eb706d55b328993b8fd8c3515c"},
|
||||
{file = "multidict-6.0.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:1354127c9687d0abbaadfb26e35db247ef1c35f0271e8052afa98b34a9140445"},
|
||||
{file = "multidict-6.0.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:2e82ac157bc0719b9802578964d4387e0d7dd530d1fd8949f3cef1c97f2e9d8a"},
|
||||
{file = "multidict-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2f90eb274732fe0958f2d9d4cc7a608bf218e63dd554a690ccafa30f9d9d1b2d"},
|
||||
{file = "multidict-6.0.1-cp310-cp310-win32.whl", hash = "sha256:593586d7ac76ab7a9f229c49d81136b8c1a78119d948ba2a672f4fa681ad54ce"},
|
||||
{file = "multidict-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:f24b3d4405271d6d1b920261437f3acb7dd1c329b0ac782b8b4e0ee7ad187e5b"},
|
||||
{file = "multidict-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9050a2487f9dee9b0abde090b2620a3ba57190d7fe1dc2932b32b9866f90df9e"},
|
||||
{file = "multidict-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff8004ecfb2cc00cd4f063c5e8b3cfd7e544bf774f17c2a861067444532e4fbc"},
|
||||
{file = "multidict-6.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0b944477c1e7be8bea2ef819157fb01b79493af1a6dba83d315be63db2957af6"},
|
||||
{file = "multidict-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70a5fff576fe9039550f8d6aacaa98ce26c41df97edc577688c563eb25bcbe83"},
|
||||
{file = "multidict-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e545e5922a4ae99e680a96ed88d03007bd4208fc2599ff273eb2086051d3ef74"},
|
||||
{file = "multidict-6.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502c1610c7737697108ba4717274cca01130ce3e23227034b1601fb9fa0a8aa"},
|
||||
{file = "multidict-6.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:caa9f2874519e0fbb19b90380532e59ef9e07d8ed22d8f0e2e69af796bae2c78"},
|
||||
{file = "multidict-6.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7c9213754993e0dcf70a4fffebef502daf9323fa8d56f0485f724bd4c2991667"},
|
||||
{file = "multidict-6.0.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:0a9857789fd76e0394a5373598820bb2ea3dd113e2c0fb9a92248e3dadd81c5c"},
|
||||
{file = "multidict-6.0.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:92ba0e4745937efef33399348f62a2dc98505dde4d0764061a34123a55fe02e9"},
|
||||
{file = "multidict-6.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3424df56debab711f29c965cadb835dc3c702930265eae26f15ac784feb1dddd"},
|
||||
{file = "multidict-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:d13af99bf380567ede69927d3188cfaccfde76e78e336151e42e60fe03ca5c00"},
|
||||
{file = "multidict-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:37228376057f37fb013130e83e7ad0921598760fb75bd8f0da17403390241d20"},
|
||||
{file = "multidict-6.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:1b469534be490ded141d74d7a158db284055b0d04991c2634a9d26d906e063dd"},
|
||||
{file = "multidict-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6ee83096d27dfe52075385f5067d2a54fb227b41666b207e6a64f98ce9048266"},
|
||||
{file = "multidict-6.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e19c9c56ddca200f7ffbf376cf2d1a370e8f2e306ec16510a4d90b78a1a0be62"},
|
||||
{file = "multidict-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a6da1575300e24011352c7c620d19b072a2c9498429d1f587e5090621e6f568"},
|
||||
{file = "multidict-6.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48e0919ed5cc5e58c68f40b6516e3f1b0837580a7a8d51d9f99bf0f415d0b73a"},
|
||||
{file = "multidict-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7c10ab3a1ad3188d9a4be01a10c0ddb67d6feaaea538781e73d6ba69fcafbbd"},
|
||||
{file = "multidict-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17289c1ad70e1104ea25f560f6e2941718112d59616482589646aa01fcf4d0d1"},
|
||||
{file = "multidict-6.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a701e9ad52e47b740b5b9aba627ac8fc4ee9e68682f0228fb4a7c4562631ffac"},
|
||||
{file = "multidict-6.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:798dd2d825ce6c0699ce261fda90020562236480192604cfc5be4a315f806810"},
|
||||
{file = "multidict-6.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:564ff7d88b4965cb5211ebf785bca409abbaac8dcca62b0fabe39d56a5ee7283"},
|
||||
{file = "multidict-6.0.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:39d5424379505110d6ca64e927f8b2772c57b05a3d240e66805244ecc4402311"},
|
||||
{file = "multidict-6.0.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:efeba14caaacb4a202d977aca63866069e8a57e632282caf971ce8ff472a49cd"},
|
||||
{file = "multidict-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:241071469989c87da1835f3ccc4143a207b99a2ca27c19b7130a5edecc41a39e"},
|
||||
{file = "multidict-6.0.1-cp38-cp38-win32.whl", hash = "sha256:7184dae6d519a8e629ef10e0e215dde022ee38d55254bea381ca0e61b8b197ea"},
|
||||
{file = "multidict-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:666f78daccf8133ebfacb77e81f4077570b03641b49555ebb5e75797b72770e1"},
|
||||
{file = "multidict-6.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5a4fcf74d50a65fe99a68531d09f10ceef911992e0e64c1b2cf212effa075f8b"},
|
||||
{file = "multidict-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4a98ee92e96157a3c787c5aa156968ceffb6fb4548c5f74ff879884fed2f9752"},
|
||||
{file = "multidict-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b6bfb0a4df79bd78907fff46c34ed47284d5eb459b096d2de896b0b1c31f3fa7"},
|
||||
{file = "multidict-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbda7f3be5a75ee87a2649382e47f419374420ef244b372312e662637931399f"},
|
||||
{file = "multidict-6.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f74a680ba9d6c543c1cdd8bcef464d58b933bed29edb89a0b3c0b33cc553181a"},
|
||||
{file = "multidict-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:389a689aa3b657b87cb21f78468caa4dfccd758639ff4b37b6e2dceef7c5b12f"},
|
||||
{file = "multidict-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36195396f2a76dd23a67ca2bdeb8589b6eaaef7e84c97cf90da3ede69f189baa"},
|
||||
{file = "multidict-6.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02847aa3602e21c04ac6fdef6f7c6dc916de8cce49a9eb59aeffedd3365f196c"},
|
||||
{file = "multidict-6.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bd21540c9cc9b90373b5a6aaed8f8d53815529e89210453e0566d00389034b82"},
|
||||
{file = "multidict-6.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e037782357ddf6741023df1a84f17a3a1bb62abba52e41efa4014199f6338143"},
|
||||
{file = "multidict-6.0.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ea08fb7383422148bcfc066c3d2dae03fcdb8bd06e7ce713badd86bc26c25179"},
|
||||
{file = "multidict-6.0.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:b85354b2d2fe38a3ab90348f8c9778a437d376252f62115cabea1106332bd1b6"},
|
||||
{file = "multidict-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9c863a506466d64871c70e4204cecdcaf2c0c92d157683bb5d5b3e7b5aaea05a"},
|
||||
{file = "multidict-6.0.1-cp39-cp39-win32.whl", hash = "sha256:d2cc15d2507b0d5f09726fd97bb9ffaa6bb47487fe77a208262cb6187fd9dcf3"},
|
||||
{file = "multidict-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:38ada537db7f9089560cf16dc5b8b280096213bc5260970929aca43675682739"},
|
||||
{file = "multidict-6.0.1.tar.gz", hash = "sha256:d40616f3f9326a18e1f2fa7c7e8e0e04a7e5228bfa2bd62c1e0d68fbc259b09b"},
|
||||
]
|
||||
mypy-extensions = [
|
||||
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
|
||||
@ -3075,36 +3074,36 @@ pycparser = [
|
||||
{file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
|
||||
]
|
||||
pycryptodome = [
|
||||
{file = "pycryptodome-3.12.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:90ad3381ccdc6a24cc2841e295706a168f32abefe64c679695712acac71fd5da"},
|
||||
{file = "pycryptodome-3.12.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e80f7469b0b3ea0f694230477d8501dc5a30a717e94fddd4821e6721f3053eae"},
|
||||
{file = "pycryptodome-3.12.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:b91404611767a7485837a6f1fd20cf9a5ae0ad362040a022cd65827ecb1b0d00"},
|
||||
{file = "pycryptodome-3.12.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:db66ccda65d5d20c17b00768e462a86f6f540f9aea8419a7f76cc7d9effd82cd"},
|
||||
{file = "pycryptodome-3.12.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:dc88355c4b261ed259268e65705b28b44d99570337694d593f06e3b1698eaaf3"},
|
||||
{file = "pycryptodome-3.12.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:6f8f5b7b53516da7511951910ab458e799173722c91fea54e2ba2f56d102e4aa"},
|
||||
{file = "pycryptodome-3.12.0-cp27-cp27m-win32.whl", hash = "sha256:93acad54a72d81253242eb0a15064be559ec9d989e5173286dc21cad19f01765"},
|
||||
{file = "pycryptodome-3.12.0-cp27-cp27m-win_amd64.whl", hash = "sha256:5a8c24d39d4a237dbfe181ea6593792bf9b5582c7fcfa7b8e0e12fda5eec07af"},
|
||||
{file = "pycryptodome-3.12.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:32d15da81959faea6cbed95df2bb44f7f796211c110cf90b5ad3b2aeeb97fc8e"},
|
||||
{file = "pycryptodome-3.12.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:aed7eb4b64c600fbc5e6d4238991ad1b4179a558401f203d1fcbd24883748982"},
|
||||
{file = "pycryptodome-3.12.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:341c6bbf932c406b4f3ee2372e8589b67ac0cf4e99e7dc081440f43a3cde9f0f"},
|
||||
{file = "pycryptodome-3.12.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:de0b711d673904dd6c65307ead36cb76622365a393569bf880895cba21195b7a"},
|
||||
{file = "pycryptodome-3.12.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:3558616f45d8584aee3eba27559bc6fd0ba9be6c076610ed3cc62bd5229ffdc3"},
|
||||
{file = "pycryptodome-3.12.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:a78e4324e566b5fbc2b51e9240950d82fa9e1c7eb77acdf27f58712f65622c1d"},
|
||||
{file = "pycryptodome-3.12.0-cp35-abi3-manylinux1_i686.whl", hash = "sha256:3f2f3dd596c6128d91314e60a6bcf4344610ef0e97f4ae4dd1770f86dd0748d8"},
|
||||
{file = "pycryptodome-3.12.0-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:e05f994f30f1cda3cbe57441f41220d16731cf99d868bb02a8f6484c454c206b"},
|
||||
{file = "pycryptodome-3.12.0-cp35-abi3-manylinux2010_i686.whl", hash = "sha256:4cded12e13785bbdf4ba1ff5fb9d261cd98162145f869e4fbc4a4b9083392f0b"},
|
||||
{file = "pycryptodome-3.12.0-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:1181c90d1a6aee68a84826825548d0db1b58d8541101f908d779d601d1690586"},
|
||||
{file = "pycryptodome-3.12.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:6bb0d340c93bcb674ea8899e2f6408ec64c6c21731a59481332b4b2a8143cc60"},
|
||||
{file = "pycryptodome-3.12.0-cp35-abi3-win32.whl", hash = "sha256:39da5807aa1ff820799c928f745f89432908bf6624b9e981d2d7f9e55d91b860"},
|
||||
{file = "pycryptodome-3.12.0-cp35-abi3-win_amd64.whl", hash = "sha256:212c7f7fe11cad9275fbcff50ca977f1c6643f13560d081e7b0f70596df447b8"},
|
||||
{file = "pycryptodome-3.12.0-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:b07a4238465eb8c65dd5df2ab8ba6df127e412293c0ed7656c003336f557a100"},
|
||||
{file = "pycryptodome-3.12.0-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:a6e1bcd9d5855f1a3c0f8d585f44c81b08f39a02754007f374fb8db9605ba29c"},
|
||||
{file = "pycryptodome-3.12.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:aceb1d217c3a025fb963849071446cf3aca1353282fe1c3cb7bd7339a4d47947"},
|
||||
{file = "pycryptodome-3.12.0-pp27-pypy_73-win32.whl", hash = "sha256:f699360ae285fcae9c8f53ca6acf33796025a82bb0ccd7c1c551b04c1726def3"},
|
||||
{file = "pycryptodome-3.12.0-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d845c587ceb82ac7cbac7d0bf8c62a1a0fe7190b028b322da5ca65f6e5a18b9e"},
|
||||
{file = "pycryptodome-3.12.0-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:d8083de50f6dec56c3c6f270fb193590999583a1b27c9c75bc0b5cac22d438cc"},
|
||||
{file = "pycryptodome-3.12.0-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:9ea2f6674c803602a7c0437fccdc2ea036707e60456974fe26ca263bd501ec45"},
|
||||
{file = "pycryptodome-3.12.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:5d4264039a2087977f50072aaff2346d1c1c101cb359f9444cf92e3d1f42b4cd"},
|
||||
{file = "pycryptodome-3.12.0.zip", hash = "sha256:12c7343aec5a3b3df5c47265281b12b611f26ec9367b6129199d67da54b768c1"},
|
||||
{file = "pycryptodome-3.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:bd800856e6dea6924504795ae4ec0d822e912e0a9a215e73b77b585c4d15a0f7"},
|
||||
{file = "pycryptodome-3.14.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:625f78ad69aa3c45e19b85b9e9cae3a30aa4a1de6b908981a63426b88e860489"},
|
||||
{file = "pycryptodome-3.14.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:a1c116dd7a00aac631f67920912fd8ef7a5ad3402cd4d497c6f5cc6b8115747b"},
|
||||
{file = "pycryptodome-3.14.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:0d0b6cca6b707b2c7cd4177c2d3cd950efa959ed8f01c30e676f102c68156f00"},
|
||||
{file = "pycryptodome-3.14.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9d939a257117cc8c6840ad69f149b3ca5e07268cfe0429bd9feec0f91da2343d"},
|
||||
{file = "pycryptodome-3.14.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:41dbb8c2129d43f34ed555cbd365d5e8f023ef0f9238fd9cd0302086b15a38b3"},
|
||||
{file = "pycryptodome-3.14.0-cp27-cp27m-win32.whl", hash = "sha256:9b454af09914807cef1222d100a8c523737a160347cb8d699facc4bdfb9fe725"},
|
||||
{file = "pycryptodome-3.14.0-cp27-cp27m-win_amd64.whl", hash = "sha256:95bac6e55411650933f3b615e57bf0966bf08f3ce07c01f07482ced95f18cbec"},
|
||||
{file = "pycryptodome-3.14.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:0ffbca43c1788243421a8583d85acb59f4cd0b82b001c485fdc3fbfd8fd0804f"},
|
||||
{file = "pycryptodome-3.14.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:69b85d78f7db628370d2cc87f1c41a449f6460896ba95f412173618f75027c2c"},
|
||||
{file = "pycryptodome-3.14.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:bba348d2823315ab8ebe44f0b2fc2ff8dfac8de881713a08def3dadcfc8e92bb"},
|
||||
{file = "pycryptodome-3.14.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:7d667daa851b1f9a20f2b5cad3cff13fba5204bc2f857d12f27c25b178d8629b"},
|
||||
{file = "pycryptodome-3.14.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:74918d5de06b12fef2255135bede41307a5f7b929b145ad867111525aea075dc"},
|
||||
{file = "pycryptodome-3.14.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c2b6faabd09d2876f9050f8af5d78046d81fe856f99e801c2ddab85b59602007"},
|
||||
{file = "pycryptodome-3.14.0-cp35-abi3-manylinux1_i686.whl", hash = "sha256:22a8629315c76d2bec57bc4fd67eb7e01664c3e3b9579df40f530ee5821db1de"},
|
||||
{file = "pycryptodome-3.14.0-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:7e3851e4fbbab72d9b30f98a504f450cc61e497e8e4b0be8205dc198703eee4d"},
|
||||
{file = "pycryptodome-3.14.0-cp35-abi3-manylinux2010_i686.whl", hash = "sha256:9006f17944efaacc3be364c01c2253c00a00f0b5fa5a1a85a1191efd861e764d"},
|
||||
{file = "pycryptodome-3.14.0-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:8f0da308fca149b4c4da78e1388f82d8dd167e0ce12992a44f81b506cede3109"},
|
||||
{file = "pycryptodome-3.14.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:d186e34747985fbd94df7ed4d621f8377165053a06872314c2a594af34741655"},
|
||||
{file = "pycryptodome-3.14.0-cp35-abi3-win32.whl", hash = "sha256:2ed4da8f8afe44895c1f49ae1141a55b15d81dc745b5baa7b7a7265d7b40b81e"},
|
||||
{file = "pycryptodome-3.14.0-cp35-abi3-win_amd64.whl", hash = "sha256:11167a1f892283e5320feb5e81589fd041a1822b94c047820f00bc03eb98a9f7"},
|
||||
{file = "pycryptodome-3.14.0-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:1714ea5f83bcff25e8ae4640e22359d7a0815157a29d9f4eebc2b9e975a3cda0"},
|
||||
{file = "pycryptodome-3.14.0-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:3a011b9fe674bd21056613e88a3e660c56f1b47263138ebf420aa3ee4b8b0107"},
|
||||
{file = "pycryptodome-3.14.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:3fd50e3682ac3a684ace5b90ba1aef8090a78eeadf38c1ec385aad3a599cfd68"},
|
||||
{file = "pycryptodome-3.14.0-pp27-pypy_73-win32.whl", hash = "sha256:08be50d4195edd595df580077bbeec5599d0e5aa0cc468083178ae870e0b29f4"},
|
||||
{file = "pycryptodome-3.14.0-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:16c171dd969c9046b7b304c6ba0c643624dcf18093a66bd30b8b091703f177a2"},
|
||||
{file = "pycryptodome-3.14.0-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:89bb56cfd1fb74663842710bc41a6be26dafceb60eb8d432536891aea08a3740"},
|
||||
{file = "pycryptodome-3.14.0-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:c30a98c8718ae93d44680a7038adb484a520319860747ba43b6cd0a20f6b5984"},
|
||||
{file = "pycryptodome-3.14.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:e972f566ef7b821c8b958dab64174afa072f8271b779e32444ad7c127b0a84b2"},
|
||||
{file = "pycryptodome-3.14.0.tar.gz", hash = "sha256:ceea92a4b8ba6c50d8d70f2efbb4ea14b002dac4160ce4dda33f1b7442f8158a"},
|
||||
]
|
||||
pyjwt = [
|
||||
{file = "PyJWT-2.3.0-py3-none-any.whl", hash = "sha256:e0c4bb8d9f0af0c7f5b1ec4c5036309617d03d56932877f2f7a0beeb5318322f"},
|
||||
@ -3139,8 +3138,8 @@ pyopenssl = [
|
||||
{file = "pyOpenSSL-21.0.0.tar.gz", hash = "sha256:5e2d8c5e46d0d865ae933bef5230090bdaf5506281e9eec60fa250ee80600cb3"},
|
||||
]
|
||||
pyparsing = [
|
||||
{file = "pyparsing-3.0.6-py3-none-any.whl", hash = "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4"},
|
||||
{file = "pyparsing-3.0.6.tar.gz", hash = "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81"},
|
||||
{file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"},
|
||||
{file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"},
|
||||
]
|
||||
pyrsistent = [
|
||||
{file = "pyrsistent-0.18.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:df46c854f490f81210870e509818b729db4488e1f30f2a1ce1698b2295a878d1"},
|
||||
@ -3251,9 +3250,8 @@ requests-mock = [
|
||||
{file = "requests_mock-1.9.3-py2.py3-none-any.whl", hash = "sha256:0a2d38a117c08bb78939ec163522976ad59a6b7fdd82b709e23bb98004a44970"},
|
||||
]
|
||||
requests-oauthlib = [
|
||||
{file = "requests-oauthlib-1.3.0.tar.gz", hash = "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a"},
|
||||
{file = "requests_oauthlib-1.3.0-py2.py3-none-any.whl", hash = "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d"},
|
||||
{file = "requests_oauthlib-1.3.0-py3.7.egg", hash = "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc"},
|
||||
{file = "requests-oauthlib-1.3.1.tar.gz", hash = "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a"},
|
||||
{file = "requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5"},
|
||||
]
|
||||
rsa = [
|
||||
{file = "rsa-4.8-py3-none-any.whl", hash = "sha256:95c5d300c4e879ee69708c428ba566c59478fd653cc3a22243eeb8ed846950bb"},
|
||||
@ -3266,7 +3264,10 @@ s3transfer = [
|
||||
selenium = [
|
||||
{file = "selenium-4.1.0-py3-none-any.whl", hash = "sha256:27e7b64df961d609f3d57237caa0df123abbbe22d038f2ec9e332fb90ec1a939"},
|
||||
]
|
||||
sentry-sdk = []
|
||||
sentry-sdk = [
|
||||
{file = "sentry-sdk-1.5.4.tar.gz", hash = "sha256:f7e54567937ebcbe938c4df1075ec891587faeb7c74184b88cf2894e47c86116"},
|
||||
{file = "sentry_sdk-1.5.4-py2.py3-none-any.whl", hash = "sha256:4fc7960a82c95d906a0514cf4d9aacba1743eb9863a5b7c2a01c525a7d9b21e6"},
|
||||
]
|
||||
service-identity = [
|
||||
{file = "service-identity-21.1.0.tar.gz", hash = "sha256:6e6c6086ca271dc11b033d17c3a8bea9f24ebff920c587da090afc9519419d34"},
|
||||
{file = "service_identity-21.1.0-py2.py3-none-any.whl", hash = "sha256:f0b0caac3d40627c3c04d7a51b6e06721857a0e10a8775f2d1d7e72901b3a7db"},
|
||||
@ -3405,8 +3406,8 @@ urllib3 = [
|
||||
{file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"},
|
||||
]
|
||||
uvicorn = [
|
||||
{file = "uvicorn-0.17.0-py3-none-any.whl", hash = "sha256:0b89c91bb8fe84c4bded9996af13c4b8c0de799d29bffeaa0c8ad298f2be0934"},
|
||||
{file = "uvicorn-0.17.0.tar.gz", hash = "sha256:192c2422b056a3beb512c6c260bf77a7a884204a4ae41856719c1913ead63bbb"},
|
||||
{file = "uvicorn-0.17.3-py3-none-any.whl", hash = "sha256:3ab1bf48aa512692db93a91c514576a0739a9d3522827e1656a172ee87118fa5"},
|
||||
{file = "uvicorn-0.17.3.tar.gz", hash = "sha256:3cebddac78c7dd6bfce2f8f838c2c9b0cfcf3dddf417315ba62378ebe7e8fae2"},
|
||||
]
|
||||
uvloop = [
|
||||
{file = "uvloop-0.16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6224f1401025b748ffecb7a6e2652b17768f30b1a6a3f7b44660e5b5b690b12d"},
|
||||
|
@ -14,7 +14,7 @@ pythonPlatform = "Linux"
|
||||
|
||||
[tool.black]
|
||||
line-length = 100
|
||||
target-version = ['py39']
|
||||
target-version = ['py310']
|
||||
exclude = 'node_modules'
|
||||
|
||||
[tool.isort]
|
||||
@ -92,7 +92,7 @@ addopts = "-p no:celery --junitxml=unittest.xml"
|
||||
|
||||
[tool.poetry]
|
||||
name = "authentik"
|
||||
version = "2022.1.1"
|
||||
version = "2022.1.5"
|
||||
description = ""
|
||||
authors = ["Jens Langhammer <jens.langhammer@beryju.org>"]
|
||||
|
||||
@ -135,7 +135,7 @@ pyjwt = "*"
|
||||
python = "^3.10"
|
||||
pyyaml = "*"
|
||||
requests-oauthlib = "*"
|
||||
sentry-sdk = { git = 'https://github.com/beryju/sentry-python.git', rev = '379aee28b15d3b87b381317746c4efd24b3d7bc3' }
|
||||
sentry-sdk = "*"
|
||||
service_identity = "*"
|
||||
structlog = "*"
|
||||
swagger-spec-validator = "*"
|
||||
|
@ -1,7 +1,7 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: authentik
|
||||
version: 2022.1.1
|
||||
version: 2022.1.5
|
||||
description: Making authentication simple.
|
||||
contact:
|
||||
email: hello@beryju.org
|
||||
|
@ -3,7 +3,11 @@ from authentik.lib.generators import generate_id
|
||||
from yaml import safe_dump
|
||||
|
||||
with open("local.env.yml", "w") as _config:
|
||||
safe_dump({
|
||||
"log_level": "debug",
|
||||
"secret_key": generate_id(),
|
||||
}, _config, default_flow_style=False)
|
||||
safe_dump(
|
||||
{
|
||||
"log_level": "debug",
|
||||
"secret_key": generate_id(),
|
||||
},
|
||||
_config,
|
||||
default_flow_style=False,
|
||||
)
|
||||
|
@ -13,7 +13,10 @@ if os.environ.get(env_pr_branch, "") != "":
|
||||
should_build = str(os.environ.get("DOCKER_USERNAME", "") != "").lower()
|
||||
|
||||
print("##[set-output name=branchName]%s" % branch_name)
|
||||
print("##[set-output name=branchNameContainer]%s" % branch_name.replace("refs/heads/", "").replace("/", "-"))
|
||||
print(
|
||||
"##[set-output name=branchNameContainer]%s"
|
||||
% branch_name.replace("refs/heads/", "").replace("/", "-")
|
||||
)
|
||||
print("##[set-output name=timestamp]%s" % int(time()))
|
||||
print("##[set-output name=sha]%s" % os.environ[sha])
|
||||
print("##[set-output name=shouldBuild]%s" % should_build)
|
||||
|
@ -2,20 +2,15 @@
|
||||
from json import loads, dumps
|
||||
|
||||
TSCONFIG_ESM = {
|
||||
"compilerOptions": {
|
||||
"declaration": True,
|
||||
"target": "es6",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "./dist/esm",
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
]
|
||||
},
|
||||
"exclude": [
|
||||
"dist",
|
||||
"node_modules"
|
||||
]
|
||||
"compilerOptions": {
|
||||
"declaration": True,
|
||||
"target": "es6",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "./dist/esm",
|
||||
"typeRoots": ["node_modules/@types"],
|
||||
},
|
||||
"exclude": ["dist", "node_modules"],
|
||||
}
|
||||
|
||||
|
||||
@ -24,7 +19,7 @@ with open("web-api/package.json", encoding="utf-8") as _package:
|
||||
package["license"] = "GPL-3.0-only"
|
||||
package["module"] = "./dist/esm/index.js"
|
||||
package["sideEffects"] = False
|
||||
package["scripts"]["build"] = "tsc && tsc --project tsconfig.esm.json"
|
||||
package["scripts"]["build"] = "tsc && tsc --project tsconfig.esm.json"
|
||||
|
||||
open("web-api/package.json", "w+", encoding="utf-8").write(dumps(package))
|
||||
open("web-api/tsconfig.esm.json", "w+", encoding="utf-8").write(dumps(TSCONFIG_ESM))
|
||||
|
1025
web/package-lock.json
generated
1025
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -20,7 +20,9 @@
|
||||
"en",
|
||||
"pseudo-LOCALE",
|
||||
"fr_FR",
|
||||
"tr"
|
||||
"tr",
|
||||
"es",
|
||||
"pl"
|
||||
],
|
||||
"formatOptions": {
|
||||
"lineNumbers": false
|
||||
@ -46,20 +48,20 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.16.10",
|
||||
"@babel/core": "^7.16.12",
|
||||
"@babel/plugin-proposal-decorators": "^7.16.7",
|
||||
"@babel/plugin-transform-runtime": "^7.16.10",
|
||||
"@babel/preset-env": "^7.16.11",
|
||||
"@babel/preset-typescript": "^7.16.7",
|
||||
"@formatjs/intl-listformat": "^6.5.0",
|
||||
"@formatjs/intl-listformat": "^6.5.1",
|
||||
"@fortawesome/fontawesome-free": "^5.15.4",
|
||||
"@goauthentik/api": "^2021.12.5-1642024690",
|
||||
"@goauthentik/api": "^2022.1.3-1643236150",
|
||||
"@jackfranklin/rollup-plugin-markdown": "^0.3.0",
|
||||
"@lingui/cli": "^3.13.0",
|
||||
"@lingui/core": "^3.13.0",
|
||||
"@lingui/detect-locale": "^3.13.0",
|
||||
"@lingui/macro": "^3.13.0",
|
||||
"@patternfly/patternfly": "^4.164.2",
|
||||
"@lingui/cli": "^3.13.2",
|
||||
"@lingui/core": "^3.13.2",
|
||||
"@lingui/detect-locale": "^3.13.2",
|
||||
"@lingui/macro": "^3.13.2",
|
||||
"@patternfly/patternfly": "^4.171.1",
|
||||
"@polymer/iron-form": "^3.0.1",
|
||||
"@polymer/paper-input": "^3.2.1",
|
||||
"@rollup/plugin-babel": "^5.3.0",
|
||||
@ -67,34 +69,34 @@
|
||||
"@rollup/plugin-node-resolve": "^13.1.3",
|
||||
"@rollup/plugin-replace": "^3.0.1",
|
||||
"@rollup/plugin-typescript": "^8.3.0",
|
||||
"@sentry/browser": "^6.16.1",
|
||||
"@sentry/tracing": "^6.16.1",
|
||||
"@sentry/browser": "^6.17.3",
|
||||
"@sentry/tracing": "^6.17.3",
|
||||
"@squoosh/cli": "^0.7.2",
|
||||
"@trivago/prettier-plugin-sort-imports": "^3.1.1",
|
||||
"@types/chart.js": "^2.9.35",
|
||||
"@types/codemirror": "5.60.5",
|
||||
"@types/grecaptcha": "^3.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.10.0",
|
||||
"@typescript-eslint/parser": "^5.10.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.10.2",
|
||||
"@typescript-eslint/parser": "^5.10.2",
|
||||
"@webcomponents/webcomponentsjs": "^2.6.0",
|
||||
"babel-plugin-macros": "^3.1.0",
|
||||
"base64-js": "^1.5.1",
|
||||
"chart.js": "^3.7.0",
|
||||
"chartjs-adapter-moment": "^1.0.0",
|
||||
"codemirror": "^5.65.1",
|
||||
"construct-style-sheets-polyfill": "^3.0.5",
|
||||
"country-flag-icons": "^1.4.19",
|
||||
"eslint": "^8.7.0",
|
||||
"construct-style-sheets-polyfill": "^3.1.0",
|
||||
"country-flag-icons": "^1.4.20",
|
||||
"eslint": "^8.8.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"eslint-plugin-custom-elements": "0.0.4",
|
||||
"eslint-plugin-lit": "^1.6.1",
|
||||
"flowchart.js": "^1.17.0",
|
||||
"fuse.js": "^6.5.3",
|
||||
"lit": "^2.1.1",
|
||||
"lit": "^2.1.2",
|
||||
"moment": "^2.29.1",
|
||||
"prettier": "^2.5.1",
|
||||
"rapidoc": "^9.1.3",
|
||||
"rollup": "^2.64.0",
|
||||
"rapidoc": "^9.1.4",
|
||||
"rollup": "^2.66.1",
|
||||
"rollup-plugin-copy": "^3.4.0",
|
||||
"rollup-plugin-cssimport": "^1.0.2",
|
||||
"rollup-plugin-minify-html-literals": "^1.2.6",
|
||||
|
@ -3,7 +3,7 @@ export const SUCCESS_CLASS = "pf-m-success";
|
||||
export const ERROR_CLASS = "pf-m-danger";
|
||||
export const PROGRESS_CLASS = "pf-m-in-progress";
|
||||
export const CURRENT_CLASS = "pf-m-current";
|
||||
export const VERSION = "2022.1.1";
|
||||
export const VERSION = "2022.1.5";
|
||||
export const TITLE_DEFAULT = "authentik";
|
||||
export const ROUTE_SEPARATOR = ";";
|
||||
|
||||
|
@ -113,6 +113,9 @@ export class FlowExecutor extends LitElement implements StageHost {
|
||||
.pf-c-drawer__content {
|
||||
background-color: transparent;
|
||||
}
|
||||
.pf-c-login__main {
|
||||
width: 100%;
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,13 @@
|
||||
import { en, fr, tr } from "make-plural/plurals";
|
||||
import { en, es, fr, pl, tr } from "make-plural/plurals";
|
||||
|
||||
import { Messages, i18n } from "@lingui/core";
|
||||
import { detect, fromNavigator, fromStorage, fromUrl } from "@lingui/detect-locale";
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { messages as localeEN } from "../locales/en";
|
||||
import { messages as localeES } from "../locales/es";
|
||||
import { messages as localeFR_FR } from "../locales/fr_FR";
|
||||
import { messages as localePL } from "../locales/pl";
|
||||
import { messages as localeDEBUG } from "../locales/pseudo-LOCALE";
|
||||
import { messages as localeTR } from "../locales/tr";
|
||||
|
||||
@ -29,7 +31,7 @@ export const LOCALES: {
|
||||
locale: localeDEBUG,
|
||||
},
|
||||
{
|
||||
code: "fr_FR",
|
||||
code: "fr",
|
||||
plurals: fr,
|
||||
label: t`French`,
|
||||
locale: localeFR_FR,
|
||||
@ -40,6 +42,18 @@ export const LOCALES: {
|
||||
label: t`Turkish`,
|
||||
locale: localeTR,
|
||||
},
|
||||
{
|
||||
code: "es",
|
||||
plurals: es,
|
||||
label: t`Spanish`,
|
||||
locale: localeES,
|
||||
},
|
||||
{
|
||||
code: "pl",
|
||||
plurals: pl,
|
||||
label: t`Polish`,
|
||||
locale: localePL,
|
||||
},
|
||||
];
|
||||
|
||||
LOCALES.forEach((locale) => {
|
||||
@ -50,9 +64,13 @@ LOCALES.forEach((locale) => {
|
||||
const DEFAULT_FALLBACK = () => "en";
|
||||
|
||||
export function autoDetectLanguage() {
|
||||
const detected =
|
||||
let detected =
|
||||
detect(fromUrl("lang"), fromStorage("lang"), fromNavigator(), DEFAULT_FALLBACK) ||
|
||||
DEFAULT_FALLBACK();
|
||||
// For now we only care about the first locale part
|
||||
if (detected.includes("_")) {
|
||||
detected = detected.split("_")[0];
|
||||
}
|
||||
if (detected in i18n._messages) {
|
||||
console.debug(`authentik/locale: Activating detected locale '${detected}'`);
|
||||
i18n.activate(detected);
|
||||
|
@ -3608,6 +3608,10 @@ msgstr "Policy {0}"
|
||||
msgid "Policy-specific settings"
|
||||
msgstr "Policy-specific settings"
|
||||
|
||||
#: src/interfaces/locale.ts
|
||||
msgid "Polish"
|
||||
msgstr "Polish"
|
||||
|
||||
#: src/pages/providers/saml/SAMLProviderForm.ts
|
||||
msgid "Post"
|
||||
msgstr "Post"
|
||||
@ -4433,6 +4437,10 @@ msgstr "Sources"
|
||||
msgid "Sources of identities, which can either be synced into authentik's database, or can be used by users to authenticate and enroll themselves."
|
||||
msgstr "Sources of identities, which can either be synced into authentik's database, or can be used by users to authenticate and enroll themselves."
|
||||
|
||||
#: src/interfaces/locale.ts
|
||||
msgid "Spanish"
|
||||
msgstr "Spanish"
|
||||
|
||||
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
||||
msgid "Specify multiple server URIs by separating them with a comma."
|
||||
msgstr "Specify multiple server URIs by separating them with a comma."
|
||||
|
6090
web/src/locales/es.po
Normal file
6090
web/src/locales/es.po
Normal file
File diff suppressed because it is too large
Load Diff
@ -3580,6 +3580,10 @@ msgstr "Poliitique {0}"
|
||||
msgid "Policy-specific settings"
|
||||
msgstr "Paramètres spécifiques à la politique"
|
||||
|
||||
#: src/interfaces/locale.ts
|
||||
msgid "Polish"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/saml/SAMLProviderForm.ts
|
||||
msgid "Post"
|
||||
msgstr "Appliquer"
|
||||
@ -4395,6 +4399,10 @@ msgstr "Sources"
|
||||
msgid "Sources of identities, which can either be synced into authentik's database, or can be used by users to authenticate and enroll themselves."
|
||||
msgstr "Sources d'identités, qui peuvent soit être synchronisées dans la base de données d'Authentik, soit être utilisées par les utilisateurs pour s'authentifier et s'inscrire."
|
||||
|
||||
#: src/interfaces/locale.ts
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
||||
msgid "Specify multiple server URIs by separating them with a comma."
|
||||
msgstr ""
|
||||
|
6090
web/src/locales/pl.po
Normal file
6090
web/src/locales/pl.po
Normal file
File diff suppressed because it is too large
Load Diff
@ -3598,6 +3598,10 @@ msgstr ""
|
||||
msgid "Policy-specific settings"
|
||||
msgstr ""
|
||||
|
||||
#: src/interfaces/locale.ts
|
||||
msgid "Polish"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/saml/SAMLProviderForm.ts
|
||||
msgid "Post"
|
||||
msgstr ""
|
||||
@ -4423,6 +4427,10 @@ msgstr ""
|
||||
msgid "Sources of identities, which can either be synced into authentik's database, or can be used by users to authenticate and enroll themselves."
|
||||
msgstr ""
|
||||
|
||||
#: src/interfaces/locale.ts
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
||||
msgid "Specify multiple server URIs by separating them with a comma."
|
||||
msgstr ""
|
||||
|
@ -3546,6 +3546,10 @@ msgstr "İlke {0}"
|
||||
msgid "Policy-specific settings"
|
||||
msgstr "İlke özel ayarlar"
|
||||
|
||||
#: src/interfaces/locale.ts
|
||||
msgid "Polish"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/saml/SAMLProviderForm.ts
|
||||
msgid "Post"
|
||||
msgstr "Post"
|
||||
@ -4344,6 +4348,10 @@ msgstr "Kaynaklar"
|
||||
msgid "Sources of identities, which can either be synced into authentik's database, or can be used by users to authenticate and enroll themselves."
|
||||
msgstr "Auentik'in veritabanına senkronize edilebilen ya da kullanıcılar tarafından kimlik doğrulaması ve kayıt yaptırmak için kullanılabilen kimliklerin kaynakları."
|
||||
|
||||
#: src/interfaces/locale.ts
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
||||
msgid "Specify multiple server URIs by separating them with a comma."
|
||||
msgstr "Birden çok sunucu URI'lerini virgülle ayırarak belirtin."
|
||||
|
@ -110,7 +110,7 @@ export class AdminOverviewPage extends LitElement {
|
||||
class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-xl pf-m-2-col-on-2xl graph-container"
|
||||
>
|
||||
<ak-aggregate-card
|
||||
icon="pf-icon pf-icon-server"
|
||||
icon="pf-icon pf-icon-process-automation"
|
||||
header=${t`Flows`}
|
||||
headerLink="#/flow/flows"
|
||||
>
|
||||
@ -121,7 +121,7 @@ export class AdminOverviewPage extends LitElement {
|
||||
class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-xl pf-m-2-col-on-2xl graph-container"
|
||||
>
|
||||
<ak-aggregate-card
|
||||
icon="fa fa-sync-alt"
|
||||
icon="pf-icon pf-icon-zone"
|
||||
header=${t`Outpost status`}
|
||||
headerLink="#/outpost/outposts"
|
||||
>
|
||||
@ -132,7 +132,7 @@ export class AdminOverviewPage extends LitElement {
|
||||
class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-xl pf-m-2-col-on-2xl graph-container"
|
||||
>
|
||||
<ak-aggregate-card
|
||||
icon="fa fa-sync-alt"
|
||||
icon="pf-icon pf-icon-user"
|
||||
header=${t`Users`}
|
||||
headerLink="#/identity/users"
|
||||
>
|
||||
@ -143,7 +143,7 @@ export class AdminOverviewPage extends LitElement {
|
||||
class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-xl pf-m-2-col-on-2xl graph-container"
|
||||
>
|
||||
<ak-aggregate-card
|
||||
icon="fa fa-sync-alt"
|
||||
icon="pf-icon pf-icon-users"
|
||||
header=${t`Groups`}
|
||||
headerLink="#/identity/groups"
|
||||
>
|
||||
|
@ -216,6 +216,7 @@ export class SAMLProviderViewPage extends LitElement {
|
||||
${t`Download`}
|
||||
</a>
|
||||
<ak-action-button
|
||||
class="pf-m-secondary"
|
||||
.apiRequest=${() => {
|
||||
const fullUrl =
|
||||
window.location.origin +
|
||||
|
@ -34,7 +34,7 @@ export class AuthenticatorSMSStageForm extends ModelForm<AuthenticatorSMSStage,
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
provider?: ProviderEnum;
|
||||
provider: ProviderEnum = ProviderEnum.Twilio;
|
||||
|
||||
@property({ attribute: false })
|
||||
authType?: AuthTypeEnum;
|
||||
@ -60,6 +60,101 @@ export class AuthenticatorSMSStageForm extends ModelForm<AuthenticatorSMSStage,
|
||||
}
|
||||
};
|
||||
|
||||
renderProviderTwillio(): TemplateResult {
|
||||
return html` <ak-form-element-horizontal
|
||||
label=${t`Twilio Account SID`}
|
||||
?required=${true}
|
||||
name="accountSid"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.accountSid || "")}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Get this value from https://console.twilio.com`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${t`Twilio Auth Token`} ?required=${true} name="auth">
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.auth || "")}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Get this value from https://console.twilio.com`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>`;
|
||||
}
|
||||
|
||||
renderProviderGeneric(): TemplateResult {
|
||||
return html` <ak-form-element-horizontal
|
||||
label=${t`Authentication Type`}
|
||||
@change=${(ev: Event) => {
|
||||
const current = (ev.target as HTMLInputElement).value;
|
||||
this.authType = current as AuthTypeEnum;
|
||||
}}
|
||||
?required=${true}
|
||||
name="authType"
|
||||
>
|
||||
<select class="pf-c-form-control">
|
||||
<option
|
||||
value="${AuthTypeEnum.Basic}"
|
||||
?selected=${this.instance?.authType === AuthTypeEnum.Basic}
|
||||
>
|
||||
${t`Basic Auth`}
|
||||
</option>
|
||||
<option
|
||||
value="${AuthTypeEnum.Bearer}"
|
||||
?selected=${this.instance?.authType === AuthTypeEnum.Bearer}
|
||||
>
|
||||
${t`Bearer Token`}
|
||||
</option>
|
||||
</select>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`External API URL`}
|
||||
?required=${true}
|
||||
name="accountSid"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.accountSid || "")}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`This is the full endpoint to send POST requests to.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${t`API Auth Username`} ?required=${true} name="auth">
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.auth || "")}"
|
||||
class="pf-c-form-control"
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`This is the username to be used with basic auth or the token when used with bearer token`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`API Auth password`}
|
||||
?required=${false}
|
||||
name="authPassword"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.authPassword)}"
|
||||
class="pf-c-form-control"
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`This is the password to be used with basic auth`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>`;
|
||||
}
|
||||
|
||||
renderForm(): TemplateResult {
|
||||
return html`<form class="pf-c-form pf-m-horizontal">
|
||||
<div class="form-help-text">
|
||||
@ -117,113 +212,9 @@ export class AuthenticatorSMSStageForm extends ModelForm<AuthenticatorSMSStage,
|
||||
${t`Number the SMS will be sent from.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Twilio Account SID`}
|
||||
?hidden=${this.provider !== ProviderEnum.Twilio}
|
||||
?required=${true}
|
||||
name="accountSid"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.accountSid || "")}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Get this value from https://console.twilio.com`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Twilio Auth Token`}
|
||||
?hidden=${this.provider !== ProviderEnum.Twilio}
|
||||
?required=${true}
|
||||
name="auth"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.auth || "")}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Get this value from https://console.twilio.com`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Authentication Type`}
|
||||
?hidden=${this.provider !== ProviderEnum.Generic}
|
||||
@change=${(ev: Event) => {
|
||||
const current = (ev.target as HTMLInputElement).value;
|
||||
this.authType = current as AuthTypeEnum;
|
||||
}}
|
||||
?required=${true}
|
||||
name="authType"
|
||||
>
|
||||
<select class="pf-c-form-control">
|
||||
<option
|
||||
value="${AuthTypeEnum.Basic}"
|
||||
?selected=${this.instance?.authType === AuthTypeEnum.Basic}
|
||||
>
|
||||
${t`Basic Auth`}
|
||||
</option>
|
||||
<option
|
||||
value="${AuthTypeEnum.Bearer}"
|
||||
?selected=${this.instance?.authType === AuthTypeEnum.Bearer}
|
||||
>
|
||||
${t`Bearer Token`}
|
||||
</option>
|
||||
</select>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`External API URL`}
|
||||
?hidden=${this.provider !== ProviderEnum.Generic}
|
||||
?required=${true}
|
||||
name="accountSid"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.accountSid || "")}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`This is the full endpoint to send POST requests to.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`API Auth Username`}
|
||||
?hidden=${this.provider !== ProviderEnum.Generic}
|
||||
?required=${true}
|
||||
name="auth"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.auth || "")}"
|
||||
class="pf-c-form-control"
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`This is the username to be used with basic auth or the token when used with bearer token`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`API Auth password`}
|
||||
?hidden=${!(
|
||||
this.provider === ProviderEnum.Generic &&
|
||||
this.authType === AuthTypeEnum.Basic
|
||||
)}
|
||||
?required=${false}
|
||||
name="authPassword"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.authPassword)}"
|
||||
class="pf-c-form-control"
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`This is the password to be used with basic auth`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
${this.provider === ProviderEnum.Generic
|
||||
? this.renderProviderGeneric()
|
||||
: this.renderProviderTwillio()}
|
||||
<ak-form-element-horizontal label=${t`Configuration flow`} name="configureFlow">
|
||||
<select class="pf-c-form-control">
|
||||
<option
|
||||
|
@ -227,6 +227,7 @@ export class UserListPage extends TablePage<User> {
|
||||
}
|
||||
return html`
|
||||
<ak-action-button
|
||||
class="pf-m-secondary"
|
||||
.apiRequest=${() => {
|
||||
return new CoreApi(DEFAULT_CONFIG)
|
||||
.coreUsersRecoveryRetrieve({
|
||||
|
@ -103,7 +103,7 @@ export class UserDetailsForm extends ModelForm<UserSelf, number> {
|
||||
value=${locale.code}
|
||||
?selected=${config.locale === locale.code}
|
||||
>
|
||||
${locale.label}
|
||||
${locale.code.toUpperCase()} - ${locale.label}
|
||||
</option>`;
|
||||
})}
|
||||
</select>
|
||||
|
38
website/developer-docs/docs/writing-documentation.md
Normal file
38
website/developer-docs/docs/writing-documentation.md
Normal file
@ -0,0 +1,38 @@
|
||||
---
|
||||
title: Writing documentation
|
||||
---
|
||||
|
||||
Writing documentation for authentik is a great way for both new and experienced users to improve and contribute to the project. Here are a few guidelines to ensure
|
||||
the documentation is easy to read and uses similar phrasing.
|
||||
|
||||
# General guidelines
|
||||
|
||||
- authentik should always be stylized as `authentik` (with a lower-case a and ending with a k)
|
||||
- Documentation should use American english
|
||||
- Feel free to use Docusaurus-specific features, see [here](https://docusaurus.io/docs/next/markdown-features)
|
||||
- Use abbreviations where it makes sense (for commonly used terms like SAML and OAuth)
|
||||
- Phrasing should never blame the user, and should be subjective, i.e
|
||||
|
||||
- **DON'T** `You may never click x.`
|
||||
- **DO** `x should never be clicked.`
|
||||
|
||||
- When referring to other objects in authentik, use cursive text, and link to the corresponding documentation if possible.
|
||||
- When referring to external tools, give an example how to use the tools or explain how the user can use them.
|
||||
- Make sure to add the documentation to add to the sidebar, if adding a new page.
|
||||
- Test how the documentation renders using the Netlify Preview, especially when using Docusaurus-specific features.
|
||||
|
||||
If you find any documentation that doesn't match these guidelines, feel free to either open an Issue or a PR so they can be fixed.
|
||||
|
||||
## Integration guidelines
|
||||
|
||||
These guidelines apply in addition to the ones above.
|
||||
|
||||
- For placeholders, use angle brackets (`<placeholder-name>`).
|
||||
|
||||
Make sure to also define if the placeholder is something the user needs to define, something another system defines, or randomly generated.
|
||||
|
||||
If you're adding configuration snippets to the documentation, and the snippet is in a language that supports comments,
|
||||
other placeholders may be used, for example comments referencing an earlier step.
|
||||
|
||||
- For placeholder domains, use `authentik.company` and `app-name.company`, where `app-name` is the name of the application you are writing documentation for.
|
||||
- Try to order the documentation in the order that makes it easiest for the user to configure.
|
@ -1,11 +1,15 @@
|
||||
---
|
||||
title: Translation
|
||||
title: Translations
|
||||
---
|
||||
|
||||
Translation in authentik is done in two places. Most of the text is defined in the frontend in `web/`, and a subset of messages is defined in the backend.
|
||||
|
||||
The frontend uses [lingui](https://lingui.js.org/), and the backend uses the built-in django translation tools.
|
||||
|
||||
:::info
|
||||
Please review the [Writing documentation](./docs/writing-documentation) guidelines as they apply to documentation too.
|
||||
:::
|
||||
|
||||
## Online translation
|
||||
|
||||
To simplify translation you can use https://www.transifex.com/beryjuorg/authentik/, which has no local requirements.
|
||||
|
@ -24,6 +24,9 @@ The following aspects can be configured:
|
||||
|
||||
- *Name*: This is the name shown for the application card
|
||||
- *Launch URL*: The URL that is opened when a user clicks on the application. When left empty, authentik tries to guess it based on the provider
|
||||
|
||||
Starting with authentik 2022.2, you can use placeholders in the launch url to build them dynamically based on logged in user. For example, you can set the Launch URL to `https://goauthentik.io/%(username)s`, which will be replaced with the currently logged in user's username.
|
||||
|
||||
- *Icon (URL)*: Optionally configure an Icon for the application
|
||||
- *Publisher*: Text shown below the application
|
||||
- *Description*: Subtext shown on the application card below the publisher
|
||||
|
@ -18,6 +18,17 @@ Example:
|
||||
user_email_local = regex_replace(request.user.email, '(.+)@.+', '')
|
||||
```
|
||||
|
||||
### `list_flatten(value: list[Any] | Any) -> Optional[Any}`
|
||||
|
||||
Flatten a list by either returning its first element, None if the list is empty, or the passed in object if its not a list.
|
||||
|
||||
Example:
|
||||
|
||||
```python
|
||||
user = list_flatten(["foo"])
|
||||
# user = "foo"
|
||||
```
|
||||
|
||||
### `ak_is_group_member(user: User, **group_filters) -> bool`
|
||||
|
||||
Check if `user` is member of a group matching `**group_filters`.
|
||||
|
@ -25,7 +25,11 @@ All of these variables can be set to values, but you can also use a URI-like for
|
||||
|
||||
### PostgreSQL Backup Settings
|
||||
|
||||
- `AUTHENTIK_POSTGRESQL__BACKUP__ENABLED`: Controls if the inbuilt backup-mechanism is enabled, defaults to true (new in 2021.10).
|
||||
:::info
|
||||
The integrated backup is deprecated in 2022.1 and will be removed in a future version.
|
||||
:::
|
||||
|
||||
- `AUTHENTIK_POSTGRESQL__BACKUP__ENABLED`: Controls if the inbuilt backup-mechanism is enabled, defaults to false (new in 2021.10).
|
||||
|
||||
Optionally enable automated database backups to S3 or S3-compatible storages.
|
||||
|
||||
|
@ -2,6 +2,10 @@
|
||||
title: Backup and restore
|
||||
---
|
||||
|
||||
:::error
|
||||
Integrated backups are deprecated and will be removed in a future authentik release.
|
||||
:::
|
||||
|
||||
:::warning
|
||||
Local backups are only supported for docker-compose installs. If you want to backup a Kubernetes instance locally, use an S3-compatible server such as [minio](https://min.io/)
|
||||
:::
|
||||
|
@ -50,6 +50,9 @@ docker_network: null
|
||||
# Optionally disable mapping of ports to outpost container, may be useful when using docker networks
|
||||
# (Available with 2021.9.4+)
|
||||
docker_map_ports: true
|
||||
# Optionally additional labels for docker containers
|
||||
# (Available with 2022.1.2)
|
||||
docker_labels: null
|
||||
########################################
|
||||
# Kubernetes outpost specific settings
|
||||
########################################
|
||||
|
@ -12,6 +12,7 @@ The following outpost settings are used:
|
||||
- `container_image`: Optionally overwrites the standard container image (see [Configuration](../../installation/configuration.md) to configure the global default)
|
||||
- `docker_network`: The docker network the container should be added to. This needs to be modified if you plan to connect to authentik using the internal hostname.
|
||||
- `docker_map_ports`: Enable/disable the mapping of ports. When using a proxy outpost with traefik for example, you might not want to bind ports as they are routed through traefik.
|
||||
- `docker_labels`: Optional additional labels that can be applied to the container.
|
||||
|
||||
The container is created with the following hardcoded properties:
|
||||
|
||||
@ -22,12 +23,12 @@ The container is created with the following hardcoded properties:
|
||||
Additionally, the proxy outposts have the following extra labels to add themselves into traefik automatically.
|
||||
|
||||
- `traefik.enable`: "true"
|
||||
- `traefik.http.routers.ak-outpost-<outpost-id>-router.rule`: `Host(...)`
|
||||
- `traefik.http.routers.ak-outpost-<outpost-id>-router.service`: `ak-outpost-<outpost-id>-service`
|
||||
- `traefik.http.routers.ak-outpost-<outpost-id>-router.tls`: "true"
|
||||
- `traefik.http.services.ak-outpost-<outpost-id>-service.loadbalancer.healthcheck.path`: "/akprox/ping"
|
||||
- `traefik.http.services.ak-outpost-<outpost-id>-service.loadbalancer.healthcheck.port`: "9300"
|
||||
- `traefik.http.services.ak-outpost-<outpost-id>-service.loadbalancer.server.port`: "9000"
|
||||
- `traefik.http.routers.ak-outpost-<outpost-name>-router.rule`: `Host(...)`
|
||||
- `traefik.http.routers.ak-outpost-<outpost-name>-router.service`: `ak-outpost-<outpost-name>-service`
|
||||
- `traefik.http.routers.ak-outpost-<outpost-name>-router.tls`: "true"
|
||||
- `traefik.http.services.ak-outpost-<outpost-name>-service.loadbalancer.healthcheck.path`: "/akprox/ping"
|
||||
- `traefik.http.services.ak-outpost-<outpost-name>-service.loadbalancer.healthcheck.port`: "9300"
|
||||
- `traefik.http.services.ak-outpost-<outpost-name>-service.loadbalancer.server.port`: "9000"
|
||||
|
||||
## Permissions
|
||||
|
||||
|
@ -32,5 +32,5 @@ metadata:
|
||||
nginx.ingress.kubernetes.io/auth-response-headers: |
|
||||
Set-Cookie,X-authentik-username,X-authentik-groups,X-authentik-email,X-authentik-name,X-authentik-uid
|
||||
nginx.ingress.kubernetes.io/auth-snippet: |
|
||||
proxy_set_header X-Forwarded-Host $http_host;
|
||||
proxy_set_header Host $http_host;
|
||||
```
|
||||
|
@ -6,8 +6,6 @@ For Nginx Proxy Manager you can use this snippet
|
||||
# header from upstream' error when trying to access an application protected by goauthentik
|
||||
proxy_buffers 8 16k;
|
||||
proxy_buffer_size 32k;
|
||||
fastcgi_buffers 16 16k;
|
||||
fastcgi_buffer_size 32k;
|
||||
|
||||
location / {
|
||||
# Put your proxy_pass to your application here
|
||||
@ -16,6 +14,8 @@ location / {
|
||||
# authentik-specific config
|
||||
auth_request /akprox/auth/nginx;
|
||||
error_page 401 = @akprox_signin;
|
||||
auth_request_set $auth_cookie $upstream_http_set_cookie;
|
||||
add_header Set-Cookie $auth_cookie;
|
||||
|
||||
# translate headers from the outposts back to the actual upstream
|
||||
auth_request_set $authentik_username $upstream_http_x_authentik_username;
|
||||
@ -37,6 +37,7 @@ location /akprox {
|
||||
# ensure the host of this vserver matches your external URL you've configured
|
||||
# in authentik
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
|
||||
add_header Set-Cookie $auth_cookie;
|
||||
auth_request_set $auth_cookie $upstream_http_set_cookie;
|
||||
}
|
||||
|
@ -13,8 +13,6 @@ server {
|
||||
# header from upstream' error when trying to access an application protected by goauthentik
|
||||
proxy_buffers 8 16k;
|
||||
proxy_buffer_size 32k;
|
||||
fastcgi_buffers 16 16k;
|
||||
fastcgi_buffer_size 32k;
|
||||
|
||||
location / {
|
||||
# Put your proxy_pass to your application here
|
||||
@ -25,6 +23,8 @@ server {
|
||||
error_page 401 = @akprox_signin;
|
||||
# For domain level, use the below error_page to redirect to your authentik server with the full redirect path
|
||||
# error_page 401 =302 https://authentik.company/akprox/start?rd=$scheme://$http_host$request_uri;
|
||||
auth_request_set $auth_cookie $upstream_http_set_cookie;
|
||||
add_header Set-Cookie $auth_cookie;
|
||||
|
||||
# translate headers from the outposts back to the actual upstream
|
||||
auth_request_set $authentik_username $upstream_http_x_authentik_username;
|
||||
@ -46,6 +46,7 @@ server {
|
||||
# ensure the host of this vserver matches your external URL you've configured
|
||||
# in authentik
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
|
||||
add_header Set-Cookie $auth_cookie;
|
||||
auth_request_set $auth_cookie $upstream_http_set_cookie;
|
||||
}
|
||||
|
@ -27,6 +27,14 @@ This release mostly removes legacy fields and features that have been deprecated
|
||||
|
||||
The deprecated /api/v2beta/ Endpoint is removed. Use `/api/v3/`.
|
||||
|
||||
- Backup:
|
||||
|
||||
The integrated backup has been deprecated for the following reasons:
|
||||
|
||||
- Difficulty with restores not working properly
|
||||
- Inflexible configuration (fixed retention, limited to once a day, only S3 supported)
|
||||
- Most users will already have an existing backup infrastructure
|
||||
|
||||
## Minor changes/fixes
|
||||
|
||||
- core: dont return 404 when trying to view key of expired token
|
||||
@ -50,6 +58,72 @@ This release mostly removes legacy fields and features that have been deprecated
|
||||
- web/admin: fix missing configure flow setting on webuahtn setup stage form
|
||||
- web/flows: remove node directly instead of using removeChild()
|
||||
|
||||
## Fixed in 2022.1.2
|
||||
|
||||
- internal/proxyv2: only allow access to /akprox in nginx mode when forward url could be extracted
|
||||
- lib: disable backup by default, add note to configuration
|
||||
- lifecycle: replace lowercase, deprecated prometheus_multiproc_dir
|
||||
- outposts: allow custom label for docker containers
|
||||
- policies/hibp: ensure password is encodable
|
||||
- providers/proxy: add PathPrefix to auto-traefik labels
|
||||
- root: upgrade python dependencies
|
||||
|
||||
## Fixed in 2022.1.3
|
||||
|
||||
- internal: add support for X-Original-URL
|
||||
- internal: add optional debug server listening on 9900
|
||||
- internal: don't override server header
|
||||
- internal: start adding tests to outpost
|
||||
- lifecycle: make secret_key warning more prominent
|
||||
- lifecycle: wait for db in worker
|
||||
- outposts/ldap: Fix more case sensitivity issues. (#2144)
|
||||
- outposts/proxy: add more test cases for domain-level auth
|
||||
- outposts/proxy: fix potential empty redirect, add tests
|
||||
- outposts/proxy: trace full headers to debug
|
||||
- providers/proxy: fix traefik label
|
||||
- root: add max-requests for gunicorn and max tasks for celery
|
||||
- root: fix redis passwords not being encoded correctly
|
||||
- web/admin: fix links which look like labels
|
||||
- web/admin: fix SMS Stage form not working
|
||||
|
||||
## Fixed in 2022.1.4
|
||||
|
||||
- core: fix view_token permission not being assigned on token creation for non-admin user
|
||||
- lifecycle: remove gunicorn reload option
|
||||
- lifecycle: send analytics in gunicorn config to decrease outgoing requests when workers get restarted
|
||||
- providers/proxy: add support for X-Original-URI in nginx, better handle missing headers and report errors to authentik
|
||||
- providers/proxy: don't include hostname and scheme in redirect when we only got a path and not a full URL
|
||||
- providers/proxy: fix routing for external_host when using forward_auth_domain
|
||||
- providers/proxy: set traefik labels using object_naming_template instead of UUID
|
||||
- sources/ldap: add list_flatten function to property mappings, enable on managed LDAP mappings
|
||||
- web: add es locale
|
||||
- web: add pl locale
|
||||
- web/admin: only check first half of locale when detecting
|
||||
- web/flows: fix width on flow container
|
||||
- web/user: include locale code in locale selection
|
||||
|
||||
## Fixed in 2022.1.5
|
||||
|
||||
- build(deps): bump uvicorn from 0.17.1 to 0.17.3 (#2229)
|
||||
- core: allow formatting strings to be used for applications' launch URLs
|
||||
- internal: don't attempt to lookup SNI Certificate if no SNI is sent
|
||||
- internal: fix CSRF error caused by Host header
|
||||
- internal: improve error handling for internal reverse proxy
|
||||
- internal: remove uvicorn server header
|
||||
- internal: trace headers and url for backend requests
|
||||
- outposts: fix channel not always having a logger attribute
|
||||
- outposts: fix compare_ports to support both service and container ports
|
||||
- outposts: fix service reconciler re-creating services
|
||||
- outposts: remove node_port on V1ServicePort checks to prevent service creation loops
|
||||
- providers/proxy: fix Host/:Authority not being modified
|
||||
- providers/proxy: fix nil error in claims
|
||||
- providers/proxy: improve error handling for invalid backend_override
|
||||
- sources/ldap: log entire exception
|
||||
- sources/saml: fix incorrect ProtocolBinding being sent
|
||||
- sources/saml: fix server error
|
||||
- stages/authenticator_validate: handle non-existent device_challenges
|
||||
- web/admin: fix mismatched icons in overview and lists
|
||||
|
||||
## Upgrading
|
||||
|
||||
This release does not introduce any new requirements.
|
||||
|
72
website/integrations/services/zulip/index.md
Normal file
72
website/integrations/services/zulip/index.md
Normal file
@ -0,0 +1,72 @@
|
||||
---
|
||||
title: Zulip
|
||||
---
|
||||
|
||||
## What is Zulip
|
||||
|
||||
From https://zulip.com
|
||||
|
||||
:::note
|
||||
**Zulip**: Chat for distributed teams. Zulip combines the immediacy of real-time chat with an email threading model.
|
||||
With Zulip, you can catch up on important conversations while ignoring irrelevant ones.
|
||||
:::
|
||||
|
||||
## Preperation
|
||||
|
||||
The following placeholders will be used:
|
||||
|
||||
- `authentik.company` is the FQDN of the authentik install.
|
||||
- `zulip.company` is the FQDN of the Zulip instance.
|
||||
|
||||
Create an application in authentik and note the slug, as this will be used later. Create a SAML provider with the following parameters:
|
||||
|
||||
- ACS URL: `https://zulip.company/complete/saml/`
|
||||
- Issuer: `https://authentik.company`
|
||||
- Service Provider Binding: `Post`
|
||||
- Signing Keypair: Select any certificate you have.
|
||||
- Property mappings: Select all Managed mappings.
|
||||
|
||||
## Zulip Configuration
|
||||
|
||||
Zulip is a Django application and is configured using `/etc/zulip/settings.py`. Only settings that differ
|
||||
from the defaults are displayed below. Please make sure you have the latest `settings.py` file as more settings
|
||||
might have been added to defaults since you installed Zulip.
|
||||
|
||||
Uncomment `zproject.backends.SAMLAuthBackend` inside the `AUTHENTICATION_BACKENDS` parameter to enable SAML support
|
||||
and fill in the following required configuration.
|
||||
|
||||
```
|
||||
SOCIAL_AUTH_SAML_ORG_INFO = {
|
||||
"en-US": {
|
||||
"displayname": "authentik Zulip",
|
||||
"name": "zulip",
|
||||
"url": "{}{}".format("https://", EXTERNAL_HOST),
|
||||
},
|
||||
}
|
||||
|
||||
SOCIAL_AUTH_SAML_ENABLED_IDPS: Dict[str, Any] = {
|
||||
# idp identifier and settings
|
||||
"authentik": {
|
||||
|
||||
# KEEP OTHER SETTINGS AS DEFAULT OR CONFIGURE THEM ACCORDING TO YOUR PREFERENCES
|
||||
"entity_id": "https://authentik.company",
|
||||
"url": "https://authentik.company/application/saml/<application slug>/sso/binding/redirect/",
|
||||
"display_name": "authentik SAML",
|
||||
},
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Place the certificate you associated with the SAML provider in authentik inside the `/etc/zulip/saml/idps` directory.
|
||||
The certificate file name must match the idp identifier name you set in the configuration (i.e. authentik.crt).
|
||||
|
||||
:::note
|
||||
Remember to restart Zulip.
|
||||
:::
|
||||
## Additional Resources
|
||||
|
||||
Please refer to the following for further information:
|
||||
|
||||
- https://zulip.com/
|
||||
- https://zulip.readthedocs.io
|
||||
- https://chat.zulip.org/ (Official public Zulip Chat instance)
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user