Compare commits
105 Commits
version/20
...
version-20
Author | SHA1 | Date | |
---|---|---|---|
c1c55a6005 | |||
2d5c45543b | |||
9d476a42d1 | |||
2c816e6162 | |||
dbcb4d46ba | |||
6600da7d98 | |||
a265dd54cc | |||
a603f42cc0 | |||
d9a788aac8 | |||
7c6185b581 | |||
41a1305555 | |||
75f252b530 | |||
a9519a4a68 | |||
bf4cbb25fe | |||
a925418f60 | |||
ffd61d0e60 | |||
71d112bdcf | |||
c58fe18b97 | |||
590c7f4c9d | |||
56f1204c9b | |||
349a5b2d00 | |||
63e3667e82 | |||
92f2a82c03 | |||
dcf074650e | |||
5a465fbc36 | |||
7cd80a903a | |||
dd00351bc7 | |||
5fca7d11b8 | |||
0ff59636f7 | |||
e5ebe390d2 | |||
b66626f9c4 | |||
23123c43ee | |||
8ce918d527 | |||
45c1a603e7 | |||
583271d5ed | |||
176360fdd7 | |||
8d2a3b67b9 | |||
d0d3072c50 | |||
34e2bbc41d | |||
ea2dbb2f33 | |||
c55f2ad10a | |||
2cde40aeee | |||
a30b32fbbf | |||
1745306cc6 | |||
8925787a13 | |||
968b7ec17a | |||
6600d5bf69 | |||
a4278833d8 | |||
942905b9b1 | |||
81056c3889 | |||
36b694fc41 | |||
2d9f216658 | |||
8d7bb7da17 | |||
965db6eaf5 | |||
9bdd6f23a4 | |||
675ad7710c | |||
9939db13c3 | |||
03e134b296 | |||
465750276c | |||
9b13191646 | |||
634ea61b50 | |||
0fcb4936a2 | |||
934e62d5be | |||
c5e9197b19 | |||
0b7ebf0e07 | |||
ddca8ef3ca | |||
709581f5a8 | |||
72e41c03f5 | |||
40503d06b7 | |||
1df8790050 | |||
3c23ad340f | |||
f9f2e00913 | |||
8362507bdf | |||
a2181c3bf0 | |||
a07ded0dae | |||
3b0b9301ee | |||
919f293fc7 | |||
c4df2e5a50 | |||
4d1500e0f3 | |||
281bd4c69a | |||
e4678aa032 | |||
ff1c4d555a | |||
4a3e34d40a | |||
6939898bbe | |||
549607c5ed | |||
f61acdfbfd | |||
e3572bad76 | |||
8f99891a9d | |||
99d5262d41 | |||
97a3c2d88b | |||
e91ff4566d | |||
dc942b2f4c | |||
a3fccbdaff | |||
bdf9f26d07 | |||
901cea1453 | |||
37b57ac28f | |||
e9aa37ba67 | |||
9a0aa4c79b | |||
34ab68a169 | |||
52cf4890cf | |||
8e5d03cb86 | |||
2190fa555b | |||
ae1edde17b | |||
3ad1c3f212 | |||
65ec444e52 |
@ -1,5 +1,5 @@
|
||||
[bumpversion]
|
||||
current_version = 2021.5.1-rc8
|
||||
current_version = 2021.5.4
|
||||
tag = True
|
||||
commit = True
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*)
|
||||
@ -31,8 +31,6 @@ values =
|
||||
|
||||
[bumpversion:file:web/src/constants.ts]
|
||||
|
||||
[bumpversion:file:web/nginx.conf]
|
||||
|
||||
[bumpversion:file:website/docs/outposts/manual-deploy-docker-compose.md]
|
||||
|
||||
[bumpversion:file:website/docs/outposts/manual-deploy-kubernetes.md]
|
||||
|
16
.github/workflows/release.yml
vendored
16
.github/workflows/release.yml
vendored
@ -36,9 +36,9 @@ jobs:
|
||||
with:
|
||||
push: ${{ github.event_name == 'release' }}
|
||||
tags: |
|
||||
beryju/authentik:2021.5.1-rc8,
|
||||
beryju/authentik:2021.5.4,
|
||||
beryju/authentik:latest,
|
||||
ghcr.io/goauthentik/server:2021.5.1-rc8,
|
||||
ghcr.io/goauthentik/server:2021.5.4,
|
||||
ghcr.io/goauthentik/server:latest
|
||||
platforms: linux/amd64,linux/arm64
|
||||
context: .
|
||||
@ -75,9 +75,9 @@ jobs:
|
||||
with:
|
||||
push: ${{ github.event_name == 'release' }}
|
||||
tags: |
|
||||
beryju/authentik-proxy:2021.5.1-rc8,
|
||||
beryju/authentik-proxy:2021.5.4,
|
||||
beryju/authentik-proxy:latest,
|
||||
ghcr.io/goauthentik/proxy:2021.5.1-rc8,
|
||||
ghcr.io/goauthentik/proxy:2021.5.4,
|
||||
ghcr.io/goauthentik/proxy:latest
|
||||
context: outpost/
|
||||
file: outpost/proxy.Dockerfile
|
||||
@ -115,9 +115,9 @@ jobs:
|
||||
with:
|
||||
push: ${{ github.event_name == 'release' }}
|
||||
tags: |
|
||||
beryju/authentik-ldap:2021.5.1-rc8,
|
||||
beryju/authentik-ldap:2021.5.4,
|
||||
beryju/authentik-ldap:latest,
|
||||
ghcr.io/goauthentik/ldap:2021.5.1-rc8,
|
||||
ghcr.io/goauthentik/ldap:2021.5.4,
|
||||
ghcr.io/goauthentik/ldap:latest
|
||||
context: outpost/
|
||||
file: outpost/ldap.Dockerfile
|
||||
@ -139,7 +139,7 @@ jobs:
|
||||
docker-compose pull -q
|
||||
docker-compose up --no-start
|
||||
docker-compose start postgresql redis
|
||||
docker-compose run -u root --entrypoint /bin/bash server -c "pip install --no-cache -r requirements-dev.txt && ./manage.py test authentik"
|
||||
docker-compose run -u root --entrypoint /bin/bash server -c "apt-get update && apt-get install -y --no-install-recommends git && pip install --no-cache -r requirements-dev.txt && ./manage.py test authentik"
|
||||
sentry-release:
|
||||
if: ${{ github.event_name == 'release' }}
|
||||
needs:
|
||||
@ -155,5 +155,5 @@ jobs:
|
||||
SENTRY_PROJECT: authentik
|
||||
SENTRY_URL: https://sentry.beryju.org
|
||||
with:
|
||||
version: authentik@2021.5.1-rc8
|
||||
version: authentik@2021.5.4
|
||||
environment: beryjuorg-prod
|
||||
|
6
.github/workflows/tag.yml
vendored
6
.github/workflows/tag.yml
vendored
@ -27,17 +27,17 @@ jobs:
|
||||
-f Dockerfile .
|
||||
docker-compose up --no-start
|
||||
docker-compose start postgresql redis
|
||||
docker-compose run -u root --entrypoint /bin/bash server -c "pip install --no-cache -r requirements-dev.txt && ./manage.py test authentik"
|
||||
docker-compose run -u root --entrypoint /bin/bash server -c "apt-get update && apt-get install -y --no-install-recommends git && pip install --no-cache -r requirements-dev.txt && ./manage.py test authentik"
|
||||
- name: Extract version number
|
||||
id: get_version
|
||||
uses: actions/github-script@0.2.0
|
||||
uses: actions/github-script@v4.0.2
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
return context.payload.ref.replace(/\/refs\/tags\/version\//, '');
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1.0.0
|
||||
uses: actions/create-release@v1.1.4
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
10
Dockerfile
10
Dockerfile
@ -48,7 +48,7 @@ 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 && \
|
||||
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 buster-pgdg main" > /etc/apt/sources.list.d/pgdg.list && \
|
||||
apt-get update && \
|
||||
@ -58,14 +58,7 @@ RUN apt-get update && \
|
||||
apt-get autoremove --purge -y && \
|
||||
apt-get clean && \
|
||||
rm -rf /tmp/* /var/lib/apt/lists/* /var/tmp/ && \
|
||||
# This is quite hacky, but docker has no guaranteed Group ID
|
||||
# we could instead check for the GID of the socket and add the user dynamically,
|
||||
# but then we have to drop permmissions later
|
||||
groupadd -g 998 docker_998 && \
|
||||
groupadd -g 999 docker_999 && \
|
||||
adduser --system --no-create-home --uid 1000 --group --home /authentik authentik && \
|
||||
usermod -a -G docker_998 authentik && \
|
||||
usermod -a -G docker_999 authentik && \
|
||||
mkdir /backups && \
|
||||
chown authentik:authentik /backups
|
||||
|
||||
@ -77,7 +70,6 @@ COPY ./lifecycle/ /lifecycle
|
||||
COPY --from=builder /work/authentik /authentik-proxy
|
||||
|
||||
USER authentik
|
||||
STOPSIGNAL SIGINT
|
||||
ENV TMPDIR /dev/shm/
|
||||
ENV PYTHONUBUFFERED 1
|
||||
ENTRYPOINT [ "/lifecycle/bootstrap.sh" ]
|
||||
|
6
Makefile
6
Makefile
@ -36,11 +36,5 @@ gen:
|
||||
--additional-properties=typescriptThreePlus=true,supportsES6=true,npmName=authentik-api,npmVersion=1.0.0
|
||||
cd web/api && npx tsc
|
||||
|
||||
local-stack:
|
||||
export AUTHENTIK_TAG=testing
|
||||
docker build -t beryju/authentik:testng .
|
||||
docker-compose up -d
|
||||
docker-compose run --rm server migrate
|
||||
|
||||
run:
|
||||
go run -v cmd/server/main.go
|
||||
|
137
Pipfile.lock
generated
137
Pipfile.lock
generated
@ -56,7 +56,6 @@
|
||||
"sha256:f881853d2643a29e643609da57b96d5f9c9b93f62429dcc1cbb413c7d07f0e1a",
|
||||
"sha256:fe60131d21b31fd1a14bd43e6bb88256f69dfc3188b3a89d736d6c71ed43ec95"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==3.7.4.post0"
|
||||
},
|
||||
"aioredis": {
|
||||
@ -71,7 +70,6 @@
|
||||
"sha256:03e16e94f2b34c31f8bf1206d8ddd3ccaa4c315f7f6a1879b7b1210d229568c2",
|
||||
"sha256:493a2ac6788ce270a2f6a765b017299f60c1998f5a8617908ee9be082f7300fb"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==5.0.6"
|
||||
},
|
||||
"asgiref": {
|
||||
@ -79,7 +77,6 @@
|
||||
"sha256:92906c611ce6c967347bbfea733f13d6313901d54dcca88195eaeb52b2a8e8ee",
|
||||
"sha256:d1216dfbdfb63826470995d31caed36225dcaf34f182e0fa257a4dd9e86f1b78"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==3.3.4"
|
||||
},
|
||||
"async-timeout": {
|
||||
@ -87,7 +84,6 @@
|
||||
"sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f",
|
||||
"sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"
|
||||
],
|
||||
"markers": "python_full_version >= '3.5.3'",
|
||||
"version": "==3.0.1"
|
||||
},
|
||||
"attrs": {
|
||||
@ -95,7 +91,6 @@
|
||||
"sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1",
|
||||
"sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==21.2.0"
|
||||
},
|
||||
"autobahn": {
|
||||
@ -103,7 +98,6 @@
|
||||
"sha256:9195df8af03b0ff29ccd4b7f5abbde957ee90273465942205f9a1bad6c3f07ac",
|
||||
"sha256:e126c1f583e872fb59e79d36977cfa1f2d0a8a79f90ae31f406faae7664b8e03"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==21.3.1"
|
||||
},
|
||||
"automat": {
|
||||
@ -122,26 +116,24 @@
|
||||
},
|
||||
"boto3": {
|
||||
"hashes": [
|
||||
"sha256:bcb1b76ca5a60586181ad202b19f4c50cb7c22ac68cae20f997abe311e22bf2a",
|
||||
"sha256:edf9b3b36e08cd575a9458bf59871852335aceb5db2d07bfc8530bae3a97d045"
|
||||
"sha256:13cfe0e3ae1bdc7baf4272b1814a7e760fbb508b19d6ac3f472a6bbd64baad61",
|
||||
"sha256:ce08b88a2d7a0ad8edb385f84ea4914296fee6813c66ebf0def956d5278de793"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.17.71"
|
||||
"version": "==1.17.73"
|
||||
},
|
||||
"botocore": {
|
||||
"hashes": [
|
||||
"sha256:414e1721d381095767db1cf673257fdfec639da3be9405a41d49cc859b817d68",
|
||||
"sha256:b7afebca1fd6ca1f8af79f377a445d474e3bd2cf88e704169d6713a6362a304f"
|
||||
"sha256:4b4aa58c61d4b125bc6ec1597924b2749e19de8f2c9a374ac087aa2561e71828",
|
||||
"sha256:69dc0b6fdc0855f5a4f8b1d29c96b9cec44e71054fea0f968e5904d6ccfd4fd9"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
|
||||
"version": "==1.20.71"
|
||||
"version": "==1.20.73"
|
||||
},
|
||||
"cachetools": {
|
||||
"hashes": [
|
||||
"sha256:2cc0b89715337ab6dbba85b5b50effe2b0c74e035d83ee8ed637cf52f12ae001",
|
||||
"sha256:61b5ed1e22a0924aed1d23b478f37e8d52549ff8a961de2909c69bf950020cff"
|
||||
],
|
||||
"markers": "python_version ~= '3.5'",
|
||||
"version": "==4.2.2"
|
||||
},
|
||||
"cbor2": {
|
||||
@ -228,7 +220,6 @@
|
||||
"sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
|
||||
"sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==4.0.0"
|
||||
},
|
||||
"click": {
|
||||
@ -236,7 +227,6 @@
|
||||
"sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
|
||||
"sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==7.1.2"
|
||||
},
|
||||
"click-didyoumean": {
|
||||
@ -310,7 +300,6 @@
|
||||
"sha256:76ffae916ba3aa66b46996c14fa713e46004788167a4873d647544e750e0e99f",
|
||||
"sha256:a9af943c79717bc52fe64a3c236ae5d3adccc8b5be19c881b442d2c3db233393"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==3.0.2"
|
||||
},
|
||||
"defusedxml": {
|
||||
@ -323,17 +312,14 @@
|
||||
},
|
||||
"django": {
|
||||
"hashes": [
|
||||
"sha256:0a1d195ad65c52bf275b8277b3d49680bd1137a5f55039a806f25f6b9752ce3d",
|
||||
"sha256:18dd3145ddbd04bf189ff79b9954d08fda5171ea7b57bf705789fea766a07d50"
|
||||
"sha256:13ac78dbfd189532cad8f383a27e58e18b3d33f80009ceb476d7fcbfc5dcebd8",
|
||||
"sha256:7e0a1393d18c16b503663752a8b6790880c5084412618990ce8a81cc908b4962"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.2.2"
|
||||
"version": "==3.2.3"
|
||||
},
|
||||
"django-dbbackup": {
|
||||
"git": "https://github.com/django-dbbackup/django-dbbackup.git",
|
||||
"hashes": [
|
||||
"sha256:bb109735cae98b64ad084e5b461b7aca2d7b39992f10c9ed9435e3ebb6fb76c8"
|
||||
],
|
||||
"ref": "9d1909c30a3271c8c9c8450add30d6e0b996e145"
|
||||
},
|
||||
"django-filter": {
|
||||
@ -436,23 +422,21 @@
|
||||
"hashes": [
|
||||
"sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.18.2"
|
||||
},
|
||||
"geoip2": {
|
||||
"hashes": [
|
||||
"sha256:57d8d15de2527e0697bbef44fc16812bba709f03a07ef99297bd56c1df3b1efd",
|
||||
"sha256:707025542ef076bd8fd80e97138bebdb7812527b2a007d141a27ad98b0370fff"
|
||||
"sha256:906a1dbf15a179a1af3522970e8420ab15bb3e0afc526942cc179e12146d9c1d",
|
||||
"sha256:b97b44031fdc463e84eb1316b4f19edd978cb1d78703465fcb1e36dc5a822ba6"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.1.0"
|
||||
"version": "==4.2.0"
|
||||
},
|
||||
"google-auth": {
|
||||
"hashes": [
|
||||
"sha256:588bdb03a41ecb4978472b847881e5518b5d9ec6153d3d679aa127a55e13b39f",
|
||||
"sha256:9ad25fba07f46a628ad4d0ca09f38dcb262830df2ac95b217f9b0129c9e42206"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
|
||||
"version": "==1.30.0"
|
||||
},
|
||||
"gunicorn": {
|
||||
@ -468,7 +452,6 @@
|
||||
"sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6",
|
||||
"sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==0.12.0"
|
||||
},
|
||||
"hiredis": {
|
||||
@ -515,7 +498,6 @@
|
||||
"sha256:f52010e0a44e3d8530437e7da38d11fb822acfb0d5b12e9cd5ba655509937ca0",
|
||||
"sha256:f8196f739092a78e4f6b1b2172679ed3343c39c61a3e9d722ce6fcf1dac2824a"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==2.0.0"
|
||||
},
|
||||
"httptools": {
|
||||
@ -564,7 +546,6 @@
|
||||
"sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417",
|
||||
"sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==0.5.1"
|
||||
},
|
||||
"itypes": {
|
||||
@ -579,7 +560,6 @@
|
||||
"sha256:2f2de5285cf37f33d33ecd4a9080b75c87cd0c1994d5a9c6df17131ea1f049c6",
|
||||
"sha256:ea8d7dd814ce9df6de6a761ec7f1cac98afe305b8cdc4aaae4e114b8d8ce24c5"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==3.0.0"
|
||||
},
|
||||
"jmespath": {
|
||||
@ -587,7 +567,6 @@
|
||||
"sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9",
|
||||
"sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.10.0"
|
||||
},
|
||||
"jsonschema": {
|
||||
@ -602,23 +581,19 @@
|
||||
"sha256:6dc509178ac4269b0e66ab4881f70a2035c33d3a622e20585f965986a5182006",
|
||||
"sha256:f4965fba0a4718d47d470beeb5d6446e3357a62402b16c510b6a2f251e05ac3c"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==5.0.2"
|
||||
},
|
||||
"kubernetes": {
|
||||
"hashes": [
|
||||
"sha256:23c85d8571df8f56e773f1a413bc081537536dc47e2b5e8dc2e6262edb2c57ca",
|
||||
"sha256:ec52ea01d52e2ec3da255992f7e859f3a76f2bdb51cf65ba8cd71dfc309d8daa"
|
||||
"sha256:225a95a0aadbd5b645ab389d941a7980db8cdad2a776fde64d1b43fc3299bde9",
|
||||
"sha256:c69b318696ba797dcf63eb928a8d4370c52319f4140023c502d7dfdf2080eb79"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==12.0.1"
|
||||
"version": "==17.17.0"
|
||||
},
|
||||
"ldap3": {
|
||||
"hashes": [
|
||||
"sha256:18c3ee656a6775b9b0d60f7c6c5b094d878d1d90fc03d56731039f0a4b546a91",
|
||||
"sha256:4139c91f0eef9782df7b77c8cbc6243086affcb6a8a249b768a9658438e5da59",
|
||||
"sha256:8c949edbad2be8a03e719ba48bd6779f327ec156929562814b3e84ab56889c8c",
|
||||
"sha256:afc6fc0d01f02af82cd7bfabd3bbfd5dc96a6ae91e97db0a2dab8a0f1b436056",
|
||||
"sha256:c1df41d89459be6f304e0ceec4b00fdea533dbbcd83c802b1272dcdb94620b57"
|
||||
],
|
||||
"index": "pypi",
|
||||
@ -713,14 +688,12 @@
|
||||
"sha256:f58b5ba13a5689ca8317b98439fccfbcc673acaaf8241c1869ceea40f5d585bf",
|
||||
"sha256:fef86115fdad7ae774720d7103aa776144cf9b66673b4afa9bcaa7af990ed07b"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==2.0.0"
|
||||
},
|
||||
"maxminddb": {
|
||||
"hashes": [
|
||||
"sha256:47e86a084dd814fac88c99ea34ba3278a74bc9de5a25f4b815b608798747c7dc"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==2.0.3"
|
||||
},
|
||||
"msgpack": {
|
||||
@ -796,7 +769,6 @@
|
||||
"sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281",
|
||||
"sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==5.1.0"
|
||||
},
|
||||
"oauthlib": {
|
||||
@ -804,7 +776,6 @@
|
||||
"sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889",
|
||||
"sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==3.1.0"
|
||||
},
|
||||
"packaging": {
|
||||
@ -820,7 +791,6 @@
|
||||
"sha256:030e4f9df5f53db2292eec37c6255957eb76168c6f974e4176c711cf91ed34aa",
|
||||
"sha256:b6c5a9643e3545bcbfd9451766cbaa5d9c67e7303c7bc32c750b6fa70ecb107d"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.10.1"
|
||||
},
|
||||
"prompt-toolkit": {
|
||||
@ -828,7 +798,6 @@
|
||||
"sha256:bf00f22079f5fadc949f42ae8ff7f05702826a97059ffcc6281036ad40ac6f04",
|
||||
"sha256:e1b4f11b9336a28fa11810bc623c357420f69dfdb6d2dac41ca2c21a55c033bc"
|
||||
],
|
||||
"markers": "python_full_version >= '3.6.1'",
|
||||
"version": "==3.0.18"
|
||||
},
|
||||
"psycopg2-binary": {
|
||||
@ -874,37 +843,15 @@
|
||||
},
|
||||
"pyasn1": {
|
||||
"hashes": [
|
||||
"sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359",
|
||||
"sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576",
|
||||
"sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf",
|
||||
"sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7",
|
||||
"sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d",
|
||||
"sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00",
|
||||
"sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8",
|
||||
"sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86",
|
||||
"sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12",
|
||||
"sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776",
|
||||
"sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba",
|
||||
"sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2",
|
||||
"sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"
|
||||
"sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"
|
||||
],
|
||||
"version": "==0.4.8"
|
||||
},
|
||||
"pyasn1-modules": {
|
||||
"hashes": [
|
||||
"sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8",
|
||||
"sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199",
|
||||
"sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811",
|
||||
"sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed",
|
||||
"sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4",
|
||||
"sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e",
|
||||
"sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74",
|
||||
"sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb",
|
||||
"sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45",
|
||||
"sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd",
|
||||
"sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0",
|
||||
"sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d",
|
||||
"sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405"
|
||||
"sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74"
|
||||
],
|
||||
"version": "==0.2.8"
|
||||
},
|
||||
@ -913,7 +860,6 @@
|
||||
"sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0",
|
||||
"sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.20"
|
||||
},
|
||||
"pycryptodome": {
|
||||
@ -957,7 +903,6 @@
|
||||
"sha256:412e00137858f04bde0729913874a48485665f2d36fe9ee449f26be864af9316",
|
||||
"sha256:7ead136e03655af85069b6f47b23eb7c3e5c221aa9f022a4fbb499f5b7308f29"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==2.0.2"
|
||||
},
|
||||
"pyjwt": {
|
||||
@ -980,14 +925,12 @@
|
||||
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
|
||||
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.4.7"
|
||||
},
|
||||
"pyrsistent": {
|
||||
"hashes": [
|
||||
"sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==0.17.3"
|
||||
},
|
||||
"python-dateutil": {
|
||||
@ -995,7 +938,6 @@
|
||||
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
|
||||
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.8.1"
|
||||
},
|
||||
"python-dotenv": {
|
||||
@ -1052,7 +994,6 @@
|
||||
"sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2",
|
||||
"sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==3.5.3"
|
||||
},
|
||||
"requests": {
|
||||
@ -1060,14 +1001,12 @@
|
||||
"sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
|
||||
"sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==2.25.1"
|
||||
},
|
||||
"requests-oauthlib": {
|
||||
"hashes": [
|
||||
"sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d",
|
||||
"sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a",
|
||||
"sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc"
|
||||
"sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.3.0"
|
||||
@ -1085,7 +1024,6 @@
|
||||
"sha256:44bc6b54fddd45e4bc0619059196679f9e8b79c027f4131bb072e6a22f4d5e28",
|
||||
"sha256:ac79fb25f5476e8e9ed1c53b8a2286d2c3f5dde49eb37dbcee5c7eb6a8415a22"
|
||||
],
|
||||
"markers": "python_version >= '3'",
|
||||
"version": "==0.17.4"
|
||||
},
|
||||
"ruamel.yaml.clib": {
|
||||
@ -1122,7 +1060,7 @@
|
||||
"sha256:e9f7d1d8c26a6a12c23421061f9022bb62704e38211fe375c645485f38df34a2",
|
||||
"sha256:f6061a31880c1ed6b6ce341215336e2f3d0c1deccd84957b6fa8ca474b41e89f"
|
||||
],
|
||||
"markers": "python_version < '3.10' and platform_python_implementation == 'CPython'",
|
||||
"markers": "platform_python_implementation == 'CPython' and python_version < '3.10'",
|
||||
"version": "==0.2.2"
|
||||
},
|
||||
"s3transfer": {
|
||||
@ -1153,7 +1091,6 @@
|
||||
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
|
||||
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.16.0"
|
||||
},
|
||||
"sqlparse": {
|
||||
@ -1161,7 +1098,6 @@
|
||||
"sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0",
|
||||
"sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==0.4.1"
|
||||
},
|
||||
"structlog": {
|
||||
@ -1217,7 +1153,6 @@
|
||||
"sha256:7d6f89745680233f1c4db9ddb748df5e88d2a7a37962be174c0fd04c8dba1dc8",
|
||||
"sha256:c16b55f9a67b2419cfdf8846576e2ec9ba94fe6978a83080c352a80db31c93fb"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==21.2.1"
|
||||
},
|
||||
"typing-extensions": {
|
||||
@ -1233,7 +1168,6 @@
|
||||
"sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f",
|
||||
"sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==3.0.1"
|
||||
},
|
||||
"urllib3": {
|
||||
@ -1278,7 +1212,6 @@
|
||||
"sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30",
|
||||
"sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==5.0.0"
|
||||
},
|
||||
"watchgod": {
|
||||
@ -1308,7 +1241,6 @@
|
||||
"sha256:2e50d26ca593f70aba7b13a489435ef88b8fc3b5c5643c1ce8808ff9b40f0b32",
|
||||
"sha256:d376bd60eace9d437ab6d7ee16f4ab4e821c9dae591e1b783c58ebd8aaf80c5c"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.59.0"
|
||||
},
|
||||
"websockets": {
|
||||
@ -1395,7 +1327,6 @@
|
||||
"sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a",
|
||||
"sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==1.6.3"
|
||||
},
|
||||
"zope.interface": {
|
||||
@ -1452,7 +1383,6 @@
|
||||
"sha256:f44e517131a98f7a76696a7b21b164bcb85291cee106a23beccce454e1f433a4",
|
||||
"sha256:f7ee479e96f7ee350db1cf24afa5685a5899e2b34992fb99e1f7c1b0b758d263"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==5.4.0"
|
||||
}
|
||||
},
|
||||
@ -1469,7 +1399,6 @@
|
||||
"sha256:4db03ab5fc3340cf619dbc25e42c2cc3755154ce6009469766d7143d1fc2ee4e",
|
||||
"sha256:8a398dfce302c13f14bab13e2b14fe385d32b73f4e4853b9bdfb64598baa1975"
|
||||
],
|
||||
"markers": "python_version ~= '3.6'",
|
||||
"version": "==2.5.6"
|
||||
},
|
||||
"attrs": {
|
||||
@ -1477,7 +1406,6 @@
|
||||
"sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1",
|
||||
"sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==21.2.0"
|
||||
},
|
||||
"bandit": {
|
||||
@ -1516,7 +1444,6 @@
|
||||
"sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
|
||||
"sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==4.0.0"
|
||||
},
|
||||
"click": {
|
||||
@ -1524,7 +1451,6 @@
|
||||
"sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
|
||||
"sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==7.1.2"
|
||||
},
|
||||
"colorama": {
|
||||
@ -1598,16 +1524,14 @@
|
||||
"sha256:6c4cc71933456991da20917998acbe6cf4fb41eeaab7d6d67fbc05ecd4c865b0",
|
||||
"sha256:96bf5c08b157a666fec41129e6d327235284cca4c81e92109260f353ba138005"
|
||||
],
|
||||
"markers": "python_version >= '3.4'",
|
||||
"version": "==4.0.7"
|
||||
},
|
||||
"gitpython": {
|
||||
"hashes": [
|
||||
"sha256:3283ae2fba31c913d857e12e5ba5f9a7772bbc064ae2bb09efafa71b0dd4939b",
|
||||
"sha256:be27633e7509e58391f10207cd32b2a6cf5b908f92d9cd30da2e514e1137af61"
|
||||
"sha256:29fe82050709760081f588dd50ce83504feddbebdc4da6956d02351552b1c135",
|
||||
"sha256:ee24bdc93dce357630764db659edaf6b8d664d4ff5447ccfeedd2dc5c253f41e"
|
||||
],
|
||||
"markers": "python_version >= '3.4'",
|
||||
"version": "==3.1.14"
|
||||
"version": "==3.1.17"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
@ -1628,7 +1552,6 @@
|
||||
"sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6",
|
||||
"sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d"
|
||||
],
|
||||
"markers": "python_version >= '3.6' and python_version < '4.0'",
|
||||
"version": "==5.8.0"
|
||||
},
|
||||
"lazy-object-proxy": {
|
||||
@ -1656,7 +1579,6 @@
|
||||
"sha256:ed361bb83436f117f9917d282a456f9e5009ea12fd6de8742d1a4752c3017e93",
|
||||
"sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
|
||||
"version": "==1.6.0"
|
||||
},
|
||||
"mccabe": {
|
||||
@ -1693,7 +1615,6 @@
|
||||
"sha256:42df03e7797b796625b1029c0400279c7c34fd7df24a7d7818a1abb5b38710dd",
|
||||
"sha256:c68c661ac5cc81058ac94247278eeda6d2e6aecb3e227b0387c30d277e7ef8d4"
|
||||
],
|
||||
"markers": "python_version >= '2.6'",
|
||||
"version": "==5.6.0"
|
||||
},
|
||||
"pluggy": {
|
||||
@ -1701,7 +1622,6 @@
|
||||
"sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
|
||||
"sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.13.1"
|
||||
},
|
||||
"py": {
|
||||
@ -1709,7 +1629,6 @@
|
||||
"sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3",
|
||||
"sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.10.0"
|
||||
},
|
||||
"pylint": {
|
||||
@ -1740,7 +1659,6 @@
|
||||
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
|
||||
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.4.7"
|
||||
},
|
||||
"pytest": {
|
||||
@ -1753,11 +1671,11 @@
|
||||
},
|
||||
"pytest-django": {
|
||||
"hashes": [
|
||||
"sha256:80f8875226ec4dc0b205f0578072034563879d98d9b1bec143a80b9045716cb0",
|
||||
"sha256:a51150d8962200250e850c6adcab670779b9c2aa07271471059d1fb92a843fa9"
|
||||
"sha256:d1c6758a592fb0ef8abaa2fe12dd28858c1dcfc3d466102ffe52aa8934733dca",
|
||||
"sha256:f96c4556f4e7b15d987dd1dcc1d1526df81d40c1548d31ce840d597ed2be8c46"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.2.0"
|
||||
"version": "==4.3.0"
|
||||
},
|
||||
"pyyaml": {
|
||||
"hashes": [
|
||||
@ -1845,7 +1763,6 @@
|
||||
"sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
|
||||
"sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==2.25.1"
|
||||
},
|
||||
"requests-mock": {
|
||||
@ -1869,7 +1786,6 @@
|
||||
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
|
||||
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.16.0"
|
||||
},
|
||||
"smmap": {
|
||||
@ -1877,7 +1793,6 @@
|
||||
"sha256:7e65386bd122d45405ddf795637b7f7d2b532e7e401d46bbe3fb49b9986d5182",
|
||||
"sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==4.0.0"
|
||||
},
|
||||
"stevedore": {
|
||||
@ -1885,7 +1800,6 @@
|
||||
"sha256:3a5bbd0652bf552748871eaa73a4a8dc2899786bc497a2aa1fcb4dcdb0debeee",
|
||||
"sha256:50d7b78fbaf0d04cd62411188fa7eedcb03eb7f4c4b37005615ceebe582aa82a"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==3.3.0"
|
||||
},
|
||||
"toml": {
|
||||
@ -1893,7 +1807,6 @@
|
||||
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
|
||||
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.10.2"
|
||||
},
|
||||
"urllib3": {
|
||||
|
@ -1,3 +1,3 @@
|
||||
"""authentik"""
|
||||
__version__ = "2021.5.1-rc8"
|
||||
__version__ = "2021.5.4"
|
||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||
|
@ -42,7 +42,7 @@ def token_from_header(raw_header: bytes) -> Optional[Token]:
|
||||
return tokens.first()
|
||||
|
||||
|
||||
class AuthentikTokenAuthentication(BaseAuthentication):
|
||||
class TokenAuthentication(BaseAuthentication):
|
||||
"""Token-based authentication using HTTP Bearer authentication"""
|
||||
|
||||
def authenticate(self, request: Request) -> Union[tuple[User, Any], None]:
|
35
authentik/api/authorization.py
Normal file
35
authentik/api/authorization.py
Normal file
@ -0,0 +1,35 @@
|
||||
"""API Authorization"""
|
||||
from django.db.models import Model
|
||||
from django.db.models.query import QuerySet
|
||||
from rest_framework.filters import BaseFilterBackend
|
||||
from rest_framework.permissions import BasePermission
|
||||
from rest_framework.request import Request
|
||||
|
||||
|
||||
class OwnerFilter(BaseFilterBackend):
|
||||
"""Filter objects by their owner"""
|
||||
|
||||
owner_key = "user"
|
||||
|
||||
def filter_queryset(self, request: Request, queryset: QuerySet, view) -> QuerySet:
|
||||
return queryset.filter(**{self.owner_key: request.user})
|
||||
|
||||
|
||||
class OwnerPermissions(BasePermission):
|
||||
"""Authorize requests by an object's owner matching the requesting user"""
|
||||
|
||||
owner_key = "user"
|
||||
|
||||
def has_permission(self, request: Request, view) -> bool:
|
||||
"""If the user is authenticated, we allow all requests here. For listing, the
|
||||
object-level permissions are done by the filter backend"""
|
||||
return request.user.is_authenticated
|
||||
|
||||
def has_object_permission(self, request: Request, view, obj: Model) -> bool:
|
||||
"""Check if the object's owner matches the currently logged in user"""
|
||||
if not hasattr(obj, self.owner_key):
|
||||
return False
|
||||
owner = getattr(obj, self.owner_key)
|
||||
if owner != request.user:
|
||||
return False
|
||||
return True
|
@ -5,7 +5,7 @@ from django.test import TestCase
|
||||
from guardian.shortcuts import get_anonymous_user
|
||||
from rest_framework.exceptions import AuthenticationFailed
|
||||
|
||||
from authentik.api.auth import token_from_header
|
||||
from authentik.api.authentication import token_from_header
|
||||
from authentik.core.models import Token, TokenIntents
|
||||
|
||||
|
||||
|
@ -169,9 +169,19 @@ router.register("propertymappings/scope", ScopeMappingViewSet)
|
||||
router.register("authenticators/static", StaticDeviceViewSet)
|
||||
router.register("authenticators/totp", TOTPDeviceViewSet)
|
||||
router.register("authenticators/webauthn", WebAuthnDeviceViewSet)
|
||||
router.register("authenticators/admin/static", StaticAdminDeviceViewSet)
|
||||
router.register("authenticators/admin/totp", TOTPAdminDeviceViewSet)
|
||||
router.register("authenticators/admin/webauthn", WebAuthnAdminDeviceViewSet)
|
||||
router.register(
|
||||
"authenticators/admin/static",
|
||||
StaticAdminDeviceViewSet,
|
||||
basename="admin-staticdevice",
|
||||
)
|
||||
router.register(
|
||||
"authenticators/admin/totp", TOTPAdminDeviceViewSet, basename="admin-totpdevice"
|
||||
)
|
||||
router.register(
|
||||
"authenticators/admin/webauthn",
|
||||
WebAuthnAdminDeviceViewSet,
|
||||
basename="admin-webauthndevice",
|
||||
)
|
||||
|
||||
router.register("stages/all", StageViewSet)
|
||||
router.register("stages/authenticator/static", AuthenticatorStaticStageViewSet)
|
||||
|
@ -23,6 +23,7 @@ from authentik.core.api.providers import ProviderSerializer
|
||||
from authentik.core.models import Application
|
||||
from authentik.events.models import EventAction
|
||||
from authentik.policies.engine import PolicyEngine
|
||||
from authentik.stages.user_login.stage import USER_LOGIN_AUTHENTICATED
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
@ -122,6 +123,7 @@ class ApplicationViewSet(ModelViewSet):
|
||||
)
|
||||
def list(self, request: Request) -> Response:
|
||||
"""Custom list method that checks Policy based access instead of guardian"""
|
||||
self.request.session.pop(USER_LOGIN_AUTHENTICATED, None)
|
||||
queryset = self._filter_queryset_for_list(self.get_queryset())
|
||||
self.paginate_queryset(queryset)
|
||||
|
||||
|
@ -78,7 +78,7 @@ class PropertyMappingViewSet(
|
||||
filterset_fields = {"managed": ["isnull"]}
|
||||
ordering = ["name"]
|
||||
|
||||
def get_queryset(self):
|
||||
def get_queryset(self): # pragma: no cover
|
||||
return PropertyMapping.objects.select_subclasses()
|
||||
|
||||
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)})
|
||||
|
@ -63,7 +63,7 @@ class ProviderViewSet(
|
||||
"application__name",
|
||||
]
|
||||
|
||||
def get_queryset(self):
|
||||
def get_queryset(self): # pragma: no cover
|
||||
return Provider.objects.select_subclasses()
|
||||
|
||||
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)})
|
||||
|
@ -61,7 +61,7 @@ class SourceViewSet(
|
||||
serializer_class = SourceSerializer
|
||||
lookup_field = "slug"
|
||||
|
||||
def get_queryset(self):
|
||||
def get_queryset(self): # pragma: no cover
|
||||
return Source.objects.select_subclasses()
|
||||
|
||||
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)})
|
||||
|
@ -139,7 +139,7 @@ class UserViewSet(ModelViewSet):
|
||||
search_fields = ["username", "name", "is_active"]
|
||||
filterset_class = UsersFilter
|
||||
|
||||
def get_queryset(self):
|
||||
def get_queryset(self): # pragma: no cover
|
||||
return User.objects.all().exclude(pk=get_anonymous_user().pk)
|
||||
|
||||
@swagger_auto_schema(responses={200: SessionUserSerializer(many=False)})
|
||||
|
@ -4,7 +4,7 @@ from channels.generic.websocket import JsonWebsocketConsumer
|
||||
from rest_framework.exceptions import AuthenticationFailed
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.api.auth import token_from_header
|
||||
from authentik.api.authentication import token_from_header
|
||||
from authentik.core.models import User
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
20
authentik/core/migrations/0021_alter_application_slug.py
Normal file
20
authentik/core/migrations/0021_alter_application_slug.py
Normal file
@ -0,0 +1,20 @@
|
||||
# Generated by Django 3.2.3 on 2021-05-14 08:48
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_core", "0020_source_user_matching_mode"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="application",
|
||||
name="slug",
|
||||
field=models.SlugField(
|
||||
help_text="Internal application name, used in URLs.", unique=True
|
||||
),
|
||||
),
|
||||
]
|
@ -207,7 +207,9 @@ class Application(PolicyBindingModel):
|
||||
add custom fields and other properties"""
|
||||
|
||||
name = models.TextField(help_text=_("Application's display Name."))
|
||||
slug = models.SlugField(help_text=_("Internal application name, used in URLs."))
|
||||
slug = models.SlugField(
|
||||
help_text=_("Internal application name, used in URLs."), unique=True
|
||||
)
|
||||
provider = models.OneToOneField(
|
||||
"Provider", null=True, blank=True, default=None, on_delete=models.SET_DEFAULT
|
||||
)
|
||||
|
@ -75,5 +75,6 @@ def backup_database(self: MonitoredTask): # pragma: no cover
|
||||
Boto3Error,
|
||||
PermissionError,
|
||||
CommandConnectorError,
|
||||
ValueError,
|
||||
) as exc:
|
||||
self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))
|
||||
|
@ -1,12 +1,12 @@
|
||||
"""Notification API Views"""
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from guardian.utils import get_anonymous_user
|
||||
from rest_framework import mixins
|
||||
from rest_framework.fields import ReadOnlyField
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import GenericViewSet
|
||||
|
||||
from authentik.api.authorization import OwnerFilter, OwnerPermissions
|
||||
from authentik.events.api.event import EventSerializer
|
||||
from authentik.events.models import Notification
|
||||
|
||||
@ -49,12 +49,5 @@ class NotificationViewSet(
|
||||
"event",
|
||||
"seen",
|
||||
]
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
OrderingFilter,
|
||||
SearchFilter,
|
||||
]
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user if self.request else get_anonymous_user()
|
||||
return Notification.objects.filter(user=user.pk)
|
||||
permission_classes = [OwnerPermissions]
|
||||
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
|
||||
|
@ -2,22 +2,25 @@
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from authentik.core.api.groups import GroupSerializer
|
||||
from authentik.events.models import NotificationRule
|
||||
|
||||
|
||||
class NotificationRuleSerializer(ModelSerializer):
|
||||
"""NotificationRule Serializer"""
|
||||
|
||||
group_obj = GroupSerializer(read_only=True, source="group")
|
||||
|
||||
class Meta:
|
||||
|
||||
model = NotificationRule
|
||||
depth = 2
|
||||
fields = [
|
||||
"pk",
|
||||
"name",
|
||||
"transports",
|
||||
"severity",
|
||||
"group",
|
||||
"group_obj",
|
||||
]
|
||||
|
||||
|
||||
|
@ -210,6 +210,7 @@ class FlowViewSet(ModelViewSet):
|
||||
request.user, "authentik_policies.view_policybinding"
|
||||
)
|
||||
.filter(target=stage_binding)
|
||||
.exclude(policy__isnull=True)
|
||||
.order_by("order")
|
||||
):
|
||||
body.append(
|
||||
|
@ -65,7 +65,7 @@ class StageViewSet(
|
||||
search_fields = ["name"]
|
||||
filterset_fields = ["name"]
|
||||
|
||||
def get_queryset(self):
|
||||
def get_queryset(self): # pragma: no cover
|
||||
return Stage.objects.select_subclasses()
|
||||
|
||||
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)})
|
||||
|
@ -21,7 +21,7 @@ context["user_backend"] = "django.contrib.auth.backends.ModelBackend"
|
||||
return True"""
|
||||
|
||||
|
||||
def create_default_oob_flow(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
def create_default_oobe_flow(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
from authentik.stages.prompt.models import FieldTypes
|
||||
|
||||
User = apps.get_model("authentik_core", "User")
|
||||
@ -52,20 +52,20 @@ def create_default_oob_flow(apps: Apps, schema_editor: BaseDatabaseSchemaEditor)
|
||||
|
||||
# Create a policy that sets the flow's user
|
||||
prefill_policy, _ = ExpressionPolicy.objects.using(db_alias).update_or_create(
|
||||
name="default-oob-prefill-user",
|
||||
name="default-oobe-prefill-user",
|
||||
defaults={"expression": PREFILL_POLICY_EXPRESSION},
|
||||
)
|
||||
password_usable_policy, _ = ExpressionPolicy.objects.using(
|
||||
db_alias
|
||||
).update_or_create(
|
||||
name="default-oob-password-usable",
|
||||
name="default-oobe-password-usable",
|
||||
defaults={"expression": PW_USABLE_POLICY_EXPRESSION},
|
||||
)
|
||||
|
||||
prompt_header, _ = Prompt.objects.using(db_alias).update_or_create(
|
||||
field_key="oob-header-text",
|
||||
field_key="oobe-header-text",
|
||||
defaults={
|
||||
"label": "oob-header-text",
|
||||
"label": "oobe-header-text",
|
||||
"type": FieldTypes.STATIC,
|
||||
"placeholder": "Welcome to authentik! Please set a password for the default admin user, akadmin.",
|
||||
"order": 100,
|
||||
@ -84,7 +84,7 @@ def create_default_oob_flow(apps: Apps, schema_editor: BaseDatabaseSchemaEditor)
|
||||
password_second = Prompt.objects.using(db_alias).get(field_key="password_repeat")
|
||||
|
||||
prompt_stage, _ = PromptStage.objects.using(db_alias).update_or_create(
|
||||
name="default-oob-password",
|
||||
name="default-oobe-password",
|
||||
)
|
||||
prompt_stage.fields.set(
|
||||
[prompt_header, prompt_email, password_first, password_second]
|
||||
@ -102,7 +102,7 @@ def create_default_oob_flow(apps: Apps, schema_editor: BaseDatabaseSchemaEditor)
|
||||
slug="initial-setup",
|
||||
designation=FlowDesignation.STAGE_CONFIGURATION,
|
||||
defaults={
|
||||
"name": "default-oob-setup",
|
||||
"name": "default-oobe-setup",
|
||||
"title": "Welcome to authentik!",
|
||||
},
|
||||
)
|
||||
@ -146,5 +146,5 @@ class Migration(migrations.Migration):
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(create_default_oob_flow),
|
||||
migrations.RunPython(create_default_oobe_flow),
|
||||
]
|
||||
|
@ -298,7 +298,7 @@ class CancelView(View):
|
||||
if SESSION_KEY_PLAN in request.session:
|
||||
del request.session[SESSION_KEY_PLAN]
|
||||
LOGGER.debug("Canceled current plan")
|
||||
return redirect("authentik_core:default-invalidation")
|
||||
return redirect("authentik_flows:default-invalidation")
|
||||
|
||||
|
||||
class ToDefaultFlow(View):
|
||||
|
@ -88,10 +88,10 @@ class ConfigLoader:
|
||||
value = os.getenv(url.netloc, url.query)
|
||||
if url.scheme == "file":
|
||||
try:
|
||||
with open(url.netloc, "r") as _file:
|
||||
with open(url.path, "r") as _file:
|
||||
value = _file.read()
|
||||
except OSError:
|
||||
self._log("error", f"Failed to read config value from {url.netloc}")
|
||||
self._log("error", f"Failed to read config value from {url.path}")
|
||||
value = url.query
|
||||
return value
|
||||
|
||||
|
@ -42,7 +42,8 @@ outposts:
|
||||
# Placeholders:
|
||||
# %(type)s: Outpost type; proxy, ldap, etc
|
||||
# %(version)s: Current version; 2021.4.1
|
||||
docker_image_base: "beryju/authentik-%(type)s:%(version)s"
|
||||
# %(build_hash)s: Build hash if you're running a beta version
|
||||
docker_image_base: "ghcr.io/goauthentik/%(type)s:%(version)s"
|
||||
|
||||
authentik:
|
||||
avatars: gravatar # gravatar or none
|
||||
|
@ -4,10 +4,15 @@ from typing import Optional
|
||||
from aioredis.errors import ConnectionClosedError, ReplyError
|
||||
from billiard.exceptions import WorkerLostError
|
||||
from botocore.client import ClientError
|
||||
from botocore.exceptions import BotoCoreError
|
||||
from celery.exceptions import CeleryError
|
||||
from channels.middleware import BaseMiddleware
|
||||
from channels_redis.core import ChannelFull
|
||||
from django.core.exceptions import SuspiciousOperation, ValidationError
|
||||
from django.core.exceptions import (
|
||||
ImproperlyConfigured,
|
||||
SuspiciousOperation,
|
||||
ValidationError,
|
||||
)
|
||||
from django.db import InternalError, OperationalError, ProgrammingError
|
||||
from django.http.response import Http404
|
||||
from django_redis.exceptions import ConnectionInterrupted
|
||||
@ -50,7 +55,8 @@ def before_send(event: dict, hint: dict) -> Optional[dict]:
|
||||
ConnectionResetError,
|
||||
OSError,
|
||||
PermissionError,
|
||||
# Django DB Errors
|
||||
# Django Errors
|
||||
ImproperlyConfigured,
|
||||
OperationalError,
|
||||
InternalError,
|
||||
ProgrammingError,
|
||||
@ -72,6 +78,7 @@ def before_send(event: dict, hint: dict) -> Optional[dict]:
|
||||
WorkerLostError,
|
||||
CeleryError,
|
||||
# S3 errors
|
||||
BotoCoreError,
|
||||
ClientError,
|
||||
# custom baseclass
|
||||
SentryIgnoredException,
|
||||
@ -87,6 +94,6 @@ def before_send(event: dict, hint: dict) -> Optional[dict]:
|
||||
if isinstance(exc_value, ignored_classes):
|
||||
return None
|
||||
if "logger" in event:
|
||||
if event["logger"] in ["dbbackup"]:
|
||||
if event["logger"] in ["dbbackup", "botocore"]:
|
||||
return None
|
||||
return event
|
||||
|
@ -17,7 +17,8 @@ def _get_client_ip_from_meta(meta: dict[str, Any]) -> Optional[str]:
|
||||
)
|
||||
for _header in headers:
|
||||
if _header in meta:
|
||||
return meta.get(_header).split(", ")[0]
|
||||
ips: list[str] = meta.get(_header).split(",")
|
||||
return ips[0].strip()
|
||||
return None
|
||||
|
||||
|
||||
|
@ -2,28 +2,6 @@
|
||||
from django.http import HttpRequest
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic import CreateView
|
||||
from guardian.shortcuts import assign_perm
|
||||
|
||||
|
||||
class CreateAssignPermView(CreateView):
|
||||
"""Assign permissions to object after creation"""
|
||||
|
||||
permissions = [
|
||||
"%s.view_%s",
|
||||
"%s.change_%s",
|
||||
"%s.delete_%s",
|
||||
]
|
||||
|
||||
def form_valid(self, form):
|
||||
response = super().form_valid(form)
|
||||
for permission in self.permissions:
|
||||
full_permission = permission % (
|
||||
self.object._meta.app_label,
|
||||
self.object._meta.model_name,
|
||||
)
|
||||
assign_perm(full_permission, self.request.user, self.object)
|
||||
return response
|
||||
|
||||
|
||||
def bad_request_message(
|
||||
|
@ -18,8 +18,6 @@ class OutpostSerializer(ModelSerializer):
|
||||
"""Outpost Serializer"""
|
||||
|
||||
config = JSONField(validators=[is_dict], source="_config")
|
||||
# TODO: Remove _config again, this is only here for legacy with older outposts
|
||||
_config = JSONField(validators=[is_dict], read_only=True)
|
||||
providers_obj = ProviderSerializer(source="providers", many=True, read_only=True)
|
||||
|
||||
def validate_config(self, config) -> dict:
|
||||
@ -42,7 +40,6 @@ class OutpostSerializer(ModelSerializer):
|
||||
"service_connection",
|
||||
"token_identifier",
|
||||
"config",
|
||||
"_config",
|
||||
]
|
||||
|
||||
|
||||
|
@ -40,7 +40,7 @@ class WebsocketMessage:
|
||||
class OutpostConsumer(AuthJsonConsumer):
|
||||
"""Handler for Outposts that connect over websockets for health checks and live updates"""
|
||||
|
||||
outpost: Optional[Outpost] = None
|
||||
outpost: Outpost
|
||||
|
||||
last_uid: Optional[str] = None
|
||||
|
||||
@ -64,7 +64,10 @@ class OutpostConsumer(AuthJsonConsumer):
|
||||
# pylint: disable=unused-argument
|
||||
def disconnect(self, close_code):
|
||||
if self.outpost and self.last_uid:
|
||||
OutpostState.for_channel(self.outpost, self.last_uid).delete()
|
||||
state = OutpostState.for_instance_uid(self.outpost, self.last_uid)
|
||||
if self.channel_name in state.channel_ids:
|
||||
state.channel_ids.remove(self.channel_name)
|
||||
state.save()
|
||||
LOGGER.debug(
|
||||
"removed outpost instance from cache",
|
||||
outpost=self.outpost,
|
||||
@ -75,13 +78,13 @@ class OutpostConsumer(AuthJsonConsumer):
|
||||
msg = from_dict(WebsocketMessage, content)
|
||||
uid = msg.args.get("uuid", self.channel_name)
|
||||
self.last_uid = uid
|
||||
state = OutpostState(
|
||||
uid=uid,
|
||||
last_seen=datetime.now(),
|
||||
_outpost=self.outpost,
|
||||
)
|
||||
state = OutpostState.for_instance_uid(self.outpost, uid)
|
||||
if self.channel_name not in state.channel_ids:
|
||||
state.channel_ids.append(self.channel_name)
|
||||
state.last_seen = datetime.now()
|
||||
if msg.instruction == WebsocketMessageInstruction.HELLO:
|
||||
state.version = msg.args.get("version", None)
|
||||
state.build_hash = msg.args.get("buildHash", "")
|
||||
elif msg.instruction == WebsocketMessageInstruction.ACK:
|
||||
return
|
||||
state.save(timeout=OUTPOST_HELLO_INTERVAL * 1.5)
|
||||
|
@ -1,11 +1,12 @@
|
||||
"""Base Controller"""
|
||||
from dataclasses import dataclass
|
||||
from os import environ
|
||||
from typing import Optional
|
||||
|
||||
from structlog.stdlib import get_logger
|
||||
from structlog.testing import capture_logs
|
||||
|
||||
from authentik import __version__
|
||||
from authentik import ENV_GIT_HASH_KEY, __version__
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.lib.sentry import SentryIgnoredException
|
||||
from authentik.outposts.models import Outpost, OutpostServiceConnection
|
||||
@ -69,4 +70,8 @@ class BaseController:
|
||||
def get_container_image(self) -> str:
|
||||
"""Get container image to use for this outpost"""
|
||||
image_name_template: str = CONFIG.y("outposts.docker_image_base")
|
||||
return image_name_template % {"type": self.outpost.type, "version": __version__}
|
||||
return image_name_template % {
|
||||
"type": self.outpost.type,
|
||||
"version": __version__,
|
||||
"build_hash": environ.get(ENV_GIT_HASH_KEY, ""),
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""Outpost models"""
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from datetime import datetime
|
||||
from os import environ
|
||||
from typing import Iterable, Optional, Union
|
||||
from uuid import uuid4
|
||||
|
||||
@ -26,7 +27,7 @@ from packaging.version import LegacyVersion, Version, parse
|
||||
from structlog.stdlib import get_logger
|
||||
from urllib3.exceptions import HTTPError
|
||||
|
||||
from authentik import __version__
|
||||
from authentik import ENV_GIT_HASH_KEY, __version__
|
||||
from authentik.core.models import USER_ATTRIBUTE_SA, Provider, Token, TokenIntents, User
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.lib.config import CONFIG
|
||||
@ -408,9 +409,11 @@ class OutpostState:
|
||||
"""Outpost instance state, last_seen and version"""
|
||||
|
||||
uid: str
|
||||
channel_ids: list[str] = field(default_factory=list)
|
||||
last_seen: Optional[datetime] = field(default=None)
|
||||
version: Optional[str] = field(default=None)
|
||||
version_should: Union[Version, LegacyVersion] = field(default=OUR_VERSION)
|
||||
build_hash: str = field(default="")
|
||||
|
||||
_outpost: Optional[Outpost] = field(default=None)
|
||||
|
||||
@ -419,6 +422,8 @@ class OutpostState:
|
||||
"""Check if outpost version matches our version"""
|
||||
if not self.version:
|
||||
return False
|
||||
if self.build_hash != environ.get(ENV_GIT_HASH_KEY, ""):
|
||||
return False
|
||||
return parse(self.version) < OUR_VERSION
|
||||
|
||||
@staticmethod
|
||||
@ -427,21 +432,20 @@ class OutpostState:
|
||||
keys = cache.keys(f"{outpost.state_cache_prefix}_*")
|
||||
states = []
|
||||
for key in keys:
|
||||
channel = key.replace(f"{outpost.state_cache_prefix}_", "")
|
||||
states.append(OutpostState.for_channel(outpost, channel))
|
||||
instance_uid = key.replace(f"{outpost.state_cache_prefix}_", "")
|
||||
states.append(OutpostState.for_instance_uid(outpost, instance_uid))
|
||||
return states
|
||||
|
||||
@staticmethod
|
||||
def for_channel(outpost: Outpost, channel: str) -> "OutpostState":
|
||||
"""Get state for a single channel"""
|
||||
key = f"{outpost.state_cache_prefix}_{channel}"
|
||||
default_data = {"uid": channel}
|
||||
def for_instance_uid(outpost: Outpost, uid: str) -> "OutpostState":
|
||||
"""Get state for a single instance"""
|
||||
key = f"{outpost.state_cache_prefix}_{uid}"
|
||||
default_data = {"uid": uid, "channel_ids": []}
|
||||
data = cache.get(key, default_data)
|
||||
if isinstance(data, str):
|
||||
cache.delete(key)
|
||||
data = default_data
|
||||
state = from_dict(OutpostState, data)
|
||||
state.uid = channel
|
||||
# pylint: disable=protected-access
|
||||
state._outpost = outpost
|
||||
return state
|
||||
|
@ -100,6 +100,8 @@ def outpost_controller(
|
||||
outpost: Outpost = cache.get(CACHE_KEY_OUTPOST_DOWN % outpost_pk)
|
||||
else:
|
||||
outpost: Outpost = Outpost.objects.get(pk=outpost_pk)
|
||||
if not outpost:
|
||||
return
|
||||
self.set_uid(slugify(outpost.name))
|
||||
try:
|
||||
controller = controller_for_outpost(outpost)
|
||||
@ -200,8 +202,11 @@ def _outpost_single_update(outpost: Outpost, layer=None):
|
||||
if not layer: # pragma: no cover
|
||||
layer = get_channel_layer()
|
||||
for state in OutpostState.for_outpost(outpost):
|
||||
LOGGER.debug("sending update", channel=state.uid, outpost=outpost)
|
||||
async_to_sync(layer.send)(state.uid, {"type": "event.update"})
|
||||
for channel in state.channel_ids:
|
||||
LOGGER.debug(
|
||||
"sending update", channel=channel, instance=state.uid, outpost=outpost
|
||||
)
|
||||
async_to_sync(layer.send)(channel, {"type": "event.update"})
|
||||
|
||||
|
||||
@CELERY_APP.task()
|
||||
|
@ -91,7 +91,7 @@ class PolicyViewSet(
|
||||
}
|
||||
search_fields = ["name"]
|
||||
|
||||
def get_queryset(self):
|
||||
def get_queryset(self): # pragma: no cover
|
||||
return Policy.objects.select_subclasses().prefetch_related(
|
||||
"bindings", "promptstage_set"
|
||||
)
|
||||
|
@ -105,16 +105,21 @@ class PolicyEngine:
|
||||
if cached_policy and self.use_cache:
|
||||
self.logger.debug(
|
||||
"P_ENG: Taking result from cache",
|
||||
policy=binding.policy,
|
||||
binding=binding,
|
||||
cache_key=key,
|
||||
request=self.request,
|
||||
)
|
||||
self.__cached_policies.append(cached_policy)
|
||||
continue
|
||||
self.logger.debug("P_ENG: Evaluating policy", policy=binding.policy)
|
||||
self.logger.debug(
|
||||
"P_ENG: Evaluating policy", binding=binding, request=self.request
|
||||
)
|
||||
our_end, task_end = Pipe(False)
|
||||
task = PolicyProcess(binding, self.request, task_end)
|
||||
task.daemon = False
|
||||
self.logger.debug("P_ENG: Starting Process", policy=binding.policy)
|
||||
self.logger.debug(
|
||||
"P_ENG: Starting Process", binding=binding, request=self.request
|
||||
)
|
||||
if not CURRENT_PROCESS._config.get("daemon"):
|
||||
task.run()
|
||||
else:
|
||||
|
@ -51,7 +51,12 @@ class PolicyRequest:
|
||||
LOGGER.warning("failed to get geoip data", exc=exc)
|
||||
|
||||
def __str__(self):
|
||||
return f"<PolicyRequest user={self.user}>"
|
||||
text = f"<PolicyRequest user={self.user}"
|
||||
if self.obj:
|
||||
text += f" obj={self.obj}"
|
||||
if self.http_request:
|
||||
text += f" http_request={self.http_request}"
|
||||
return text + ">"
|
||||
|
||||
|
||||
@dataclass
|
||||
|
@ -6,13 +6,11 @@ import time
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from datetime import datetime
|
||||
from hashlib import sha256
|
||||
from typing import Any, Optional, Type, Union
|
||||
from typing import Any, Optional, Type
|
||||
from urllib.parse import urlparse
|
||||
from uuid import uuid4
|
||||
|
||||
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
|
||||
from dacite import from_dict
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.http import HttpRequest
|
||||
from django.utils import dateformat, timezone
|
||||
@ -239,7 +237,7 @@ class OAuth2Provider(Provider):
|
||||
token.access_token = token.create_access_token(user, request)
|
||||
return token
|
||||
|
||||
def get_jwt_keys(self) -> Union[RSAPrivateKey, str]:
|
||||
def get_jwt_key(self) -> str:
|
||||
"""
|
||||
Takes a provider and returns the set of keys associated with it.
|
||||
Returns a list of keys.
|
||||
@ -256,7 +254,7 @@ class OAuth2Provider(Provider):
|
||||
self.jwt_alg = JWTAlgorithms.HS256
|
||||
self.save()
|
||||
else:
|
||||
return self.rsa_key.private_key
|
||||
return self.rsa_key.key_data
|
||||
|
||||
if self.jwt_alg == JWTAlgorithms.HS256:
|
||||
return self.client_secret
|
||||
@ -300,11 +298,14 @@ class OAuth2Provider(Provider):
|
||||
|
||||
def encode(self, payload: dict[str, Any]) -> str:
|
||||
"""Represent the ID Token as a JSON Web Token (JWT)."""
|
||||
key = self.get_jwt_keys()
|
||||
headers = {}
|
||||
if self.rsa_key:
|
||||
headers["kid"] = self.rsa_key.kid
|
||||
key = self.get_jwt_key()
|
||||
# If the provider does not have an RSA Key assigned, it was switched to Symmetric
|
||||
self.refresh_from_db()
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
return encode(payload, key, algorithm=self.jwt_alg)
|
||||
return encode(payload, key, algorithm=self.jwt_alg, headers=headers)
|
||||
|
||||
class Meta:
|
||||
|
||||
@ -457,7 +458,7 @@ class RefreshToken(ExpiringModel, BaseGrantModel):
|
||||
See: http://openid.net/specs/openid-connect-core-1_0.html#IDToken"""
|
||||
sub = ""
|
||||
if self.provider.sub_mode == SubModes.HASHED_USER_ID:
|
||||
sub = sha256(f"{user.id}-{settings.SECRET_KEY}".encode("ascii")).hexdigest()
|
||||
sub = user.uid
|
||||
elif self.provider.sub_mode == SubModes.USER_EMAIL:
|
||||
sub = user.email
|
||||
elif self.provider.sub_mode == SubModes.USER_USERNAME:
|
||||
|
@ -4,6 +4,7 @@ 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.flows.challenge import ChallengeTypes
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.providers.oauth2.errors import (
|
||||
@ -207,6 +208,7 @@ class TestAuthorize(OAuthTestCase):
|
||||
client_secret=generate_client_secret(),
|
||||
authorization_flow=flow,
|
||||
redirect_uris="http://localhost",
|
||||
rsa_key=CertificateKeyPair.objects.first(),
|
||||
)
|
||||
Application.objects.create(name="app", slug="app", provider=provider)
|
||||
state = generate_client_id()
|
||||
|
@ -2,7 +2,11 @@
|
||||
from django.test import TestCase
|
||||
from jwt import decode
|
||||
|
||||
from authentik.providers.oauth2.models import OAuth2Provider, RefreshToken
|
||||
from authentik.providers.oauth2.models import (
|
||||
JWTAlgorithms,
|
||||
OAuth2Provider,
|
||||
RefreshToken,
|
||||
)
|
||||
|
||||
|
||||
class OAuthTestCase(TestCase):
|
||||
@ -19,9 +23,12 @@ class OAuthTestCase(TestCase):
|
||||
|
||||
def validate_jwt(self, token: RefreshToken, provider: OAuth2Provider):
|
||||
"""Validate that all required fields are set"""
|
||||
key = provider.client_secret
|
||||
if provider.jwt_alg == JWTAlgorithms.RS256:
|
||||
key = provider.rsa_key.public_key
|
||||
jwt = decode(
|
||||
token.access_token,
|
||||
provider.client_secret,
|
||||
key,
|
||||
algorithms=[provider.jwt_alg],
|
||||
audience=provider.client_id,
|
||||
)
|
||||
|
@ -54,6 +54,7 @@ from authentik.stages.consent.stage import (
|
||||
PLAN_CONTEXT_CONSENT_PERMISSIONS,
|
||||
ConsentStageView,
|
||||
)
|
||||
from authentik.stages.user_login.stage import USER_LOGIN_AUTHENTICATED
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
@ -437,6 +438,10 @@ class AuthorizationFlowInitView(PolicyAccessView):
|
||||
if (
|
||||
PROMPT_LOGIN in self.params.prompt
|
||||
and SESSION_NEEDS_LOGIN not in self.request.session
|
||||
# To prevent the user from having to double login when prompt is set to login
|
||||
# and the user has just signed it. This session variable is set in the UserLoginStage
|
||||
# and is (quite hackily) removed from the session in applications's API's List method
|
||||
and USER_LOGIN_AUTHENTICATED not in self.request.session
|
||||
):
|
||||
self.request.session[SESSION_NEEDS_LOGIN] = True
|
||||
return self.handle_no_permission()
|
||||
|
@ -53,8 +53,10 @@ class ProxyProviderSerializer(ProviderSerializer):
|
||||
return instance
|
||||
|
||||
def update(self, instance: ProxyProvider, validated_data):
|
||||
instance = super().update(instance, validated_data)
|
||||
instance.set_oauth_defaults()
|
||||
return super().update(instance, validated_data)
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
class Meta:
|
||||
|
||||
|
@ -127,7 +127,7 @@ class ProxyProvider(OutpostModel, OAuth2Provider):
|
||||
"""Ensure all OAuth2-related settings are correct"""
|
||||
self.client_type = ClientTypes.CONFIDENTIAL
|
||||
self.jwt_alg = JWTAlgorithms.RS256
|
||||
self.rsa_key = CertificateKeyPair.objects.first()
|
||||
self.rsa_key = CertificateKeyPair.objects.exclude(key_data__iexact="").first()
|
||||
scopes = ScopeMapping.objects.filter(
|
||||
scope_name__in=[
|
||||
SCOPE_OPENID,
|
||||
|
@ -20,6 +20,7 @@ from time import time
|
||||
import structlog
|
||||
from celery.schedules import crontab
|
||||
from sentry_sdk import init as sentry_init
|
||||
from sentry_sdk.api import set_tag
|
||||
from sentry_sdk.integrations.celery import CeleryIntegration
|
||||
from sentry_sdk.integrations.django import DjangoIntegration
|
||||
from sentry_sdk.integrations.redis import RedisIntegration
|
||||
@ -52,11 +53,9 @@ STATIC_ROOT = BASE_DIR + "/static"
|
||||
STATICFILES_DIRS = [BASE_DIR + "/web"]
|
||||
MEDIA_ROOT = BASE_DIR + "/media"
|
||||
|
||||
SECRET_KEY = CONFIG.y(
|
||||
"secret_key", "9$@r!d^1^jrn#fk#1#@ks#9&i$^s#1)_13%$rwjrhd=e8jfi_s"
|
||||
) # noqa Debug
|
||||
|
||||
DEBUG = CONFIG.y_bool("debug")
|
||||
SECRET_KEY = CONFIG.y("secret_key")
|
||||
|
||||
INTERNAL_IPS = ["127.0.0.1"]
|
||||
ALLOWED_HOSTS = ["*"]
|
||||
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
||||
@ -162,7 +161,7 @@ REST_FRAMEWORK = {
|
||||
"rest_framework.permissions.DjangoObjectPermissions",
|
||||
),
|
||||
"DEFAULT_AUTHENTICATION_CLASSES": (
|
||||
"authentik.api.auth.AuthentikTokenAuthentication",
|
||||
"authentik.api.authentication.TokenAuthentication",
|
||||
"rest_framework.authentication.SessionAuthentication",
|
||||
),
|
||||
"DEFAULT_RENDERER_CLASSES": [
|
||||
@ -320,6 +319,7 @@ CELERY_RESULT_BACKEND = (
|
||||
# Database backup
|
||||
DBBACKUP_STORAGE = "django.core.files.storage.FileSystemStorage"
|
||||
DBBACKUP_STORAGE_OPTIONS = {"location": "./backups" if DEBUG else "/backups"}
|
||||
DBBACKUP_FILENAME_TEMPLATE = "authentik-backup-{datetime}.sql"
|
||||
if CONFIG.y("postgresql.s3_backup"):
|
||||
DBBACKUP_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
|
||||
DBBACKUP_STORAGE_OPTIONS = {
|
||||
@ -353,6 +353,11 @@ if _ERROR_REPORTING:
|
||||
environment=CONFIG.y("error_reporting.environment", "customer"),
|
||||
send_default_pii=CONFIG.y_bool("error_reporting.send_pii", False),
|
||||
)
|
||||
set_tag("authentik:build_hash", os.environ.get(ENV_GIT_HASH_KEY, "tagged"))
|
||||
set_tag(
|
||||
"authentik:env", "kubernetes" if "KUBERNETES_PORT" in os.environ else "compose"
|
||||
)
|
||||
set_tag("authentik:component", "backend")
|
||||
j_print(
|
||||
"Error reporting is enabled",
|
||||
env=CONFIG.y("error_reporting.environment", "customer"),
|
||||
|
@ -1,9 +1,10 @@
|
||||
"""OAuth Source Serializer"""
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from guardian.utils import get_anonymous_user
|
||||
from rest_framework import mixins
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
from rest_framework.viewsets import GenericViewSet
|
||||
|
||||
from authentik.api.authorization import OwnerFilter, OwnerPermissions
|
||||
from authentik.core.api.sources import SourceSerializer
|
||||
from authentik.sources.oauth.models import UserOAuthSourceConnection
|
||||
|
||||
@ -21,20 +22,17 @@ class UserOAuthSourceConnectionSerializer(SourceSerializer):
|
||||
]
|
||||
|
||||
|
||||
class UserOAuthSourceConnectionViewSet(ModelViewSet):
|
||||
class UserOAuthSourceConnectionViewSet(
|
||||
mixins.RetrieveModelMixin,
|
||||
mixins.UpdateModelMixin,
|
||||
mixins.DestroyModelMixin,
|
||||
mixins.ListModelMixin,
|
||||
GenericViewSet,
|
||||
):
|
||||
"""Source Viewset"""
|
||||
|
||||
queryset = UserOAuthSourceConnection.objects.all()
|
||||
serializer_class = UserOAuthSourceConnectionSerializer
|
||||
filterset_fields = ["source__slug"]
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
OrderingFilter,
|
||||
SearchFilter,
|
||||
]
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user if self.request else get_anonymous_user()
|
||||
if user.is_superuser:
|
||||
return super().get_queryset()
|
||||
return super().get_queryset().filter(user=user.pk)
|
||||
permission_classes = [OwnerPermissions]
|
||||
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
|
||||
|
@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.2.3 on 2021-05-20 17:04
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_sources_plex", "0002_auto_20210505_1717"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="plexsource",
|
||||
name="plex_token",
|
||||
field=models.TextField(help_text="Plex token used to check firends"),
|
||||
),
|
||||
]
|
@ -41,9 +41,7 @@ class PlexSource(Source):
|
||||
default=True,
|
||||
help_text=_("Allow friends to authenticate, even if you don't share a server."),
|
||||
)
|
||||
plex_token = models.TextField(
|
||||
default="", help_text=_("Plex token used to check firends")
|
||||
)
|
||||
plex_token = models.TextField(help_text=_("Plex token used to check firends"))
|
||||
|
||||
@property
|
||||
def component(self) -> str:
|
||||
|
@ -1,13 +1,13 @@
|
||||
"""AuthenticatorStaticStage API Views"""
|
||||
from django_filters import OrderingFilter
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from django_otp.plugins.otp_static.models import StaticDevice
|
||||
from guardian.utils import get_anonymous_user
|
||||
from rest_framework.filters import SearchFilter
|
||||
from rest_framework import mixins
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.permissions import IsAdminUser
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
|
||||
from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet
|
||||
|
||||
from authentik.api.authorization import OwnerFilter, OwnerPermissions
|
||||
from authentik.flows.api.stages import StageSerializer
|
||||
from authentik.stages.authenticator_static.models import AuthenticatorStaticStage
|
||||
|
||||
@ -38,23 +38,22 @@ class StaticDeviceSerializer(ModelSerializer):
|
||||
depth = 2
|
||||
|
||||
|
||||
class StaticDeviceViewSet(ModelViewSet):
|
||||
class StaticDeviceViewSet(
|
||||
mixins.RetrieveModelMixin,
|
||||
mixins.UpdateModelMixin,
|
||||
mixins.DestroyModelMixin,
|
||||
mixins.ListModelMixin,
|
||||
GenericViewSet,
|
||||
):
|
||||
"""Viewset for static authenticator devices"""
|
||||
|
||||
queryset = StaticDevice.objects.none()
|
||||
queryset = StaticDevice.objects.all()
|
||||
serializer_class = StaticDeviceSerializer
|
||||
permission_classes = [OwnerPermissions]
|
||||
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
|
||||
search_fields = ["name"]
|
||||
filterset_fields = ["name"]
|
||||
ordering = ["name"]
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
OrderingFilter,
|
||||
SearchFilter,
|
||||
]
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user if self.request else get_anonymous_user()
|
||||
return StaticDevice.objects.filter(user=user.pk)
|
||||
|
||||
|
||||
class StaticAdminDeviceViewSet(ReadOnlyModelViewSet):
|
||||
|
20
authentik/stages/authenticator_static/tests.py
Normal file
20
authentik/stages/authenticator_static/tests.py
Normal file
@ -0,0 +1,20 @@
|
||||
"""Test Static API"""
|
||||
from django.urls import reverse
|
||||
from django_otp.plugins.otp_static.models import StaticDevice
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.models import User
|
||||
|
||||
|
||||
class AuthenticatorStaticStage(APITestCase):
|
||||
"""Test Static API"""
|
||||
|
||||
def test_api_delete(self):
|
||||
"""Test api delete"""
|
||||
user = User.objects.create(username="foo")
|
||||
self.client.force_login(user)
|
||||
dev = StaticDevice.objects.create(user=user)
|
||||
response = self.client.delete(
|
||||
reverse("authentik_api:staticdevice-detail", kwargs={"pk": dev.pk})
|
||||
)
|
||||
self.assertEqual(response.status_code, 204)
|
@ -1,12 +1,13 @@
|
||||
"""AuthenticatorTOTPStage API Views"""
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from django_filters.rest_framework.backends import DjangoFilterBackend
|
||||
from django_otp.plugins.otp_totp.models import TOTPDevice
|
||||
from guardian.utils import get_anonymous_user
|
||||
from rest_framework import mixins
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.permissions import IsAdminUser
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
|
||||
from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet
|
||||
|
||||
from authentik.api.authorization import OwnerFilter, OwnerPermissions
|
||||
from authentik.flows.api.stages import StageSerializer
|
||||
from authentik.stages.authenticator_totp.models import AuthenticatorTOTPStage
|
||||
|
||||
@ -40,23 +41,22 @@ class TOTPDeviceSerializer(ModelSerializer):
|
||||
depth = 2
|
||||
|
||||
|
||||
class TOTPDeviceViewSet(ModelViewSet):
|
||||
class TOTPDeviceViewSet(
|
||||
mixins.RetrieveModelMixin,
|
||||
mixins.UpdateModelMixin,
|
||||
mixins.DestroyModelMixin,
|
||||
mixins.ListModelMixin,
|
||||
GenericViewSet,
|
||||
):
|
||||
"""Viewset for totp authenticator devices"""
|
||||
|
||||
queryset = TOTPDevice.objects.none()
|
||||
queryset = TOTPDevice.objects.all()
|
||||
serializer_class = TOTPDeviceSerializer
|
||||
permission_classes = [OwnerPermissions]
|
||||
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
|
||||
search_fields = ["name"]
|
||||
filterset_fields = ["name"]
|
||||
ordering = ["name"]
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
OrderingFilter,
|
||||
SearchFilter,
|
||||
]
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user if self.request else get_anonymous_user()
|
||||
return TOTPDevice.objects.filter(user=user.pk)
|
||||
|
||||
|
||||
class TOTPAdminDeviceViewSet(ReadOnlyModelViewSet):
|
||||
|
20
authentik/stages/authenticator_totp/tests.py
Normal file
20
authentik/stages/authenticator_totp/tests.py
Normal file
@ -0,0 +1,20 @@
|
||||
"""Test TOTP API"""
|
||||
from django.urls import reverse
|
||||
from django_otp.plugins.otp_totp.models import TOTPDevice
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.models import User
|
||||
|
||||
|
||||
class AuthenticatorTOTPStage(APITestCase):
|
||||
"""Test TOTP API"""
|
||||
|
||||
def test_api_delete(self):
|
||||
"""Test api delete"""
|
||||
user = User.objects.create(username="foo")
|
||||
self.client.force_login(user)
|
||||
dev = TOTPDevice.objects.create(user=user)
|
||||
response = self.client.delete(
|
||||
reverse("authentik_api:totpdevice-detail", kwargs={"pk": dev.pk})
|
||||
)
|
||||
self.assertEqual(response.status_code, 204)
|
@ -1,11 +1,12 @@
|
||||
"""AuthenticateWebAuthnStage API Views"""
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from guardian.utils import get_anonymous_user
|
||||
from django_filters.rest_framework.backends import DjangoFilterBackend
|
||||
from rest_framework import mixins
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.permissions import IsAdminUser
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
|
||||
from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet
|
||||
|
||||
from authentik.api.authorization import OwnerFilter, OwnerPermissions
|
||||
from authentik.flows.api.stages import StageSerializer
|
||||
from authentik.stages.authenticator_webauthn.models import (
|
||||
AuthenticateWebAuthnStage,
|
||||
@ -39,23 +40,22 @@ class WebAuthnDeviceSerializer(ModelSerializer):
|
||||
depth = 2
|
||||
|
||||
|
||||
class WebAuthnDeviceViewSet(ModelViewSet):
|
||||
class WebAuthnDeviceViewSet(
|
||||
mixins.RetrieveModelMixin,
|
||||
mixins.UpdateModelMixin,
|
||||
mixins.DestroyModelMixin,
|
||||
mixins.ListModelMixin,
|
||||
GenericViewSet,
|
||||
):
|
||||
"""Viewset for WebAuthn authenticator devices"""
|
||||
|
||||
queryset = WebAuthnDevice.objects.none()
|
||||
queryset = WebAuthnDevice.objects.all()
|
||||
serializer_class = WebAuthnDeviceSerializer
|
||||
search_fields = ["name"]
|
||||
filterset_fields = ["name"]
|
||||
ordering = ["name"]
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
OrderingFilter,
|
||||
SearchFilter,
|
||||
]
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user if self.request else get_anonymous_user()
|
||||
return WebAuthnDevice.objects.filter(user=user.pk)
|
||||
permission_classes = [OwnerPermissions]
|
||||
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
|
||||
|
||||
|
||||
class WebAuthnAdminDeviceViewSet(ReadOnlyModelViewSet):
|
||||
|
20
authentik/stages/authenticator_webauthn/tests.py
Normal file
20
authentik/stages/authenticator_webauthn/tests.py
Normal file
@ -0,0 +1,20 @@
|
||||
"""Test WebAuthn API"""
|
||||
from django.urls import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.models import User
|
||||
from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
|
||||
|
||||
|
||||
class AuthenticatorWebAuthnStage(APITestCase):
|
||||
"""Test WebAuthn API"""
|
||||
|
||||
def test_api_delete(self):
|
||||
"""Test api delete"""
|
||||
user = User.objects.create(username="foo")
|
||||
self.client.force_login(user)
|
||||
dev = WebAuthnDevice.objects.create(user=user)
|
||||
response = self.client.delete(
|
||||
reverse("authentik_api:webauthndevice-detail", kwargs={"pk": dev.pk})
|
||||
)
|
||||
self.assertEqual(response.status_code, 204)
|
@ -1,5 +1,5 @@
|
||||
"""dummy tests"""
|
||||
from django.test import Client, TestCase
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from django.utils.encoding import force_str
|
||||
|
||||
@ -14,7 +14,6 @@ class TestDummyStage(TestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.user = User.objects.create(username="unittest", email="test@beryju.org")
|
||||
self.client = Client()
|
||||
|
||||
self.flow = Flow.objects.create(
|
||||
name="test-dummy",
|
||||
|
@ -12,6 +12,7 @@ from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
|
||||
|
||||
LOGGER = get_logger()
|
||||
DEFAULT_BACKEND = "django.contrib.auth.backends.ModelBackend"
|
||||
USER_LOGIN_AUTHENTICATED = "user_login_authenticated"
|
||||
|
||||
|
||||
class UserLoginStageView(StageView):
|
||||
@ -43,5 +44,6 @@ class UserLoginStageView(StageView):
|
||||
flow_slug=self.executor.flow.slug,
|
||||
session_duration=self.executor.current_stage.session_duration,
|
||||
)
|
||||
self.request.session[USER_LOGIN_AUTHENTICATED] = True
|
||||
messages.success(self.request, _("Successfully logged in!"))
|
||||
return self.executor.stage_ok()
|
||||
|
@ -43,7 +43,9 @@ stages:
|
||||
pipenv install --dev
|
||||
- task: CmdLine@2
|
||||
inputs:
|
||||
script: pipenv run pylint authentik tests lifecycle
|
||||
script: |
|
||||
pipenv run python -m scripts.generate_ci_config
|
||||
pipenv run pylint authentik tests lifecycle
|
||||
- job: black
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
@ -140,7 +142,9 @@ stages:
|
||||
pipenv install --dev
|
||||
- task: CmdLine@2
|
||||
inputs:
|
||||
script: pipenv run ./manage.py migrate
|
||||
script: |
|
||||
pipenv run python -m scripts.generate_ci_config
|
||||
pipenv run ./manage.py migrate
|
||||
- job: migrations_from_previous_release
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
@ -171,8 +175,9 @@ stages:
|
||||
- task: CmdLine@2
|
||||
displayName: Migrate to last tagged release
|
||||
inputs:
|
||||
script:
|
||||
pipenv run ./manage.py migrate
|
||||
script: |
|
||||
pipenv run python -m scripts.generate_ci_config
|
||||
pipenv run python -m lifecycle.migrate
|
||||
- task: CmdLine@2
|
||||
displayName: Install current branch
|
||||
inputs:
|
||||
@ -184,8 +189,8 @@ stages:
|
||||
displayName: Migrate to current branch
|
||||
inputs:
|
||||
script: |
|
||||
pipenv run python -m scripts.generate_ci_config
|
||||
pipenv run python -m lifecycle.migrate
|
||||
pipenv run ./manage.py migrate
|
||||
- job: coverage_unittest
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
@ -210,6 +215,7 @@ stages:
|
||||
displayName: Run full test suite
|
||||
inputs:
|
||||
script: |
|
||||
pipenv run python -m scripts.generate_ci_config
|
||||
pipenv run make test
|
||||
- task: CmdLine@2
|
||||
inputs:
|
||||
@ -253,6 +259,7 @@ stages:
|
||||
displayName: Run full test suite
|
||||
inputs:
|
||||
script: |
|
||||
pipenv run python -m scripts.generate_ci_config
|
||||
pipenv run make test-integration
|
||||
- task: CmdLine@2
|
||||
inputs:
|
||||
@ -308,6 +315,7 @@ stages:
|
||||
displayName: Run full test suite
|
||||
inputs:
|
||||
script: |
|
||||
pipenv run python -m scripts.generate_ci_config
|
||||
pipenv run make test-e2e
|
||||
- task: CmdLine@2
|
||||
condition: always()
|
||||
|
@ -21,7 +21,7 @@ services:
|
||||
networks:
|
||||
- internal
|
||||
server:
|
||||
image: ${AUTHENTIK_IMAGE:-beryju/authentik}:${AUTHENTIK_TAG:-2021.5.1-rc8}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.5.4}
|
||||
restart: unless-stopped
|
||||
command: server
|
||||
environment:
|
||||
@ -52,7 +52,7 @@ services:
|
||||
- "0.0.0.0:9000:9000"
|
||||
- "0.0.0.0:9443:9443"
|
||||
worker:
|
||||
image: ${AUTHENTIK_IMAGE:-beryju/authentik}:${AUTHENTIK_TAG:-2021.5.1-rc8}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.5.4}
|
||||
restart: unless-stopped
|
||||
command: worker
|
||||
networks:
|
||||
@ -64,8 +64,13 @@ services:
|
||||
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
|
||||
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
|
||||
# AUTHENTIK_ERROR_REPORTING__ENABLED: "true"
|
||||
# This is optional, and can be removed. If you remove this, the following will happen
|
||||
# - The permissions for the /backups and /media folders aren't fixed, so make sure they are 1000:1000
|
||||
# - The docker socket can't be accessed anymore
|
||||
user: root
|
||||
volumes:
|
||||
- ./backups:/backups
|
||||
- ./media:/media
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./custom-templates:/templates
|
||||
- geoip:/geoip
|
||||
|
@ -1,3 +1,3 @@
|
||||
package constants
|
||||
|
||||
const VERSION = "2021.5.1-rc8"
|
||||
const VERSION = "2021.5.4"
|
||||
|
@ -9,7 +9,18 @@ import (
|
||||
func (ws *WebServer) configureProxy() {
|
||||
// Reverse proxy to the application server
|
||||
u, _ := url.Parse("http://localhost:8000")
|
||||
rp := httputil.NewSingleHostReverseProxy(u)
|
||||
director := func(req *http.Request) {
|
||||
req.URL.Scheme = u.Scheme
|
||||
req.URL.Host = u.Host
|
||||
if _, ok := req.Header["User-Agent"]; !ok {
|
||||
// explicitly disable User-Agent so it's not set to default value
|
||||
req.Header.Set("User-Agent", "")
|
||||
}
|
||||
if req.TLS != nil {
|
||||
req.Header.Set("X-Forwarded-Proto", "https")
|
||||
}
|
||||
}
|
||||
rp := &httputil.ReverseProxy{Director: director}
|
||||
rp.ErrorHandler = ws.proxyErrorHandler
|
||||
rp.ModifyResponse = ws.proxyModifyResponse
|
||||
ws.m.PathPrefix("/").Handler(rp)
|
||||
|
@ -4,16 +4,19 @@ import (
|
||||
"net/http"
|
||||
|
||||
"goauthentik.io/internal/config"
|
||||
"goauthentik.io/internal/constants"
|
||||
staticWeb "goauthentik.io/web"
|
||||
)
|
||||
|
||||
func (ws *WebServer) configureStatic() {
|
||||
statRouter := ws.lh.NewRoute().Subrouter()
|
||||
if config.G.Debug {
|
||||
ws.log.Debug("Using local static files")
|
||||
ws.lh.PathPrefix("/static/dist").Handler(http.StripPrefix("/static/dist", http.FileServer(http.Dir("./web/dist"))))
|
||||
ws.lh.PathPrefix("/static/authentik").Handler(http.StripPrefix("/static/authentik", http.FileServer(http.Dir("./web/authentik"))))
|
||||
} else {
|
||||
ws.log.Debug("Using packaged static files")
|
||||
statRouter.Use(ws.staticHeaderMiddleware)
|
||||
ws.log.Debug("Using packaged static files with aggressive caching")
|
||||
ws.lh.PathPrefix("/static/dist").Handler(http.StripPrefix("/static", http.FileServer(http.FS(staticWeb.StaticDist))))
|
||||
ws.lh.PathPrefix("/static/authentik").Handler(http.StripPrefix("/static", http.FileServer(http.FS(staticWeb.StaticAuthentik))))
|
||||
}
|
||||
@ -41,3 +44,12 @@ func (ws *WebServer) configureStatic() {
|
||||
// Media files, always local
|
||||
ws.lh.PathPrefix("/media").Handler(http.StripPrefix("/media", http.FileServer(http.Dir(config.G.Paths.Media))))
|
||||
}
|
||||
|
||||
func (ws *WebServer) staticHeaderMiddleware(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Cache-Control", "\"public, no-transform\"")
|
||||
w.Header().Set("X-authentik-version", constants.VERSION)
|
||||
w.Header().Set("Vary", "X-authentik-version")
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
@ -1,14 +1,31 @@
|
||||
#!/bin/bash -e
|
||||
python -m lifecycle.wait_for_db
|
||||
printf '{"event": "Bootstrap completed", "level": "info", "logger": "bootstrap", "command": "%s"}\n' "$@" > /dev/stderr
|
||||
|
||||
function check_if_root {
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
printf '{"event": "Not running as root, disabling permission fixes", "level": "info", "logger": "bootstrap", "command": "%s"}\n' "$@" > /dev/stderr
|
||||
$1
|
||||
return
|
||||
fi
|
||||
SOCKET="/var/run/docker.sock"
|
||||
if [[ -e "$SOCKET" ]]; then
|
||||
# Get group ID of the docker socket, so we can create a matching group and
|
||||
# add ourselves to it
|
||||
DOCKER_GID=$(stat -c '%g' $SOCKET)
|
||||
getent group $DOCKER_GID || groupadd -f -g $DOCKER_GID docker
|
||||
usermod -a -G $DOCKER_GID authentik
|
||||
fi
|
||||
# Fix permissions of backups and media
|
||||
chown -R authentik:authentik /media /backups
|
||||
chpst -u authentik:authentik:docker env HOME=/authentik $1
|
||||
}
|
||||
|
||||
if [[ "$1" == "server" ]]; then
|
||||
python -m lifecycle.migrate
|
||||
/authentik-proxy
|
||||
elif [[ "$1" == "worker" ]]; then
|
||||
celery -A authentik.root.celery worker --autoscale 3,1 -E -B -s /tmp/celerybeat-schedule -Q authentik,authentik_scheduled,authentik_events
|
||||
elif [[ "$1" == "migrate" ]]; then
|
||||
printf "DEPERECATED: database migrations are now executed automatically on startup."
|
||||
python -m lifecycle.migrate
|
||||
check_if_root "celery -A authentik.root.celery worker --autoscale 3,1 -E -B -s /tmp/celerybeat-schedule -Q authentik,authentik_scheduled,authentik_events"
|
||||
elif [[ "$1" == "backup" ]]; then
|
||||
python -m manage dbbackup --clean
|
||||
elif [[ "$1" == "restore" ]]; then
|
||||
|
@ -113,10 +113,21 @@ stages:
|
||||
inputs:
|
||||
containerRegistry: 'beryjuorg-harbor'
|
||||
repository: 'authentik/outpost-proxy'
|
||||
command: 'buildAndPush'
|
||||
command: 'build'
|
||||
Dockerfile: 'outpost/proxy.Dockerfile'
|
||||
buildContext: 'outpost/'
|
||||
tags: "gh-$(branchName)"
|
||||
tags: |
|
||||
gh-$(branchName)
|
||||
gh-$(Build.SourceVersion)
|
||||
arguments: '--build-arg GIT_BUILD_HASH=$(Build.SourceVersion)'
|
||||
- task: Docker@2
|
||||
inputs:
|
||||
containerRegistry: 'beryjuorg-harbor'
|
||||
repository: 'authentik/outpost-proxy'
|
||||
command: 'push'
|
||||
tags: |
|
||||
gh-$(branchName)
|
||||
gh-$(Build.SourceVersion)
|
||||
- job: ldap_build_docker
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
@ -138,7 +149,18 @@ stages:
|
||||
inputs:
|
||||
containerRegistry: 'beryjuorg-harbor'
|
||||
repository: 'authentik/outpost-ldap'
|
||||
command: 'buildAndPush'
|
||||
command: 'build'
|
||||
Dockerfile: 'outpost/ldap.Dockerfile'
|
||||
buildContext: 'outpost/'
|
||||
tags: "gh-$(branchName)"
|
||||
tags: |
|
||||
gh-$(branchName)
|
||||
gh-$(Build.SourceVersion)
|
||||
arguments: '--build-arg GIT_BUILD_HASH=$(Build.SourceVersion)'
|
||||
- task: Docker@2
|
||||
inputs:
|
||||
containerRegistry: 'beryjuorg-harbor'
|
||||
repository: 'authentik/outpost-ldap'
|
||||
command: 'push'
|
||||
tags: |
|
||||
gh-$(branchName)
|
||||
gh-$(Build.SourceVersion)
|
||||
|
@ -1,4 +1,6 @@
|
||||
FROM golang:1.16.4 AS builder
|
||||
ARG GIT_BUILD_HASH
|
||||
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
|
||||
|
||||
WORKDIR /work
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
package ak
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"os"
|
||||
@ -43,7 +42,7 @@ type APIController struct {
|
||||
// NewAPIController initialise new API Controller instance from URL and API token
|
||||
func NewAPIController(akURL url.URL, token string) *APIController {
|
||||
transport := httptransport.New(akURL.Host, client.DefaultBasePath, []string{akURL.Scheme})
|
||||
transport.Transport = SetUserAgent(getTLSTransport(), fmt.Sprintf("authentik-proxy@%s", pkg.VERSION))
|
||||
transport.Transport = SetUserAgent(GetTLSTransport(), pkg.UserAgent())
|
||||
|
||||
// create the transport
|
||||
auth := httptransport.BearerToken(token)
|
||||
|
@ -23,7 +23,7 @@ func (ac *APIController) initWS(akURL url.URL, outpostUUID strfmt.UUID) {
|
||||
|
||||
header := http.Header{
|
||||
"Authorization": []string{authHeader},
|
||||
"User-Agent": []string{fmt.Sprintf("authentik-proxy@%s", pkg.VERSION)},
|
||||
"User-Agent": []string{pkg.UserAgent()},
|
||||
}
|
||||
|
||||
value, set := os.LookupEnv("AUTHENTIK_INSECURE")
|
||||
@ -46,8 +46,9 @@ func (ac *APIController) initWS(akURL url.URL, outpostUUID strfmt.UUID) {
|
||||
msg := websocketMessage{
|
||||
Instruction: WebsocketInstructionHello,
|
||||
Args: map[string]interface{}{
|
||||
"version": pkg.VERSION,
|
||||
"uuid": ac.instanceUUID.String(),
|
||||
"version": pkg.VERSION,
|
||||
"buildHash": pkg.BUILD(),
|
||||
"uuid": ac.instanceUUID.String(),
|
||||
},
|
||||
}
|
||||
err := ws.WriteJSON(msg)
|
||||
@ -76,7 +77,7 @@ func (ac *APIController) startWSHandler() {
|
||||
var wsMsg websocketMessage
|
||||
err := ac.wsConn.ReadJSON(&wsMsg)
|
||||
if err != nil {
|
||||
logger.Println("read:", err)
|
||||
logger.WithError(err).Warning("ws write error, reconnecting")
|
||||
ac.wsConn.CloseAndReconnect()
|
||||
continue
|
||||
}
|
||||
@ -100,14 +101,15 @@ func (ac *APIController) startWSHealth() {
|
||||
aliveMsg := websocketMessage{
|
||||
Instruction: WebsocketInstructionHello,
|
||||
Args: map[string]interface{}{
|
||||
"version": pkg.VERSION,
|
||||
"uuid": ac.instanceUUID.String(),
|
||||
"version": pkg.VERSION,
|
||||
"buildHash": pkg.BUILD(),
|
||||
"uuid": ac.instanceUUID.String(),
|
||||
},
|
||||
}
|
||||
err := ac.wsConn.WriteJSON(aliveMsg)
|
||||
ac.logger.WithField("loop", "ws-health").Trace("hello'd")
|
||||
if err != nil {
|
||||
ac.logger.WithField("loop", "ws-health").Println("write:", err)
|
||||
ac.logger.WithField("loop", "ws-health").WithError(err).Warning("ws write error, reconnecting")
|
||||
ac.wsConn.CloseAndReconnect()
|
||||
continue
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ func doGlobalSetup(config map[string]interface{}) {
|
||||
default:
|
||||
log.SetLevel(log.DebugLevel)
|
||||
}
|
||||
log.WithField("version", pkg.VERSION).Info("Starting authentik outpost")
|
||||
log.WithField("buildHash", pkg.BUILD()).WithField("version", pkg.VERSION).Info("Starting authentik outpost")
|
||||
|
||||
var dsn string
|
||||
if config[ConfigErrorReportingEnabled].(bool) {
|
||||
@ -52,7 +52,8 @@ func doGlobalSetup(config map[string]interface{}) {
|
||||
defer sentry.Flush(2 * time.Second)
|
||||
}
|
||||
|
||||
func getTLSTransport() http.RoundTripper {
|
||||
// GetTLSTransport Get a TLS transport instance, that skips verification if configured via environment variables.
|
||||
func GetTLSTransport() http.RoundTripper {
|
||||
value, set := os.LookupEnv("AUTHENTIK_INSECURE")
|
||||
if !set {
|
||||
value = "false"
|
||||
|
@ -55,14 +55,18 @@ func (ls *LDAPServer) Start() error {
|
||||
|
||||
type transport struct {
|
||||
headers map[string]string
|
||||
inner http.RoundTripper
|
||||
}
|
||||
|
||||
func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
for key, value := range t.headers {
|
||||
req.Header.Add(key, value)
|
||||
}
|
||||
return http.DefaultTransport.RoundTrip(req)
|
||||
return t.inner.RoundTrip(req)
|
||||
}
|
||||
func newTransport(headers map[string]string) *transport {
|
||||
return &transport{headers}
|
||||
func newTransport(inner http.RoundTripper, headers map[string]string) *transport {
|
||||
return &transport{
|
||||
inner: inner,
|
||||
headers: headers,
|
||||
}
|
||||
}
|
||||
|
@ -2,20 +2,22 @@ package ldap
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/nmcclain/ldap"
|
||||
)
|
||||
|
||||
func (ls *LDAPServer) Bind(bindDN string, bindPW string, conn net.Conn) (ldap.LDAPResultCode, error) {
|
||||
ls.log.WithField("boundDN", bindDN).Info("bind")
|
||||
ls.log.WithField("bindDN", bindDN).Info("bind")
|
||||
bindDN = strings.ToLower(bindDN)
|
||||
for _, instance := range ls.providers {
|
||||
username, err := instance.getUsername(bindDN)
|
||||
if err == nil {
|
||||
return instance.Bind(username, bindPW, conn)
|
||||
return instance.Bind(username, bindDN, bindPW, conn)
|
||||
} else {
|
||||
ls.log.WithError(err).Debug("Username not for instance")
|
||||
}
|
||||
}
|
||||
ls.log.WithField("boundDN", bindDN).WithField("request", "bind").Warning("No provider found for request")
|
||||
ls.log.WithField("bindDN", bindDN).WithField("request", "bind").Warning("No provider found for request")
|
||||
return ldap.LDAPResultOperationsError, nil
|
||||
}
|
||||
|
@ -14,6 +14,8 @@ import (
|
||||
goldap "github.com/go-ldap/ldap/v3"
|
||||
httptransport "github.com/go-openapi/runtime/client"
|
||||
"github.com/nmcclain/ldap"
|
||||
"goauthentik.io/outpost/pkg"
|
||||
"goauthentik.io/outpost/pkg/ak"
|
||||
"goauthentik.io/outpost/pkg/client/core"
|
||||
"goauthentik.io/outpost/pkg/client/flows"
|
||||
"goauthentik.io/outpost/pkg/models"
|
||||
@ -47,7 +49,7 @@ func (pi *ProviderInstance) getUsername(dn string) (string, error) {
|
||||
return "", errors.New("failed to find cn")
|
||||
}
|
||||
|
||||
func (pi *ProviderInstance) Bind(username string, bindPW string, conn net.Conn) (ldap.LDAPResultCode, error) {
|
||||
func (pi *ProviderInstance) Bind(username string, bindDN, bindPW string, conn net.Conn) (ldap.LDAPResultCode, error) {
|
||||
jar, err := cookiejar.New(nil)
|
||||
if err != nil {
|
||||
pi.log.WithError(err).Warning("Failed to create cookiejar")
|
||||
@ -61,15 +63,15 @@ func (pi *ProviderInstance) Bind(username string, bindPW string, conn net.Conn)
|
||||
// Create new http client that also sets the correct ip
|
||||
client := &http.Client{
|
||||
Jar: jar,
|
||||
Transport: newTransport(map[string]string{
|
||||
Transport: newTransport(ak.SetUserAgent(ak.GetTLSTransport(), pkg.UserAgent()), map[string]string{
|
||||
"X-authentik-remote-ip": host,
|
||||
}),
|
||||
}
|
||||
params := url.Values{}
|
||||
params.Add("goauthentik.io/outpost/ldap", "true")
|
||||
passed, err := pi.solveFlowChallenge(username, bindPW, client, params.Encode())
|
||||
passed, err := pi.solveFlowChallenge(username, bindPW, client, params.Encode(), 1)
|
||||
if err != nil {
|
||||
pi.log.WithField("boundDN", username).WithError(err).Warning("failed to solve challenge")
|
||||
pi.log.WithField("bindDN", bindDN).WithError(err).Warning("failed to solve challenge")
|
||||
return ldap.LDAPResultOperationsError, nil
|
||||
}
|
||||
if !passed {
|
||||
@ -82,25 +84,25 @@ func (pi *ProviderInstance) Bind(username string, bindPW string, conn net.Conn)
|
||||
}, httptransport.PassThroughAuth)
|
||||
if err != nil {
|
||||
if _, denied := err.(*core.CoreApplicationsCheckAccessForbidden); denied {
|
||||
pi.log.WithField("boundDN", username).Info("Access denied for user")
|
||||
pi.log.WithField("bindDN", bindDN).Info("Access denied for user")
|
||||
return ldap.LDAPResultInsufficientAccessRights, nil
|
||||
}
|
||||
pi.log.WithField("boundDN", username).WithError(err).Warning("failed to check access")
|
||||
pi.log.WithField("bindDN", bindDN).WithError(err).Warning("failed to check access")
|
||||
return ldap.LDAPResultOperationsError, nil
|
||||
}
|
||||
pi.log.WithField("boundDN", username).Info("User has access")
|
||||
pi.log.WithField("bindDN", bindDN).Info("User has access")
|
||||
// Get user info to store in context
|
||||
userInfo, err := pi.s.ac.Client.Core.CoreUsersMe(&core.CoreUsersMeParams{
|
||||
Context: context.Background(),
|
||||
HTTPClient: client,
|
||||
}, httptransport.PassThroughAuth)
|
||||
if err != nil {
|
||||
pi.log.WithField("boundDN", username).WithError(err).Warning("failed to get user info")
|
||||
pi.log.WithField("bindDN", bindDN).WithError(err).Warning("failed to get user info")
|
||||
return ldap.LDAPResultOperationsError, nil
|
||||
}
|
||||
pi.boundUsersMutex.Lock()
|
||||
pi.boundUsers[username] = UserFlags{
|
||||
UserInfo: userInfo.Payload.User,
|
||||
pi.boundUsers[bindDN] = UserFlags{
|
||||
UserInfo: *userInfo.Payload.User,
|
||||
CanSearch: pi.SearchAccessCheck(userInfo.Payload.User),
|
||||
}
|
||||
defer pi.boundUsersMutex.Unlock()
|
||||
@ -112,7 +114,8 @@ func (pi *ProviderInstance) Bind(username string, bindPW string, conn net.Conn)
|
||||
func (pi *ProviderInstance) SearchAccessCheck(user *models.User) bool {
|
||||
for _, group := range user.Groups {
|
||||
for _, allowedGroup := range pi.searchAllowedGroups {
|
||||
if &group.Pk == allowedGroup {
|
||||
pi.log.WithField("userGroup", group.Pk).WithField("allowedGroup", allowedGroup).Trace("Checking search access")
|
||||
if group.Pk.String() == allowedGroup.String() {
|
||||
pi.log.WithField("group", group.Name).Info("Allowed access to search")
|
||||
return true
|
||||
}
|
||||
@ -139,7 +142,7 @@ func (pi *ProviderInstance) delayDeleteUserInfo(dn string) {
|
||||
}()
|
||||
}
|
||||
|
||||
func (pi *ProviderInstance) solveFlowChallenge(bindDN string, password string, client *http.Client, urlParams string) (bool, error) {
|
||||
func (pi *ProviderInstance) solveFlowChallenge(bindDN string, password string, client *http.Client, urlParams string, depth int) (bool, error) {
|
||||
challenge, err := pi.s.ac.Client.Flows.FlowsExecutorGet(&flows.FlowsExecutorGetParams{
|
||||
FlowSlug: pi.flowSlug,
|
||||
Query: urlParams,
|
||||
@ -169,6 +172,10 @@ func (pi *ProviderInstance) solveFlowChallenge(bindDN string, password string, c
|
||||
}
|
||||
response, err := pi.s.ac.Client.Flows.FlowsExecutorSolve(responseParams, pi.s.ac.Auth)
|
||||
pi.log.WithField("component", response.Payload.Component).WithField("type", *response.Payload.Type).Debug("Got response")
|
||||
switch response.Payload.Component {
|
||||
case "ak-stage-access-denied":
|
||||
return false, errors.New("got ak-stage-access-denied")
|
||||
}
|
||||
if *response.Payload.Type == "redirect" {
|
||||
return true, nil
|
||||
}
|
||||
@ -184,5 +191,8 @@ func (pi *ProviderInstance) solveFlowChallenge(bindDN string, password string, c
|
||||
}
|
||||
}
|
||||
}
|
||||
return pi.solveFlowChallenge(bindDN, password, client, urlParams)
|
||||
if depth >= 10 {
|
||||
return false, errors.New("exceeded stage recursion depth")
|
||||
}
|
||||
return pi.solveFlowChallenge(bindDN, password, client, urlParams, depth+1)
|
||||
}
|
||||
|
@ -29,10 +29,13 @@ func (pi *ProviderInstance) Search(bindDN string, searchReq ldap.SearchRequest,
|
||||
pi.boundUsersMutex.RLock()
|
||||
defer pi.boundUsersMutex.RUnlock()
|
||||
flags, ok := pi.boundUsers[bindDN]
|
||||
pi.log.WithField("bindDN", bindDN).WithField("ok", ok).Debugf("%+v\n", flags)
|
||||
if !ok {
|
||||
pi.log.Debug("User info not cached")
|
||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, errors.New("access denied")
|
||||
}
|
||||
if !flags.CanSearch {
|
||||
pi.log.Debug("User can't search")
|
||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, errors.New("access denied")
|
||||
}
|
||||
|
||||
@ -114,7 +117,7 @@ func (pi *ProviderInstance) Search(bindDN string, searchReq ldap.SearchRequest,
|
||||
|
||||
attrs = append(attrs, AKAttrsToLDAP(u.Attributes)...)
|
||||
|
||||
dn := fmt.Sprintf("cn=%s,%s", *u.Name, pi.UserDN)
|
||||
dn := fmt.Sprintf("cn=%s,%s", *u.Username, pi.UserDN)
|
||||
entries = append(entries, &ldap.Entry{DN: dn, Attributes: attrs})
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ type ProviderInstance struct {
|
||||
}
|
||||
|
||||
type UserFlags struct {
|
||||
UserInfo *models.User
|
||||
UserInfo models.User
|
||||
CanSearch bool
|
||||
}
|
||||
|
||||
|
@ -8,8 +8,8 @@ import (
|
||||
"github.com/nmcclain/ldap"
|
||||
)
|
||||
|
||||
func (ls *LDAPServer) Search(boundDN string, searchReq ldap.SearchRequest, conn net.Conn) (ldap.ServerSearchResult, error) {
|
||||
ls.log.WithField("boundDN", boundDN).WithField("baseDN", searchReq.BaseDN).Info("search")
|
||||
func (ls *LDAPServer) Search(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (ldap.ServerSearchResult, error) {
|
||||
ls.log.WithField("bindDN", bindDN).WithField("baseDN", searchReq.BaseDN).Info("search")
|
||||
if searchReq.BaseDN == "" {
|
||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultSuccess}, nil
|
||||
}
|
||||
@ -21,7 +21,7 @@ func (ls *LDAPServer) Search(boundDN string, searchReq ldap.SearchRequest, conn
|
||||
for _, provider := range ls.providers {
|
||||
providerBase, _ := goldap.ParseDN(provider.BaseDN)
|
||||
if providerBase.AncestorOf(bd) {
|
||||
return provider.Search(boundDN, searchReq, conn)
|
||||
return provider.Search(bindDN, searchReq, conn)
|
||||
}
|
||||
}
|
||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, errors.New("no provider could handle request")
|
||||
|
@ -80,19 +80,19 @@ func (pb *providerBundle) prepareOpts(provider *models.ProxyOutpostConfig) *opti
|
||||
ID: "default",
|
||||
URI: provider.InternalHost,
|
||||
Path: "/",
|
||||
InsecureSkipTLSVerify: provider.InternalHostSslValidation,
|
||||
InsecureSkipTLSVerify: !provider.InternalHostSslValidation,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if provider.Certificate != nil {
|
||||
pb.log.WithField("provider", provider.ClientID).Debug("Enabling TLS")
|
||||
pb.log.WithField("provider", provider.Name).Debug("Enabling TLS")
|
||||
cert, err := pb.s.ak.Client.Crypto.CryptoCertificatekeypairsViewCertificate(&crypto.CryptoCertificatekeypairsViewCertificateParams{
|
||||
Context: context.Background(),
|
||||
KpUUID: *provider.Certificate,
|
||||
}, pb.s.ak.Auth)
|
||||
if err != nil {
|
||||
pb.log.WithField("provider", provider.ClientID).WithError(err).Warning("Failed to fetch certificate")
|
||||
pb.log.WithField("provider", provider.Name).WithError(err).Warning("Failed to fetch certificate")
|
||||
return providerOpts
|
||||
}
|
||||
key, err := pb.s.ak.Client.Crypto.CryptoCertificatekeypairsViewPrivateKey(&crypto.CryptoCertificatekeypairsViewPrivateKeyParams{
|
||||
@ -100,17 +100,17 @@ func (pb *providerBundle) prepareOpts(provider *models.ProxyOutpostConfig) *opti
|
||||
KpUUID: *provider.Certificate,
|
||||
}, pb.s.ak.Auth)
|
||||
if err != nil {
|
||||
pb.log.WithField("provider", provider.ClientID).WithError(err).Warning("Failed to fetch private key")
|
||||
pb.log.WithField("provider", provider.Name).WithError(err).Warning("Failed to fetch private key")
|
||||
return providerOpts
|
||||
}
|
||||
|
||||
x509cert, err := tls.X509KeyPair([]byte(cert.Payload.Data), []byte(key.Payload.Data))
|
||||
if err != nil {
|
||||
pb.log.WithField("provider", provider.ClientID).WithError(err).Warning("Failed to parse certificate")
|
||||
pb.log.WithField("provider", provider.Name).WithError(err).Warning("Failed to parse certificate")
|
||||
return providerOpts
|
||||
}
|
||||
pb.cert = &x509cert
|
||||
pb.log.WithField("provider", provider.ClientID).Debug("Loaded certificates")
|
||||
pb.log.WithField("provider", provider.Name).Debug("Loaded certificates")
|
||||
}
|
||||
return providerOpts
|
||||
}
|
||||
|
@ -161,7 +161,7 @@ func (p *OAuthProxy) OAuthStart(rw http.ResponseWriter, req *http.Request) {
|
||||
p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error())
|
||||
return
|
||||
}
|
||||
redirectURI := p.GetRedirectURI(getHost(req))
|
||||
redirectURI := p.GetRedirectURI(req.Host)
|
||||
http.Redirect(rw, req, p.provider.GetLoginURL(redirectURI, fmt.Sprintf("%v:%v", nonce, redirect)), http.StatusFound)
|
||||
}
|
||||
|
||||
@ -184,7 +184,7 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
session, err := p.redeemCode(req.Context(), getHost(req), req.Form.Get("code"))
|
||||
session, err := p.redeemCode(req.Context(), req.Host, req.Form.Get("code"))
|
||||
if err != nil {
|
||||
p.logger.Errorf("Error redeeming code during OAuth2 callback: %v", err)
|
||||
p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", "Internal Error")
|
||||
@ -207,7 +207,7 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
p.ClearCSRFCookie(rw, req)
|
||||
if c.Value != nonce {
|
||||
p.logger.WithField("user", session.Email).WithField("status", "AuthFailure").Info("Invalid authentication via OAuth2: CSRF token mismatch, potential attack")
|
||||
p.logger.WithField("is", c.Value).WithField("should", nonce).WithField("user", session.Email).WithField("status", "AuthFailure").Info("Invalid authentication via OAuth2: CSRF token mismatch, potential attack")
|
||||
p.ErrorPage(rw, http.StatusForbidden, "Permission Denied", "CSRF Failed")
|
||||
return
|
||||
}
|
||||
|
@ -13,12 +13,14 @@ func getTemplates() *template.Template {
|
||||
<head>
|
||||
<title>{{.Title}}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<style>* { font-family: sans-serif; }</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>{{.Title}}</h2>
|
||||
<p>{{.Message}}</p>
|
||||
<hr>
|
||||
<p><a href="{{.ProxyPrefix}}/sign_in">Sign In</a></p>
|
||||
<p>Powered by <a href="https://goauthentik.io">authentik</a></p>
|
||||
</body>
|
||||
</html>{{end}}`)
|
||||
if err != nil {
|
||||
|
@ -1,3 +1,16 @@
|
||||
package pkg
|
||||
|
||||
const VERSION = "2021.5.1-rc8"
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
const VERSION = "2021.5.4"
|
||||
|
||||
func BUILD() string {
|
||||
return os.Getenv("GIT_BUILD_HASH")
|
||||
}
|
||||
|
||||
func UserAgent() string {
|
||||
return fmt.Sprintf("authentik-outpost@%s (%s)", VERSION, BUILD())
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
FROM golang:1.16.4 AS builder
|
||||
ARG GIT_BUILD_HASH
|
||||
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
|
||||
|
||||
WORKDIR /work
|
||||
|
||||
|
@ -1,16 +0,0 @@
|
||||
#!/bin/bash -xe
|
||||
wget -q -O - https://raw.githubusercontent.com/rancher/k3d/main/install.sh | bash
|
||||
|
||||
VERSION=3.9.0
|
||||
|
||||
wget https://www.python.org/ftp/python/$VERSION/Python-$VERSION.tgz
|
||||
tar xvzf Python-$VERSION.tgz
|
||||
cd Python-$VERSION/
|
||||
|
||||
./configure --prefix=$HOME/_work/_tool/Python/$VERSION/x64/ --enable-optimizations --with-ensurepip=install
|
||||
make -j 8
|
||||
sudo make altinstall
|
||||
touch $HOME/_work/_tool/Python/$VERSION/x64.complete
|
||||
|
||||
ln -s $HOME/_work/_tool/Python/3.9.5/x64 $HOME/_work/_tool/Python/3/x64
|
||||
ln -s $HOME/_work/_tool/Python/3.9.5/x64 $HOME/_work/_tool/Python/3.9/x64
|
8
scripts/generate_ci_config.py
Normal file
8
scripts/generate_ci_config.py
Normal file
@ -0,0 +1,8 @@
|
||||
"""Utility script to generate a config for CI runs"""
|
||||
from authentik.providers.oauth2.generators import generate_client_id
|
||||
from yaml import safe_dump
|
||||
|
||||
with open("local.env.yml", "w") as _config:
|
||||
safe_dump({
|
||||
"secret_key": generate_client_id()
|
||||
}, _config, default_flow_style=False)
|
201
swagger.yaml
201
swagger.yaml
@ -531,6 +531,11 @@ paths:
|
||||
description: ''
|
||||
required: false
|
||||
type: string
|
||||
- name: ordering
|
||||
in: query
|
||||
description: Which field to use when ordering the results.
|
||||
required: false
|
||||
type: string
|
||||
- name: search
|
||||
in: query
|
||||
description: A search term.
|
||||
@ -590,30 +595,6 @@ paths:
|
||||
$ref: '#/definitions/GenericError'
|
||||
tags:
|
||||
- authenticators
|
||||
post:
|
||||
operationId: authenticators_static_create
|
||||
description: Viewset for static authenticator devices
|
||||
parameters:
|
||||
- name: data
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/StaticDevice'
|
||||
responses:
|
||||
'201':
|
||||
description: ''
|
||||
schema:
|
||||
$ref: '#/definitions/StaticDevice'
|
||||
'400':
|
||||
description: Invalid input.
|
||||
schema:
|
||||
$ref: '#/definitions/ValidationError'
|
||||
'403':
|
||||
description: Authentication credentials were invalid, absent or insufficient.
|
||||
schema:
|
||||
$ref: '#/definitions/GenericError'
|
||||
tags:
|
||||
- authenticators
|
||||
parameters: []
|
||||
/authenticators/static/{id}/:
|
||||
get:
|
||||
@ -792,30 +773,6 @@ paths:
|
||||
$ref: '#/definitions/GenericError'
|
||||
tags:
|
||||
- authenticators
|
||||
post:
|
||||
operationId: authenticators_totp_create
|
||||
description: Viewset for totp authenticator devices
|
||||
parameters:
|
||||
- name: data
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/TOTPDevice'
|
||||
responses:
|
||||
'201':
|
||||
description: ''
|
||||
schema:
|
||||
$ref: '#/definitions/TOTPDevice'
|
||||
'400':
|
||||
description: Invalid input.
|
||||
schema:
|
||||
$ref: '#/definitions/ValidationError'
|
||||
'403':
|
||||
description: Authentication credentials were invalid, absent or insufficient.
|
||||
schema:
|
||||
$ref: '#/definitions/GenericError'
|
||||
tags:
|
||||
- authenticators
|
||||
parameters: []
|
||||
/authenticators/totp/{id}/:
|
||||
get:
|
||||
@ -994,30 +951,6 @@ paths:
|
||||
$ref: '#/definitions/GenericError'
|
||||
tags:
|
||||
- authenticators
|
||||
post:
|
||||
operationId: authenticators_webauthn_create
|
||||
description: Viewset for WebAuthn authenticator devices
|
||||
parameters:
|
||||
- name: data
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/WebAuthnDevice'
|
||||
responses:
|
||||
'201':
|
||||
description: ''
|
||||
schema:
|
||||
$ref: '#/definitions/WebAuthnDevice'
|
||||
'400':
|
||||
description: Invalid input.
|
||||
schema:
|
||||
$ref: '#/definitions/ValidationError'
|
||||
'403':
|
||||
description: Authentication credentials were invalid, absent or insufficient.
|
||||
schema:
|
||||
$ref: '#/definitions/GenericError'
|
||||
tags:
|
||||
- authenticators
|
||||
parameters: []
|
||||
/authenticators/webauthn/{id}/:
|
||||
get:
|
||||
@ -10420,30 +10353,6 @@ paths:
|
||||
$ref: '#/definitions/GenericError'
|
||||
tags:
|
||||
- sources
|
||||
post:
|
||||
operationId: sources_oauth_user_connections_create
|
||||
description: Source Viewset
|
||||
parameters:
|
||||
- name: data
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/UserOAuthSourceConnection'
|
||||
responses:
|
||||
'201':
|
||||
description: ''
|
||||
schema:
|
||||
$ref: '#/definitions/UserOAuthSourceConnection'
|
||||
'400':
|
||||
description: Invalid input.
|
||||
schema:
|
||||
$ref: '#/definitions/ValidationError'
|
||||
'403':
|
||||
description: Authentication credentials were invalid, absent or insufficient.
|
||||
schema:
|
||||
$ref: '#/definitions/GenericError'
|
||||
tags:
|
||||
- sources
|
||||
parameters: []
|
||||
/sources/oauth_user_connections/{id}/:
|
||||
get:
|
||||
@ -15694,6 +15603,7 @@ definitions:
|
||||
NotificationRule:
|
||||
required:
|
||||
- name
|
||||
- transports
|
||||
type: object
|
||||
properties:
|
||||
pk:
|
||||
@ -15706,38 +15616,17 @@ definitions:
|
||||
type: string
|
||||
minLength: 1
|
||||
transports:
|
||||
description: Select which transports should be used to notify the user. If
|
||||
none are selected, the notification will only be shown in the authentik
|
||||
UI.
|
||||
type: array
|
||||
items:
|
||||
required:
|
||||
- name
|
||||
- mode
|
||||
type: object
|
||||
properties:
|
||||
uuid:
|
||||
title: Uuid
|
||||
type: string
|
||||
format: uuid
|
||||
readOnly: true
|
||||
name:
|
||||
title: Name
|
||||
type: string
|
||||
minLength: 1
|
||||
mode:
|
||||
title: Mode
|
||||
type: string
|
||||
enum:
|
||||
- webhook
|
||||
- webhook_slack
|
||||
- email
|
||||
webhook_url:
|
||||
title: Webhook url
|
||||
type: string
|
||||
send_once:
|
||||
title: Send once
|
||||
description: Only send notification once, for example when sending a
|
||||
webhook into a chat channel.
|
||||
type: boolean
|
||||
readOnly: true
|
||||
description: Select which transports should be used to notify the user.
|
||||
If none are selected, the notification will only be shown in the authentik
|
||||
UI.
|
||||
type: string
|
||||
format: uuid
|
||||
uniqueItems: true
|
||||
severity:
|
||||
title: Severity
|
||||
description: Controls which severity level the created notifications will
|
||||
@ -15748,57 +15637,14 @@ definitions:
|
||||
- warning
|
||||
- alert
|
||||
group:
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
properties:
|
||||
group_uuid:
|
||||
title: Group uuid
|
||||
type: string
|
||||
format: uuid
|
||||
readOnly: true
|
||||
name:
|
||||
title: Name
|
||||
type: string
|
||||
maxLength: 80
|
||||
minLength: 1
|
||||
is_superuser:
|
||||
title: Is superuser
|
||||
description: Users added to this group will be superusers.
|
||||
type: boolean
|
||||
attributes:
|
||||
title: Attributes
|
||||
type: object
|
||||
parent:
|
||||
required:
|
||||
- name
|
||||
- parent
|
||||
type: object
|
||||
properties:
|
||||
group_uuid:
|
||||
title: Group uuid
|
||||
type: string
|
||||
format: uuid
|
||||
readOnly: true
|
||||
name:
|
||||
title: Name
|
||||
type: string
|
||||
maxLength: 80
|
||||
minLength: 1
|
||||
is_superuser:
|
||||
title: Is superuser
|
||||
description: Users added to this group will be superusers.
|
||||
type: boolean
|
||||
attributes:
|
||||
title: Attributes
|
||||
type: object
|
||||
parent:
|
||||
title: Parent
|
||||
type: string
|
||||
format: uuid
|
||||
x-nullable: true
|
||||
readOnly: true
|
||||
readOnly: true
|
||||
title: Group
|
||||
description: Define which group of users this notification should be sent
|
||||
and shown to. If left empty, Notification won't ben sent.
|
||||
type: string
|
||||
format: uuid
|
||||
x-nullable: true
|
||||
group_obj:
|
||||
$ref: '#/definitions/Group'
|
||||
NotificationTransport:
|
||||
required:
|
||||
- name
|
||||
@ -18103,6 +17949,7 @@ definitions:
|
||||
required:
|
||||
- name
|
||||
- slug
|
||||
- plex_token
|
||||
type: object
|
||||
properties:
|
||||
pk:
|
||||
|
@ -46,7 +46,7 @@ class TestProviderProxy(SeleniumTestCase):
|
||||
"""Start proxy container based on outpost created"""
|
||||
client: DockerClient = from_env()
|
||||
container = client.containers.run(
|
||||
image=f"beryju/authentik-proxy:{__version__}",
|
||||
image=f"ghcr.io/goauthentik/proxy:{__version__}",
|
||||
detach=True,
|
||||
network_mode="host",
|
||||
auto_remove=True,
|
||||
@ -70,7 +70,7 @@ class TestProviderProxy(SeleniumTestCase):
|
||||
authorization_flow=Flow.objects.get(
|
||||
slug="default-provider-authorization-implicit-consent"
|
||||
),
|
||||
internal_host="http://localhost:80",
|
||||
internal_host="http://localhost",
|
||||
external_host="http://localhost:4180",
|
||||
)
|
||||
# Ensure OAuth2 Params are set
|
||||
@ -123,7 +123,7 @@ class TestProviderProxyConnect(ChannelsLiveServerTestCase):
|
||||
authorization_flow=Flow.objects.get(
|
||||
slug="default-provider-authorization-implicit-consent"
|
||||
),
|
||||
internal_host="http://localhost:80",
|
||||
internal_host="http://localhost",
|
||||
external_host="http://localhost:4180",
|
||||
)
|
||||
# Ensure OAuth2 Params are set
|
||||
|
@ -104,5 +104,5 @@ class OutpostDockerTests(TestCase):
|
||||
self.assertEqual(compose["version"], "3.5")
|
||||
self.assertEqual(
|
||||
compose["services"]["authentik_proxy"]["image"],
|
||||
f"beryju/authentik-proxy:{__version__}",
|
||||
f"ghcr.io/goauthentik/proxy:{__version__}",
|
||||
)
|
||||
|
@ -104,5 +104,5 @@ class TestProxyDocker(TestCase):
|
||||
self.assertEqual(compose["version"], "3.5")
|
||||
self.assertEqual(
|
||||
compose["services"]["authentik_proxy"]["image"],
|
||||
f"beryju/authentik-proxy:{__version__}",
|
||||
f"ghcr.io/goauthentik/proxy:{__version__}",
|
||||
)
|
||||
|
@ -1,98 +0,0 @@
|
||||
worker_processes auto;
|
||||
pid /tmp/nginx.pid;
|
||||
include /etc/nginx/modules-enabled/*.conf;
|
||||
error_log /dev/stdout;
|
||||
user www-data;
|
||||
|
||||
events {
|
||||
worker_connections 768;
|
||||
# multi_accept on;
|
||||
}
|
||||
|
||||
http {
|
||||
|
||||
##
|
||||
# Basic Settings
|
||||
##
|
||||
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
keepalive_timeout 65;
|
||||
types_hash_max_size 2048;
|
||||
# server_tokens off;
|
||||
|
||||
# server_names_hash_bucket_size 64;
|
||||
# server_name_in_redirect off;
|
||||
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
##
|
||||
# SSL Settings
|
||||
##
|
||||
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
|
||||
ssl_prefer_server_ciphers on;
|
||||
|
||||
##
|
||||
# Logging Settings
|
||||
##
|
||||
log_format json_combined escape=json
|
||||
'{'
|
||||
'"timestamp":"$time_local",'
|
||||
'"host":"$remote_addr",'
|
||||
'"request_username":"$remote_user",'
|
||||
'"event":"$request",'
|
||||
'"status": "$status",'
|
||||
'"size":"$body_bytes_sent",'
|
||||
'"runtime":"$request_time",'
|
||||
'"logger":"nginx",'
|
||||
'"request_useragent":"$http_user_agent"'
|
||||
'}';
|
||||
access_log /dev/null json_combined;
|
||||
|
||||
##
|
||||
# Gzip Settings
|
||||
##
|
||||
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_buffers 16 8k;
|
||||
gzip_http_version 1.1;
|
||||
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
|
||||
|
||||
##
|
||||
# Virtual Host Configs
|
||||
##
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
charset utf-8;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
access_log /dev/stdout json_combined;
|
||||
}
|
||||
location /static/ {
|
||||
expires 31d;
|
||||
add_header Cache-Control "public, no-transform";
|
||||
add_header X-authentik-version "2021.5.1-rc8";
|
||||
add_header Vary X-authentik-version;
|
||||
}
|
||||
|
||||
location /if/admin {
|
||||
root /usr/share/nginx/html/static/dist;
|
||||
try_files $uri /static/dist/if/admin/index.html;
|
||||
}
|
||||
location /if/flow {
|
||||
root /usr/share/nginx/html/static/dist;
|
||||
try_files $uri /static/dist/if/flow/index.html;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
1407
web/package-lock.json
generated
1407
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -35,10 +35,10 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.14.0",
|
||||
"@babel/plugin-proposal-decorators": "^7.13.15",
|
||||
"@babel/plugin-transform-runtime": "^7.13.15",
|
||||
"@babel/preset-env": "^7.14.1",
|
||||
"@babel/core": "^7.14.2",
|
||||
"@babel/plugin-proposal-decorators": "^7.14.2",
|
||||
"@babel/plugin-transform-runtime": "^7.14.2",
|
||||
"@babel/preset-env": "^7.14.2",
|
||||
"@babel/preset-typescript": "^7.13.0",
|
||||
"@fortawesome/fontawesome-free": "^5.15.3",
|
||||
"@lingui/cli": "^3.8.10",
|
||||
@ -68,13 +68,13 @@
|
||||
"eslint": "^7.26.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"eslint-plugin-custom-elements": "0.0.2",
|
||||
"eslint-plugin-lit": "^1.4.0",
|
||||
"eslint-plugin-lit": "^1.4.1",
|
||||
"flowchart.js": "^1.15.0",
|
||||
"lit-element": "^2.5.1",
|
||||
"lit-html": "^1.4.1",
|
||||
"moment": "^2.29.1",
|
||||
"rapidoc": "^9.0.0",
|
||||
"rollup": "^2.47.0",
|
||||
"rollup": "^2.48.0",
|
||||
"rollup-plugin-commonjs": "^10.1.0",
|
||||
"rollup-plugin-copy": "^3.4.0",
|
||||
"rollup-plugin-cssimport": "^1.0.2",
|
||||
|
@ -104,6 +104,7 @@ export default [
|
||||
dir: "dist",
|
||||
sourcemap: true,
|
||||
manualChunks: manualChunks,
|
||||
chunkFileNames: "admin-[name].js"
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
@ -135,6 +136,7 @@ export default [
|
||||
dir: "dist",
|
||||
sourcemap: true,
|
||||
manualChunks: manualChunks,
|
||||
chunkFileNames: "flow-[name].js"
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
|
@ -6,7 +6,9 @@ import { me } from "./Users";
|
||||
import { config } from "./Config";
|
||||
import { Config } from "authentik-api";
|
||||
|
||||
export function configureSentry(canDoPpi: boolean = false): Promise<Config> {
|
||||
export const TAG_SENTRY_COMPONENT = "authentik:component";
|
||||
|
||||
export function configureSentry(canDoPpi: boolean = false, tags: { [key: string]: string; } = {}): Promise<Config> {
|
||||
return config().then((config) => {
|
||||
if (config.errorReportingEnabled) {
|
||||
Sentry.init({
|
||||
@ -51,6 +53,12 @@ export function configureSentry(canDoPpi: boolean = false): Promise<Config> {
|
||||
return event;
|
||||
},
|
||||
});
|
||||
Sentry.setTags(tags);
|
||||
if (window.location.pathname.includes("if/")) {
|
||||
// Get the interface name from URL
|
||||
const intf = window.location.pathname.replace(/.+if\/(.+)\//, "$1");
|
||||
Sentry.setTag(TAG_SENTRY_COMPONENT, `web/${intf}`);
|
||||
}
|
||||
console.debug("authentik/config: Sentry enabled.");
|
||||
if (config.errorReportingSendPii && canDoPpi) {
|
||||
me().then(user => {
|
||||
|
@ -3,7 +3,7 @@ export const SUCCESS_CLASS = "pf-m-success";
|
||||
export const ERROR_CLASS = "pf-m-danger";
|
||||
export const PROGRESS_CLASS = "pf-m-in-progress";
|
||||
export const CURRENT_CLASS = "pf-m-current";
|
||||
export const VERSION = "2021.5.1-rc8";
|
||||
export const VERSION = "2021.5.4";
|
||||
export const PAGE_SIZE = 20;
|
||||
export const EVENT_REFRESH = "ak-refresh";
|
||||
export const EVENT_NOTIFICATION_TOGGLE = "ak-notification-toggle";
|
||||
|
@ -55,7 +55,7 @@ export class ModalButton extends LitElement {
|
||||
|
||||
resetForms(): void {
|
||||
this.querySelectorAll<HTMLFormElement>("[slot=form]").forEach(form => {
|
||||
form.reset();
|
||||
form?.reset();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -29,8 +29,8 @@ export class TokenCopyButton extends ActionButton {
|
||||
this.buttonClass = PRIMARY_CLASS;
|
||||
}, 1500);
|
||||
});
|
||||
}).catch((err: Response) => {
|
||||
return err.json().then(errResp => {
|
||||
}).catch((err: Response | undefined) => {
|
||||
return err?.json().then(errResp => {
|
||||
throw new Error(errResp["detail"]);
|
||||
});
|
||||
});
|
||||
|
@ -76,10 +76,7 @@ export class Form<T> extends LitElement {
|
||||
*/
|
||||
reset(): void {
|
||||
const ironForm = this.shadowRoot?.querySelector("iron-form");
|
||||
if (!ironForm) {
|
||||
return;
|
||||
}
|
||||
ironForm.reset();
|
||||
ironForm?.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -23,7 +23,7 @@ export class ModalForm extends ModalButton {
|
||||
return formPromise.then(() => {
|
||||
if (this.closeAfterSuccessfulSubmit) {
|
||||
this.open = false;
|
||||
form.reset();
|
||||
form?.reset();
|
||||
}
|
||||
this.dispatchEvent(
|
||||
new CustomEvent(EVENT_REFRESH, {
|
||||
|
@ -34,7 +34,7 @@ export abstract class TableModal<T> extends Table<T> {
|
||||
|
||||
resetForms(): void {
|
||||
this.querySelectorAll<HTMLFormElement>("[slot=form]").forEach(form => {
|
||||
form.reset();
|
||||
form?.reset();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -10,9 +10,8 @@
|
||||
<link rel="stylesheet" type="text/css" href="/static/dist/empty-state.css">
|
||||
<link rel="stylesheet" type="text/css" href="/static/dist/spinner.css">
|
||||
<link rel="stylesheet" type="text/css" href="/static/dist/authentik.css">
|
||||
<script>ShadyDOM = { force: !navigator.webdriver };</script>
|
||||
<script>ShadyDOM = { force: !navigator.webdriver }; window["polymerSkipLoadingFontRoboto"] = true;</script>
|
||||
<script src="/static/dist/poly.js" type="module"></script>
|
||||
<script>window["polymerSkipLoadingFontRoboto"] = true;</script>
|
||||
<script src="/static/dist/FlowInterface.js" type="module"></script>
|
||||
<title>authentik</title>
|
||||
</head>
|
||||
|
@ -144,7 +144,7 @@ msgstr "Algorithm used to sign the JWT Tokens."
|
||||
msgid "Allow IDP-initiated logins"
|
||||
msgstr "Allow IDP-initiated logins"
|
||||
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:150
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:103
|
||||
msgid "Allow friends to authenticate via Plex, even if you don't share any servers"
|
||||
msgstr "Allow friends to authenticate via Plex, even if you don't share any servers"
|
||||
|
||||
@ -152,7 +152,7 @@ msgstr "Allow friends to authenticate via Plex, even if you don't share any serv
|
||||
msgid "Allow up to N occurrences in the HIBP database."
|
||||
msgstr "Allow up to N occurrences in the HIBP database."
|
||||
|
||||
#: src/pages/policies/PolicyListPage.ts:41
|
||||
#: src/pages/policies/PolicyListPage.ts:42
|
||||
msgid "Allow users to use Applications based on properties, enforce Password Criteria and selectively apply Stages."
|
||||
msgstr "Allow users to use Applications based on properties, enforce Password Criteria and selectively apply Stages."
|
||||
|
||||
@ -164,7 +164,7 @@ msgstr "Allowed Redirect URIs"
|
||||
msgid "Allowed count"
|
||||
msgstr "Allowed count"
|
||||
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:155
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:108
|
||||
msgid "Allowed servers"
|
||||
msgstr "Allowed servers"
|
||||
|
||||
@ -218,6 +218,22 @@ msgstr "Applications"
|
||||
msgid "Apps with most usage"
|
||||
msgstr "Apps with most usage"
|
||||
|
||||
#: src/pages/flows/FlowListPage.ts:146
|
||||
msgid ""
|
||||
"Are you sure you want to clear the flow cache?\n"
|
||||
"This will cause all flows to be re-evaluated on their next usage."
|
||||
msgstr ""
|
||||
"Are you sure you want to clear the flow cache?\n"
|
||||
"This will cause all flows to be re-evaluated on their next usage."
|
||||
|
||||
#: src/pages/policies/PolicyListPage.ts:165
|
||||
msgid ""
|
||||
"Are you sure you want to clear the policy cache?\n"
|
||||
"This will cause all policies to be re-evaluated on their next usage."
|
||||
msgstr ""
|
||||
"Are you sure you want to clear the policy cache?\n"
|
||||
"This will cause all policies to be re-evaluated on their next usage."
|
||||
|
||||
#: src/elements/forms/DeleteForm.ts:69
|
||||
msgid "Are you sure you want to delete {0} {objName} ?"
|
||||
msgstr "Are you sure you want to delete {0} {objName} ?"
|
||||
@ -250,7 +266,7 @@ msgstr "Assertions is empty"
|
||||
msgid "Assigned to application"
|
||||
msgstr "Assigned to application"
|
||||
|
||||
#: src/pages/policies/PolicyListPage.ts:68
|
||||
#: src/pages/policies/PolicyListPage.ts:69
|
||||
msgid "Assigned to {0} objects."
|
||||
msgstr "Assigned to {0} objects."
|
||||
|
||||
@ -282,7 +298,7 @@ msgid "Authentication"
|
||||
msgstr "Authentication"
|
||||
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:231
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:185
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:190
|
||||
#: src/pages/sources/saml/SAMLSourceForm.ts:243
|
||||
msgid "Authentication flow"
|
||||
msgstr "Authentication flow"
|
||||
@ -530,6 +546,21 @@ msgstr "Checks if the request's user's password has been changed in the last x d
|
||||
msgid "Checks the value from the policy request against several rules, mostly used to ensure password strength."
|
||||
msgstr "Checks the value from the policy request against several rules, mostly used to ensure password strength."
|
||||
|
||||
#: src/pages/flows/FlowListPage.ts:143
|
||||
msgid "Clear Flow cache"
|
||||
msgstr "Clear Flow cache"
|
||||
|
||||
#: src/pages/policies/PolicyListPage.ts:162
|
||||
msgid "Clear Policy cache"
|
||||
msgstr "Clear Policy cache"
|
||||
|
||||
#: src/pages/flows/FlowListPage.ts:138
|
||||
#: src/pages/flows/FlowListPage.ts:150
|
||||
#: src/pages/policies/PolicyListPage.ts:157
|
||||
#: src/pages/policies/PolicyListPage.ts:169
|
||||
msgid "Clear cache"
|
||||
msgstr "Clear cache"
|
||||
|
||||
#: src/elements/forms/HorizontalFormElement.ts:82
|
||||
msgid "Click to change value"
|
||||
msgstr "Click to change value"
|
||||
@ -540,7 +571,7 @@ msgstr "Click to copy token"
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts:107
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts:99
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:141
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:176
|
||||
msgid "Client ID"
|
||||
msgstr "Client ID"
|
||||
|
||||
@ -726,8 +757,8 @@ msgstr "Copy download URL"
|
||||
#: src/pages/flows/BoundStagesList.ts:119
|
||||
#: src/pages/flows/BoundStagesList.ts:146
|
||||
#: src/pages/flows/BoundStagesList.ts:167
|
||||
#: src/pages/flows/FlowListPage.ts:109
|
||||
#: src/pages/flows/FlowListPage.ts:117
|
||||
#: src/pages/flows/FlowListPage.ts:110
|
||||
#: src/pages/flows/FlowListPage.ts:118
|
||||
#: src/pages/groups/GroupListPage.ts:90
|
||||
#: src/pages/groups/GroupListPage.ts:98
|
||||
#: src/pages/outposts/OutpostListPage.ts:101
|
||||
@ -737,8 +768,8 @@ msgstr "Copy download URL"
|
||||
#: src/pages/policies/BoundPoliciesList.ts:162
|
||||
#: src/pages/policies/BoundPoliciesList.ts:189
|
||||
#: src/pages/policies/BoundPoliciesList.ts:210
|
||||
#: src/pages/policies/PolicyListPage.ts:124
|
||||
#: src/pages/policies/PolicyListPage.ts:133
|
||||
#: src/pages/policies/PolicyListPage.ts:125
|
||||
#: src/pages/policies/PolicyListPage.ts:134
|
||||
#: src/pages/property-mappings/PropertyMappingListPage.ts:113
|
||||
#: src/pages/property-mappings/PropertyMappingListPage.ts:122
|
||||
#: src/pages/providers/ProviderListPage.ts:108
|
||||
@ -778,7 +809,7 @@ msgstr "Create Binding"
|
||||
msgid "Create Certificate-Key Pair"
|
||||
msgstr "Create Certificate-Key Pair"
|
||||
|
||||
#: src/pages/flows/FlowListPage.ts:112
|
||||
#: src/pages/flows/FlowListPage.ts:113
|
||||
msgid "Create Flow"
|
||||
msgstr "Create Flow"
|
||||
|
||||
@ -840,7 +871,7 @@ msgstr "Create provider"
|
||||
#: src/pages/flows/BoundStagesList.ts:149
|
||||
#: src/pages/outposts/ServiceConnectionListPage.ts:122
|
||||
#: src/pages/policies/BoundPoliciesList.ts:192
|
||||
#: src/pages/policies/PolicyListPage.ts:136
|
||||
#: src/pages/policies/PolicyListPage.ts:137
|
||||
#: src/pages/property-mappings/PropertyMappingListPage.ts:125
|
||||
#: src/pages/providers/ProviderListPage.ts:120
|
||||
#: src/pages/sources/SourcesListPage.ts:126
|
||||
@ -892,11 +923,11 @@ msgstr "Define how notifications are sent to users, like Email or Webhook."
|
||||
#: src/pages/crypto/CertificateKeyPairListPage.ts:86
|
||||
#: src/pages/events/RuleListPage.ts:82
|
||||
#: src/pages/events/TransportListPage.ts:86
|
||||
#: src/pages/flows/FlowListPage.ts:86
|
||||
#: src/pages/flows/FlowListPage.ts:87
|
||||
#: src/pages/groups/GroupListPage.ts:81
|
||||
#: src/pages/outposts/OutpostListPage.ts:87
|
||||
#: src/pages/outposts/ServiceConnectionListPage.ts:101
|
||||
#: src/pages/policies/PolicyListPage.ts:115
|
||||
#: src/pages/policies/PolicyListPage.ts:116
|
||||
#: src/pages/property-mappings/PropertyMappingListPage.ts:104
|
||||
#: src/pages/providers/ProviderListPage.ts:99
|
||||
#: src/pages/sources/SourcesListPage.ts:95
|
||||
@ -967,7 +998,7 @@ msgid "Designates whether this user should be treated as active. Unselect this i
|
||||
msgstr "Designates whether this user should be treated as active. Unselect this instead of deleting accounts."
|
||||
|
||||
#: src/pages/flows/FlowForm.ts:119
|
||||
#: src/pages/flows/FlowListPage.ts:48
|
||||
#: src/pages/flows/FlowListPage.ts:49
|
||||
msgid "Designation"
|
||||
msgstr "Designation"
|
||||
|
||||
@ -1049,11 +1080,11 @@ msgstr "Each provider has a different issuer, based on the application slug."
|
||||
#: src/pages/crypto/CertificateKeyPairListPage.ts:74
|
||||
#: src/pages/events/RuleListPage.ts:70
|
||||
#: src/pages/events/TransportListPage.ts:74
|
||||
#: src/pages/flows/FlowListPage.ts:74
|
||||
#: src/pages/flows/FlowListPage.ts:75
|
||||
#: src/pages/groups/GroupListPage.ts:69
|
||||
#: src/pages/outposts/OutpostListPage.ts:75
|
||||
#: src/pages/outposts/ServiceConnectionListPage.ts:89
|
||||
#: src/pages/policies/PolicyListPage.ts:90
|
||||
#: src/pages/policies/PolicyListPage.ts:91
|
||||
#: src/pages/property-mappings/PropertyMappingListPage.ts:79
|
||||
#: src/pages/providers/ProviderListPage.ts:87
|
||||
#: src/pages/providers/ldap/LDAPProviderViewPage.ts:103
|
||||
@ -1148,7 +1179,7 @@ msgstr "Enable this if you don't want to use this provider as a proxy, and want
|
||||
#: src/pages/policies/PolicyBindingForm.ts:199
|
||||
#: src/pages/sources/ldap/LDAPSourceForm.ts:67
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:134
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:108
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:143
|
||||
#: src/pages/sources/saml/SAMLSourceForm.ts:67
|
||||
msgid "Enabled"
|
||||
msgstr "Enabled"
|
||||
@ -1158,7 +1189,7 @@ msgid "Enrollment"
|
||||
msgstr "Enrollment"
|
||||
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:252
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:206
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:211
|
||||
#: src/pages/sources/saml/SAMLSourceForm.ts:264
|
||||
#: src/pages/stages/identification/IdentificationStageForm.ts:104
|
||||
msgid "Enrollment flow"
|
||||
@ -1225,12 +1256,12 @@ msgstr "Events"
|
||||
msgid "Exception"
|
||||
msgstr "Exception"
|
||||
|
||||
#: src/pages/flows/FlowListPage.ts:98
|
||||
#: src/pages/flows/FlowViewPage.ts:83
|
||||
#: src/pages/flows/FlowListPage.ts:99
|
||||
#: src/pages/flows/FlowViewPage.ts:75
|
||||
msgid "Execute"
|
||||
msgstr "Execute"
|
||||
|
||||
#: src/pages/flows/FlowViewPage.ts:69
|
||||
#: src/pages/flows/FlowViewPage.ts:61
|
||||
msgid "Execute flow"
|
||||
msgstr "Execute flow"
|
||||
|
||||
@ -1277,7 +1308,7 @@ msgstr "Expiry date"
|
||||
msgid "Explicit Consent"
|
||||
msgstr "Explicit Consent"
|
||||
|
||||
#: src/pages/flows/FlowListPage.ts:101
|
||||
#: src/pages/flows/FlowListPage.ts:102
|
||||
msgid "Export"
|
||||
msgstr "Export"
|
||||
|
||||
@ -1316,6 +1347,14 @@ msgstr "Failed attempts before cancel"
|
||||
msgid "Failed sources"
|
||||
msgstr "Failed sources"
|
||||
|
||||
#: src/pages/flows/FlowListPage.ts:137
|
||||
msgid "Failed to delete flow cache"
|
||||
msgstr "Failed to delete flow cache"
|
||||
|
||||
#: src/pages/policies/PolicyListPage.ts:156
|
||||
msgid "Failed to delete policy cache"
|
||||
msgstr "Failed to delete policy cache"
|
||||
|
||||
#: src/elements/forms/DeleteForm.ts:46
|
||||
msgid "Failed to delete {0}: {1}"
|
||||
msgstr "Failed to delete {0}: {1}"
|
||||
@ -1358,7 +1397,7 @@ msgid "Fields a user can identify themselves with. If no fields are selected, th
|
||||
msgstr "Fields a user can identify themselves with. If no fields are selected, the user will only be able to use sources."
|
||||
|
||||
#: src/pages/flows/FlowImportForm.ts:34
|
||||
#: src/pages/flows/FlowListPage.ts:79
|
||||
#: src/pages/flows/FlowListPage.ts:80
|
||||
msgid "Flow"
|
||||
msgstr "Flow"
|
||||
|
||||
@ -1367,19 +1406,19 @@ msgid "Flow Overview"
|
||||
msgstr "Flow Overview"
|
||||
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:227
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:181
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:186
|
||||
#: src/pages/sources/saml/SAMLSourceForm.ts:218
|
||||
msgid "Flow settings"
|
||||
msgstr "Flow settings"
|
||||
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:249
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:203
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:208
|
||||
#: src/pages/sources/saml/SAMLSourceForm.ts:261
|
||||
msgid "Flow to use when authenticating existing users."
|
||||
msgstr "Flow to use when authenticating existing users."
|
||||
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:270
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:224
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:229
|
||||
#: src/pages/sources/saml/SAMLSourceForm.ts:282
|
||||
msgid "Flow to use when enrolling new users."
|
||||
msgstr "Flow to use when enrolling new users."
|
||||
@ -1411,12 +1450,12 @@ msgstr "Flow used when authorizing this provider."
|
||||
#: src/interfaces/AdminInterface.ts:82
|
||||
#: src/interfaces/AdminInterface.ts:84
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts:61
|
||||
#: src/pages/flows/FlowListPage.ts:28
|
||||
#: src/pages/flows/FlowListPage.ts:29
|
||||
#: src/pages/stages/StageListPage.ts:66
|
||||
msgid "Flows"
|
||||
msgstr "Flows"
|
||||
|
||||
#: src/pages/flows/FlowListPage.ts:31
|
||||
#: src/pages/flows/FlowListPage.ts:32
|
||||
msgid "Flows describe a chain of Stages to authenticate, enroll or recover a user. Stages are chosen based on policies applied to them."
|
||||
msgstr "Flows describe a chain of Stages to authenticate, enroll or recover a user. Stages are chosen based on policies applied to them."
|
||||
|
||||
@ -1546,7 +1585,7 @@ msgstr "Hide service-accounts"
|
||||
#: src/pages/providers/saml/SAMLProviderForm.ts:176
|
||||
#: src/pages/sources/ldap/LDAPSourceForm.ts:165
|
||||
#: src/pages/sources/ldap/LDAPSourceForm.ts:191
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:168
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:121
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts:114
|
||||
#: src/pages/stages/identification/IdentificationStageForm.ts:83
|
||||
#: src/pages/stages/password/PasswordStageForm.ts:84
|
||||
@ -1567,7 +1606,7 @@ msgstr "ID"
|
||||
msgid "Icon"
|
||||
msgstr "Icon"
|
||||
|
||||
#: src/pages/flows/FlowListPage.ts:46
|
||||
#: src/pages/flows/FlowListPage.ts:47
|
||||
#: src/pages/system-tasks/SystemTaskListPage.ts:54
|
||||
#: src/pages/tokens/TokenListPage.ts:44
|
||||
#: src/pages/user-settings/tokens/UserTokenForm.ts:49
|
||||
@ -1600,12 +1639,12 @@ msgstr "If your authentik Instance is using a self-signed certificate, set this
|
||||
msgid "Impersonate"
|
||||
msgstr "Impersonate"
|
||||
|
||||
#: src/pages/flows/FlowListPage.ts:122
|
||||
#: src/pages/flows/FlowListPage.ts:130
|
||||
#: src/pages/flows/FlowListPage.ts:123
|
||||
#: src/pages/flows/FlowListPage.ts:131
|
||||
msgid "Import"
|
||||
msgstr "Import"
|
||||
|
||||
#: src/pages/flows/FlowListPage.ts:125
|
||||
#: src/pages/flows/FlowListPage.ts:126
|
||||
msgid "Import Flow"
|
||||
msgstr "Import Flow"
|
||||
|
||||
@ -1716,7 +1755,7 @@ msgstr "Last login"
|
||||
msgid "Last run"
|
||||
msgstr "Last run"
|
||||
|
||||
#: src/pages/outposts/OutpostHealth.ts:54
|
||||
#: src/pages/outposts/OutpostHealth.ts:53
|
||||
msgid "Last seen: {0}"
|
||||
msgstr "Last seen: {0}"
|
||||
|
||||
@ -1742,21 +1781,21 @@ msgid "Library"
|
||||
msgstr "Library"
|
||||
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:147
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:121
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:156
|
||||
msgid "Link to a user with identical email address. Can have security implications when a source doesn't validate email addresses"
|
||||
msgstr "Link to a user with identical email address. Can have security implications when a source doesn't validate email addresses"
|
||||
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:153
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:127
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:162
|
||||
msgid "Link to a user with identical username address. Can have security implications when a username is used with another source."
|
||||
msgstr "Link to a user with identical username address. Can have security implications when a username is used with another source."
|
||||
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:144
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:118
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:153
|
||||
msgid "Link users on unique identifier"
|
||||
msgstr "Link users on unique identifier"
|
||||
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:173
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:96
|
||||
msgid "Load servers"
|
||||
msgstr "Load servers"
|
||||
|
||||
@ -1821,8 +1860,8 @@ msgstr "Loading"
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:219
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:247
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:268
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:201
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:222
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:206
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:227
|
||||
#: src/pages/sources/saml/SAMLSourceForm.ts:124
|
||||
#: src/pages/sources/saml/SAMLSourceForm.ts:238
|
||||
#: src/pages/sources/saml/SAMLSourceForm.ts:259
|
||||
@ -1961,7 +2000,7 @@ msgstr "My Applications"
|
||||
#: src/pages/events/TransportListPage.ts:46
|
||||
#: src/pages/flows/BoundStagesList.ts:39
|
||||
#: src/pages/flows/FlowForm.ts:86
|
||||
#: src/pages/flows/FlowListPage.ts:47
|
||||
#: src/pages/flows/FlowListPage.ts:48
|
||||
#: src/pages/groups/GroupForm.ts:58
|
||||
#: src/pages/groups/GroupListPage.ts:45
|
||||
#: src/pages/groups/MemberSelectModal.ts:45
|
||||
@ -1970,7 +2009,7 @@ msgstr "My Applications"
|
||||
#: src/pages/outposts/ServiceConnectionDockerForm.ts:51
|
||||
#: src/pages/outposts/ServiceConnectionKubernetesForm.ts:52
|
||||
#: src/pages/outposts/ServiceConnectionListPage.ts:53
|
||||
#: src/pages/policies/PolicyListPage.ts:56
|
||||
#: src/pages/policies/PolicyListPage.ts:57
|
||||
#: src/pages/policies/dummy/DummyPolicyForm.ts:54
|
||||
#: src/pages/policies/event_matcher/EventMatcherPolicyForm.ts:55
|
||||
#: src/pages/policies/expiry/ExpiryPolicyForm.ts:54
|
||||
@ -1997,7 +2036,7 @@ msgstr "My Applications"
|
||||
#: src/pages/sources/ldap/LDAPSourceViewPage.ts:64
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:108
|
||||
#: src/pages/sources/oauth/OAuthSourceViewPage.ts:64
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:93
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:128
|
||||
#: src/pages/sources/plex/PlexSourceViewPage.ts:63
|
||||
#: src/pages/sources/saml/SAMLSourceForm.ts:52
|
||||
#: src/pages/sources/saml/SAMLSourceViewPage.ts:66
|
||||
@ -2359,15 +2398,15 @@ msgstr "Please enter your password"
|
||||
|
||||
#: src/interfaces/AdminInterface.ts:74
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts:56
|
||||
#: src/pages/flows/FlowListPage.ts:50
|
||||
#: src/pages/policies/PolicyListPage.ts:38
|
||||
#: src/pages/flows/FlowListPage.ts:51
|
||||
#: src/pages/policies/PolicyListPage.ts:39
|
||||
msgid "Policies"
|
||||
msgstr "Policies"
|
||||
|
||||
#: src/pages/policies/PolicyBindingForm.ts:108
|
||||
#: src/pages/policies/PolicyBindingForm.ts:117
|
||||
#: src/pages/policies/PolicyBindingForm.ts:148
|
||||
#: src/pages/policies/PolicyListPage.ts:108
|
||||
#: src/pages/policies/PolicyListPage.ts:109
|
||||
msgid "Policy"
|
||||
msgstr "Policy"
|
||||
|
||||
@ -2492,7 +2531,7 @@ msgstr "Protocol Settings"
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts:123
|
||||
#: src/pages/providers/saml/SAMLProviderForm.ts:77
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:163
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:137
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:172
|
||||
#: src/pages/sources/saml/SAMLSourceForm.ts:74
|
||||
msgid "Protocol settings"
|
||||
msgstr "Protocol settings"
|
||||
@ -2623,7 +2662,7 @@ msgid "Regular expressions for which authentication is not required. Each new li
|
||||
msgstr "Regular expressions for which authentication is not required. Each new line is interpreted as a new Regular Expression."
|
||||
|
||||
#: src/pages/applications/ApplicationViewPage.ts:62
|
||||
#: src/pages/flows/FlowViewPage.ts:64
|
||||
#: src/pages/flows/FlowViewPage.ts:56
|
||||
msgid "Related"
|
||||
msgstr "Related"
|
||||
|
||||
@ -2800,7 +2839,7 @@ msgstr "Select users to add"
|
||||
msgid "Select which scopes can be used by the client. The client stil has to specify the scope to access the data."
|
||||
msgstr "Select which scopes can be used by the client. The client stil has to specify the scope to access the data."
|
||||
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:167
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:120
|
||||
msgid "Select which server a user has to be a member of to be allowed to authenticate."
|
||||
msgstr "Select which server a user has to be a member of to be allowed to authenticate."
|
||||
|
||||
@ -2940,7 +2979,7 @@ msgstr "Skip path regex"
|
||||
#: src/pages/flows/FlowForm.ts:99
|
||||
#: src/pages/sources/ldap/LDAPSourceForm.ts:58
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:114
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:99
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:134
|
||||
#: src/pages/sources/saml/SAMLSourceForm.ts:58
|
||||
msgid "Slug"
|
||||
msgstr "Slug"
|
||||
@ -3017,7 +3056,7 @@ msgid "Stage-specific settings"
|
||||
msgstr "Stage-specific settings"
|
||||
|
||||
#: src/interfaces/AdminInterface.ts:87
|
||||
#: src/pages/flows/FlowListPage.ts:49
|
||||
#: src/pages/flows/FlowListPage.ts:50
|
||||
#: src/pages/stages/StageListPage.ts:44
|
||||
#: src/pages/stages/prompt/PromptListPage.ts:50
|
||||
msgid "Stages"
|
||||
@ -3081,6 +3120,14 @@ msgstr "Subject-alt name"
|
||||
msgid "Successful"
|
||||
msgstr "Successful"
|
||||
|
||||
#: src/pages/flows/FlowListPage.ts:136
|
||||
msgid "Successfully cleared flow cache"
|
||||
msgstr "Successfully cleared flow cache"
|
||||
|
||||
#: src/pages/policies/PolicyListPage.ts:155
|
||||
msgid "Successfully cleared policy cache"
|
||||
msgstr "Successfully cleared policy cache"
|
||||
|
||||
#: src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts:63
|
||||
msgid "Successfully copied TOTP Config."
|
||||
msgstr "Successfully copied TOTP Config."
|
||||
@ -3405,14 +3452,14 @@ msgid "Template"
|
||||
msgstr "Template"
|
||||
|
||||
#: src/pages/events/TransportListPage.ts:62
|
||||
#: src/pages/policies/PolicyListPage.ts:95
|
||||
#: src/pages/policies/PolicyListPage.ts:103
|
||||
#: src/pages/policies/PolicyListPage.ts:96
|
||||
#: src/pages/policies/PolicyListPage.ts:104
|
||||
#: src/pages/property-mappings/PropertyMappingListPage.ts:84
|
||||
#: src/pages/property-mappings/PropertyMappingListPage.ts:92
|
||||
msgid "Test"
|
||||
msgstr "Test"
|
||||
|
||||
#: src/pages/policies/PolicyListPage.ts:98
|
||||
#: src/pages/policies/PolicyListPage.ts:99
|
||||
msgid "Test Policy"
|
||||
msgstr "Test Policy"
|
||||
|
||||
@ -3429,8 +3476,8 @@ msgid "The URL \"{0}\" was not found."
|
||||
msgstr "The URL \"{0}\" was not found."
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts:131
|
||||
msgid "The external URL you'll access the application at"
|
||||
msgstr "The external URL you'll access the application at"
|
||||
msgid "The external URL you'll access the application at. Include any non-standard port."
|
||||
msgstr "The external URL you'll access the application at. Include any non-standard port."
|
||||
|
||||
#: src/pages/policies/dummy/DummyPolicyForm.ts:88
|
||||
msgid "The policy takes a random time to execute. This controls the minimum time it will take."
|
||||
@ -3564,7 +3611,7 @@ msgstr "Transports"
|
||||
#: src/pages/flows/BoundStagesList.ts:40
|
||||
#: src/pages/outposts/OutpostForm.ts:58
|
||||
#: src/pages/outposts/ServiceConnectionListPage.ts:54
|
||||
#: src/pages/policies/PolicyListPage.ts:57
|
||||
#: src/pages/policies/PolicyListPage.ts:58
|
||||
#: src/pages/property-mappings/PropertyMappingListPage.ts:55
|
||||
#: src/pages/providers/ProviderListPage.ts:55
|
||||
#: src/pages/sources/SourcesListPage.ts:53
|
||||
@ -3640,7 +3687,7 @@ msgstr "Up-to-date!"
|
||||
#: src/pages/events/TransportListPage.ts:66
|
||||
#: src/pages/flows/BoundStagesList.ts:53
|
||||
#: src/pages/flows/BoundStagesList.ts:71
|
||||
#: src/pages/flows/FlowListPage.ts:66
|
||||
#: src/pages/flows/FlowListPage.ts:67
|
||||
#: src/pages/groups/GroupListPage.ts:61
|
||||
#: src/pages/outposts/OutpostListPage.ts:67
|
||||
#: src/pages/outposts/ServiceConnectionListPage.ts:76
|
||||
@ -3648,7 +3695,7 @@ msgstr "Up-to-date!"
|
||||
#: src/pages/policies/BoundPoliciesList.ts:88
|
||||
#: src/pages/policies/BoundPoliciesList.ts:103
|
||||
#: src/pages/policies/BoundPoliciesList.ts:129
|
||||
#: src/pages/policies/PolicyListPage.ts:77
|
||||
#: src/pages/policies/PolicyListPage.ts:78
|
||||
#: src/pages/property-mappings/PropertyMappingListPage.ts:66
|
||||
#: src/pages/providers/ProviderListPage.ts:74
|
||||
#: src/pages/providers/ldap/LDAPProviderViewPage.ts:93
|
||||
@ -3686,7 +3733,7 @@ msgstr "Update Binding"
|
||||
msgid "Update Certificate-Key Pair"
|
||||
msgstr "Update Certificate-Key Pair"
|
||||
|
||||
#: src/pages/flows/FlowListPage.ts:69
|
||||
#: src/pages/flows/FlowListPage.ts:70
|
||||
msgid "Update Flow"
|
||||
msgstr "Update Flow"
|
||||
|
||||
@ -3764,7 +3811,7 @@ msgstr "Update details"
|
||||
#: src/pages/flows/BoundStagesList.ts:56
|
||||
#: src/pages/outposts/ServiceConnectionListPage.ts:79
|
||||
#: src/pages/policies/BoundPoliciesList.ts:71
|
||||
#: src/pages/policies/PolicyListPage.ts:80
|
||||
#: src/pages/policies/PolicyListPage.ts:81
|
||||
#: src/pages/property-mappings/PropertyMappingListPage.ts:69
|
||||
#: src/pages/providers/ProviderListPage.ts:77
|
||||
#: src/pages/sources/SourcesListPage.ts:73
|
||||
@ -3798,12 +3845,12 @@ msgid "Use global settings"
|
||||
msgstr "Use global settings"
|
||||
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:150
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:124
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:159
|
||||
msgid "Use the user's email address, but deny enrollment when the email address already exists."
|
||||
msgstr "Use the user's email address, but deny enrollment when the email address already exists."
|
||||
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:156
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:130
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:165
|
||||
msgid "Use the user's username, but deny enrollment when the username already exists."
|
||||
msgstr "Use the user's username, but deny enrollment when the username already exists."
|
||||
|
||||
@ -3852,7 +3899,7 @@ msgid "User fields"
|
||||
msgstr "User fields"
|
||||
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:139
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:113
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:148
|
||||
msgid "User matching mode"
|
||||
msgstr "User matching mode"
|
||||
|
||||
@ -3913,8 +3960,8 @@ msgid "Users added to this group will be superusers."
|
||||
msgstr "Users added to this group will be superusers."
|
||||
|
||||
#: src/pages/providers/ldap/LDAPProviderForm.ts:86
|
||||
msgid "Users in the selected group can do search queries."
|
||||
msgstr "Users in the selected group can do search queries."
|
||||
msgid "Users in the selected group can do search queries. If no group is selected, no LDAP Searches are allowed."
|
||||
msgstr "Users in the selected group can do search queries. If no group is selected, no LDAP Searches are allowed."
|
||||
|
||||
#: src/pages/events/EventInfo.ts:108
|
||||
msgid "Using flow"
|
||||
@ -3956,7 +4003,7 @@ msgstr "Verify the user's email address by sending them a one-time-link. Can als
|
||||
msgid "Version"
|
||||
msgstr "Version"
|
||||
|
||||
#: src/pages/outposts/OutpostHealth.ts:60
|
||||
#: src/pages/outposts/OutpostHealth.ts:59
|
||||
msgid "Version: {0}"
|
||||
msgstr "Version: {0}"
|
||||
|
||||
@ -3985,7 +4032,7 @@ msgstr "Wait (min)"
|
||||
msgid "Warning"
|
||||
msgstr "Warning"
|
||||
|
||||
#: src/pages/policies/PolicyListPage.ts:71
|
||||
#: src/pages/policies/PolicyListPage.ts:72
|
||||
msgid "Warning: Policy is not assigned."
|
||||
msgstr "Warning: Policy is not assigned."
|
||||
|
||||
@ -4122,7 +4169,7 @@ msgstr "{0} is available!"
|
||||
msgid "{0} unread"
|
||||
msgstr "{0} unread"
|
||||
|
||||
#: src/pages/outposts/OutpostHealth.ts:59
|
||||
#: src/pages/outposts/OutpostHealth.ts:58
|
||||
msgid "{0}, should be {1}"
|
||||
msgstr "{0}, should be {1}"
|
||||
|
||||
|
@ -144,7 +144,7 @@ msgstr ""
|
||||
msgid "Allow IDP-initiated logins"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:150
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:103
|
||||
msgid "Allow friends to authenticate via Plex, even if you don't share any servers"
|
||||
msgstr ""
|
||||
|
||||
@ -152,7 +152,7 @@ msgstr ""
|
||||
msgid "Allow up to N occurrences in the HIBP database."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/policies/PolicyListPage.ts:41
|
||||
#: src/pages/policies/PolicyListPage.ts:42
|
||||
msgid "Allow users to use Applications based on properties, enforce Password Criteria and selectively apply Stages."
|
||||
msgstr ""
|
||||
|
||||
@ -164,7 +164,7 @@ msgstr ""
|
||||
msgid "Allowed count"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:155
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:108
|
||||
msgid "Allowed servers"
|
||||
msgstr ""
|
||||
|
||||
@ -218,6 +218,18 @@ msgstr ""
|
||||
msgid "Apps with most usage"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/FlowListPage.ts:146
|
||||
msgid ""
|
||||
"Are you sure you want to clear the flow cache?\n"
|
||||
"This will cause all flows to be re-evaluated on their next usage."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/policies/PolicyListPage.ts:165
|
||||
msgid ""
|
||||
"Are you sure you want to clear the policy cache?\n"
|
||||
"This will cause all policies to be re-evaluated on their next usage."
|
||||
msgstr ""
|
||||
|
||||
#: src/elements/forms/DeleteForm.ts:69
|
||||
msgid "Are you sure you want to delete {0} {objName} ?"
|
||||
msgstr ""
|
||||
@ -250,7 +262,7 @@ msgstr ""
|
||||
msgid "Assigned to application"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/policies/PolicyListPage.ts:68
|
||||
#: src/pages/policies/PolicyListPage.ts:69
|
||||
msgid "Assigned to {0} objects."
|
||||
msgstr ""
|
||||
|
||||
@ -282,7 +294,7 @@ msgid "Authentication"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:231
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:185
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:190
|
||||
#: src/pages/sources/saml/SAMLSourceForm.ts:243
|
||||
msgid "Authentication flow"
|
||||
msgstr ""
|
||||
@ -528,6 +540,21 @@ msgstr ""
|
||||
msgid "Checks the value from the policy request against several rules, mostly used to ensure password strength."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/FlowListPage.ts:143
|
||||
msgid "Clear Flow cache"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/policies/PolicyListPage.ts:162
|
||||
msgid "Clear Policy cache"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/FlowListPage.ts:138
|
||||
#: src/pages/flows/FlowListPage.ts:150
|
||||
#: src/pages/policies/PolicyListPage.ts:157
|
||||
#: src/pages/policies/PolicyListPage.ts:169
|
||||
msgid "Clear cache"
|
||||
msgstr ""
|
||||
|
||||
#: src/elements/forms/HorizontalFormElement.ts:82
|
||||
msgid "Click to change value"
|
||||
msgstr ""
|
||||
@ -538,7 +565,7 @@ msgstr ""
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts:107
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts:99
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:141
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:176
|
||||
msgid "Client ID"
|
||||
msgstr ""
|
||||
|
||||
@ -724,8 +751,8 @@ msgstr ""
|
||||
#: src/pages/flows/BoundStagesList.ts:119
|
||||
#: src/pages/flows/BoundStagesList.ts:146
|
||||
#: src/pages/flows/BoundStagesList.ts:167
|
||||
#: src/pages/flows/FlowListPage.ts:109
|
||||
#: src/pages/flows/FlowListPage.ts:117
|
||||
#: src/pages/flows/FlowListPage.ts:110
|
||||
#: src/pages/flows/FlowListPage.ts:118
|
||||
#: src/pages/groups/GroupListPage.ts:90
|
||||
#: src/pages/groups/GroupListPage.ts:98
|
||||
#: src/pages/outposts/OutpostListPage.ts:101
|
||||
@ -735,8 +762,8 @@ msgstr ""
|
||||
#: src/pages/policies/BoundPoliciesList.ts:162
|
||||
#: src/pages/policies/BoundPoliciesList.ts:189
|
||||
#: src/pages/policies/BoundPoliciesList.ts:210
|
||||
#: src/pages/policies/PolicyListPage.ts:124
|
||||
#: src/pages/policies/PolicyListPage.ts:133
|
||||
#: src/pages/policies/PolicyListPage.ts:125
|
||||
#: src/pages/policies/PolicyListPage.ts:134
|
||||
#: src/pages/property-mappings/PropertyMappingListPage.ts:113
|
||||
#: src/pages/property-mappings/PropertyMappingListPage.ts:122
|
||||
#: src/pages/providers/ProviderListPage.ts:108
|
||||
@ -776,7 +803,7 @@ msgstr ""
|
||||
msgid "Create Certificate-Key Pair"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/FlowListPage.ts:112
|
||||
#: src/pages/flows/FlowListPage.ts:113
|
||||
msgid "Create Flow"
|
||||
msgstr ""
|
||||
|
||||
@ -838,7 +865,7 @@ msgstr ""
|
||||
#: src/pages/flows/BoundStagesList.ts:149
|
||||
#: src/pages/outposts/ServiceConnectionListPage.ts:122
|
||||
#: src/pages/policies/BoundPoliciesList.ts:192
|
||||
#: src/pages/policies/PolicyListPage.ts:136
|
||||
#: src/pages/policies/PolicyListPage.ts:137
|
||||
#: src/pages/property-mappings/PropertyMappingListPage.ts:125
|
||||
#: src/pages/providers/ProviderListPage.ts:120
|
||||
#: src/pages/sources/SourcesListPage.ts:126
|
||||
@ -890,11 +917,11 @@ msgstr ""
|
||||
#: src/pages/crypto/CertificateKeyPairListPage.ts:86
|
||||
#: src/pages/events/RuleListPage.ts:82
|
||||
#: src/pages/events/TransportListPage.ts:86
|
||||
#: src/pages/flows/FlowListPage.ts:86
|
||||
#: src/pages/flows/FlowListPage.ts:87
|
||||
#: src/pages/groups/GroupListPage.ts:81
|
||||
#: src/pages/outposts/OutpostListPage.ts:87
|
||||
#: src/pages/outposts/ServiceConnectionListPage.ts:101
|
||||
#: src/pages/policies/PolicyListPage.ts:115
|
||||
#: src/pages/policies/PolicyListPage.ts:116
|
||||
#: src/pages/property-mappings/PropertyMappingListPage.ts:104
|
||||
#: src/pages/providers/ProviderListPage.ts:99
|
||||
#: src/pages/sources/SourcesListPage.ts:95
|
||||
@ -963,7 +990,7 @@ msgid "Designates whether this user should be treated as active. Unselect this i
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/FlowForm.ts:119
|
||||
#: src/pages/flows/FlowListPage.ts:48
|
||||
#: src/pages/flows/FlowListPage.ts:49
|
||||
msgid "Designation"
|
||||
msgstr ""
|
||||
|
||||
@ -1045,11 +1072,11 @@ msgstr ""
|
||||
#: src/pages/crypto/CertificateKeyPairListPage.ts:74
|
||||
#: src/pages/events/RuleListPage.ts:70
|
||||
#: src/pages/events/TransportListPage.ts:74
|
||||
#: src/pages/flows/FlowListPage.ts:74
|
||||
#: src/pages/flows/FlowListPage.ts:75
|
||||
#: src/pages/groups/GroupListPage.ts:69
|
||||
#: src/pages/outposts/OutpostListPage.ts:75
|
||||
#: src/pages/outposts/ServiceConnectionListPage.ts:89
|
||||
#: src/pages/policies/PolicyListPage.ts:90
|
||||
#: src/pages/policies/PolicyListPage.ts:91
|
||||
#: src/pages/property-mappings/PropertyMappingListPage.ts:79
|
||||
#: src/pages/providers/ProviderListPage.ts:87
|
||||
#: src/pages/providers/ldap/LDAPProviderViewPage.ts:103
|
||||
@ -1144,7 +1171,7 @@ msgstr ""
|
||||
#: src/pages/policies/PolicyBindingForm.ts:199
|
||||
#: src/pages/sources/ldap/LDAPSourceForm.ts:67
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:134
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:108
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:143
|
||||
#: src/pages/sources/saml/SAMLSourceForm.ts:67
|
||||
msgid "Enabled"
|
||||
msgstr ""
|
||||
@ -1154,7 +1181,7 @@ msgid "Enrollment"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:252
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:206
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:211
|
||||
#: src/pages/sources/saml/SAMLSourceForm.ts:264
|
||||
#: src/pages/stages/identification/IdentificationStageForm.ts:104
|
||||
msgid "Enrollment flow"
|
||||
@ -1221,12 +1248,12 @@ msgstr ""
|
||||
msgid "Exception"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/FlowListPage.ts:98
|
||||
#: src/pages/flows/FlowViewPage.ts:83
|
||||
#: src/pages/flows/FlowListPage.ts:99
|
||||
#: src/pages/flows/FlowViewPage.ts:75
|
||||
msgid "Execute"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/FlowViewPage.ts:69
|
||||
#: src/pages/flows/FlowViewPage.ts:61
|
||||
msgid "Execute flow"
|
||||
msgstr ""
|
||||
|
||||
@ -1273,7 +1300,7 @@ msgstr ""
|
||||
msgid "Explicit Consent"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/FlowListPage.ts:101
|
||||
#: src/pages/flows/FlowListPage.ts:102
|
||||
msgid "Export"
|
||||
msgstr ""
|
||||
|
||||
@ -1312,6 +1339,14 @@ msgstr ""
|
||||
msgid "Failed sources"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/FlowListPage.ts:137
|
||||
msgid "Failed to delete flow cache"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/policies/PolicyListPage.ts:156
|
||||
msgid "Failed to delete policy cache"
|
||||
msgstr ""
|
||||
|
||||
#: src/elements/forms/DeleteForm.ts:46
|
||||
msgid "Failed to delete {0}: {1}"
|
||||
msgstr ""
|
||||
@ -1354,7 +1389,7 @@ msgid "Fields a user can identify themselves with. If no fields are selected, th
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/FlowImportForm.ts:34
|
||||
#: src/pages/flows/FlowListPage.ts:79
|
||||
#: src/pages/flows/FlowListPage.ts:80
|
||||
msgid "Flow"
|
||||
msgstr ""
|
||||
|
||||
@ -1363,19 +1398,19 @@ msgid "Flow Overview"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:227
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:181
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:186
|
||||
#: src/pages/sources/saml/SAMLSourceForm.ts:218
|
||||
msgid "Flow settings"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:249
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:203
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:208
|
||||
#: src/pages/sources/saml/SAMLSourceForm.ts:261
|
||||
msgid "Flow to use when authenticating existing users."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:270
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:224
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:229
|
||||
#: src/pages/sources/saml/SAMLSourceForm.ts:282
|
||||
msgid "Flow to use when enrolling new users."
|
||||
msgstr ""
|
||||
@ -1407,12 +1442,12 @@ msgstr ""
|
||||
#: src/interfaces/AdminInterface.ts:82
|
||||
#: src/interfaces/AdminInterface.ts:84
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts:61
|
||||
#: src/pages/flows/FlowListPage.ts:28
|
||||
#: src/pages/flows/FlowListPage.ts:29
|
||||
#: src/pages/stages/StageListPage.ts:66
|
||||
msgid "Flows"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/FlowListPage.ts:31
|
||||
#: src/pages/flows/FlowListPage.ts:32
|
||||
msgid "Flows describe a chain of Stages to authenticate, enroll or recover a user. Stages are chosen based on policies applied to them."
|
||||
msgstr ""
|
||||
|
||||
@ -1542,7 +1577,7 @@ msgstr ""
|
||||
#: src/pages/providers/saml/SAMLProviderForm.ts:176
|
||||
#: src/pages/sources/ldap/LDAPSourceForm.ts:165
|
||||
#: src/pages/sources/ldap/LDAPSourceForm.ts:191
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:168
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:121
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts:114
|
||||
#: src/pages/stages/identification/IdentificationStageForm.ts:83
|
||||
#: src/pages/stages/password/PasswordStageForm.ts:84
|
||||
@ -1563,7 +1598,7 @@ msgstr ""
|
||||
msgid "Icon"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/FlowListPage.ts:46
|
||||
#: src/pages/flows/FlowListPage.ts:47
|
||||
#: src/pages/system-tasks/SystemTaskListPage.ts:54
|
||||
#: src/pages/tokens/TokenListPage.ts:44
|
||||
#: src/pages/user-settings/tokens/UserTokenForm.ts:49
|
||||
@ -1596,12 +1631,12 @@ msgstr ""
|
||||
msgid "Impersonate"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/FlowListPage.ts:122
|
||||
#: src/pages/flows/FlowListPage.ts:130
|
||||
#: src/pages/flows/FlowListPage.ts:123
|
||||
#: src/pages/flows/FlowListPage.ts:131
|
||||
msgid "Import"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/FlowListPage.ts:125
|
||||
#: src/pages/flows/FlowListPage.ts:126
|
||||
msgid "Import Flow"
|
||||
msgstr ""
|
||||
|
||||
@ -1712,7 +1747,7 @@ msgstr ""
|
||||
msgid "Last run"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/outposts/OutpostHealth.ts:54
|
||||
#: src/pages/outposts/OutpostHealth.ts:53
|
||||
msgid "Last seen: {0}"
|
||||
msgstr ""
|
||||
|
||||
@ -1738,21 +1773,21 @@ msgid "Library"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:147
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:121
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:156
|
||||
msgid "Link to a user with identical email address. Can have security implications when a source doesn't validate email addresses"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:153
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:127
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:162
|
||||
msgid "Link to a user with identical username address. Can have security implications when a username is used with another source."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:144
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:118
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:153
|
||||
msgid "Link users on unique identifier"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:173
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:96
|
||||
msgid "Load servers"
|
||||
msgstr ""
|
||||
|
||||
@ -1817,8 +1852,8 @@ msgstr ""
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:219
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:247
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:268
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:201
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:222
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:206
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:227
|
||||
#: src/pages/sources/saml/SAMLSourceForm.ts:124
|
||||
#: src/pages/sources/saml/SAMLSourceForm.ts:238
|
||||
#: src/pages/sources/saml/SAMLSourceForm.ts:259
|
||||
@ -1957,7 +1992,7 @@ msgstr ""
|
||||
#: src/pages/events/TransportListPage.ts:46
|
||||
#: src/pages/flows/BoundStagesList.ts:39
|
||||
#: src/pages/flows/FlowForm.ts:86
|
||||
#: src/pages/flows/FlowListPage.ts:47
|
||||
#: src/pages/flows/FlowListPage.ts:48
|
||||
#: src/pages/groups/GroupForm.ts:58
|
||||
#: src/pages/groups/GroupListPage.ts:45
|
||||
#: src/pages/groups/MemberSelectModal.ts:45
|
||||
@ -1966,7 +2001,7 @@ msgstr ""
|
||||
#: src/pages/outposts/ServiceConnectionDockerForm.ts:51
|
||||
#: src/pages/outposts/ServiceConnectionKubernetesForm.ts:52
|
||||
#: src/pages/outposts/ServiceConnectionListPage.ts:53
|
||||
#: src/pages/policies/PolicyListPage.ts:56
|
||||
#: src/pages/policies/PolicyListPage.ts:57
|
||||
#: src/pages/policies/dummy/DummyPolicyForm.ts:54
|
||||
#: src/pages/policies/event_matcher/EventMatcherPolicyForm.ts:55
|
||||
#: src/pages/policies/expiry/ExpiryPolicyForm.ts:54
|
||||
@ -1993,7 +2028,7 @@ msgstr ""
|
||||
#: src/pages/sources/ldap/LDAPSourceViewPage.ts:64
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:108
|
||||
#: src/pages/sources/oauth/OAuthSourceViewPage.ts:64
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:93
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:128
|
||||
#: src/pages/sources/plex/PlexSourceViewPage.ts:63
|
||||
#: src/pages/sources/saml/SAMLSourceForm.ts:52
|
||||
#: src/pages/sources/saml/SAMLSourceViewPage.ts:66
|
||||
@ -2355,15 +2390,15 @@ msgstr ""
|
||||
|
||||
#: src/interfaces/AdminInterface.ts:74
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts:56
|
||||
#: src/pages/flows/FlowListPage.ts:50
|
||||
#: src/pages/policies/PolicyListPage.ts:38
|
||||
#: src/pages/flows/FlowListPage.ts:51
|
||||
#: src/pages/policies/PolicyListPage.ts:39
|
||||
msgid "Policies"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/policies/PolicyBindingForm.ts:108
|
||||
#: src/pages/policies/PolicyBindingForm.ts:117
|
||||
#: src/pages/policies/PolicyBindingForm.ts:148
|
||||
#: src/pages/policies/PolicyListPage.ts:108
|
||||
#: src/pages/policies/PolicyListPage.ts:109
|
||||
msgid "Policy"
|
||||
msgstr ""
|
||||
|
||||
@ -2488,7 +2523,7 @@ msgstr ""
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts:123
|
||||
#: src/pages/providers/saml/SAMLProviderForm.ts:77
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:163
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:137
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:172
|
||||
#: src/pages/sources/saml/SAMLSourceForm.ts:74
|
||||
msgid "Protocol settings"
|
||||
msgstr ""
|
||||
@ -2619,7 +2654,7 @@ msgid "Regular expressions for which authentication is not required. Each new li
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/applications/ApplicationViewPage.ts:62
|
||||
#: src/pages/flows/FlowViewPage.ts:64
|
||||
#: src/pages/flows/FlowViewPage.ts:56
|
||||
msgid "Related"
|
||||
msgstr ""
|
||||
|
||||
@ -2796,7 +2831,7 @@ msgstr ""
|
||||
msgid "Select which scopes can be used by the client. The client stil has to specify the scope to access the data."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:167
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:120
|
||||
msgid "Select which server a user has to be a member of to be allowed to authenticate."
|
||||
msgstr ""
|
||||
|
||||
@ -2936,7 +2971,7 @@ msgstr ""
|
||||
#: src/pages/flows/FlowForm.ts:99
|
||||
#: src/pages/sources/ldap/LDAPSourceForm.ts:58
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:114
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:99
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:134
|
||||
#: src/pages/sources/saml/SAMLSourceForm.ts:58
|
||||
msgid "Slug"
|
||||
msgstr ""
|
||||
@ -3013,7 +3048,7 @@ msgid "Stage-specific settings"
|
||||
msgstr ""
|
||||
|
||||
#: src/interfaces/AdminInterface.ts:87
|
||||
#: src/pages/flows/FlowListPage.ts:49
|
||||
#: src/pages/flows/FlowListPage.ts:50
|
||||
#: src/pages/stages/StageListPage.ts:44
|
||||
#: src/pages/stages/prompt/PromptListPage.ts:50
|
||||
msgid "Stages"
|
||||
@ -3077,6 +3112,14 @@ msgstr ""
|
||||
msgid "Successful"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/FlowListPage.ts:136
|
||||
msgid "Successfully cleared flow cache"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/policies/PolicyListPage.ts:155
|
||||
msgid "Successfully cleared policy cache"
|
||||
msgstr ""
|
||||
|
||||
#: src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts:63
|
||||
msgid "Successfully copied TOTP Config."
|
||||
msgstr ""
|
||||
@ -3401,14 +3444,14 @@ msgid "Template"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/events/TransportListPage.ts:62
|
||||
#: src/pages/policies/PolicyListPage.ts:95
|
||||
#: src/pages/policies/PolicyListPage.ts:103
|
||||
#: src/pages/policies/PolicyListPage.ts:96
|
||||
#: src/pages/policies/PolicyListPage.ts:104
|
||||
#: src/pages/property-mappings/PropertyMappingListPage.ts:84
|
||||
#: src/pages/property-mappings/PropertyMappingListPage.ts:92
|
||||
msgid "Test"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/policies/PolicyListPage.ts:98
|
||||
#: src/pages/policies/PolicyListPage.ts:99
|
||||
msgid "Test Policy"
|
||||
msgstr ""
|
||||
|
||||
@ -3425,7 +3468,7 @@ msgid "The URL \"{0}\" was not found."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts:131
|
||||
msgid "The external URL you'll access the application at"
|
||||
msgid "The external URL you'll access the application at. Include any non-standard port."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/policies/dummy/DummyPolicyForm.ts:88
|
||||
@ -3556,7 +3599,7 @@ msgstr ""
|
||||
#: src/pages/flows/BoundStagesList.ts:40
|
||||
#: src/pages/outposts/OutpostForm.ts:58
|
||||
#: src/pages/outposts/ServiceConnectionListPage.ts:54
|
||||
#: src/pages/policies/PolicyListPage.ts:57
|
||||
#: src/pages/policies/PolicyListPage.ts:58
|
||||
#: src/pages/property-mappings/PropertyMappingListPage.ts:55
|
||||
#: src/pages/providers/ProviderListPage.ts:55
|
||||
#: src/pages/sources/SourcesListPage.ts:53
|
||||
@ -3632,7 +3675,7 @@ msgstr ""
|
||||
#: src/pages/events/TransportListPage.ts:66
|
||||
#: src/pages/flows/BoundStagesList.ts:53
|
||||
#: src/pages/flows/BoundStagesList.ts:71
|
||||
#: src/pages/flows/FlowListPage.ts:66
|
||||
#: src/pages/flows/FlowListPage.ts:67
|
||||
#: src/pages/groups/GroupListPage.ts:61
|
||||
#: src/pages/outposts/OutpostListPage.ts:67
|
||||
#: src/pages/outposts/ServiceConnectionListPage.ts:76
|
||||
@ -3640,7 +3683,7 @@ msgstr ""
|
||||
#: src/pages/policies/BoundPoliciesList.ts:88
|
||||
#: src/pages/policies/BoundPoliciesList.ts:103
|
||||
#: src/pages/policies/BoundPoliciesList.ts:129
|
||||
#: src/pages/policies/PolicyListPage.ts:77
|
||||
#: src/pages/policies/PolicyListPage.ts:78
|
||||
#: src/pages/property-mappings/PropertyMappingListPage.ts:66
|
||||
#: src/pages/providers/ProviderListPage.ts:74
|
||||
#: src/pages/providers/ldap/LDAPProviderViewPage.ts:93
|
||||
@ -3678,7 +3721,7 @@ msgstr ""
|
||||
msgid "Update Certificate-Key Pair"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/FlowListPage.ts:69
|
||||
#: src/pages/flows/FlowListPage.ts:70
|
||||
msgid "Update Flow"
|
||||
msgstr ""
|
||||
|
||||
@ -3756,7 +3799,7 @@ msgstr ""
|
||||
#: src/pages/flows/BoundStagesList.ts:56
|
||||
#: src/pages/outposts/ServiceConnectionListPage.ts:79
|
||||
#: src/pages/policies/BoundPoliciesList.ts:71
|
||||
#: src/pages/policies/PolicyListPage.ts:80
|
||||
#: src/pages/policies/PolicyListPage.ts:81
|
||||
#: src/pages/property-mappings/PropertyMappingListPage.ts:69
|
||||
#: src/pages/providers/ProviderListPage.ts:77
|
||||
#: src/pages/sources/SourcesListPage.ts:73
|
||||
@ -3790,12 +3833,12 @@ msgid "Use global settings"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:150
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:124
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:159
|
||||
msgid "Use the user's email address, but deny enrollment when the email address already exists."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:156
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:130
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:165
|
||||
msgid "Use the user's username, but deny enrollment when the username already exists."
|
||||
msgstr ""
|
||||
|
||||
@ -3844,7 +3887,7 @@ msgid "User fields"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts:139
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:113
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts:148
|
||||
msgid "User matching mode"
|
||||
msgstr ""
|
||||
|
||||
@ -3905,7 +3948,7 @@ msgid "Users added to this group will be superusers."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/ldap/LDAPProviderForm.ts:86
|
||||
msgid "Users in the selected group can do search queries."
|
||||
msgid "Users in the selected group can do search queries. If no group is selected, no LDAP Searches are allowed."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/events/EventInfo.ts:108
|
||||
@ -3948,7 +3991,7 @@ msgstr ""
|
||||
msgid "Version"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/outposts/OutpostHealth.ts:60
|
||||
#: src/pages/outposts/OutpostHealth.ts:59
|
||||
msgid "Version: {0}"
|
||||
msgstr ""
|
||||
|
||||
@ -3977,7 +4020,7 @@ msgstr ""
|
||||
msgid "Warning"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/policies/PolicyListPage.ts:71
|
||||
#: src/pages/policies/PolicyListPage.ts:72
|
||||
msgid "Warning: Policy is not assigned."
|
||||
msgstr ""
|
||||
|
||||
@ -4112,7 +4155,7 @@ msgstr ""
|
||||
msgid "{0} unread"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/outposts/OutpostHealth.ts:59
|
||||
#: src/pages/outposts/OutpostHealth.ts:58
|
||||
msgid "{0}, should be {1}"
|
||||
msgstr ""
|
||||
|
||||
|
@ -67,7 +67,7 @@ export class RuleForm extends ModelForm<NotificationRule, string> {
|
||||
<option value="" ?selected=${this.instance?.group === undefined}>---------</option>
|
||||
${until(new CoreApi(DEFAULT_CONFIG).coreGroupsList({}).then(groups => {
|
||||
return groups.results.map(group => {
|
||||
return html`<option value=${ifDefined(group.pk)} ?selected=${this.instance?.group?.groupUuid === group.pk}>${group.name}</option>`;
|
||||
return html`<option value=${ifDefined(group.pk)} ?selected=${this.instance?.group === group.pk}>${group.name}</option>`;
|
||||
});
|
||||
}), html`<option>${t`Loading...`}</option>`)}
|
||||
</select>
|
||||
@ -80,7 +80,7 @@ export class RuleForm extends ModelForm<NotificationRule, string> {
|
||||
${until(new EventsApi(DEFAULT_CONFIG).eventsTransportsList({}).then(transports => {
|
||||
return transports.results.map(transport => {
|
||||
const selected = Array.from(this.instance?.transports || []).some(su => {
|
||||
return su.uuid == transport.pk;
|
||||
return su == transport.pk;
|
||||
});
|
||||
return html`<option value=${ifDefined(transport.pk)} ?selected=${selected}>${transport.name}</option>`;
|
||||
});
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user