Compare commits
256 Commits
version/20
...
version/20
Author | SHA1 | Date | |
---|---|---|---|
2b5504ff63 | |||
f8a6aa3250 | |||
639c2f5c2e | |||
e44632f9a0 | |||
3f2ce34468 | |||
426cef998f | |||
8ddb62ed0f | |||
572f6d4ea0 | |||
8db68410c6 | |||
caa3c3de32 | |||
23b5ca761a | |||
f1b9021e3e | |||
99c62af89e | |||
8ae50814fe | |||
2e2b491ec7 | |||
ac432e78e2 | |||
83ac42ac43 | |||
4bd1cd127b | |||
2eb5a5cc76 | |||
75051687e6 | |||
7e316b5fc2 | |||
5594ad0b36 | |||
ea097afeae | |||
b77b4b5c80 | |||
f8dc7f48f2 | |||
692e75b057 | |||
02771683a6 | |||
40404ff41d | |||
fdd5211253 | |||
85a417d22e | |||
66c530ea06 | |||
347c3793fc | |||
cf78c89830 | |||
20c738c384 | |||
4f54ce6afb | |||
f0d7edb963 | |||
e42ad8db93 | |||
e917e756cc | |||
b4963bec76 | |||
0d23796989 | |||
d0ceafe79e | |||
f2023a7af2 | |||
31d597005f | |||
62dc86be7b | |||
7aa8e35f87 | |||
60b95271eb | |||
382b0e8941 | |||
3b068610b9 | |||
9a8f62f42e | |||
632e3cf7dc | |||
e7144649d5 | |||
dd8909c9b2 | |||
e6818c1f6a | |||
10c4e3c717 | |||
b8425867c8 | |||
a05da8cdbf | |||
c3aeefa653 | |||
62c840df21 | |||
45d1db8880 | |||
b34f30f1dd | |||
7a54e84eb4 | |||
917eef96fb | |||
9a393848b2 | |||
a6abeb50c6 | |||
39acb044fb | |||
7d2f622f4b | |||
a2b38caf64 | |||
1193b9fd22 | |||
e3a5ef1907 | |||
e597bb4542 | |||
c31df2b3f9 | |||
3f2637cffa | |||
3b6d9bec0a | |||
b184210610 | |||
d2010808ee | |||
f5b185dd06 | |||
ae161c1ba9 | |||
109283b189 | |||
235d283def | |||
96a86b3298 | |||
db9ea8603c | |||
8b7f698c7b | |||
813c13ce45 | |||
629a0e1a4d | |||
d1e2c018a3 | |||
1e86844823 | |||
b58875d4c7 | |||
03e0eecb1d | |||
7aa61d86e4 | |||
0e6a799e6d | |||
bc6afdf94f | |||
80364b04a9 | |||
0948e0ee1c | |||
5c54de66fc | |||
937edc73bc | |||
2c0d8d8943 | |||
059ccdd592 | |||
0ec0d3f1aa | |||
0a0eee138a | |||
3ed4c38101 | |||
de8cf65503 | |||
121b36f35f | |||
363aed2a47 | |||
ef994e0084 | |||
e1ef196283 | |||
f81ffd54f3 | |||
f9bfae9190 | |||
0d686465a4 | |||
e13b4a561f | |||
f6417f95e5 | |||
9c6bf5f4ae | |||
d2d7acb50e | |||
c7681dde32 | |||
8cf9661e08 | |||
2dbd76cf90 | |||
28d39f4d80 | |||
760428aa18 | |||
49bbac7441 | |||
0b8cfd437b | |||
b69aaf9417 | |||
758d1bdfd4 | |||
ab501ca971 | |||
9657741a3d | |||
29b7368f42 | |||
75724b6f8d | |||
7c9f821bfd | |||
5b9e6bed6c | |||
6113d7d768 | |||
0e3602d7eb | |||
2b94e9a687 | |||
6ed7d842e4 | |||
8794c840cf | |||
9c9c00755a | |||
6703c0a5d1 | |||
060f19ce06 | |||
b2d2e7cbc8 | |||
91fd792f88 | |||
2d9cd28221 | |||
aa64cf898f | |||
27d109c1fe | |||
1b4a14f3ee | |||
9835785864 | |||
d785998c5a | |||
8ba9553220 | |||
6eb132c48b | |||
b523cd064b | |||
355b832cc3 | |||
8f5af464a2 | |||
fb70769358 | |||
ad06778c34 | |||
bcb4451fb7 | |||
110d558572 | |||
e32d4f0095 | |||
0e413acd61 | |||
d3397c349f | |||
fb18a10e61 | |||
9bb0d04aeb | |||
666cf77b04 | |||
90ca1b8e5a | |||
f1e95b8816 | |||
dad8547212 | |||
a957e1fc45 | |||
39e3f02503 | |||
2b999e922c | |||
4224134a19 | |||
eda260dddd | |||
8a1dd521e1 | |||
1c5e91de1d | |||
4b1744fad0 | |||
f17b83010d | |||
12ddf9e73c | |||
0b3b300333 | |||
23f1a19765 | |||
b27e998615 | |||
2b928146a8 | |||
a94b0504b7 | |||
4fcbfa7709 | |||
986e01db20 | |||
9092d1189b | |||
605ed94ba2 | |||
4cbeeb9a0c | |||
993dee6aad | |||
c663deb659 | |||
61621e7d60 | |||
0ee9b07172 | |||
431ba6b4ef | |||
146818793e | |||
0ce663bce4 | |||
923ba4fb42 | |||
bb6eed0db1 | |||
d1bd8f333b | |||
2ac9f5426d | |||
8d1fd48003 | |||
241cb01ec6 | |||
65b4139997 | |||
1431be8c44 | |||
049fceeeee | |||
e6638afa3c | |||
465898c7d0 | |||
c363b1cfde | |||
b30ffd1318 | |||
fe0d3a64c8 | |||
ae9f1c1063 | |||
ea63d384fd | |||
c28d75754d | |||
518b691e00 | |||
cd845be45d | |||
a813d8e05e | |||
75f850f4d2 | |||
c84265c6f0 | |||
a477ea29cd | |||
f6aa85e340 | |||
0aeedb3ad8 | |||
4b29f238b5 | |||
34157db06a | |||
84b9e66a97 | |||
e831e4fb94 | |||
956922820b | |||
b0fac9c9f1 | |||
f4db09cd59 | |||
047030f901 | |||
638e8d741f | |||
425b87a6d0 | |||
e7dc763612 | |||
a80cc94da9 | |||
547dd3cb7a | |||
95739a934c | |||
d12e24017e | |||
e4a0345231 | |||
078633c2af | |||
4b8b800648 | |||
6f9ed001a1 | |||
e4095dfffe | |||
d5341c2284 | |||
357bd65028 | |||
867fb0dac0 | |||
2666aa2c73 | |||
f0e9bafa35 | |||
0d739f5c1a | |||
e08077c73a | |||
7cf8a31057 | |||
c43049a981 | |||
1a9ace6f9d | |||
b8d86bc482 | |||
f7044e41c6 | |||
fa59fec17a | |||
e29afa289e | |||
4d4193a586 | |||
59343ff441 | |||
cab564152d | |||
97b814ab33 | |||
88516ba2ca | |||
f069cfb643 | |||
4ce3c2341c | |||
77e42d60cb | |||
cacb919c6f |
@ -1,5 +1,5 @@
|
||||
[bumpversion]
|
||||
current_version = 2021.10.4
|
||||
current_version = 2021.12.1-rc2
|
||||
tag = True
|
||||
commit = True
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*)
|
||||
|
195
.github/workflows/ci-main.yml
vendored
195
.github/workflows/ci-main.yml
vendored
@ -18,79 +18,17 @@ env:
|
||||
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
|
||||
|
||||
jobs:
|
||||
lint-pylint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.9'
|
||||
# - id: cache-pipenv
|
||||
# uses: actions/cache@v2.1.6
|
||||
# with:
|
||||
# path: ~/.local/share/virtualenvs
|
||||
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
|
||||
- name: prepare
|
||||
# env:
|
||||
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
run: scripts/ci_prepare.sh
|
||||
- name: run pylint
|
||||
run: pipenv run pylint authentik tests lifecycle
|
||||
lint-black:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.9'
|
||||
# - id: cache-pipenv
|
||||
# uses: actions/cache@v2.1.6
|
||||
# with:
|
||||
# path: ~/.local/share/virtualenvs
|
||||
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
|
||||
- name: prepare
|
||||
# env:
|
||||
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
run: scripts/ci_prepare.sh
|
||||
- name: run black
|
||||
run: pipenv run black --check authentik tests lifecycle
|
||||
lint-isort:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.9'
|
||||
# - id: cache-pipenv
|
||||
# uses: actions/cache@v2.1.6
|
||||
# with:
|
||||
# path: ~/.local/share/virtualenvs
|
||||
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
|
||||
- name: prepare
|
||||
# env:
|
||||
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
run: scripts/ci_prepare.sh
|
||||
- name: run isort
|
||||
run: pipenv run isort --check authentik tests lifecycle
|
||||
lint-bandit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.9'
|
||||
# - id: cache-pipenv
|
||||
# uses: actions/cache@v2.1.6
|
||||
# with:
|
||||
# path: ~/.local/share/virtualenvs
|
||||
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
|
||||
- name: prepare
|
||||
# env:
|
||||
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
run: scripts/ci_prepare.sh
|
||||
- name: run bandit
|
||||
run: pipenv run bandit -r authentik tests lifecycle
|
||||
lint-pyright:
|
||||
lint:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
job:
|
||||
- pylint
|
||||
- black
|
||||
- isort
|
||||
- bandit
|
||||
- pyright
|
||||
- pending-migrations
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@ -100,12 +38,17 @@ jobs:
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16'
|
||||
- id: cache-pipenv
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: ~/.local/share/virtualenvs
|
||||
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
|
||||
- name: prepare
|
||||
run: |
|
||||
scripts/ci_prepare.sh
|
||||
npm install -g pyright@1.1.136
|
||||
- name: run bandit
|
||||
run: pipenv run pyright e2e lifecycle
|
||||
env:
|
||||
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
run: scripts/ci_prepare.sh
|
||||
- name: run pylint
|
||||
run: pipenv run make ci-${{ matrix.job }}
|
||||
test-migrations:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@ -113,14 +56,14 @@ jobs:
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.9'
|
||||
# - id: cache-pipenv
|
||||
# uses: actions/cache@v2.1.6
|
||||
# with:
|
||||
# path: ~/.local/share/virtualenvs
|
||||
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
|
||||
- id: cache-pipenv
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: ~/.local/share/virtualenvs
|
||||
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
|
||||
- name: prepare
|
||||
# env:
|
||||
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
env:
|
||||
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
run: scripts/ci_prepare.sh
|
||||
- name: run migrations
|
||||
run: pipenv run python -m lifecycle.migrate
|
||||
@ -137,21 +80,21 @@ jobs:
|
||||
id: ev
|
||||
run: |
|
||||
python ./scripts/gh_env.py
|
||||
# - id: cache-pipenv
|
||||
# uses: actions/cache@v2.1.6
|
||||
# with:
|
||||
# path: ~/.local/share/virtualenvs
|
||||
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
|
||||
- id: cache-pipenv
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: ~/.local/share/virtualenvs
|
||||
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
|
||||
- name: checkout stable
|
||||
run: |
|
||||
# Copy current, latest config to local
|
||||
cp authentik/lib/default.yml local.env.yml
|
||||
git checkout $(git describe --abbrev=0 --match 'version/*')
|
||||
git checkout ${{ steps.ev.outputs.branchName }} -- .github
|
||||
git checkout ${{ steps.ev.outputs.branchName }} -- scripts
|
||||
git checkout $GITHUB_HEAD_REF -- .github
|
||||
git checkout $GITHUB_HEAD_REF -- scripts
|
||||
- name: prepare
|
||||
# env:
|
||||
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
env:
|
||||
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
run: |
|
||||
scripts/ci_prepare.sh
|
||||
# Sync anyways since stable will have different dependencies
|
||||
@ -162,11 +105,11 @@ jobs:
|
||||
run: |
|
||||
set -x
|
||||
git fetch
|
||||
git checkout ${{ steps.ev.outputs.branchName }}
|
||||
git checkout $GITHUB_HEAD_REF
|
||||
pipenv sync --dev
|
||||
- name: prepare
|
||||
# env:
|
||||
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
env:
|
||||
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
run: scripts/ci_prepare.sh
|
||||
- name: migrate to latest
|
||||
run: pipenv run python -m lifecycle.migrate
|
||||
@ -177,14 +120,14 @@ jobs:
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.9'
|
||||
# - id: cache-pipenv
|
||||
# uses: actions/cache@v2.1.6
|
||||
# with:
|
||||
# path: ~/.local/share/virtualenvs
|
||||
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
|
||||
- id: cache-pipenv
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: ~/.local/share/virtualenvs
|
||||
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
|
||||
- name: prepare
|
||||
# env:
|
||||
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
env:
|
||||
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
run: scripts/ci_prepare.sh
|
||||
- uses: testspace-com/setup-testspace@v1
|
||||
with:
|
||||
@ -206,14 +149,14 @@ jobs:
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.9'
|
||||
# - id: cache-pipenv
|
||||
# uses: actions/cache@v2.1.6
|
||||
# with:
|
||||
# path: ~/.local/share/virtualenvs
|
||||
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
|
||||
- id: cache-pipenv
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: ~/.local/share/virtualenvs
|
||||
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
|
||||
- name: prepare
|
||||
# env:
|
||||
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
env:
|
||||
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
run: scripts/ci_prepare.sh
|
||||
- uses: testspace-com/setup-testspace@v1
|
||||
with:
|
||||
@ -245,19 +188,19 @@ jobs:
|
||||
- uses: testspace-com/setup-testspace@v1
|
||||
with:
|
||||
domain: ${{github.repository_owner}}
|
||||
# - id: cache-pipenv
|
||||
# uses: actions/cache@v2.1.6
|
||||
# with:
|
||||
# path: ~/.local/share/virtualenvs
|
||||
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
|
||||
- id: cache-pipenv
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: ~/.local/share/virtualenvs
|
||||
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
|
||||
- name: prepare
|
||||
# env:
|
||||
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
env:
|
||||
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
run: |
|
||||
scripts/ci_prepare.sh
|
||||
docker-compose -f tests/e2e/docker-compose.yml up -d
|
||||
- id: cache-web
|
||||
uses: actions/cache@v2.1.6
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: web/dist
|
||||
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/**') }}
|
||||
@ -279,11 +222,7 @@ jobs:
|
||||
uses: codecov/codecov-action@v2
|
||||
build:
|
||||
needs:
|
||||
- lint-pylint
|
||||
- lint-black
|
||||
- lint-isort
|
||||
- lint-bandit
|
||||
- lint-pyright
|
||||
- lint
|
||||
- test-migrations
|
||||
- test-migrations-from-stable
|
||||
- test-unittest
|
||||
@ -291,8 +230,15 @@ jobs:
|
||||
- test-e2e
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 120
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch:
|
||||
- 'linux/amd64'
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1.2.0
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: prepare variables
|
||||
@ -317,3 +263,4 @@ jobs:
|
||||
ghcr.io/goauthentik/dev-server:gh-${{ steps.ev.outputs.branchNameContainer }}-${{ steps.ev.outputs.timestamp }}-${{ steps.ev.outputs.sha }}
|
||||
build-args: |
|
||||
GIT_BUILD_HASH=${{ steps.ev.outputs.sha }}
|
||||
platforms: ${{ matrix.arch }}
|
||||
|
6
.github/workflows/ci-outpost.yml
vendored
6
.github/workflows/ci-outpost.yml
vendored
@ -35,13 +35,18 @@ jobs:
|
||||
needs:
|
||||
- lint-golint
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
type:
|
||||
- proxy
|
||||
- ldap
|
||||
arch:
|
||||
- 'linux/amd64'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1.2.0
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: prepare variables
|
||||
@ -68,3 +73,4 @@ jobs:
|
||||
file: ${{ matrix.type }}.Dockerfile
|
||||
build-args: |
|
||||
GIT_BUILD_HASH=${{ steps.ev.outputs.sha }}
|
||||
platforms: ${{ matrix.arch }}
|
||||
|
85
.github/workflows/release-publish.yml
vendored
85
.github/workflows/release-publish.yml
vendored
@ -30,14 +30,14 @@ jobs:
|
||||
with:
|
||||
push: ${{ github.event_name == 'release' }}
|
||||
tags: |
|
||||
beryju/authentik:2021.10.4,
|
||||
beryju/authentik:2021.12.1-rc2,
|
||||
beryju/authentik:latest,
|
||||
ghcr.io/goauthentik/server:2021.10.4,
|
||||
ghcr.io/goauthentik/server:2021.12.1-rc2,
|
||||
ghcr.io/goauthentik/server:latest
|
||||
platforms: linux/amd64,linux/arm64
|
||||
context: .
|
||||
- name: Building Docker Image (stable)
|
||||
if: ${{ github.event_name == 'release' && !contains('2021.10.4', 'rc') }}
|
||||
if: ${{ github.event_name == 'release' && !contains('2021.12.1-rc2', 'rc') }}
|
||||
run: |
|
||||
docker pull beryju/authentik:latest
|
||||
docker tag beryju/authentik:latest beryju/authentik:stable
|
||||
@ -45,8 +45,14 @@ jobs:
|
||||
docker pull ghcr.io/goauthentik/server:latest
|
||||
docker tag ghcr.io/goauthentik/server:latest ghcr.io/goauthentik/server:stable
|
||||
docker push ghcr.io/goauthentik/server:stable
|
||||
build-proxy:
|
||||
build-outpost:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
type:
|
||||
- proxy
|
||||
- ldap
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-go@v2
|
||||
@ -72,68 +78,25 @@ jobs:
|
||||
with:
|
||||
push: ${{ github.event_name == 'release' }}
|
||||
tags: |
|
||||
beryju/authentik-proxy:2021.10.4,
|
||||
beryju/authentik-proxy:latest,
|
||||
ghcr.io/goauthentik/proxy:2021.10.4,
|
||||
ghcr.io/goauthentik/proxy:latest
|
||||
file: proxy.Dockerfile
|
||||
beryju/authentik-${{ matrix.type }}:2021.12.1-rc2,
|
||||
beryju/authentik-${{ matrix.type }}:latest,
|
||||
ghcr.io/goauthentik/${{ matrix.type }}:2021.12.1-rc2,
|
||||
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('2021.10.4', 'rc') }}
|
||||
if: ${{ github.event_name == 'release' && !contains('2021.12.1-rc2', 'rc') }}
|
||||
run: |
|
||||
docker pull beryju/authentik-proxy:latest
|
||||
docker tag beryju/authentik-proxy:latest beryju/authentik-proxy:stable
|
||||
docker push beryju/authentik-proxy:stable
|
||||
docker pull ghcr.io/goauthentik/proxy:latest
|
||||
docker tag ghcr.io/goauthentik/proxy:latest ghcr.io/goauthentik/proxy:stable
|
||||
docker push ghcr.io/goauthentik/proxy:stable
|
||||
build-ldap:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: "^1.15"
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1.2.0
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: Docker Login Registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Building Docker Image
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
push: ${{ github.event_name == 'release' }}
|
||||
tags: |
|
||||
beryju/authentik-ldap:2021.10.4,
|
||||
beryju/authentik-ldap:latest,
|
||||
ghcr.io/goauthentik/ldap:2021.10.4,
|
||||
ghcr.io/goauthentik/ldap:latest
|
||||
file: ldap.Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
- name: Building Docker Image (stable)
|
||||
if: ${{ github.event_name == 'release' && !contains('2021.10.4', 'rc') }}
|
||||
run: |
|
||||
docker pull beryju/authentik-ldap:latest
|
||||
docker tag beryju/authentik-ldap:latest beryju/authentik-ldap:stable
|
||||
docker push beryju/authentik-ldap:stable
|
||||
docker pull ghcr.io/goauthentik/ldap:latest
|
||||
docker tag ghcr.io/goauthentik/ldap:latest ghcr.io/goauthentik/ldap:stable
|
||||
docker push ghcr.io/goauthentik/ldap:stable
|
||||
docker pull beryju/authentik-${{ matrix.type }}:latest
|
||||
docker tag beryju/authentik-${{ matrix.type }}:latest beryju/authentik-${{ matrix.type }}:stable
|
||||
docker push beryju/authentik-${{ matrix.type }}:stable
|
||||
docker pull ghcr.io/goauthentik/${{ matrix.type }}:latest
|
||||
docker tag ghcr.io/goauthentik/${{ matrix.type }}:latest ghcr.io/goauthentik/${{ matrix.type }}:stable
|
||||
docker push ghcr.io/goauthentik/${{ matrix.type }}:stable
|
||||
test-release:
|
||||
needs:
|
||||
- build-server
|
||||
- build-proxy
|
||||
- build-ldap
|
||||
- build-outpost
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@ -170,7 +133,7 @@ jobs:
|
||||
SENTRY_PROJECT: authentik
|
||||
SENTRY_URL: https://sentry.beryju.org
|
||||
with:
|
||||
version: authentik@2021.10.4
|
||||
version: authentik@2021.12.1-rc2
|
||||
environment: beryjuorg-prod
|
||||
sourcemaps: './web/dist'
|
||||
url_prefix: '~/static/dist'
|
||||
|
1
.github/workflows/release-tag.yml
vendored
1
.github/workflows/release-tag.yml
vendored
@ -15,6 +15,7 @@ jobs:
|
||||
run: |
|
||||
echo "PG_PASS=$(openssl rand -base64 32)" >> .env
|
||||
echo "AUTHENTIK_SECRET_KEY=$(openssl rand -base64 32)" >> .env
|
||||
docker buildx install
|
||||
docker build \
|
||||
--no-cache \
|
||||
-t testing:latest \
|
||||
|
19
.github/workflows/translation-compile.yml
vendored
19
.github/workflows/translation-compile.yml
vendored
@ -4,6 +4,9 @@ on:
|
||||
branches: [ master ]
|
||||
paths:
|
||||
- '/locale/'
|
||||
pull_request:
|
||||
paths:
|
||||
- '/locale/'
|
||||
schedule:
|
||||
- cron: "0 */2 * * *"
|
||||
workflow_dispatch:
|
||||
@ -21,7 +24,14 @@ jobs:
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.9'
|
||||
- id: cache-pipenv
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: ~/.local/share/virtualenvs
|
||||
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
|
||||
- name: prepare
|
||||
env:
|
||||
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gettext
|
||||
@ -30,10 +40,19 @@ jobs:
|
||||
run: pipenv run ./manage.py compilemessages
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
id: cpr
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
branch: compile-backend-translation
|
||||
commit-message: "core: compile backend translations"
|
||||
title: "core: compile backend translations"
|
||||
body: "core: compile backend translations"
|
||||
delete-branch: true
|
||||
signoff: true
|
||||
- name: Enable Pull Request Automerge
|
||||
if: steps.cpr.outputs.pull-request-operation == 'created'
|
||||
uses: peter-evans/enable-pull-request-automerge@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
pull-request-number: ${{ steps.cpr.outputs.pull-request-number }}
|
||||
merge-method: squash
|
||||
|
9
.github/workflows/web-api-publish.yml
vendored
9
.github/workflows/web-api-publish.yml
vendored
@ -30,10 +30,19 @@ jobs:
|
||||
npm i @goauthentik/api@$VERSION
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
id: cpr
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
branch: update-web-api-client
|
||||
commit-message: "web: Update Web API Client version"
|
||||
title: "web: Update Web API Client version"
|
||||
body: "web: Update Web API Client version"
|
||||
delete-branch: true
|
||||
signoff: true
|
||||
- name: Enable Pull Request Automerge
|
||||
if: steps.cpr.outputs.pull-request-operation == 'created'
|
||||
uses: peter-evans/enable-pull-request-automerge@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
pull-request-number: ${{ steps.cpr.outputs.pull-request-number }}
|
||||
merge-method: squash
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -66,7 +66,9 @@ coverage.xml
|
||||
unittest.xml
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
# Have to include binary mo files as they are annoying to compile at build time
|
||||
# since a full postgres and redis instance are required
|
||||
# *.mo
|
||||
|
||||
# Django stuff:
|
||||
|
||||
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -10,7 +10,8 @@
|
||||
"plex",
|
||||
"saml",
|
||||
"totp",
|
||||
"webauthn"
|
||||
"webauthn",
|
||||
"traefik"
|
||||
],
|
||||
"python.linting.pylintEnabled": true,
|
||||
"todo-tree.tree.showCountsInTree": true,
|
||||
|
37
Dockerfile
37
Dockerfile
@ -11,35 +11,32 @@ RUN pip install pipenv && \
|
||||
pipenv lock -r --dev-only > requirements-dev.txt
|
||||
|
||||
# Stage 2: Build website
|
||||
FROM docker.io/node:16 as website-builder
|
||||
FROM --platform=${BUILDPLATFORM} docker.io/node:16 as website-builder
|
||||
|
||||
COPY ./website /static/
|
||||
COPY ./website /work/website/
|
||||
|
||||
ENV NODE_ENV=production
|
||||
RUN cd /static && npm i && npm run build-docs-only
|
||||
RUN cd /work/website && npm i && npm run build-docs-only
|
||||
|
||||
# Stage 3: Build webui
|
||||
FROM docker.io/node:16 as web-builder
|
||||
FROM --platform=${BUILDPLATFORM} docker.io/node:16 as web-builder
|
||||
|
||||
COPY ./web /static/
|
||||
COPY ./web /work/web/
|
||||
COPY ./website /work/website/
|
||||
|
||||
ENV NODE_ENV=production
|
||||
RUN cd /static && npm i && npm run build
|
||||
RUN cd /work/web && npm i && npm run build
|
||||
|
||||
# Stage 4: Build go proxy
|
||||
FROM docker.io/golang:1.17.3-bullseye AS builder
|
||||
|
||||
WORKDIR /work
|
||||
|
||||
COPY --from=web-builder /static/robots.txt /work/web/robots.txt
|
||||
COPY --from=web-builder /static/security.txt /work/web/security.txt
|
||||
COPY --from=web-builder /static/dist/ /work/web/dist/
|
||||
COPY --from=web-builder /static/authentik/ /work/web/authentik/
|
||||
COPY --from=website-builder /static/help/ /work/website/help/
|
||||
COPY --from=web-builder /work/web/robots.txt /work/web/robots.txt
|
||||
COPY --from=web-builder /work/web/security.txt /work/web/security.txt
|
||||
|
||||
COPY ./cmd /work/cmd
|
||||
COPY ./web/static.go /work/web/static.go
|
||||
COPY ./website/static.go /work/website/static.go
|
||||
COPY ./internal /work/internal
|
||||
COPY ./go.mod /work/go.mod
|
||||
COPY ./go.sum /work/go.sum
|
||||
@ -57,19 +54,18 @@ ARG GIT_BUILD_HASH
|
||||
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends curl ca-certificates gnupg git runit && \
|
||||
curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - && \
|
||||
echo "deb http://apt.postgresql.org/pub/repos/apt bullseye-pgdg main" > /etc/apt/sources.list.d/pgdg.list && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends libpq-dev postgresql-client build-essential libxmlsec1-dev pkg-config libmaxminddb0 && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
curl ca-certificates gnupg git runit libpq-dev \
|
||||
postgresql-client build-essential libxmlsec1-dev \
|
||||
pkg-config libmaxminddb0 && \
|
||||
pip install -r /requirements.txt --no-cache-dir && \
|
||||
apt-get remove --purge -y build-essential git && \
|
||||
apt-get autoremove --purge -y && \
|
||||
apt-get clean && \
|
||||
rm -rf /tmp/* /var/lib/apt/lists/* /var/tmp/ && \
|
||||
adduser --system --no-create-home --uid 1000 --group --home /authentik authentik && \
|
||||
mkdir /backups && \
|
||||
chown authentik:authentik /backups
|
||||
mkdir /backups /certs && \
|
||||
chown authentik:authentik /backups /certs
|
||||
|
||||
COPY ./authentik/ /authentik
|
||||
COPY ./pyproject.toml /
|
||||
@ -78,6 +74,9 @@ COPY ./tests /tests
|
||||
COPY ./manage.py /
|
||||
COPY ./lifecycle/ /lifecycle
|
||||
COPY --from=builder /work/authentik /authentik-proxy
|
||||
COPY --from=web-builder /work/web/dist/ /web/dist/
|
||||
COPY --from=web-builder /work/web/authentik/ /web/authentik/
|
||||
COPY --from=website-builder /work/website/help/ /website/help/
|
||||
|
||||
USER authentik
|
||||
|
||||
|
49
Makefile
49
Makefile
@ -7,13 +7,13 @@ NPM_VERSION = $(shell python -m scripts.npm_version)
|
||||
all: lint-fix lint test gen
|
||||
|
||||
test-integration:
|
||||
coverage run manage.py test -v 3 tests/integration
|
||||
coverage run manage.py test tests/integration
|
||||
|
||||
test-e2e:
|
||||
coverage run manage.py test --failfast -v 3 tests/e2e
|
||||
coverage run manage.py test tests/e2e
|
||||
|
||||
test:
|
||||
coverage run manage.py test -v 3 authentik
|
||||
coverage run manage.py test authentik
|
||||
coverage html
|
||||
coverage report
|
||||
|
||||
@ -33,9 +33,10 @@ lint:
|
||||
bandit -r authentik tests lifecycle -x node_modules
|
||||
pylint authentik tests lifecycle
|
||||
|
||||
i18n-extract:
|
||||
i18n-extract: i18n-extract-core web-extract
|
||||
|
||||
i18n-extract-core:
|
||||
./manage.py makemessages --ignore web --ignore internal --ignore web --ignore web-api --ignore website -l en
|
||||
cd web && npm run extract
|
||||
|
||||
gen-build:
|
||||
./manage.py spectacular --file schema.yml
|
||||
@ -48,7 +49,7 @@ gen-web:
|
||||
docker run \
|
||||
--rm -v ${PWD}:/local \
|
||||
--user ${UID}:${GID} \
|
||||
ghcr.io/beryju/openapi-generator generate \
|
||||
openapitools/openapi-generator-cli generate \
|
||||
-i /local/schema.yml \
|
||||
-g typescript-fetch \
|
||||
-o /local/web-api \
|
||||
@ -67,12 +68,13 @@ gen-outpost:
|
||||
docker run \
|
||||
--rm -v ${PWD}:/local \
|
||||
--user ${UID}:${GID} \
|
||||
openapitools/openapi-generator-cli generate \
|
||||
openapitools/openapi-generator-cli:v5.2.1 generate \
|
||||
-i /local/schema.yml \
|
||||
-g go \
|
||||
-o /local/api \
|
||||
-c /local/config.yaml
|
||||
go mod edit -replace goauthentik.io/api=./api
|
||||
rm -rf config.yaml ./templates/
|
||||
|
||||
gen: gen-build gen-clean gen-web
|
||||
|
||||
@ -81,3 +83,36 @@ migrate:
|
||||
|
||||
run:
|
||||
go run -v cmd/server/main.go
|
||||
|
||||
web: web-lint-fix web-lint web-extract
|
||||
|
||||
web-lint-fix:
|
||||
cd web && npm run prettier
|
||||
|
||||
web-lint:
|
||||
cd web && npm run lint
|
||||
cd web && npm run lit-analyse
|
||||
|
||||
web-extract:
|
||||
cd web && npm run extract
|
||||
|
||||
# These targets are use by GitHub actions to allow usage of matrix
|
||||
# which makes the YAML File a lot smaller
|
||||
|
||||
ci-pylint:
|
||||
pylint authentik tests lifecycle
|
||||
|
||||
ci-black:
|
||||
black --check authentik tests lifecycle
|
||||
|
||||
ci-isort:
|
||||
isort --check authentik tests lifecycle
|
||||
|
||||
ci-bandit:
|
||||
bandit -r authentik tests lifecycle
|
||||
|
||||
ci-pyright:
|
||||
pyright e2e lifecycle
|
||||
|
||||
ci-pending-migrations:
|
||||
./manage.py makemigrations --check
|
||||
|
16
Pipfile
16
Pipfile
@ -8,7 +8,10 @@ boto3 = "*"
|
||||
celery = "*"
|
||||
channels = "*"
|
||||
channels-redis = "*"
|
||||
codespell = "*"
|
||||
colorama = "*"
|
||||
dacite = "*"
|
||||
deepmerge = "*"
|
||||
defusedxml = "*"
|
||||
django = "*"
|
||||
django-dbbackup = { git = 'https://github.com/django-dbbackup/django-dbbackup.git', ref = '9d1909c30a3271c8c9c8450add30d6e0b996e145' }
|
||||
@ -23,6 +26,7 @@ djangorestframework = "*"
|
||||
djangorestframework-guardian = "*"
|
||||
docker = "*"
|
||||
drf-spectacular = "*"
|
||||
duo-client = "*"
|
||||
facebook-sdk = "*"
|
||||
geoip2 = "*"
|
||||
gunicorn = "*"
|
||||
@ -40,19 +44,15 @@ service_identity = "*"
|
||||
structlog = "*"
|
||||
swagger-spec-validator = "*"
|
||||
twisted = "==21.7.0"
|
||||
ua-parser = "*"
|
||||
urllib3 = {extras = ["secure"],version = "*"}
|
||||
uvicorn = {extras = ["standard"],version = "*"}
|
||||
webauthn = "*"
|
||||
xmlsec = "*"
|
||||
duo-client = "*"
|
||||
ua-parser = "*"
|
||||
deepmerge = "*"
|
||||
colorama = "*"
|
||||
codespell = "*"
|
||||
|
||||
[dev-packages]
|
||||
bandit = "*"
|
||||
black = "==21.9b0"
|
||||
black = "==21.11b1"
|
||||
bump2version = "*"
|
||||
colorama = "*"
|
||||
coverage = {extras = ["toml"],version = "*"}
|
||||
@ -60,5 +60,7 @@ pylint = "*"
|
||||
pylint-django = "*"
|
||||
pytest = "*"
|
||||
pytest-django = "*"
|
||||
selenium = "*"
|
||||
pytest-randomly = "*"
|
||||
requests-mock = "*"
|
||||
selenium = "*"
|
||||
importlib-metadata = "*"
|
||||
|
567
Pipfile.lock
generated
567
Pipfile.lock
generated
@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "2955828c31ceb0f8266987b5a34cb31d2718d856997a384e7c0a92374ddfaa10"
|
||||
"sha256": "cb44f399eed2d9be5057c2c1ce572435b6a449cdfdf13abfee8787db1f2856d9"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {},
|
||||
@ -16,81 +16,81 @@
|
||||
"default": {
|
||||
"aiohttp": {
|
||||
"hashes": [
|
||||
"sha256:09754a0d5eaab66c37591f2f8fac8f9781a5f61d51aa852a3261c4805ca6b984",
|
||||
"sha256:097ecf52f6b9859b025c1e36401f8aa4573552e887d1b91b4b999d68d0b5a3b3",
|
||||
"sha256:0a96473a1f61d7920a9099bc8e729dc8282539d25f79c12573ee0fdb9c8b66a8",
|
||||
"sha256:0af379221975054162959e00daf21159ff69a712fc42ed0052caddbd70d52ff4",
|
||||
"sha256:0d7b056fd3972d353cb4bc305c03f9381583766b7f8c7f1c44478dba69099e33",
|
||||
"sha256:14a6f026eca80dfa3d52e86be89feb5cd878f6f4a6adb34457e2c689fd85229b",
|
||||
"sha256:15a660d06092b7c92ed17c1dbe6c1eab0a02963992d60e3e8b9d5fa7fa81f01e",
|
||||
"sha256:1fa9f50aa1f114249b7963c98e20dc35c51be64096a85bc92433185f331de9cc",
|
||||
"sha256:257f4fad1714d26d562572095c8c5cd271d5a333252795cb7a002dca41fdbad7",
|
||||
"sha256:28369fe331a59d80393ec82df3d43307c7461bfaf9217999e33e2acc7984ff7c",
|
||||
"sha256:2bdd655732e38b40f8a8344d330cfae3c727fb257585df923316aabbd489ccb8",
|
||||
"sha256:2f44d1b1c740a9e2275160d77c73a11f61e8a916191c572876baa7b282bcc934",
|
||||
"sha256:3ba08a71caa42eef64357257878fb17f3fba3fba6e81a51d170e32321569e079",
|
||||
"sha256:3c5e9981e449d54308c6824f172ec8ab63eb9c5f922920970249efee83f7e919",
|
||||
"sha256:3f58aa995b905ab82fe228acd38538e7dc1509e01508dcf307dad5046399130f",
|
||||
"sha256:48c996eb91bfbdab1e01e2c02e7ff678c51e2b28e3a04e26e41691991cc55795",
|
||||
"sha256:48f218a5257b6bc16bcf26a91d97ecea0c7d29c811a90d965f3dd97c20f016d6",
|
||||
"sha256:4a6551057a846bf72c7a04f73de3fcaca269c0bd85afe475ceb59d261c6a938c",
|
||||
"sha256:51f90dabd9933b1621260b32c2f0d05d36923c7a5a909eb823e429dba0fd2f3e",
|
||||
"sha256:577cc2c7b807b174814dac2d02e673728f2e46c7f90ceda3a70ea4bb6d90b769",
|
||||
"sha256:5d79174d96446a02664e2bffc95e7b6fa93b9e6d8314536c5840dff130d0878b",
|
||||
"sha256:5e3f81fbbc170418e22918a9585fd7281bbc11d027064d62aa4b507552c92671",
|
||||
"sha256:5ecffdc748d3b40dd3618ede0170e4f5e1d3c9647cfb410d235d19e62cb54ee0",
|
||||
"sha256:63fa57a0708573d3c059f7b5527617bd0c291e4559298473df238d502e4ab98c",
|
||||
"sha256:67ca7032dfac8d001023fadafc812d9f48bf8a8c3bb15412d9cdcf92267593f4",
|
||||
"sha256:688a1eb8c1a5f7e795c7cb67e0fe600194e6723ba35f138dfae0db20c0cb8f94",
|
||||
"sha256:6a038cb1e6e55b26bb5520ccffab7f539b3786f5553af2ee47eb2ec5cbd7084e",
|
||||
"sha256:6b79f6c31e68b6dafc0317ec453c83c86dd8db1f8f0c6f28e97186563fca87a0",
|
||||
"sha256:6d3e027fe291b77f6be9630114a0200b2c52004ef20b94dc50ca59849cd623b3",
|
||||
"sha256:6f1d39a744101bf4043fa0926b3ead616607578192d0a169974fb5265ab1e9d2",
|
||||
"sha256:707adc30ea6918fba725c3cb3fe782d271ba352b22d7ae54a7f9f2e8a8488c41",
|
||||
"sha256:730b7c2b7382194d9985ffdc32ab317e893bca21e0665cb1186bdfbb4089d990",
|
||||
"sha256:764c7c6aa1f78bd77bd9674fc07d1ec44654da1818d0eef9fb48aa8371a3c847",
|
||||
"sha256:78d51e35ed163783d721b6f2ce8ce3f82fccfe471e8e50a10fba13a766d31f5a",
|
||||
"sha256:7a315ceb813208ef32bdd6ec3a85cbe3cb3be9bbda5fd030c234592fa9116993",
|
||||
"sha256:7ba09bb3dcb0b7ec936a485db2b64be44fe14cdce0a5eac56f50e55da3627385",
|
||||
"sha256:7d76e8a83396e06abe3df569b25bd3fc88bf78b7baa2c8e4cf4aaf5983af66a3",
|
||||
"sha256:84fe1732648c1bc303a70faa67cbc2f7f2e810c8a5bca94f6db7818e722e4c0a",
|
||||
"sha256:871d4fdc56288caa58b1094c20f2364215f7400411f76783ea19ad13be7c8e19",
|
||||
"sha256:88d4917c30fcd7f6404fb1dc713fa21de59d3063dcc048f4a8a1a90e6bbbd739",
|
||||
"sha256:8a50150419b741ee048b53146c39c47053f060cb9d98e78be08fdbe942eaa3c4",
|
||||
"sha256:90a97c2ed2830e7974cbe45f0838de0aefc1c123313f7c402e21c29ec063fbb4",
|
||||
"sha256:949a605ef3907254b122f845baa0920407080cdb1f73aa64f8d47df4a7f4c4f9",
|
||||
"sha256:9689af0f0a89e5032426c143fa3683b0451f06c83bf3b1e27902bd33acfae769",
|
||||
"sha256:98b1ea2763b33559dd9ec621d67fc17b583484cb90735bfb0ec3614c17b210e4",
|
||||
"sha256:9951c2696c4357703001e1fe6edc6ae8e97553ac630492ea1bf64b429cb712a3",
|
||||
"sha256:9a52b141ff3b923a9166595de6e3768a027546e75052ffba267d95b54267f4ab",
|
||||
"sha256:9e8723c3256641e141cd18f6ce478d54a004138b9f1a36e41083b36d9ecc5fc5",
|
||||
"sha256:a2fee4d656a7cc9ab47771b2a9e8fad8a9a33331c1b59c3057ecf0ac858f5bfe",
|
||||
"sha256:a4759e85a191de58e0ea468ab6fd9c03941986eee436e0518d7a9291fab122c8",
|
||||
"sha256:a5399a44a529083951b55521cf4ecbf6ad79fd54b9df57dbf01699ffa0549fc9",
|
||||
"sha256:a6074a3b2fa2d0c9bf0963f8dfc85e1e54a26114cc8594126bc52d3fa061c40e",
|
||||
"sha256:a84c335337b676d832c1e2bc47c3a97531b46b82de9f959dafb315cbcbe0dfcd",
|
||||
"sha256:adf0cb251b1b842c9dee5cfcdf880ba0aae32e841b8d0e6b6feeaef002a267c5",
|
||||
"sha256:b76669b7c058b8020b11008283c3b8e9c61bfd978807c45862956119b77ece45",
|
||||
"sha256:bda75d73e7400e81077b0910c9a60bf9771f715420d7e35fa7739ae95555f195",
|
||||
"sha256:be03a7483ad9ea60388f930160bb3728467dd0af538aa5edc60962ee700a0bdc",
|
||||
"sha256:c62d4791a8212c885b97a63ef5f3974b2cd41930f0cd224ada9c6ee6654f8150",
|
||||
"sha256:cb751ef712570d3bda9a73fd765ff3e1aba943ec5d52a54a0c2e89c7eef9da1e",
|
||||
"sha256:d3b19d8d183bcfd68b25beebab8dc3308282fe2ca3d6ea3cb4cd101b3c279f8d",
|
||||
"sha256:d3f90ee275b1d7c942e65b5c44c8fb52d55502a0b9a679837d71be2bd8927661",
|
||||
"sha256:d5f8c04574efa814a24510122810e3a3c77c0552f9f6ff65c9862f1f046be2c3",
|
||||
"sha256:d6a1a66bb8bac9bc2892c2674ea363486bfb748b86504966a390345a11b1680e",
|
||||
"sha256:d7715daf84f10bcebc083ad137e3eced3e1c8e7fa1f096ade9a8d02b08f0d91c",
|
||||
"sha256:dafc01a32b4a1d7d3ef8bfd3699406bb44f7b2e0d3eb8906d574846e1019b12f",
|
||||
"sha256:dcc4d5dd5fba3affaf4fd08f00ef156407573de8c63338787614ccc64f96b321",
|
||||
"sha256:de42f513ed7a997bc821bddab356b72e55e8396b1b7ba1bf39926d538a76a90f",
|
||||
"sha256:e27cde1e8d17b09730801ce97b6e0c444ba2a1f06348b169fd931b51d3402f0d",
|
||||
"sha256:ecb314e59bedb77188017f26e6b684b1f6d0465e724c3122a726359fa62ca1ba",
|
||||
"sha256:f348ebd20554e8bc26e8ef3ed8a134110c0f4bf015b3b4da6a4ddf34e0515b19",
|
||||
"sha256:fa818609357dde5c4a94a64c097c6404ad996b1d38ca977a72834b682830a722",
|
||||
"sha256:fe4a327da0c6b6e59f2e474ae79d6ee7745ac3279fd15f200044602fa31e3d79"
|
||||
"sha256:01d7bdb774a9acc838e6b8f1d114f45303841b89b95984cbb7d80ea41172a9e3",
|
||||
"sha256:03a6d5349c9ee8f79ab3ff3694d6ce1cfc3ced1c9d36200cb8f08ba06bd3b782",
|
||||
"sha256:04d48b8ce6ab3cf2097b1855e1505181bdd05586ca275f2505514a6e274e8e75",
|
||||
"sha256:0770e2806a30e744b4e21c9d73b7bee18a1cfa3c47991ee2e5a65b887c49d5cf",
|
||||
"sha256:07b05cd3305e8a73112103c834e91cd27ce5b4bd07850c4b4dbd1877d3f45be7",
|
||||
"sha256:086f92daf51a032d062ec5f58af5ca6a44d082c35299c96376a41cbb33034675",
|
||||
"sha256:099ebd2c37ac74cce10a3527d2b49af80243e2a4fa39e7bce41617fbc35fa3c1",
|
||||
"sha256:0c7ebbbde809ff4e970824b2b6cb7e4222be6b95a296e46c03cf050878fc1785",
|
||||
"sha256:102e487eeb82afac440581e5d7f8f44560b36cf0bdd11abc51a46c1cd88914d4",
|
||||
"sha256:11691cf4dc5b94236ccc609b70fec991234e7ef8d4c02dd0c9668d1e486f5abf",
|
||||
"sha256:11a67c0d562e07067c4e86bffc1553f2cf5b664d6111c894671b2b8712f3aba5",
|
||||
"sha256:12de6add4038df8f72fac606dff775791a60f113a725c960f2bab01d8b8e6b15",
|
||||
"sha256:13487abd2f761d4be7c8ff9080de2671e53fff69711d46de703c310c4c9317ca",
|
||||
"sha256:15b09b06dae900777833fe7fc4b4aa426556ce95847a3e8d7548e2d19e34edb8",
|
||||
"sha256:1c182cb873bc91b411e184dab7a2b664d4fea2743df0e4d57402f7f3fa644bac",
|
||||
"sha256:1ed0b6477896559f17b9eaeb6d38e07f7f9ffe40b9f0f9627ae8b9926ae260a8",
|
||||
"sha256:28d490af82bc6b7ce53ff31337a18a10498303fe66f701ab65ef27e143c3b0ef",
|
||||
"sha256:2e5d962cf7e1d426aa0e528a7e198658cdc8aa4fe87f781d039ad75dcd52c516",
|
||||
"sha256:2ed076098b171573161eb146afcb9129b5ff63308960aeca4b676d9d3c35e700",
|
||||
"sha256:2f2f69dca064926e79997f45b2f34e202b320fd3782f17a91941f7eb85502ee2",
|
||||
"sha256:31560d268ff62143e92423ef183680b9829b1b482c011713ae941997921eebc8",
|
||||
"sha256:31d1e1c0dbf19ebccbfd62eff461518dcb1e307b195e93bba60c965a4dcf1ba0",
|
||||
"sha256:37951ad2f4a6df6506750a23f7cbabad24c73c65f23f72e95897bb2cecbae676",
|
||||
"sha256:3af642b43ce56c24d063325dd2cf20ee012d2b9ba4c3c008755a301aaea720ad",
|
||||
"sha256:44db35a9e15d6fe5c40d74952e803b1d96e964f683b5a78c3cc64eb177878155",
|
||||
"sha256:473d93d4450880fe278696549f2e7aed8cd23708c3c1997981464475f32137db",
|
||||
"sha256:477c3ea0ba410b2b56b7efb072c36fa91b1e6fc331761798fa3f28bb224830dd",
|
||||
"sha256:4a4a4e30bf1edcad13fb0804300557aedd07a92cabc74382fdd0ba6ca2661091",
|
||||
"sha256:4aed991a28ea3ce320dc8ce655875e1e00a11bdd29fe9444dd4f88c30d558602",
|
||||
"sha256:51467000f3647d519272392f484126aa716f747859794ac9924a7aafa86cd411",
|
||||
"sha256:55c3d1072704d27401c92339144d199d9de7b52627f724a949fc7d5fc56d8b93",
|
||||
"sha256:589c72667a5febd36f1315aa6e5f56dd4aa4862df295cb51c769d16142ddd7cd",
|
||||
"sha256:5bfde62d1d2641a1f5173b8c8c2d96ceb4854f54a44c23102e2ccc7e02f003ec",
|
||||
"sha256:5c23b1ad869653bc818e972b7a3a79852d0e494e9ab7e1a701a3decc49c20d51",
|
||||
"sha256:61bfc23df345d8c9716d03717c2ed5e27374e0fe6f659ea64edcd27b4b044cf7",
|
||||
"sha256:6ae828d3a003f03ae31915c31fa684b9890ea44c9c989056fea96e3d12a9fa17",
|
||||
"sha256:6c7cefb4b0640703eb1069835c02486669312bf2f12b48a748e0a7756d0de33d",
|
||||
"sha256:6d69f36d445c45cda7b3b26afef2fc34ef5ac0cdc75584a87ef307ee3c8c6d00",
|
||||
"sha256:6f0d5f33feb5f69ddd57a4a4bd3d56c719a141080b445cbf18f238973c5c9923",
|
||||
"sha256:6f8b01295e26c68b3a1b90efb7a89029110d3a4139270b24fda961893216c440",
|
||||
"sha256:713ac174a629d39b7c6a3aa757b337599798da4c1157114a314e4e391cd28e32",
|
||||
"sha256:718626a174e7e467f0558954f94af117b7d4695d48eb980146016afa4b580b2e",
|
||||
"sha256:7187a76598bdb895af0adbd2fb7474d7f6025d170bc0a1130242da817ce9e7d1",
|
||||
"sha256:71927042ed6365a09a98a6377501af5c9f0a4d38083652bcd2281a06a5976724",
|
||||
"sha256:7d08744e9bae2ca9c382581f7dce1273fe3c9bae94ff572c3626e8da5b193c6a",
|
||||
"sha256:7dadf3c307b31e0e61689cbf9e06be7a867c563d5a63ce9dca578f956609abf8",
|
||||
"sha256:81e3d8c34c623ca4e36c46524a3530e99c0bc95ed068fd6e9b55cb721d408fb2",
|
||||
"sha256:844a9b460871ee0a0b0b68a64890dae9c415e513db0f4a7e3cab41a0f2fedf33",
|
||||
"sha256:8b7ef7cbd4fec9a1e811a5de813311ed4f7ac7d93e0fda233c9b3e1428f7dd7b",
|
||||
"sha256:97ef77eb6b044134c0b3a96e16abcb05ecce892965a2124c566af0fd60f717e2",
|
||||
"sha256:99b5eeae8e019e7aad8af8bb314fb908dd2e028b3cdaad87ec05095394cce632",
|
||||
"sha256:a25fa703a527158aaf10dafd956f7d42ac6d30ec80e9a70846253dd13e2f067b",
|
||||
"sha256:a2f635ce61a89c5732537a7896b6319a8fcfa23ba09bec36e1b1ac0ab31270d2",
|
||||
"sha256:a79004bb58748f31ae1cbe9fa891054baaa46fb106c2dc7af9f8e3304dc30316",
|
||||
"sha256:a996d01ca39b8dfe77440f3cd600825d05841088fd6bc0144cc6c2ec14cc5f74",
|
||||
"sha256:b0e20cddbd676ab8a64c774fefa0ad787cc506afd844de95da56060348021e96",
|
||||
"sha256:b6613280ccedf24354406caf785db748bebbddcf31408b20c0b48cb86af76866",
|
||||
"sha256:b9d00268fcb9f66fbcc7cd9fe423741d90c75ee029a1d15c09b22d23253c0a44",
|
||||
"sha256:bb01ba6b0d3f6c68b89fce7305080145d4877ad3acaed424bae4d4ee75faa950",
|
||||
"sha256:c2aef4703f1f2ddc6df17519885dbfa3514929149d3ff900b73f45998f2532fa",
|
||||
"sha256:c34dc4958b232ef6188c4318cb7b2c2d80521c9a56c52449f8f93ab7bc2a8a1c",
|
||||
"sha256:c3630c3ef435c0a7c549ba170a0633a56e92629aeed0e707fec832dee313fb7a",
|
||||
"sha256:c3d6a4d0619e09dcd61021debf7059955c2004fa29f48788a3dfaf9c9901a7cd",
|
||||
"sha256:d15367ce87c8e9e09b0f989bfd72dc641bcd04ba091c68cd305312d00962addd",
|
||||
"sha256:d2f9b69293c33aaa53d923032fe227feac867f81682f002ce33ffae978f0a9a9",
|
||||
"sha256:e999f2d0e12eea01caeecb17b653f3713d758f6dcc770417cf29ef08d3931421",
|
||||
"sha256:ea302f34477fda3f85560a06d9ebdc7fa41e82420e892fc50b577e35fc6a50b2",
|
||||
"sha256:eaba923151d9deea315be1f3e2b31cc39a6d1d2f682f942905951f4e40200922",
|
||||
"sha256:ef9612483cb35171d51d9173647eed5d0069eaa2ee812793a75373447d487aa4",
|
||||
"sha256:f5315a2eb0239185af1bddb1abf472d877fede3cc8d143c6cddad37678293237",
|
||||
"sha256:fa0ffcace9b3aa34d205d8130f7873fcfefcb6a4dd3dd705b0dab69af6712642",
|
||||
"sha256:fc5471e1a54de15ef71c1bc6ebe80d4dc681ea600e68bfd1cbce40427f0b7578"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==3.8.0"
|
||||
"version": "==3.8.1"
|
||||
},
|
||||
"aioredis": {
|
||||
"hashes": [
|
||||
@ -148,11 +148,10 @@
|
||||
},
|
||||
"autobahn": {
|
||||
"hashes": [
|
||||
"sha256:9195df8af03b0ff29ccd4b7f5abbde957ee90273465942205f9a1bad6c3f07ac",
|
||||
"sha256:e126c1f583e872fb59e79d36977cfa1f2d0a8a79f90ae31f406faae7664b8e03"
|
||||
"sha256:bd6f46315419ca0a5be4109f737410208ad5f19718f67ca6a4a674cc66ca9b18"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==21.3.1"
|
||||
"version": "==21.11.1"
|
||||
},
|
||||
"automat": {
|
||||
"hashes": [
|
||||
@ -170,19 +169,19 @@
|
||||
},
|
||||
"boto3": {
|
||||
"hashes": [
|
||||
"sha256:2a3d7ddbf68d3ac81c19051d8715207043e09b6731a0e1f699aa2d2ecad3720e",
|
||||
"sha256:5ebcf0a585779e25867a0357de489e4602e5cd3e0b93c7b0f1c3013638268b3c"
|
||||
"sha256:035191ad6c7e8aed972e1374f4e0ecb38767c497fd6c961e4ae33898b62f78fb",
|
||||
"sha256:cd58563dd3f36d5909815752b12c80a2c510c051474f8296e28dbd3ef5634d65"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.20.4"
|
||||
"version": "==1.20.11"
|
||||
},
|
||||
"botocore": {
|
||||
"hashes": [
|
||||
"sha256:9222f2478dda5e2dd901dc9a3c9e335fc4c8919406dcae2225944674f2d13c0c",
|
||||
"sha256:cc487360e9dffd9b701c0bbf4dba4b4bb36af7882e8411594106dd1127a3b107"
|
||||
"sha256:133fa0837762587fb4e5da3fb61ac0b45495cd9fd2d2be7679ba64899da1f3ba",
|
||||
"sha256:497234f137810909289a600433cec5583ea8dc05a78b644653d76484138d78b9"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==1.23.4"
|
||||
"version": "==1.23.11"
|
||||
},
|
||||
"cachetools": {
|
||||
"hashes": [
|
||||
@ -213,11 +212,11 @@
|
||||
},
|
||||
"celery": {
|
||||
"hashes": [
|
||||
"sha256:1664b8cf5051c86188e86f1afb85213927f92b8818e2315e34b010da0d767b98",
|
||||
"sha256:4d858a8fe53c07a9f0cbf8cf1da28e8abe5464d0aba5713bf83908e74277734b"
|
||||
"sha256:b41a590b49caf8e6498a57db628e580d5f8dc6febda0f42de5d783aed5b7f808",
|
||||
"sha256:cc63ea6572d558be65297ba6db7a7979e64c0a3d0d61212d6302ef1ca05a0d22"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==5.2.0"
|
||||
"version": "==5.2.1"
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
@ -302,7 +301,7 @@
|
||||
"sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0",
|
||||
"sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b"
|
||||
],
|
||||
"markers": "python_version >= '3'",
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==2.0.7"
|
||||
},
|
||||
"click": {
|
||||
@ -360,28 +359,30 @@
|
||||
},
|
||||
"cryptography": {
|
||||
"hashes": [
|
||||
"sha256:07bb7fbfb5de0980590ddfc7f13081520def06dc9ed214000ad4372fb4e3c7f6",
|
||||
"sha256:18d90f4711bf63e2fb21e8c8e51ed8189438e6b35a6d996201ebd98a26abbbe6",
|
||||
"sha256:1ed82abf16df40a60942a8c211251ae72858b25b7421ce2497c2eb7a1cee817c",
|
||||
"sha256:22a38e96118a4ce3b97509443feace1d1011d0571fae81fc3ad35f25ba3ea999",
|
||||
"sha256:2d69645f535f4b2c722cfb07a8eab916265545b3475fdb34e0be2f4ee8b0b15e",
|
||||
"sha256:4a2d0e0acc20ede0f06ef7aa58546eee96d2592c00f450c9acb89c5879b61992",
|
||||
"sha256:54b2605e5475944e2213258e0ab8696f4f357a31371e538ef21e8d61c843c28d",
|
||||
"sha256:7075b304cd567694dc692ffc9747f3e9cb393cc4aa4fb7b9f3abd6f5c4e43588",
|
||||
"sha256:7b7ceeff114c31f285528ba8b390d3e9cfa2da17b56f11d366769a807f17cbaa",
|
||||
"sha256:7eba2cebca600a7806b893cb1d541a6e910afa87e97acf2021a22b32da1df52d",
|
||||
"sha256:928185a6d1ccdb816e883f56ebe92e975a262d31cc536429041921f8cb5a62fd",
|
||||
"sha256:9933f28f70d0517686bd7de36166dda42094eac49415459d9bdf5e7df3e0086d",
|
||||
"sha256:a688ebcd08250eab5bb5bca318cc05a8c66de5e4171a65ca51db6bd753ff8953",
|
||||
"sha256:abb5a361d2585bb95012a19ed9b2c8f412c5d723a9836418fab7aaa0243e67d2",
|
||||
"sha256:c10c797ac89c746e488d2ee92bd4abd593615694ee17b2500578b63cad6b93a8",
|
||||
"sha256:ced40344e811d6abba00295ced98c01aecf0c2de39481792d87af4fa58b7b4d6",
|
||||
"sha256:d57e0cdc1b44b6cdf8af1d01807db06886f10177469312fbde8f44ccbb284bc9",
|
||||
"sha256:d99915d6ab265c22873f1b4d6ea5ef462ef797b4140be4c9d8b179915e0985c6",
|
||||
"sha256:eb80e8a1f91e4b7ef8b33041591e6d89b2b8e122d787e87eeb2b08da71bb16ad",
|
||||
"sha256:ebeddd119f526bcf323a89f853afb12e225902a24d29b55fe18dd6fcb2838a76"
|
||||
"sha256:2049f8b87f449fc6190350de443ee0c1dd631f2ce4fa99efad2984de81031681",
|
||||
"sha256:231c4a69b11f6af79c1495a0e5a85909686ea8db946935224b7825cfb53827ed",
|
||||
"sha256:24469d9d33217ffd0ce4582dfcf2a76671af115663a95328f63c99ec7ece61a4",
|
||||
"sha256:2deab5ec05d83ddcf9b0916319674d3dae88b0e7ee18f8962642d3cde0496568",
|
||||
"sha256:494106e9cd945c2cadfce5374fa44c94cfadf01d4566a3b13bb487d2e6c7959e",
|
||||
"sha256:4c702855cd3174666ef0d2d13dcc879090aa9c6c38f5578896407a7028f75b9f",
|
||||
"sha256:52f769ecb4ef39865719aedc67b4b7eae167bafa48dbc2a26dd36fa56460507f",
|
||||
"sha256:5c49c9e8fb26a567a2b3fa0343c89f5d325447956cc2fc7231c943b29a973712",
|
||||
"sha256:684993ff6f67000a56454b41bdc7e015429732d65a52d06385b6e9de6181c71e",
|
||||
"sha256:6fbbbb8aab4053fa018984bb0e95a16faeb051dd8cca15add2a27e267ba02b58",
|
||||
"sha256:8982c19bb90a4fa2aad3d635c6d71814e38b643649b4000a8419f8691f20ac44",
|
||||
"sha256:9511416e85e449fe1de73f7f99b21b3aa04fba4c4d335d30c486ba3756e3a2a6",
|
||||
"sha256:97199a13b772e74cdcdb03760c32109c808aff7cd49c29e9cf4b7754bb725d1d",
|
||||
"sha256:a776bae1629c8d7198396fd93ec0265f8dd2341c553dc32b976168aaf0e6a636",
|
||||
"sha256:aa94d617a4cd4cdf4af9b5af65100c036bce22280ebb15d8b5262e8273ebc6ba",
|
||||
"sha256:b17d83b3d1610e571fedac21b2eb36b816654d6f7496004d6a0d32f99d1d8120",
|
||||
"sha256:d73e3a96c38173e0aa5646c31bf8473bc3564837977dd480f5cbeacf1d7ef3a3",
|
||||
"sha256:d91bc9f535599bed58f6d2e21a2724cb0c3895bf41c6403fe881391d29096f1d",
|
||||
"sha256:ef216d13ac8d24d9cd851776662f75f8d29c9f2d05cdcc2d34a18d32463a9b0b",
|
||||
"sha256:f6a5a85beb33e57998dc605b9dbe7deaa806385fdf5c4810fb849fcd04640c81",
|
||||
"sha256:f92556f94e476c1b616e6daec5f7ddded2c082efa7cee7f31c7aeda615906ed8"
|
||||
],
|
||||
"version": "==35.0.0"
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==36.0.0"
|
||||
},
|
||||
"dacite": {
|
||||
"hashes": [
|
||||
@ -415,6 +416,14 @@
|
||||
"index": "pypi",
|
||||
"version": "==0.7.1"
|
||||
},
|
||||
"deprecated": {
|
||||
"hashes": [
|
||||
"sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d",
|
||||
"sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.2.13"
|
||||
},
|
||||
"django": {
|
||||
"hashes": [
|
||||
"sha256:51284300f1522ffcdb07ccbdf676a307c6678659e1284f0618e5a774127a6a08",
|
||||
@ -611,11 +620,11 @@
|
||||
},
|
||||
"geoip2": {
|
||||
"hashes": [
|
||||
"sha256:f150bed3190d543712a17467208388d31bd8ddb49b2226fba53db8aaedb8ba89",
|
||||
"sha256:f9172cdfb2a5f9225ace5e30dd7426413ad28798a5f474cd1538780686bd6a87"
|
||||
"sha256:2aed03b45f8037b9e16f02567bcf3623801d6d20689c5fc5676d7c08ed067334",
|
||||
"sha256:b542252e87eb40adc3a2fc0f4e84b514c4c5e04ed46923a3a74d509f25f3103a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.4.0"
|
||||
"version": "==4.5.0"
|
||||
},
|
||||
"google-auth": {
|
||||
"hashes": [
|
||||
@ -755,11 +764,11 @@
|
||||
},
|
||||
"kombu": {
|
||||
"hashes": [
|
||||
"sha256:31bd287191bf56b1addba54a28eced8d6b6b5ba57ad184f48b065578f73c8e33",
|
||||
"sha256:f262a2adc71b53e5b7dad4933bbdee65d8766ca4df6a9043b13edaad2144aaec"
|
||||
"sha256:0f5d0763fb916808f617b886697b2be28e6bc35026f08e679697fc814b48a608",
|
||||
"sha256:d36f0cde6a18d9eb7b6b3aa62a59bfdff7f5724689850e447eca5be8efc9d501"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==5.2.1"
|
||||
"version": "==5.2.2"
|
||||
},
|
||||
"kubernetes": {
|
||||
"hashes": [
|
||||
@ -767,7 +776,7 @@
|
||||
"sha256:52312adda60d92ba45b325f2c1505924656389222005f7e089718e1ad03bc07f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==19.15.0"
|
||||
"version": "==v19.15.0"
|
||||
},
|
||||
"ldap3": {
|
||||
"hashes": [
|
||||
@ -974,11 +983,11 @@
|
||||
},
|
||||
"packaging": {
|
||||
"hashes": [
|
||||
"sha256:096d689d78ca690e4cd8a89568ba06d07ca097e3306a4381635073ca91479966",
|
||||
"sha256:14317396d1e8cdb122989b916fa2c7e9ca8e2be9e8060a6eff75b6b7b4d8a7e0"
|
||||
"sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb",
|
||||
"sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==21.2"
|
||||
"version": "==21.3"
|
||||
},
|
||||
"prometheus-client": {
|
||||
"hashes": [
|
||||
@ -999,6 +1008,8 @@
|
||||
"psycopg2-binary": {
|
||||
"hashes": [
|
||||
"sha256:029e09a892b9ebc3c77851f69ce0720e1b72a9c6850460cee49b14dfbf9ccdd2",
|
||||
"sha256:108b0380969ddab7c8ef2a813a57f87b308b2f88ec15f1a1e7b653964a3cfb25",
|
||||
"sha256:14427437117f38e65f71db65d8eafd0e86837be456567798712b8da89db2b2dd",
|
||||
"sha256:234b1f48488b2f86aac04fb00cb04e5e9bcb960f34fa8a8e41b73149d581a93b",
|
||||
"sha256:2e4bbcfb403221ea1953f3e0a85cef00ed15c1683a66cf35c956a7e37c33a4c4",
|
||||
"sha256:2eecbdc5fa5886f2dd6cc673ce4291cc0fb8900965315268960ad9c2477f8276",
|
||||
@ -1013,17 +1024,25 @@
|
||||
"sha256:578c279cd1ce04f05ae0912530ece00bab92854911808e5aec27588aba87e361",
|
||||
"sha256:717525cdc97b23182ff6f470fb5bf6f0bc796b5a7000c6f6699d6679991e4a5e",
|
||||
"sha256:7585ca73dcfe326f31fafa8f96e6bb98ea9e9e46c7a1924ec8101d797914ae27",
|
||||
"sha256:7e6bd4f532c2cd297b81114526176b240109a1c52020adca69c3f3226c65dc18",
|
||||
"sha256:8d2aafe46eb87742425ece38130510fbb035787ee89a329af299029c4d9ae318",
|
||||
"sha256:9c0aaad07941419926b9bd00171e49fe6b06e42e5527fb91671e137fe6c93d77",
|
||||
"sha256:a04cfa231e7d9b63639e62166a4051cb47ca599fa341463fa3e1c48585fcee64",
|
||||
"sha256:a1852c5bef7e5f52bd43fde5eda610d4df0fb2efc31028150933e84b4140d47a",
|
||||
"sha256:a507db7758953b1b170c4310691a1a89877029b1e11b08ba5fc8ae3ddb35596b",
|
||||
"sha256:a77e98c68b0e6c51d4d6a994d22b30e77276cbd33e4aabdde03b9ad3a2c148aa",
|
||||
"sha256:aa2847d8073951dbc84c4f8b32c620764db3c2eb0d99a04835fecfab7d04816e",
|
||||
"sha256:b592f09ff18cfcc9037b9a976fcd62db48cae9dbd5385f2471d4c2ba40c52b4d",
|
||||
"sha256:b9d45374ba98c1184df9cce93a0b766097544f8bdfcd5de83ff10f939c193125",
|
||||
"sha256:bf31e6fdb4ec1f6d98a07f48836508ed6edd19b48b13bbf168fbc1bd014b4ca2",
|
||||
"sha256:c0e1fb7097ded2cc44d9037cfc68ad86a30341261492e7de95d180e534969fb2",
|
||||
"sha256:c6e16e085fe6dc6c099ee0be56657aa9ad71027465ef9591d302ba230c404c7e",
|
||||
"sha256:daf6b5c62eb738872d61a1fa740d7768904911ba5a7e055ed72169d379b58beb",
|
||||
"sha256:db1b03c189f85b8df29030ad32d521dd7dcb862fd5f8892035314f5b886e70ce",
|
||||
"sha256:eeee7b18c51d02e49bf1984d7af26e8843fe68e31fa1cbab5366ebdfa1c89ade",
|
||||
"sha256:f4dff0f15af6936c6fe6da7067b4216edbbe076ad8625da819cc066591b1133c"
|
||||
"sha256:ef97578fab5115e3af4334dd3376dea3c3a79328a3314b21ec7ced02920b916d",
|
||||
"sha256:f4dff0f15af6936c6fe6da7067b4216edbbe076ad8625da819cc066591b1133c",
|
||||
"sha256:f9c37ecb173d76cf49e519133fd70851b8f9c38b6b8c1cb7fcfc71368d4cc6fc"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.9.2"
|
||||
@ -1153,11 +1172,11 @@
|
||||
},
|
||||
"pyparsing": {
|
||||
"hashes": [
|
||||
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
|
||||
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
|
||||
"sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4",
|
||||
"sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.4.7"
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==3.0.6"
|
||||
},
|
||||
"pyrsistent": {
|
||||
"hashes": [
|
||||
@ -1249,11 +1268,11 @@
|
||||
},
|
||||
"redis": {
|
||||
"hashes": [
|
||||
"sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2",
|
||||
"sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"
|
||||
"sha256:c8481cf414474e3497ec7971a1ba9b998c8efad0f0d289a009a5bbef040894f9",
|
||||
"sha256:ccf692811f2c1fc7a92b466aa2599e4a6d2d73d5f736a2c70be600657c0da34a"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==3.5.3"
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==4.0.2"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
@ -1290,11 +1309,11 @@
|
||||
},
|
||||
"sentry-sdk": {
|
||||
"hashes": [
|
||||
"sha256:b9844751e40710e84a457c5bc29b21c383ccb2b63d76eeaad72f7f1c808c8828",
|
||||
"sha256:c091cc7115ff25fe3a0e410dbecd7a996f81a3f6137d2272daef32d6c3cfa6dc"
|
||||
"sha256:0db297ab32e095705c20f742c3a5dac62fe15c4318681884053d0898e5abb2f6",
|
||||
"sha256:789a11a87ca02491896e121efdd64e8fd93327b69e8f2f7d42f03e2569648e88"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.4.3"
|
||||
"version": "==1.5.0"
|
||||
},
|
||||
"service-identity": {
|
||||
"hashes": [
|
||||
@ -1304,6 +1323,14 @@
|
||||
"index": "pypi",
|
||||
"version": "==21.1.0"
|
||||
},
|
||||
"setuptools": {
|
||||
"hashes": [
|
||||
"sha256:157d21de9d055ab9e8ea3186d91e7f4f865e11f42deafa952d90842671fc2576",
|
||||
"sha256:4adde3d1e1c89bde1c643c64d89cdd94cbfd8c75252ee459d4500bccb9c7d05d"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==59.2.0"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
|
||||
@ -1322,11 +1349,11 @@
|
||||
},
|
||||
"structlog": {
|
||||
"hashes": [
|
||||
"sha256:63a7111a32e5b615671536bb745692ea02cebfea2b39dcb7d2617eed19437cfe",
|
||||
"sha256:7ac42b565e1295712313f91edbcb64e0840a9037d888c8954f11fa6c43270e99"
|
||||
"sha256:063216becff8e6f6558122a9b00734f7e50bfef309eb730c85a52c74ed861a96",
|
||||
"sha256:4da2aec0aebf6dee7beb884eb0fda26ed9d6cce5338fcd523e8597d0f1826746"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==21.2.0"
|
||||
"version": "==21.3.0"
|
||||
},
|
||||
"swagger-spec-validator": {
|
||||
"hashes": [
|
||||
@ -1337,9 +1364,7 @@
|
||||
"version": "==2.7.4"
|
||||
},
|
||||
"twisted": {
|
||||
"extras": [
|
||||
"tls"
|
||||
],
|
||||
"extras": [],
|
||||
"hashes": [
|
||||
"sha256:13c1d1d2421ae556d91e81e66cf0d4f4e4e1e4a36a0486933bee4305c6a4fb9b",
|
||||
"sha256:2cd652542463277378b0d349f47c62f20d9306e57d1247baabd6d1d38a109006"
|
||||
@ -1357,11 +1382,11 @@
|
||||
},
|
||||
"typing-extensions": {
|
||||
"hashes": [
|
||||
"sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e",
|
||||
"sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7",
|
||||
"sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"
|
||||
"sha256:2cdf80e4e04866a9b3689a51869016d36db0814d84b8d8a568d22781d45d27ed",
|
||||
"sha256:829704698b22e13ec9eaf959122315eabb370b0884400e9818334d8b677023d9"
|
||||
],
|
||||
"version": "==3.10.0.2"
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==4.0.0"
|
||||
},
|
||||
"ua-parser": {
|
||||
"hashes": [
|
||||
@ -1462,33 +1487,113 @@
|
||||
},
|
||||
"websockets": {
|
||||
"hashes": [
|
||||
"sha256:01db0ecd1a0ca6702d02a5ed40413e18b7d22f94afb3bbe0d323bac86c42c1c8",
|
||||
"sha256:085bb8a6e780d30eaa1ba48ac7f3a6707f925edea787cfb761ce5a39e77ac09b",
|
||||
"sha256:1ac35426fe3e7d3d0fac3d63c8965c76ed67a8fd713937be072bf0ce22808539",
|
||||
"sha256:1f6b814cff6aadc4288297cb3a248614829c6e4ff5556593c44a115e9dd49939",
|
||||
"sha256:2a43072e434c041a99f2e1eb9b692df0232a38c37c61d00e9f24db79474329e4",
|
||||
"sha256:5b2600e01c7ca6f840c42c747ffbe0254f319594ed108db847eb3d75f4aacb80",
|
||||
"sha256:62160772314920397f9d219147f958b33fa27a12c662d4455c9ccbba9a07e474",
|
||||
"sha256:706e200fc7f03bed99ad0574cd1ea8b0951477dd18cc978ccb190683c69dba76",
|
||||
"sha256:71358c7816e2762f3e4af3adf0040f268e219f5a38cb3487a9d0fc2e554fef6a",
|
||||
"sha256:7d2e12e4f901f1bc062dfdf91831712c4106ed18a9a4cdb65e2e5f502124ca37",
|
||||
"sha256:7f79f02c7f9a8320aff7d3321cd1c7e3a7dbc15d922ac996cca827301ee75238",
|
||||
"sha256:82b17524b1ce6ae7f7dd93e4d18e9b9474071e28b65dbf1dfe9b5767778db379",
|
||||
"sha256:82bd921885231f4a30d9bc550552495b3fc36b1235add6d374e7c65c3babd805",
|
||||
"sha256:8bbf8660c3f833ddc8b1afab90213f2e672a9ddac6eecb3cde968e6b2807c1c7",
|
||||
"sha256:9a4d889162bd48588e80950e07fa5e039eee9deb76a58092e8c3ece96d7ef537",
|
||||
"sha256:b4ade7569b6fd17912452f9c3757d96f8e4044016b6d22b3b8391e641ca50456",
|
||||
"sha256:b8176deb6be540a46695960a765a77c28ac8b2e3ef2ec95d50a4f5df901edb1c",
|
||||
"sha256:c4fc9a1d242317892590abe5b61a9127f1a61740477bfb121743f290b8054002",
|
||||
"sha256:c5880442f5fc268f1ef6d37b2c152c114deccca73f48e3a8c48004d2f16f4567",
|
||||
"sha256:cd8c6f2ec24aedace251017bc7a414525171d4e6578f914acab9349362def4da",
|
||||
"sha256:d67646ddd17a86117ae21c27005d83c1895c0cef5d7be548b7549646372f868a",
|
||||
"sha256:e42a1f1e03437b017af341e9bbfdc09252cd48ef32a8c3c3ead769eab3b17368",
|
||||
"sha256:eb282127e9c136f860c6068a4fba5756eb25e755baffb5940b6f1eae071928b2",
|
||||
"sha256:fe83b3ec9ef34063d86dfe1029160a85f24a5a94271036e5714a57acfdd089a1",
|
||||
"sha256:ff59c6bdb87b31f7e2d596f09353d5a38c8c8ff571b0e2238e8ee2d55ad68465"
|
||||
"sha256:002071169d2e44ce8eb9e5ebac9fbce142ba4b5146eef1cfb16b177a27662657",
|
||||
"sha256:05e7f098c76b0a4743716590bb8f9706de19f1ef5148d61d0cf76495ec3edb9c",
|
||||
"sha256:08a42856158307e231b199671c4fce52df5786dd3d703f36b5d8ac76b206c485",
|
||||
"sha256:0d93b7cadc761347d98da12ec1930b5c71b2096f1ceed213973e3cda23fead9c",
|
||||
"sha256:10edd9d7d3581cfb9ff544ac09fc98cab7ee8f26778a5a8b2d5fd4b0684c5ba5",
|
||||
"sha256:14e9cf68a08d1a5d42109549201aefba473b1d925d233ae19035c876dd845da9",
|
||||
"sha256:181d2b25de5a437b36aefedaf006ecb6fa3aa1328ec0236cdde15f32f9d3ff6d",
|
||||
"sha256:189ed478395967d6a98bb293abf04e8815349e17456a0a15511f1088b6cb26e4",
|
||||
"sha256:1d858fb31e5ac992a2cdf17e874c95f8a5b1e917e1fb6b45ad85da30734b223f",
|
||||
"sha256:1dafe98698ece09b8ccba81b910643ff37198e43521d977be76caf37709cf62b",
|
||||
"sha256:3477146d1f87ead8df0f27e8960249f5248dceb7c2741e8bbec9aa5338d0c053",
|
||||
"sha256:38db6e2163b021642d0a43200ee2dec8f4980bdbda96db54fde72b283b54cbfc",
|
||||
"sha256:3a02ab91d84d9056a9ee833c254895421a6333d7ae7fff94b5c68e4fa8095519",
|
||||
"sha256:3bbf080f3892ba1dc8838786ec02899516a9d227abe14a80ef6fd17d4fb57127",
|
||||
"sha256:3ef6f73854cded34e78390dbdf40dfdcf0b89b55c0e282468ef92646fce8d13a",
|
||||
"sha256:468f0031fdbf4d643f89403a66383247eb82803430b14fa27ce2d44d2662ca37",
|
||||
"sha256:483edee5abed738a0b6a908025be47f33634c2ad8e737edd03ffa895bd600909",
|
||||
"sha256:531d8eb013a9bc6b3ad101588182aa9b6dd994b190c56df07f0d84a02b85d530",
|
||||
"sha256:5560558b0dace8312c46aa8915da977db02738ac8ecffbc61acfbfe103e10155",
|
||||
"sha256:5bb6256de5a4fb1d42b3747b4e2268706c92965d75d0425be97186615bf2f24f",
|
||||
"sha256:667c41351a6d8a34b53857ceb8343a45c85d438ee4fd835c279591db8aeb85be",
|
||||
"sha256:6b014875fae19577a392372075e937ebfebf53fd57f613df07b35ab210f31534",
|
||||
"sha256:6fdec1a0b3e5630c58e3d8704d2011c678929fce90b40908c97dfc47de8dca72",
|
||||
"sha256:7bdd3d26315db0a9cf8a0af30ca95e0aa342eda9c1377b722e71ccd86bc5d1dd",
|
||||
"sha256:7c9407719f42cb77049975410490c58a705da6af541adb64716573e550e5c9db",
|
||||
"sha256:7d6673b2753f9c5377868a53445d0c321ef41ff3c8e3b6d57868e72054bfce5f",
|
||||
"sha256:816ae7dac2c6522cfa620947ead0ca95ac654916eebf515c94d7c28de5601a6e",
|
||||
"sha256:882c0b8bdff3bf1bd7f024ce17c6b8006042ec4cceba95cf15df57e57efa471c",
|
||||
"sha256:8877861e3dee38c8d302eee0d5dbefa6663de3b46dc6a888f70cd7e82562d1f7",
|
||||
"sha256:888a5fa2a677e0c2b944f9826c756475980f1b276b6302e606f5c4ff5635be9e",
|
||||
"sha256:89e985d40d407545d5f5e2e58e1fdf19a22bd2d8cd54d20a882e29f97e930a0a",
|
||||
"sha256:97b4b68a2ddaf5c4707ae79c110bfd874c5be3c6ac49261160fb243fa45d8bbb",
|
||||
"sha256:98de71f86bdb29430fd7ba9997f47a6b10866800e3ea577598a786a785701bb0",
|
||||
"sha256:9f304a22ece735a3da8a51309bc2c010e23961a8f675fae46fdf62541ed62123",
|
||||
"sha256:9fd62c6dc83d5d35fb6a84ff82ec69df8f4657fff05f9cd6c7d9bec0dd57f0f6",
|
||||
"sha256:a249139abc62ef333e9e85064c27fefb113b16ffc5686cefc315bdaef3eefbc8",
|
||||
"sha256:b66e6d514f12c28d7a2d80bb2a48ef223342e99c449782d9831b0d29a9e88a17",
|
||||
"sha256:b68b6caecb9a0c6db537aa79750d1b592a841e4f1a380c6196091e65b2ad35f9",
|
||||
"sha256:baa83174390c0ff4fc1304fbe24393843ac7a08fdd59295759c4b439e06b1536",
|
||||
"sha256:bb01ea7b5f52e7125bdc3c5807aeaa2d08a0553979cf2d96a8b7803ea33e15e7",
|
||||
"sha256:cfae282c2aa7f0c4be45df65c248481f3509f8c40ca8b15ed96c35668ae0ff69",
|
||||
"sha256:d0d81b46a5c87d443e40ce2272436da8e6092aa91f5fbeb60d1be9f11eff5b4c",
|
||||
"sha256:d9b245db5a7e64c95816e27d72830e51411c4609c05673d1ae81eb5d23b0be54",
|
||||
"sha256:ddab2dc69ee5ae27c74dbfe9d7bb6fee260826c136dca257faa1a41d1db61a89",
|
||||
"sha256:e1b60fd297adb9fc78375778a5220da7f07bf54d2a33ac781319650413fc6a60",
|
||||
"sha256:e259be0863770cb91b1a6ccf6907f1ac2f07eff0b7f01c249ed751865a70cb0d",
|
||||
"sha256:e3872ae57acd4306ecf937d36177854e218e999af410a05c17168cd99676c512",
|
||||
"sha256:e4819c6fb4f336fd5388372cb556b1f3a165f3f68e66913d1a2fc1de55dc6f58"
|
||||
],
|
||||
"version": "==10.0"
|
||||
"version": "==10.1"
|
||||
},
|
||||
"wrapt": {
|
||||
"hashes": [
|
||||
"sha256:086218a72ec7d986a3eddb7707c8c4526d677c7b35e355875a0fe2918b059179",
|
||||
"sha256:0877fe981fd76b183711d767500e6b3111378ed2043c145e21816ee589d91096",
|
||||
"sha256:0a017a667d1f7411816e4bf214646d0ad5b1da2c1ea13dec6c162736ff25a374",
|
||||
"sha256:0cb23d36ed03bf46b894cfec777eec754146d68429c30431c99ef28482b5c1df",
|
||||
"sha256:1fea9cd438686e6682271d36f3481a9f3636195578bab9ca3382e2f5f01fc185",
|
||||
"sha256:220a869982ea9023e163ba915077816ca439489de6d2c09089b219f4e11b6785",
|
||||
"sha256:25b1b1d5df495d82be1c9d2fad408f7ce5ca8a38085e2da41bb63c914baadff7",
|
||||
"sha256:2dded5496e8f1592ec27079b28b6ad2a1ef0b9296d270f77b8e4a3a796cf6909",
|
||||
"sha256:2ebdde19cd3c8cdf8df3fc165bc7827334bc4e353465048b36f7deeae8ee0918",
|
||||
"sha256:43e69ffe47e3609a6aec0fe723001c60c65305784d964f5007d5b4fb1bc6bf33",
|
||||
"sha256:46f7f3af321a573fc0c3586612db4decb7eb37172af1bc6173d81f5b66c2e068",
|
||||
"sha256:47f0a183743e7f71f29e4e21574ad3fa95676136f45b91afcf83f6a050914829",
|
||||
"sha256:498e6217523111d07cd67e87a791f5e9ee769f9241fcf8a379696e25806965af",
|
||||
"sha256:4b9c458732450ec42578b5642ac53e312092acf8c0bfce140ada5ca1ac556f79",
|
||||
"sha256:51799ca950cfee9396a87f4a1240622ac38973b6df5ef7a41e7f0b98797099ce",
|
||||
"sha256:5601f44a0f38fed36cc07db004f0eedeaadbdcec90e4e90509480e7e6060a5bc",
|
||||
"sha256:5f223101f21cfd41deec8ce3889dc59f88a59b409db028c469c9b20cfeefbe36",
|
||||
"sha256:610f5f83dd1e0ad40254c306f4764fcdc846641f120c3cf424ff57a19d5f7ade",
|
||||
"sha256:6a03d9917aee887690aa3f1747ce634e610f6db6f6b332b35c2dd89412912bca",
|
||||
"sha256:705e2af1f7be4707e49ced9153f8d72131090e52be9278b5dbb1498c749a1e32",
|
||||
"sha256:766b32c762e07e26f50d8a3468e3b4228b3736c805018e4b0ec8cc01ecd88125",
|
||||
"sha256:77416e6b17926d953b5c666a3cb718d5945df63ecf922af0ee576206d7033b5e",
|
||||
"sha256:778fd096ee96890c10ce96187c76b3e99b2da44e08c9e24d5652f356873f6709",
|
||||
"sha256:78dea98c81915bbf510eb6a3c9c24915e4660302937b9ae05a0947164248020f",
|
||||
"sha256:7dd215e4e8514004c8d810a73e342c536547038fb130205ec4bba9f5de35d45b",
|
||||
"sha256:7dde79d007cd6dfa65afe404766057c2409316135cb892be4b1c768e3f3a11cb",
|
||||
"sha256:81bd7c90d28a4b2e1df135bfbd7c23aee3050078ca6441bead44c42483f9ebfb",
|
||||
"sha256:85148f4225287b6a0665eef08a178c15097366d46b210574a658c1ff5b377489",
|
||||
"sha256:865c0b50003616f05858b22174c40ffc27a38e67359fa1495605f96125f76640",
|
||||
"sha256:87883690cae293541e08ba2da22cacaae0a092e0ed56bbba8d018cc486fbafbb",
|
||||
"sha256:8aab36778fa9bba1a8f06a4919556f9f8c7b33102bd71b3ab307bb3fecb21851",
|
||||
"sha256:8c73c1a2ec7c98d7eaded149f6d225a692caa1bd7b2401a14125446e9e90410d",
|
||||
"sha256:936503cb0a6ed28dbfa87e8fcd0a56458822144e9d11a49ccee6d9a8adb2ac44",
|
||||
"sha256:944b180f61f5e36c0634d3202ba8509b986b5fbaf57db3e94df11abee244ba13",
|
||||
"sha256:96b81ae75591a795d8c90edc0bfaab44d3d41ffc1aae4d994c5aa21d9b8e19a2",
|
||||
"sha256:981da26722bebb9247a0601e2922cedf8bb7a600e89c852d063313102de6f2cb",
|
||||
"sha256:ae9de71eb60940e58207f8e71fe113c639da42adb02fb2bcbcaccc1ccecd092b",
|
||||
"sha256:b73d4b78807bd299b38e4598b8e7bd34ed55d480160d2e7fdaabd9931afa65f9",
|
||||
"sha256:d4a5f6146cfa5c7ba0134249665acd322a70d1ea61732723c7d3e8cc0fa80755",
|
||||
"sha256:dd91006848eb55af2159375134d724032a2d1d13bcc6f81cd8d3ed9f2b8e846c",
|
||||
"sha256:e05e60ff3b2b0342153be4d1b597bbcfd8330890056b9619f4ad6b8d5c96a81a",
|
||||
"sha256:e6906d6f48437dfd80464f7d7af1740eadc572b9f7a4301e7dd3d65db285cacf",
|
||||
"sha256:e92d0d4fa68ea0c02d39f1e2f9cb5bc4b4a71e8c442207433d8db47ee79d7aa3",
|
||||
"sha256:e94b7d9deaa4cc7bac9198a58a7240aaf87fe56c6277ee25fa5b3aa1edebd229",
|
||||
"sha256:ea3e746e29d4000cd98d572f3ee2a6050a4f784bb536f4ac1f035987fc1ed83e",
|
||||
"sha256:ec7e20258ecc5174029a0f391e1b948bf2906cd64c198a9b8b281b811cbc04de",
|
||||
"sha256:ec9465dd69d5657b5d2fa6133b3e1e989ae27d29471a672416fd729b429eb554",
|
||||
"sha256:f122ccd12fdc69628786d0c947bdd9cb2733be8f800d88b5a37c57f1f1d73c10",
|
||||
"sha256:f99c0489258086308aad4ae57da9e8ecf9e1f3f30fa35d5e170b4d4896554d80",
|
||||
"sha256:f9c51d9af9abb899bd34ace878fbec8bf357b3194a10c4e8e0a25512826ef056",
|
||||
"sha256:fd76c47f20984b43d93de9a82011bb6e5f8325df6c9ed4d8310029a55fa361ea"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==1.13.3"
|
||||
},
|
||||
"xmlsec": {
|
||||
"hashes": [
|
||||
@ -1646,11 +1751,11 @@
|
||||
"develop": {
|
||||
"astroid": {
|
||||
"hashes": [
|
||||
"sha256:0755c998e7117078dcb7d0bda621391dd2a85da48052d948c7411ab187325346",
|
||||
"sha256:1e83a69fd51b013ebf5912d26b9338d6643a55fec2f20c787792680610eed4a2"
|
||||
"sha256:5f6f75e45f15290e73b56f9dfde95b4bf96382284cde406ef4203e928335a495",
|
||||
"sha256:cd8326b424c971e7d87678609cf6275d22028afd37d6ac59c16d47f1245882f6"
|
||||
],
|
||||
"markers": "python_version ~= '3.6'",
|
||||
"version": "==2.8.4"
|
||||
"version": "==2.8.6"
|
||||
},
|
||||
"async-generator": {
|
||||
"hashes": [
|
||||
@ -1670,19 +1775,19 @@
|
||||
},
|
||||
"bandit": {
|
||||
"hashes": [
|
||||
"sha256:216be4d044209fa06cf2a3e51b319769a51be8318140659719aa7a115c35ed07",
|
||||
"sha256:8a4c7415254d75df8ff3c3b15cfe9042ecee628a1e40b44c15a98890fbfc2608"
|
||||
"sha256:a81b00b5436e6880fa8ad6799bc830e02032047713cbb143a12939ac67eb756c",
|
||||
"sha256:f5acd838e59c038a159b5c621cf0f8270b279e884eadd7b782d7491c02add0d4"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.7.0"
|
||||
"version": "==1.7.1"
|
||||
},
|
||||
"black": {
|
||||
"hashes": [
|
||||
"sha256:380f1b5da05e5a1429225676655dddb96f5ae8c75bdf91e53d798871b902a115",
|
||||
"sha256:7de4cfc7eb6b710de325712d40125689101d21d25283eed7e9998722cf10eb91"
|
||||
"sha256:802c6c30b637b28645b7fde282ed2569c0cd777dbe493a41b6a03c1d903f99ac",
|
||||
"sha256:a042adbb18b3262faad5aff4e834ff186bb893f95ba3a8013f09de1e5569def2"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==21.9b0"
|
||||
"version": "==21.11b1"
|
||||
},
|
||||
"bump2version": {
|
||||
"hashes": [
|
||||
@ -1759,7 +1864,7 @@
|
||||
"sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0",
|
||||
"sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b"
|
||||
],
|
||||
"markers": "python_version >= '3'",
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==2.0.7"
|
||||
},
|
||||
"click": {
|
||||
@ -1836,28 +1941,30 @@
|
||||
},
|
||||
"cryptography": {
|
||||
"hashes": [
|
||||
"sha256:07bb7fbfb5de0980590ddfc7f13081520def06dc9ed214000ad4372fb4e3c7f6",
|
||||
"sha256:18d90f4711bf63e2fb21e8c8e51ed8189438e6b35a6d996201ebd98a26abbbe6",
|
||||
"sha256:1ed82abf16df40a60942a8c211251ae72858b25b7421ce2497c2eb7a1cee817c",
|
||||
"sha256:22a38e96118a4ce3b97509443feace1d1011d0571fae81fc3ad35f25ba3ea999",
|
||||
"sha256:2d69645f535f4b2c722cfb07a8eab916265545b3475fdb34e0be2f4ee8b0b15e",
|
||||
"sha256:4a2d0e0acc20ede0f06ef7aa58546eee96d2592c00f450c9acb89c5879b61992",
|
||||
"sha256:54b2605e5475944e2213258e0ab8696f4f357a31371e538ef21e8d61c843c28d",
|
||||
"sha256:7075b304cd567694dc692ffc9747f3e9cb393cc4aa4fb7b9f3abd6f5c4e43588",
|
||||
"sha256:7b7ceeff114c31f285528ba8b390d3e9cfa2da17b56f11d366769a807f17cbaa",
|
||||
"sha256:7eba2cebca600a7806b893cb1d541a6e910afa87e97acf2021a22b32da1df52d",
|
||||
"sha256:928185a6d1ccdb816e883f56ebe92e975a262d31cc536429041921f8cb5a62fd",
|
||||
"sha256:9933f28f70d0517686bd7de36166dda42094eac49415459d9bdf5e7df3e0086d",
|
||||
"sha256:a688ebcd08250eab5bb5bca318cc05a8c66de5e4171a65ca51db6bd753ff8953",
|
||||
"sha256:abb5a361d2585bb95012a19ed9b2c8f412c5d723a9836418fab7aaa0243e67d2",
|
||||
"sha256:c10c797ac89c746e488d2ee92bd4abd593615694ee17b2500578b63cad6b93a8",
|
||||
"sha256:ced40344e811d6abba00295ced98c01aecf0c2de39481792d87af4fa58b7b4d6",
|
||||
"sha256:d57e0cdc1b44b6cdf8af1d01807db06886f10177469312fbde8f44ccbb284bc9",
|
||||
"sha256:d99915d6ab265c22873f1b4d6ea5ef462ef797b4140be4c9d8b179915e0985c6",
|
||||
"sha256:eb80e8a1f91e4b7ef8b33041591e6d89b2b8e122d787e87eeb2b08da71bb16ad",
|
||||
"sha256:ebeddd119f526bcf323a89f853afb12e225902a24d29b55fe18dd6fcb2838a76"
|
||||
"sha256:2049f8b87f449fc6190350de443ee0c1dd631f2ce4fa99efad2984de81031681",
|
||||
"sha256:231c4a69b11f6af79c1495a0e5a85909686ea8db946935224b7825cfb53827ed",
|
||||
"sha256:24469d9d33217ffd0ce4582dfcf2a76671af115663a95328f63c99ec7ece61a4",
|
||||
"sha256:2deab5ec05d83ddcf9b0916319674d3dae88b0e7ee18f8962642d3cde0496568",
|
||||
"sha256:494106e9cd945c2cadfce5374fa44c94cfadf01d4566a3b13bb487d2e6c7959e",
|
||||
"sha256:4c702855cd3174666ef0d2d13dcc879090aa9c6c38f5578896407a7028f75b9f",
|
||||
"sha256:52f769ecb4ef39865719aedc67b4b7eae167bafa48dbc2a26dd36fa56460507f",
|
||||
"sha256:5c49c9e8fb26a567a2b3fa0343c89f5d325447956cc2fc7231c943b29a973712",
|
||||
"sha256:684993ff6f67000a56454b41bdc7e015429732d65a52d06385b6e9de6181c71e",
|
||||
"sha256:6fbbbb8aab4053fa018984bb0e95a16faeb051dd8cca15add2a27e267ba02b58",
|
||||
"sha256:8982c19bb90a4fa2aad3d635c6d71814e38b643649b4000a8419f8691f20ac44",
|
||||
"sha256:9511416e85e449fe1de73f7f99b21b3aa04fba4c4d335d30c486ba3756e3a2a6",
|
||||
"sha256:97199a13b772e74cdcdb03760c32109c808aff7cd49c29e9cf4b7754bb725d1d",
|
||||
"sha256:a776bae1629c8d7198396fd93ec0265f8dd2341c553dc32b976168aaf0e6a636",
|
||||
"sha256:aa94d617a4cd4cdf4af9b5af65100c036bce22280ebb15d8b5262e8273ebc6ba",
|
||||
"sha256:b17d83b3d1610e571fedac21b2eb36b816654d6f7496004d6a0d32f99d1d8120",
|
||||
"sha256:d73e3a96c38173e0aa5646c31bf8473bc3564837977dd480f5cbeacf1d7ef3a3",
|
||||
"sha256:d91bc9f535599bed58f6d2e21a2724cb0c3895bf41c6403fe881391d29096f1d",
|
||||
"sha256:ef216d13ac8d24d9cd851776662f75f8d29c9f2d05cdcc2d34a18d32463a9b0b",
|
||||
"sha256:f6a5a85beb33e57998dc605b9dbe7deaa806385fdf5c4810fb849fcd04640c81",
|
||||
"sha256:f92556f94e476c1b616e6daec5f7ddded2c082efa7cee7f31c7aeda615906ed8"
|
||||
],
|
||||
"version": "==35.0.0"
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==36.0.0"
|
||||
},
|
||||
"gitdb": {
|
||||
"hashes": [
|
||||
@ -1890,6 +1997,14 @@
|
||||
],
|
||||
"version": "==3.3"
|
||||
},
|
||||
"importlib-metadata": {
|
||||
"hashes": [
|
||||
"sha256:53ccfd5c134223e497627b9815d5030edf77d2ed573922f7a0b8f8bb81a1c100",
|
||||
"sha256:75bdec14c397f528724c1bfd9709d660b33a4d2e77387a3358f20b848bb5e5fb"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.8.2"
|
||||
},
|
||||
"iniconfig": {
|
||||
"hashes": [
|
||||
"sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3",
|
||||
@ -1902,7 +2017,7 @@
|
||||
"sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7",
|
||||
"sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"
|
||||
],
|
||||
"markers": "python_version < '4' and python_full_version >= '3.6.1'",
|
||||
"markers": "python_version < '4.0' and python_full_version >= '3.6.1'",
|
||||
"version": "==5.10.1"
|
||||
},
|
||||
"lazy-object-proxy": {
|
||||
@ -1957,11 +2072,11 @@
|
||||
},
|
||||
"packaging": {
|
||||
"hashes": [
|
||||
"sha256:096d689d78ca690e4cd8a89568ba06d07ca097e3306a4381635073ca91479966",
|
||||
"sha256:14317396d1e8cdb122989b916fa2c7e9ca8e2be9e8060a6eff75b6b7b4d8a7e0"
|
||||
"sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb",
|
||||
"sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==21.2"
|
||||
"version": "==21.3"
|
||||
},
|
||||
"pathspec": {
|
||||
"hashes": [
|
||||
@ -1972,11 +2087,11 @@
|
||||
},
|
||||
"pbr": {
|
||||
"hashes": [
|
||||
"sha256:4651ca1445e80f2781827305de3d76b3ce53195f2227762684eb08f17bc473b7",
|
||||
"sha256:60002958e459b195e8dbe61bf22bcf344eedf1b4e03a321a5414feb15566100c"
|
||||
"sha256:176e8560eaf61e127817ef93d8a844803abb27a4d4637f0ff3bb783129be2e0a",
|
||||
"sha256:672d8ebee84921862110f23fcec2acea191ef58543d34dfe9ef3d9f13c31cddf"
|
||||
],
|
||||
"markers": "python_version >= '2.6'",
|
||||
"version": "==5.7.0"
|
||||
"version": "==5.8.0"
|
||||
},
|
||||
"platformdirs": {
|
||||
"hashes": [
|
||||
@ -2042,11 +2157,11 @@
|
||||
},
|
||||
"pyparsing": {
|
||||
"hashes": [
|
||||
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
|
||||
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
|
||||
"sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4",
|
||||
"sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.4.7"
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==3.0.6"
|
||||
},
|
||||
"pytest": {
|
||||
"hashes": [
|
||||
@ -2064,6 +2179,14 @@
|
||||
"index": "pypi",
|
||||
"version": "==4.4.0"
|
||||
},
|
||||
"pytest-randomly": {
|
||||
"hashes": [
|
||||
"sha256:2c0a332c4b124e372e2473803bcc91ec87797664f4955afef2b844c0021662b1",
|
||||
"sha256:cbd5c50b7c41491c202c71a3df33a75619d610a4f5c34aa2bd02ac30fce7cd43"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.10.2"
|
||||
},
|
||||
"pyyaml": {
|
||||
"hashes": [
|
||||
"sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293",
|
||||
@ -2175,10 +2298,18 @@
|
||||
},
|
||||
"selenium": {
|
||||
"hashes": [
|
||||
"sha256:c942b166a21ce9c9065ad249b54059e926d39f9000167b5ca0fa4950d2ef9a82"
|
||||
"sha256:27e7b64df961d609f3d57237caa0df123abbbe22d038f2ec9e332fb90ec1a939"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.0.0"
|
||||
"version": "==4.1.0"
|
||||
},
|
||||
"setuptools": {
|
||||
"hashes": [
|
||||
"sha256:157d21de9d055ab9e8ea3186d91e7f4f865e11f42deafa952d90842671fc2576",
|
||||
"sha256:4adde3d1e1c89bde1c643c64d89cdd94cbfd8c75252ee459d4500bccb9c7d05d"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==59.2.0"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
@ -2253,11 +2384,11 @@
|
||||
},
|
||||
"typing-extensions": {
|
||||
"hashes": [
|
||||
"sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e",
|
||||
"sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7",
|
||||
"sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"
|
||||
"sha256:2cdf80e4e04866a9b3689a51869016d36db0814d84b8d8a568d22781d45d27ed",
|
||||
"sha256:829704698b22e13ec9eaf959122315eabb370b0884400e9818334d8b677023d9"
|
||||
],
|
||||
"version": "==3.10.0.2"
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==4.0.0"
|
||||
},
|
||||
"urllib3": {
|
||||
"extras": [
|
||||
@ -2334,6 +2465,14 @@
|
||||
],
|
||||
"markers": "python_full_version >= '3.6.1'",
|
||||
"version": "==1.0.0"
|
||||
},
|
||||
"zipp": {
|
||||
"hashes": [
|
||||
"sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832",
|
||||
"sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==3.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
"""authentik"""
|
||||
__version__ = "2021.10.4"
|
||||
__version__ = "2021.12.1-rc2"
|
||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||
|
@ -86,7 +86,7 @@ class SystemSerializer(PassiveSerializer):
|
||||
def get_embedded_outpost_host(self, request: Request) -> str:
|
||||
"""Get the FQDN configured on the embedded outpost"""
|
||||
outposts = Outpost.objects.filter(managed=MANAGED_OUTPOST)
|
||||
if not outposts.exists():
|
||||
if not outposts.exists(): # pragma: no cover
|
||||
return ""
|
||||
return outposts.first().config.authentik_host
|
||||
|
||||
|
@ -36,7 +36,7 @@ class TaskSerializer(PassiveSerializer):
|
||||
are pickled in cache. In that case, just delete the info"""
|
||||
try:
|
||||
return super().to_representation(instance)
|
||||
except AttributeError:
|
||||
except AttributeError: # pragma: no cover
|
||||
if isinstance(self.instance, list):
|
||||
for inst in self.instance:
|
||||
inst.delete()
|
||||
|
@ -23,6 +23,6 @@ class WorkerView(APIView):
|
||||
"""Get currently connected worker count."""
|
||||
count = len(CELERY_APP.control.ping(timeout=0.5))
|
||||
# In debug we run with `CELERY_TASK_ALWAYS_EAGER`, so tasks are ran on the main process
|
||||
if settings.DEBUG:
|
||||
if settings.DEBUG: # pragma: no cover
|
||||
count += 1
|
||||
return Response({"count": count})
|
||||
|
@ -11,12 +11,7 @@ from structlog.stdlib import get_logger
|
||||
|
||||
from authentik import ENV_GIT_HASH_KEY, __version__
|
||||
from authentik.events.models import Event, EventAction, Notification
|
||||
from authentik.events.monitored_tasks import (
|
||||
MonitoredTask,
|
||||
TaskResult,
|
||||
TaskResultStatus,
|
||||
prefill_task,
|
||||
)
|
||||
from authentik.events.monitored_tasks import PrefilledMonitoredTask, TaskResult, TaskResultStatus
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.lib.utils.http import get_http_session
|
||||
from authentik.root.celery import CELERY_APP
|
||||
@ -53,9 +48,8 @@ def clear_update_notifications():
|
||||
notification.delete()
|
||||
|
||||
|
||||
@CELERY_APP.task(bind=True, base=MonitoredTask)
|
||||
@prefill_task()
|
||||
def update_latest_version(self: MonitoredTask):
|
||||
@CELERY_APP.task(bind=True, base=PrefilledMonitoredTask)
|
||||
def update_latest_version(self: PrefilledMonitoredTask):
|
||||
"""Update latest version info"""
|
||||
if CONFIG.y_bool("disable_update_check"):
|
||||
cache.set(VERSION_CACHE_KEY, "0.0.0", VERSION_CACHE_TIMEOUT)
|
||||
|
@ -8,6 +8,7 @@ from authentik import __version__
|
||||
from authentik.core.models import Group, User
|
||||
from authentik.core.tasks import clean_expired_models
|
||||
from authentik.events.monitored_tasks import TaskResultStatus
|
||||
from authentik.managed.tasks import managed_reconcile
|
||||
|
||||
|
||||
class TestAdminAPI(TestCase):
|
||||
@ -94,5 +95,7 @@ class TestAdminAPI(TestCase):
|
||||
|
||||
def test_system(self):
|
||||
"""Test system API"""
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
managed_reconcile() # pylint: disable=no-value-for-parameter
|
||||
response = self.client.get(reverse("authentik_api:admin_system"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
@ -3,8 +3,13 @@ from django.core.cache import cache
|
||||
from django.test import TestCase
|
||||
from requests_mock import Mocker
|
||||
|
||||
from authentik.admin.tasks import VERSION_CACHE_KEY, update_latest_version
|
||||
from authentik.admin.tasks import (
|
||||
VERSION_CACHE_KEY,
|
||||
clear_update_notifications,
|
||||
update_latest_version,
|
||||
)
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.lib.config import CONFIG
|
||||
|
||||
RESPONSE_VALID = {
|
||||
"$schema": "https://version.goauthentik.io/schema.json",
|
||||
@ -56,3 +61,23 @@ class TestAdminTasks(TestCase):
|
||||
action=EventAction.UPDATE_AVAILABLE, context__new_version="0.0.0"
|
||||
).exists()
|
||||
)
|
||||
|
||||
def test_version_disabled(self):
|
||||
"""Test Update checker while its disabled"""
|
||||
with CONFIG.patch("disable_update_check", True):
|
||||
update_latest_version.delay().get()
|
||||
self.assertEqual(cache.get(VERSION_CACHE_KEY), "0.0.0")
|
||||
|
||||
def test_clear_update_notifications(self):
|
||||
"""Test clear of previous notification"""
|
||||
Event.objects.create(
|
||||
action=EventAction.UPDATE_AVAILABLE, context={"new_version": "99999999.9999999.9999999"}
|
||||
)
|
||||
Event.objects.create(action=EventAction.UPDATE_AVAILABLE, context={"new_version": "1.1.1"})
|
||||
Event.objects.create(action=EventAction.UPDATE_AVAILABLE, context={})
|
||||
clear_update_notifications()
|
||||
self.assertFalse(
|
||||
Event.objects.filter(
|
||||
action=EventAction.UPDATE_AVAILABLE, context__new_version="1.1"
|
||||
).exists()
|
||||
)
|
||||
|
@ -1,18 +0,0 @@
|
||||
"""Throttling classes"""
|
||||
from typing import Type
|
||||
|
||||
from django.views import View
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.throttling import ScopedRateThrottle
|
||||
|
||||
|
||||
class SessionThrottle(ScopedRateThrottle):
|
||||
"""Throttle based on session key"""
|
||||
|
||||
def allow_request(self, request: Request, view):
|
||||
if request._request.user.is_superuser:
|
||||
return True
|
||||
return super().allow_request(request, view)
|
||||
|
||||
def get_cache_key(self, request: Request, view: Type[View]) -> str:
|
||||
return f"authentik-throttle-session-{request._request.session.session_key}"
|
@ -5,7 +5,14 @@ from django.conf import settings
|
||||
from django.db import models
|
||||
from drf_spectacular.utils import extend_schema
|
||||
from kubernetes.config.incluster_config import SERVICE_HOST_ENV_NAME
|
||||
from rest_framework.fields import BooleanField, CharField, ChoiceField, IntegerField, ListField
|
||||
from rest_framework.fields import (
|
||||
BooleanField,
|
||||
CharField,
|
||||
ChoiceField,
|
||||
FloatField,
|
||||
IntegerField,
|
||||
ListField,
|
||||
)
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
@ -24,13 +31,19 @@ class Capabilities(models.TextChoices):
|
||||
CAN_BACKUP = "can_backup"
|
||||
|
||||
|
||||
class ErrorReportingConfigSerializer(PassiveSerializer):
|
||||
"""Config for error reporting"""
|
||||
|
||||
enabled = BooleanField(read_only=True)
|
||||
environment = CharField(read_only=True)
|
||||
send_pii = BooleanField(read_only=True)
|
||||
traces_sample_rate = FloatField(read_only=True)
|
||||
|
||||
|
||||
class ConfigSerializer(PassiveSerializer):
|
||||
"""Serialize authentik Config into DRF Object"""
|
||||
|
||||
error_reporting_enabled = BooleanField(read_only=True)
|
||||
error_reporting_environment = CharField(read_only=True)
|
||||
error_reporting_send_pii = BooleanField(read_only=True)
|
||||
|
||||
error_reporting = ErrorReportingConfigSerializer(required=True)
|
||||
capabilities = ListField(child=ChoiceField(choices=Capabilities.choices))
|
||||
|
||||
cache_timeout = IntegerField(required=True)
|
||||
@ -66,9 +79,12 @@ class ConfigView(APIView):
|
||||
"""Retrieve public configuration options"""
|
||||
config = ConfigSerializer(
|
||||
{
|
||||
"error_reporting_enabled": CONFIG.y("error_reporting.enabled"),
|
||||
"error_reporting_environment": CONFIG.y("error_reporting.environment"),
|
||||
"error_reporting_send_pii": CONFIG.y("error_reporting.send_pii"),
|
||||
"error_reporting": {
|
||||
"enabled": CONFIG.y("error_reporting.enabled"),
|
||||
"environment": CONFIG.y("error_reporting.environment"),
|
||||
"send_pii": CONFIG.y("error_reporting.send_pii"),
|
||||
"traces_sample_rate": float(CONFIG.y("error_reporting.sample_rate", 0.4)),
|
||||
},
|
||||
"capabilities": self.get_capabilities(),
|
||||
"cache_timeout": int(CONFIG.y("redis.cache_timeout")),
|
||||
"cache_timeout_flows": int(CONFIG.y("redis.cache_timeout_flows")),
|
||||
|
@ -56,6 +56,7 @@ class PropertyMappingSerializer(ManagedSerializer, ModelSerializer, MetaNameSeri
|
||||
"component",
|
||||
"verbose_name",
|
||||
"verbose_name_plural",
|
||||
"meta_model_name",
|
||||
]
|
||||
|
||||
|
||||
|
@ -43,6 +43,7 @@ class ProviderSerializer(ModelSerializer, MetaNameSerializer):
|
||||
"assigned_application_name",
|
||||
"verbose_name",
|
||||
"verbose_name_plural",
|
||||
"meta_model_name",
|
||||
]
|
||||
|
||||
|
||||
|
@ -48,6 +48,7 @@ class SourceSerializer(ModelSerializer, MetaNameSerializer):
|
||||
"component",
|
||||
"verbose_name",
|
||||
"verbose_name_plural",
|
||||
"meta_model_name",
|
||||
"policy_engine_mode",
|
||||
"user_matching_mode",
|
||||
]
|
||||
|
@ -55,6 +55,7 @@ from authentik.core.models import (
|
||||
User,
|
||||
)
|
||||
from authentik.events.models import EventAction
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.stages.email.models import EmailStage
|
||||
from authentik.stages.email.tasks import send_mails
|
||||
from authentik.stages.email.utils import TemplateEmailMessage
|
||||
@ -125,7 +126,9 @@ class UserSelfSerializer(ModelSerializer):
|
||||
|
||||
def validate_email(self, email: str):
|
||||
"""Check if the user is allowed to change their email"""
|
||||
if self.instance.group_attributes().get(USER_ATTRIBUTE_CHANGE_EMAIL, True):
|
||||
if self.instance.group_attributes().get(
|
||||
USER_ATTRIBUTE_CHANGE_EMAIL, CONFIG.y_bool("default_user_change_email", True)
|
||||
):
|
||||
return email
|
||||
if email != self.instance.email:
|
||||
raise ValidationError("Not allowed to change email.")
|
||||
@ -133,7 +136,9 @@ class UserSelfSerializer(ModelSerializer):
|
||||
|
||||
def validate_username(self, username: str):
|
||||
"""Check if the user is allowed to change their username"""
|
||||
if self.instance.group_attributes().get(USER_ATTRIBUTE_CHANGE_USERNAME, True):
|
||||
if self.instance.group_attributes().get(
|
||||
USER_ATTRIBUTE_CHANGE_USERNAME, CONFIG.y_bool("default_user_change_username", True)
|
||||
):
|
||||
return username
|
||||
if username != self.instance.username:
|
||||
raise ValidationError("Not allowed to change username.")
|
||||
|
@ -41,6 +41,7 @@ class MetaNameSerializer(PassiveSerializer):
|
||||
|
||||
verbose_name = SerializerMethodField()
|
||||
verbose_name_plural = SerializerMethodField()
|
||||
meta_model_name = SerializerMethodField()
|
||||
|
||||
def get_verbose_name(self, obj: Model) -> str:
|
||||
"""Return object's verbose_name"""
|
||||
@ -50,6 +51,10 @@ class MetaNameSerializer(PassiveSerializer):
|
||||
"""Return object's plural verbose_name"""
|
||||
return obj._meta.verbose_name_plural
|
||||
|
||||
def get_meta_model_name(self, obj: Model) -> str:
|
||||
"""Return internal model name"""
|
||||
return f"{obj._meta.app_label}.{obj._meta.model_name}"
|
||||
|
||||
|
||||
class TypeCreateSerializer(PassiveSerializer):
|
||||
"""Types of an object that can be created"""
|
||||
|
@ -1,15 +0,0 @@
|
||||
"""Output full config"""
|
||||
from json import dumps
|
||||
|
||||
from django.core.management.base import BaseCommand, no_translations
|
||||
|
||||
from authentik.lib.config import CONFIG
|
||||
|
||||
|
||||
class Command(BaseCommand): # pragma: no cover
|
||||
"""Output full config"""
|
||||
|
||||
@no_translations
|
||||
def handle(self, *args, **options):
|
||||
"""Check permissions for all apps"""
|
||||
print(dumps(CONFIG.raw, indent=4))
|
@ -12,7 +12,6 @@ LOCAL = local()
|
||||
RESPONSE_HEADER_ID = "X-authentik-id"
|
||||
KEY_AUTH_VIA = "auth_via"
|
||||
KEY_USER = "user"
|
||||
INTERNAL_HEADER_PREFIX = "X-authentik-internal-"
|
||||
|
||||
|
||||
class ImpersonateMiddleware:
|
||||
@ -53,9 +52,9 @@ class RequestIDMiddleware:
|
||||
}
|
||||
response = self.get_response(request)
|
||||
response[RESPONSE_HEADER_ID] = request.request_id
|
||||
if auth_via := LOCAL.authentik.get(KEY_AUTH_VIA, None):
|
||||
response[INTERNAL_HEADER_PREFIX + KEY_AUTH_VIA] = auth_via
|
||||
response[INTERNAL_HEADER_PREFIX + KEY_USER] = request.user.username
|
||||
setattr(response, "ak_context", {})
|
||||
response.ak_context.update(LOCAL.authentik)
|
||||
response.ak_context[KEY_USER] = request.user.username
|
||||
for key in list(LOCAL.authentik.keys()):
|
||||
del LOCAL.authentik[key]
|
||||
return response
|
||||
|
@ -3,7 +3,6 @@
|
||||
import uuid
|
||||
from os import environ
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
from django.apps.registry import Apps
|
||||
from django.conf import settings
|
||||
@ -12,6 +11,7 @@ from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
from django.db.models import Count
|
||||
|
||||
import authentik.core.models
|
||||
import authentik.lib.models
|
||||
|
||||
|
||||
def migrate_sessions(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
@ -161,7 +161,7 @@ class Migration(migrations.Migration):
|
||||
model_name="application",
|
||||
name="meta_launch_url",
|
||||
field=models.TextField(
|
||||
blank=True, default="", validators=[django.core.validators.URLValidator()]
|
||||
blank=True, default="", validators=[authentik.lib.models.DomainlessURLValidator()]
|
||||
),
|
||||
),
|
||||
migrations.RunPython(
|
||||
|
@ -1,8 +1,9 @@
|
||||
# Generated by Django 3.2.3 on 2021-06-02 21:51
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
import authentik.lib.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
@ -17,7 +18,7 @@ class Migration(migrations.Migration):
|
||||
field=models.TextField(
|
||||
blank=True,
|
||||
default="",
|
||||
validators=[django.core.validators.URLValidator()],
|
||||
validators=[authentik.lib.models.DomainlessURLValidator()],
|
||||
),
|
||||
),
|
||||
]
|
||||
|
@ -9,7 +9,6 @@ from deepmerge import always_merger
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.contrib.auth.models import UserManager as DjangoUserManager
|
||||
from django.core import validators
|
||||
from django.db import models
|
||||
from django.db.models import Q, QuerySet, options
|
||||
from django.http import HttpRequest
|
||||
@ -29,7 +28,7 @@ from authentik.core.types import UILoginButton, UserSettingSerializer
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.lib.models import CreatedUpdatedModel, SerializerModel
|
||||
from authentik.lib.models import CreatedUpdatedModel, DomainlessURLValidator, SerializerModel
|
||||
from authentik.lib.utils.http import get_client_ip
|
||||
from authentik.managed.models import ManagedModel
|
||||
from authentik.policies.models import PolicyBindingModel
|
||||
@ -246,7 +245,7 @@ class Application(PolicyBindingModel):
|
||||
)
|
||||
|
||||
meta_launch_url = models.TextField(
|
||||
default="", blank=True, validators=[validators.URLValidator()]
|
||||
default="", blank=True, validators=[DomainlessURLValidator()]
|
||||
)
|
||||
# For template applications, this can be set to /static/authentik/applications/*
|
||||
meta_icon = models.FileField(
|
||||
|
@ -16,21 +16,15 @@ from kubernetes.config.incluster_config import SERVICE_HOST_ENV_NAME
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.core.models import AuthenticatedSession, ExpiringModel
|
||||
from authentik.events.monitored_tasks import (
|
||||
MonitoredTask,
|
||||
TaskResult,
|
||||
TaskResultStatus,
|
||||
prefill_task,
|
||||
)
|
||||
from authentik.events.monitored_tasks import PrefilledMonitoredTask, TaskResult, TaskResultStatus
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.root.celery import CELERY_APP
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
@CELERY_APP.task(bind=True, base=MonitoredTask)
|
||||
@prefill_task()
|
||||
def clean_expired_models(self: MonitoredTask):
|
||||
@CELERY_APP.task(bind=True, base=PrefilledMonitoredTask)
|
||||
def clean_expired_models(self: PrefilledMonitoredTask):
|
||||
"""Remove expired objects"""
|
||||
messages = []
|
||||
for cls in ExpiringModel.__subclasses__():
|
||||
@ -68,9 +62,8 @@ def should_backup() -> bool:
|
||||
return True
|
||||
|
||||
|
||||
@CELERY_APP.task(bind=True, base=MonitoredTask)
|
||||
@prefill_task()
|
||||
def backup_database(self: MonitoredTask): # pragma: no cover
|
||||
@CELERY_APP.task(bind=True, base=PrefilledMonitoredTask)
|
||||
def backup_database(self: PrefilledMonitoredTask): # pragma: no cover
|
||||
"""Database backup"""
|
||||
self.result_timeout_hours = 25
|
||||
if not should_backup():
|
||||
|
@ -3,7 +3,8 @@ from django.urls import reverse
|
||||
from django.utils.encoding import force_str
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.models import Application, User
|
||||
from authentik.core.models import Application
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
from authentik.policies.dummy.models import DummyPolicy
|
||||
from authentik.policies.models import PolicyBinding
|
||||
|
||||
@ -12,7 +13,7 @@ class TestApplicationsAPI(APITestCase):
|
||||
"""Test applications API"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.user = User.objects.get(username="akadmin")
|
||||
self.user = create_test_admin_user()
|
||||
self.allowed = Application.objects.create(name="allowed", slug="allowed")
|
||||
self.denied = Application.objects.create(name="denied", slug="denied")
|
||||
PolicyBinding.objects.create(
|
||||
|
@ -6,6 +6,7 @@ from django.utils.encoding import force_str
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.models import User
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
|
||||
|
||||
class TestAuthenticatedSessionsAPI(APITestCase):
|
||||
@ -13,7 +14,7 @@ class TestAuthenticatedSessionsAPI(APITestCase):
|
||||
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
self.user = User.objects.get(username="akadmin")
|
||||
self.user = create_test_admin_user()
|
||||
self.other_user = User.objects.create(username="normal-user")
|
||||
|
||||
def test_list(self):
|
||||
|
@ -5,6 +5,7 @@ from django.test.testcases import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from authentik.core.models import User
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
|
||||
|
||||
class TestImpersonation(TestCase):
|
||||
@ -13,14 +14,14 @@ class TestImpersonation(TestCase):
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
self.other_user = User.objects.create(username="to-impersonate")
|
||||
self.akadmin = User.objects.get(username="akadmin")
|
||||
self.user = create_test_admin_user()
|
||||
|
||||
def test_impersonate_simple(self):
|
||||
"""test simple impersonation and un-impersonation"""
|
||||
# test with an inactive user to ensure that still works
|
||||
self.other_user.is_active = False
|
||||
self.other_user.save()
|
||||
self.client.force_login(self.akadmin)
|
||||
self.client.force_login(self.user)
|
||||
|
||||
self.client.get(
|
||||
reverse(
|
||||
@ -32,13 +33,13 @@ class TestImpersonation(TestCase):
|
||||
response = self.client.get(reverse("authentik_api:user-me"))
|
||||
response_body = loads(response.content.decode())
|
||||
self.assertEqual(response_body["user"]["username"], self.other_user.username)
|
||||
self.assertEqual(response_body["original"]["username"], self.akadmin.username)
|
||||
self.assertEqual(response_body["original"]["username"], self.user.username)
|
||||
|
||||
self.client.get(reverse("authentik_core:impersonate-end"))
|
||||
|
||||
response = self.client.get(reverse("authentik_api:user-me"))
|
||||
response_body = loads(response.content.decode())
|
||||
self.assertEqual(response_body["user"]["username"], self.akadmin.username)
|
||||
self.assertEqual(response_body["user"]["username"], self.user.username)
|
||||
self.assertNotIn("original", response_body)
|
||||
|
||||
def test_impersonate_denied(self):
|
||||
@ -46,7 +47,7 @@ class TestImpersonation(TestCase):
|
||||
self.client.force_login(self.other_user)
|
||||
|
||||
self.client.get(
|
||||
reverse("authentik_core:impersonate-init", kwargs={"user_id": self.akadmin.pk})
|
||||
reverse("authentik_core:impersonate-init", kwargs={"user_id": self.user.pk})
|
||||
)
|
||||
|
||||
response = self.client.get(reverse("authentik_api:user-me"))
|
||||
|
@ -49,7 +49,7 @@ def provider_tester_factory(test_model: Type[Stage]) -> Callable:
|
||||
|
||||
def tester(self: TestModels):
|
||||
model_class = None
|
||||
if test_model._meta.abstract:
|
||||
if test_model._meta.abstract: # pragma: no cover
|
||||
model_class = test_model.__bases__[0]()
|
||||
else:
|
||||
model_class = test_model()
|
||||
@ -59,6 +59,6 @@ def provider_tester_factory(test_model: Type[Stage]) -> Callable:
|
||||
|
||||
|
||||
for model in all_subclasses(Source):
|
||||
setattr(TestModels, f"test_model_{model.__name__}", source_tester_factory(model))
|
||||
setattr(TestModels, f"test_source_{model.__name__}", source_tester_factory(model))
|
||||
for model in all_subclasses(Provider):
|
||||
setattr(TestModels, f"test_model_{model.__name__}", provider_tester_factory(model))
|
||||
setattr(TestModels, f"test_provider_{model.__name__}", provider_tester_factory(model))
|
||||
|
@ -6,7 +6,8 @@ from rest_framework.serializers import ValidationError
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.api.propertymappings import PropertyMappingSerializer
|
||||
from authentik.core.models import PropertyMapping, User
|
||||
from authentik.core.models import PropertyMapping
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
|
||||
|
||||
class TestPropertyMappingAPI(APITestCase):
|
||||
@ -17,7 +18,7 @@ class TestPropertyMappingAPI(APITestCase):
|
||||
self.mapping = PropertyMapping.objects.create(
|
||||
name="dummy", expression="""return {'foo': 'bar'}"""
|
||||
)
|
||||
self.user = User.objects.get(username="akadmin")
|
||||
self.user = create_test_admin_user()
|
||||
self.client.force_login(self.user)
|
||||
|
||||
def test_test_call(self):
|
||||
|
@ -2,7 +2,8 @@
|
||||
from django.urls import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.models import PropertyMapping, User
|
||||
from authentik.core.models import PropertyMapping
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
|
||||
|
||||
class TestProvidersAPI(APITestCase):
|
||||
@ -13,7 +14,7 @@ class TestProvidersAPI(APITestCase):
|
||||
self.mapping = PropertyMapping.objects.create(
|
||||
name="dummy", expression="""return {'foo': 'bar'}"""
|
||||
)
|
||||
self.user = User.objects.get(username="akadmin")
|
||||
self.user = create_test_admin_user()
|
||||
self.client.force_login(self.user)
|
||||
|
||||
def test_types(self):
|
||||
|
@ -8,6 +8,7 @@ from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.models import USER_ATTRIBUTE_TOKEN_EXPIRING, Token, TokenIntents, User
|
||||
from authentik.core.tasks import clean_expired_models
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
|
||||
|
||||
class TestTokenAPI(APITestCase):
|
||||
@ -16,7 +17,7 @@ class TestTokenAPI(APITestCase):
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
self.user = User.objects.create(username="testuser")
|
||||
self.admin = User.objects.get(username="akadmin")
|
||||
self.admin = create_test_admin_user()
|
||||
self.client.force_login(self.user)
|
||||
|
||||
def test_token_create(self):
|
||||
|
@ -3,7 +3,8 @@ from django.urls.base import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.models import USER_ATTRIBUTE_CHANGE_EMAIL, USER_ATTRIBUTE_CHANGE_USERNAME, User
|
||||
from authentik.flows.models import Flow, FlowDesignation
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_flow, create_test_tenant
|
||||
from authentik.flows.models import FlowDesignation
|
||||
from authentik.stages.email.models import EmailStage
|
||||
from authentik.tenants.models import Tenant
|
||||
|
||||
@ -12,7 +13,7 @@ class TestUsersAPI(APITestCase):
|
||||
"""Test Users API"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.admin = User.objects.get(username="akadmin")
|
||||
self.admin = create_test_admin_user()
|
||||
self.user = User.objects.create(username="test-user")
|
||||
|
||||
def test_update_self(self):
|
||||
@ -69,10 +70,8 @@ class TestUsersAPI(APITestCase):
|
||||
|
||||
def test_recovery(self):
|
||||
"""Test user recovery link (no recovery flow set)"""
|
||||
flow = Flow.objects.create(
|
||||
name="test", title="test", slug="test", designation=FlowDesignation.RECOVERY
|
||||
)
|
||||
tenant: Tenant = Tenant.objects.first()
|
||||
flow = create_test_flow(FlowDesignation.RECOVERY)
|
||||
tenant: Tenant = create_test_tenant()
|
||||
tenant.flow_recovery = flow
|
||||
tenant.save()
|
||||
self.client.force_login(self.admin)
|
||||
@ -99,10 +98,8 @@ class TestUsersAPI(APITestCase):
|
||||
"""Test user recovery link (no email stage)"""
|
||||
self.user.email = "foo@bar.baz"
|
||||
self.user.save()
|
||||
flow = Flow.objects.create(
|
||||
name="test", title="test", slug="test", designation=FlowDesignation.RECOVERY
|
||||
)
|
||||
tenant: Tenant = Tenant.objects.first()
|
||||
flow = create_test_flow(designation=FlowDesignation.RECOVERY)
|
||||
tenant: Tenant = create_test_tenant()
|
||||
tenant.flow_recovery = flow
|
||||
tenant.save()
|
||||
self.client.force_login(self.admin)
|
||||
@ -115,10 +112,8 @@ class TestUsersAPI(APITestCase):
|
||||
"""Test user recovery link"""
|
||||
self.user.email = "foo@bar.baz"
|
||||
self.user.save()
|
||||
flow = Flow.objects.create(
|
||||
name="test", title="test", slug="test", designation=FlowDesignation.RECOVERY
|
||||
)
|
||||
tenant: Tenant = Tenant.objects.first()
|
||||
flow = create_test_flow(FlowDesignation.RECOVERY)
|
||||
tenant: Tenant = create_test_tenant()
|
||||
tenant.flow_recovery = flow
|
||||
tenant.save()
|
||||
|
||||
|
57
authentik/core/tests/utils.py
Normal file
57
authentik/core/tests/utils.py
Normal file
@ -0,0 +1,57 @@
|
||||
"""Test Utils"""
|
||||
from typing import Optional
|
||||
|
||||
from django.utils.text import slugify
|
||||
|
||||
from authentik.core.models import Group, User
|
||||
from authentik.crypto.builder import CertificateBuilder
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.flows.models import Flow, FlowDesignation
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.tenants.models import Tenant
|
||||
|
||||
|
||||
def create_test_flow(designation: FlowDesignation = FlowDesignation.STAGE_CONFIGURATION) -> Flow:
|
||||
"""Generate a flow that can be used for testing"""
|
||||
uid = generate_id(10)
|
||||
return Flow.objects.create(
|
||||
name=uid,
|
||||
title=uid,
|
||||
slug=slugify(uid),
|
||||
designation=designation,
|
||||
)
|
||||
|
||||
|
||||
def create_test_admin_user(name: Optional[str] = None) -> User:
|
||||
"""Generate a test-admin user"""
|
||||
uid = generate_id(20) if not name else name
|
||||
group = Group.objects.create(name=uid, is_superuser=True)
|
||||
user: User = User.objects.create(
|
||||
username=uid,
|
||||
name=uid,
|
||||
email=f"{uid}@goauthentik.io",
|
||||
)
|
||||
user.set_password(uid)
|
||||
user.save()
|
||||
group.users.add(user)
|
||||
return user
|
||||
|
||||
|
||||
def create_test_tenant() -> Tenant:
|
||||
"""Generate a test tenant, removing all other tenants to make sure this one
|
||||
matches."""
|
||||
uid = generate_id(20)
|
||||
Tenant.objects.all().delete()
|
||||
return Tenant.objects.create(domain=uid, default=True)
|
||||
|
||||
|
||||
def create_test_cert() -> CertificateKeyPair:
|
||||
"""Generate a certificate for testing"""
|
||||
CertificateKeyPair.objects.filter(name="goauthentik.io").delete()
|
||||
builder = CertificateBuilder()
|
||||
builder.common_name = "goauthentik.io"
|
||||
builder.build(
|
||||
subject_alt_names=["goauthentik.io"],
|
||||
validity_days=360,
|
||||
)
|
||||
return builder.save()
|
@ -20,6 +20,7 @@ from authentik.api.decorators import permission_required
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import PassiveSerializer
|
||||
from authentik.crypto.builder import CertificateBuilder
|
||||
from authentik.crypto.managed import MANAGED_KEY
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.events.models import Event, EventAction
|
||||
|
||||
@ -141,9 +142,11 @@ class CertificateKeyPairFilter(FilterSet):
|
||||
class CertificateKeyPairViewSet(UsedByMixin, ModelViewSet):
|
||||
"""CertificateKeyPair Viewset"""
|
||||
|
||||
queryset = CertificateKeyPair.objects.exclude(managed__isnull=False)
|
||||
queryset = CertificateKeyPair.objects.exclude(managed=MANAGED_KEY)
|
||||
serializer_class = CertificateKeyPairSerializer
|
||||
filterset_class = CertificateKeyPairFilter
|
||||
ordering = ["name"]
|
||||
search_fields = ["name"]
|
||||
|
||||
@permission_required(None, ["authentik_crypto.add_certificatekeypair"])
|
||||
@extend_schema(
|
||||
|
@ -13,3 +13,4 @@ class AuthentikCryptoConfig(AppConfig):
|
||||
|
||||
def ready(self):
|
||||
import_module("authentik.crypto.managed")
|
||||
import_module("authentik.crypto.tasks")
|
||||
|
@ -55,7 +55,7 @@ class CertificateKeyPair(ManagedModel, CreatedUpdatedModel):
|
||||
@property
|
||||
def private_key(self) -> Optional[RSAPrivateKey]:
|
||||
"""Get python cryptography PrivateKey instance"""
|
||||
if not self._private_key and self._private_key != "":
|
||||
if not self._private_key and self.key_data != "":
|
||||
try:
|
||||
self._private_key = load_pem_private_key(
|
||||
str.encode("\n".join([x.strip() for x in self.key_data.split("\n")])),
|
||||
|
10
authentik/crypto/settings.py
Normal file
10
authentik/crypto/settings.py
Normal file
@ -0,0 +1,10 @@
|
||||
"""Crypto task Settings"""
|
||||
from celery.schedules import crontab
|
||||
|
||||
CELERY_BEAT_SCHEDULE = {
|
||||
"crypto_certificate_discovery": {
|
||||
"task": "authentik.crypto.tasks.certificate_discovery",
|
||||
"schedule": crontab(minute="*/5"),
|
||||
"options": {"queue": "authentik_scheduled"},
|
||||
},
|
||||
}
|
67
authentik/crypto/tasks.py
Normal file
67
authentik/crypto/tasks.py
Normal file
@ -0,0 +1,67 @@
|
||||
"""Crypto tasks"""
|
||||
from glob import glob
|
||||
from pathlib import Path
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.events.monitored_tasks import PrefilledMonitoredTask, TaskResult, TaskResultStatus
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.root.celery import CELERY_APP
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
MANAGED_DISCOVERED = "goauthentik.io/crypto/discovered/%s"
|
||||
|
||||
|
||||
@CELERY_APP.task(bind=True, base=PrefilledMonitoredTask)
|
||||
def certificate_discovery(self: PrefilledMonitoredTask):
|
||||
"""Discover and update certificates form the filesystem"""
|
||||
certs = {}
|
||||
private_keys = {}
|
||||
discovered = 0
|
||||
for file in glob(CONFIG.y("cert_discovery_dir") + "/**", recursive=True):
|
||||
path = Path(file)
|
||||
if not path.exists():
|
||||
continue
|
||||
if path.is_dir():
|
||||
continue
|
||||
# Support certbot's directory structure
|
||||
if path.name in ["fullchain.pem", "privkey.pem"]:
|
||||
cert_name = path.parent.name
|
||||
else:
|
||||
cert_name = path.name.replace(path.suffix, "")
|
||||
try:
|
||||
with open(path, "r+", encoding="utf-8") as _file:
|
||||
body = _file.read()
|
||||
if "BEGIN RSA PRIVATE KEY" in body:
|
||||
private_keys[cert_name] = body
|
||||
else:
|
||||
certs[cert_name] = body
|
||||
except OSError as exc:
|
||||
LOGGER.warning("Failed to open file", exc=exc, file=path)
|
||||
discovered += 1
|
||||
for name, cert_data in certs.items():
|
||||
cert = CertificateKeyPair.objects.filter(managed=MANAGED_DISCOVERED % name).first()
|
||||
if not cert:
|
||||
cert = CertificateKeyPair(
|
||||
name=name,
|
||||
managed=MANAGED_DISCOVERED % name,
|
||||
)
|
||||
dirty = False
|
||||
if cert.certificate_data != cert_data:
|
||||
cert.certificate_data = cert_data
|
||||
dirty = True
|
||||
if name in private_keys:
|
||||
if cert.key_data == private_keys[name]:
|
||||
cert.key_data = private_keys[name]
|
||||
dirty = True
|
||||
if dirty:
|
||||
cert.save()
|
||||
self.set_status(
|
||||
TaskResult(
|
||||
TaskResultStatus.SUCCESSFUL,
|
||||
messages=[_("Successfully imported %(count)d files." % {"count": discovered})],
|
||||
)
|
||||
)
|
@ -1,25 +1,37 @@
|
||||
"""Crypto tests"""
|
||||
import datetime
|
||||
from os import makedirs
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.api.used_by import DeleteAction
|
||||
from authentik.core.models import User
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
|
||||
from authentik.crypto.api import CertificateKeyPairSerializer
|
||||
from authentik.crypto.builder import CertificateBuilder
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.crypto.tasks import MANAGED_DISCOVERED, certificate_discovery
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.lib.generators import generate_key
|
||||
from authentik.providers.oauth2.models import OAuth2Provider
|
||||
|
||||
|
||||
class TestCrypto(TestCase):
|
||||
class TestCrypto(APITestCase):
|
||||
"""Test Crypto validation"""
|
||||
|
||||
def test_model_private(self):
|
||||
"""Test model private key"""
|
||||
cert = CertificateKeyPair.objects.create(
|
||||
name="test",
|
||||
certificate_data="foo",
|
||||
key_data="foo",
|
||||
)
|
||||
self.assertIsNone(cert.private_key)
|
||||
|
||||
def test_serializer(self):
|
||||
"""Test API Validation"""
|
||||
keypair = CertificateKeyPair.objects.first()
|
||||
keypair = create_test_cert()
|
||||
self.assertTrue(
|
||||
CertificateKeyPairSerializer(
|
||||
data={
|
||||
@ -54,10 +66,38 @@ class TestCrypto(TestCase):
|
||||
self.assertEqual(instance.name, "test-cert")
|
||||
self.assertEqual((instance.certificate.not_valid_after - now).days, 2)
|
||||
|
||||
def test_builder_api(self):
|
||||
"""Test Builder (via API)"""
|
||||
self.client.force_login(create_test_admin_user())
|
||||
self.client.post(
|
||||
reverse("authentik_api:certificatekeypair-generate"),
|
||||
data={"common_name": "foo", "subject_alt_name": "bar,baz", "validity_days": 3},
|
||||
)
|
||||
self.assertTrue(CertificateKeyPair.objects.filter(name="foo").exists())
|
||||
|
||||
def test_builder_api_invalid(self):
|
||||
"""Test Builder (via API) (invalid)"""
|
||||
self.client.force_login(create_test_admin_user())
|
||||
response = self.client.post(
|
||||
reverse("authentik_api:certificatekeypair-generate"),
|
||||
data={},
|
||||
)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
def test_list(self):
|
||||
"""Test API List"""
|
||||
self.client.force_login(create_test_admin_user())
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"authentik_api:certificatekeypair-list",
|
||||
)
|
||||
)
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
def test_certificate_download(self):
|
||||
"""Test certificate export (download)"""
|
||||
self.client.force_login(User.objects.get(username="akadmin"))
|
||||
keypair = CertificateKeyPair.objects.first()
|
||||
self.client.force_login(create_test_admin_user())
|
||||
keypair = create_test_cert()
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"authentik_api:certificatekeypair-view-certificate",
|
||||
@ -77,8 +117,8 @@ class TestCrypto(TestCase):
|
||||
|
||||
def test_private_key_download(self):
|
||||
"""Test private_key export (download)"""
|
||||
self.client.force_login(User.objects.get(username="akadmin"))
|
||||
keypair = CertificateKeyPair.objects.first()
|
||||
self.client.force_login(create_test_admin_user())
|
||||
keypair = create_test_cert()
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"authentik_api:certificatekeypair-view-private-key",
|
||||
@ -98,15 +138,15 @@ class TestCrypto(TestCase):
|
||||
|
||||
def test_used_by(self):
|
||||
"""Test used_by endpoint"""
|
||||
self.client.force_login(User.objects.get(username="akadmin"))
|
||||
keypair = CertificateKeyPair.objects.first()
|
||||
self.client.force_login(create_test_admin_user())
|
||||
keypair = create_test_cert()
|
||||
provider = OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
client_id="test",
|
||||
client_secret=generate_key(),
|
||||
authorization_flow=Flow.objects.first(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://localhost",
|
||||
rsa_key=CertificateKeyPair.objects.first(),
|
||||
rsa_key=keypair,
|
||||
)
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
@ -127,3 +167,33 @@ class TestCrypto(TestCase):
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
def test_discovery(self):
|
||||
"""Test certificate discovery"""
|
||||
builder = CertificateBuilder()
|
||||
builder.common_name = "test-cert"
|
||||
with self.assertRaises(ValueError):
|
||||
builder.save()
|
||||
builder.build(
|
||||
subject_alt_names=[],
|
||||
validity_days=3,
|
||||
)
|
||||
with TemporaryDirectory() as temp_dir:
|
||||
with open(f"{temp_dir}/foo.pem", "w+", encoding="utf-8") as _cert:
|
||||
_cert.write(builder.certificate)
|
||||
with open(f"{temp_dir}/foo.key", "w+", encoding="utf-8") as _key:
|
||||
_key.write(builder.private_key)
|
||||
makedirs(f"{temp_dir}/foo.bar", exist_ok=True)
|
||||
with open(f"{temp_dir}/foo.bar/fullchain.pem", "w+", encoding="utf-8") as _cert:
|
||||
_cert.write(builder.certificate)
|
||||
with open(f"{temp_dir}/foo.bar/privkey.pem", "w+", encoding="utf-8") as _key:
|
||||
_key.write(builder.private_key)
|
||||
with CONFIG.patch("cert_discovery_dir", temp_dir):
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
certificate_discovery() # pylint: disable=no-value-for-parameter
|
||||
self.assertTrue(
|
||||
CertificateKeyPair.objects.filter(managed=MANAGED_DISCOVERED % "foo").exists()
|
||||
)
|
||||
self.assertTrue(
|
||||
CertificateKeyPair.objects.filter(managed=MANAGED_DISCOVERED % "foo.bar").exists()
|
||||
)
|
||||
|
@ -4,7 +4,6 @@ import uuid
|
||||
from datetime import timedelta
|
||||
from typing import Iterable
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
from django.apps.registry import Apps
|
||||
from django.conf import settings
|
||||
@ -12,6 +11,7 @@ from django.db import migrations, models
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
import authentik.events.models
|
||||
import authentik.lib.models
|
||||
from authentik.events.models import EventAction, NotificationSeverity, TransportMode
|
||||
|
||||
|
||||
@ -826,6 +826,8 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name="notificationtransport",
|
||||
name="webhook_url",
|
||||
field=models.TextField(blank=True, validators=[django.core.validators.URLValidator()]),
|
||||
field=models.TextField(
|
||||
blank=True, validators=[authentik.lib.models.DomainlessURLValidator()]
|
||||
),
|
||||
),
|
||||
]
|
||||
|
@ -1,8 +1,9 @@
|
||||
# Generated by Django 3.2.7 on 2021-10-04 15:31
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
import authentik.lib.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
@ -14,6 +15,8 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name="notificationtransport",
|
||||
name="webhook_url",
|
||||
field=models.TextField(blank=True, validators=[django.core.validators.URLValidator()]),
|
||||
field=models.TextField(
|
||||
blank=True, validators=[authentik.lib.models.DomainlessURLValidator()]
|
||||
),
|
||||
),
|
||||
]
|
||||
|
@ -6,7 +6,6 @@ from typing import TYPE_CHECKING, Optional, Type, Union
|
||||
from uuid import uuid4
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.validators import URLValidator
|
||||
from django.db import models
|
||||
from django.http import HttpRequest
|
||||
from django.http.request import QueryDict
|
||||
@ -20,6 +19,7 @@ from authentik.core.middleware import SESSION_IMPERSONATE_ORIGINAL_USER, SESSION
|
||||
from authentik.core.models import ExpiringModel, Group, PropertyMapping, User
|
||||
from authentik.events.geo import GEOIP_READER
|
||||
from authentik.events.utils import cleanse_dict, get_user, model_to_dict, sanitize_dict
|
||||
from authentik.lib.models import DomainlessURLValidator
|
||||
from authentik.lib.sentry import SentryIgnoredException
|
||||
from authentik.lib.utils.http import get_client_ip, get_http_session
|
||||
from authentik.lib.utils.time import timedelta_from_string
|
||||
@ -224,7 +224,7 @@ class NotificationTransport(models.Model):
|
||||
name = models.TextField(unique=True)
|
||||
mode = models.TextField(choices=TransportMode.choices)
|
||||
|
||||
webhook_url = models.TextField(blank=True, validators=[URLValidator()])
|
||||
webhook_url = models.TextField(blank=True, validators=[DomainlessURLValidator()])
|
||||
webhook_mapping = models.ForeignKey(
|
||||
"NotificationWebhookMapping", on_delete=models.SET_DEFAULT, null=True, default=None
|
||||
)
|
||||
|
@ -112,30 +112,6 @@ class TaskInfo:
|
||||
cache.set(key, self, timeout=timeout_hours * 60 * 60)
|
||||
|
||||
|
||||
def prefill_task():
|
||||
"""Ensure a task's details are always in cache, so it can always be triggered via API"""
|
||||
|
||||
def inner_wrap(func):
|
||||
status = TaskInfo.by_name(func.__name__)
|
||||
if status:
|
||||
return func
|
||||
TaskInfo(
|
||||
task_name=func.__name__,
|
||||
task_description=func.__doc__,
|
||||
result=TaskResult(TaskResultStatus.UNKNOWN, messages=[_("Task has not been run yet.")]),
|
||||
task_call_module=func.__module__,
|
||||
task_call_func=func.__name__,
|
||||
# We don't have real values for these attributes but they cannot be null
|
||||
start_timestamp=default_timer(),
|
||||
finish_timestamp=default_timer(),
|
||||
finish_time=datetime.now(),
|
||||
).save(86400)
|
||||
LOGGER.debug("prefilled task", task_name=func.__name__)
|
||||
return func
|
||||
|
||||
return inner_wrap
|
||||
|
||||
|
||||
class MonitoredTask(Task):
|
||||
"""Task which can save its state to the cache"""
|
||||
|
||||
@ -210,5 +186,31 @@ class MonitoredTask(Task):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class PrefilledMonitoredTask(MonitoredTask):
|
||||
"""Subclass of MonitoredTask, but create entry in cache if task hasn't been run
|
||||
Does not support UID"""
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
status = TaskInfo.by_name(self.__name__)
|
||||
if status:
|
||||
return
|
||||
TaskInfo(
|
||||
task_name=self.__name__,
|
||||
task_description=self.__doc__,
|
||||
result=TaskResult(TaskResultStatus.UNKNOWN, messages=[_("Task has not been run yet.")]),
|
||||
task_call_module=self.__module__,
|
||||
task_call_func=self.__name__,
|
||||
# We don't have real values for these attributes but they cannot be null
|
||||
start_timestamp=default_timer(),
|
||||
finish_timestamp=default_timer(),
|
||||
finish_time=datetime.now(),
|
||||
).save(86400)
|
||||
LOGGER.debug("prefilled task", task_name=self.__name__)
|
||||
|
||||
def run(self, *args, **kwargs):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
for task in TaskInfo.all().values():
|
||||
task.set_prom_metrics()
|
||||
|
@ -3,14 +3,14 @@ from threading import Thread
|
||||
from typing import Any, Optional
|
||||
|
||||
from django.contrib.auth.signals import user_logged_in, user_logged_out, user_login_failed
|
||||
from django.db.models.signals import post_save
|
||||
from django.db.models.signals import post_save, pre_delete
|
||||
from django.dispatch import receiver
|
||||
from django.http import HttpRequest
|
||||
|
||||
from authentik.core.models import User
|
||||
from authentik.core.signals import password_changed
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.events.tasks import event_notification_handler
|
||||
from authentik.events.tasks import event_notification_handler, gdpr_cleanup
|
||||
from authentik.flows.planner import PLAN_CONTEXT_SOURCE, FlowPlan
|
||||
from authentik.flows.views.executor import SESSION_KEY_PLAN
|
||||
from authentik.stages.invitation.models import Invitation
|
||||
@ -108,3 +108,10 @@ def on_password_changed(sender, user: User, password: str, **_):
|
||||
def event_post_save_notification(sender, instance: Event, **_):
|
||||
"""Start task to check if any policies trigger an notification on this event"""
|
||||
event_notification_handler.delay(instance.event_uuid.hex)
|
||||
|
||||
|
||||
@receiver(pre_delete, sender=User)
|
||||
# pylint: disable=unused-argument
|
||||
def event_user_pre_delete_cleanup(sender, instance: User, **_):
|
||||
"""If gdpr_compliance is enabled, remove all the user's events"""
|
||||
gdpr_cleanup.delay(instance.pk)
|
||||
|
@ -106,3 +106,11 @@ def notification_transport(self: MonitoredTask, notification_pk: int, transport_
|
||||
except NotificationTransportError as exc:
|
||||
self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))
|
||||
raise exc
|
||||
|
||||
|
||||
@CELERY_APP.task()
|
||||
def gdpr_cleanup(user_pk: int):
|
||||
"""cleanup events from gdpr_compliance"""
|
||||
events = Event.objects.filter(user__pk=user_pk)
|
||||
LOGGER.debug("GDPR cleanup, removing events from user", events=events.count())
|
||||
events.delete()
|
||||
|
@ -3,7 +3,7 @@
|
||||
from django.urls import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.models import User
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
from authentik.events.models import (
|
||||
Event,
|
||||
EventAction,
|
||||
@ -17,7 +17,7 @@ class TestEventsAPI(APITestCase):
|
||||
"""Test Event API"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.user = User.objects.get(username="akadmin")
|
||||
self.user = create_test_admin_user()
|
||||
self.client.force_login(self.user)
|
||||
|
||||
def test_top_n(self):
|
||||
|
@ -3,7 +3,8 @@
|
||||
from django.urls import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.models import Application, User
|
||||
from authentik.core.models import Application
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
from authentik.events.models import Event, EventAction
|
||||
|
||||
|
||||
@ -12,7 +13,7 @@ class TestEventsMiddleware(APITestCase):
|
||||
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
self.user = User.objects.get(username="akadmin")
|
||||
self.user = create_test_admin_user()
|
||||
self.client.force_login(self.user)
|
||||
|
||||
def test_create(self):
|
||||
|
@ -41,6 +41,7 @@ class StageSerializer(ModelSerializer, MetaNameSerializer):
|
||||
"component",
|
||||
"verbose_name",
|
||||
"verbose_name_plural",
|
||||
"meta_model_name",
|
||||
"flow_set",
|
||||
]
|
||||
|
||||
|
@ -10,7 +10,7 @@ from django.test import RequestFactory
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik import __version__
|
||||
from authentik.core.models import User
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlanner
|
||||
|
||||
@ -68,7 +68,7 @@ class Command(BaseCommand): # pragma: no cover
|
||||
def benchmark_flows(self, proc_count):
|
||||
"""Get full recovery link"""
|
||||
flow = Flow.objects.get(slug="default-authentication-flow")
|
||||
user = User.objects.get(username="akadmin")
|
||||
user = create_test_admin_user()
|
||||
manager = Manager()
|
||||
return_dict = manager.dict()
|
||||
|
||||
|
@ -149,7 +149,7 @@ class ChallengeStageView(StageView):
|
||||
)
|
||||
challenge_response.initial_data["response_errors"] = full_errors
|
||||
if not challenge_response.is_valid():
|
||||
LOGGER.warning(
|
||||
LOGGER.error(
|
||||
"f(ch): invalid challenge response",
|
||||
binding=self.executor.current_binding,
|
||||
errors=challenge_response.errors,
|
||||
|
@ -2,7 +2,7 @@
|
||||
from django.urls import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.models import User
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
from authentik.flows.api.stages import StageSerializer, StageViewSet
|
||||
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding, Stage
|
||||
from authentik.policies.dummy.models import DummyPolicy
|
||||
@ -47,7 +47,7 @@ class TestFlowsAPI(APITestCase):
|
||||
|
||||
def test_api_diagram(self):
|
||||
"""Test flow diagram."""
|
||||
user = User.objects.get(username="akadmin")
|
||||
user = create_test_admin_user()
|
||||
self.client.force_login(user)
|
||||
|
||||
flow = Flow.objects.create(
|
||||
@ -77,7 +77,7 @@ class TestFlowsAPI(APITestCase):
|
||||
|
||||
def test_api_diagram_no_stages(self):
|
||||
"""Test flow diagram with no stages."""
|
||||
user = User.objects.get(username="akadmin")
|
||||
user = create_test_admin_user()
|
||||
self.client.force_login(user)
|
||||
|
||||
flow = Flow.objects.create(
|
||||
@ -93,7 +93,7 @@ class TestFlowsAPI(APITestCase):
|
||||
|
||||
def test_types(self):
|
||||
"""Test Stage's types endpoint"""
|
||||
user = User.objects.get(username="akadmin")
|
||||
user = create_test_admin_user()
|
||||
self.client.force_login(user)
|
||||
|
||||
response = self.client.get(
|
||||
|
@ -6,7 +6,7 @@ from django.test.client import RequestFactory
|
||||
from django.urls.base import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.models import User
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
from authentik.flows.challenge import ChallengeTypes
|
||||
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding, InvalidResponseAction
|
||||
from authentik.stages.dummy.models import DummyStage
|
||||
@ -18,7 +18,7 @@ class TestFlowInspector(APITestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.request_factory = RequestFactory()
|
||||
self.admin = User.objects.get(username="akadmin")
|
||||
self.admin = create_test_admin_user()
|
||||
self.client.force_login(self.admin)
|
||||
|
||||
def test(self):
|
||||
@ -77,7 +77,7 @@ class TestFlowInspector(APITestCase):
|
||||
|
||||
self.client.post(
|
||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
|
||||
{"uid_field": "akadmin"},
|
||||
{"uid_field": self.admin.username},
|
||||
follow=True,
|
||||
)
|
||||
|
||||
@ -89,5 +89,5 @@ class TestFlowInspector(APITestCase):
|
||||
self.assertEqual(content["plans"][0]["current_stage"]["stage_obj"]["name"], "ident")
|
||||
self.assertEqual(content["current_plan"]["current_stage"]["stage_obj"]["name"], "dummy2")
|
||||
self.assertEqual(
|
||||
content["current_plan"]["plan_context"]["pending_user"]["username"], "akadmin"
|
||||
content["current_plan"]["plan_context"]["pending_user"]["username"], self.admin.username
|
||||
)
|
||||
|
@ -17,13 +17,13 @@ def model_tester_factory(test_model: Type[Stage]) -> Callable:
|
||||
|
||||
def tester(self: TestModels):
|
||||
model_class = None
|
||||
if test_model._meta.abstract:
|
||||
if test_model._meta.abstract: # pragma: no cover
|
||||
model_class = test_model.__bases__[0]()
|
||||
else:
|
||||
model_class = test_model()
|
||||
self.assertTrue(issubclass(model_class.type, StageView))
|
||||
self.assertIsNotNone(test_model.component)
|
||||
_ = test_model.ui_user_settings
|
||||
_ = model_class.ui_user_settings
|
||||
|
||||
return tester
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from authentik.core.tests.utils import create_test_flow
|
||||
from authentik.flows.models import Flow, FlowDesignation
|
||||
from authentik.flows.planner import FlowPlan
|
||||
from authentik.flows.views.executor import SESSION_KEY_PLAN
|
||||
@ -12,9 +13,8 @@ class TestHelperView(TestCase):
|
||||
|
||||
def test_default_view(self):
|
||||
"""Test that ToDefaultFlow returns the expected URL"""
|
||||
flow = Flow.objects.filter(
|
||||
designation=FlowDesignation.INVALIDATION,
|
||||
).first()
|
||||
Flow.objects.filter(designation=FlowDesignation.INVALIDATION).delete()
|
||||
flow = create_test_flow(FlowDesignation.INVALIDATION)
|
||||
response = self.client.get(
|
||||
reverse("authentik_flows:default-invalidation"),
|
||||
)
|
||||
@ -24,9 +24,8 @@ class TestHelperView(TestCase):
|
||||
|
||||
def test_default_view_invalid_plan(self):
|
||||
"""Test that ToDefaultFlow returns the expected URL (with an invalid plan)"""
|
||||
flow = Flow.objects.filter(
|
||||
designation=FlowDesignation.INVALIDATION,
|
||||
).first()
|
||||
Flow.objects.filter(designation=FlowDesignation.INVALIDATION).delete()
|
||||
flow = create_test_flow(FlowDesignation.INVALIDATION)
|
||||
plan = FlowPlan(flow_pk=flow.pk.hex + "aa")
|
||||
session = self.client.session
|
||||
session[SESSION_KEY_PLAN] = plan
|
||||
|
@ -53,6 +53,7 @@ NEXT_ARG_NAME = "next"
|
||||
SESSION_KEY_PLAN = "authentik_flows_plan"
|
||||
SESSION_KEY_APPLICATION_PRE = "authentik_flows_application_pre"
|
||||
SESSION_KEY_GET = "authentik_flows_get"
|
||||
SESSION_KEY_POST = "authentik_flows_post"
|
||||
SESSION_KEY_HISTORY = "authentik_flows_history"
|
||||
|
||||
|
||||
|
@ -3,7 +3,9 @@ import os
|
||||
from collections.abc import Mapping
|
||||
from contextlib import contextmanager
|
||||
from glob import glob
|
||||
from json import dumps
|
||||
from json import dumps, loads
|
||||
from json.decoder import JSONDecodeError
|
||||
from sys import argv, stderr
|
||||
from time import time
|
||||
from typing import Any
|
||||
from urllib.parse import urlparse
|
||||
@ -59,7 +61,7 @@ class ConfigLoader:
|
||||
"timestamp": time(),
|
||||
}
|
||||
output.update(kwargs)
|
||||
print(dumps(output))
|
||||
print(dumps(output), file=stderr)
|
||||
|
||||
def update(self, root: dict[str, Any], updatee: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Recursively update dictionary"""
|
||||
@ -81,8 +83,8 @@ class ConfigLoader:
|
||||
try:
|
||||
with open(url.path, "r", encoding="utf8") as _file:
|
||||
value = _file.read()
|
||||
except OSError:
|
||||
self._log("error", f"Failed to read config value from {url.path}")
|
||||
except OSError as exc:
|
||||
self._log("error", f"Failed to read config value from {url.path}: {exc}")
|
||||
value = url.query
|
||||
return value
|
||||
|
||||
@ -123,6 +125,11 @@ class ConfigLoader:
|
||||
if dot_part not in current_obj:
|
||||
current_obj[dot_part] = {}
|
||||
current_obj = current_obj[dot_part]
|
||||
# Check if the value is json, and try to load it
|
||||
try:
|
||||
value = loads(value)
|
||||
except JSONDecodeError:
|
||||
pass
|
||||
current_obj[dot_parts[-1]] = value
|
||||
idx += 1
|
||||
if idx > 0:
|
||||
@ -174,3 +181,9 @@ class ConfigLoader:
|
||||
|
||||
|
||||
CONFIG = ConfigLoader()
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(argv) < 2:
|
||||
print(dumps(CONFIG.raw, indent=4))
|
||||
else:
|
||||
print(CONFIG.y(argv[1]))
|
||||
|
@ -47,6 +47,7 @@ error_reporting:
|
||||
enabled: false
|
||||
environment: customer
|
||||
send_pii: false
|
||||
sample_rate: 0.5
|
||||
|
||||
# Global email settings
|
||||
email:
|
||||
@ -64,7 +65,7 @@ outposts:
|
||||
# %(type)s: Outpost type; proxy, ldap, etc
|
||||
# %(version)s: Current version; 2021.4.1
|
||||
# %(build_hash)s: Build hash if you're running a beta version
|
||||
container_image_base: env://AUTHENTIK_OUTPOSTS__DOCKER_IMAGE_BASE?goauthentik.io/%(type)s:%(version)s
|
||||
container_image_base: goauthentik.io/%(type)s:%(version)s
|
||||
|
||||
cookie_domain: null
|
||||
disable_update_check: false
|
||||
@ -72,9 +73,14 @@ disable_startup_analytics: false
|
||||
avatars: env://AUTHENTIK_AUTHENTIK__AVATARS?gravatar
|
||||
geoip: "./GeoLite2-City.mmdb"
|
||||
|
||||
# Can't currently be configured via environment variables, only yaml
|
||||
footer_links:
|
||||
- name: Documentation
|
||||
href: https://goauthentik.io/docs/?utm_source=authentik
|
||||
- name: authentik Website
|
||||
href: https://goauthentik.io/?utm_source=authentik
|
||||
|
||||
default_user_change_email: true
|
||||
default_user_change_username: true
|
||||
|
||||
gdpr_compliance: true
|
||||
cert_discovery_dir: /certs
|
||||
|
@ -66,3 +66,11 @@ class DomainlessURLValidator(URLValidator):
|
||||
r"\Z",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
self.schemes = ["http", "https", "blank"] + list(self.schemes)
|
||||
|
||||
def __call__(self, value: str):
|
||||
# Check if the scheme is valid.
|
||||
scheme = value.split("://")[0].lower()
|
||||
if scheme not in self.schemes:
|
||||
value = "default" + value
|
||||
super().__call__(value)
|
||||
|
@ -8,6 +8,7 @@ from botocore.exceptions import BotoCoreError
|
||||
from celery.exceptions import CeleryError
|
||||
from channels.middleware import BaseMiddleware
|
||||
from channels_redis.core import ChannelFull
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation, ValidationError
|
||||
from django.db import InternalError, OperationalError, ProgrammingError
|
||||
from django.http.response import Http404
|
||||
@ -92,6 +93,7 @@ def before_send(event: dict, hint: dict) -> Optional[dict]:
|
||||
# End-user errors
|
||||
Http404,
|
||||
)
|
||||
exc_value = None
|
||||
if "exc_info" in hint:
|
||||
_, exc_value, _ = hint["exc_info"]
|
||||
if isinstance(exc_value, ignored_classes):
|
||||
@ -105,6 +107,10 @@ def before_send(event: dict, hint: dict) -> Optional[dict]:
|
||||
"asyncio",
|
||||
"multiprocessing",
|
||||
"django_redis",
|
||||
"django.security.DisallowedHost",
|
||||
]:
|
||||
return None
|
||||
LOGGER.debug("sending event to sentry", exc=exc_value, source_logger=event.get("logger", None))
|
||||
if settings.DEBUG:
|
||||
return None
|
||||
return event
|
||||
|
@ -1,7 +1,7 @@
|
||||
"""Test Evaluator base functions"""
|
||||
from django.test import TestCase
|
||||
|
||||
from authentik.core.models import User
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
from authentik.lib.expression.evaluator import BaseEvaluator
|
||||
|
||||
|
||||
@ -19,12 +19,11 @@ class TestEvaluator(TestCase):
|
||||
|
||||
def test_user_by(self):
|
||||
"""Test expr_user_by"""
|
||||
self.assertIsNotNone(BaseEvaluator.expr_user_by(username="akadmin"))
|
||||
user = create_test_admin_user()
|
||||
self.assertIsNotNone(BaseEvaluator.expr_user_by(username=user.username))
|
||||
self.assertIsNone(BaseEvaluator.expr_user_by(username="bar"))
|
||||
self.assertIsNone(BaseEvaluator.expr_user_by(foo="bar"))
|
||||
|
||||
def test_is_group_member(self):
|
||||
"""Test expr_is_group_member"""
|
||||
self.assertFalse(
|
||||
BaseEvaluator.expr_is_group_member(User.objects.get(username="akadmin"), name="test")
|
||||
)
|
||||
self.assertFalse(BaseEvaluator.expr_is_group_member(create_test_admin_user(), name="test"))
|
||||
|
@ -1,17 +1,24 @@
|
||||
"""Test HTTP Helpers"""
|
||||
from django.test import RequestFactory, TestCase
|
||||
|
||||
from authentik.core.models import USER_ATTRIBUTE_CAN_OVERRIDE_IP, Token, TokenIntents, User
|
||||
from authentik.core.models import USER_ATTRIBUTE_CAN_OVERRIDE_IP, Token, TokenIntents
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
from authentik.lib.utils.http import OUTPOST_REMOTE_IP_HEADER, OUTPOST_TOKEN_HEADER, get_client_ip
|
||||
from authentik.lib.views import bad_request_message
|
||||
|
||||
|
||||
class TestHTTP(TestCase):
|
||||
"""Test HTTP Helpers"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.user = User.objects.get(username="akadmin")
|
||||
self.user = create_test_admin_user()
|
||||
self.factory = RequestFactory()
|
||||
|
||||
def test_bad_request_message(self):
|
||||
"""test bad_request_message"""
|
||||
request = self.factory.get("/")
|
||||
self.assertEqual(bad_request_message(request, "foo").status_code, 400)
|
||||
|
||||
def test_normal(self):
|
||||
"""Test normal request"""
|
||||
request = self.factory.get("/")
|
||||
|
@ -2,23 +2,17 @@
|
||||
from django.db import DatabaseError
|
||||
|
||||
from authentik.core.tasks import CELERY_APP
|
||||
from authentik.events.monitored_tasks import (
|
||||
MonitoredTask,
|
||||
TaskResult,
|
||||
TaskResultStatus,
|
||||
prefill_task,
|
||||
)
|
||||
from authentik.events.monitored_tasks import PrefilledMonitoredTask, TaskResult, TaskResultStatus
|
||||
from authentik.managed.manager import ObjectManager
|
||||
|
||||
|
||||
@CELERY_APP.task(bind=True, base=MonitoredTask)
|
||||
@prefill_task()
|
||||
def managed_reconcile(self: MonitoredTask):
|
||||
@CELERY_APP.task(bind=True, base=PrefilledMonitoredTask)
|
||||
def managed_reconcile(self: PrefilledMonitoredTask):
|
||||
"""Run ObjectManager to ensure objects are up-to-date"""
|
||||
try:
|
||||
ObjectManager().run()
|
||||
self.set_status(
|
||||
TaskResult(TaskResultStatus.SUCCESSFUL, ["Successfully updated managed models."])
|
||||
)
|
||||
except DatabaseError as exc:
|
||||
except DatabaseError as exc: # pragma: no cover
|
||||
self.set_status(TaskResult(TaskResultStatus.WARNING, [str(exc)]))
|
||||
|
13
authentik/managed/tests.py
Normal file
13
authentik/managed/tests.py
Normal file
@ -0,0 +1,13 @@
|
||||
"""managed tests"""
|
||||
from django.test import TestCase
|
||||
|
||||
from authentik.managed.tasks import managed_reconcile
|
||||
|
||||
|
||||
class TestManaged(TestCase):
|
||||
"""managed tests"""
|
||||
|
||||
def test_reconcile(self):
|
||||
"""Test reconcile"""
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
managed_reconcile() # pylint: disable=no-value-for-parameter
|
@ -46,6 +46,7 @@ class ServiceConnectionSerializer(ModelSerializer, MetaNameSerializer):
|
||||
"component",
|
||||
"verbose_name",
|
||||
"verbose_name_plural",
|
||||
"meta_model_name",
|
||||
]
|
||||
|
||||
|
||||
|
@ -19,8 +19,9 @@ class AuthentikOutpostConfig(AppConfig):
|
||||
import_module("authentik.outposts.signals")
|
||||
import_module("authentik.outposts.managed")
|
||||
try:
|
||||
from authentik.outposts.tasks import outpost_local_connection
|
||||
from authentik.outposts.tasks import outpost_controller_all, outpost_local_connection
|
||||
|
||||
outpost_local_connection.delay()
|
||||
outpost_controller_all.delay()
|
||||
except ProgrammingError:
|
||||
pass
|
||||
|
@ -126,7 +126,7 @@ class OutpostConsumer(AuthJsonConsumer):
|
||||
self.send_json(asdict(response))
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def event_update(self, event):
|
||||
def event_update(self, event): # pragma: no cover
|
||||
"""Event handler which is called by post_save signals, Send update instruction"""
|
||||
self.send_json(
|
||||
asdict(WebsocketMessage(instruction=WebsocketMessageInstruction.TRIGGER_UPDATE))
|
||||
|
@ -67,8 +67,6 @@ class OutpostConfig:
|
||||
authentik_host_browser: str = ""
|
||||
|
||||
log_level: str = CONFIG.y("log_level")
|
||||
error_reporting_enabled: bool = CONFIG.y_bool("error_reporting.enabled")
|
||||
error_reporting_environment: str = CONFIG.y("error_reporting.environment", "customer")
|
||||
object_naming_template: str = field(default="ak-outpost-%(name)s")
|
||||
|
||||
docker_network: Optional[str] = field(default=None)
|
||||
|
@ -19,9 +19,9 @@ from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.events.monitored_tasks import (
|
||||
MonitoredTask,
|
||||
PrefilledMonitoredTask,
|
||||
TaskResult,
|
||||
TaskResultStatus,
|
||||
prefill_task,
|
||||
)
|
||||
from authentik.lib.utils.reflection import path_to_class
|
||||
from authentik.outposts.controllers.base import BaseController, ControllerException
|
||||
@ -75,9 +75,8 @@ def outpost_service_connection_state(connection_pk: Any):
|
||||
cache.set(connection.state_key, state, timeout=None)
|
||||
|
||||
|
||||
@CELERY_APP.task(bind=True, base=MonitoredTask)
|
||||
@prefill_task()
|
||||
def outpost_service_connection_monitor(self: MonitoredTask):
|
||||
@CELERY_APP.task(bind=True, base=PrefilledMonitoredTask)
|
||||
def outpost_service_connection_monitor(self: PrefilledMonitoredTask):
|
||||
"""Regularly check the state of Outpost Service Connections"""
|
||||
connections = OutpostServiceConnection.objects.all()
|
||||
for connection in connections.iterator():
|
||||
@ -125,9 +124,8 @@ def outpost_controller(
|
||||
self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL, logs))
|
||||
|
||||
|
||||
@CELERY_APP.task(bind=True, base=MonitoredTask)
|
||||
@prefill_task()
|
||||
def outpost_token_ensurer(self: MonitoredTask):
|
||||
@CELERY_APP.task(bind=True, base=PrefilledMonitoredTask)
|
||||
def outpost_token_ensurer(self: PrefilledMonitoredTask):
|
||||
"""Periodically ensure that all Outposts have valid Service Accounts
|
||||
and Tokens"""
|
||||
all_outposts = Outpost.objects.all()
|
||||
|
@ -2,8 +2,8 @@
|
||||
from django.urls import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.models import PropertyMapping, User
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.core.models import PropertyMapping
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
|
||||
from authentik.outposts.api.outposts import OutpostSerializer
|
||||
from authentik.outposts.models import OutpostType, default_outpost_config
|
||||
from authentik.providers.ldap.models import LDAPProvider
|
||||
@ -18,7 +18,7 @@ class TestOutpostServiceConnectionsAPI(APITestCase):
|
||||
self.mapping = PropertyMapping.objects.create(
|
||||
name="dummy", expression="""return {'foo': 'bar'}"""
|
||||
)
|
||||
self.user = User.objects.get(username="akadmin")
|
||||
self.user = create_test_admin_user()
|
||||
self.client.force_login(self.user)
|
||||
|
||||
def test_outpost_validaton(self):
|
||||
@ -30,7 +30,7 @@ class TestOutpostServiceConnectionsAPI(APITestCase):
|
||||
"config": default_outpost_config(),
|
||||
"providers": [
|
||||
ProxyProvider.objects.create(
|
||||
name="test", authorization_flow=Flow.objects.first()
|
||||
name="test", authorization_flow=create_test_flow()
|
||||
).pk
|
||||
],
|
||||
}
|
||||
@ -43,7 +43,7 @@ class TestOutpostServiceConnectionsAPI(APITestCase):
|
||||
"config": default_outpost_config(),
|
||||
"providers": [
|
||||
LDAPProvider.objects.create(
|
||||
name="test", authorization_flow=Flow.objects.first()
|
||||
name="test", authorization_flow=create_test_flow()
|
||||
).pk
|
||||
],
|
||||
}
|
||||
@ -60,9 +60,7 @@ class TestOutpostServiceConnectionsAPI(APITestCase):
|
||||
|
||||
def test_outpost_config(self):
|
||||
"""Test Outpost's config field"""
|
||||
provider = ProxyProvider.objects.create(
|
||||
name="test", authorization_flow=Flow.objects.first()
|
||||
)
|
||||
provider = ProxyProvider.objects.create(name="test", authorization_flow=create_test_flow())
|
||||
invalid = OutpostSerializer(data={"name": "foo", "providers": [provider.pk], "config": ""})
|
||||
self.assertFalse(invalid.is_valid())
|
||||
self.assertIn("config", invalid.errors)
|
||||
|
15
authentik/outposts/tests/test_commands.py
Normal file
15
authentik/outposts/tests/test_commands.py
Normal file
@ -0,0 +1,15 @@
|
||||
"""management command tests"""
|
||||
from io import StringIO
|
||||
|
||||
from django.core.management import call_command
|
||||
from django.test import TestCase
|
||||
|
||||
|
||||
class TestManagementCommands(TestCase):
|
||||
"""management command tests"""
|
||||
|
||||
def test_repair_permissions(self):
|
||||
"""Test repair_permissions"""
|
||||
out = StringIO()
|
||||
call_command("repair_permissions", stdout=out)
|
||||
self.assertNotEqual(out.getvalue(), "")
|
@ -4,8 +4,7 @@ from django.contrib.auth.management import create_permissions
|
||||
from django.test import TestCase
|
||||
from guardian.models import UserObjectPermission
|
||||
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.core.tests.utils import create_test_cert, create_test_flow
|
||||
from authentik.outposts.models import Outpost, OutpostType
|
||||
from authentik.providers.proxy.models import ProxyProvider
|
||||
|
||||
@ -23,7 +22,7 @@ class OutpostTests(TestCase):
|
||||
name="test",
|
||||
internal_host="http://localhost",
|
||||
external_host="http://localhost",
|
||||
authorization_flow=Flow.objects.first(),
|
||||
authorization_flow=create_test_flow(),
|
||||
)
|
||||
outpost: Outpost = Outpost.objects.create(
|
||||
name="test",
|
||||
@ -45,7 +44,7 @@ class OutpostTests(TestCase):
|
||||
self.assertEqual(permissions[1].object_pk, str(provider.pk))
|
||||
|
||||
# Provider requires a certificate-key-pair, user should have permissions for it
|
||||
keypair = CertificateKeyPair.objects.first()
|
||||
keypair = create_test_cert()
|
||||
provider.certificate = keypair
|
||||
provider.save()
|
||||
permissions = UserObjectPermission.objects.filter(user=outpost.user).order_by(
|
||||
|
97
authentik/outposts/tests/test_ws.py
Normal file
97
authentik/outposts/tests/test_ws.py
Normal file
@ -0,0 +1,97 @@
|
||||
"""Websocket tests"""
|
||||
from dataclasses import asdict
|
||||
|
||||
from channels.routing import URLRouter
|
||||
from channels.testing import WebsocketCommunicator
|
||||
from django.test import TransactionTestCase
|
||||
|
||||
from authentik import __version__
|
||||
from authentik.flows.models import Flow, FlowDesignation
|
||||
from authentik.outposts.channels import WebsocketMessage, WebsocketMessageInstruction
|
||||
from authentik.outposts.models import Outpost, OutpostType
|
||||
from authentik.providers.proxy.models import ProxyProvider
|
||||
from authentik.root import websocket
|
||||
|
||||
|
||||
class TestOutpostWS(TransactionTestCase):
|
||||
"""Websocket tests"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.provider: ProxyProvider = ProxyProvider.objects.create(
|
||||
name="test",
|
||||
internal_host="http://localhost",
|
||||
external_host="http://localhost",
|
||||
authorization_flow=Flow.objects.create(
|
||||
name="foo", slug="foo", designation=FlowDesignation.AUTHORIZATION
|
||||
),
|
||||
)
|
||||
self.outpost: Outpost = Outpost.objects.create(
|
||||
name="test",
|
||||
type=OutpostType.PROXY,
|
||||
)
|
||||
self.outpost.providers.add(self.provider)
|
||||
self.token = self.outpost.token.key
|
||||
|
||||
async def test_auth(self):
|
||||
"""Test auth without token"""
|
||||
communicator = WebsocketCommunicator(
|
||||
URLRouter(websocket.websocket_urlpatterns), f"/ws/outpost/{self.outpost.pk}/"
|
||||
)
|
||||
connected, _ = await communicator.connect()
|
||||
self.assertFalse(connected)
|
||||
|
||||
async def test_auth_valid(self):
|
||||
"""Test auth with token"""
|
||||
communicator = WebsocketCommunicator(
|
||||
URLRouter(websocket.websocket_urlpatterns),
|
||||
f"/ws/outpost/{self.outpost.pk}/",
|
||||
{b"authorization": f"Bearer {self.token}".encode()},
|
||||
)
|
||||
connected, _ = await communicator.connect()
|
||||
self.assertTrue(connected)
|
||||
|
||||
async def test_send(self):
|
||||
"""Test sending of Hello"""
|
||||
communicator = WebsocketCommunicator(
|
||||
URLRouter(websocket.websocket_urlpatterns),
|
||||
f"/ws/outpost/{self.outpost.pk}/",
|
||||
{b"authorization": f"Bearer {self.token}".encode()},
|
||||
)
|
||||
connected, _ = await communicator.connect()
|
||||
self.assertTrue(connected)
|
||||
await communicator.send_json_to(
|
||||
asdict(
|
||||
WebsocketMessage(
|
||||
instruction=WebsocketMessageInstruction.HELLO,
|
||||
args={
|
||||
"version": __version__,
|
||||
"buildHash": "foo",
|
||||
"uuid": "123",
|
||||
},
|
||||
)
|
||||
)
|
||||
)
|
||||
response = await communicator.receive_json_from()
|
||||
self.assertEqual(
|
||||
response, asdict(WebsocketMessage(instruction=WebsocketMessageInstruction.ACK, args={}))
|
||||
)
|
||||
await communicator.disconnect()
|
||||
|
||||
async def test_send_ack(self):
|
||||
"""Test sending of ACK"""
|
||||
communicator = WebsocketCommunicator(
|
||||
URLRouter(websocket.websocket_urlpatterns),
|
||||
f"/ws/outpost/{self.outpost.pk}/",
|
||||
{b"authorization": f"Bearer {self.token}".encode()},
|
||||
)
|
||||
connected, _ = await communicator.connect()
|
||||
self.assertTrue(connected)
|
||||
await communicator.send_json_to(
|
||||
asdict(
|
||||
WebsocketMessage(
|
||||
instruction=WebsocketMessageInstruction.ACK,
|
||||
args={},
|
||||
)
|
||||
)
|
||||
)
|
||||
await communicator.disconnect()
|
@ -66,6 +66,7 @@ class PolicySerializer(ModelSerializer, MetaNameSerializer):
|
||||
"component",
|
||||
"verbose_name",
|
||||
"verbose_name_plural",
|
||||
"meta_model_name",
|
||||
"bound_to",
|
||||
]
|
||||
depth = 3
|
||||
|
@ -69,8 +69,8 @@ class Migration(migrations.Migration):
|
||||
("authentik.stages.user_logout", "authentik Stages.User Logout"),
|
||||
("authentik.stages.user_write", "authentik Stages.User Write"),
|
||||
("authentik.tenants", "authentik Tenants"),
|
||||
("authentik.core", "authentik Core"),
|
||||
("authentik.managed", "authentik Managed"),
|
||||
("authentik.core", "authentik Core"),
|
||||
],
|
||||
default="",
|
||||
help_text="Match events created by selected application. When left empty, all applications are matched.",
|
||||
|
@ -50,7 +50,7 @@ class PolicyEvaluator(BaseEvaluator):
|
||||
if device_class == device_type:
|
||||
return True
|
||||
return False
|
||||
return len(user_devices) > 0
|
||||
return len(list(user_devices)) > 0
|
||||
|
||||
def set_policy_request(self, request: PolicyRequest):
|
||||
"""Update context based on policy request (if http request is given, update that too)"""
|
||||
|
@ -26,7 +26,7 @@ class TestHIBPPolicy(TestCase):
|
||||
name="test_false",
|
||||
)
|
||||
request = PolicyRequest(get_anonymous_user())
|
||||
request.context["password"] = "password"
|
||||
request.context["password"] = "password" # nosec
|
||||
result: PolicyResult = policy.passes(request)
|
||||
self.assertFalse(result.passing)
|
||||
self.assertTrue(result.messages[0].startswith("Password exists on "))
|
||||
|
@ -30,7 +30,7 @@ class TestPasswordPolicy(TestCase):
|
||||
def test_failed_length(self):
|
||||
"""Password too short"""
|
||||
request = PolicyRequest(get_anonymous_user())
|
||||
request.context["password"] = "test"
|
||||
request.context["password"] = "test" # nosec
|
||||
result: PolicyResult = self.policy.passes(request)
|
||||
self.assertFalse(result.passing)
|
||||
self.assertEqual(result.messages, ("test message",))
|
||||
@ -38,7 +38,7 @@ class TestPasswordPolicy(TestCase):
|
||||
def test_failed_lowercase(self):
|
||||
"""not enough lowercase"""
|
||||
request = PolicyRequest(get_anonymous_user())
|
||||
request.context["password"] = "TTTTTTTTTTTTTTTTTTTTTTTe"
|
||||
request.context["password"] = "TTTTTTTTTTTTTTTTTTTTTTTe" # nosec
|
||||
result: PolicyResult = self.policy.passes(request)
|
||||
self.assertFalse(result.passing)
|
||||
self.assertEqual(result.messages, ("test message",))
|
||||
@ -46,7 +46,7 @@ class TestPasswordPolicy(TestCase):
|
||||
def test_failed_uppercase(self):
|
||||
"""not enough uppercase"""
|
||||
request = PolicyRequest(get_anonymous_user())
|
||||
request.context["password"] = "tttttttttttttttttttttttE"
|
||||
request.context["password"] = "tttttttttttttttttttttttE" # nosec
|
||||
result: PolicyResult = self.policy.passes(request)
|
||||
self.assertFalse(result.passing)
|
||||
self.assertEqual(result.messages, ("test message",))
|
||||
@ -54,7 +54,7 @@ class TestPasswordPolicy(TestCase):
|
||||
def test_failed_symbols(self):
|
||||
"""not enough uppercase"""
|
||||
request = PolicyRequest(get_anonymous_user())
|
||||
request.context["password"] = "TETETETETETETETETETETETETe!!!"
|
||||
request.context["password"] = "TETETETETETETETETETETETETe!!!" # nosec
|
||||
result: PolicyResult = self.policy.passes(request)
|
||||
self.assertFalse(result.passing)
|
||||
self.assertEqual(result.messages, ("test message",))
|
||||
@ -62,7 +62,7 @@ class TestPasswordPolicy(TestCase):
|
||||
def test_true(self):
|
||||
"""Positive password case"""
|
||||
request = PolicyRequest(get_anonymous_user())
|
||||
request.context["password"] = generate_key() + "ee!!!"
|
||||
request.context["password"] = generate_key() + "ee!!!" # nosec
|
||||
result: PolicyResult = self.policy.passes(request)
|
||||
self.assertTrue(result.passing)
|
||||
self.assertEqual(result.messages, tuple())
|
||||
|
@ -2,12 +2,7 @@
|
||||
from django.core.cache import cache
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.events.monitored_tasks import (
|
||||
MonitoredTask,
|
||||
TaskResult,
|
||||
TaskResultStatus,
|
||||
prefill_task,
|
||||
)
|
||||
from authentik.events.monitored_tasks import PrefilledMonitoredTask, TaskResult, TaskResultStatus
|
||||
from authentik.policies.reputation.models import IPReputation, UserReputation
|
||||
from authentik.policies.reputation.signals import CACHE_KEY_IP_PREFIX, CACHE_KEY_USER_PREFIX
|
||||
from authentik.root.celery import CELERY_APP
|
||||
@ -15,9 +10,8 @@ from authentik.root.celery import CELERY_APP
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
@CELERY_APP.task(bind=True, base=MonitoredTask)
|
||||
@prefill_task()
|
||||
def save_ip_reputation(self: MonitoredTask):
|
||||
@CELERY_APP.task(bind=True, base=PrefilledMonitoredTask)
|
||||
def save_ip_reputation(self: PrefilledMonitoredTask):
|
||||
"""Save currently cached reputation to database"""
|
||||
objects_to_update = []
|
||||
for key, score in cache.get_many(cache.keys(CACHE_KEY_IP_PREFIX + "*")).items():
|
||||
@ -29,9 +23,8 @@ def save_ip_reputation(self: MonitoredTask):
|
||||
self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL, ["Successfully updated IP Reputation"]))
|
||||
|
||||
|
||||
@CELERY_APP.task(bind=True, base=MonitoredTask)
|
||||
@prefill_task()
|
||||
def save_user_reputation(self: MonitoredTask):
|
||||
@CELERY_APP.task(bind=True, base=PrefilledMonitoredTask)
|
||||
def save_user_reputation(self: PrefilledMonitoredTask):
|
||||
"""Save currently cached reputation to database"""
|
||||
objects_to_update = []
|
||||
for key, score in cache.get_many(cache.keys(CACHE_KEY_USER_PREFIX + "*")).items():
|
||||
|
@ -2,7 +2,7 @@
|
||||
from django.urls import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.models import Group, User
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
from authentik.policies.models import PolicyBindingModel
|
||||
|
||||
|
||||
@ -12,8 +12,8 @@ class TestBindingsAPI(APITestCase):
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
self.pbm = PolicyBindingModel.objects.create()
|
||||
self.group = Group.objects.first()
|
||||
self.user = User.objects.get(username="akadmin")
|
||||
self.user = create_test_admin_user()
|
||||
self.group = self.user.ak_groups.first()
|
||||
self.client.force_login(self.user)
|
||||
|
||||
def test_valid_binding(self):
|
||||
|
@ -2,7 +2,7 @@
|
||||
from django.urls import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.models import User
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
from authentik.policies.dummy.models import DummyPolicy
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ class TestPoliciesAPI(APITestCase):
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
self.policy = DummyPolicy.objects.create(name="dummy", result=True)
|
||||
self.user = User.objects.get(username="akadmin")
|
||||
self.user = create_test_admin_user()
|
||||
self.client.force_login(self.user)
|
||||
|
||||
def test_test_call(self):
|
||||
|
@ -10,7 +10,7 @@ from django.views.generic.base import View
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.core.models import Application, Provider, User
|
||||
from authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE
|
||||
from authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE, SESSION_KEY_POST
|
||||
from authentik.lib.sentry import SentryIgnoredException
|
||||
from authentik.policies.denied import AccessDeniedResponse
|
||||
from authentik.policies.engine import PolicyEngine
|
||||
@ -84,6 +84,10 @@ class PolicyAccessView(AccessMixin, View):
|
||||
a hint on the Identification Stage what the user should login for."""
|
||||
if self.application:
|
||||
self.request.session[SESSION_KEY_APPLICATION_PRE] = self.application
|
||||
# Because this view might get hit with a POST request, we need to preserve that data
|
||||
# since later views might need it (mostly SAML)
|
||||
if self.request.method.lower() == "post":
|
||||
self.request.session[SESSION_KEY_POST] = self.request.POST
|
||||
return redirect_to_login(
|
||||
self.request.get_full_path(),
|
||||
self.get_login_url(),
|
||||
|
@ -2,8 +2,7 @@
|
||||
from django.urls import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.models import User
|
||||
from authentik.flows.models import Flow, FlowDesignation
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
|
||||
from authentik.providers.oauth2.models import JWTAlgorithms
|
||||
|
||||
|
||||
@ -12,7 +11,7 @@ class TestOAuth2ProviderAPI(APITestCase):
|
||||
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
self.user = User.objects.get(username="akadmin")
|
||||
self.user = create_test_admin_user()
|
||||
self.client.force_login(self.user)
|
||||
|
||||
def test_validate(self):
|
||||
@ -24,9 +23,7 @@ class TestOAuth2ProviderAPI(APITestCase):
|
||||
data={
|
||||
"name": "test",
|
||||
"jwt_alg": str(JWTAlgorithms.RS256),
|
||||
"authorization_flow": Flow.objects.filter(designation=FlowDesignation.AUTHORIZATION)
|
||||
.first()
|
||||
.pk,
|
||||
"authorization_flow": create_test_flow().pk,
|
||||
},
|
||||
)
|
||||
self.assertJSONEqual(
|
||||
|
@ -3,8 +3,8 @@ from django.test import RequestFactory
|
||||
from django.urls import reverse
|
||||
from django.utils.encoding import force_str
|
||||
|
||||
from authentik.core.models import Application, User
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.core.models import Application
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
|
||||
from authentik.flows.challenge import ChallengeTypes
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.lib.generators import generate_id, generate_key
|
||||
@ -43,7 +43,7 @@ class TestAuthorize(OAuthTestCase):
|
||||
OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
client_id="test",
|
||||
authorization_flow=Flow.objects.first(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://local.invalid",
|
||||
)
|
||||
with self.assertRaises(AuthorizeError):
|
||||
@ -63,7 +63,7 @@ class TestAuthorize(OAuthTestCase):
|
||||
OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
client_id="test",
|
||||
authorization_flow=Flow.objects.first(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://local.invalid",
|
||||
)
|
||||
with self.assertRaises(RedirectUriError):
|
||||
@ -85,7 +85,7 @@ class TestAuthorize(OAuthTestCase):
|
||||
OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
client_id="test",
|
||||
authorization_flow=Flow.objects.first(),
|
||||
authorization_flow=create_test_flow(),
|
||||
)
|
||||
with self.assertRaises(RedirectUriError):
|
||||
request = self.factory.get("/", data={"response_type": "code", "client_id": "test"})
|
||||
@ -105,7 +105,7 @@ class TestAuthorize(OAuthTestCase):
|
||||
OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
client_id="test",
|
||||
authorization_flow=Flow.objects.first(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://local.invalid",
|
||||
)
|
||||
request = self.factory.get(
|
||||
@ -184,7 +184,7 @@ class TestAuthorize(OAuthTestCase):
|
||||
)
|
||||
Application.objects.create(name="app", slug="app", provider=provider)
|
||||
state = generate_id()
|
||||
user = User.objects.get(username="akadmin")
|
||||
user = create_test_admin_user()
|
||||
self.client.force_login(user)
|
||||
# Step 1, initiate params and get redirect to flow
|
||||
self.client.get(
|
||||
@ -218,11 +218,11 @@ class TestAuthorize(OAuthTestCase):
|
||||
client_secret=generate_key(),
|
||||
authorization_flow=flow,
|
||||
redirect_uris="http://localhost",
|
||||
rsa_key=CertificateKeyPair.objects.first(),
|
||||
rsa_key=create_test_cert(),
|
||||
)
|
||||
Application.objects.create(name="app", slug="app", provider=provider)
|
||||
state = generate_id()
|
||||
user = User.objects.get(username="akadmin")
|
||||
user = create_test_admin_user()
|
||||
self.client.force_login(user)
|
||||
# Step 1, initiate params and get redirect to flow
|
||||
self.client.get(
|
||||
|
@ -6,8 +6,7 @@ from django.urls.base import reverse
|
||||
from django.utils.encoding import force_str
|
||||
|
||||
from authentik.core.models import Application
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.core.tests.utils import create_test_cert, create_test_flow
|
||||
from authentik.providers.oauth2.models import OAuth2Provider
|
||||
from authentik.providers.oauth2.tests.utils import OAuthTestCase
|
||||
|
||||
@ -24,9 +23,9 @@ class TestJWKS(OAuthTestCase):
|
||||
provider = OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
client_id="test",
|
||||
authorization_flow=Flow.objects.first(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://local.invalid",
|
||||
rsa_key=CertificateKeyPair.objects.first(),
|
||||
rsa_key=create_test_cert(),
|
||||
)
|
||||
app = Application.objects.create(name="test", slug="test", provider=provider)
|
||||
response = self.client.get(
|
||||
@ -40,7 +39,7 @@ class TestJWKS(OAuthTestCase):
|
||||
provider = OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
client_id="test",
|
||||
authorization_flow=Flow.objects.first(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://local.invalid",
|
||||
)
|
||||
app = Application.objects.create(name="test", slug="test", provider=provider)
|
||||
|
@ -5,10 +5,9 @@ from django.test import RequestFactory
|
||||
from django.urls import reverse
|
||||
from django.utils.encoding import force_str
|
||||
|
||||
from authentik.core.models import Application, User
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.core.models import Application
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.lib.generators import generate_id, generate_key
|
||||
from authentik.providers.oauth2.constants import (
|
||||
GRANT_TYPE_AUTHORIZATION_CODE,
|
||||
@ -34,12 +33,12 @@ class TestToken(OAuthTestCase):
|
||||
name="test",
|
||||
client_id=generate_id(),
|
||||
client_secret=generate_key(),
|
||||
authorization_flow=Flow.objects.first(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://testserver",
|
||||
rsa_key=CertificateKeyPair.objects.first(),
|
||||
rsa_key=create_test_cert(),
|
||||
)
|
||||
header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
|
||||
user = User.objects.get(username="akadmin")
|
||||
user = create_test_admin_user()
|
||||
code = AuthorizationCode.objects.create(code="foobar", provider=provider, user=user)
|
||||
request = self.factory.post(
|
||||
"/",
|
||||
@ -61,9 +60,9 @@ class TestToken(OAuthTestCase):
|
||||
name="test",
|
||||
client_id=generate_id(),
|
||||
client_secret=generate_key(),
|
||||
authorization_flow=Flow.objects.first(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://testserver",
|
||||
rsa_key=CertificateKeyPair.objects.first(),
|
||||
rsa_key=create_test_cert(),
|
||||
)
|
||||
header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
|
||||
request = self.factory.post(
|
||||
@ -84,12 +83,12 @@ class TestToken(OAuthTestCase):
|
||||
name="test",
|
||||
client_id=generate_id(),
|
||||
client_secret=generate_key(),
|
||||
authorization_flow=Flow.objects.first(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://local.invalid",
|
||||
rsa_key=CertificateKeyPair.objects.first(),
|
||||
rsa_key=create_test_cert(),
|
||||
)
|
||||
header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
|
||||
user = User.objects.get(username="akadmin")
|
||||
user = create_test_admin_user()
|
||||
token: RefreshToken = RefreshToken.objects.create(
|
||||
provider=provider,
|
||||
user=user,
|
||||
@ -113,15 +112,15 @@ class TestToken(OAuthTestCase):
|
||||
name="test",
|
||||
client_id=generate_id(),
|
||||
client_secret=generate_key(),
|
||||
authorization_flow=Flow.objects.first(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://local.invalid",
|
||||
rsa_key=CertificateKeyPair.objects.first(),
|
||||
rsa_key=create_test_cert(),
|
||||
)
|
||||
# Needs to be assigned to an application for iss to be set
|
||||
self.app.provider = provider
|
||||
self.app.save()
|
||||
header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
|
||||
user = User.objects.get(username="akadmin")
|
||||
user = create_test_admin_user()
|
||||
code = AuthorizationCode.objects.create(
|
||||
code="foobar", provider=provider, user=user, is_open_id=True
|
||||
)
|
||||
@ -155,15 +154,15 @@ class TestToken(OAuthTestCase):
|
||||
name="test",
|
||||
client_id=generate_id(),
|
||||
client_secret=generate_key(),
|
||||
authorization_flow=Flow.objects.first(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://local.invalid",
|
||||
rsa_key=CertificateKeyPair.objects.first(),
|
||||
rsa_key=create_test_cert(),
|
||||
)
|
||||
# Needs to be assigned to an application for iss to be set
|
||||
self.app.provider = provider
|
||||
self.app.save()
|
||||
header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
|
||||
user = User.objects.get(username="akadmin")
|
||||
user = create_test_admin_user()
|
||||
token: RefreshToken = RefreshToken.objects.create(
|
||||
provider=provider,
|
||||
user=user,
|
||||
@ -204,12 +203,12 @@ class TestToken(OAuthTestCase):
|
||||
name="test",
|
||||
client_id=generate_id(),
|
||||
client_secret=generate_key(),
|
||||
authorization_flow=Flow.objects.first(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://local.invalid",
|
||||
rsa_key=CertificateKeyPair.objects.first(),
|
||||
rsa_key=create_test_cert(),
|
||||
)
|
||||
header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
|
||||
user = User.objects.get(username="akadmin")
|
||||
user = create_test_admin_user()
|
||||
token: RefreshToken = RefreshToken.objects.create(
|
||||
provider=provider,
|
||||
user=user,
|
||||
@ -249,15 +248,15 @@ class TestToken(OAuthTestCase):
|
||||
name="test",
|
||||
client_id=generate_id(),
|
||||
client_secret=generate_key(),
|
||||
authorization_flow=Flow.objects.first(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://testserver",
|
||||
rsa_key=CertificateKeyPair.objects.first(),
|
||||
rsa_key=create_test_cert(),
|
||||
)
|
||||
# Needs to be assigned to an application for iss to be set
|
||||
self.app.provider = provider
|
||||
self.app.save()
|
||||
header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
|
||||
user = User.objects.get(username="akadmin")
|
||||
user = create_test_admin_user()
|
||||
token: RefreshToken = RefreshToken.objects.create(
|
||||
provider=provider,
|
||||
user=user,
|
||||
|
@ -5,10 +5,9 @@ from dataclasses import asdict
|
||||
from django.urls import reverse
|
||||
from django.utils.encoding import force_str
|
||||
|
||||
from authentik.core.models import Application, User
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.core.models import Application
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.lib.generators import generate_id, generate_key
|
||||
from authentik.managed.manager import ObjectManager
|
||||
from authentik.providers.oauth2.models import IDToken, OAuth2Provider, RefreshToken, ScopeMapping
|
||||
@ -26,15 +25,15 @@ class TestUserinfo(OAuthTestCase):
|
||||
name="test",
|
||||
client_id=generate_id(),
|
||||
client_secret=generate_key(),
|
||||
authorization_flow=Flow.objects.first(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="",
|
||||
rsa_key=CertificateKeyPair.objects.first(),
|
||||
rsa_key=create_test_cert(),
|
||||
)
|
||||
self.provider.property_mappings.set(ScopeMapping.objects.all())
|
||||
# Needs to be assigned to an application for iss to be set
|
||||
self.app.provider = self.provider
|
||||
self.app.save()
|
||||
self.user = User.objects.get(username="akadmin")
|
||||
self.user = create_test_admin_user()
|
||||
self.token: RefreshToken = RefreshToken.objects.create(
|
||||
provider=self.provider,
|
||||
user=self.user,
|
||||
@ -57,12 +56,12 @@ class TestUserinfo(OAuthTestCase):
|
||||
self.assertJSONEqual(
|
||||
force_str(res.content),
|
||||
{
|
||||
"name": "authentik Default Admin",
|
||||
"given_name": "authentik Default Admin",
|
||||
"name": self.user.name,
|
||||
"given_name": self.user.name,
|
||||
"family_name": "",
|
||||
"preferred_username": "akadmin",
|
||||
"nickname": "akadmin",
|
||||
"groups": ["authentik Admins"],
|
||||
"preferred_username": self.user.name,
|
||||
"nickname": self.user.name,
|
||||
"groups": [group.name for group in self.user.ak_groups.all()],
|
||||
"sub": "bar",
|
||||
},
|
||||
)
|
||||
@ -80,12 +79,12 @@ class TestUserinfo(OAuthTestCase):
|
||||
self.assertJSONEqual(
|
||||
force_str(res.content),
|
||||
{
|
||||
"name": "authentik Default Admin",
|
||||
"given_name": "authentik Default Admin",
|
||||
"name": self.user.name,
|
||||
"given_name": self.user.name,
|
||||
"family_name": "",
|
||||
"preferred_username": "akadmin",
|
||||
"nickname": "akadmin",
|
||||
"groups": ["authentik Admins"],
|
||||
"preferred_username": self.user.name,
|
||||
"nickname": self.user.name,
|
||||
"groups": [group.name for group in self.user.ak_groups.all()],
|
||||
"sub": "bar",
|
||||
},
|
||||
)
|
||||
|
@ -369,7 +369,7 @@ class OAuthFulfillmentStage(StageView):
|
||||
if self.params.grant_type == GrantTypes.HYBRID:
|
||||
query_fragment["code"] = code.code
|
||||
|
||||
query_fragment["token_type"] = "bearer"
|
||||
query_fragment["token_type"] = "bearer" # nosec
|
||||
query_fragment["expires_in"] = int(
|
||||
timedelta_from_string(self.provider.access_code_validity).total_seconds()
|
||||
)
|
||||
|
@ -3,7 +3,7 @@ from typing import Any, Optional
|
||||
|
||||
from drf_spectacular.utils import extend_schema_field
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.fields import CharField, ListField, SerializerMethodField
|
||||
from rest_framework.fields import CharField, ListField, ReadOnlyField, SerializerMethodField
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
|
||||
|
||||
@ -109,6 +109,9 @@ class ProxyProviderViewSet(UsedByMixin, ModelViewSet):
|
||||
class ProxyOutpostConfigSerializer(ModelSerializer):
|
||||
"""Proxy provider serializer for outposts"""
|
||||
|
||||
assigned_application_slug = ReadOnlyField(source="application.slug")
|
||||
assigned_application_name = ReadOnlyField(source="application.name")
|
||||
|
||||
oidc_configuration = SerializerMethodField()
|
||||
token_validity = SerializerMethodField()
|
||||
scopes_to_request = SerializerMethodField()
|
||||
@ -152,6 +155,8 @@ class ProxyOutpostConfigSerializer(ModelSerializer):
|
||||
"cookie_domain",
|
||||
"token_validity",
|
||||
"scopes_to_request",
|
||||
"assigned_application_slug",
|
||||
"assigned_application_name",
|
||||
]
|
||||
|
||||
|
||||
|
@ -20,9 +20,11 @@ class TraefikMiddlewareSpecForwardAuth:
|
||||
|
||||
address: str
|
||||
# pylint: disable=invalid-name
|
||||
authResponseHeaders: list[str]
|
||||
authResponseHeadersRegex: str = field(default="")
|
||||
# pylint: disable=invalid-name
|
||||
trustForwardHeader: bool
|
||||
authResponseHeaders: list[str] = field(default_factory=list)
|
||||
# pylint: disable=invalid-name
|
||||
trustForwardHeader: bool = field(default=True)
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -108,21 +110,8 @@ class TraefikMiddlewareReconciler(KubernetesObjectReconciler[TraefikMiddleware])
|
||||
spec=TraefikMiddlewareSpec(
|
||||
forwardAuth=TraefikMiddlewareSpecForwardAuth(
|
||||
address=f"http://{self.name}.{self.namespace}:9000/akprox/auth/traefik",
|
||||
authResponseHeaders=[
|
||||
"Set-Cookie",
|
||||
# Legacy headers, remove after 2022.1
|
||||
"X-Auth-Username",
|
||||
"X-Auth-Groups",
|
||||
"X-Forwarded-Email",
|
||||
"X-Forwarded-Preferred-Username",
|
||||
"X-Forwarded-User",
|
||||
# New headers, unique prefix
|
||||
"X-authentik-username",
|
||||
"X-authentik-groups",
|
||||
"X-authentik-email",
|
||||
"X-authentik-name",
|
||||
"X-authentik-uid",
|
||||
],
|
||||
authResponseHeaders=[],
|
||||
authResponseHeadersRegex="^.*$",
|
||||
trustForwardHeader=True,
|
||||
)
|
||||
),
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user