Merge branch 'master' into inbuilt-proxy

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

# Conflicts:
#	internal/constants/constants.go
#	outpost/pkg/version.go
This commit is contained in:
Jens Langhammer 2021-06-23 20:40:51 +02:00
commit 1005f341e4
114 changed files with 1633 additions and 667 deletions

View File

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 2021.6.1-rc6 current_version = 2021.6.2
tag = True tag = True
commit = True commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*) parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*)

1
.github/stale.yml vendored
View File

@ -6,6 +6,7 @@ daysUntilClose: 7
exemptLabels: exemptLabels:
- pinned - pinned
- security - security
- pr_wanted
# Comment to post when marking an issue as stale. Set to `false` to disable # Comment to post when marking an issue as stale. Set to `false` to disable
markComment: > markComment: >
This issue has been automatically marked as stale because it has not had This issue has been automatically marked as stale because it has not had

View File

@ -33,22 +33,21 @@ jobs:
with: with:
push: ${{ github.event_name == 'release' }} push: ${{ github.event_name == 'release' }}
tags: | tags: |
beryju/authentik:2021.6.1-rc6, beryju/authentik:2021.6.2,
beryju/authentik:latest, beryju/authentik:latest,
ghcr.io/goauthentik/server:2021.6.1-rc6, ghcr.io/goauthentik/server:2021.6.2,
ghcr.io/goauthentik/server:latest ghcr.io/goauthentik/server:latest
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
context: . context: .
- name: Building Docker Image (stable) - name: Building Docker Image (stable)
uses: docker/build-push-action@v2 if: ${{ github.event_name == 'release' && !contains('2021.6.2', 'rc') }}
if: ${{ github.event_name == 'release' && !contains('2021.6.1-rc6', 'rc') }} run: |
with: docker pull beryju/authentik:latest
push: true docker tag beryju/authentik:latest beryju/authentik:stable
tags: | docker push beryju/authentik:stable
beryju/authentik:stable, docker pull ghcr.io/goauthentik/server:latest
ghcr.io/goauthentik/server:stable docker tag ghcr.io/goauthentik/server:latest ghcr.io/goauthentik/server:stable
platforms: linux/amd64,linux/arm64 docker push ghcr.io/goauthentik/server:stable
context: .
build-proxy: build-proxy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -76,22 +75,21 @@ jobs:
with: with:
push: ${{ github.event_name == 'release' }} push: ${{ github.event_name == 'release' }}
tags: | tags: |
beryju/authentik-proxy:2021.6.1-rc6, beryju/authentik-proxy:2021.6.2,
beryju/authentik-proxy:latest, beryju/authentik-proxy:latest,
ghcr.io/goauthentik/proxy:2021.6.1-rc6, ghcr.io/goauthentik/proxy:2021.6.2,
ghcr.io/goauthentik/proxy:latest ghcr.io/goauthentik/proxy:latest
file: proxy.Dockerfile file: proxy.Dockerfile
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
- name: Building Docker Image (stable) - name: Building Docker Image (stable)
uses: docker/build-push-action@v2 if: ${{ github.event_name == 'release' && !contains('2021.6.2', 'rc') }}
if: ${{ github.event_name == 'release' && !contains('2021.6.1-rc6', 'rc') }} run: |
with: docker pull beryju/authentik-proxy:latest
push: true docker tag beryju/authentik-proxy:latest beryju/authentik-proxy:stable
tags: | docker push beryju/authentik-proxy:stable
beryju/authentik-proxy:stable, docker pull ghcr.io/goauthentik/proxy:latest
ghcr.io/goauthentik/proxy:stable docker tag ghcr.io/goauthentik/proxy:latest ghcr.io/goauthentik/proxy:stable
platforms: linux/amd64,linux/arm64 docker push ghcr.io/goauthentik/proxy:stable
context: .
build-ldap: build-ldap:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -119,24 +117,22 @@ jobs:
with: with:
push: ${{ github.event_name == 'release' }} push: ${{ github.event_name == 'release' }}
tags: | tags: |
beryju/authentik-ldap:2021.6.1-rc6, beryju/authentik-ldap:2021.6.2,
beryju/authentik-ldap:latest, beryju/authentik-ldap:latest,
ghcr.io/goauthentik/ldap:2021.6.1-rc6, ghcr.io/goauthentik/ldap:2021.6.2,
ghcr.io/goauthentik/ldap:latest ghcr.io/goauthentik/ldap:latest
file: ldap.Dockerfile file: ldap.Dockerfile
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
- name: Building Docker Image (stable) - name: Building Docker Image (stable)
uses: docker/build-push-action@v2 if: ${{ github.event_name == 'release' && !contains('2021.6.2', 'rc') }}
if: ${{ github.event_name == 'release' && !contains('2021.6.1-rc6', 'rc') }} run: |
with: docker pull beryju/authentik-ldap:latest
push: true docker tag beryju/authentik-ldap:latest beryju/authentik-ldap:stable
tags: | docker push beryju/authentik-ldap:stable
beryju/authentik-ldap:stable, docker pull ghcr.io/goauthentik/ldap:latest
ghcr.io/goauthentik/ldap:stable docker tag ghcr.io/goauthentik/ldap:latest ghcr.io/goauthentik/ldap:stable
platforms: linux/amd64,linux/arm64 docker push ghcr.io/goauthentik/ldap:stable
context: .
test-release: test-release:
if: ${{ github.event_name == 'release' }}
needs: needs:
- build-server - build-server
- build-proxy - build-proxy
@ -160,13 +156,26 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Setup Node.js environment
uses: actions/setup-node@v2.1.5
with:
node-version: 12.x
- name: Build web api client and web ui
run: |
export NODE_ENV=production
make gen-web
cd web
npm i
npm run build
- name: Create a Sentry.io release - name: Create a Sentry.io release
uses: getsentry/action-release@v1 uses: getsentry/action-release@v1
if: ${{ github.event_name == 'release' }}
env: env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: beryjuorg SENTRY_ORG: beryjuorg
SENTRY_PROJECT: authentik SENTRY_PROJECT: authentik
SENTRY_URL: https://sentry.beryju.org SENTRY_URL: https://sentry.beryju.org
with: with:
version: authentik@2021.6.1-rc6 version: authentik@2021.6.2
environment: beryjuorg-prod environment: beryjuorg-prod
sourcemaps: './web/dist'

View File

@ -46,6 +46,7 @@ webauthn = "*"
xmlsec = "*" xmlsec = "*"
duo-client = "*" duo-client = "*"
ua-parser = "*" ua-parser = "*"
deepmerge = "*"
[requires] [requires]
python_version = "3.9" python_version = "3.9"

128
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "4fa1ad681762c867a95410074f31ac5d00119e187e0f38982cd59fdf301cccf5" "sha256": "f90d9fb4713eaf9c5ffe6a3858e64843670f79ab5007e7debf914c1f094c8d63"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -122,19 +122,19 @@
}, },
"boto3": { "boto3": {
"hashes": [ "hashes": [
"sha256:8e5af9c7ea16ce1c35b7c3220d073dea9735bb1790107820d475462500ae1eff", "sha256:2c2f70608934b03f9c08f4cd185de223b5abd18245dd4d4800e1fbc2a2523e31",
"sha256:e61607211816c194dbe2701db48dcddc87cf19372e6f57a9ebe4dfe93dfe177c" "sha256:fccfa81cda69bb2317ed97e7149d7d84d19e6ec3bfbe3f721139e7ac0c407c73"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.17.95" "version": "==1.17.98"
}, },
"botocore": { "botocore": {
"hashes": [ "hashes": [
"sha256:240a9ef007292e986a4e11662f9038435d9d4fd242e083db160c86eb5c24af30", "sha256:b2a49de4ee04b690142c8e7240f0f5758e3f7673dd39cf398efe893bf5e11c3f",
"sha256:dc215f59735a3abde6c66a61f43f10d95bc18754d310da4e2037b3b8c4d8aa2d" "sha256:b955b23fe2fbdbbc8e66f37fe2970de6b5d8169f940b200bcf434751709d38f6"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", "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.95" "version": "==1.20.98"
}, },
"cachetools": { "cachetools": {
"hashes": [ "hashes": [
@ -165,11 +165,11 @@
}, },
"celery": { "celery": {
"hashes": [ "hashes": [
"sha256:1329de1edeaf734ef859e630cb42df2c116d53e59d2f46433b13aed196e85620", "sha256:54436cd97b031bf2e08064223240e2a83d601d9414bcb1b702f94c6c33c29485",
"sha256:65f061c04578cf189cd7352c192e1a79fdeb370b916bff792bcc769560e81184" "sha256:b5399d76cf70d5cfac3ec993f8796ec1aa90d4cef55972295751f384758a80d7"
], ],
"index": "pypi", "index": "pypi",
"version": "==5.1.0" "version": "==5.1.1"
}, },
"certifi": { "certifi": {
"hashes": [ "hashes": [
@ -324,6 +324,14 @@
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==3.0.2" "version": "==3.0.2"
}, },
"deepmerge": {
"hashes": [
"sha256:87166dbe9ba1a3348a45c9d4ada6778f518d41afc0b85aa017ea3041facc3f9c",
"sha256:f6fd7f1293c535fb599e197e750dbe8674503c5d2a89759b3c72a3c46746d4fd"
],
"index": "pypi",
"version": "==0.3.0"
},
"defusedxml": { "defusedxml": {
"hashes": [ "hashes": [
"sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69",
@ -465,11 +473,11 @@
}, },
"google-auth": { "google-auth": {
"hashes": [ "hashes": [
"sha256:154f7889c5d679a6f626f36adb12afbd4dbb0a9a04ec575d989d6ba79c4fd65e", "sha256:b3a67fa9ba5b768861dacf374c2135eb09fa14a0e40c851c3b8ea7abe6fc8fef",
"sha256:6d47c79b5d09fbc7e8355fd9594cc4cf65fdde5d401c63951eaac4baa1ba2ae1" "sha256:e34e5f5de5610b202f9b40ebd9f8b27571d5c5537db9afed3a72b2db5a345039"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==1.31.0" "version": "==1.32.0"
}, },
"gunicorn": { "gunicorn": {
"hashes": [ "hashes": [
@ -786,52 +794,46 @@
}, },
"prompt-toolkit": { "prompt-toolkit": {
"hashes": [ "hashes": [
"sha256:bf00f22079f5fadc949f42ae8ff7f05702826a97059ffcc6281036ad40ac6f04", "sha256:08360ee3a3148bdb5163621709ee322ec34fc4375099afa4bbf751e9b7b7fa4f",
"sha256:e1b4f11b9336a28fa11810bc623c357420f69dfdb6d2dac41ca2c21a55c033bc" "sha256:7089d8d2938043508aa9420ec18ce0922885304cddae87fb96eebca942299f88"
], ],
"markers": "python_full_version >= '3.6.1'", "markers": "python_full_version >= '3.6.1'",
"version": "==3.0.18" "version": "==3.0.19"
}, },
"psycopg2-binary": { "psycopg2-binary": {
"hashes": [ "hashes": [
"sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c", "sha256:0b7dae87f0b729922e06f85f667de7bf16455d411971b2043bbd9577af9d1975",
"sha256:0e4dc3d5996760104746e6cfcdb519d9d2cd27c738296525d5867ea695774e67", "sha256:0f2e04bd2a2ab54fa44ee67fe2d002bb90cee1c0f1cc0ebc3148af7b02034cbd",
"sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0", "sha256:123c3fb684e9abfc47218d3784c7b4c47c8587951ea4dd5bc38b6636ac57f616",
"sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6", "sha256:1473c0215b0613dd938db54a653f68251a45a78b05f6fc21af4326f40e8360a2",
"sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db", "sha256:14db1752acdd2187d99cb2ca0a1a6dfe57fc65c3281e0f20e597aac8d2a5bd90",
"sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94", "sha256:1e3a362790edc0a365385b1ac4cc0acc429a0c0d662d829a50b6ce743ae61b5a",
"sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52", "sha256:1e85b74cbbb3056e3656f1cc4781294df03383127a8114cbc6531e8b8367bf1e",
"sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056", "sha256:20f1ab44d8c352074e2d7ca67dc00843067788791be373e67a0911998787ce7d",
"sha256:6a32f3a4cb2f6e1a0b15215f448e8ce2da192fd4ff35084d80d5e39da683e79b", "sha256:2f62c207d1740b0bde5c4e949f857b044818f734a3d57f1d0d0edc65050532ed",
"sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd", "sha256:3242b9619de955ab44581a03a64bdd7d5e470cc4183e8fcadd85ab9d3756ce7a",
"sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550", "sha256:35c4310f8febe41f442d3c65066ca93cccefd75013df3d8c736c5b93ec288140",
"sha256:833709a5c66ca52f1d21d41865a637223b368c0ee76ea54ca5bad6f2526c7679", "sha256:4235f9d5ddcab0b8dbd723dca56ea2922b485ea00e1dafacf33b0c7e840b3d32",
"sha256:89705f45ce07b2dfa806ee84439ec67c5d9a0ef20154e0e475e2b2ed392a5b83", "sha256:5ced67f1e34e1a450cdb48eb53ca73b60aa0af21c46b9b35ac3e581cf9f00e31",
"sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77", "sha256:7360647ea04db2e7dff1648d1da825c8cf68dc5fbd80b8fb5b3ee9f068dcd21a",
"sha256:950bc22bb56ee6ff142a2cb9ee980b571dd0912b0334aa3fe0fe3788d860bea2", "sha256:8c13d72ed6af7fd2c8acbd95661cf9477f94e381fce0792c04981a8283b52917",
"sha256:a0c50db33c32594305b0ef9abc0cb7db13de7621d2cadf8392a1d9b3c437ef77", "sha256:988b47ac70d204aed01589ed342303da7c4d84b56c2f4c4b8b00deda123372bf",
"sha256:a0eb43a07386c3f1f1ebb4dc7aafb13f67188eab896e7397aa1ee95a9c884eb2", "sha256:995fc41ebda5a7a663a254a1dcac52638c3e847f48307b5416ee373da15075d7",
"sha256:aaa4213c862f0ef00022751161df35804127b78adf4a2755b9f991a507e425fd", "sha256:a36c7eb6152ba5467fb264d73844877be8b0847874d4822b7cf2d3c0cb8cdcb0",
"sha256:ac0c682111fbf404525dfc0f18a8b5f11be52657d4f96e9fcb75daf4f3984859", "sha256:aed4a9a7e3221b3e252c39d0bf794c438dc5453bc2963e8befe9d4cd324dff72",
"sha256:ad20d2eb875aaa1ea6d0f2916949f5c08a19c74d05b16ce6ebf6d24f2c9f75d1", "sha256:aef9aee84ec78af51107181d02fe8773b100b01c5dfde351184ad9223eab3698",
"sha256:b4afc542c0ac0db720cf516dd20c0846f71c248d2b3d21013aa0d4ef9c71ca25", "sha256:b0221ca5a9837e040ebf61f48899926b5783668b7807419e4adae8175a31f773",
"sha256:b8a3715b3c4e604bcc94c90a825cd7f5635417453b253499664f784fc4da0152", "sha256:b4d7679a08fea64573c969f6994a2631908bb2c0e69a7235648642f3d2e39a68",
"sha256:ba28584e6bca48c59eecbf7efb1576ca214b47f05194646b081717fa628dfddf", "sha256:c250a7ec489b652c892e4f0a5d122cc14c3780f9f643e1a326754aedf82d9a76",
"sha256:ba381aec3a5dc29634f20692349d73f2d21f17653bda1decf0b52b11d694541f", "sha256:ca86db5b561b894f9e5f115d6a159fff2a2570a652e07889d8a383b5fae66eb4",
"sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729", "sha256:cfc523edecddaef56f6740d7de1ce24a2fdf94fd5e704091856a201872e37f9f",
"sha256:c2507d796fca339c8fb03216364cca68d87e037c1f774977c8fc377627d01c71", "sha256:da113b70f6ec40e7d81b43d1b139b9db6a05727ab8be1ee559f3a69854a69d34",
"sha256:cec7e622ebc545dbb4564e483dd20e4e404da17ae07e06f3e780b2dacd5cee66", "sha256:f6fac64a38f6768e7bc7b035b9e10d8a538a9fadce06b983fb3e6fa55ac5f5ce",
"sha256:d14b140a4439d816e3b1229a4a525df917d6ea22a0771a2a78332273fd9528a4", "sha256:f8559617b1fcf59a9aedba2c9838b5b6aa211ffedecabca412b92a1ff75aac1a",
"sha256:d1b4ab59e02d9008efe10ceabd0b31e79519da6fb67f7d8e8977118832d0f449", "sha256:fbb42a541b1093385a2d8c7eec94d26d30437d0e77c1d25dae1dcc46741a385e"
"sha256:d5227b229005a696cc67676e24c214740efd90b148de5733419ac9aaba3773da",
"sha256:e1f57aa70d3f7cc6947fd88636a481638263ba04a742b4a37dd25c373e41491a",
"sha256:e74a55f6bad0e7d3968399deb50f61f4db1926acf4a6d83beaaa7df986f48b1c",
"sha256:e82aba2188b9ba309fd8e271702bd0d0fc9148ae3150532bbb474f4590039ffb",
"sha256:ee69dad2c7155756ad114c02db06002f4cded41132cc51378e57aad79cc8e4f4",
"sha256:f5ab93a2cb2d8338b1674be43b442a7f544a0971da062a5da774ed40587f18f5"
], ],
"index": "pypi", "index": "pypi",
"version": "==2.8.6" "version": "==2.9.1"
}, },
"pyasn1": { "pyasn1": {
"hashes": [ "hashes": [
@ -961,10 +963,10 @@
}, },
"python-dotenv": { "python-dotenv": {
"hashes": [ "hashes": [
"sha256:00aa34e92d992e9f8383730816359647f358f4a3be1ba45e5a5cefd27ee91544", "sha256:dd8fe852847f4fbfadabf6183ddd4c824a9651f02d51714fa075c95561959c7d",
"sha256:b1ae5e9643d5ed987fc57cc2583021e38db531946518130777734f9589b3141f" "sha256:effaac3c1e58d89b3ccb4d04a40dc7ad6e0275fda25fd75ae9d323e2465e202d"
], ],
"version": "==0.17.1" "version": "==0.18.0"
}, },
"pytz": { "pytz": {
"hashes": [ "hashes": [
@ -1538,11 +1540,11 @@
}, },
"gitpython": { "gitpython": {
"hashes": [ "hashes": [
"sha256:3283ae2fba31c913d857e12e5ba5f9a7772bbc064ae2bb09efafa71b0dd4939b", "sha256:b838a895977b45ab6f0cc926a9045c8d1c44e2b653c1fcc39fe91f42c6e8f05b",
"sha256:be27633e7509e58391f10207cd32b2a6cf5b908f92d9cd30da2e514e1137af61" "sha256:fce760879cd2aebd2991b3542876dc5c4a909b30c9d69dfc488e504a8db37ee8"
], ],
"markers": "python_version >= '3.4'", "markers": "python_version >= '3.6'",
"version": "==3.1.14" "version": "==3.1.18"
}, },
"idna": { "idna": {
"hashes": [ "hashes": [
@ -1560,11 +1562,11 @@
}, },
"isort": { "isort": {
"hashes": [ "hashes": [
"sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6", "sha256:83510593e07e433b77bd5bff0f6f607dbafa06d1a89022616f02d8b699cfcd56",
"sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d" "sha256:8e2c107091cfec7286bc0f68a547d0ba4c094d460b732075b6fba674f1035c0c"
], ],
"markers": "python_version >= '3.6' and python_version < '4'", "markers": "python_version < '4.0' and python_full_version >= '3.6.1'",
"version": "==5.8.0" "version": "==5.9.1"
}, },
"lazy-object-proxy": { "lazy-object-proxy": {
"hashes": [ "hashes": [

View File

@ -1,3 +1,3 @@
"""authentik""" """authentik"""
__version__ = "2021.6.1-rc6" __version__ = "2021.6.2"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH" ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"

View File

@ -3,23 +3,33 @@ from traceback import format_tb
from typing import Optional from typing import Optional
from django.http import HttpRequest from django.http import HttpRequest
from guardian.utils import get_anonymous_user
from authentik.core.models import User from authentik.core.models import PropertyMapping, User
from authentik.events.models import Event, EventAction from authentik.events.models import Event, EventAction
from authentik.lib.expression.evaluator import BaseEvaluator from authentik.lib.expression.evaluator import BaseEvaluator
from authentik.policies.types import PolicyRequest
class PropertyMappingEvaluator(BaseEvaluator): class PropertyMappingEvaluator(BaseEvaluator):
"""Custom Evalautor that adds some different context variables.""" """Custom Evalautor that adds some different context variables."""
def set_context( def set_context(
self, user: Optional[User], request: Optional[HttpRequest], **kwargs self,
user: Optional[User],
request: Optional[HttpRequest],
mapping: PropertyMapping,
**kwargs,
): ):
"""Update context with context from PropertyMapping's evaluate""" """Update context with context from PropertyMapping's evaluate"""
req = PolicyRequest(user=get_anonymous_user())
req.obj = mapping
if user: if user:
req.user = user
self._context["user"] = user self._context["user"] = user
if request: if request:
self._context["request"] = request req.http_request = request
self._context["request"] = req
self._context.update(**kwargs) self._context.update(**kwargs)
def handle_error(self, exc: Exception, expression_source: str): def handle_error(self, exc: Exception, expression_source: str):
@ -30,9 +40,8 @@ class PropertyMappingEvaluator(BaseEvaluator):
expression=expression_source, expression=expression_source,
message=error_string, message=error_string,
) )
if "user" in self._context:
event.set_user(self._context["user"])
if "request" in self._context: if "request" in self._context:
event.from_http(self._context["request"]) req: PolicyRequest = self._context["request"]
event.from_http(req.http_request, req.user)
return return
event.save() event.save()

View File

@ -6,6 +6,7 @@ from urllib.parse import urlencode
from uuid import uuid4 from uuid import uuid4
import django.db.models.options as options import django.db.models.options as options
from deepmerge import always_merger
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.contrib.auth.models import UserManager as DjangoUserManager from django.contrib.auth.models import UserManager as DjangoUserManager
@ -114,8 +115,8 @@ class User(GuardianUserMixin, AbstractUser):
including the users attributes""" including the users attributes"""
final_attributes = {} final_attributes = {}
for group in self.ak_groups.all().order_by("name"): for group in self.ak_groups.all().order_by("name"):
final_attributes.update(group.attributes) always_merger.merge(final_attributes, group.attributes)
final_attributes.update(self.attributes) always_merger.merge(final_attributes, self.attributes)
return final_attributes return final_attributes
@cached_property @cached_property
@ -142,21 +143,25 @@ class User(GuardianUserMixin, AbstractUser):
@property @property
def avatar(self) -> str: def avatar(self) -> str:
"""Get avatar, depending on authentik.avatar setting""" """Get avatar, depending on authentik.avatar setting"""
mode = CONFIG.raw.get("authentik").get("avatars") mode: str = CONFIG.y("avatars", "none")
if mode == "none": if mode == "none":
return DEFAULT_AVATAR return DEFAULT_AVATAR
# gravatar uses md5 for their URLs, so md5 can't be avoided
mail_hash = md5(self.email.encode("utf-8")).hexdigest() # nosec
if mode == "gravatar": if mode == "gravatar":
parameters = [ parameters = [
("s", "158"), ("s", "158"),
("r", "g"), ("r", "g"),
] ]
# gravatar uses md5 for their URLs, so md5 can't be avoided
mail_hash = md5(self.email.encode("utf-8")).hexdigest() # nosec
gravatar_url = ( gravatar_url = (
f"{GRAVATAR_URL}/avatar/{mail_hash}?{urlencode(parameters, doseq=True)}" f"{GRAVATAR_URL}/avatar/{mail_hash}?{urlencode(parameters, doseq=True)}"
) )
return escape(gravatar_url) return escape(gravatar_url)
raise ValueError(f"Invalid avatar mode {mode}") return mode % {
"username": self.username,
"mail_hash": mail_hash,
"upn": self.attributes.get("upn", ""),
}
class Meta: class Meta:
@ -460,7 +465,7 @@ class PropertyMapping(SerializerModel, ManagedModel):
from authentik.core.expression import PropertyMappingEvaluator from authentik.core.expression import PropertyMappingEvaluator
evaluator = PropertyMappingEvaluator() evaluator = PropertyMappingEvaluator()
evaluator.set_context(user, request, **kwargs) evaluator.set_context(user, request, self, **kwargs)
try: try:
return evaluator.evaluate(self.expression) return evaluator.evaluate(self.expression)
except (ValueError, SyntaxError) as exc: except (ValueError, SyntaxError) as exc:
@ -494,8 +499,12 @@ class AuthenticatedSession(ExpiringModel):
last_used = models.DateTimeField(auto_now=True) last_used = models.DateTimeField(auto_now=True)
@staticmethod @staticmethod
def from_request(request: HttpRequest, user: User) -> "AuthenticatedSession": def from_request(
request: HttpRequest, user: User
) -> Optional["AuthenticatedSession"]:
"""Create a new session from a http request""" """Create a new session from a http request"""
if not hasattr(request, "session") or not request.session.session_key:
return None
return AuthenticatedSession( return AuthenticatedSession(
session_key=request.session.session_key, session_key=request.session.session_key,
user=user, user=user,

View File

@ -49,7 +49,9 @@ def user_logged_in_session(sender, request: HttpRequest, user: "User", **_):
"""Create an AuthenticatedSession from request""" """Create an AuthenticatedSession from request"""
from authentik.core.models import AuthenticatedSession from authentik.core.models import AuthenticatedSession
AuthenticatedSession.from_request(request, user).save() session = AuthenticatedSession.from_request(request, user)
if session:
session.save()
@receiver(user_logged_out) @receiver(user_logged_out)

View File

@ -183,6 +183,8 @@ class SourceFlowManager:
# pylint: disable=unused-argument # pylint: disable=unused-argument
def get_stages_to_append(self, flow: Flow) -> list[Stage]: def get_stages_to_append(self, flow: Flow) -> list[Stage]:
"""Hook to override stages which are appended to the flow""" """Hook to override stages which are appended to the flow"""
if not self.source.enrollment_flow:
return []
if flow.slug == self.source.enrollment_flow.slug: if flow.slug == self.source.enrollment_flow.slug:
return [ return [
in_memory_stage(PostUserEnrollmentStage), in_memory_stage(PostUserEnrollmentStage),

View File

@ -11,6 +11,11 @@
{% block head %} {% block head %}
<script src="{% static 'dist/FlowInterface.js' %}?v={{ ak_version }}" type="module"></script> <script src="{% static 'dist/FlowInterface.js' %}?v={{ ak_version }}" type="module"></script>
<style>
.pf-c-background-image::before {
background-image: url("{{ flow.background_url }}");
}
</style>
{% endblock %} {% endblock %}
{% block body %} {% block body %}

View File

@ -7,6 +7,14 @@
<link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly.min.css' %}?v={{ ak_version }}"> <link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly.min.css' %}?v={{ ak_version }}">
{% endblock %} {% endblock %}
{% block head %}
<style>
.pf-c-background-image::before {
background-image: url("/static/dist/assets/images/flow_background.jpg");
}
</style>
{% endblock %}
{% block body %} {% block body %}
<div class="pf-c-background-image"> <div class="pf-c-background-image">
<svg xmlns="http://www.w3.org/2000/svg" class="pf-c-background-image__filter" width="0" height="0"> <svg xmlns="http://www.w3.org/2000/svg" class="pf-c-background-image__filter" width="0" height="0">

View File

@ -55,11 +55,16 @@ class CertificateKeyPair(CreatedUpdatedModel):
def private_key(self) -> Optional[RSAPrivateKey]: def private_key(self) -> Optional[RSAPrivateKey]:
"""Get python cryptography PrivateKey instance""" """Get python cryptography PrivateKey instance"""
if not self._private_key and self._private_key != "": if not self._private_key and self._private_key != "":
try:
self._private_key = load_pem_private_key( self._private_key = load_pem_private_key(
str.encode("\n".join([x.strip() for x in self.key_data.split("\n")])), str.encode(
"\n".join([x.strip() for x in self.key_data.split("\n")])
),
password=None, password=None,
backend=default_backend(), backend=default_backend(),
) )
except ValueError:
return None
return self._private_key return self._private_key
@property @property

View File

@ -14,6 +14,7 @@ from authentik.events.models import cleanse_dict
from authentik.flows.exceptions import EmptyFlowException, FlowNonApplicableException from authentik.flows.exceptions import EmptyFlowException, FlowNonApplicableException
from authentik.flows.markers import ReevaluateMarker, StageMarker from authentik.flows.markers import ReevaluateMarker, StageMarker
from authentik.flows.models import Flow, FlowStageBinding, Stage from authentik.flows.models import Flow, FlowStageBinding, Stage
from authentik.lib.config import CONFIG
from authentik.policies.engine import PolicyEngine from authentik.policies.engine import PolicyEngine
from authentik.root.monitoring import UpdatingGauge from authentik.root.monitoring import UpdatingGauge
@ -33,6 +34,7 @@ HIST_FLOWS_PLAN_TIME = Histogram(
"Duration to build a plan for a flow", "Duration to build a plan for a flow",
["flow_slug"], ["flow_slug"],
) )
CACHE_TIMEOUT = int(CONFIG.y("redis.cache_timeout_flows"))
def cache_key(flow: Flow, user: Optional[User] = None) -> str: def cache_key(flow: Flow, user: Optional[User] = None) -> str:
@ -157,7 +159,7 @@ class FlowPlanner:
"f(plan): building plan", "f(plan): building plan",
) )
plan = self._build_plan(user, request, default_context) plan = self._build_plan(user, request, default_context)
cache.set(cache_key(self.flow, user), plan) cache.set(cache_key(self.flow, user), plan, CACHE_TIMEOUT)
GAUGE_FLOWS_CACHED.update() GAUGE_FLOWS_CACHED.update()
if not plan.stages and not self.allow_empty_flows: if not plan.stages and not self.allow_empty_flows:
raise EmptyFlowException() raise EmptyFlowException()

View File

@ -18,27 +18,11 @@ from authentik.flows.challenge import (
) )
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.views import FlowExecutorView from authentik.flows.views import FlowExecutorView
from authentik.lib.sentry import SentryIgnoredException
PLAN_CONTEXT_PENDING_USER_IDENTIFIER = "pending_user_identifier" PLAN_CONTEXT_PENDING_USER_IDENTIFIER = "pending_user_identifier"
LOGGER = get_logger() LOGGER = get_logger()
class InvalidChallengeError(SentryIgnoredException):
"""Error raised when a challenge from a stage is not valid"""
def __init__(self, errors, stage_view: View, challenge: Challenge) -> None:
super().__init__()
self.errors = errors
self.stage_view = stage_view
self.challenge = challenge
def __str__(self) -> str:
return (
f"Invalid challenge from {self.stage_view}: {self.errors}\n{self.challenge}"
)
class StageView(View): class StageView(View):
"""Abstract Stage, inherits TemplateView but can be combined with FormView""" """Abstract Stage, inherits TemplateView but can be combined with FormView"""

View File

@ -44,6 +44,7 @@ from authentik.flows.planner import (
FlowPlan, FlowPlan,
FlowPlanner, FlowPlanner,
) )
from authentik.lib.sentry import SentryIgnoredException
from authentik.lib.utils.reflection import all_subclasses, class_to_path from authentik.lib.utils.reflection import all_subclasses, class_to_path
from authentik.lib.utils.urls import is_url_absolute, redirect_with_qs from authentik.lib.utils.urls import is_url_absolute, redirect_with_qs
from authentik.tenants.models import Tenant from authentik.tenants.models import Tenant
@ -93,6 +94,10 @@ def challenge_response_types():
return Inner() return Inner()
class InvalidStageError(SentryIgnoredException):
"""Error raised when a challenge from a stage is not valid"""
@method_decorator(xframe_options_sameorigin, name="dispatch") @method_decorator(xframe_options_sameorigin, name="dispatch")
class FlowExecutorView(APIView): class FlowExecutorView(APIView):
"""Stage 1 Flow executor, passing requests to Stage Views""" """Stage 1 Flow executor, passing requests to Stage Views"""
@ -164,12 +169,19 @@ class FlowExecutorView(APIView):
current_stage=self.current_stage, current_stage=self.current_stage,
flow_slug=self.flow.slug, flow_slug=self.flow.slug,
) )
try:
stage_cls = self.current_stage.type stage_cls = self.current_stage.type
except NotImplementedError as exc:
self._logger.debug("Error getting stage type", exc=exc)
return self.stage_invalid()
self.current_stage_view = stage_cls(self) self.current_stage_view = stage_cls(self)
self.current_stage_view.args = self.args self.current_stage_view.args = self.args
self.current_stage_view.kwargs = self.kwargs self.current_stage_view.kwargs = self.kwargs
self.current_stage_view.request = request self.current_stage_view.request = request
try:
return super().dispatch(request) return super().dispatch(request)
except InvalidStageError as exc:
return self.stage_invalid(str(exc))
@extend_schema( @extend_schema(
responses={ responses={
@ -353,8 +365,11 @@ class FlowErrorResponse(TemplateResponse):
context = {} context = {}
context["error"] = self.error context["error"] = self.error
if self._request.user and self._request.user.is_authenticated: if self._request.user and self._request.user.is_authenticated:
if self._request.user.is_superuser or self._request.user.attributes.get( if (
self._request.user.is_superuser
or self._request.user.group_attributes().get(
USER_ATTRIBUTE_DEBUG, False USER_ATTRIBUTE_DEBUG, False
)
): ):
context["tb"] = "".join(format_tb(self.error.__traceback__)) context["tb"] = "".join(format_tb(self.error.__traceback__))
return context return context

View File

@ -62,7 +62,7 @@ class ConfigLoader:
output.update(kwargs) output.update(kwargs)
print(dumps(output)) print(dumps(output))
def update(self, root, updatee): def update(self, root: dict[str, Any], updatee: dict[str, Any]) -> dict[str, Any]:
"""Recursively update dictionary""" """Recursively update dictionary"""
for key, value in updatee.items(): for key, value in updatee.items():
if isinstance(value, Mapping): if isinstance(value, Mapping):
@ -73,7 +73,7 @@ class ConfigLoader:
root[key] = value root[key] = value
return root return root
def parse_uri(self, value): def parse_uri(self, value: str) -> str:
"""Parse string values which start with a URI""" """Parse string values which start with a URI"""
url = urlparse(value) url = urlparse(value)
if url.scheme == "env": if url.scheme == "env":
@ -99,7 +99,10 @@ class ConfigLoader:
raise ImproperlyConfigured from exc raise ImproperlyConfigured from exc
except PermissionError as exc: except PermissionError as exc:
self._log( self._log(
"warning", "Permission denied while reading file", path=path, error=exc "warning",
"Permission denied while reading file",
path=path,
error=str(exc),
) )
def update_from_dict(self, update: dict): def update_from_dict(self, update: dict):

View File

@ -9,6 +9,7 @@ postgresql:
web: web:
listen: 0.0.0.0:9000 listen: 0.0.0.0:9000
listen_tls: 0.0.0.0:9443 listen_tls: 0.0.0.0:9443
load_local_files: false
redis: redis:
host: localhost host: localhost
@ -16,6 +17,10 @@ redis:
cache_db: 0 cache_db: 0
message_queue_db: 1 message_queue_db: 1
ws_db: 2 ws_db: 2
cache_timeout: 300
cache_timeout_flows: 300
cache_timeout_policies: 300
cache_timeout_reputation: 300
debug: false debug: false
@ -45,10 +50,10 @@ outposts:
# %(build_hash)s: Build hash if you're running a beta version # %(build_hash)s: Build hash if you're running a beta version
docker_image_base: "ghcr.io/goauthentik/%(type)s:%(version)s" docker_image_base: "ghcr.io/goauthentik/%(type)s:%(version)s"
authentik: avatars: env://AUTHENTIK_AUTHENTIK__AVATARS?gravatar
avatars: gravatar # gravatar or none
geoip: "./GeoLite2-City.mmdb" geoip: "./GeoLite2-City.mmdb"
# Optionally add links to the footer on the login page
# Can't currently be configured via environment variables, only yaml
footer_links: footer_links:
- name: Documentation - name: Documentation
href: https://goauthentik.io/docs/ href: https://goauthentik.io/docs/

View File

@ -3,6 +3,7 @@ import re
from textwrap import indent from textwrap import indent
from typing import Any, Iterable, Optional from typing import Any, Iterable, Optional
from django.core.exceptions import FieldError
from requests import Session from requests import Session
from rest_framework.serializers import ValidationError from rest_framework.serializers import ValidationError
from sentry_sdk.hub import Hub from sentry_sdk.hub import Hub
@ -29,10 +30,10 @@ class BaseEvaluator:
# update website/docs/expressions/_objects.md # update website/docs/expressions/_objects.md
# update website/docs/expressions/_functions.md # update website/docs/expressions/_functions.md
self._globals = { self._globals = {
"regex_match": BaseEvaluator.expr_filter_regex_match, "regex_match": BaseEvaluator.expr_regex_match,
"regex_replace": BaseEvaluator.expr_filter_regex_replace, "regex_replace": BaseEvaluator.expr_regex_replace,
"ak_is_group_member": BaseEvaluator.expr_func_is_group_member, "ak_is_group_member": BaseEvaluator.expr_is_group_member,
"ak_user_by": BaseEvaluator.expr_func_user_by, "ak_user_by": BaseEvaluator.expr_user_by,
"ak_logger": get_logger(), "ak_logger": get_logger(),
"requests": Session(), "requests": Session(),
} }
@ -40,25 +41,28 @@ class BaseEvaluator:
self._filename = "BaseEvalautor" self._filename = "BaseEvalautor"
@staticmethod @staticmethod
def expr_filter_regex_match(value: Any, regex: str) -> bool: def expr_regex_match(value: Any, regex: str) -> bool:
"""Expression Filter to run re.search""" """Expression Filter to run re.search"""
return re.search(regex, value) is None return re.search(regex, value) is not None
@staticmethod @staticmethod
def expr_filter_regex_replace(value: Any, regex: str, repl: str) -> str: def expr_regex_replace(value: Any, regex: str, repl: str) -> str:
"""Expression Filter to run re.sub""" """Expression Filter to run re.sub"""
return re.sub(regex, repl, value) return re.sub(regex, repl, value)
@staticmethod @staticmethod
def expr_func_user_by(**filters) -> Optional[User]: def expr_user_by(**filters) -> Optional[User]:
"""Get user by filters""" """Get user by filters"""
try:
users = User.objects.filter(**filters) users = User.objects.filter(**filters)
if users: if users:
return users.first() return users.first()
return None return None
except FieldError:
return None
@staticmethod @staticmethod
def expr_func_is_group_member(user: User, **group_filters) -> bool: def expr_is_group_member(user: User, **group_filters) -> bool:
"""Check if `user` is member of group with name `group_name`""" """Check if `user` is member of group with name `group_name`"""
return user.ak_groups.filter(**group_filters).exists() return user.ak_groups.filter(**group_filters).exists()

View File

@ -0,0 +1,61 @@
"""Test config loader"""
from os import chmod, environ, unlink, write
from tempfile import mkstemp
from django.conf import ImproperlyConfigured
from django.test import TestCase
from authentik.lib.config import ENV_PREFIX, ConfigLoader
class TestConfig(TestCase):
"""Test config loader"""
def test_env(self):
"""Test simple instance"""
config = ConfigLoader()
environ[ENV_PREFIX + "_test__test"] = "bar"
config.update_from_env()
self.assertEqual(config.y("test.test"), "bar")
def test_patch(self):
"""Test patch decorator"""
config = ConfigLoader()
config.y_set("foo.bar", "bar")
self.assertEqual(config.y("foo.bar"), "bar")
with config.patch("foo.bar", "baz"):
self.assertEqual(config.y("foo.bar"), "baz")
self.assertEqual(config.y("foo.bar"), "bar")
def test_uri_env(self):
"""Test URI parsing (environment)"""
config = ConfigLoader()
environ["foo"] = "bar"
self.assertEqual(config.parse_uri("env://foo"), "bar")
self.assertEqual(config.parse_uri("env://fo?bar"), "bar")
def test_uri_file(self):
"""Test URI parsing (file load)"""
config = ConfigLoader()
file, file_name = mkstemp()
write(file, "foo".encode())
_, file2_name = mkstemp()
chmod(file2_name, 0o000) # Remove all permissions so we can't read the file
self.assertEqual(config.parse_uri(f"file://{file_name}"), "foo")
self.assertEqual(config.parse_uri(f"file://{file2_name}?def"), "def")
unlink(file_name)
unlink(file2_name)
def test_file_update(self):
"""Test update_from_file"""
config = ConfigLoader()
file, file_name = mkstemp()
write(file, "{".encode())
file2, file2_name = mkstemp()
write(file2, "{".encode())
chmod(file2_name, 0o000) # Remove all permissions so we can't read the file
with self.assertRaises(ImproperlyConfigured):
config.update_from_file(file_name)
config.update_from_file(file2_name)
unlink(file_name)
unlink(file2_name)

View File

@ -0,0 +1,32 @@
"""Test Evaluator base functions"""
from django.test import TestCase
from authentik.core.models import User
from authentik.lib.expression.evaluator import BaseEvaluator
class TestEvaluator(TestCase):
"""Test Evaluator base functions"""
def test_regex_match(self):
"""Test expr_regex_match"""
self.assertFalse(BaseEvaluator.expr_regex_match("foo", "bar"))
self.assertTrue(BaseEvaluator.expr_regex_match("foo", "foo"))
def test_regex_replace(self):
"""Test expr_regex_replace"""
self.assertEqual(BaseEvaluator.expr_regex_replace("foo", "o", "a"), "faa")
def test_user_by(self):
"""Test expr_user_by"""
self.assertIsNotNone(BaseEvaluator.expr_user_by(username="akadmin"))
self.assertIsNone(BaseEvaluator.expr_user_by(username="bar"))
self.assertIsNone(BaseEvaluator.expr_user_by(foo="bar"))
def test_is_group_member(self):
"""Test expr_is_group_member"""
self.assertFalse(
BaseEvaluator.expr_is_group_member(
User.objects.get(username="akadmin"), name="test"
)
)

View File

@ -33,7 +33,7 @@ def _get_outpost_override_ip(request: HttpRequest) -> Optional[str]:
return None return None
if OUTPOST_REMOTE_IP_HEADER not in request.META: if OUTPOST_REMOTE_IP_HEADER not in request.META:
return None return None
if request.user.attributes.get(USER_ATTRIBUTE_CAN_OVERRIDE_IP, False): if request.user.group_attributes().get(USER_ATTRIBUTE_CAN_OVERRIDE_IP, False):
return None return None
return request.META[OUTPOST_REMOTE_IP_HEADER] return request.META[OUTPOST_REMOTE_IP_HEADER]

View File

@ -67,11 +67,6 @@ class OutpostConsumer(AuthJsonConsumer):
self.accept() self.accept()
self.outpost = outpost.first() self.outpost = outpost.first()
self.last_uid = self.channel_name self.last_uid = self.channel_name
LOGGER.debug(
"added outpost instace to cache",
outpost=self.outpost,
channel_name=self.channel_name,
)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def disconnect(self, close_code): def disconnect(self, close_code):
@ -108,6 +103,11 @@ class OutpostConsumer(AuthJsonConsumer):
outpost=self.outpost.name, outpost=self.outpost.name,
uid=self.last_uid, uid=self.last_uid,
).inc() ).inc()
LOGGER.debug(
"added outpost instace to cache",
outpost=self.outpost,
instance_uuid=self.last_uid,
)
self.first_msg = True self.first_msg = True
if msg.instruction == WebsocketMessageInstruction.HELLO: if msg.instruction == WebsocketMessageInstruction.HELLO:

View File

@ -66,7 +66,7 @@ class DockerController(BaseController):
"name": container_name, "name": container_name,
"detach": True, "detach": True,
"ports": { "ports": {
f"{port.port}/{port.protocol.lower()}": port.inner_port or port.port f"{port.inner_port or port.port}/{port.protocol.lower()}": port.port
for port in self.deployment_ports for port in self.deployment_ports
}, },
"environment": self._get_env(), "environment": self._get_env(),

View File

@ -37,7 +37,9 @@ class AccessDeniedResponse(TemplateResponse):
if self._request.user and self._request.user.is_authenticated: if self._request.user and self._request.user.is_authenticated:
if ( if (
self._request.user.is_superuser self._request.user.is_superuser
or self._request.user.attributes.get(USER_ATTRIBUTE_DEBUG, False) or self._request.user.group_attributes().get(
USER_ATTRIBUTE_DEBUG, False
)
): ):
context["policy_result"] = self.policy_result context["policy_result"] = self.policy_result
return context return context

View File

@ -10,6 +10,7 @@ from sentry_sdk.tracing import Span
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.events.models import Event, EventAction from authentik.events.models import Event, EventAction
from authentik.lib.config import CONFIG
from authentik.lib.utils.errors import exception_to_string from authentik.lib.utils.errors import exception_to_string
from authentik.policies.exceptions import PolicyException from authentik.policies.exceptions import PolicyException
from authentik.policies.models import PolicyBinding from authentik.policies.models import PolicyBinding
@ -18,6 +19,7 @@ from authentik.policies.types import PolicyRequest, PolicyResult
LOGGER = get_logger() LOGGER = get_logger()
FORK_CTX = get_context("fork") FORK_CTX = get_context("fork")
CACHE_TIMEOUT = int(CONFIG.y("redis.cache_timeout_policies"))
PROCESS_CLASS = FORK_CTX.Process PROCESS_CLASS = FORK_CTX.Process
HIST_POLICIES_EXECUTION_TIME = Histogram( HIST_POLICIES_EXECUTION_TIME = Histogram(
"authentik_policies_execution_time", "authentik_policies_execution_time",
@ -114,7 +116,7 @@ class PolicyProcess(PROCESS_CLASS):
policy_result.source_binding = self.binding policy_result.source_binding = self.binding
if not self.request.debug: if not self.request.debug:
key = cache_key(self.binding, self.request) key = cache_key(self.binding, self.request)
cache.set(key, policy_result) cache.set(key, policy_result, CACHE_TIMEOUT)
LOGGER.debug( LOGGER.debug(
"P_ENG(proc): finished and cached ", "P_ENG(proc): finished and cached ",
policy=self.binding.policy, policy=self.binding.policy,

View File

@ -5,6 +5,7 @@ from django.dispatch import receiver
from django.http import HttpRequest from django.http import HttpRequest
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.lib.config import CONFIG
from authentik.lib.utils.http import get_client_ip from authentik.lib.utils.http import get_client_ip
from authentik.policies.reputation.models import ( from authentik.policies.reputation.models import (
CACHE_KEY_IP_PREFIX, CACHE_KEY_IP_PREFIX,
@ -13,6 +14,7 @@ from authentik.policies.reputation.models import (
from authentik.stages.identification.signals import identification_failed from authentik.stages.identification.signals import identification_failed
LOGGER = get_logger() LOGGER = get_logger()
CACHE_TIMEOUT = int(CONFIG.y("redis.cache_timeout_reputation"))
def update_score(request: HttpRequest, username: str, amount: int): def update_score(request: HttpRequest, username: str, amount: int):
@ -20,10 +22,10 @@ def update_score(request: HttpRequest, username: str, amount: int):
remote_ip = get_client_ip(request) remote_ip = get_client_ip(request)
# We only update the cache here, as its faster than writing to the DB # We only update the cache here, as its faster than writing to the DB
cache.get_or_set(CACHE_KEY_IP_PREFIX + remote_ip, 0) cache.get_or_set(CACHE_KEY_IP_PREFIX + remote_ip, 0, CACHE_TIMEOUT)
cache.incr(CACHE_KEY_IP_PREFIX + remote_ip, amount) cache.incr(CACHE_KEY_IP_PREFIX + remote_ip, amount)
cache.get_or_set(CACHE_KEY_USER_PREFIX + username, 0) cache.get_or_set(CACHE_KEY_USER_PREFIX + username, 0, CACHE_TIMEOUT)
cache.incr(CACHE_KEY_USER_PREFIX + username, amount) cache.incr(CACHE_KEY_USER_PREFIX + username, amount)
LOGGER.debug("Updated score", amount=amount, for_user=username, for_ip=remote_ip) LOGGER.debug("Updated score", amount=amount, for_user=username, for_ip=remote_ip)

View File

@ -105,6 +105,7 @@ class PolicyAccessView(AccessMixin, View):
policy_engine = PolicyEngine( policy_engine = PolicyEngine(
self.application, user or self.request.user, self.request self.application, user or self.request.user, self.request
) )
policy_engine.use_cache = False
policy_engine.build() policy_engine.build()
result = policy_engine.result result = policy_engine.result
LOGGER.debug( LOGGER.debug(

View File

@ -9,7 +9,7 @@ return {}
""" """
SCOPE_EMAIL_EXPRESSION = """ SCOPE_EMAIL_EXPRESSION = """
return { return {
"email": user.email, "email": request.user.email,
"email_verified": True "email_verified": True
} }
""" """
@ -17,14 +17,14 @@ SCOPE_PROFILE_EXPRESSION = """
return { return {
# Because authentik only saves the user's full name, and has no concept of first and last names, # Because authentik only saves the user's full name, and has no concept of first and last names,
# the full name is used as given name. # the full name is used as given name.
# You can override this behaviour in custom mappings, i.e. `user.name.split(" ")` # You can override this behaviour in custom mappings, i.e. `request.user.name.split(" ")`
"name": user.name, "name": request.user.name,
"given_name": user.name, "given_name": request.user.name,
"family_name": "", "family_name": "",
"preferred_username": user.username, "preferred_username": request.user.username,
"nickname": user.username, "nickname": request.user.username,
# groups is not part of the official userinfo schema, but is a quasi-standard # groups is not part of the official userinfo schema, but is a quasi-standard
"groups": [group.name for group in user.ak_groups.all()], "groups": [group.name for group in request.user.ak_groups.all()],
} }
""" """

View File

@ -8,7 +8,7 @@ SCOPE_AK_PROXY_EXPRESSION = """
# which are used for example for the HTTP-Basic Authentication mapping. # which are used for example for the HTTP-Basic Authentication mapping.
return { return {
"ak_proxy": { "ak_proxy": {
"user_attributes": user.group_attributes() "user_attributes": request.user.group_attributes()
} }
}""" }"""

View File

@ -3,7 +3,7 @@ from authentik.managed.manager import EnsureExists, ObjectManager
from authentik.providers.saml.models import SAMLPropertyMapping from authentik.providers.saml.models import SAMLPropertyMapping
GROUP_EXPRESSION = """ GROUP_EXPRESSION = """
for group in user.ak_groups.all(): for group in request.user.ak_groups.all():
yield group.name yield group.name
""" """
@ -18,7 +18,7 @@ class SAMLProviderManager(ObjectManager):
"goauthentik.io/providers/saml/upn", "goauthentik.io/providers/saml/upn",
name="authentik default SAML Mapping: UPN", name="authentik default SAML Mapping: UPN",
saml_name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn", saml_name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn",
expression="return user.attributes.get('upn', user.email)", expression="return request.user.attributes.get('upn', request.user.email)",
friendly_name="", friendly_name="",
), ),
EnsureExists( EnsureExists(
@ -26,7 +26,7 @@ class SAMLProviderManager(ObjectManager):
"goauthentik.io/providers/saml/name", "goauthentik.io/providers/saml/name",
name="authentik default SAML Mapping: Name", name="authentik default SAML Mapping: Name",
saml_name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", saml_name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
expression="return user.name", expression="return request.user.name",
friendly_name="", friendly_name="",
), ),
EnsureExists( EnsureExists(
@ -34,7 +34,7 @@ class SAMLProviderManager(ObjectManager):
"goauthentik.io/providers/saml/email", "goauthentik.io/providers/saml/email",
name="authentik default SAML Mapping: Email", name="authentik default SAML Mapping: Email",
saml_name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", saml_name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
expression="return user.email", expression="return request.user.email",
friendly_name="", friendly_name="",
), ),
EnsureExists( EnsureExists(
@ -42,7 +42,7 @@ class SAMLProviderManager(ObjectManager):
"goauthentik.io/providers/saml/username", "goauthentik.io/providers/saml/username",
name="authentik default SAML Mapping: Username", name="authentik default SAML Mapping: Username",
saml_name="http://schemas.goauthentik.io/2021/02/saml/username", saml_name="http://schemas.goauthentik.io/2021/02/saml/username",
expression="return user.username", expression="return request.user.username",
friendly_name="", friendly_name="",
), ),
EnsureExists( EnsureExists(
@ -50,7 +50,7 @@ class SAMLProviderManager(ObjectManager):
"goauthentik.io/providers/saml/uid", "goauthentik.io/providers/saml/uid",
name="authentik default SAML Mapping: User ID", name="authentik default SAML Mapping: User ID",
saml_name="http://schemas.goauthentik.io/2021/02/saml/uid", saml_name="http://schemas.goauthentik.io/2021/02/saml/uid",
expression="return user.pk", expression="return request.user.pk",
friendly_name="", friendly_name="",
), ),
EnsureExists( EnsureExists(
@ -68,7 +68,7 @@ class SAMLProviderManager(ObjectManager):
saml_name=( saml_name=(
"http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname" "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"
), ),
expression="return user.username", expression="return request.user.username",
friendly_name="", friendly_name="",
), ),
] ]

View File

@ -24,6 +24,7 @@ from authentik.sources.saml.processors.constants import (
SAML_NAME_ID_FORMAT_EMAIL, SAML_NAME_ID_FORMAT_EMAIL,
SAML_NAME_ID_FORMAT_PERSISTENT, SAML_NAME_ID_FORMAT_PERSISTENT,
SAML_NAME_ID_FORMAT_TRANSIENT, SAML_NAME_ID_FORMAT_TRANSIENT,
SAML_NAME_ID_FORMAT_UNSPECIFIED,
SAML_NAME_ID_FORMAT_WINDOWS, SAML_NAME_ID_FORMAT_WINDOWS,
SAML_NAME_ID_FORMAT_X509, SAML_NAME_ID_FORMAT_X509,
SIGN_ALGORITHM_TRANSFORM_MAP, SIGN_ALGORITHM_TRANSFORM_MAP,
@ -165,7 +166,10 @@ class AssertionProcessor:
if name_id.attrib["Format"] == SAML_NAME_ID_FORMAT_EMAIL: if name_id.attrib["Format"] == SAML_NAME_ID_FORMAT_EMAIL:
name_id.text = self.http_request.user.email name_id.text = self.http_request.user.email
return name_id return name_id
if name_id.attrib["Format"] == SAML_NAME_ID_FORMAT_PERSISTENT: if name_id.attrib["Format"] in [
SAML_NAME_ID_FORMAT_PERSISTENT,
SAML_NAME_ID_FORMAT_UNSPECIFIED,
]:
name_id.text = persistent name_id.text = persistent
return name_id return name_id
if name_id.attrib["Format"] == SAML_NAME_ID_FORMAT_X509: if name_id.attrib["Format"] == SAML_NAME_ID_FORMAT_X509:
@ -180,7 +184,7 @@ class AssertionProcessor:
return name_id return name_id
if name_id.attrib["Format"] == SAML_NAME_ID_FORMAT_TRANSIENT: if name_id.attrib["Format"] == SAML_NAME_ID_FORMAT_TRANSIENT:
# Use the hash of the user's session, which changes every session # Use the hash of the user's session, which changes every session
session_key: str = self.http_request.user.session.session_key session_key: str = self.http_request.session.session_key
name_id.text = sha256(session_key.encode()).hexdigest() name_id.text = sha256(session_key.encode()).hexdigest()
return name_id return name_id
raise UnsupportedNameIDFormat( raise UnsupportedNameIDFormat(

View File

@ -20,10 +20,11 @@ from authentik.sources.saml.processors.constants import (
RSA_SHA256, RSA_SHA256,
RSA_SHA384, RSA_SHA384,
RSA_SHA512, RSA_SHA512,
SAML_NAME_ID_FORMAT_EMAIL, SAML_NAME_ID_FORMAT_UNSPECIFIED,
) )
LOGGER = get_logger() LOGGER = get_logger()
ERROR_CANNOT_DECODE_REQUEST = "Cannot decode SAML request."
ERROR_SIGNATURE_REQUIRED_BUT_ABSENT = ( ERROR_SIGNATURE_REQUIRED_BUT_ABSENT = (
"Verification Certificate configured, but request is not signed." "Verification Certificate configured, but request is not signed."
) )
@ -42,7 +43,7 @@ class AuthNRequest:
relay_state: Optional[str] = None relay_state: Optional[str] = None
name_id_policy: str = SAML_NAME_ID_FORMAT_EMAIL name_id_policy: str = SAML_NAME_ID_FORMAT_UNSPECIFIED
class AuthNRequestParser: class AuthNRequestParser:
@ -69,16 +70,21 @@ class AuthNRequestParser:
auth_n_request = AuthNRequest(id=root.attrib["ID"], relay_state=relay_state) auth_n_request = AuthNRequest(id=root.attrib["ID"], relay_state=relay_state)
# Check if AuthnRequest has a NameID Policy object # Check if AuthnRequest has a NameID Policy object
name_id_policies = root.findall(f"{{{NS_SAML_PROTOCOL}}}:NameIDPolicy") name_id_policies = root.findall(f"{{{NS_SAML_PROTOCOL}}}NameIDPolicy")
if len(name_id_policies) > 0: if len(name_id_policies) > 0:
name_id_policy = name_id_policies[0] name_id_policy = name_id_policies[0]
auth_n_request.name_id_policy = name_id_policy.attrib["Format"] auth_n_request.name_id_policy = name_id_policy.attrib.get(
"Format", SAML_NAME_ID_FORMAT_UNSPECIFIED
)
return auth_n_request return auth_n_request
def parse(self, saml_request: str, relay_state: Optional[str]) -> AuthNRequest: def parse(self, saml_request: str, relay_state: Optional[str]) -> AuthNRequest:
"""Validate and parse raw request with enveloped signautre.""" """Validate and parse raw request with enveloped signautre."""
try:
decoded_xml = b64decode(saml_request.encode()).decode() decoded_xml = b64decode(saml_request.encode()).decode()
except UnicodeDecodeError:
raise CannotHandleAssertion(ERROR_CANNOT_DECODE_REQUEST)
verifier = self.provider.verification_kp verifier = self.provider.verification_kp
@ -121,7 +127,10 @@ class AuthNRequestParser:
sig_alg: Optional[str] = None, sig_alg: Optional[str] = None,
) -> AuthNRequest: ) -> AuthNRequest:
"""Validate and parse raw request with detached signature""" """Validate and parse raw request with detached signature"""
try:
decoded_xml = decode_base64_and_inflate(saml_request) decoded_xml = decode_base64_and_inflate(saml_request)
except UnicodeDecodeError:
raise CannotHandleAssertion(ERROR_CANNOT_DECODE_REQUEST)
verifier = self.provider.verification_kp verifier = self.provider.verification_kp

View File

@ -14,7 +14,7 @@ from authentik.providers.saml.processors.assertion import AssertionProcessor
from authentik.providers.saml.processors.request_parser import AuthNRequestParser from authentik.providers.saml.processors.request_parser import AuthNRequestParser
from authentik.sources.saml.exceptions import MismatchedRequestID from authentik.sources.saml.exceptions import MismatchedRequestID
from authentik.sources.saml.models import SAMLSource from authentik.sources.saml.models import SAMLSource
from authentik.sources.saml.processors.constants import SAML_NAME_ID_FORMAT_EMAIL from authentik.sources.saml.processors.constants import SAML_NAME_ID_FORMAT_UNSPECIFIED
from authentik.sources.saml.processors.request import ( from authentik.sources.saml.processors.request import (
SESSION_REQUEST_ID, SESSION_REQUEST_ID,
RequestProcessor, RequestProcessor,
@ -206,5 +206,5 @@ class TestAuthNRequest(TestCase):
REDIRECT_REQUEST, REDIRECT_RELAY_STATE, REDIRECT_SIGNATURE, REDIRECT_SIG_ALG REDIRECT_REQUEST, REDIRECT_RELAY_STATE, REDIRECT_SIGNATURE, REDIRECT_SIG_ALG
) )
self.assertEqual(parsed_request.id, "_dcf55fcd27a887e60a7ef9ee6fd3adab") self.assertEqual(parsed_request.id, "_dcf55fcd27a887e60a7ef9ee6fd3adab")
self.assertEqual(parsed_request.name_id_policy, SAML_NAME_ID_FORMAT_EMAIL) self.assertEqual(parsed_request.name_id_policy, SAML_NAME_ID_FORMAT_UNSPECIFIED)
self.assertEqual(parsed_request.relay_state, REDIRECT_RELAY_STATE) self.assertEqual(parsed_request.relay_state, REDIRECT_RELAY_STATE)

View File

@ -17,6 +17,7 @@ from authentik.providers.saml.models import SAMLBindings, SAMLProvider
from authentik.providers.saml.processors.assertion import AssertionProcessor from authentik.providers.saml.processors.assertion import AssertionProcessor
from authentik.providers.saml.processors.request_parser import AuthNRequest from authentik.providers.saml.processors.request_parser import AuthNRequest
from authentik.providers.saml.utils.encoding import deflate_and_base64_encode, nice64 from authentik.providers.saml.utils.encoding import deflate_and_base64_encode, nice64
from authentik.sources.saml.exceptions import SAMLException
LOGGER = get_logger() LOGGER = get_logger()
URL_VALIDATOR = URLValidator(schemes=("http", "https")) URL_VALIDATOR = URLValidator(schemes=("http", "https"))
@ -56,22 +57,30 @@ class SAMLFlowFinalView(ChallengeStageView):
provider: SAMLProvider = get_object_or_404( provider: SAMLProvider = get_object_or_404(
SAMLProvider, pk=application.provider_id SAMLProvider, pk=application.provider_id
) )
# Log Application Authorization
Event.new(
EventAction.AUTHORIZE_APPLICATION,
authorized_application=application,
flow=self.executor.plan.flow_pk,
).from_http(self.request)
if SESSION_KEY_AUTH_N_REQUEST not in self.request.session: if SESSION_KEY_AUTH_N_REQUEST not in self.request.session:
return self.executor.stage_invalid() return self.executor.stage_invalid()
auth_n_request: AuthNRequest = self.request.session.pop( auth_n_request: AuthNRequest = self.request.session.pop(
SESSION_KEY_AUTH_N_REQUEST SESSION_KEY_AUTH_N_REQUEST
) )
try:
response = AssertionProcessor( response = AssertionProcessor(
provider, request, auth_n_request provider, request, auth_n_request
).build_response() ).build_response()
except SAMLException as exc:
Event.new(
EventAction.CONFIGURATION_ERROR,
message=f"Failed to process SAML assertion: {str(exc)}",
provider=provider,
).from_http(self.request)
return self.executor.stage_invalid()
# Log Application Authorization
Event.new(
EventAction.AUTHORIZE_APPLICATION,
authorized_application=application,
flow=self.executor.plan.flow_pk,
).from_http(self.request)
if provider.sp_binding == SAMLBindings.POST: if provider.sp_binding == SAMLBindings.POST:
form_attrs = { form_attrs = {

View File

@ -44,7 +44,7 @@ class Command(BaseCommand):
user=user, user=user,
intent=TokenIntents.INTENT_RECOVERY, intent=TokenIntents.INTENT_RECOVERY,
description=f"Recovery Token generated by {getuser()} on {_now}", description=f"Recovery Token generated by {getuser()} on {_now}",
identifier=f"ak-recovery-{user}", identifier=f"ak-recovery-{user}-{_now}",
) )
self.stdout.write( self.stdout.write(
( (

View File

@ -15,6 +15,7 @@ import logging
import os import os
import sys import sys
from json import dumps from json import dumps
from tempfile import gettempdir
from time import time from time import time
import structlog import structlog
@ -193,6 +194,7 @@ CACHES = {
f"redis://:{CONFIG.y('redis.password')}@{CONFIG.y('redis.host')}:6379" f"redis://:{CONFIG.y('redis.password')}@{CONFIG.y('redis.host')}:6379"
f"/{CONFIG.y('redis.cache_db')}" f"/{CONFIG.y('redis.cache_db')}"
), ),
"TIMEOUT": int(CONFIG.y("redis.cache_timeout", 300)),
"OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient"}, "OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient"},
} }
} }
@ -341,7 +343,7 @@ DBBACKUP_FILENAME_TEMPLATE = "authentik-backup-{datetime}.sql"
DBBACKUP_CONNECTOR_MAPPING = { DBBACKUP_CONNECTOR_MAPPING = {
"django_prometheus.db.backends.postgresql": "dbbackup.db.postgresql.PgDumpConnector", "django_prometheus.db.backends.postgresql": "dbbackup.db.postgresql.PgDumpConnector",
} }
DBBACKUP_TMP_DIR = gettempdir() if DEBUG else "/tmp" # nosec
if CONFIG.y("postgresql.s3_backup"): if CONFIG.y("postgresql.s3_backup"):
DBBACKUP_STORAGE = "storages.backends.s3boto3.S3Boto3Storage" DBBACKUP_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
DBBACKUP_STORAGE_OPTIONS = { DBBACKUP_STORAGE_OPTIONS = {

View File

@ -15,6 +15,10 @@ class PytestTestRunner: # pragma: no cover
settings.CELERY_TASK_ALWAYS_EAGER = True settings.CELERY_TASK_ALWAYS_EAGER = True
CONFIG.y_set("authentik.avatars", "none") CONFIG.y_set("authentik.avatars", "none")
CONFIG.y_set("authentik.geoip", "tests/GeoLite2-City-Test.mmdb") CONFIG.y_set("authentik.geoip", "tests/GeoLite2-City-Test.mmdb")
CONFIG.y_set(
"outposts.docker_image_base",
"beryju.org/authentik/outpost-%(type)s:gh-master",
)
def run_tests(self, test_labels): def run_tests(self, test_labels):
"""Run pytest and return the exitcode. """Run pytest and return the exitcode.

View File

@ -60,14 +60,21 @@ class LDAPPasswordChanger:
def check_ad_password_complexity_enabled(self) -> bool: def check_ad_password_complexity_enabled(self) -> bool:
"""Check if DOMAIN_PASSWORD_COMPLEX is enabled""" """Check if DOMAIN_PASSWORD_COMPLEX is enabled"""
root_dn = self.get_domain_root_dn() root_dn = self.get_domain_root_dn()
try:
root_attrs = self._source.connection.extend.standard.paged_search( root_attrs = self._source.connection.extend.standard.paged_search(
search_base=root_dn, search_base=root_dn,
search_filter="(objectClass=*)", search_filter="(objectClass=*)",
search_scope=ldap3.BASE, search_scope=ldap3.BASE,
attributes=["pwdProperties"], attributes=["pwdProperties"],
) )
except ldap3.core.exceptions.LDAPAttributeError:
return False
root_attrs = list(root_attrs)[0] root_attrs = list(root_attrs)[0]
pwd_properties = PwdProperties(root_attrs["attributes"]["pwdProperties"]) raw_pwd_properties = root_attrs.get("attributes", {}).get("pwdProperties", None)
if raw_pwd_properties is None:
return False
pwd_properties = PwdProperties(raw_pwd_properties)
if PwdProperties.DOMAIN_PASSWORD_COMPLEX in pwd_properties: if PwdProperties.DOMAIN_PASSWORD_COMPLEX in pwd_properties:
return True return True

View File

@ -2,17 +2,21 @@
from authentik.lib.sentry import SentryIgnoredException from authentik.lib.sentry import SentryIgnoredException
class MissingSAMLResponse(SentryIgnoredException): class SAMLException(SentryIgnoredException):
"""Base SAML Exception"""
class MissingSAMLResponse(SAMLException):
"""Exception raised when request does not contain SAML Response.""" """Exception raised when request does not contain SAML Response."""
class UnsupportedNameIDFormat(SentryIgnoredException): class UnsupportedNameIDFormat(SAMLException):
"""Exception raised when SAML Response contains NameID Format not supported.""" """Exception raised when SAML Response contains NameID Format not supported."""
class MismatchedRequestID(SentryIgnoredException): class MismatchedRequestID(SAMLException):
"""Exception raised when the returned request ID doesn't match the saved ID.""" """Exception raised when the returned request ID doesn't match the saved ID."""
class InvalidSignature(SentryIgnoredException): class InvalidSignature(SAMLException):
"""Signature of XML Object is either missing or invalid""" """Signature of XML Object is either missing or invalid"""

View File

@ -15,6 +15,9 @@ NS_MAP = {
SAML_NAME_ID_FORMAT_EMAIL = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" SAML_NAME_ID_FORMAT_EMAIL = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
SAML_NAME_ID_FORMAT_PERSISTENT = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" SAML_NAME_ID_FORMAT_PERSISTENT = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
SAML_NAME_ID_FORMAT_UNSPECIFIED = (
"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
)
SAML_NAME_ID_FORMAT_X509 = "urn:oasis:names:tc:SAML:2.0:nameid-format:X509SubjectName" SAML_NAME_ID_FORMAT_X509 = "urn:oasis:names:tc:SAML:2.0:nameid-format:X509SubjectName"
SAML_NAME_ID_FORMAT_WINDOWS = ( SAML_NAME_ID_FORMAT_WINDOWS = (
"urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName" "urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName"

View File

@ -9,7 +9,7 @@ from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet from rest_framework.viewsets import GenericViewSet, ModelViewSet
from authentik.api.authorization import OwnerFilter, OwnerPermissions from authentik.api.authorization import OwnerFilter, OwnerPermissions
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
@ -94,7 +94,7 @@ class DuoDeviceViewSet(
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter] filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
class DuoAdminDeviceViewSet(ReadOnlyModelViewSet): class DuoAdminDeviceViewSet(ModelViewSet):
"""Viewset for Duo authenticator devices (for admins)""" """Viewset for Duo authenticator devices (for admins)"""
permission_classes = [IsAdminUser] permission_classes = [IsAdminUser]

View File

@ -3,6 +3,7 @@ from django.http import HttpRequest, HttpResponse
from rest_framework.fields import CharField from rest_framework.fields import CharField
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.events.models import Event, EventAction
from authentik.flows.challenge import ( from authentik.flows.challenge import (
Challenge, Challenge,
ChallengeResponse, ChallengeResponse,
@ -11,6 +12,7 @@ from authentik.flows.challenge import (
) )
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import ChallengeStageView from authentik.flows.stage import ChallengeStageView
from authentik.flows.views import InvalidStageError
from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice
LOGGER = get_logger() LOGGER = get_logger()
@ -42,7 +44,15 @@ class AuthenticatorDuoStageView(ChallengeStageView):
def get_challenge(self, *args, **kwargs) -> Challenge: def get_challenge(self, *args, **kwargs) -> Challenge:
user = self.get_pending_user() user = self.get_pending_user()
stage: AuthenticatorDuoStage = self.executor.current_stage stage: AuthenticatorDuoStage = self.executor.current_stage
try:
enroll = stage.client.enroll(user.username) enroll = stage.client.enroll(user.username)
except RuntimeError as exc:
Event.new(
EventAction.CONFIGURATION_ERROR,
message=f"Failed to enroll user: {str(exc)}",
user=user,
).from_http(self.request, user)
raise InvalidStageError(str(exc)) from exc
user_id = enroll["user_id"] user_id = enroll["user_id"]
self.request.session[SESSION_KEY_DUO_USER_ID] = user_id self.request.session[SESSION_KEY_DUO_USER_ID] = user_id
self.request.session[SESSION_KEY_DUO_ACTIVATION_CODE] = enroll[ self.request.session[SESSION_KEY_DUO_ACTIVATION_CODE] = enroll[

View File

@ -10,7 +10,7 @@ from authentik.flows.challenge import (
ChallengeTypes, ChallengeTypes,
WithUserInfoChallenge, WithUserInfoChallenge,
) )
from authentik.flows.models import NotConfiguredAction from authentik.flows.models import NotConfiguredAction, Stage
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import ChallengeStageView from authentik.flows.stage import ChallengeStageView
from authentik.stages.authenticator_validate.challenge import ( from authentik.stages.authenticator_validate.challenge import (
@ -143,9 +143,12 @@ class AuthenticatorValidateStageView(ChallengeStageView):
return self.executor.stage_invalid() return self.executor.stage_invalid()
if stage.not_configured_action == NotConfiguredAction.CONFIGURE: if stage.not_configured_action == NotConfiguredAction.CONFIGURE:
LOGGER.debug("Authenticator not configured, sending user to configure") LOGGER.debug("Authenticator not configured, sending user to configure")
# Because the foreign key to stage.configuration_stage points to
# a base stage class, we need to do another lookup
stage = Stage.objects.get_subclass(pk=stage.configuration_stage.pk)
# plan.insert inserts at 1 index, so when stage_ok pops 0, # plan.insert inserts at 1 index, so when stage_ok pops 0,
# the configuration stage is next # the configuration stage is next
self.executor.plan.insert(stage.configuration_stage) self.executor.plan.insert(stage)
return self.executor.stage_ok() return self.executor.stage_ok()
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)

View File

@ -4,11 +4,14 @@ from unittest.mock import MagicMock, patch
from django.contrib.sessions.middleware import SessionMiddleware from django.contrib.sessions.middleware import SessionMiddleware
from django.test import TestCase from django.test import TestCase
from django.test.client import RequestFactory from django.test.client import RequestFactory
from django.urls.base import reverse
from django.utils.encoding import force_str
from django_otp.plugins.otp_totp.models import TOTPDevice from django_otp.plugins.otp_totp.models import TOTPDevice
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import ValidationError
from authentik.core.models import User from authentik.core.models import User
from authentik.flows.models import NotConfiguredAction from authentik.flows.challenge import ChallengeTypes
from authentik.flows.models import Flow, FlowStageBinding, NotConfiguredAction
from authentik.flows.tests.test_planner import dummy_get_response from authentik.flows.tests.test_planner import dummy_get_response
from authentik.providers.oauth2.generators import ( from authentik.providers.oauth2.generators import (
generate_client_id, generate_client_id,
@ -24,7 +27,9 @@ from authentik.stages.authenticator_validate.challenge import (
validate_challenge_duo, validate_challenge_duo,
validate_challenge_webauthn, validate_challenge_webauthn,
) )
from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage
from authentik.stages.authenticator_webauthn.models import WebAuthnDevice from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
from authentik.stages.identification.models import IdentificationStage, UserFields
class AuthenticatorValidateStageTests(TestCase): class AuthenticatorValidateStageTests(TestCase):
@ -34,6 +39,50 @@ class AuthenticatorValidateStageTests(TestCase):
self.user = User.objects.get(username="akadmin") self.user = User.objects.get(username="akadmin")
self.request_factory = RequestFactory() self.request_factory = RequestFactory()
def test_not_configured_action(self):
"""Test not_configured_action"""
conf_stage = IdentificationStage.objects.create(
name="conf",
user_fields=[
UserFields.USERNAME,
],
)
stage = AuthenticatorValidateStage.objects.create(
name="foo",
not_configured_action=NotConfiguredAction.CONFIGURE,
configuration_stage=conf_stage,
)
flow = Flow.objects.create(name="test", slug="test", title="test")
FlowStageBinding.objects.create(target=flow, stage=conf_stage, order=0)
FlowStageBinding.objects.create(target=flow, stage=stage, order=1)
response = self.client.post(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
{"uid_field": "akadmin"},
)
self.assertEqual(response.status_code, 302)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
follow=True,
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-identification",
"password_fields": False,
"primary_action": "Log in",
"flow_info": {
"background": flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": flow.title,
},
"user_fields": ["username"],
"sources": [],
},
)
def test_stage_validation(self): def test_stage_validation(self):
"""Test serializer validation""" """Test serializer validation"""
self.client.force_login(self.user) self.client.force_login(self.user)

View File

@ -51,7 +51,7 @@ class CurrentTenantSerializer(PassiveSerializer):
ui_footer_links = ListField( ui_footer_links = ListField(
child=FooterLinkSerializer(), child=FooterLinkSerializer(),
read_only=True, read_only=True,
default=CONFIG.y("authentik.footer_links"), default=CONFIG.y("footer_links", []),
) )
flow_unenrollment = CharField(source="flow_unenrollment.slug", required=False) flow_unenrollment = CharField(source="flow_unenrollment.slug", required=False)

View File

@ -19,7 +19,26 @@ class TestTenants(TestCase):
"branding_favicon": "/static/dist/assets/icons/icon.png", "branding_favicon": "/static/dist/assets/icons/icon.png",
"branding_title": "authentik", "branding_title": "authentik",
"matched_domain": "authentik-default", "matched_domain": "authentik-default",
"ui_footer_links": CONFIG.y("authentik.footer_links"), "ui_footer_links": CONFIG.y("footer_links"),
},
)
def test_tenant_subdomain(self):
"""Test Current tenant API"""
Tenant.objects.all().delete()
Tenant.objects.create(domain="bar.baz", branding_title="custom")
self.assertJSONEqual(
force_str(
self.client.get(
reverse("authentik_api:tenant-current"), HTTP_HOST="foo.bar.baz"
).content
),
{
"branding_logo": "/static/dist/assets/icons/icon_left_brand.svg",
"branding_favicon": "/static/dist/assets/icons/icon.png",
"branding_title": "custom",
"matched_domain": "bar.baz",
"ui_footer_links": CONFIG.y("footer_links"),
}, },
) )
@ -33,6 +52,6 @@ class TestTenants(TestCase):
"branding_favicon": "/static/dist/assets/icons/icon.png", "branding_favicon": "/static/dist/assets/icons/icon.png",
"branding_title": "authentik", "branding_title": "authentik",
"matched_domain": "fallback", "matched_domain": "fallback",
"ui_footer_links": CONFIG.y("authentik.footer_links"), "ui_footer_links": CONFIG.y("footer_links"),
}, },
) )

View File

@ -1,7 +1,8 @@
"""Tenant utilities""" """Tenant utilities"""
from typing import Any from typing import Any
from django.db.models import Q from django.db.models import F, Q
from django.db.models import Value as V
from django.http.request import HttpRequest from django.http.request import HttpRequest
from authentik import __version__ from authentik import __version__
@ -14,8 +15,10 @@ DEFAULT_TENANT = Tenant(domain="fallback")
def get_tenant_for_request(request: HttpRequest) -> Tenant: def get_tenant_for_request(request: HttpRequest) -> Tenant:
"""Get tenant object for current request""" """Get tenant object for current request"""
db_tenants = Tenant.objects.filter( db_tenants = (
Q(domain__iendswith=request.get_host()) | _q_default Tenant.objects.annotate(host_domain=V(request.get_host()))
.filter(Q(host_domain__iendswith=F("domain")) | _q_default)
.order_by("default")
) )
if not db_tenants.exists(): if not db_tenants.exists():
return DEFAULT_TENANT return DEFAULT_TENANT
@ -28,5 +31,5 @@ def context_processor(request: HttpRequest) -> dict[str, Any]:
return { return {
"tenant": tenant, "tenant": tenant,
"ak_version": __version__, "ak_version": __version__,
"footer_links": CONFIG.y("authentik.footer_links"), "footer_links": CONFIG.y("footer_links"),
} }

View File

@ -148,7 +148,7 @@ stages:
inputs: inputs:
script: | script: |
pipenv run python -m scripts.generate_ci_config pipenv run python -m scripts.generate_ci_config
pipenv run ./manage.py migrate pipenv run python -m lifecycle.migrate
- job: migrations_from_previous_release - job: migrations_from_previous_release
pool: pool:
vmImage: 'ubuntu-latest' vmImage: 'ubuntu-latest'

View File

@ -1,8 +1,11 @@
package main package main
import ( import (
"errors"
"fmt" "fmt"
"sync" "net/url"
"os"
"time"
"github.com/getsentry/sentry-go" "github.com/getsentry/sentry-go"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@ -10,6 +13,8 @@ import (
"goauthentik.io/internal/config" "goauthentik.io/internal/config"
"goauthentik.io/internal/constants" "goauthentik.io/internal/constants"
"goauthentik.io/internal/gounicorn" "goauthentik.io/internal/gounicorn"
"goauthentik.io/internal/outpost/ak"
"goauthentik.io/internal/outpost/proxy"
"goauthentik.io/internal/web" "goauthentik.io/internal/web"
) )
@ -30,23 +35,53 @@ func main() {
}) })
} }
ex := common.Init()
defer common.Defer() defer common.Defer()
rl := log.WithField("logger", "authentik.g") u, _ := url.Parse("http://localhost:8000")
wg := sync.WaitGroup{}
wg.Add(3)
go func() {
defer wg.Done()
g := gounicorn.NewGoUnicorn()
for { for {
err := g.Start() g := gounicorn.NewGoUnicorn()
rl.WithError(err).Warning("gunicorn process died, restarting") go attemptStartBackend(g)
}
}()
go func() {
defer wg.Done()
ws := web.NewWebServer() ws := web.NewWebServer()
ws.Run() ws.Run()
}()
wg.Wait() proxy :=
<-ex
ws.Stop()
g.Kill()
}
go func() {
maxTries := 100
attempt := 0
for {
err := attemptProxyStart(u, ex)
if err != nil {
attempt += 1
time.Sleep(5 * time.Second)
if attempt > maxTries {
break
}
}
}
}()
}
func attemptStartBackend(g *gounicorn.GoUnicorn) error {
for {
err := g.Start()
log.WithField("logger", "authentik.g").WithError(err).Warning("gunicorn process died, restarting")
}
}
func attemptProxyStart(u *url.URL, exitSignal chan os.Signal) (*ak.APIController, error) {
ac := ak.NewAPIController(*u, config.G.SecretKey)
if ac == nil {
return nil, errors.New("failed to start")
}
ac.Server = proxy.NewServer(ac)
return ac, nil
} }

View File

@ -21,7 +21,7 @@ services:
networks: networks:
- internal - internal
server: server:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.6.1-rc6} image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.6.2}
restart: unless-stopped restart: unless-stopped
command: server command: server
environment: environment:
@ -38,21 +38,13 @@ services:
- geoip:/geoip - geoip:/geoip
networks: networks:
- internal - internal
labels:
traefik.enable: 'true'
traefik.docker.network: internal
traefik.http.routers.app-router.rule: PathPrefix(`/`)
traefik.http.routers.app-router.service: app-service
traefik.http.routers.app-router.tls: 'true'
traefik.http.services.app-service.loadbalancer.healthcheck.path: /-/health/live/
traefik.http.services.app-service.loadbalancer.server.port: '9000'
env_file: env_file:
- .env - .env
ports: ports:
- "0.0.0.0:9000:9000" - "0.0.0.0:9000:9000"
- "0.0.0.0:9443:9443" - "0.0.0.0:9443:9443"
worker: worker:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.6.1-rc6} image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.6.2}
restart: unless-stopped restart: unless-stopped
command: worker command: worker
networks: networks:

View File

@ -18,6 +18,7 @@ func DefaultConfig() {
Web: WebConfig{ Web: WebConfig{
Listen: "localhost:9000", Listen: "localhost:9000",
ListenTLS: "localhost:9443", ListenTLS: "localhost:9443",
LoadLocalFiles: false,
}, },
Paths: PathsConfig{ Paths: PathsConfig{
Media: "./media", Media: "./media",

View File

@ -2,6 +2,7 @@ package config
type Config struct { type Config struct {
Debug bool `yaml:"debug"` Debug bool `yaml:"debug"`
SecretKey string `yaml:"secret_key"`
Web WebConfig `yaml:"web"` Web WebConfig `yaml:"web"`
Paths PathsConfig `yaml:"paths"` Paths PathsConfig `yaml:"paths"`
LogLevel string `yaml:"log_level"` LogLevel string `yaml:"log_level"`
@ -11,6 +12,7 @@ type Config struct {
type WebConfig struct { type WebConfig struct {
Listen string `yaml:"listen"` Listen string `yaml:"listen"`
ListenTLS string `yaml:"listen_tls"` ListenTLS string `yaml:"listen_tls"`
LoadLocalFiles bool `yaml:"load_local_files"`
} }
type PathsConfig struct { type PathsConfig struct {

View File

@ -5,7 +5,7 @@ import (
"os" "os"
) )
const VERSION = "2021.6.1-rc6" const VERSION = "2021.6.2"
func BUILD() string { func BUILD() string {
build := os.Getenv("GIT_BUILD_HASH") build := os.Getenv("GIT_BUILD_HASH")

View File

@ -10,27 +10,34 @@ import (
type GoUnicorn struct { type GoUnicorn struct {
log *log.Entry log *log.Entry
p *exec.Cmd
} }
func NewGoUnicorn() *GoUnicorn { func NewGoUnicorn() *GoUnicorn {
return &GoUnicorn{ logger := log.WithField("logger", "authentik.g.unicorn")
log: log.WithField("logger", "authentik.g.unicorn"),
}
}
func (g *GoUnicorn) Start() error {
command := "gunicorn" command := "gunicorn"
args := []string{"-c", "./lifecycle/gunicorn.conf.py", "authentik.root.asgi:application"} args := []string{"-c", "./lifecycle/gunicorn.conf.py", "authentik.root.asgi:application"}
if config.G.Debug { if config.G.Debug {
command = "python" command = "python"
args = []string{"manage.py", "runserver", "localhost:8000"} args = []string{"manage.py", "runserver", "localhost:8000"}
} }
g.log.WithField("args", args).WithField("cmd", command).Debug("Starting gunicorn") logger.WithField("args", args).WithField("cmd", command).Debug("Starting gunicorn")
p := exec.Command(command, args...) p := exec.Command(command, args...)
p.Env = append(os.Environ(), p.Env = append(os.Environ(),
"WORKERS=2", "WORKERS=2",
) )
p.Stdout = os.Stdout p.Stdout = os.Stdout
p.Stderr = os.Stderr p.Stderr = os.Stderr
return p.Run() return &GoUnicorn{
log: logger,
p: p,
}
}
func (g *GoUnicorn) Start() error {
return g.p.Run()
}
func (g *GoUnicorn) Kill() error {
return g.p.Process.Kill()
} }

View File

@ -6,7 +6,6 @@ import (
"math/rand" "math/rand"
"net/http" "net/http"
"net/url" "net/url"
"os"
"time" "time"
"github.com/go-openapi/strfmt" "github.com/go-openapi/strfmt"
@ -59,7 +58,7 @@ func NewAPIController(akURL url.URL, token string) *APIController {
if err != nil { if err != nil {
log.WithError(err).Error("Failed to fetch configuration") log.WithError(err).Error("Failed to fetch configuration")
os.Exit(1) return nil
} }
outpost := outposts.Results[0] outpost := outposts.Results[0]
doGlobalSetup(outpost.Config) doGlobalSetup(outpost.Config)

View File

@ -446,15 +446,17 @@ func (p *OAuthProxy) addHeadersForProxying(rw http.ResponseWriter, req *http.Req
username = session.Email username = session.Email
} }
authVal := b64.StdEncoding.EncodeToString([]byte(username + ":" + password)) authVal := b64.StdEncoding.EncodeToString([]byte(username + ":" + password))
p.logger.WithField("username", username).Trace("setting http basic auth")
req.Header["Authorization"] = []string{fmt.Sprintf("Basic %s", authVal)} req.Header["Authorization"] = []string{fmt.Sprintf("Basic %s", authVal)}
} }
// Check if user has additional headers set that we should sent // Check if user has additional headers set that we should sent
if additionalHeaders, ok := userAttributes["additionalHeaders"].(map[string]string); ok { if additionalHeaders, ok := userAttributes["additionalHeaders"].(map[string]interface{}); ok {
p.logger.WithField("headers", additionalHeaders).Trace("setting additional headers")
if additionalHeaders == nil { if additionalHeaders == nil {
return return
} }
for key, value := range additionalHeaders { for key, value := range additionalHeaders {
req.Header.Set(key, value) req.Header.Set(key, toString(value))
} }
} }
} }

View File

@ -3,6 +3,7 @@ package proxy
import ( import (
"net" "net"
"net/http" "net/http"
"strconv"
) )
var xForwardedHost = http.CanonicalHeaderKey("X-Forwarded-Host") var xForwardedHost = http.CanonicalHeaderKey("X-Forwarded-Host")
@ -18,3 +19,16 @@ func getHost(req *http.Request) string {
} }
return hostOnly return hostOnly
} }
// toString Generic to string function, currently supports actual strings and integers
func toString(in interface{}) string {
switch v := in.(type) {
case string:
return v
case *string:
return *v
case int:
return strconv.Itoa(v)
}
return ""
}

View File

@ -5,7 +5,6 @@ import (
"errors" "errors"
"net" "net"
"net/http" "net/http"
"sync"
"github.com/gorilla/handlers" "github.com/gorilla/handlers"
"github.com/gorilla/mux" "github.com/gorilla/mux"
@ -49,17 +48,12 @@ func NewWebServer() *WebServer {
} }
func (ws *WebServer) Run() { func (ws *WebServer) Run() {
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
ws.listenPlain() ws.listenPlain()
}()
go func() {
defer wg.Done()
ws.listenTLS() ws.listenTLS()
}() }
wg.Done()
func (ws *WebServer) Stop() {
ws.stop <- struct{}{}
} }
func (ws *WebServer) listenPlain() { func (ws *WebServer) listenPlain() {

View File

@ -10,7 +10,7 @@ import (
func (ws *WebServer) configureStatic() { func (ws *WebServer) configureStatic() {
statRouter := ws.lh.NewRoute().Subrouter() statRouter := ws.lh.NewRoute().Subrouter()
if config.G.Debug { if config.G.Debug || config.G.Web.LoadLocalFiles {
ws.log.Debug("Using local static files") 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/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")))) ws.lh.PathPrefix("/static/authentik").Handler(http.StripPrefix("/static/authentik", http.FileServer(http.Dir("./web/authentik"))))

View File

@ -1,9 +1,5 @@
all: clean all: clean
run:
go run -v .
clean: clean:
go mod tidy go mod tidy
go clean . go clean .

View File

@ -1,7 +1,7 @@
openapi: 3.0.3 openapi: 3.0.3
info: info:
title: authentik title: authentik
version: 2021.6.1-rc5 version: 2021.6.2
description: Making authentication simple. description: Making authentication simple.
contact: contact:
email: hello@beryju.org email: hello@beryju.org
@ -236,6 +236,37 @@ paths:
$ref: '#/components/schemas/ValidationError' $ref: '#/components/schemas/ValidationError'
'403': '403':
$ref: '#/components/schemas/GenericError' $ref: '#/components/schemas/GenericError'
post:
operationId: authenticators_admin_duo_create
description: Viewset for Duo authenticator devices (for admins)
tags:
- authenticators
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/DuoDeviceRequest'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/DuoDeviceRequest'
multipart/form-data:
schema:
$ref: '#/components/schemas/DuoDeviceRequest'
required: true
security:
- authentik: []
- cookieAuth: []
responses:
'201':
content:
application/json:
schema:
$ref: '#/components/schemas/DuoDevice'
description: ''
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
/api/v2beta/authenticators/admin/duo/{id}/: /api/v2beta/authenticators/admin/duo/{id}/:
get: get:
operationId: authenticators_admin_duo_retrieve operationId: authenticators_admin_duo_retrieve
@ -263,6 +294,103 @@ paths:
$ref: '#/components/schemas/ValidationError' $ref: '#/components/schemas/ValidationError'
'403': '403':
$ref: '#/components/schemas/GenericError' $ref: '#/components/schemas/GenericError'
put:
operationId: authenticators_admin_duo_update
description: Viewset for Duo authenticator devices (for admins)
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this Duo Device.
required: true
tags:
- authenticators
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/DuoDeviceRequest'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/DuoDeviceRequest'
multipart/form-data:
schema:
$ref: '#/components/schemas/DuoDeviceRequest'
required: true
security:
- authentik: []
- cookieAuth: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/DuoDevice'
description: ''
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
patch:
operationId: authenticators_admin_duo_partial_update
description: Viewset for Duo authenticator devices (for admins)
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this Duo Device.
required: true
tags:
- authenticators
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PatchedDuoDeviceRequest'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/PatchedDuoDeviceRequest'
multipart/form-data:
schema:
$ref: '#/components/schemas/PatchedDuoDeviceRequest'
security:
- authentik: []
- cookieAuth: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/DuoDevice'
description: ''
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
delete:
operationId: authenticators_admin_duo_destroy
description: Viewset for Duo authenticator devices (for admins)
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this Duo Device.
required: true
tags:
- authenticators
security:
- authentik: []
- cookieAuth: []
responses:
'204':
description: No response body
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
/api/v2beta/authenticators/admin/static/: /api/v2beta/authenticators/admin/static/:
get: get:
operationId: authenticators_admin_static_list operationId: authenticators_admin_static_list

View File

@ -21,7 +21,13 @@ from authentik.outposts.models import (
) )
from authentik.outposts.tasks import outpost_local_connection from authentik.outposts.tasks import outpost_local_connection
from authentik.providers.proxy.models import ProxyProvider from authentik.providers.proxy.models import ProxyProvider
from tests.e2e.utils import SeleniumTestCase, apply_migration, object_manager, retry from tests.e2e.utils import (
USER,
SeleniumTestCase,
apply_migration,
object_manager,
retry,
)
@skipUnless(platform.startswith("linux"), "requires local docker") @skipUnless(platform.startswith("linux"), "requires local docker")
@ -47,7 +53,7 @@ class TestProviderProxy(SeleniumTestCase):
"""Start proxy container based on outpost created""" """Start proxy container based on outpost created"""
client: DockerClient = from_env() client: DockerClient = from_env()
container = client.containers.run( container = client.containers.run(
image=f"ghcr.io/goauthentik/proxy:{__version__}", image="beryju.org/authentik/outpost-proxy:gh-master",
detach=True, detach=True,
network_mode="host", network_mode="host",
auto_remove=True, auto_remove=True,
@ -67,6 +73,11 @@ class TestProviderProxy(SeleniumTestCase):
@object_manager @object_manager
def test_proxy_simple(self): def test_proxy_simple(self):
"""Test simple outpost setup with single provider""" """Test simple outpost setup with single provider"""
# set additionalHeaders to test later
user = USER()
user.attributes["additionalHeaders"] = {"X-Foo": "bar"}
user.save()
proxy: ProxyProvider = ProxyProvider.objects.create( proxy: ProxyProvider = ProxyProvider.objects.create(
name="proxy_provider", name="proxy_provider",
authorization_flow=Flow.objects.get( authorization_flow=Flow.objects.get(
@ -106,6 +117,7 @@ class TestProviderProxy(SeleniumTestCase):
full_body_text = self.driver.find_element(By.CSS_SELECTOR, "pre").text full_body_text = self.driver.find_element(By.CSS_SELECTOR, "pre").text
self.assertIn("X-Forwarded-Preferred-Username: akadmin", full_body_text) self.assertIn("X-Forwarded-Preferred-Username: akadmin", full_body_text)
self.assertIn("X-Foo: bar", full_body_text)
@skipUnless(platform.startswith("linux"), "requires local docker") @skipUnless(platform.startswith("linux"), "requires local docker")

View File

@ -104,5 +104,5 @@ class OutpostDockerTests(TestCase):
self.assertEqual(compose["version"], "3.5") self.assertEqual(compose["version"], "3.5")
self.assertEqual( self.assertEqual(
compose["services"]["authentik_proxy"]["image"], compose["services"]["authentik_proxy"]["image"],
f"ghcr.io/goauthentik/proxy:{__version__}", "beryju.org/authentik/outpost-proxy:gh-master",
) )

View File

@ -104,5 +104,5 @@ class TestProxyDocker(TestCase):
self.assertEqual(compose["version"], "3.5") self.assertEqual(compose["version"], "3.5")
self.assertEqual( self.assertEqual(
compose["services"]["authentik_proxy"]["image"], compose["services"]["authentik_proxy"]["image"],
f"ghcr.io/goauthentik/proxy:{__version__}", "beryju.org/authentik/outpost-proxy:gh-master",
) )

510
web/package-lock.json generated
View File

@ -12,11 +12,11 @@
"@babel/core": "^7.14.6", "@babel/core": "^7.14.6",
"@babel/plugin-proposal-decorators": "^7.14.5", "@babel/plugin-proposal-decorators": "^7.14.5",
"@babel/plugin-transform-runtime": "^7.14.5", "@babel/plugin-transform-runtime": "^7.14.5",
"@babel/preset-env": "^7.14.5", "@babel/preset-env": "^7.14.7",
"@babel/preset-typescript": "^7.14.5", "@babel/preset-typescript": "^7.14.5",
"@fortawesome/fontawesome-free": "^5.15.3", "@fortawesome/fontawesome-free": "^5.15.3",
"@lingui/cli": "^3.10.2", "@lingui/cli": "^3.10.2",
"@lingui/core": "^3.10.3", "@lingui/core": "^3.10.4",
"@lingui/macro": "^3.10.2", "@lingui/macro": "^3.10.2",
"@patternfly/patternfly": "^4.108.2", "@patternfly/patternfly": "^4.108.2",
"@polymer/iron-form": "^3.0.1", "@polymer/iron-form": "^3.0.1",
@ -24,22 +24,22 @@
"@rollup/plugin-babel": "^5.3.0", "@rollup/plugin-babel": "^5.3.0",
"@rollup/plugin-replace": "^2.4.2", "@rollup/plugin-replace": "^2.4.2",
"@rollup/plugin-typescript": "^8.2.1", "@rollup/plugin-typescript": "^8.2.1",
"@sentry/browser": "^6.7.1", "@sentry/browser": "^6.7.2",
"@sentry/tracing": "^6.7.1", "@sentry/tracing": "^6.7.2",
"@types/chart.js": "^2.9.32", "@types/chart.js": "^2.9.32",
"@types/codemirror": "5.60.0", "@types/codemirror": "5.60.0",
"@types/grecaptcha": "^3.0.2", "@types/grecaptcha": "^3.0.2",
"@typescript-eslint/eslint-plugin": "^4.27.0", "@typescript-eslint/eslint-plugin": "^4.28.0",
"@typescript-eslint/parser": "^4.27.0", "@typescript-eslint/parser": "^4.28.0",
"@webcomponents/webcomponentsjs": "^2.5.0", "@webcomponents/webcomponentsjs": "^2.5.0",
"authentik-api": "file:api", "authentik-api": "file:api",
"babel-plugin-macros": "^3.1.0", "babel-plugin-macros": "^3.1.0",
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
"chart.js": "^3.3.2", "chart.js": "^3.3.2",
"chartjs-adapter-moment": "^1.0.0", "chartjs-adapter-moment": "^1.0.0",
"codemirror": "^5.61.1", "codemirror": "^5.62.0",
"construct-style-sheets-polyfill": "^2.4.16", "construct-style-sheets-polyfill": "^2.4.16",
"eslint": "^7.28.0", "eslint": "^7.29.0",
"eslint-config-google": "^0.14.0", "eslint-config-google": "^0.14.0",
"eslint-plugin-custom-elements": "0.0.2", "eslint-plugin-custom-elements": "0.0.2",
"eslint-plugin-lit": "^1.5.1", "eslint-plugin-lit": "^1.5.1",
@ -48,7 +48,7 @@
"lit-html": "^1.4.1", "lit-html": "^1.4.1",
"moment": "^2.29.1", "moment": "^2.29.1",
"rapidoc": "^9.0.0", "rapidoc": "^9.0.0",
"rollup": "^2.51.2", "rollup": "^2.52.2",
"rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-copy": "^3.4.0", "rollup-plugin-copy": "^3.4.0",
"rollup-plugin-cssimport": "^1.0.2", "rollup-plugin-cssimport": "^1.0.2",
@ -58,7 +58,7 @@
"rollup-plugin-terser": "^7.0.2", "rollup-plugin-terser": "^7.0.2",
"ts-lit-plugin": "^1.2.1", "ts-lit-plugin": "^1.2.1",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"typescript": "^4.3.2", "typescript": "^4.3.4",
"webcomponent-qr-code": "^1.0.5", "webcomponent-qr-code": "^1.0.5",
"yaml": "^1.10.2" "yaml": "^1.10.2"
} }
@ -102,9 +102,9 @@
} }
}, },
"node_modules/@babel/compat-data": { "node_modules/@babel/compat-data": {
"version": "7.14.5", "version": "7.14.7",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.5.tgz", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.7.tgz",
"integrity": "sha512-kixrYn4JwfAVPa0f2yfzc2AWti6WRRyO3XjWW5PJAvtE11qhSayrrcrEnee05KAtNaPC+EwehE8Qt1UedEVB8w==", "integrity": "sha512-nS6dZaISCXJ3+518CWiBfEr//gHyMO02uDxBkXTKZDN5POruCnOZ1N4YBRZDCabwF8nZMWBpRxIicmXtBs+fvw==",
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
@ -532,9 +532,9 @@
} }
}, },
"node_modules/@babel/plugin-proposal-async-generator-functions": { "node_modules/@babel/plugin-proposal-async-generator-functions": {
"version": "7.14.5", "version": "7.14.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.14.5.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.14.7.tgz",
"integrity": "sha512-tbD/CG3l43FIXxmu4a7RBe4zH7MLJ+S/lFowPFO7HetS2hyOZ/0nnnznegDuzFzfkyQYTxqdTH/hKmuBngaDAA==", "integrity": "sha512-RK8Wj7lXLY3bqei69/cc25gwS5puEc3dknoFPFbqfy3XxYQBQFvu4ioWpafMBAB+L9NyptQK4nMOa5Xz16og8Q==",
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.14.5", "@babel/helper-plugin-utils": "^7.14.5",
"@babel/helper-remap-async-to-generator": "^7.14.5", "@babel/helper-remap-async-to-generator": "^7.14.5",
@ -685,11 +685,11 @@
} }
}, },
"node_modules/@babel/plugin-proposal-object-rest-spread": { "node_modules/@babel/plugin-proposal-object-rest-spread": {
"version": "7.14.5", "version": "7.14.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.14.5.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.14.7.tgz",
"integrity": "sha512-VzMyY6PWNPPT3pxc5hi9LloKNr4SSrVCg7Yr6aZpW4Ym07r7KqSU/QXYwjXLVxqwSv0t/XSXkFoKBPUkZ8vb2A==", "integrity": "sha512-082hsZz+sVabfmDWo1Oct1u1AgbKbUAyVgmX4otIc7bdsRgHBXwTwb3DpDmD4Eyyx6DNiuz5UAATT655k+kL5g==",
"dependencies": { "dependencies": {
"@babel/compat-data": "^7.14.5", "@babel/compat-data": "^7.14.7",
"@babel/helper-compilation-targets": "^7.14.5", "@babel/helper-compilation-targets": "^7.14.5",
"@babel/helper-plugin-utils": "^7.14.5", "@babel/helper-plugin-utils": "^7.14.5",
"@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
@ -1077,9 +1077,9 @@
} }
}, },
"node_modules/@babel/plugin-transform-destructuring": { "node_modules/@babel/plugin-transform-destructuring": {
"version": "7.14.5", "version": "7.14.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.14.5.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.14.7.tgz",
"integrity": "sha512-wU9tYisEbRMxqDezKUqC9GleLycCRoUsai9ddlsq54r8QRLaeEhc+d+9DqCG+kV9W2GgQjTZESPTpn5bAFMDww==", "integrity": "sha512-0mDE99nK+kVh3xlc5vKwB6wnP9ecuSj+zQCa/n0voENtP/zymdT4HH6QEb65wjjcbqr1Jb/7z9Qp7TF5FtwYGw==",
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.14.5" "@babel/helper-plugin-utils": "^7.14.5"
}, },
@ -1258,9 +1258,9 @@
} }
}, },
"node_modules/@babel/plugin-transform-named-capturing-groups-regex": { "node_modules/@babel/plugin-transform-named-capturing-groups-regex": {
"version": "7.14.5", "version": "7.14.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.14.5.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.14.7.tgz",
"integrity": "sha512-+Xe5+6MWFo311U8SchgeX5c1+lJM+eZDBZgD+tvXu9VVQPXwwVzeManMMjYX6xw2HczngfOSZjoFYKwdeB/Jvw==", "integrity": "sha512-DTNOTaS7TkW97xsDMrp7nycUVh6sn/eq22VaxWfEdzuEbRsiaOU0pqU7DlyUGHVsbQbSghvjKRpEl+nUCKGQSg==",
"dependencies": { "dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.14.5" "@babel/helper-create-regexp-features-plugin": "^7.14.5"
}, },
@ -1398,9 +1398,9 @@
} }
}, },
"node_modules/@babel/plugin-transform-spread": { "node_modules/@babel/plugin-transform-spread": {
"version": "7.14.5", "version": "7.14.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.14.5.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.14.6.tgz",
"integrity": "sha512-/3iqoQdiWergnShZYl0xACb4ADeYCJ7X/RgmwtXshn6cIvautRPAFzhd58frQlokLO6Jb4/3JXvmm6WNTPtiTw==", "integrity": "sha512-Zr0x0YroFJku7n7+/HH3A2eIrGMjbmAIbJSVv0IZ+t3U2WUQUA64S/oeied2e+MaGSjmt4alzBCsK9E8gh+fag==",
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.14.5", "@babel/helper-plugin-utils": "^7.14.5",
"@babel/helper-skip-transparent-expression-wrappers": "^7.14.5" "@babel/helper-skip-transparent-expression-wrappers": "^7.14.5"
@ -1500,16 +1500,16 @@
} }
}, },
"node_modules/@babel/preset-env": { "node_modules/@babel/preset-env": {
"version": "7.14.5", "version": "7.14.7",
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.14.5.tgz", "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.14.7.tgz",
"integrity": "sha512-ci6TsS0bjrdPpWGnQ+m4f+JSSzDKlckqKIJJt9UZ/+g7Zz9k0N8lYU8IeLg/01o2h8LyNZDMLGgRLDTxpudLsA==", "integrity": "sha512-itOGqCKLsSUl0Y+1nSfhbuuOlTs0MJk2Iv7iSH+XT/mR8U1zRLO7NjWlYXB47yhK4J/7j+HYty/EhFZDYKa/VA==",
"dependencies": { "dependencies": {
"@babel/compat-data": "^7.14.5", "@babel/compat-data": "^7.14.7",
"@babel/helper-compilation-targets": "^7.14.5", "@babel/helper-compilation-targets": "^7.14.5",
"@babel/helper-plugin-utils": "^7.14.5", "@babel/helper-plugin-utils": "^7.14.5",
"@babel/helper-validator-option": "^7.14.5", "@babel/helper-validator-option": "^7.14.5",
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.14.5", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.14.5",
"@babel/plugin-proposal-async-generator-functions": "^7.14.5", "@babel/plugin-proposal-async-generator-functions": "^7.14.7",
"@babel/plugin-proposal-class-properties": "^7.14.5", "@babel/plugin-proposal-class-properties": "^7.14.5",
"@babel/plugin-proposal-class-static-block": "^7.14.5", "@babel/plugin-proposal-class-static-block": "^7.14.5",
"@babel/plugin-proposal-dynamic-import": "^7.14.5", "@babel/plugin-proposal-dynamic-import": "^7.14.5",
@ -1518,7 +1518,7 @@
"@babel/plugin-proposal-logical-assignment-operators": "^7.14.5", "@babel/plugin-proposal-logical-assignment-operators": "^7.14.5",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5",
"@babel/plugin-proposal-numeric-separator": "^7.14.5", "@babel/plugin-proposal-numeric-separator": "^7.14.5",
"@babel/plugin-proposal-object-rest-spread": "^7.14.5", "@babel/plugin-proposal-object-rest-spread": "^7.14.7",
"@babel/plugin-proposal-optional-catch-binding": "^7.14.5", "@babel/plugin-proposal-optional-catch-binding": "^7.14.5",
"@babel/plugin-proposal-optional-chaining": "^7.14.5", "@babel/plugin-proposal-optional-chaining": "^7.14.5",
"@babel/plugin-proposal-private-methods": "^7.14.5", "@babel/plugin-proposal-private-methods": "^7.14.5",
@ -1544,7 +1544,7 @@
"@babel/plugin-transform-block-scoping": "^7.14.5", "@babel/plugin-transform-block-scoping": "^7.14.5",
"@babel/plugin-transform-classes": "^7.14.5", "@babel/plugin-transform-classes": "^7.14.5",
"@babel/plugin-transform-computed-properties": "^7.14.5", "@babel/plugin-transform-computed-properties": "^7.14.5",
"@babel/plugin-transform-destructuring": "^7.14.5", "@babel/plugin-transform-destructuring": "^7.14.7",
"@babel/plugin-transform-dotall-regex": "^7.14.5", "@babel/plugin-transform-dotall-regex": "^7.14.5",
"@babel/plugin-transform-duplicate-keys": "^7.14.5", "@babel/plugin-transform-duplicate-keys": "^7.14.5",
"@babel/plugin-transform-exponentiation-operator": "^7.14.5", "@babel/plugin-transform-exponentiation-operator": "^7.14.5",
@ -1556,7 +1556,7 @@
"@babel/plugin-transform-modules-commonjs": "^7.14.5", "@babel/plugin-transform-modules-commonjs": "^7.14.5",
"@babel/plugin-transform-modules-systemjs": "^7.14.5", "@babel/plugin-transform-modules-systemjs": "^7.14.5",
"@babel/plugin-transform-modules-umd": "^7.14.5", "@babel/plugin-transform-modules-umd": "^7.14.5",
"@babel/plugin-transform-named-capturing-groups-regex": "^7.14.5", "@babel/plugin-transform-named-capturing-groups-regex": "^7.14.7",
"@babel/plugin-transform-new-target": "^7.14.5", "@babel/plugin-transform-new-target": "^7.14.5",
"@babel/plugin-transform-object-super": "^7.14.5", "@babel/plugin-transform-object-super": "^7.14.5",
"@babel/plugin-transform-parameters": "^7.14.5", "@babel/plugin-transform-parameters": "^7.14.5",
@ -1564,7 +1564,7 @@
"@babel/plugin-transform-regenerator": "^7.14.5", "@babel/plugin-transform-regenerator": "^7.14.5",
"@babel/plugin-transform-reserved-words": "^7.14.5", "@babel/plugin-transform-reserved-words": "^7.14.5",
"@babel/plugin-transform-shorthand-properties": "^7.14.5", "@babel/plugin-transform-shorthand-properties": "^7.14.5",
"@babel/plugin-transform-spread": "^7.14.5", "@babel/plugin-transform-spread": "^7.14.6",
"@babel/plugin-transform-sticky-regex": "^7.14.5", "@babel/plugin-transform-sticky-regex": "^7.14.5",
"@babel/plugin-transform-template-literals": "^7.14.5", "@babel/plugin-transform-template-literals": "^7.14.5",
"@babel/plugin-transform-typeof-symbol": "^7.14.5", "@babel/plugin-transform-typeof-symbol": "^7.14.5",
@ -1575,7 +1575,7 @@
"babel-plugin-polyfill-corejs2": "^0.2.2", "babel-plugin-polyfill-corejs2": "^0.2.2",
"babel-plugin-polyfill-corejs3": "^0.2.2", "babel-plugin-polyfill-corejs3": "^0.2.2",
"babel-plugin-polyfill-regenerator": "^0.2.2", "babel-plugin-polyfill-regenerator": "^0.2.2",
"core-js-compat": "^3.14.0", "core-js-compat": "^3.15.0",
"semver": "^6.3.0" "semver": "^6.3.0"
}, },
"engines": { "engines": {
@ -2047,9 +2047,9 @@
} }
}, },
"node_modules/@lingui/core": { "node_modules/@lingui/core": {
"version": "3.10.3", "version": "3.10.4",
"resolved": "https://registry.npmjs.org/@lingui/core/-/core-3.10.3.tgz", "resolved": "https://registry.npmjs.org/@lingui/core/-/core-3.10.4.tgz",
"integrity": "sha512-BiuWi5xPpQa27oIWWnkOYNx4qTMdMeu7vp5y1AGPYQ/4SO0rHfAtOxXtvRU/ktVwht/lIgx5Ygq5J3F+XLvOQA==", "integrity": "sha512-V9QKQ9PFMTPrGGz2PaeKHZcxFikQZzJbptyQbVFJdXaKhdE2RH6HhdK1PIziDHqp6ZWPthVIfVLURT3ku8eu5w==",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.11.2", "@babel/runtime": "^7.11.2",
"make-plural": "^6.2.2", "make-plural": "^6.2.2",
@ -2314,13 +2314,13 @@
"integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==" "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg=="
}, },
"node_modules/@sentry/browser": { "node_modules/@sentry/browser": {
"version": "6.7.1", "version": "6.7.2",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.7.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.7.2.tgz",
"integrity": "sha512-R5PYx4TTvifcU790XkK6JVGwavKaXwycDU0MaAwfc4Vf7BLm5KCNJCsDySu1RPAap/017MVYf54p6dWvKiRviA==", "integrity": "sha512-Lv0Ne1QcesyGAhVcQDfQa3hDPR/MhPSDTMg3xFi+LxqztchVc4w/ynzR0wCZFb8KIHpTj5SpJHfxpDhXYMtS9g==",
"dependencies": { "dependencies": {
"@sentry/core": "6.7.1", "@sentry/core": "6.7.2",
"@sentry/types": "6.7.1", "@sentry/types": "6.7.2",
"@sentry/utils": "6.7.1", "@sentry/utils": "6.7.2",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"engines": { "engines": {
@ -2333,14 +2333,14 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}, },
"node_modules/@sentry/core": { "node_modules/@sentry/core": {
"version": "6.7.1", "version": "6.7.2",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.7.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.7.2.tgz",
"integrity": "sha512-VAv8OR/7INn2JfiLcuop4hfDcyC7mfL9fdPndQEhlacjmw8gRrgXjR7qyhnCTgzFLkHI7V5bcdIzA83TRPYQpA==", "integrity": "sha512-NTZqwN5nR94yrXmSfekoPs1mIFuKvf8esdIW/DadwSKWAdLJwQTJY9xK/8PQv+SEzd7wiitPAx+mCw2By1xiNQ==",
"dependencies": { "dependencies": {
"@sentry/hub": "6.7.1", "@sentry/hub": "6.7.2",
"@sentry/minimal": "6.7.1", "@sentry/minimal": "6.7.2",
"@sentry/types": "6.7.1", "@sentry/types": "6.7.2",
"@sentry/utils": "6.7.1", "@sentry/utils": "6.7.2",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"engines": { "engines": {
@ -2353,12 +2353,12 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}, },
"node_modules/@sentry/hub": { "node_modules/@sentry/hub": {
"version": "6.7.1", "version": "6.7.2",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.7.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.7.2.tgz",
"integrity": "sha512-eVCTWvvcp6xa0A5GGNHMQEWslmKPlisE5rGmsV/kjvSUv3zSrI0eIDfb51ikdnCiBjHpK2NBWP8Vy8cZOEJegg==", "integrity": "sha512-05qVW6ymChJsXag4+fYCQokW3AcABIgcqrVYZUBf6GMU/Gbz5SJqpV7y1+njwWvnPZydMncP9LaDVpMKbE7UYQ==",
"dependencies": { "dependencies": {
"@sentry/types": "6.7.1", "@sentry/types": "6.7.2",
"@sentry/utils": "6.7.1", "@sentry/utils": "6.7.2",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"engines": { "engines": {
@ -2371,12 +2371,12 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}, },
"node_modules/@sentry/minimal": { "node_modules/@sentry/minimal": {
"version": "6.7.1", "version": "6.7.2",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.7.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.7.2.tgz",
"integrity": "sha512-HDDPEnQRD6hC0qaHdqqKDStcdE1KhkFh0RCtJNMCDn0zpav8Dj9AteF70x6kLSlliAJ/JFwi6AmQrLz+FxPexw==", "integrity": "sha512-jkpwFv2GFHoVl5vnK+9/Q+Ea8eVdbJ3hn3/Dqq9MOLFnVK7ED6MhdHKLT79puGSFj+85OuhM5m2Q44mIhyS5mw==",
"dependencies": { "dependencies": {
"@sentry/hub": "6.7.1", "@sentry/hub": "6.7.2",
"@sentry/types": "6.7.1", "@sentry/types": "6.7.2",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"engines": { "engines": {
@ -2389,14 +2389,14 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}, },
"node_modules/@sentry/tracing": { "node_modules/@sentry/tracing": {
"version": "6.7.1", "version": "6.7.2",
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.7.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.7.2.tgz",
"integrity": "sha512-wyS3nWNl5mzaC1qZ2AIp1hjXnfO9EERjMIJjCihs2LWBz1r3efxrHxJHs8wXlNWvrT3KLhq/7vvF5CdU82uPeQ==", "integrity": "sha512-juKlI7FICKONWJFJxDxerj0A+8mNRhmtrdR+OXFqOkqSAy/QXlSFZcA/j//O19k2CfwK1BrvoMcQ/4gnffUOVg==",
"dependencies": { "dependencies": {
"@sentry/hub": "6.7.1", "@sentry/hub": "6.7.2",
"@sentry/minimal": "6.7.1", "@sentry/minimal": "6.7.2",
"@sentry/types": "6.7.1", "@sentry/types": "6.7.2",
"@sentry/utils": "6.7.1", "@sentry/utils": "6.7.2",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"engines": { "engines": {
@ -2409,19 +2409,19 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}, },
"node_modules/@sentry/types": { "node_modules/@sentry/types": {
"version": "6.7.1", "version": "6.7.2",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.7.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.7.2.tgz",
"integrity": "sha512-9AO7HKoip2MBMNQJEd6+AKtjj2+q9Ze4ooWUdEvdOVSt5drg7BGpK221/p9JEOyJAZwEPEXdcMd3VAIMiOb4MA==", "integrity": "sha512-h21Go/PfstUN+ZV6SbwRSZVg9GXRJWdLfHoO5PSVb3TVEMckuxk8tAE57/u+UZDwX8wu+Xyon2TgsKpiWKxqUg==",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/@sentry/utils": { "node_modules/@sentry/utils": {
"version": "6.7.1", "version": "6.7.2",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.7.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.7.2.tgz",
"integrity": "sha512-Tq2otdbWlHAkctD+EWTYKkEx6BL1Qn3Z/imkO06/PvzpWvVhJWQ5qHAzz5XnwwqNHyV03KVzYB6znq1Bea9HuA==", "integrity": "sha512-9COL7aaBbe61Hp5BlArtXZ1o/cxli1NGONLPrVT4fMyeQFmLonhUiy77NdsW19XnvhvaA+2IoV5dg3dnFiF/og==",
"dependencies": { "dependencies": {
"@sentry/types": "6.7.1", "@sentry/types": "6.7.2",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"engines": { "engines": {
@ -2579,15 +2579,14 @@
"integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==" "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA=="
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "4.27.0", "version": "4.28.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.27.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.0.tgz",
"integrity": "sha512-DsLqxeUfLVNp3AO7PC3JyaddmEHTtI9qTSAs+RB6ja27QvIM0TA8Cizn1qcS6vOu+WDLFJzkwkgweiyFhssDdQ==", "integrity": "sha512-KcF6p3zWhf1f8xO84tuBailV5cN92vhS+VT7UJsPzGBm9VnQqfI9AsiMUFUCYHTYPg1uCCo+HyiDnpDuvkAMfQ==",
"dependencies": { "dependencies": {
"@typescript-eslint/experimental-utils": "4.27.0", "@typescript-eslint/experimental-utils": "4.28.0",
"@typescript-eslint/scope-manager": "4.27.0", "@typescript-eslint/scope-manager": "4.28.0",
"debug": "^4.3.1", "debug": "^4.3.1",
"functional-red-black-tree": "^1.0.1", "functional-red-black-tree": "^1.0.1",
"lodash": "^4.17.21",
"regexpp": "^3.1.0", "regexpp": "^3.1.0",
"semver": "^7.3.5", "semver": "^7.3.5",
"tsutils": "^3.21.0" "tsutils": "^3.21.0"
@ -2610,14 +2609,14 @@
} }
}, },
"node_modules/@typescript-eslint/experimental-utils": { "node_modules/@typescript-eslint/experimental-utils": {
"version": "4.27.0", "version": "4.28.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.27.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.0.tgz",
"integrity": "sha512-n5NlbnmzT2MXlyT+Y0Jf0gsmAQzCnQSWXKy4RGSXVStjDvS5we9IWbh7qRVKdGcxT0WYlgcCYUK/HRg7xFhvjQ==", "integrity": "sha512-9XD9s7mt3QWMk82GoyUpc/Ji03vz4T5AYlHF9DcoFNfJ/y3UAclRsfGiE2gLfXtyC+JRA3trR7cR296TEb1oiQ==",
"dependencies": { "dependencies": {
"@types/json-schema": "^7.0.7", "@types/json-schema": "^7.0.7",
"@typescript-eslint/scope-manager": "4.27.0", "@typescript-eslint/scope-manager": "4.28.0",
"@typescript-eslint/types": "4.27.0", "@typescript-eslint/types": "4.28.0",
"@typescript-eslint/typescript-estree": "4.27.0", "@typescript-eslint/typescript-estree": "4.28.0",
"eslint-scope": "^5.1.1", "eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0" "eslint-utils": "^3.0.0"
}, },
@ -2650,13 +2649,13 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "4.27.0", "version": "4.28.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.27.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.0.tgz",
"integrity": "sha512-XpbxL+M+gClmJcJ5kHnUpBGmlGdgNvy6cehgR6ufyxkEJMGP25tZKCaKyC0W/JVpuhU3VU1RBn7SYUPKSMqQvQ==", "integrity": "sha512-7x4D22oPY8fDaOCvkuXtYYTQ6mTMmkivwEzS+7iml9F9VkHGbbZ3x4fHRwxAb5KeuSkLqfnYjs46tGx2Nour4A==",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "4.27.0", "@typescript-eslint/scope-manager": "4.28.0",
"@typescript-eslint/types": "4.27.0", "@typescript-eslint/types": "4.28.0",
"@typescript-eslint/typescript-estree": "4.27.0", "@typescript-eslint/typescript-estree": "4.28.0",
"debug": "^4.3.1" "debug": "^4.3.1"
}, },
"engines": { "engines": {
@ -2676,12 +2675,12 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "4.27.0", "version": "4.28.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.27.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.0.tgz",
"integrity": "sha512-DY73jK6SEH6UDdzc6maF19AHQJBFVRf6fgAXHPXCGEmpqD4vYgPEzqpFz1lf/daSbOcMpPPj9tyXXDPW2XReAw==", "integrity": "sha512-eCALCeScs5P/EYjwo6se9bdjtrh8ByWjtHzOkC4Tia6QQWtQr3PHovxh3TdYTuFcurkYI4rmFsRFpucADIkseg==",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "4.27.0", "@typescript-eslint/types": "4.28.0",
"@typescript-eslint/visitor-keys": "4.27.0" "@typescript-eslint/visitor-keys": "4.28.0"
}, },
"engines": { "engines": {
"node": "^8.10.0 || ^10.13.0 || >=11.10.1" "node": "^8.10.0 || ^10.13.0 || >=11.10.1"
@ -2692,9 +2691,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "4.27.0", "version": "4.28.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.27.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.0.tgz",
"integrity": "sha512-I4ps3SCPFCKclRcvnsVA/7sWzh7naaM/b4pBO2hVxnM3wrU51Lveybdw5WoIktU/V4KfXrTt94V9b065b/0+wA==", "integrity": "sha512-p16xMNKKoiJCVZY5PW/AfILw2xe1LfruTcfAKBj3a+wgNYP5I9ZEKNDOItoRt53p4EiPV6iRSICy8EPanG9ZVA==",
"engines": { "engines": {
"node": "^8.10.0 || ^10.13.0 || >=11.10.1" "node": "^8.10.0 || ^10.13.0 || >=11.10.1"
}, },
@ -2704,12 +2703,12 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "4.27.0", "version": "4.28.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.27.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.0.tgz",
"integrity": "sha512-KH03GUsUj41sRLLEy2JHstnezgpS5VNhrJouRdmh6yNdQ+yl8w5LrSwBkExM+jWwCJa7Ct2c8yl8NdtNRyQO6g==", "integrity": "sha512-m19UQTRtxMzKAm8QxfKpvh6OwQSXaW1CdZPoCaQuLwAq7VZMNuhJmZR4g5281s2ECt658sldnJfdpSZZaxUGMQ==",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "4.27.0", "@typescript-eslint/types": "4.28.0",
"@typescript-eslint/visitor-keys": "4.27.0", "@typescript-eslint/visitor-keys": "4.28.0",
"debug": "^4.3.1", "debug": "^4.3.1",
"globby": "^11.0.3", "globby": "^11.0.3",
"is-glob": "^4.0.1", "is-glob": "^4.0.1",
@ -2730,9 +2729,9 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree/node_modules/globby": { "node_modules/@typescript-eslint/typescript-estree/node_modules/globby": {
"version": "11.0.3", "version": "11.0.4",
"resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz", "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz",
"integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==", "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==",
"dependencies": { "dependencies": {
"array-union": "^2.1.0", "array-union": "^2.1.0",
"dir-glob": "^3.0.1", "dir-glob": "^3.0.1",
@ -2749,11 +2748,11 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "4.27.0", "version": "4.28.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.27.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.0.tgz",
"integrity": "sha512-es0GRYNZp0ieckZ938cEANfEhsfHrzuLrePukLKtY3/KPXcq1Xd555Mno9/GOgXhKzn0QfkDLVgqWO3dGY80bg==", "integrity": "sha512-PjJyTWwrlrvM5jazxYF5ZPs/nl0kHDZMVbuIcbpawVXaDPelp3+S9zpOz5RmVUfS/fD5l5+ZXNKnWhNYjPzCvw==",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "4.27.0", "@typescript-eslint/types": "4.28.0",
"eslint-visitor-keys": "^2.0.0" "eslint-visitor-keys": "^2.0.0"
}, },
"engines": { "engines": {
@ -3476,9 +3475,9 @@
} }
}, },
"node_modules/codemirror": { "node_modules/codemirror": {
"version": "5.61.1", "version": "5.62.0",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.61.1.tgz", "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.62.0.tgz",
"integrity": "sha512-+D1NZjAucuzE93vJGbAaXzvoBHwp9nJZWWWF9utjv25+5AZUiah6CIlfb4ikG4MoDsFsCG8niiJH5++OO2LgIQ==" "integrity": "sha512-Xnl3304iCc8nyVZuRkzDVVwc794uc9QNX0UcPGeNic1fbzkSrO4l4GVXho9tRNKBgPYZXgocUqXyfIv3BILhCQ=="
}, },
"node_modules/collection-visit": { "node_modules/collection-visit": {
"version": "1.0.0", "version": "1.0.0",
@ -3579,9 +3578,9 @@
} }
}, },
"node_modules/core-js-compat": { "node_modules/core-js-compat": {
"version": "3.14.0", "version": "3.15.0",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.14.0.tgz", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.15.0.tgz",
"integrity": "sha512-R4NS2eupxtiJU+VwgkF9WTpnSfZW4pogwKHd8bclWU2sp93Pr5S1uYJI84cMOubJRou7bcfL0vmwtLslWN5p3A==", "integrity": "sha512-8X6lWsG+s7IfOKzV93a7fRYfWRZobOfjw5V5rrq43Vh/W+V6qYxl7Akalsvgab4PFT/4L/pjQbdBUEM36NXKrw==",
"dependencies": { "dependencies": {
"browserslist": "^4.16.6", "browserslist": "^4.16.6",
"semver": "7.0.0" "semver": "7.0.0"
@ -3862,9 +3861,9 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "7.28.0", "version": "7.29.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.28.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.29.0.tgz",
"integrity": "sha512-UMfH0VSjP0G4p3EWirscJEQ/cHqnT/iuH6oNZOB94nBjWbMnhGEPxsZm1eyIW0C/9jLI0Fow4W5DXLjEI7mn1g==", "integrity": "sha512-82G/JToB9qIy/ArBzIWG9xvvwL3R86AlCjtGw+A29OMZDqhTybz/MByORSukGxeI+YPCR4coYyITKk8BFH9nDA==",
"dependencies": { "dependencies": {
"@babel/code-frame": "7.12.11", "@babel/code-frame": "7.12.11",
"@eslint/eslintrc": "^0.4.2", "@eslint/eslintrc": "^0.4.2",
@ -6771,9 +6770,9 @@
} }
}, },
"node_modules/rollup": { "node_modules/rollup": {
"version": "2.51.2", "version": "2.52.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.51.2.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.52.2.tgz",
"integrity": "sha512-ReV2eGEadA7hmXSzjxdDKs10neqH2QURf2RxJ6ayAlq93ugy6qIvXMmbc5cWMGCDh1h5T4thuWO1e2VNbMq8FA==", "integrity": "sha512-4RlFC3k2BIHlUsJ9mGd8OO+9Lm2eDF5P7+6DNQOp5sx+7N/1tFM01kELfbxlMX3MxT6owvLB1ln4S3QvvQlbUA==",
"bin": { "bin": {
"rollup": "dist/bin/rollup" "rollup": "dist/bin/rollup"
}, },
@ -6781,7 +6780,7 @@
"node": ">=10.0.0" "node": ">=10.0.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"fsevents": "~2.3.1" "fsevents": "~2.3.2"
} }
}, },
"node_modules/rollup-plugin-commonjs": { "node_modules/rollup-plugin-commonjs": {
@ -7605,9 +7604,9 @@
} }
}, },
"node_modules/typescript": { "node_modules/typescript": {
"version": "4.3.2", "version": "4.3.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.4.tgz",
"integrity": "sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==", "integrity": "sha512-uauPG7XZn9F/mo+7MrsRjyvbxFpzemRjKEZXS4AK83oP2KKOJPvb+9cO/gmnv8arWZvhnjVOXz7B49m1l0e9Ew==",
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
@ -8066,9 +8065,9 @@
} }
}, },
"@babel/compat-data": { "@babel/compat-data": {
"version": "7.14.5", "version": "7.14.7",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.5.tgz", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.7.tgz",
"integrity": "sha512-kixrYn4JwfAVPa0f2yfzc2AWti6WRRyO3XjWW5PJAvtE11qhSayrrcrEnee05KAtNaPC+EwehE8Qt1UedEVB8w==" "integrity": "sha512-nS6dZaISCXJ3+518CWiBfEr//gHyMO02uDxBkXTKZDN5POruCnOZ1N4YBRZDCabwF8nZMWBpRxIicmXtBs+fvw=="
}, },
"@babel/core": { "@babel/core": {
"version": "7.14.6", "version": "7.14.6",
@ -8380,9 +8379,9 @@
} }
}, },
"@babel/plugin-proposal-async-generator-functions": { "@babel/plugin-proposal-async-generator-functions": {
"version": "7.14.5", "version": "7.14.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.14.5.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.14.7.tgz",
"integrity": "sha512-tbD/CG3l43FIXxmu4a7RBe4zH7MLJ+S/lFowPFO7HetS2hyOZ/0nnnznegDuzFzfkyQYTxqdTH/hKmuBngaDAA==", "integrity": "sha512-RK8Wj7lXLY3bqei69/cc25gwS5puEc3dknoFPFbqfy3XxYQBQFvu4ioWpafMBAB+L9NyptQK4nMOa5Xz16og8Q==",
"requires": { "requires": {
"@babel/helper-plugin-utils": "^7.14.5", "@babel/helper-plugin-utils": "^7.14.5",
"@babel/helper-remap-async-to-generator": "^7.14.5", "@babel/helper-remap-async-to-generator": "^7.14.5",
@ -8473,11 +8472,11 @@
} }
}, },
"@babel/plugin-proposal-object-rest-spread": { "@babel/plugin-proposal-object-rest-spread": {
"version": "7.14.5", "version": "7.14.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.14.5.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.14.7.tgz",
"integrity": "sha512-VzMyY6PWNPPT3pxc5hi9LloKNr4SSrVCg7Yr6aZpW4Ym07r7KqSU/QXYwjXLVxqwSv0t/XSXkFoKBPUkZ8vb2A==", "integrity": "sha512-082hsZz+sVabfmDWo1Oct1u1AgbKbUAyVgmX4otIc7bdsRgHBXwTwb3DpDmD4Eyyx6DNiuz5UAATT655k+kL5g==",
"requires": { "requires": {
"@babel/compat-data": "^7.14.5", "@babel/compat-data": "^7.14.7",
"@babel/helper-compilation-targets": "^7.14.5", "@babel/helper-compilation-targets": "^7.14.5",
"@babel/helper-plugin-utils": "^7.14.5", "@babel/helper-plugin-utils": "^7.14.5",
"@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
@ -8732,9 +8731,9 @@
} }
}, },
"@babel/plugin-transform-destructuring": { "@babel/plugin-transform-destructuring": {
"version": "7.14.5", "version": "7.14.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.14.5.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.14.7.tgz",
"integrity": "sha512-wU9tYisEbRMxqDezKUqC9GleLycCRoUsai9ddlsq54r8QRLaeEhc+d+9DqCG+kV9W2GgQjTZESPTpn5bAFMDww==", "integrity": "sha512-0mDE99nK+kVh3xlc5vKwB6wnP9ecuSj+zQCa/n0voENtP/zymdT4HH6QEb65wjjcbqr1Jb/7z9Qp7TF5FtwYGw==",
"requires": { "requires": {
"@babel/helper-plugin-utils": "^7.14.5" "@babel/helper-plugin-utils": "^7.14.5"
} }
@ -8841,9 +8840,9 @@
} }
}, },
"@babel/plugin-transform-named-capturing-groups-regex": { "@babel/plugin-transform-named-capturing-groups-regex": {
"version": "7.14.5", "version": "7.14.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.14.5.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.14.7.tgz",
"integrity": "sha512-+Xe5+6MWFo311U8SchgeX5c1+lJM+eZDBZgD+tvXu9VVQPXwwVzeManMMjYX6xw2HczngfOSZjoFYKwdeB/Jvw==", "integrity": "sha512-DTNOTaS7TkW97xsDMrp7nycUVh6sn/eq22VaxWfEdzuEbRsiaOU0pqU7DlyUGHVsbQbSghvjKRpEl+nUCKGQSg==",
"requires": { "requires": {
"@babel/helper-create-regexp-features-plugin": "^7.14.5" "@babel/helper-create-regexp-features-plugin": "^7.14.5"
} }
@ -8926,9 +8925,9 @@
} }
}, },
"@babel/plugin-transform-spread": { "@babel/plugin-transform-spread": {
"version": "7.14.5", "version": "7.14.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.14.5.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.14.6.tgz",
"integrity": "sha512-/3iqoQdiWergnShZYl0xACb4ADeYCJ7X/RgmwtXshn6cIvautRPAFzhd58frQlokLO6Jb4/3JXvmm6WNTPtiTw==", "integrity": "sha512-Zr0x0YroFJku7n7+/HH3A2eIrGMjbmAIbJSVv0IZ+t3U2WUQUA64S/oeied2e+MaGSjmt4alzBCsK9E8gh+fag==",
"requires": { "requires": {
"@babel/helper-plugin-utils": "^7.14.5", "@babel/helper-plugin-utils": "^7.14.5",
"@babel/helper-skip-transparent-expression-wrappers": "^7.14.5" "@babel/helper-skip-transparent-expression-wrappers": "^7.14.5"
@ -8986,16 +8985,16 @@
} }
}, },
"@babel/preset-env": { "@babel/preset-env": {
"version": "7.14.5", "version": "7.14.7",
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.14.5.tgz", "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.14.7.tgz",
"integrity": "sha512-ci6TsS0bjrdPpWGnQ+m4f+JSSzDKlckqKIJJt9UZ/+g7Zz9k0N8lYU8IeLg/01o2h8LyNZDMLGgRLDTxpudLsA==", "integrity": "sha512-itOGqCKLsSUl0Y+1nSfhbuuOlTs0MJk2Iv7iSH+XT/mR8U1zRLO7NjWlYXB47yhK4J/7j+HYty/EhFZDYKa/VA==",
"requires": { "requires": {
"@babel/compat-data": "^7.14.5", "@babel/compat-data": "^7.14.7",
"@babel/helper-compilation-targets": "^7.14.5", "@babel/helper-compilation-targets": "^7.14.5",
"@babel/helper-plugin-utils": "^7.14.5", "@babel/helper-plugin-utils": "^7.14.5",
"@babel/helper-validator-option": "^7.14.5", "@babel/helper-validator-option": "^7.14.5",
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.14.5", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.14.5",
"@babel/plugin-proposal-async-generator-functions": "^7.14.5", "@babel/plugin-proposal-async-generator-functions": "^7.14.7",
"@babel/plugin-proposal-class-properties": "^7.14.5", "@babel/plugin-proposal-class-properties": "^7.14.5",
"@babel/plugin-proposal-class-static-block": "^7.14.5", "@babel/plugin-proposal-class-static-block": "^7.14.5",
"@babel/plugin-proposal-dynamic-import": "^7.14.5", "@babel/plugin-proposal-dynamic-import": "^7.14.5",
@ -9004,7 +9003,7 @@
"@babel/plugin-proposal-logical-assignment-operators": "^7.14.5", "@babel/plugin-proposal-logical-assignment-operators": "^7.14.5",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5",
"@babel/plugin-proposal-numeric-separator": "^7.14.5", "@babel/plugin-proposal-numeric-separator": "^7.14.5",
"@babel/plugin-proposal-object-rest-spread": "^7.14.5", "@babel/plugin-proposal-object-rest-spread": "^7.14.7",
"@babel/plugin-proposal-optional-catch-binding": "^7.14.5", "@babel/plugin-proposal-optional-catch-binding": "^7.14.5",
"@babel/plugin-proposal-optional-chaining": "^7.14.5", "@babel/plugin-proposal-optional-chaining": "^7.14.5",
"@babel/plugin-proposal-private-methods": "^7.14.5", "@babel/plugin-proposal-private-methods": "^7.14.5",
@ -9030,7 +9029,7 @@
"@babel/plugin-transform-block-scoping": "^7.14.5", "@babel/plugin-transform-block-scoping": "^7.14.5",
"@babel/plugin-transform-classes": "^7.14.5", "@babel/plugin-transform-classes": "^7.14.5",
"@babel/plugin-transform-computed-properties": "^7.14.5", "@babel/plugin-transform-computed-properties": "^7.14.5",
"@babel/plugin-transform-destructuring": "^7.14.5", "@babel/plugin-transform-destructuring": "^7.14.7",
"@babel/plugin-transform-dotall-regex": "^7.14.5", "@babel/plugin-transform-dotall-regex": "^7.14.5",
"@babel/plugin-transform-duplicate-keys": "^7.14.5", "@babel/plugin-transform-duplicate-keys": "^7.14.5",
"@babel/plugin-transform-exponentiation-operator": "^7.14.5", "@babel/plugin-transform-exponentiation-operator": "^7.14.5",
@ -9042,7 +9041,7 @@
"@babel/plugin-transform-modules-commonjs": "^7.14.5", "@babel/plugin-transform-modules-commonjs": "^7.14.5",
"@babel/plugin-transform-modules-systemjs": "^7.14.5", "@babel/plugin-transform-modules-systemjs": "^7.14.5",
"@babel/plugin-transform-modules-umd": "^7.14.5", "@babel/plugin-transform-modules-umd": "^7.14.5",
"@babel/plugin-transform-named-capturing-groups-regex": "^7.14.5", "@babel/plugin-transform-named-capturing-groups-regex": "^7.14.7",
"@babel/plugin-transform-new-target": "^7.14.5", "@babel/plugin-transform-new-target": "^7.14.5",
"@babel/plugin-transform-object-super": "^7.14.5", "@babel/plugin-transform-object-super": "^7.14.5",
"@babel/plugin-transform-parameters": "^7.14.5", "@babel/plugin-transform-parameters": "^7.14.5",
@ -9050,7 +9049,7 @@
"@babel/plugin-transform-regenerator": "^7.14.5", "@babel/plugin-transform-regenerator": "^7.14.5",
"@babel/plugin-transform-reserved-words": "^7.14.5", "@babel/plugin-transform-reserved-words": "^7.14.5",
"@babel/plugin-transform-shorthand-properties": "^7.14.5", "@babel/plugin-transform-shorthand-properties": "^7.14.5",
"@babel/plugin-transform-spread": "^7.14.5", "@babel/plugin-transform-spread": "^7.14.6",
"@babel/plugin-transform-sticky-regex": "^7.14.5", "@babel/plugin-transform-sticky-regex": "^7.14.5",
"@babel/plugin-transform-template-literals": "^7.14.5", "@babel/plugin-transform-template-literals": "^7.14.5",
"@babel/plugin-transform-typeof-symbol": "^7.14.5", "@babel/plugin-transform-typeof-symbol": "^7.14.5",
@ -9061,7 +9060,7 @@
"babel-plugin-polyfill-corejs2": "^0.2.2", "babel-plugin-polyfill-corejs2": "^0.2.2",
"babel-plugin-polyfill-corejs3": "^0.2.2", "babel-plugin-polyfill-corejs3": "^0.2.2",
"babel-plugin-polyfill-regenerator": "^0.2.2", "babel-plugin-polyfill-regenerator": "^0.2.2",
"core-js-compat": "^3.14.0", "core-js-compat": "^3.15.0",
"semver": "^6.3.0" "semver": "^6.3.0"
}, },
"dependencies": { "dependencies": {
@ -9431,9 +9430,9 @@
} }
}, },
"@lingui/core": { "@lingui/core": {
"version": "3.10.3", "version": "3.10.4",
"resolved": "https://registry.npmjs.org/@lingui/core/-/core-3.10.3.tgz", "resolved": "https://registry.npmjs.org/@lingui/core/-/core-3.10.4.tgz",
"integrity": "sha512-BiuWi5xPpQa27oIWWnkOYNx4qTMdMeu7vp5y1AGPYQ/4SO0rHfAtOxXtvRU/ktVwht/lIgx5Ygq5J3F+XLvOQA==", "integrity": "sha512-V9QKQ9PFMTPrGGz2PaeKHZcxFikQZzJbptyQbVFJdXaKhdE2RH6HhdK1PIziDHqp6ZWPthVIfVLURT3ku8eu5w==",
"requires": { "requires": {
"@babel/runtime": "^7.11.2", "@babel/runtime": "^7.11.2",
"make-plural": "^6.2.2", "make-plural": "^6.2.2",
@ -9670,13 +9669,13 @@
} }
}, },
"@sentry/browser": { "@sentry/browser": {
"version": "6.7.1", "version": "6.7.2",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.7.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.7.2.tgz",
"integrity": "sha512-R5PYx4TTvifcU790XkK6JVGwavKaXwycDU0MaAwfc4Vf7BLm5KCNJCsDySu1RPAap/017MVYf54p6dWvKiRviA==", "integrity": "sha512-Lv0Ne1QcesyGAhVcQDfQa3hDPR/MhPSDTMg3xFi+LxqztchVc4w/ynzR0wCZFb8KIHpTj5SpJHfxpDhXYMtS9g==",
"requires": { "requires": {
"@sentry/core": "6.7.1", "@sentry/core": "6.7.2",
"@sentry/types": "6.7.1", "@sentry/types": "6.7.2",
"@sentry/utils": "6.7.1", "@sentry/utils": "6.7.2",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -9688,14 +9687,14 @@
} }
}, },
"@sentry/core": { "@sentry/core": {
"version": "6.7.1", "version": "6.7.2",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.7.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.7.2.tgz",
"integrity": "sha512-VAv8OR/7INn2JfiLcuop4hfDcyC7mfL9fdPndQEhlacjmw8gRrgXjR7qyhnCTgzFLkHI7V5bcdIzA83TRPYQpA==", "integrity": "sha512-NTZqwN5nR94yrXmSfekoPs1mIFuKvf8esdIW/DadwSKWAdLJwQTJY9xK/8PQv+SEzd7wiitPAx+mCw2By1xiNQ==",
"requires": { "requires": {
"@sentry/hub": "6.7.1", "@sentry/hub": "6.7.2",
"@sentry/minimal": "6.7.1", "@sentry/minimal": "6.7.2",
"@sentry/types": "6.7.1", "@sentry/types": "6.7.2",
"@sentry/utils": "6.7.1", "@sentry/utils": "6.7.2",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -9707,12 +9706,12 @@
} }
}, },
"@sentry/hub": { "@sentry/hub": {
"version": "6.7.1", "version": "6.7.2",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.7.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.7.2.tgz",
"integrity": "sha512-eVCTWvvcp6xa0A5GGNHMQEWslmKPlisE5rGmsV/kjvSUv3zSrI0eIDfb51ikdnCiBjHpK2NBWP8Vy8cZOEJegg==", "integrity": "sha512-05qVW6ymChJsXag4+fYCQokW3AcABIgcqrVYZUBf6GMU/Gbz5SJqpV7y1+njwWvnPZydMncP9LaDVpMKbE7UYQ==",
"requires": { "requires": {
"@sentry/types": "6.7.1", "@sentry/types": "6.7.2",
"@sentry/utils": "6.7.1", "@sentry/utils": "6.7.2",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -9724,12 +9723,12 @@
} }
}, },
"@sentry/minimal": { "@sentry/minimal": {
"version": "6.7.1", "version": "6.7.2",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.7.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.7.2.tgz",
"integrity": "sha512-HDDPEnQRD6hC0qaHdqqKDStcdE1KhkFh0RCtJNMCDn0zpav8Dj9AteF70x6kLSlliAJ/JFwi6AmQrLz+FxPexw==", "integrity": "sha512-jkpwFv2GFHoVl5vnK+9/Q+Ea8eVdbJ3hn3/Dqq9MOLFnVK7ED6MhdHKLT79puGSFj+85OuhM5m2Q44mIhyS5mw==",
"requires": { "requires": {
"@sentry/hub": "6.7.1", "@sentry/hub": "6.7.2",
"@sentry/types": "6.7.1", "@sentry/types": "6.7.2",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -9741,14 +9740,14 @@
} }
}, },
"@sentry/tracing": { "@sentry/tracing": {
"version": "6.7.1", "version": "6.7.2",
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.7.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.7.2.tgz",
"integrity": "sha512-wyS3nWNl5mzaC1qZ2AIp1hjXnfO9EERjMIJjCihs2LWBz1r3efxrHxJHs8wXlNWvrT3KLhq/7vvF5CdU82uPeQ==", "integrity": "sha512-juKlI7FICKONWJFJxDxerj0A+8mNRhmtrdR+OXFqOkqSAy/QXlSFZcA/j//O19k2CfwK1BrvoMcQ/4gnffUOVg==",
"requires": { "requires": {
"@sentry/hub": "6.7.1", "@sentry/hub": "6.7.2",
"@sentry/minimal": "6.7.1", "@sentry/minimal": "6.7.2",
"@sentry/types": "6.7.1", "@sentry/types": "6.7.2",
"@sentry/utils": "6.7.1", "@sentry/utils": "6.7.2",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -9760,16 +9759,16 @@
} }
}, },
"@sentry/types": { "@sentry/types": {
"version": "6.7.1", "version": "6.7.2",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.7.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.7.2.tgz",
"integrity": "sha512-9AO7HKoip2MBMNQJEd6+AKtjj2+q9Ze4ooWUdEvdOVSt5drg7BGpK221/p9JEOyJAZwEPEXdcMd3VAIMiOb4MA==" "integrity": "sha512-h21Go/PfstUN+ZV6SbwRSZVg9GXRJWdLfHoO5PSVb3TVEMckuxk8tAE57/u+UZDwX8wu+Xyon2TgsKpiWKxqUg=="
}, },
"@sentry/utils": { "@sentry/utils": {
"version": "6.7.1", "version": "6.7.2",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.7.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.7.2.tgz",
"integrity": "sha512-Tq2otdbWlHAkctD+EWTYKkEx6BL1Qn3Z/imkO06/PvzpWvVhJWQ5qHAzz5XnwwqNHyV03KVzYB6znq1Bea9HuA==", "integrity": "sha512-9COL7aaBbe61Hp5BlArtXZ1o/cxli1NGONLPrVT4fMyeQFmLonhUiy77NdsW19XnvhvaA+2IoV5dg3dnFiF/og==",
"requires": { "requires": {
"@sentry/types": "6.7.1", "@sentry/types": "6.7.2",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -9926,29 +9925,28 @@
"integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==" "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA=="
}, },
"@typescript-eslint/eslint-plugin": { "@typescript-eslint/eslint-plugin": {
"version": "4.27.0", "version": "4.28.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.27.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.0.tgz",
"integrity": "sha512-DsLqxeUfLVNp3AO7PC3JyaddmEHTtI9qTSAs+RB6ja27QvIM0TA8Cizn1qcS6vOu+WDLFJzkwkgweiyFhssDdQ==", "integrity": "sha512-KcF6p3zWhf1f8xO84tuBailV5cN92vhS+VT7UJsPzGBm9VnQqfI9AsiMUFUCYHTYPg1uCCo+HyiDnpDuvkAMfQ==",
"requires": { "requires": {
"@typescript-eslint/experimental-utils": "4.27.0", "@typescript-eslint/experimental-utils": "4.28.0",
"@typescript-eslint/scope-manager": "4.27.0", "@typescript-eslint/scope-manager": "4.28.0",
"debug": "^4.3.1", "debug": "^4.3.1",
"functional-red-black-tree": "^1.0.1", "functional-red-black-tree": "^1.0.1",
"lodash": "^4.17.21",
"regexpp": "^3.1.0", "regexpp": "^3.1.0",
"semver": "^7.3.5", "semver": "^7.3.5",
"tsutils": "^3.21.0" "tsutils": "^3.21.0"
} }
}, },
"@typescript-eslint/experimental-utils": { "@typescript-eslint/experimental-utils": {
"version": "4.27.0", "version": "4.28.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.27.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.0.tgz",
"integrity": "sha512-n5NlbnmzT2MXlyT+Y0Jf0gsmAQzCnQSWXKy4RGSXVStjDvS5we9IWbh7qRVKdGcxT0WYlgcCYUK/HRg7xFhvjQ==", "integrity": "sha512-9XD9s7mt3QWMk82GoyUpc/Ji03vz4T5AYlHF9DcoFNfJ/y3UAclRsfGiE2gLfXtyC+JRA3trR7cR296TEb1oiQ==",
"requires": { "requires": {
"@types/json-schema": "^7.0.7", "@types/json-schema": "^7.0.7",
"@typescript-eslint/scope-manager": "4.27.0", "@typescript-eslint/scope-manager": "4.28.0",
"@typescript-eslint/types": "4.27.0", "@typescript-eslint/types": "4.28.0",
"@typescript-eslint/typescript-estree": "4.27.0", "@typescript-eslint/typescript-estree": "4.28.0",
"eslint-scope": "^5.1.1", "eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0" "eslint-utils": "^3.0.0"
}, },
@ -9964,37 +9962,37 @@
} }
}, },
"@typescript-eslint/parser": { "@typescript-eslint/parser": {
"version": "4.27.0", "version": "4.28.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.27.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.0.tgz",
"integrity": "sha512-XpbxL+M+gClmJcJ5kHnUpBGmlGdgNvy6cehgR6ufyxkEJMGP25tZKCaKyC0W/JVpuhU3VU1RBn7SYUPKSMqQvQ==", "integrity": "sha512-7x4D22oPY8fDaOCvkuXtYYTQ6mTMmkivwEzS+7iml9F9VkHGbbZ3x4fHRwxAb5KeuSkLqfnYjs46tGx2Nour4A==",
"requires": { "requires": {
"@typescript-eslint/scope-manager": "4.27.0", "@typescript-eslint/scope-manager": "4.28.0",
"@typescript-eslint/types": "4.27.0", "@typescript-eslint/types": "4.28.0",
"@typescript-eslint/typescript-estree": "4.27.0", "@typescript-eslint/typescript-estree": "4.28.0",
"debug": "^4.3.1" "debug": "^4.3.1"
} }
}, },
"@typescript-eslint/scope-manager": { "@typescript-eslint/scope-manager": {
"version": "4.27.0", "version": "4.28.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.27.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.0.tgz",
"integrity": "sha512-DY73jK6SEH6UDdzc6maF19AHQJBFVRf6fgAXHPXCGEmpqD4vYgPEzqpFz1lf/daSbOcMpPPj9tyXXDPW2XReAw==", "integrity": "sha512-eCALCeScs5P/EYjwo6se9bdjtrh8ByWjtHzOkC4Tia6QQWtQr3PHovxh3TdYTuFcurkYI4rmFsRFpucADIkseg==",
"requires": { "requires": {
"@typescript-eslint/types": "4.27.0", "@typescript-eslint/types": "4.28.0",
"@typescript-eslint/visitor-keys": "4.27.0" "@typescript-eslint/visitor-keys": "4.28.0"
} }
}, },
"@typescript-eslint/types": { "@typescript-eslint/types": {
"version": "4.27.0", "version": "4.28.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.27.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.0.tgz",
"integrity": "sha512-I4ps3SCPFCKclRcvnsVA/7sWzh7naaM/b4pBO2hVxnM3wrU51Lveybdw5WoIktU/V4KfXrTt94V9b065b/0+wA==" "integrity": "sha512-p16xMNKKoiJCVZY5PW/AfILw2xe1LfruTcfAKBj3a+wgNYP5I9ZEKNDOItoRt53p4EiPV6iRSICy8EPanG9ZVA=="
}, },
"@typescript-eslint/typescript-estree": { "@typescript-eslint/typescript-estree": {
"version": "4.27.0", "version": "4.28.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.27.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.0.tgz",
"integrity": "sha512-KH03GUsUj41sRLLEy2JHstnezgpS5VNhrJouRdmh6yNdQ+yl8w5LrSwBkExM+jWwCJa7Ct2c8yl8NdtNRyQO6g==", "integrity": "sha512-m19UQTRtxMzKAm8QxfKpvh6OwQSXaW1CdZPoCaQuLwAq7VZMNuhJmZR4g5281s2ECt658sldnJfdpSZZaxUGMQ==",
"requires": { "requires": {
"@typescript-eslint/types": "4.27.0", "@typescript-eslint/types": "4.28.0",
"@typescript-eslint/visitor-keys": "4.27.0", "@typescript-eslint/visitor-keys": "4.28.0",
"debug": "^4.3.1", "debug": "^4.3.1",
"globby": "^11.0.3", "globby": "^11.0.3",
"is-glob": "^4.0.1", "is-glob": "^4.0.1",
@ -10003,9 +10001,9 @@
}, },
"dependencies": { "dependencies": {
"globby": { "globby": {
"version": "11.0.3", "version": "11.0.4",
"resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz", "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz",
"integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==", "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==",
"requires": { "requires": {
"array-union": "^2.1.0", "array-union": "^2.1.0",
"dir-glob": "^3.0.1", "dir-glob": "^3.0.1",
@ -10018,11 +10016,11 @@
} }
}, },
"@typescript-eslint/visitor-keys": { "@typescript-eslint/visitor-keys": {
"version": "4.27.0", "version": "4.28.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.27.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.0.tgz",
"integrity": "sha512-es0GRYNZp0ieckZ938cEANfEhsfHrzuLrePukLKtY3/KPXcq1Xd555Mno9/GOgXhKzn0QfkDLVgqWO3dGY80bg==", "integrity": "sha512-PjJyTWwrlrvM5jazxYF5ZPs/nl0kHDZMVbuIcbpawVXaDPelp3+S9zpOz5RmVUfS/fD5l5+ZXNKnWhNYjPzCvw==",
"requires": { "requires": {
"@typescript-eslint/types": "4.27.0", "@typescript-eslint/types": "4.28.0",
"eslint-visitor-keys": "^2.0.0" "eslint-visitor-keys": "^2.0.0"
} }
}, },
@ -10591,9 +10589,9 @@
"integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4="
}, },
"codemirror": { "codemirror": {
"version": "5.61.1", "version": "5.62.0",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.61.1.tgz", "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.62.0.tgz",
"integrity": "sha512-+D1NZjAucuzE93vJGbAaXzvoBHwp9nJZWWWF9utjv25+5AZUiah6CIlfb4ikG4MoDsFsCG8niiJH5++OO2LgIQ==" "integrity": "sha512-Xnl3304iCc8nyVZuRkzDVVwc794uc9QNX0UcPGeNic1fbzkSrO4l4GVXho9tRNKBgPYZXgocUqXyfIv3BILhCQ=="
}, },
"collection-visit": { "collection-visit": {
"version": "1.0.0", "version": "1.0.0",
@ -10681,9 +10679,9 @@
"integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40="
}, },
"core-js-compat": { "core-js-compat": {
"version": "3.14.0", "version": "3.15.0",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.14.0.tgz", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.15.0.tgz",
"integrity": "sha512-R4NS2eupxtiJU+VwgkF9WTpnSfZW4pogwKHd8bclWU2sp93Pr5S1uYJI84cMOubJRou7bcfL0vmwtLslWN5p3A==", "integrity": "sha512-8X6lWsG+s7IfOKzV93a7fRYfWRZobOfjw5V5rrq43Vh/W+V6qYxl7Akalsvgab4PFT/4L/pjQbdBUEM36NXKrw==",
"requires": { "requires": {
"browserslist": "^4.16.6", "browserslist": "^4.16.6",
"semver": "7.0.0" "semver": "7.0.0"
@ -10901,9 +10899,9 @@
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
}, },
"eslint": { "eslint": {
"version": "7.28.0", "version": "7.29.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.28.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.29.0.tgz",
"integrity": "sha512-UMfH0VSjP0G4p3EWirscJEQ/cHqnT/iuH6oNZOB94nBjWbMnhGEPxsZm1eyIW0C/9jLI0Fow4W5DXLjEI7mn1g==", "integrity": "sha512-82G/JToB9qIy/ArBzIWG9xvvwL3R86AlCjtGw+A29OMZDqhTybz/MByORSukGxeI+YPCR4coYyITKk8BFH9nDA==",
"requires": { "requires": {
"@babel/code-frame": "7.12.11", "@babel/code-frame": "7.12.11",
"@eslint/eslintrc": "^0.4.2", "@eslint/eslintrc": "^0.4.2",
@ -13202,11 +13200,11 @@
} }
}, },
"rollup": { "rollup": {
"version": "2.51.2", "version": "2.52.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.51.2.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.52.2.tgz",
"integrity": "sha512-ReV2eGEadA7hmXSzjxdDKs10neqH2QURf2RxJ6ayAlq93ugy6qIvXMmbc5cWMGCDh1h5T4thuWO1e2VNbMq8FA==", "integrity": "sha512-4RlFC3k2BIHlUsJ9mGd8OO+9Lm2eDF5P7+6DNQOp5sx+7N/1tFM01kELfbxlMX3MxT6owvLB1ln4S3QvvQlbUA==",
"requires": { "requires": {
"fsevents": "~2.3.1" "fsevents": "~2.3.2"
} }
}, },
"rollup-plugin-commonjs": { "rollup-plugin-commonjs": {
@ -13898,9 +13896,9 @@
} }
}, },
"typescript": { "typescript": {
"version": "4.3.2", "version": "4.3.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.4.tgz",
"integrity": "sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==" "integrity": "sha512-uauPG7XZn9F/mo+7MrsRjyvbxFpzemRjKEZXS4AK83oP2KKOJPvb+9cO/gmnv8arWZvhnjVOXz7B49m1l0e9Ew=="
}, },
"uglify-js": { "uglify-js": {
"version": "3.13.0", "version": "3.13.0",

View File

@ -41,11 +41,11 @@
"@babel/core": "^7.14.6", "@babel/core": "^7.14.6",
"@babel/plugin-proposal-decorators": "^7.14.5", "@babel/plugin-proposal-decorators": "^7.14.5",
"@babel/plugin-transform-runtime": "^7.14.5", "@babel/plugin-transform-runtime": "^7.14.5",
"@babel/preset-env": "^7.14.5", "@babel/preset-env": "^7.14.7",
"@babel/preset-typescript": "^7.14.5", "@babel/preset-typescript": "^7.14.5",
"@fortawesome/fontawesome-free": "^5.15.3", "@fortawesome/fontawesome-free": "^5.15.3",
"@lingui/cli": "^3.10.2", "@lingui/cli": "^3.10.2",
"@lingui/core": "^3.10.3", "@lingui/core": "^3.10.4",
"@lingui/macro": "^3.10.2", "@lingui/macro": "^3.10.2",
"@patternfly/patternfly": "^4.108.2", "@patternfly/patternfly": "^4.108.2",
"@polymer/iron-form": "^3.0.1", "@polymer/iron-form": "^3.0.1",
@ -53,22 +53,22 @@
"@rollup/plugin-babel": "^5.3.0", "@rollup/plugin-babel": "^5.3.0",
"@rollup/plugin-replace": "^2.4.2", "@rollup/plugin-replace": "^2.4.2",
"@rollup/plugin-typescript": "^8.2.1", "@rollup/plugin-typescript": "^8.2.1",
"@sentry/browser": "^6.7.1", "@sentry/browser": "^6.7.2",
"@sentry/tracing": "^6.7.1", "@sentry/tracing": "^6.7.2",
"@types/chart.js": "^2.9.32", "@types/chart.js": "^2.9.32",
"@types/codemirror": "5.60.0", "@types/codemirror": "5.60.0",
"@types/grecaptcha": "^3.0.2", "@types/grecaptcha": "^3.0.2",
"@typescript-eslint/eslint-plugin": "^4.27.0", "@typescript-eslint/eslint-plugin": "^4.28.0",
"@typescript-eslint/parser": "^4.27.0", "@typescript-eslint/parser": "^4.28.0",
"@webcomponents/webcomponentsjs": "^2.5.0", "@webcomponents/webcomponentsjs": "^2.5.0",
"authentik-api": "file:api", "authentik-api": "file:api",
"babel-plugin-macros": "^3.1.0", "babel-plugin-macros": "^3.1.0",
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
"chart.js": "^3.3.2", "chart.js": "^3.3.2",
"chartjs-adapter-moment": "^1.0.0", "chartjs-adapter-moment": "^1.0.0",
"codemirror": "^5.61.1", "codemirror": "^5.62.0",
"construct-style-sheets-polyfill": "^2.4.16", "construct-style-sheets-polyfill": "^2.4.16",
"eslint": "^7.28.0", "eslint": "^7.29.0",
"eslint-config-google": "^0.14.0", "eslint-config-google": "^0.14.0",
"eslint-plugin-custom-elements": "0.0.2", "eslint-plugin-custom-elements": "0.0.2",
"eslint-plugin-lit": "^1.5.1", "eslint-plugin-lit": "^1.5.1",
@ -77,7 +77,7 @@
"lit-html": "^1.4.1", "lit-html": "^1.4.1",
"moment": "^2.29.1", "moment": "^2.29.1",
"rapidoc": "^9.0.0", "rapidoc": "^9.0.0",
"rollup": "^2.51.2", "rollup": "^2.52.2",
"rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-copy": "^3.4.0", "rollup-plugin-copy": "^3.4.0",
"rollup-plugin-cssimport": "^1.0.2", "rollup-plugin-cssimport": "^1.0.2",
@ -87,7 +87,7 @@
"rollup-plugin-terser": "^7.0.2", "rollup-plugin-terser": "^7.0.2",
"ts-lit-plugin": "^1.2.1", "ts-lit-plugin": "^1.2.1",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"typescript": "^4.3.2", "typescript": "^4.3.4",
"webcomponent-qr-code": "^1.0.5", "webcomponent-qr-code": "^1.0.5",
"yaml": "^1.10.2" "yaml": "^1.10.2"
}, },

View File

@ -68,7 +68,7 @@ html > form > input {
/* ensure background on non-flow pages match */ /* ensure background on non-flow pages match */
.pf-c-background-image::before { .pf-c-background-image::before {
background-image: var(--ak-flow-background, url("/static/dist/assets/images/flow_background.jpg")); background-image: var(--ak-flow-background);
background-position: center; background-position: center;
} }

View File

@ -3,7 +3,7 @@ export const SUCCESS_CLASS = "pf-m-success";
export const ERROR_CLASS = "pf-m-danger"; export const ERROR_CLASS = "pf-m-danger";
export const PROGRESS_CLASS = "pf-m-in-progress"; export const PROGRESS_CLASS = "pf-m-in-progress";
export const CURRENT_CLASS = "pf-m-current"; export const CURRENT_CLASS = "pf-m-current";
export const VERSION = "2021.6.1-rc6"; export const VERSION = "2021.6.2";
export const PAGE_SIZE = 20; export const PAGE_SIZE = 20;
export const EVENT_REFRESH = "ak-refresh"; export const EVENT_REFRESH = "ak-refresh";
export const EVENT_NOTIFICATION_TOGGLE = "ak-notification-toggle"; export const EVENT_NOTIFICATION_TOGGLE = "ak-notification-toggle";

View File

@ -54,7 +54,9 @@ export class ModalButton extends LitElement {
resetForms(): void { resetForms(): void {
this.querySelectorAll<HTMLFormElement>("[slot=form]").forEach(form => { this.querySelectorAll<HTMLFormElement>("[slot=form]").forEach(form => {
if ("resetForm" in form) {
form?.resetForm(); form?.resetForm();
}
}); });
} }

View File

@ -34,7 +34,9 @@ export abstract class TableModal<T> extends Table<T> {
resetForms(): void { resetForms(): void {
this.querySelectorAll<HTMLFormElement>("[slot=form]").forEach(form => { this.querySelectorAll<HTMLFormElement>("[slot=form]").forEach(form => {
if ("resetForm" in form) {
form?.resetForm(); form?.resetForm();
}
}); });
} }

View File

@ -67,6 +67,27 @@ export class AuthenticatorValidateStageForm extends ModelForm<AuthenticatorValid
${t`Stage-specific settings`} ${t`Stage-specific settings`}
</span> </span>
<div slot="body" class="pf-c-form"> <div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Device classes`}
?required=${true}
name="deviceClasses">
<select name="users" class="pf-c-form-control" multiple>
<option value=${DeviceClassesEnum.Static} ?selected=${this.isDeviceClassSelected(DeviceClassesEnum.Static)}>
${t`Static Tokens`}
</option>
<option value=${DeviceClassesEnum.Totp} ?selected=${this.isDeviceClassSelected(DeviceClassesEnum.Totp)}>
${t`TOTP Authenticators`}
</option>
<option value=${DeviceClassesEnum.Webauthn} ?selected=${this.isDeviceClassSelected(DeviceClassesEnum.Webauthn)}>
${t`WebAuthn Authenticators`}
</option>
<option value=${DeviceClassesEnum.Duo} ?selected=${this.isDeviceClassSelected(DeviceClassesEnum.Duo)}>
${t`Duo Authenticators`}
</option>
</select>
<p class="pf-c-form__helper-text">${t`Device classes which can be used to authenticate.`}</p>
<p class="pf-c-form__helper-text">${t`Hold control/command to select multiple items.`}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal <ak-form-element-horizontal
label=${t`Not configured action`} label=${t`Not configured action`}
?required=${true} ?required=${true}
@ -90,27 +111,6 @@ export class AuthenticatorValidateStageForm extends ModelForm<AuthenticatorValid
</option> </option>
</select> </select>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Device classes`}
?required=${true}
name="deviceClasses">
<select name="users" class="pf-c-form-control" multiple>
<option value=${DeviceClassesEnum.Static} ?selected=${this.isDeviceClassSelected(DeviceClassesEnum.Static)}>
${t`Static Tokens`}
</option>
<option value=${DeviceClassesEnum.Totp} ?selected=${this.isDeviceClassSelected(DeviceClassesEnum.Totp)}>
${t`TOTP Authenticators`}
</option>
<option value=${DeviceClassesEnum.Webauthn} ?selected=${this.isDeviceClassSelected(DeviceClassesEnum.Webauthn)}>
${t`WebAuthn Authenticators`}
</option>
<option value=${DeviceClassesEnum.Duo} ?selected=${this.isDeviceClassSelected(DeviceClassesEnum.Duo)}>
${t`Duo Authenticators`}
</option>
</select>
<p class="pf-c-form__helper-text">${t`Device classes which can be used to authenticate.`}</p>
<p class="pf-c-form__helper-text">${t`Hold control/command to select multiple items.`}</p>
</ak-form-element-horizontal>
${this.showConfigurationStage ? html` ${this.showConfigurationStage ? html`
<ak-form-element-horizontal <ak-form-element-horizontal
label=${t`Configuration stage`} label=${t`Configuration stage`}

View File

@ -49,7 +49,7 @@ export class TenantForm extends ModelForm<Tenant, string> {
<input type="text" value="${first(this.instance?.domain, window.location.host)}" class="pf-c-form-control" required> <input type="text" value="${first(this.instance?.domain, window.location.host)}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`Matching is done based on domain suffix, so if you enter domain.tld, foo.domain.tld will still match.`}</p> <p class="pf-c-form__helper-text">${t`Matching is done based on domain suffix, so if you enter domain.tld, foo.domain.tld will still match.`}</p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal name="default"> <ak-form-element-horizontal name="_default">
<div class="pf-c-check"> <div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?._default, false)}> <input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?._default, false)}>
<label class="pf-c-check__label"> <label class="pf-c-check__label">

View File

@ -0,0 +1,50 @@
---
title: Frontend-only development environment
---
If you want to only make changes on the UI, you don't need a backend running from source. You can user the docker-compose install with a few customizations.
1. Clone the git repo from https://github.com/goauthentik/authentik
2. In the cloned repository, follow the docker-compose installation instructions [here](../../docs/installation/docker-compose)
3. Add the following entry to your `.env` file:
```
AUTHENTIK_IMAGE=beryju.org/authentik/server
AUTHENTIK_TAG=gh-next
AUTHENTIK_OUTPOSTS__DOCKER_IMAGE_BASE=beryju.org/authentik/outpost-%(type)s:gh-next
```
This will cause authentik to use the beta images.
4. Create a `local.env.yml` file to tell authentik to use local files instead of the bundled ones:
```yaml
log_level: debug
web:
load_local_files: true
```
5. Add this volume mapping to your compose file
```yaml
version: '3.2'
services:
# [...]
server:
# [...]
volumes:
- ./web:/web
- ./local.env.yml:/local.env.yml
```
This makes the local web files and the config file available to the authentik server.
6. Run `docker-compose up -d` to apply those changes to your containers.
7. Run `make gen-web` in the project root directory to generate the API Client used by the web interfaces
8. `cd web`
9. Run `npm i` and then `npm run watch` to start the build process.
You can now access authentik on http://localhost:9000 (or https://localhost:9443).
You might also want to complete the initial setup under `/if/flow/initial-setup/`.

View File

@ -1,6 +1,5 @@
--- ---
title: Getting started title: Full development environment
slug: /
--- ---
## Backend ## Backend

View File

@ -0,0 +1,7 @@
---
title: Developer documentation
slug: /
---
Welcome to the authentik developer documentation. authentik is fully open source and can be found here: https://github.com/goauthentik/authentik

View File

@ -29,6 +29,10 @@ All of these variables can be set to values, but you can also use a URI-like for
- `AUTHENTIK_REDIS__CACHE_DB`: Database for caching, defaults to 0 - `AUTHENTIK_REDIS__CACHE_DB`: Database for caching, defaults to 0
- `AUTHENTIK_REDIS__MESSAGE_QUEUE_DB`: Database for the message queue, defaults to 1 - `AUTHENTIK_REDIS__MESSAGE_QUEUE_DB`: Database for the message queue, defaults to 1
- `AUTHENTIK_REDIS__WS_DB`: Database for websocket connections, defaults to 2 - `AUTHENTIK_REDIS__WS_DB`: Database for websocket connections, defaults to 2
- `AUTHENTIK_REDIS__CACHE_TIMEOUT`: Timeout for cached data until it expires in seconds, defaults to 300
- `AUTHENTIK_REDIS__CACHE_TIMEOUT_FLOWS`: Timeout for cached flow plans until they expire in seconds, defaults to 300
- `AUTHENTIK_REDIS__CACHE_TIMEOUT_POLICIES`: Timeout for cached polices until they expire in seconds, defaults to 300
- `AUTHENTIK_REDIS__CACHE_TIMEOUT_REPUTATION`: Timeout for cached reputation until they expire in seconds, defaults to 300
## authentik Settings ## authentik Settings
@ -100,16 +104,16 @@ Defaults to `info`.
Placeholder for outpost docker images. Default: `ghcr.io/goauthentik/%(type)s:%(version)s`. Placeholder for outpost docker images. Default: `ghcr.io/goauthentik/%(type)s:%(version)s`.
### AUTHENTIK_AUTHENTIK ### AUTHENTIK_AVATARS
- `AUTHENTIK_AUTHENTIK__AVATARS` Configure how authentik should show avatars for users. Following values can be set:
Controls which avatars are shown. Defaults to `gravatar`. Can be set to `none` to disable avatars. - `none`: Disables per-user avatars and just shows a 1x1 pixel transparent picture
- `gravatar`: Uses gravatar with the user's email address
- Any URL: If you want to use images hosted on another server, you can set any URL.
- `AUTHENTIK_AUTHENTIK__BRANDING__TITLE` Additionally, these placeholders can be used:
Branding title used throughout the UI. Defaults to `authentik`. - `%(username)s`: The user's username
- `%(mail_hash)s`: The email address, md5 hashed
- `AUTHENTIK_AUTHENTIK__BRANDING__LOGO` - `%(upn)s`: The user's UPN, if set (otherwise an empty string)
Logo shown in the sidebar and flow executions. Defaults to `/static/dist/assets/icons/icon_left_brand.svg`

View File

@ -12,11 +12,11 @@ This installation method is for test-setups and small-scale productive setups.
## Preparation ## Preparation
Download the latest `docker-compose.yml` from [here](https://raw.githubusercontent.com/goauthentik/authentik/version/2021.6.1-rc6/docker-compose.yml). Place it in a directory of your choice. Download the latest `docker-compose.yml` from [here](https://raw.githubusercontent.com/goauthentik/authentik/version/2021.6.2/docker-compose.yml). Place it in a directory of your choice.
To optionally enable error-reporting, run `echo AUTHENTIK_ERROR_REPORTING__ENABLED=true >> .env` To optionally enable error-reporting, run `echo AUTHENTIK_ERROR_REPORTING__ENABLED=true >> .env`
To optionally deploy a different version run `echo AUTHENTIK_TAG=2021.6.1-rc6 >> .env` To optionally deploy a different version run `echo AUTHENTIK_TAG=2021.6.2 >> .env`
If this is a fresh authentik install run the following commands to generate a password: If this is a fresh authentik install run the following commands to generate a password:
@ -74,7 +74,6 @@ Afterwards, run these commands to finish
```shell ```shell
docker-compose pull docker-compose pull
docker-compose up -d docker-compose up -d
docker-compose run --rm server migrate
``` ```
The compose file statically references the latest version available at the time of downloading, which can be overridden with the `SERVER_TAG` environment variable. The compose file statically references the latest version available at the time of downloading, which can be overridden with the `SERVER_TAG` environment variable.

View File

@ -55,13 +55,19 @@ Under Attribute mapping, set these values:
- Attribute to map the email address to.: `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress` - Attribute to map the email address to.: `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress`
- Attribute to map the users groups to.: `http://schemas.xmlsoap.org/claims/Group` - Attribute to map the users groups to.: `http://schemas.xmlsoap.org/claims/Group`
:::note
If Nextcloud is behind a reverse proxy you may need to force Nextcloud to use HTTPS.
To do this you will need to add the line `'overwriteprotocol' => 'https'` to `config.php` in the Nextcloud `config\config.php` file
See https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/reverse_proxy_configuration.html#overwrite-parameters for additional information
:::
## Group Quotas ## Group Quotas
Create a group for each different level of quota you want users to have. Set a custom attribute, for example called `nextcloud_quota`, to the quota you want, for example `15 GB`. Create a group for each different level of quota you want users to have. Set a custom attribute, for example called `nextcloud_quota`, to the quota you want, for example `15 GB`.
Afterwards, create a custom SAML Property Mapping with the name `SAML NextCloud Quota`. Afterwards, create a custom SAML Property Mapping with the name `SAML NextCloud Quota`.
Set the *SAML Name* to `nextcloud_quota`. Set the *SAML Name* to `nextcloud_quota`.
Set the *Expression* to `return user.group_attributes.get("nextcloud_quota", "1 GB")`, where `1 GB` is the default value for users that don't belong to another group (or have another value set). Set the *Expression* to `return user.group_attributes().get("nextcloud_quota", "1 GB")`, where `1 GB` is the default value for users that don't belong to another group (or have another value set).
## Admin Group ## Admin Group

View File

@ -0,0 +1,80 @@
---
title: Wekan
---
## What is Wekan
From https://github.com/wekan/wekan/wiki
:::note
Wekan is an open-source kanban board which allows a card-based task and to-do management.
:::
## Preparation
The following placeholders will be used:
- `wekan.company` is the FQDN of the wekan install.
- `authentik.company` is the FQDN of the authentik install.
Create an application in authentik. Create an OAuth2/OpenID provider with the following parameters:
- Client Type: `Confidential`
- JWT Algorithm: `RS256`
- Scopes: OpenID, Email and Profile
- RSA Key: Select any available key
- Redirect URIs: `https://wekan.company/_oauth/oidc`
Note the Client ID and Client Secret values. Create an application, using the provider you've created above. Note the slug of the application you've created.
## Wekan
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
<Tabs
defaultValue="docker"
values={[
{label: 'Docker', value: 'docker'},
{label: 'Standalone', value: 'standalone'},
]}>
<TabItem value="docker">
If your Wekan is running in docker, add the following environment variables for Authentik
```yaml
environment:
OAUTH2_ENABLED=true
OAUTH2_LOGIN_STYLE=redirect
OAUTH2_CLIENT_ID=<Client ID from above>
OAUTH2_SERVER_URL=https://authentik.company
OAUTH2_AUTH_ENDPOINT=/application/o/authorize/
OAUTH2_USERINFO_ENDPOINT=/application/o/userinfo/
OAUTH2_TOKEN_ENDPOINT=/application/o/token/
OAUTH2_SECRET=<Client Secret from above>
OAUTH2_ID_MAP=preferred_username
OAUTH2_USERNAME_MAP=preferred_username
OAUTH2_FULLNAME_MAP=given_name
OAUTH2_EMAIL_MAP=email
```
</TabItem>
<TabItem value="standalone">
edit `.env` and add the following:
```ini
# Authentik OAUTH Config
OAUTH2_ENABLED='true'
OAUTH2_LOGIN_STYLE='redirect'
OAUTH2_CLIENT_ID='<Client ID from above>'
OAUTH2_SERVER_URL='https://authentik.company'
OAUTH2_AUTH_ENDPOINT='/application/o/authorize/'
OAUTH2_USERINFO_ENDPOINT='/application/o/userinfo/'
OAUTH2_TOKEN_ENDPOINT='/application/o/token/'
OAUTH2_SECRET='<Client Secret from above>'
OAUTH2_ID_MAP='preferred_username'
OAUTH2_USERNAME_MAP='preferred_username'
OAUTH2_FULLNAME_MAP='given_name'
OAUTH2_EMAIL_MAP='email'
```
</TabItem>
</Tabs>

View File

@ -39,6 +39,8 @@ Set the Field `Username attribute` to `http://schemas.goauthentik.io/2021/02/sam
Set the Field `SP entity ID` to `https://authentik.company/application/saml/zabbix/sso/binding/redirect/` Set the Field `SP entity ID` to `https://authentik.company/application/saml/zabbix/sso/binding/redirect/`
Set the Field `SP name ID format` to `urn:oasis:names:tc:SAML:2.0:nameid-format:transient`
Check the box for `Case sensitive login`. Check the box for `Case sensitive login`.
For the `SAML Service Provider Certificate` and `SAML Service Provider Private Key`, you can either use custom certificates, or use the self-signed pair generated by authentik. For the `SAML Service Provider Certificate` and `SAML Service Provider Private Key`, you can either use custom certificates, or use the self-signed pair generated by authentik.

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@ -0,0 +1,54 @@
---
title: Discord
---
Allows users to authenticate using their Discord credentials
## Preparation
The following placeholders will be used:
- `authentik.company` is the FQDN of the authentik install.
## Discord
1. Create an application in the Discord Developer Portal (This is Free) https://discord.com/developers/applications
![New Application Button](discord1.png)
2. Name the Application
![Name App](discord2.png)
3. Select **OAuth2** from the left Menu
4. Copy the **Client ID** and _save it for later_
5. **Click to Reveal** the Client Secret and _save it for later_
6. Click **Add Redirect** and add https://authentik.company/source/oauth/callback/discord
Here is an example of a completed OAuth2 screen for Discord.
![Example Screen](discord4.png)
## Authentik
8. Under _Resources -> Sources_ Click **Create Discord OAuth Source**
9. **Name:** Choose a name (For the example I used Discord)
10. **Slug:** discord (You can choose a different slug, if you do you will need to update the Discord redirect URLand point it to the correct slug.)
11. **Consumer Key:** Client ID from step 4
12. **Consumer Secret:** Client Secret from step 5
13. **Provider type:** Discord
Here is an exmple of a complete Authentik Discord OAuth Source
![Example Screen](discord5.png)
Save, and you now have Discord as a source.
:::note
For more details on how-to have the new source display on the Login Page see the Sources page
:::

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View File

@ -0,0 +1,60 @@
---
title: Github
---
Allows users to authenticate using their Github credentials
## Preparation
The following placeholders will be used:
- `authentik.company` is the FQDN of the authentik install.
- `www.my.company` Homepage URL for your site
## Github
1. Create an OAuth app under Developer Settings https://github.com/settings/developers by clicking on the **Register a neww application**
![Register OAuth App](githubdeveloper1.png)
2. **Application Name:** Choose a name users will recognize ie: Authentik
3. **Homepage URL**:: www.my.company
4. **Authorization callback URL**: https://authentik.company/source/oauth/callback/github
5. Click **Register Application**
Example screenshot
![Example Screen](githubdeveloperexample.png)
6. Copy the **Client ID** and _save it for later_
7. Click **Generate a new client secret** and _save it for later_ You will not be able to see the secret again, so be sure to copy it now.
## Authentik
8. Under _Resources -> Sources_ Click **Create Github OAuth Source**
9. **Name**: Choose a name (For the example I use Github)
10. **Slug**: github (If you choose a different slug the URLs will need to be updated to reflect the change)
11. **Consumer Key:** Client ID from step 6
12. **Consumer Secret:** Client Secret from step 7
13. **Provider Type:** Github
Expand URL settings:
:::note
As of June 20 2021 these URLS are correct. Here is the Github reference URL https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps
:::
14. **Authorization URL:** `https://github.com/login/oauth/authorize`
15. **Access token URL:** `https://github.com/login/oauth/access_token`
16. **Profile URL:** `https://api.github.com/user`
Here is an exmple of a complete Authentik Github OAuth Source
![Example Screen](githubexample2.png)
Save, and you now have Github as a source.
:::note
For more details on how-to have the new source display on the Login Page see the Sources page
:::

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@ -0,0 +1,83 @@
---
title: Google
---
Allows users to authenticate using their Google credentials
## Preparation
The following placeholders will be used:
- `authentik.company` is the FQDN of the authentik install.
## Google
You will need to create a new project, and OAuth credentials in the Google Developer console. The developer console can be overwhelming at first.
1. Visit https://console.developers.google.com/ to create a new project
2. Create a New project.
![Example Screen](googledeveloper1.png)
3. **Project Name**: Choose a name
4. **Organization**: Leave as defaut if unsure
5. **Location**: Leave as default if unsure
![Example Screen](googledeveloper2.png)
6. Click **Create**
7. Choose your project from the drop down at the top
8. Click the **Credentials** menu item on the left. It looks like a key.
![Example Screen](googledeveloper3.png)
9. Click on **Configure Consent Screen**
![Example Screen](googledeveloper4.png)
10. **User Type:** If you do not have a Google Workspace (GSuite) account choose _External_. If you do have a Google Workspace (Gsuite) account and want to limit acces to only users inside of your organization choose _Internal_
_I'm only going to list the mandatory/important fields to complete._
11. **App Name:** Choose an Application
12. **User Support Email:** Must have a value
13. **Authorized Domains:** authentik.company
14. **Developer Contact Info:** Must have a value
15. Click **Save and Continue**
16. If you have special scopes configured for google, enter them on this screen. If not click **Save and Continue**
17. If you want to create Test Users enter them here, if not click **Save and Continue**
18. From the _Summary Page_ click on the **Credentials* link on the left. Same link as step 8
19. Click **Create Credentials** on the top of the screen
20. Choose **OAuth Client ID**
![Example Screen](googledeveloper5.png)
21. **Application Type:** Web Application
22. **Name:** Choose a name
23. **Authorized redirect URIs:** `https://authenik.company/source/oauth/callback/google/`
![Example Screen](googledeveloper6.png)
24. Click **Create**
25. Copy and store _Your Client ID_ and _Your Client Secret_ for later
## Authentik
26. Under _Resources -> Sources_ Click **Create Google OAuth Source**
27. **Name**: Choose a name (For the example I use Google)
28. **Slug**: google (If you choose a different slug the URLs will need to be updated to reflect the change)
29. **Consumer Key:** Your Client ID from step 25
30. **Consumer Secret:** Your Client Secret from step 25
31. **Provider Type:** Google
Here is an exmple of a complete Authentik Google OAuth Source
![Example Screen](authentiksource.png)
Save, and you now have Google as a source.
:::note
For more details on how-to have the new source display on the Login Page see the Sources page
:::

View File

@ -0,0 +1,14 @@
---
title: Sources
---
Sources allow you to connect authentik to an existing user directory. They can also be used for social logins, using external providers such as Facebook, Twitter, etc.
### Add Sources to Default Login Page
To have sources show on the default login screen you will need to add them. This is assuming you have not created or renamed the default stages and flows.
1. Access the **Flows** section
2. Click on **default-authentication-flow**
3. Click the **Stage Bindings** tab
4. Chose **Edit Stage** for the _default-authentication-identification_ stage
5. Under **Sources** you should see the addtional sources you have configured. Click all applicable sources to have them displayed on the Login Page

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