diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 78da27548..9b4f5c3c1 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 2021.6.1-rc6 +current_version = 2021.6.2 tag = True commit = True parse = (?P\d+)\.(?P\d+)\.(?P\d+)\-?(?P.*) diff --git a/.github/stale.yml b/.github/stale.yml index cf796c647..4e6b706f0 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -6,6 +6,7 @@ daysUntilClose: 7 exemptLabels: - pinned - security + - pr_wanted # Comment to post when marking an issue as stale. Set to `false` to disable markComment: > This issue has been automatically marked as stale because it has not had diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 419a6d090..4d9b69ffd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,22 +33,21 @@ jobs: with: push: ${{ github.event_name == 'release' }} tags: | - beryju/authentik:2021.6.1-rc6, + beryju/authentik:2021.6.2, beryju/authentik:latest, - ghcr.io/goauthentik/server:2021.6.1-rc6, + ghcr.io/goauthentik/server:2021.6.2, ghcr.io/goauthentik/server:latest platforms: linux/amd64,linux/arm64 context: . - name: Building Docker Image (stable) - uses: docker/build-push-action@v2 - if: ${{ github.event_name == 'release' && !contains('2021.6.1-rc6', 'rc') }} - with: - push: true - tags: | - beryju/authentik:stable, - ghcr.io/goauthentik/server:stable - platforms: linux/amd64,linux/arm64 - context: . + if: ${{ github.event_name == 'release' && !contains('2021.6.2', 'rc') }} + run: | + docker pull beryju/authentik:latest + docker tag beryju/authentik:latest beryju/authentik:stable + docker push beryju/authentik:stable + docker pull ghcr.io/goauthentik/server:latest + docker tag ghcr.io/goauthentik/server:latest ghcr.io/goauthentik/server:stable + docker push ghcr.io/goauthentik/server:stable build-proxy: runs-on: ubuntu-latest steps: @@ -76,22 +75,21 @@ jobs: with: push: ${{ github.event_name == 'release' }} tags: | - beryju/authentik-proxy:2021.6.1-rc6, + beryju/authentik-proxy:2021.6.2, beryju/authentik-proxy:latest, - ghcr.io/goauthentik/proxy:2021.6.1-rc6, + ghcr.io/goauthentik/proxy:2021.6.2, ghcr.io/goauthentik/proxy:latest file: proxy.Dockerfile platforms: linux/amd64,linux/arm64 - name: Building Docker Image (stable) - uses: docker/build-push-action@v2 - if: ${{ github.event_name == 'release' && !contains('2021.6.1-rc6', 'rc') }} - with: - push: true - tags: | - beryju/authentik-proxy:stable, - ghcr.io/goauthentik/proxy:stable - platforms: linux/amd64,linux/arm64 - context: . + if: ${{ github.event_name == 'release' && !contains('2021.6.2', 'rc') }} + run: | + docker pull beryju/authentik-proxy:latest + docker tag beryju/authentik-proxy:latest beryju/authentik-proxy:stable + docker push beryju/authentik-proxy:stable + docker pull ghcr.io/goauthentik/proxy:latest + docker tag ghcr.io/goauthentik/proxy:latest ghcr.io/goauthentik/proxy:stable + docker push ghcr.io/goauthentik/proxy:stable build-ldap: runs-on: ubuntu-latest steps: @@ -119,24 +117,22 @@ jobs: with: push: ${{ github.event_name == 'release' }} tags: | - beryju/authentik-ldap:2021.6.1-rc6, + beryju/authentik-ldap:2021.6.2, beryju/authentik-ldap:latest, - ghcr.io/goauthentik/ldap:2021.6.1-rc6, + ghcr.io/goauthentik/ldap:2021.6.2, ghcr.io/goauthentik/ldap:latest file: ldap.Dockerfile platforms: linux/amd64,linux/arm64 - name: Building Docker Image (stable) - uses: docker/build-push-action@v2 - if: ${{ github.event_name == 'release' && !contains('2021.6.1-rc6', 'rc') }} - with: - push: true - tags: | - beryju/authentik-ldap:stable, - ghcr.io/goauthentik/ldap:stable - platforms: linux/amd64,linux/arm64 - context: . + if: ${{ github.event_name == 'release' && !contains('2021.6.2', 'rc') }} + run: | + docker pull beryju/authentik-ldap:latest + docker tag beryju/authentik-ldap:latest beryju/authentik-ldap:stable + docker push beryju/authentik-ldap:stable + docker pull ghcr.io/goauthentik/ldap:latest + docker tag ghcr.io/goauthentik/ldap:latest ghcr.io/goauthentik/ldap:stable + docker push ghcr.io/goauthentik/ldap:stable test-release: - if: ${{ github.event_name == 'release' }} needs: - build-server - build-proxy @@ -160,13 +156,26 @@ jobs: runs-on: ubuntu-latest steps: - 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 uses: getsentry/action-release@v1 + if: ${{ github.event_name == 'release' }} env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_ORG: beryjuorg SENTRY_PROJECT: authentik SENTRY_URL: https://sentry.beryju.org with: - version: authentik@2021.6.1-rc6 + version: authentik@2021.6.2 environment: beryjuorg-prod + sourcemaps: './web/dist' diff --git a/Pipfile b/Pipfile index 876f669ae..0cf076c36 100644 --- a/Pipfile +++ b/Pipfile @@ -46,6 +46,7 @@ webauthn = "*" xmlsec = "*" duo-client = "*" ua-parser = "*" +deepmerge = "*" [requires] python_version = "3.9" diff --git a/Pipfile.lock b/Pipfile.lock index ae276bd31..59747ff6b 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "4fa1ad681762c867a95410074f31ac5d00119e187e0f38982cd59fdf301cccf5" + "sha256": "f90d9fb4713eaf9c5ffe6a3858e64843670f79ab5007e7debf914c1f094c8d63" }, "pipfile-spec": 6, "requires": { @@ -122,19 +122,19 @@ }, "boto3": { "hashes": [ - "sha256:8e5af9c7ea16ce1c35b7c3220d073dea9735bb1790107820d475462500ae1eff", - "sha256:e61607211816c194dbe2701db48dcddc87cf19372e6f57a9ebe4dfe93dfe177c" + "sha256:2c2f70608934b03f9c08f4cd185de223b5abd18245dd4d4800e1fbc2a2523e31", + "sha256:fccfa81cda69bb2317ed97e7149d7d84d19e6ec3bfbe3f721139e7ac0c407c73" ], "index": "pypi", - "version": "==1.17.95" + "version": "==1.17.98" }, "botocore": { "hashes": [ - "sha256:240a9ef007292e986a4e11662f9038435d9d4fd242e083db160c86eb5c24af30", - "sha256:dc215f59735a3abde6c66a61f43f10d95bc18754d310da4e2037b3b8c4d8aa2d" + "sha256:b2a49de4ee04b690142c8e7240f0f5758e3f7673dd39cf398efe893bf5e11c3f", + "sha256:b955b23fe2fbdbbc8e66f37fe2970de6b5d8169f940b200bcf434751709d38f6" ], "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": { "hashes": [ @@ -165,11 +165,11 @@ }, "celery": { "hashes": [ - "sha256:1329de1edeaf734ef859e630cb42df2c116d53e59d2f46433b13aed196e85620", - "sha256:65f061c04578cf189cd7352c192e1a79fdeb370b916bff792bcc769560e81184" + "sha256:54436cd97b031bf2e08064223240e2a83d601d9414bcb1b702f94c6c33c29485", + "sha256:b5399d76cf70d5cfac3ec993f8796ec1aa90d4cef55972295751f384758a80d7" ], "index": "pypi", - "version": "==5.1.0" + "version": "==5.1.1" }, "certifi": { "hashes": [ @@ -324,6 +324,14 @@ "markers": "python_version >= '3.6'", "version": "==3.0.2" }, + "deepmerge": { + "hashes": [ + "sha256:87166dbe9ba1a3348a45c9d4ada6778f518d41afc0b85aa017ea3041facc3f9c", + "sha256:f6fd7f1293c535fb599e197e750dbe8674503c5d2a89759b3c72a3c46746d4fd" + ], + "index": "pypi", + "version": "==0.3.0" + }, "defusedxml": { "hashes": [ "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", @@ -465,11 +473,11 @@ }, "google-auth": { "hashes": [ - "sha256:154f7889c5d679a6f626f36adb12afbd4dbb0a9a04ec575d989d6ba79c4fd65e", - "sha256:6d47c79b5d09fbc7e8355fd9594cc4cf65fdde5d401c63951eaac4baa1ba2ae1" + "sha256:b3a67fa9ba5b768861dacf374c2135eb09fa14a0e40c851c3b8ea7abe6fc8fef", + "sha256:e34e5f5de5610b202f9b40ebd9f8b27571d5c5537db9afed3a72b2db5a345039" ], "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": { "hashes": [ @@ -786,52 +794,46 @@ }, "prompt-toolkit": { "hashes": [ - "sha256:bf00f22079f5fadc949f42ae8ff7f05702826a97059ffcc6281036ad40ac6f04", - "sha256:e1b4f11b9336a28fa11810bc623c357420f69dfdb6d2dac41ca2c21a55c033bc" + "sha256:08360ee3a3148bdb5163621709ee322ec34fc4375099afa4bbf751e9b7b7fa4f", + "sha256:7089d8d2938043508aa9420ec18ce0922885304cddae87fb96eebca942299f88" ], "markers": "python_full_version >= '3.6.1'", - "version": "==3.0.18" + "version": "==3.0.19" }, "psycopg2-binary": { "hashes": [ - "sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c", - "sha256:0e4dc3d5996760104746e6cfcdb519d9d2cd27c738296525d5867ea695774e67", - "sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0", - "sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6", - "sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db", - "sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94", - "sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52", - "sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056", - "sha256:6a32f3a4cb2f6e1a0b15215f448e8ce2da192fd4ff35084d80d5e39da683e79b", - "sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd", - "sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550", - "sha256:833709a5c66ca52f1d21d41865a637223b368c0ee76ea54ca5bad6f2526c7679", - "sha256:89705f45ce07b2dfa806ee84439ec67c5d9a0ef20154e0e475e2b2ed392a5b83", - "sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77", - "sha256:950bc22bb56ee6ff142a2cb9ee980b571dd0912b0334aa3fe0fe3788d860bea2", - "sha256:a0c50db33c32594305b0ef9abc0cb7db13de7621d2cadf8392a1d9b3c437ef77", - "sha256:a0eb43a07386c3f1f1ebb4dc7aafb13f67188eab896e7397aa1ee95a9c884eb2", - "sha256:aaa4213c862f0ef00022751161df35804127b78adf4a2755b9f991a507e425fd", - "sha256:ac0c682111fbf404525dfc0f18a8b5f11be52657d4f96e9fcb75daf4f3984859", - "sha256:ad20d2eb875aaa1ea6d0f2916949f5c08a19c74d05b16ce6ebf6d24f2c9f75d1", - "sha256:b4afc542c0ac0db720cf516dd20c0846f71c248d2b3d21013aa0d4ef9c71ca25", - "sha256:b8a3715b3c4e604bcc94c90a825cd7f5635417453b253499664f784fc4da0152", - "sha256:ba28584e6bca48c59eecbf7efb1576ca214b47f05194646b081717fa628dfddf", - "sha256:ba381aec3a5dc29634f20692349d73f2d21f17653bda1decf0b52b11d694541f", - "sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729", - "sha256:c2507d796fca339c8fb03216364cca68d87e037c1f774977c8fc377627d01c71", - "sha256:cec7e622ebc545dbb4564e483dd20e4e404da17ae07e06f3e780b2dacd5cee66", - "sha256:d14b140a4439d816e3b1229a4a525df917d6ea22a0771a2a78332273fd9528a4", - "sha256:d1b4ab59e02d9008efe10ceabd0b31e79519da6fb67f7d8e8977118832d0f449", - "sha256:d5227b229005a696cc67676e24c214740efd90b148de5733419ac9aaba3773da", - "sha256:e1f57aa70d3f7cc6947fd88636a481638263ba04a742b4a37dd25c373e41491a", - "sha256:e74a55f6bad0e7d3968399deb50f61f4db1926acf4a6d83beaaa7df986f48b1c", - "sha256:e82aba2188b9ba309fd8e271702bd0d0fc9148ae3150532bbb474f4590039ffb", - "sha256:ee69dad2c7155756ad114c02db06002f4cded41132cc51378e57aad79cc8e4f4", - "sha256:f5ab93a2cb2d8338b1674be43b442a7f544a0971da062a5da774ed40587f18f5" + "sha256:0b7dae87f0b729922e06f85f667de7bf16455d411971b2043bbd9577af9d1975", + "sha256:0f2e04bd2a2ab54fa44ee67fe2d002bb90cee1c0f1cc0ebc3148af7b02034cbd", + "sha256:123c3fb684e9abfc47218d3784c7b4c47c8587951ea4dd5bc38b6636ac57f616", + "sha256:1473c0215b0613dd938db54a653f68251a45a78b05f6fc21af4326f40e8360a2", + "sha256:14db1752acdd2187d99cb2ca0a1a6dfe57fc65c3281e0f20e597aac8d2a5bd90", + "sha256:1e3a362790edc0a365385b1ac4cc0acc429a0c0d662d829a50b6ce743ae61b5a", + "sha256:1e85b74cbbb3056e3656f1cc4781294df03383127a8114cbc6531e8b8367bf1e", + "sha256:20f1ab44d8c352074e2d7ca67dc00843067788791be373e67a0911998787ce7d", + "sha256:2f62c207d1740b0bde5c4e949f857b044818f734a3d57f1d0d0edc65050532ed", + "sha256:3242b9619de955ab44581a03a64bdd7d5e470cc4183e8fcadd85ab9d3756ce7a", + "sha256:35c4310f8febe41f442d3c65066ca93cccefd75013df3d8c736c5b93ec288140", + "sha256:4235f9d5ddcab0b8dbd723dca56ea2922b485ea00e1dafacf33b0c7e840b3d32", + "sha256:5ced67f1e34e1a450cdb48eb53ca73b60aa0af21c46b9b35ac3e581cf9f00e31", + "sha256:7360647ea04db2e7dff1648d1da825c8cf68dc5fbd80b8fb5b3ee9f068dcd21a", + "sha256:8c13d72ed6af7fd2c8acbd95661cf9477f94e381fce0792c04981a8283b52917", + "sha256:988b47ac70d204aed01589ed342303da7c4d84b56c2f4c4b8b00deda123372bf", + "sha256:995fc41ebda5a7a663a254a1dcac52638c3e847f48307b5416ee373da15075d7", + "sha256:a36c7eb6152ba5467fb264d73844877be8b0847874d4822b7cf2d3c0cb8cdcb0", + "sha256:aed4a9a7e3221b3e252c39d0bf794c438dc5453bc2963e8befe9d4cd324dff72", + "sha256:aef9aee84ec78af51107181d02fe8773b100b01c5dfde351184ad9223eab3698", + "sha256:b0221ca5a9837e040ebf61f48899926b5783668b7807419e4adae8175a31f773", + "sha256:b4d7679a08fea64573c969f6994a2631908bb2c0e69a7235648642f3d2e39a68", + "sha256:c250a7ec489b652c892e4f0a5d122cc14c3780f9f643e1a326754aedf82d9a76", + "sha256:ca86db5b561b894f9e5f115d6a159fff2a2570a652e07889d8a383b5fae66eb4", + "sha256:cfc523edecddaef56f6740d7de1ce24a2fdf94fd5e704091856a201872e37f9f", + "sha256:da113b70f6ec40e7d81b43d1b139b9db6a05727ab8be1ee559f3a69854a69d34", + "sha256:f6fac64a38f6768e7bc7b035b9e10d8a538a9fadce06b983fb3e6fa55ac5f5ce", + "sha256:f8559617b1fcf59a9aedba2c9838b5b6aa211ffedecabca412b92a1ff75aac1a", + "sha256:fbb42a541b1093385a2d8c7eec94d26d30437d0e77c1d25dae1dcc46741a385e" ], "index": "pypi", - "version": "==2.8.6" + "version": "==2.9.1" }, "pyasn1": { "hashes": [ @@ -961,10 +963,10 @@ }, "python-dotenv": { "hashes": [ - "sha256:00aa34e92d992e9f8383730816359647f358f4a3be1ba45e5a5cefd27ee91544", - "sha256:b1ae5e9643d5ed987fc57cc2583021e38db531946518130777734f9589b3141f" + "sha256:dd8fe852847f4fbfadabf6183ddd4c824a9651f02d51714fa075c95561959c7d", + "sha256:effaac3c1e58d89b3ccb4d04a40dc7ad6e0275fda25fd75ae9d323e2465e202d" ], - "version": "==0.17.1" + "version": "==0.18.0" }, "pytz": { "hashes": [ @@ -1538,11 +1540,11 @@ }, "gitpython": { "hashes": [ - "sha256:3283ae2fba31c913d857e12e5ba5f9a7772bbc064ae2bb09efafa71b0dd4939b", - "sha256:be27633e7509e58391f10207cd32b2a6cf5b908f92d9cd30da2e514e1137af61" + "sha256:b838a895977b45ab6f0cc926a9045c8d1c44e2b653c1fcc39fe91f42c6e8f05b", + "sha256:fce760879cd2aebd2991b3542876dc5c4a909b30c9d69dfc488e504a8db37ee8" ], - "markers": "python_version >= '3.4'", - "version": "==3.1.14" + "markers": "python_version >= '3.6'", + "version": "==3.1.18" }, "idna": { "hashes": [ @@ -1560,11 +1562,11 @@ }, "isort": { "hashes": [ - "sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6", - "sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d" + "sha256:83510593e07e433b77bd5bff0f6f607dbafa06d1a89022616f02d8b699cfcd56", + "sha256:8e2c107091cfec7286bc0f68a547d0ba4c094d460b732075b6fba674f1035c0c" ], - "markers": "python_version >= '3.6' and python_version < '4'", - "version": "==5.8.0" + "markers": "python_version < '4.0' and python_full_version >= '3.6.1'", + "version": "==5.9.1" }, "lazy-object-proxy": { "hashes": [ diff --git a/authentik/__init__.py b/authentik/__init__.py index 18b50e8bb..7c978754f 100644 --- a/authentik/__init__.py +++ b/authentik/__init__.py @@ -1,3 +1,3 @@ """authentik""" -__version__ = "2021.6.1-rc6" +__version__ = "2021.6.2" ENV_GIT_HASH_KEY = "GIT_BUILD_HASH" diff --git a/authentik/core/expression.py b/authentik/core/expression.py index 220e37b13..13d196136 100644 --- a/authentik/core/expression.py +++ b/authentik/core/expression.py @@ -3,23 +3,33 @@ from traceback import format_tb from typing import Optional 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.lib.expression.evaluator import BaseEvaluator +from authentik.policies.types import PolicyRequest class PropertyMappingEvaluator(BaseEvaluator): """Custom Evalautor that adds some different context variables.""" 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""" + req = PolicyRequest(user=get_anonymous_user()) + req.obj = mapping if user: + req.user = user self._context["user"] = user if request: - self._context["request"] = request + req.http_request = request + self._context["request"] = req self._context.update(**kwargs) def handle_error(self, exc: Exception, expression_source: str): @@ -30,9 +40,8 @@ class PropertyMappingEvaluator(BaseEvaluator): expression=expression_source, message=error_string, ) - if "user" in self._context: - event.set_user(self._context["user"]) 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 event.save() diff --git a/authentik/core/models.py b/authentik/core/models.py index 677627d64..b144d4761 100644 --- a/authentik/core/models.py +++ b/authentik/core/models.py @@ -6,6 +6,7 @@ from urllib.parse import urlencode from uuid import uuid4 import django.db.models.options as options +from deepmerge import always_merger from django.conf import settings from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import UserManager as DjangoUserManager @@ -114,8 +115,8 @@ class User(GuardianUserMixin, AbstractUser): including the users attributes""" final_attributes = {} for group in self.ak_groups.all().order_by("name"): - final_attributes.update(group.attributes) - final_attributes.update(self.attributes) + always_merger.merge(final_attributes, group.attributes) + always_merger.merge(final_attributes, self.attributes) return final_attributes @cached_property @@ -142,21 +143,25 @@ class User(GuardianUserMixin, AbstractUser): @property def avatar(self) -> str: """Get avatar, depending on authentik.avatar setting""" - mode = CONFIG.raw.get("authentik").get("avatars") + mode: str = CONFIG.y("avatars", "none") if mode == "none": 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": parameters = [ ("s", "158"), ("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 = ( f"{GRAVATAR_URL}/avatar/{mail_hash}?{urlencode(parameters, doseq=True)}" ) 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: @@ -460,7 +465,7 @@ class PropertyMapping(SerializerModel, ManagedModel): from authentik.core.expression import PropertyMappingEvaluator evaluator = PropertyMappingEvaluator() - evaluator.set_context(user, request, **kwargs) + evaluator.set_context(user, request, self, **kwargs) try: return evaluator.evaluate(self.expression) except (ValueError, SyntaxError) as exc: @@ -494,8 +499,12 @@ class AuthenticatedSession(ExpiringModel): last_used = models.DateTimeField(auto_now=True) @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""" + if not hasattr(request, "session") or not request.session.session_key: + return None return AuthenticatedSession( session_key=request.session.session_key, user=user, diff --git a/authentik/core/signals.py b/authentik/core/signals.py index daca16ce1..497fbdc8b 100644 --- a/authentik/core/signals.py +++ b/authentik/core/signals.py @@ -49,7 +49,9 @@ def user_logged_in_session(sender, request: HttpRequest, user: "User", **_): """Create an AuthenticatedSession from request""" 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) diff --git a/authentik/core/sources/flow_manager.py b/authentik/core/sources/flow_manager.py index d871d03e6..173064822 100644 --- a/authentik/core/sources/flow_manager.py +++ b/authentik/core/sources/flow_manager.py @@ -183,6 +183,8 @@ class SourceFlowManager: # pylint: disable=unused-argument def get_stages_to_append(self, flow: Flow) -> list[Stage]: """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: return [ in_memory_stage(PostUserEnrollmentStage), diff --git a/authentik/core/templates/if/flow.html b/authentik/core/templates/if/flow.html index cdb9afbd5..b1435d2f6 100644 --- a/authentik/core/templates/if/flow.html +++ b/authentik/core/templates/if/flow.html @@ -11,6 +11,11 @@ {% block head %} + {% endblock %} {% block body %} diff --git a/authentik/core/templates/login/base_full.html b/authentik/core/templates/login/base_full.html index b236bb7a7..8750fb341 100644 --- a/authentik/core/templates/login/base_full.html +++ b/authentik/core/templates/login/base_full.html @@ -7,6 +7,14 @@ {% endblock %} +{% block head %} + +{% endblock %} + {% block body %}
diff --git a/authentik/crypto/models.py b/authentik/crypto/models.py index 3b447e66d..1cfd71c79 100644 --- a/authentik/crypto/models.py +++ b/authentik/crypto/models.py @@ -55,11 +55,16 @@ class CertificateKeyPair(CreatedUpdatedModel): def private_key(self) -> Optional[RSAPrivateKey]: """Get python cryptography PrivateKey instance""" if not self._private_key and self._private_key != "": - self._private_key = load_pem_private_key( - str.encode("\n".join([x.strip() for x in self.key_data.split("\n")])), - password=None, - backend=default_backend(), - ) + try: + self._private_key = load_pem_private_key( + str.encode( + "\n".join([x.strip() for x in self.key_data.split("\n")]) + ), + password=None, + backend=default_backend(), + ) + except ValueError: + return None return self._private_key @property diff --git a/authentik/flows/planner.py b/authentik/flows/planner.py index 65f2e24cb..9edb719fa 100644 --- a/authentik/flows/planner.py +++ b/authentik/flows/planner.py @@ -14,6 +14,7 @@ from authentik.events.models import cleanse_dict from authentik.flows.exceptions import EmptyFlowException, FlowNonApplicableException from authentik.flows.markers import ReevaluateMarker, StageMarker from authentik.flows.models import Flow, FlowStageBinding, Stage +from authentik.lib.config import CONFIG from authentik.policies.engine import PolicyEngine from authentik.root.monitoring import UpdatingGauge @@ -33,6 +34,7 @@ HIST_FLOWS_PLAN_TIME = Histogram( "Duration to build a plan for a flow", ["flow_slug"], ) +CACHE_TIMEOUT = int(CONFIG.y("redis.cache_timeout_flows")) def cache_key(flow: Flow, user: Optional[User] = None) -> str: @@ -157,7 +159,7 @@ class FlowPlanner: "f(plan): building plan", ) 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() if not plan.stages and not self.allow_empty_flows: raise EmptyFlowException() diff --git a/authentik/flows/stage.py b/authentik/flows/stage.py index 3c32472c3..8502a42c6 100644 --- a/authentik/flows/stage.py +++ b/authentik/flows/stage.py @@ -18,27 +18,11 @@ from authentik.flows.challenge import ( ) from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER from authentik.flows.views import FlowExecutorView -from authentik.lib.sentry import SentryIgnoredException PLAN_CONTEXT_PENDING_USER_IDENTIFIER = "pending_user_identifier" 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): """Abstract Stage, inherits TemplateView but can be combined with FormView""" diff --git a/authentik/flows/views.py b/authentik/flows/views.py index a8b0d9720..499338d97 100644 --- a/authentik/flows/views.py +++ b/authentik/flows/views.py @@ -44,6 +44,7 @@ from authentik.flows.planner import ( FlowPlan, FlowPlanner, ) +from authentik.lib.sentry import SentryIgnoredException 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.tenants.models import Tenant @@ -93,6 +94,10 @@ def challenge_response_types(): return Inner() +class InvalidStageError(SentryIgnoredException): + """Error raised when a challenge from a stage is not valid""" + + @method_decorator(xframe_options_sameorigin, name="dispatch") class FlowExecutorView(APIView): """Stage 1 Flow executor, passing requests to Stage Views""" @@ -164,12 +169,19 @@ class FlowExecutorView(APIView): current_stage=self.current_stage, flow_slug=self.flow.slug, ) - stage_cls = self.current_stage.type + try: + 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.args = self.args self.current_stage_view.kwargs = self.kwargs self.current_stage_view.request = request - return super().dispatch(request) + try: + return super().dispatch(request) + except InvalidStageError as exc: + return self.stage_invalid(str(exc)) @extend_schema( responses={ @@ -353,8 +365,11 @@ class FlowErrorResponse(TemplateResponse): context = {} context["error"] = self.error if self._request.user and self._request.user.is_authenticated: - if self._request.user.is_superuser or self._request.user.attributes.get( - USER_ATTRIBUTE_DEBUG, False + if ( + self._request.user.is_superuser + or self._request.user.group_attributes().get( + USER_ATTRIBUTE_DEBUG, False + ) ): context["tb"] = "".join(format_tb(self.error.__traceback__)) return context diff --git a/authentik/lib/config.py b/authentik/lib/config.py index e3b00a7e1..8656756f3 100644 --- a/authentik/lib/config.py +++ b/authentik/lib/config.py @@ -62,7 +62,7 @@ class ConfigLoader: output.update(kwargs) 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""" for key, value in updatee.items(): if isinstance(value, Mapping): @@ -73,7 +73,7 @@ class ConfigLoader: root[key] = value return root - def parse_uri(self, value): + def parse_uri(self, value: str) -> str: """Parse string values which start with a URI""" url = urlparse(value) if url.scheme == "env": @@ -99,7 +99,10 @@ class ConfigLoader: raise ImproperlyConfigured from exc except PermissionError as exc: 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): diff --git a/authentik/lib/default.yml b/authentik/lib/default.yml index 899fb311d..fe5056e7f 100644 --- a/authentik/lib/default.yml +++ b/authentik/lib/default.yml @@ -9,6 +9,7 @@ postgresql: web: listen: 0.0.0.0:9000 listen_tls: 0.0.0.0:9443 + load_local_files: false redis: host: localhost @@ -16,6 +17,10 @@ redis: cache_db: 0 message_queue_db: 1 ws_db: 2 + cache_timeout: 300 + cache_timeout_flows: 300 + cache_timeout_policies: 300 + cache_timeout_reputation: 300 debug: false @@ -45,12 +50,12 @@ outposts: # %(build_hash)s: Build hash if you're running a beta version docker_image_base: "ghcr.io/goauthentik/%(type)s:%(version)s" -authentik: - avatars: gravatar # gravatar or none - geoip: "./GeoLite2-City.mmdb" - # Optionally add links to the footer on the login page - footer_links: - - name: Documentation - href: https://goauthentik.io/docs/ - - name: authentik Website - href: https://goauthentik.io/ +avatars: env://AUTHENTIK_AUTHENTIK__AVATARS?gravatar +geoip: "./GeoLite2-City.mmdb" + +# Can't currently be configured via environment variables, only yaml +footer_links: + - name: Documentation + href: https://goauthentik.io/docs/ + - name: authentik Website + href: https://goauthentik.io/ diff --git a/authentik/lib/expression/evaluator.py b/authentik/lib/expression/evaluator.py index 7681ef6c0..452eb4a85 100644 --- a/authentik/lib/expression/evaluator.py +++ b/authentik/lib/expression/evaluator.py @@ -3,6 +3,7 @@ import re from textwrap import indent from typing import Any, Iterable, Optional +from django.core.exceptions import FieldError from requests import Session from rest_framework.serializers import ValidationError from sentry_sdk.hub import Hub @@ -29,10 +30,10 @@ class BaseEvaluator: # update website/docs/expressions/_objects.md # update website/docs/expressions/_functions.md self._globals = { - "regex_match": BaseEvaluator.expr_filter_regex_match, - "regex_replace": BaseEvaluator.expr_filter_regex_replace, - "ak_is_group_member": BaseEvaluator.expr_func_is_group_member, - "ak_user_by": BaseEvaluator.expr_func_user_by, + "regex_match": BaseEvaluator.expr_regex_match, + "regex_replace": BaseEvaluator.expr_regex_replace, + "ak_is_group_member": BaseEvaluator.expr_is_group_member, + "ak_user_by": BaseEvaluator.expr_user_by, "ak_logger": get_logger(), "requests": Session(), } @@ -40,25 +41,28 @@ class BaseEvaluator: self._filename = "BaseEvalautor" @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""" - return re.search(regex, value) is None + return re.search(regex, value) is not None @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""" return re.sub(regex, repl, value) @staticmethod - def expr_func_user_by(**filters) -> Optional[User]: + def expr_user_by(**filters) -> Optional[User]: """Get user by filters""" - users = User.objects.filter(**filters) - if users: - return users.first() - return None + try: + users = User.objects.filter(**filters) + if users: + return users.first() + return None + except FieldError: + return None @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`""" return user.ak_groups.filter(**group_filters).exists() diff --git a/authentik/lib/tests/test_config.py b/authentik/lib/tests/test_config.py new file mode 100644 index 000000000..30c00932e --- /dev/null +++ b/authentik/lib/tests/test_config.py @@ -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) diff --git a/authentik/lib/tests/test_evaluator.py b/authentik/lib/tests/test_evaluator.py new file mode 100644 index 000000000..931224d46 --- /dev/null +++ b/authentik/lib/tests/test_evaluator.py @@ -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" + ) + ) diff --git a/authentik/lib/utils/http.py b/authentik/lib/utils/http.py index dc4c003b4..ac4ed67af 100644 --- a/authentik/lib/utils/http.py +++ b/authentik/lib/utils/http.py @@ -33,7 +33,7 @@ def _get_outpost_override_ip(request: HttpRequest) -> Optional[str]: return None if OUTPOST_REMOTE_IP_HEADER not in request.META: 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 request.META[OUTPOST_REMOTE_IP_HEADER] diff --git a/authentik/outposts/channels.py b/authentik/outposts/channels.py index efd781f2c..a9aed6200 100644 --- a/authentik/outposts/channels.py +++ b/authentik/outposts/channels.py @@ -67,11 +67,6 @@ class OutpostConsumer(AuthJsonConsumer): self.accept() self.outpost = outpost.first() 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 def disconnect(self, close_code): @@ -108,6 +103,11 @@ class OutpostConsumer(AuthJsonConsumer): outpost=self.outpost.name, uid=self.last_uid, ).inc() + LOGGER.debug( + "added outpost instace to cache", + outpost=self.outpost, + instance_uuid=self.last_uid, + ) self.first_msg = True if msg.instruction == WebsocketMessageInstruction.HELLO: diff --git a/authentik/outposts/controllers/docker.py b/authentik/outposts/controllers/docker.py index 5a3c45c57..c85276471 100644 --- a/authentik/outposts/controllers/docker.py +++ b/authentik/outposts/controllers/docker.py @@ -66,7 +66,7 @@ class DockerController(BaseController): "name": container_name, "detach": True, "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 }, "environment": self._get_env(), diff --git a/authentik/policies/denied.py b/authentik/policies/denied.py index 5da2a30ce..9ffc2a7b1 100644 --- a/authentik/policies/denied.py +++ b/authentik/policies/denied.py @@ -37,7 +37,9 @@ class AccessDeniedResponse(TemplateResponse): if self._request.user and self._request.user.is_authenticated: if ( 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 return context diff --git a/authentik/policies/process.py b/authentik/policies/process.py index 013de618f..640cc5b88 100644 --- a/authentik/policies/process.py +++ b/authentik/policies/process.py @@ -10,6 +10,7 @@ from sentry_sdk.tracing import Span from structlog.stdlib import get_logger from authentik.events.models import Event, EventAction +from authentik.lib.config import CONFIG from authentik.lib.utils.errors import exception_to_string from authentik.policies.exceptions import PolicyException from authentik.policies.models import PolicyBinding @@ -18,6 +19,7 @@ from authentik.policies.types import PolicyRequest, PolicyResult LOGGER = get_logger() FORK_CTX = get_context("fork") +CACHE_TIMEOUT = int(CONFIG.y("redis.cache_timeout_policies")) PROCESS_CLASS = FORK_CTX.Process HIST_POLICIES_EXECUTION_TIME = Histogram( "authentik_policies_execution_time", @@ -114,7 +116,7 @@ class PolicyProcess(PROCESS_CLASS): policy_result.source_binding = self.binding if not self.request.debug: key = cache_key(self.binding, self.request) - cache.set(key, policy_result) + cache.set(key, policy_result, CACHE_TIMEOUT) LOGGER.debug( "P_ENG(proc): finished and cached ", policy=self.binding.policy, diff --git a/authentik/policies/reputation/signals.py b/authentik/policies/reputation/signals.py index d6eaf9fa0..361490c96 100644 --- a/authentik/policies/reputation/signals.py +++ b/authentik/policies/reputation/signals.py @@ -5,6 +5,7 @@ from django.dispatch import receiver from django.http import HttpRequest from structlog.stdlib import get_logger +from authentik.lib.config import CONFIG from authentik.lib.utils.http import get_client_ip from authentik.policies.reputation.models import ( CACHE_KEY_IP_PREFIX, @@ -13,6 +14,7 @@ from authentik.policies.reputation.models import ( from authentik.stages.identification.signals import identification_failed LOGGER = get_logger() +CACHE_TIMEOUT = int(CONFIG.y("redis.cache_timeout_reputation")) 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) # 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.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) LOGGER.debug("Updated score", amount=amount, for_user=username, for_ip=remote_ip) diff --git a/authentik/policies/views.py b/authentik/policies/views.py index 73f2526eb..6fbb7c139 100644 --- a/authentik/policies/views.py +++ b/authentik/policies/views.py @@ -105,6 +105,7 @@ class PolicyAccessView(AccessMixin, View): policy_engine = PolicyEngine( self.application, user or self.request.user, self.request ) + policy_engine.use_cache = False policy_engine.build() result = policy_engine.result LOGGER.debug( diff --git a/authentik/providers/oauth2/managed.py b/authentik/providers/oauth2/managed.py index 873640942..9f4a3136a 100644 --- a/authentik/providers/oauth2/managed.py +++ b/authentik/providers/oauth2/managed.py @@ -9,7 +9,7 @@ return {} """ SCOPE_EMAIL_EXPRESSION = """ return { - "email": user.email, + "email": request.user.email, "email_verified": True } """ @@ -17,14 +17,14 @@ SCOPE_PROFILE_EXPRESSION = """ return { # 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. - # You can override this behaviour in custom mappings, i.e. `user.name.split(" ")` - "name": user.name, - "given_name": user.name, + # You can override this behaviour in custom mappings, i.e. `request.user.name.split(" ")` + "name": request.user.name, + "given_name": request.user.name, "family_name": "", - "preferred_username": user.username, - "nickname": user.username, + "preferred_username": request.user.username, + "nickname": request.user.username, # 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()], } """ diff --git a/authentik/providers/proxy/managed.py b/authentik/providers/proxy/managed.py index 24f197b5c..fb7cf35c2 100644 --- a/authentik/providers/proxy/managed.py +++ b/authentik/providers/proxy/managed.py @@ -8,7 +8,7 @@ SCOPE_AK_PROXY_EXPRESSION = """ # which are used for example for the HTTP-Basic Authentication mapping. return { "ak_proxy": { - "user_attributes": user.group_attributes() + "user_attributes": request.user.group_attributes() } }""" diff --git a/authentik/providers/saml/managed.py b/authentik/providers/saml/managed.py index e191759d5..4023fccaa 100644 --- a/authentik/providers/saml/managed.py +++ b/authentik/providers/saml/managed.py @@ -3,7 +3,7 @@ from authentik.managed.manager import EnsureExists, ObjectManager from authentik.providers.saml.models import SAMLPropertyMapping GROUP_EXPRESSION = """ -for group in user.ak_groups.all(): +for group in request.user.ak_groups.all(): yield group.name """ @@ -18,7 +18,7 @@ class SAMLProviderManager(ObjectManager): "goauthentik.io/providers/saml/upn", name="authentik default SAML Mapping: 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="", ), EnsureExists( @@ -26,7 +26,7 @@ class SAMLProviderManager(ObjectManager): "goauthentik.io/providers/saml/name", name="authentik default SAML Mapping: Name", saml_name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", - expression="return user.name", + expression="return request.user.name", friendly_name="", ), EnsureExists( @@ -34,7 +34,7 @@ class SAMLProviderManager(ObjectManager): "goauthentik.io/providers/saml/email", name="authentik default SAML Mapping: Email", saml_name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", - expression="return user.email", + expression="return request.user.email", friendly_name="", ), EnsureExists( @@ -42,7 +42,7 @@ class SAMLProviderManager(ObjectManager): "goauthentik.io/providers/saml/username", name="authentik default SAML Mapping: Username", saml_name="http://schemas.goauthentik.io/2021/02/saml/username", - expression="return user.username", + expression="return request.user.username", friendly_name="", ), EnsureExists( @@ -50,7 +50,7 @@ class SAMLProviderManager(ObjectManager): "goauthentik.io/providers/saml/uid", name="authentik default SAML Mapping: User ID", saml_name="http://schemas.goauthentik.io/2021/02/saml/uid", - expression="return user.pk", + expression="return request.user.pk", friendly_name="", ), EnsureExists( @@ -68,7 +68,7 @@ class SAMLProviderManager(ObjectManager): saml_name=( "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname" ), - expression="return user.username", + expression="return request.user.username", friendly_name="", ), ] diff --git a/authentik/providers/saml/processors/assertion.py b/authentik/providers/saml/processors/assertion.py index ea273ab6b..20e14e1bf 100644 --- a/authentik/providers/saml/processors/assertion.py +++ b/authentik/providers/saml/processors/assertion.py @@ -24,6 +24,7 @@ from authentik.sources.saml.processors.constants import ( SAML_NAME_ID_FORMAT_EMAIL, SAML_NAME_ID_FORMAT_PERSISTENT, SAML_NAME_ID_FORMAT_TRANSIENT, + SAML_NAME_ID_FORMAT_UNSPECIFIED, SAML_NAME_ID_FORMAT_WINDOWS, SAML_NAME_ID_FORMAT_X509, SIGN_ALGORITHM_TRANSFORM_MAP, @@ -165,7 +166,10 @@ class AssertionProcessor: if name_id.attrib["Format"] == SAML_NAME_ID_FORMAT_EMAIL: name_id.text = self.http_request.user.email 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 return name_id if name_id.attrib["Format"] == SAML_NAME_ID_FORMAT_X509: @@ -180,7 +184,7 @@ class AssertionProcessor: return name_id if name_id.attrib["Format"] == SAML_NAME_ID_FORMAT_TRANSIENT: # 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() return name_id raise UnsupportedNameIDFormat( diff --git a/authentik/providers/saml/processors/request_parser.py b/authentik/providers/saml/processors/request_parser.py index f1d9393b3..71b7aa859 100644 --- a/authentik/providers/saml/processors/request_parser.py +++ b/authentik/providers/saml/processors/request_parser.py @@ -20,10 +20,11 @@ from authentik.sources.saml.processors.constants import ( RSA_SHA256, RSA_SHA384, RSA_SHA512, - SAML_NAME_ID_FORMAT_EMAIL, + SAML_NAME_ID_FORMAT_UNSPECIFIED, ) LOGGER = get_logger() +ERROR_CANNOT_DECODE_REQUEST = "Cannot decode SAML request." ERROR_SIGNATURE_REQUIRED_BUT_ABSENT = ( "Verification Certificate configured, but request is not signed." ) @@ -42,7 +43,7 @@ class AuthNRequest: 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: @@ -69,16 +70,21 @@ class AuthNRequestParser: auth_n_request = AuthNRequest(id=root.attrib["ID"], relay_state=relay_state) # 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: 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 def parse(self, saml_request: str, relay_state: Optional[str]) -> AuthNRequest: """Validate and parse raw request with enveloped signautre.""" - decoded_xml = b64decode(saml_request.encode()).decode() + try: + decoded_xml = b64decode(saml_request.encode()).decode() + except UnicodeDecodeError: + raise CannotHandleAssertion(ERROR_CANNOT_DECODE_REQUEST) verifier = self.provider.verification_kp @@ -121,7 +127,10 @@ class AuthNRequestParser: sig_alg: Optional[str] = None, ) -> AuthNRequest: """Validate and parse raw request with detached signature""" - decoded_xml = decode_base64_and_inflate(saml_request) + try: + decoded_xml = decode_base64_and_inflate(saml_request) + except UnicodeDecodeError: + raise CannotHandleAssertion(ERROR_CANNOT_DECODE_REQUEST) verifier = self.provider.verification_kp diff --git a/authentik/providers/saml/tests/test_auth_n_request.py b/authentik/providers/saml/tests/test_auth_n_request.py index b5527b5d5..ba5915a8e 100644 --- a/authentik/providers/saml/tests/test_auth_n_request.py +++ b/authentik/providers/saml/tests/test_auth_n_request.py @@ -14,7 +14,7 @@ from authentik.providers.saml.processors.assertion import AssertionProcessor from authentik.providers.saml.processors.request_parser import AuthNRequestParser from authentik.sources.saml.exceptions import MismatchedRequestID 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 ( SESSION_REQUEST_ID, RequestProcessor, @@ -206,5 +206,5 @@ class TestAuthNRequest(TestCase): REDIRECT_REQUEST, REDIRECT_RELAY_STATE, REDIRECT_SIGNATURE, REDIRECT_SIG_ALG ) 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) diff --git a/authentik/providers/saml/views/flows.py b/authentik/providers/saml/views/flows.py index 803ff6d19..f7f4c13de 100644 --- a/authentik/providers/saml/views/flows.py +++ b/authentik/providers/saml/views/flows.py @@ -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.request_parser import AuthNRequest from authentik.providers.saml.utils.encoding import deflate_and_base64_encode, nice64 +from authentik.sources.saml.exceptions import SAMLException LOGGER = get_logger() URL_VALIDATOR = URLValidator(schemes=("http", "https")) @@ -56,22 +57,30 @@ class SAMLFlowFinalView(ChallengeStageView): provider: SAMLProvider = get_object_or_404( 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: return self.executor.stage_invalid() auth_n_request: AuthNRequest = self.request.session.pop( SESSION_KEY_AUTH_N_REQUEST ) - response = AssertionProcessor( - provider, request, auth_n_request - ).build_response() + try: + response = AssertionProcessor( + provider, request, auth_n_request + ).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: form_attrs = { diff --git a/authentik/recovery/management/commands/create_recovery_key.py b/authentik/recovery/management/commands/create_recovery_key.py index 0ce420880..aa502d6bd 100644 --- a/authentik/recovery/management/commands/create_recovery_key.py +++ b/authentik/recovery/management/commands/create_recovery_key.py @@ -44,7 +44,7 @@ class Command(BaseCommand): user=user, intent=TokenIntents.INTENT_RECOVERY, description=f"Recovery Token generated by {getuser()} on {_now}", - identifier=f"ak-recovery-{user}", + identifier=f"ak-recovery-{user}-{_now}", ) self.stdout.write( ( diff --git a/authentik/root/settings.py b/authentik/root/settings.py index 61393826d..f832299e8 100644 --- a/authentik/root/settings.py +++ b/authentik/root/settings.py @@ -15,6 +15,7 @@ import logging import os import sys from json import dumps +from tempfile import gettempdir from time import time import structlog @@ -193,6 +194,7 @@ CACHES = { f"redis://:{CONFIG.y('redis.password')}@{CONFIG.y('redis.host')}:6379" f"/{CONFIG.y('redis.cache_db')}" ), + "TIMEOUT": int(CONFIG.y("redis.cache_timeout", 300)), "OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient"}, } } @@ -341,7 +343,7 @@ DBBACKUP_FILENAME_TEMPLATE = "authentik-backup-{datetime}.sql" DBBACKUP_CONNECTOR_MAPPING = { "django_prometheus.db.backends.postgresql": "dbbackup.db.postgresql.PgDumpConnector", } - +DBBACKUP_TMP_DIR = gettempdir() if DEBUG else "/tmp" # nosec if CONFIG.y("postgresql.s3_backup"): DBBACKUP_STORAGE = "storages.backends.s3boto3.S3Boto3Storage" DBBACKUP_STORAGE_OPTIONS = { diff --git a/authentik/root/test_runner.py b/authentik/root/test_runner.py index 810a8ce1f..9cab2175d 100644 --- a/authentik/root/test_runner.py +++ b/authentik/root/test_runner.py @@ -15,6 +15,10 @@ class PytestTestRunner: # pragma: no cover settings.CELERY_TASK_ALWAYS_EAGER = True CONFIG.y_set("authentik.avatars", "none") 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): """Run pytest and return the exitcode. diff --git a/authentik/sources/ldap/password.py b/authentik/sources/ldap/password.py index 65c042b68..24c064cfc 100644 --- a/authentik/sources/ldap/password.py +++ b/authentik/sources/ldap/password.py @@ -60,14 +60,21 @@ class LDAPPasswordChanger: def check_ad_password_complexity_enabled(self) -> bool: """Check if DOMAIN_PASSWORD_COMPLEX is enabled""" root_dn = self.get_domain_root_dn() - root_attrs = self._source.connection.extend.standard.paged_search( - search_base=root_dn, - search_filter="(objectClass=*)", - search_scope=ldap3.BASE, - attributes=["pwdProperties"], - ) + try: + root_attrs = self._source.connection.extend.standard.paged_search( + search_base=root_dn, + search_filter="(objectClass=*)", + search_scope=ldap3.BASE, + attributes=["pwdProperties"], + ) + except ldap3.core.exceptions.LDAPAttributeError: + return False 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: return True diff --git a/authentik/sources/saml/exceptions.py b/authentik/sources/saml/exceptions.py index 09f7afbff..344ee005b 100644 --- a/authentik/sources/saml/exceptions.py +++ b/authentik/sources/saml/exceptions.py @@ -2,17 +2,21 @@ 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.""" -class UnsupportedNameIDFormat(SentryIgnoredException): +class UnsupportedNameIDFormat(SAMLException): """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.""" -class InvalidSignature(SentryIgnoredException): +class InvalidSignature(SAMLException): """Signature of XML Object is either missing or invalid""" diff --git a/authentik/sources/saml/processors/constants.py b/authentik/sources/saml/processors/constants.py index b688ddac6..967a365cb 100644 --- a/authentik/sources/saml/processors/constants.py +++ b/authentik/sources/saml/processors/constants.py @@ -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_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_WINDOWS = ( "urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName" diff --git a/authentik/stages/authenticator_duo/api.py b/authentik/stages/authenticator_duo/api.py index 4e02f5966..b44670a8b 100644 --- a/authentik/stages/authenticator_duo/api.py +++ b/authentik/stages/authenticator_duo/api.py @@ -9,7 +9,7 @@ from rest_framework.permissions import IsAdminUser from rest_framework.request import Request from rest_framework.response import Response 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.core.api.used_by import UsedByMixin @@ -94,7 +94,7 @@ class DuoDeviceViewSet( filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter] -class DuoAdminDeviceViewSet(ReadOnlyModelViewSet): +class DuoAdminDeviceViewSet(ModelViewSet): """Viewset for Duo authenticator devices (for admins)""" permission_classes = [IsAdminUser] diff --git a/authentik/stages/authenticator_duo/stage.py b/authentik/stages/authenticator_duo/stage.py index 999f5d82c..abca61479 100644 --- a/authentik/stages/authenticator_duo/stage.py +++ b/authentik/stages/authenticator_duo/stage.py @@ -3,6 +3,7 @@ from django.http import HttpRequest, HttpResponse from rest_framework.fields import CharField from structlog.stdlib import get_logger +from authentik.events.models import Event, EventAction from authentik.flows.challenge import ( Challenge, ChallengeResponse, @@ -11,6 +12,7 @@ from authentik.flows.challenge import ( ) from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER from authentik.flows.stage import ChallengeStageView +from authentik.flows.views import InvalidStageError from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice LOGGER = get_logger() @@ -42,7 +44,15 @@ class AuthenticatorDuoStageView(ChallengeStageView): def get_challenge(self, *args, **kwargs) -> Challenge: user = self.get_pending_user() stage: AuthenticatorDuoStage = self.executor.current_stage - enroll = stage.client.enroll(user.username) + try: + 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"] self.request.session[SESSION_KEY_DUO_USER_ID] = user_id self.request.session[SESSION_KEY_DUO_ACTIVATION_CODE] = enroll[ diff --git a/authentik/stages/authenticator_validate/stage.py b/authentik/stages/authenticator_validate/stage.py index a6da94ffa..6312bfbc4 100644 --- a/authentik/stages/authenticator_validate/stage.py +++ b/authentik/stages/authenticator_validate/stage.py @@ -10,7 +10,7 @@ from authentik.flows.challenge import ( ChallengeTypes, 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.stage import ChallengeStageView from authentik.stages.authenticator_validate.challenge import ( @@ -143,9 +143,12 @@ class AuthenticatorValidateStageView(ChallengeStageView): return self.executor.stage_invalid() if stage.not_configured_action == NotConfiguredAction.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, # the configuration stage is next - self.executor.plan.insert(stage.configuration_stage) + self.executor.plan.insert(stage) return self.executor.stage_ok() return super().get(request, *args, **kwargs) diff --git a/authentik/stages/authenticator_validate/tests.py b/authentik/stages/authenticator_validate/tests.py index f0e604fbe..e66ef266e 100644 --- a/authentik/stages/authenticator_validate/tests.py +++ b/authentik/stages/authenticator_validate/tests.py @@ -4,11 +4,14 @@ from unittest.mock import MagicMock, patch from django.contrib.sessions.middleware import SessionMiddleware from django.test import TestCase 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 rest_framework.exceptions import ValidationError 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.providers.oauth2.generators import ( generate_client_id, @@ -24,7 +27,9 @@ from authentik.stages.authenticator_validate.challenge import ( validate_challenge_duo, validate_challenge_webauthn, ) +from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage from authentik.stages.authenticator_webauthn.models import WebAuthnDevice +from authentik.stages.identification.models import IdentificationStage, UserFields class AuthenticatorValidateStageTests(TestCase): @@ -34,6 +39,50 @@ class AuthenticatorValidateStageTests(TestCase): self.user = User.objects.get(username="akadmin") 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): """Test serializer validation""" self.client.force_login(self.user) diff --git a/authentik/tenants/api.py b/authentik/tenants/api.py index 3e85257e5..e5b7cc957 100644 --- a/authentik/tenants/api.py +++ b/authentik/tenants/api.py @@ -51,7 +51,7 @@ class CurrentTenantSerializer(PassiveSerializer): ui_footer_links = ListField( child=FooterLinkSerializer(), read_only=True, - default=CONFIG.y("authentik.footer_links"), + default=CONFIG.y("footer_links", []), ) flow_unenrollment = CharField(source="flow_unenrollment.slug", required=False) diff --git a/authentik/tenants/tests.py b/authentik/tenants/tests.py index f3455e8e4..15fbfd7a8 100644 --- a/authentik/tenants/tests.py +++ b/authentik/tenants/tests.py @@ -19,7 +19,26 @@ class TestTenants(TestCase): "branding_favicon": "/static/dist/assets/icons/icon.png", "branding_title": "authentik", "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_title": "authentik", "matched_domain": "fallback", - "ui_footer_links": CONFIG.y("authentik.footer_links"), + "ui_footer_links": CONFIG.y("footer_links"), }, ) diff --git a/authentik/tenants/utils.py b/authentik/tenants/utils.py index 8aafc30ce..06b9905e2 100644 --- a/authentik/tenants/utils.py +++ b/authentik/tenants/utils.py @@ -1,7 +1,8 @@ """Tenant utilities""" 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 authentik import __version__ @@ -14,8 +15,10 @@ DEFAULT_TENANT = Tenant(domain="fallback") def get_tenant_for_request(request: HttpRequest) -> Tenant: """Get tenant object for current request""" - db_tenants = Tenant.objects.filter( - Q(domain__iendswith=request.get_host()) | _q_default + db_tenants = ( + 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(): return DEFAULT_TENANT @@ -28,5 +31,5 @@ def context_processor(request: HttpRequest) -> dict[str, Any]: return { "tenant": tenant, "ak_version": __version__, - "footer_links": CONFIG.y("authentik.footer_links"), + "footer_links": CONFIG.y("footer_links"), } diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 9bae8cce6..5f03da0c7 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -148,7 +148,7 @@ stages: inputs: script: | pipenv run python -m scripts.generate_ci_config - pipenv run ./manage.py migrate + pipenv run python -m lifecycle.migrate - job: migrations_from_previous_release pool: vmImage: 'ubuntu-latest' diff --git a/cmd/server/main.go b/cmd/server/main.go index 8d4a91145..9e42d4df9 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -1,8 +1,11 @@ package main import ( + "errors" "fmt" - "sync" + "net/url" + "os" + "time" "github.com/getsentry/sentry-go" log "github.com/sirupsen/logrus" @@ -10,6 +13,8 @@ import ( "goauthentik.io/internal/config" "goauthentik.io/internal/constants" "goauthentik.io/internal/gounicorn" + "goauthentik.io/internal/outpost/ak" + "goauthentik.io/internal/outpost/proxy" "goauthentik.io/internal/web" ) @@ -30,23 +35,53 @@ func main() { }) } + ex := common.Init() defer common.Defer() - rl := log.WithField("logger", "authentik.g") - wg := sync.WaitGroup{} - wg.Add(3) - go func() { - defer wg.Done() + u, _ := url.Parse("http://localhost:8000") + + for { g := gounicorn.NewGoUnicorn() - for { - err := g.Start() - rl.WithError(err).Warning("gunicorn process died, restarting") - } - }() - go func() { - defer wg.Done() + go attemptStartBackend(g) + ws := web.NewWebServer() ws.Run() + + 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 + } + } + } }() - wg.Wait() +} + +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 } diff --git a/docker-compose.yml b/docker-compose.yml index a005cfe5a..465cf68b0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,7 +21,7 @@ services: networks: - internal 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 command: server environment: @@ -38,21 +38,13 @@ services: - geoip:/geoip networks: - 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 ports: - "0.0.0.0:9000:9000" - "0.0.0.0:9443:9443" 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 command: worker networks: diff --git a/internal/config/config.go b/internal/config/config.go index c1811dd5c..db640f8d5 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -16,8 +16,9 @@ func DefaultConfig() { G = Config{ Debug: false, Web: WebConfig{ - Listen: "localhost:9000", - ListenTLS: "localhost:9443", + Listen: "localhost:9000", + ListenTLS: "localhost:9443", + LoadLocalFiles: false, }, Paths: PathsConfig{ Media: "./media", diff --git a/internal/config/struct.go b/internal/config/struct.go index 155e7f702..984bf8b6c 100644 --- a/internal/config/struct.go +++ b/internal/config/struct.go @@ -2,6 +2,7 @@ package config type Config struct { Debug bool `yaml:"debug"` + SecretKey string `yaml:"secret_key"` Web WebConfig `yaml:"web"` Paths PathsConfig `yaml:"paths"` LogLevel string `yaml:"log_level"` @@ -9,8 +10,9 @@ type Config struct { } type WebConfig struct { - Listen string `yaml:"listen"` - ListenTLS string `yaml:"listen_tls"` + Listen string `yaml:"listen"` + ListenTLS string `yaml:"listen_tls"` + LoadLocalFiles bool `yaml:"load_local_files"` } type PathsConfig struct { diff --git a/internal/constants/constants.go b/internal/constants/constants.go index e83d74157..998db0ad6 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -5,7 +5,7 @@ import ( "os" ) -const VERSION = "2021.6.1-rc6" +const VERSION = "2021.6.2" func BUILD() string { build := os.Getenv("GIT_BUILD_HASH") diff --git a/internal/gounicorn/gounicorn.go b/internal/gounicorn/gounicorn.go index 9345974b5..cdbc907c8 100644 --- a/internal/gounicorn/gounicorn.go +++ b/internal/gounicorn/gounicorn.go @@ -10,27 +10,34 @@ import ( type GoUnicorn struct { log *log.Entry + p *exec.Cmd } func NewGoUnicorn() *GoUnicorn { - return &GoUnicorn{ - log: log.WithField("logger", "authentik.g.unicorn"), - } -} - -func (g *GoUnicorn) Start() error { + logger := log.WithField("logger", "authentik.g.unicorn") command := "gunicorn" args := []string{"-c", "./lifecycle/gunicorn.conf.py", "authentik.root.asgi:application"} if config.G.Debug { command = "python" 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.Env = append(os.Environ(), "WORKERS=2", ) p.Stdout = os.Stdout 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() } diff --git a/internal/outpost/ak/api.go b/internal/outpost/ak/api.go index 176534b36..3ceab548a 100644 --- a/internal/outpost/ak/api.go +++ b/internal/outpost/ak/api.go @@ -6,7 +6,6 @@ import ( "math/rand" "net/http" "net/url" - "os" "time" "github.com/go-openapi/strfmt" @@ -59,7 +58,7 @@ func NewAPIController(akURL url.URL, token string) *APIController { if err != nil { log.WithError(err).Error("Failed to fetch configuration") - os.Exit(1) + return nil } outpost := outposts.Results[0] doGlobalSetup(outpost.Config) diff --git a/internal/outpost/proxy/proxy.go b/internal/outpost/proxy/proxy.go index c0f7ce5f4..ec0aae395 100644 --- a/internal/outpost/proxy/proxy.go +++ b/internal/outpost/proxy/proxy.go @@ -446,15 +446,17 @@ func (p *OAuthProxy) addHeadersForProxying(rw http.ResponseWriter, req *http.Req username = session.Email } 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)} } // 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 { return } for key, value := range additionalHeaders { - req.Header.Set(key, value) + req.Header.Set(key, toString(value)) } } } diff --git a/internal/outpost/proxy/utils.go b/internal/outpost/proxy/utils.go index 32e26c905..6fb02841f 100644 --- a/internal/outpost/proxy/utils.go +++ b/internal/outpost/proxy/utils.go @@ -3,6 +3,7 @@ package proxy import ( "net" "net/http" + "strconv" ) var xForwardedHost = http.CanonicalHeaderKey("X-Forwarded-Host") @@ -18,3 +19,16 @@ func getHost(req *http.Request) string { } 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 "" +} diff --git a/internal/web/web.go b/internal/web/web.go index 3c08e10a7..bcf5f4f17 100644 --- a/internal/web/web.go +++ b/internal/web/web.go @@ -5,7 +5,6 @@ import ( "errors" "net" "net/http" - "sync" "github.com/gorilla/handlers" "github.com/gorilla/mux" @@ -49,17 +48,12 @@ func NewWebServer() *WebServer { } func (ws *WebServer) Run() { - wg := sync.WaitGroup{} - wg.Add(2) - go func() { - defer wg.Done() - ws.listenPlain() - }() - go func() { - defer wg.Done() - ws.listenTLS() - }() - wg.Done() + ws.listenPlain() + ws.listenTLS() +} + +func (ws *WebServer) Stop() { + ws.stop <- struct{}{} } func (ws *WebServer) listenPlain() { diff --git a/internal/web/web_static.go b/internal/web/web_static.go index 41452f480..06d30eddd 100644 --- a/internal/web/web_static.go +++ b/internal/web/web_static.go @@ -10,7 +10,7 @@ import ( func (ws *WebServer) configureStatic() { 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.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")))) diff --git a/outpost/Makefile b/outpost/Makefile index 03a50e4ff..c6c6caa6b 100644 --- a/outpost/Makefile +++ b/outpost/Makefile @@ -1,9 +1,5 @@ all: clean -run: - go run -v . - clean: go mod tidy go clean . - diff --git a/schema.yml b/schema.yml index e44f9233d..8e85f84f7 100644 --- a/schema.yml +++ b/schema.yml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: title: authentik - version: 2021.6.1-rc5 + version: 2021.6.2 description: Making authentication simple. contact: email: hello@beryju.org @@ -236,6 +236,37 @@ paths: $ref: '#/components/schemas/ValidationError' '403': $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}/: get: operationId: authenticators_admin_duo_retrieve @@ -263,6 +294,103 @@ paths: $ref: '#/components/schemas/ValidationError' '403': $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/: get: operationId: authenticators_admin_static_list diff --git a/tests/e2e/test_provider_proxy.py b/tests/e2e/test_provider_proxy.py index 467d1349b..1875ff7c2 100644 --- a/tests/e2e/test_provider_proxy.py +++ b/tests/e2e/test_provider_proxy.py @@ -21,7 +21,13 @@ from authentik.outposts.models import ( ) from authentik.outposts.tasks import outpost_local_connection 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") @@ -47,7 +53,7 @@ class TestProviderProxy(SeleniumTestCase): """Start proxy container based on outpost created""" client: DockerClient = from_env() container = client.containers.run( - image=f"ghcr.io/goauthentik/proxy:{__version__}", + image="beryju.org/authentik/outpost-proxy:gh-master", detach=True, network_mode="host", auto_remove=True, @@ -67,6 +73,11 @@ class TestProviderProxy(SeleniumTestCase): @object_manager def test_proxy_simple(self): """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( name="proxy_provider", authorization_flow=Flow.objects.get( @@ -106,6 +117,7 @@ class TestProviderProxy(SeleniumTestCase): 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-Foo: bar", full_body_text) @skipUnless(platform.startswith("linux"), "requires local docker") diff --git a/tests/integration/test_outpost_docker.py b/tests/integration/test_outpost_docker.py index a513699ee..10d4b9876 100644 --- a/tests/integration/test_outpost_docker.py +++ b/tests/integration/test_outpost_docker.py @@ -104,5 +104,5 @@ class OutpostDockerTests(TestCase): self.assertEqual(compose["version"], "3.5") self.assertEqual( compose["services"]["authentik_proxy"]["image"], - f"ghcr.io/goauthentik/proxy:{__version__}", + "beryju.org/authentik/outpost-proxy:gh-master", ) diff --git a/tests/integration/test_proxy_docker.py b/tests/integration/test_proxy_docker.py index 7e1aeb5b1..2b499e626 100644 --- a/tests/integration/test_proxy_docker.py +++ b/tests/integration/test_proxy_docker.py @@ -104,5 +104,5 @@ class TestProxyDocker(TestCase): self.assertEqual(compose["version"], "3.5") self.assertEqual( compose["services"]["authentik_proxy"]["image"], - f"ghcr.io/goauthentik/proxy:{__version__}", + "beryju.org/authentik/outpost-proxy:gh-master", ) diff --git a/web/package-lock.json b/web/package-lock.json index 22e05a570..2cbb3c248 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -12,11 +12,11 @@ "@babel/core": "^7.14.6", "@babel/plugin-proposal-decorators": "^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", "@fortawesome/fontawesome-free": "^5.15.3", "@lingui/cli": "^3.10.2", - "@lingui/core": "^3.10.3", + "@lingui/core": "^3.10.4", "@lingui/macro": "^3.10.2", "@patternfly/patternfly": "^4.108.2", "@polymer/iron-form": "^3.0.1", @@ -24,22 +24,22 @@ "@rollup/plugin-babel": "^5.3.0", "@rollup/plugin-replace": "^2.4.2", "@rollup/plugin-typescript": "^8.2.1", - "@sentry/browser": "^6.7.1", - "@sentry/tracing": "^6.7.1", + "@sentry/browser": "^6.7.2", + "@sentry/tracing": "^6.7.2", "@types/chart.js": "^2.9.32", "@types/codemirror": "5.60.0", "@types/grecaptcha": "^3.0.2", - "@typescript-eslint/eslint-plugin": "^4.27.0", - "@typescript-eslint/parser": "^4.27.0", + "@typescript-eslint/eslint-plugin": "^4.28.0", + "@typescript-eslint/parser": "^4.28.0", "@webcomponents/webcomponentsjs": "^2.5.0", "authentik-api": "file:api", "babel-plugin-macros": "^3.1.0", "base64-js": "^1.5.1", "chart.js": "^3.3.2", "chartjs-adapter-moment": "^1.0.0", - "codemirror": "^5.61.1", + "codemirror": "^5.62.0", "construct-style-sheets-polyfill": "^2.4.16", - "eslint": "^7.28.0", + "eslint": "^7.29.0", "eslint-config-google": "^0.14.0", "eslint-plugin-custom-elements": "0.0.2", "eslint-plugin-lit": "^1.5.1", @@ -48,7 +48,7 @@ "lit-html": "^1.4.1", "moment": "^2.29.1", "rapidoc": "^9.0.0", - "rollup": "^2.51.2", + "rollup": "^2.52.2", "rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-copy": "^3.4.0", "rollup-plugin-cssimport": "^1.0.2", @@ -58,7 +58,7 @@ "rollup-plugin-terser": "^7.0.2", "ts-lit-plugin": "^1.2.1", "tslib": "^2.3.0", - "typescript": "^4.3.2", + "typescript": "^4.3.4", "webcomponent-qr-code": "^1.0.5", "yaml": "^1.10.2" } @@ -102,9 +102,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.5.tgz", - "integrity": "sha512-kixrYn4JwfAVPa0f2yfzc2AWti6WRRyO3XjWW5PJAvtE11qhSayrrcrEnee05KAtNaPC+EwehE8Qt1UedEVB8w==", + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.7.tgz", + "integrity": "sha512-nS6dZaISCXJ3+518CWiBfEr//gHyMO02uDxBkXTKZDN5POruCnOZ1N4YBRZDCabwF8nZMWBpRxIicmXtBs+fvw==", "engines": { "node": ">=6.9.0" } @@ -532,9 +532,9 @@ } }, "node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.14.5.tgz", - "integrity": "sha512-tbD/CG3l43FIXxmu4a7RBe4zH7MLJ+S/lFowPFO7HetS2hyOZ/0nnnznegDuzFzfkyQYTxqdTH/hKmuBngaDAA==", + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.14.7.tgz", + "integrity": "sha512-RK8Wj7lXLY3bqei69/cc25gwS5puEc3dknoFPFbqfy3XxYQBQFvu4ioWpafMBAB+L9NyptQK4nMOa5Xz16og8Q==", "dependencies": { "@babel/helper-plugin-utils": "^7.14.5", "@babel/helper-remap-async-to-generator": "^7.14.5", @@ -685,11 +685,11 @@ } }, "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.14.5.tgz", - "integrity": "sha512-VzMyY6PWNPPT3pxc5hi9LloKNr4SSrVCg7Yr6aZpW4Ym07r7KqSU/QXYwjXLVxqwSv0t/XSXkFoKBPUkZ8vb2A==", + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.14.7.tgz", + "integrity": "sha512-082hsZz+sVabfmDWo1Oct1u1AgbKbUAyVgmX4otIc7bdsRgHBXwTwb3DpDmD4Eyyx6DNiuz5UAATT655k+kL5g==", "dependencies": { - "@babel/compat-data": "^7.14.5", + "@babel/compat-data": "^7.14.7", "@babel/helper-compilation-targets": "^7.14.5", "@babel/helper-plugin-utils": "^7.14.5", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", @@ -1077,9 +1077,9 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.14.5.tgz", - "integrity": "sha512-wU9tYisEbRMxqDezKUqC9GleLycCRoUsai9ddlsq54r8QRLaeEhc+d+9DqCG+kV9W2GgQjTZESPTpn5bAFMDww==", + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.14.7.tgz", + "integrity": "sha512-0mDE99nK+kVh3xlc5vKwB6wnP9ecuSj+zQCa/n0voENtP/zymdT4HH6QEb65wjjcbqr1Jb/7z9Qp7TF5FtwYGw==", "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, @@ -1258,9 +1258,9 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.14.5.tgz", - "integrity": "sha512-+Xe5+6MWFo311U8SchgeX5c1+lJM+eZDBZgD+tvXu9VVQPXwwVzeManMMjYX6xw2HczngfOSZjoFYKwdeB/Jvw==", + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.14.7.tgz", + "integrity": "sha512-DTNOTaS7TkW97xsDMrp7nycUVh6sn/eq22VaxWfEdzuEbRsiaOU0pqU7DlyUGHVsbQbSghvjKRpEl+nUCKGQSg==", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.14.5" }, @@ -1398,9 +1398,9 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.14.5.tgz", - "integrity": "sha512-/3iqoQdiWergnShZYl0xACb4ADeYCJ7X/RgmwtXshn6cIvautRPAFzhd58frQlokLO6Jb4/3JXvmm6WNTPtiTw==", + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.14.6.tgz", + "integrity": "sha512-Zr0x0YroFJku7n7+/HH3A2eIrGMjbmAIbJSVv0IZ+t3U2WUQUA64S/oeied2e+MaGSjmt4alzBCsK9E8gh+fag==", "dependencies": { "@babel/helper-plugin-utils": "^7.14.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.14.5" @@ -1500,16 +1500,16 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.14.5.tgz", - "integrity": "sha512-ci6TsS0bjrdPpWGnQ+m4f+JSSzDKlckqKIJJt9UZ/+g7Zz9k0N8lYU8IeLg/01o2h8LyNZDMLGgRLDTxpudLsA==", + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.14.7.tgz", + "integrity": "sha512-itOGqCKLsSUl0Y+1nSfhbuuOlTs0MJk2Iv7iSH+XT/mR8U1zRLO7NjWlYXB47yhK4J/7j+HYty/EhFZDYKa/VA==", "dependencies": { - "@babel/compat-data": "^7.14.5", + "@babel/compat-data": "^7.14.7", "@babel/helper-compilation-targets": "^7.14.5", "@babel/helper-plugin-utils": "^7.14.5", "@babel/helper-validator-option": "^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-static-block": "^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-nullish-coalescing-operator": "^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-chaining": "^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-classes": "^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-duplicate-keys": "^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-systemjs": "^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-object-super": "^7.14.5", "@babel/plugin-transform-parameters": "^7.14.5", @@ -1564,7 +1564,7 @@ "@babel/plugin-transform-regenerator": "^7.14.5", "@babel/plugin-transform-reserved-words": "^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-template-literals": "^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-corejs3": "^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" }, "engines": { @@ -2047,9 +2047,9 @@ } }, "node_modules/@lingui/core": { - "version": "3.10.3", - "resolved": "https://registry.npmjs.org/@lingui/core/-/core-3.10.3.tgz", - "integrity": "sha512-BiuWi5xPpQa27oIWWnkOYNx4qTMdMeu7vp5y1AGPYQ/4SO0rHfAtOxXtvRU/ktVwht/lIgx5Ygq5J3F+XLvOQA==", + "version": "3.10.4", + "resolved": "https://registry.npmjs.org/@lingui/core/-/core-3.10.4.tgz", + "integrity": "sha512-V9QKQ9PFMTPrGGz2PaeKHZcxFikQZzJbptyQbVFJdXaKhdE2RH6HhdK1PIziDHqp6ZWPthVIfVLURT3ku8eu5w==", "dependencies": { "@babel/runtime": "^7.11.2", "make-plural": "^6.2.2", @@ -2314,13 +2314,13 @@ "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==" }, "node_modules/@sentry/browser": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.7.1.tgz", - "integrity": "sha512-R5PYx4TTvifcU790XkK6JVGwavKaXwycDU0MaAwfc4Vf7BLm5KCNJCsDySu1RPAap/017MVYf54p6dWvKiRviA==", + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.7.2.tgz", + "integrity": "sha512-Lv0Ne1QcesyGAhVcQDfQa3hDPR/MhPSDTMg3xFi+LxqztchVc4w/ynzR0wCZFb8KIHpTj5SpJHfxpDhXYMtS9g==", "dependencies": { - "@sentry/core": "6.7.1", - "@sentry/types": "6.7.1", - "@sentry/utils": "6.7.1", + "@sentry/core": "6.7.2", + "@sentry/types": "6.7.2", + "@sentry/utils": "6.7.2", "tslib": "^1.9.3" }, "engines": { @@ -2333,14 +2333,14 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@sentry/core": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.7.1.tgz", - "integrity": "sha512-VAv8OR/7INn2JfiLcuop4hfDcyC7mfL9fdPndQEhlacjmw8gRrgXjR7qyhnCTgzFLkHI7V5bcdIzA83TRPYQpA==", + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.7.2.tgz", + "integrity": "sha512-NTZqwN5nR94yrXmSfekoPs1mIFuKvf8esdIW/DadwSKWAdLJwQTJY9xK/8PQv+SEzd7wiitPAx+mCw2By1xiNQ==", "dependencies": { - "@sentry/hub": "6.7.1", - "@sentry/minimal": "6.7.1", - "@sentry/types": "6.7.1", - "@sentry/utils": "6.7.1", + "@sentry/hub": "6.7.2", + "@sentry/minimal": "6.7.2", + "@sentry/types": "6.7.2", + "@sentry/utils": "6.7.2", "tslib": "^1.9.3" }, "engines": { @@ -2353,12 +2353,12 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@sentry/hub": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.7.1.tgz", - "integrity": "sha512-eVCTWvvcp6xa0A5GGNHMQEWslmKPlisE5rGmsV/kjvSUv3zSrI0eIDfb51ikdnCiBjHpK2NBWP8Vy8cZOEJegg==", + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.7.2.tgz", + "integrity": "sha512-05qVW6ymChJsXag4+fYCQokW3AcABIgcqrVYZUBf6GMU/Gbz5SJqpV7y1+njwWvnPZydMncP9LaDVpMKbE7UYQ==", "dependencies": { - "@sentry/types": "6.7.1", - "@sentry/utils": "6.7.1", + "@sentry/types": "6.7.2", + "@sentry/utils": "6.7.2", "tslib": "^1.9.3" }, "engines": { @@ -2371,12 +2371,12 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@sentry/minimal": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.7.1.tgz", - "integrity": "sha512-HDDPEnQRD6hC0qaHdqqKDStcdE1KhkFh0RCtJNMCDn0zpav8Dj9AteF70x6kLSlliAJ/JFwi6AmQrLz+FxPexw==", + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.7.2.tgz", + "integrity": "sha512-jkpwFv2GFHoVl5vnK+9/Q+Ea8eVdbJ3hn3/Dqq9MOLFnVK7ED6MhdHKLT79puGSFj+85OuhM5m2Q44mIhyS5mw==", "dependencies": { - "@sentry/hub": "6.7.1", - "@sentry/types": "6.7.1", + "@sentry/hub": "6.7.2", + "@sentry/types": "6.7.2", "tslib": "^1.9.3" }, "engines": { @@ -2389,14 +2389,14 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@sentry/tracing": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.7.1.tgz", - "integrity": "sha512-wyS3nWNl5mzaC1qZ2AIp1hjXnfO9EERjMIJjCihs2LWBz1r3efxrHxJHs8wXlNWvrT3KLhq/7vvF5CdU82uPeQ==", + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.7.2.tgz", + "integrity": "sha512-juKlI7FICKONWJFJxDxerj0A+8mNRhmtrdR+OXFqOkqSAy/QXlSFZcA/j//O19k2CfwK1BrvoMcQ/4gnffUOVg==", "dependencies": { - "@sentry/hub": "6.7.1", - "@sentry/minimal": "6.7.1", - "@sentry/types": "6.7.1", - "@sentry/utils": "6.7.1", + "@sentry/hub": "6.7.2", + "@sentry/minimal": "6.7.2", + "@sentry/types": "6.7.2", + "@sentry/utils": "6.7.2", "tslib": "^1.9.3" }, "engines": { @@ -2409,19 +2409,19 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@sentry/types": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.7.1.tgz", - "integrity": "sha512-9AO7HKoip2MBMNQJEd6+AKtjj2+q9Ze4ooWUdEvdOVSt5drg7BGpK221/p9JEOyJAZwEPEXdcMd3VAIMiOb4MA==", + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.7.2.tgz", + "integrity": "sha512-h21Go/PfstUN+ZV6SbwRSZVg9GXRJWdLfHoO5PSVb3TVEMckuxk8tAE57/u+UZDwX8wu+Xyon2TgsKpiWKxqUg==", "engines": { "node": ">=6" } }, "node_modules/@sentry/utils": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.7.1.tgz", - "integrity": "sha512-Tq2otdbWlHAkctD+EWTYKkEx6BL1Qn3Z/imkO06/PvzpWvVhJWQ5qHAzz5XnwwqNHyV03KVzYB6znq1Bea9HuA==", + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.7.2.tgz", + "integrity": "sha512-9COL7aaBbe61Hp5BlArtXZ1o/cxli1NGONLPrVT4fMyeQFmLonhUiy77NdsW19XnvhvaA+2IoV5dg3dnFiF/og==", "dependencies": { - "@sentry/types": "6.7.1", + "@sentry/types": "6.7.2", "tslib": "^1.9.3" }, "engines": { @@ -2579,15 +2579,14 @@ "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "4.27.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.27.0.tgz", - "integrity": "sha512-DsLqxeUfLVNp3AO7PC3JyaddmEHTtI9qTSAs+RB6ja27QvIM0TA8Cizn1qcS6vOu+WDLFJzkwkgweiyFhssDdQ==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.0.tgz", + "integrity": "sha512-KcF6p3zWhf1f8xO84tuBailV5cN92vhS+VT7UJsPzGBm9VnQqfI9AsiMUFUCYHTYPg1uCCo+HyiDnpDuvkAMfQ==", "dependencies": { - "@typescript-eslint/experimental-utils": "4.27.0", - "@typescript-eslint/scope-manager": "4.27.0", + "@typescript-eslint/experimental-utils": "4.28.0", + "@typescript-eslint/scope-manager": "4.28.0", "debug": "^4.3.1", "functional-red-black-tree": "^1.0.1", - "lodash": "^4.17.21", "regexpp": "^3.1.0", "semver": "^7.3.5", "tsutils": "^3.21.0" @@ -2610,14 +2609,14 @@ } }, "node_modules/@typescript-eslint/experimental-utils": { - "version": "4.27.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.27.0.tgz", - "integrity": "sha512-n5NlbnmzT2MXlyT+Y0Jf0gsmAQzCnQSWXKy4RGSXVStjDvS5we9IWbh7qRVKdGcxT0WYlgcCYUK/HRg7xFhvjQ==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.0.tgz", + "integrity": "sha512-9XD9s7mt3QWMk82GoyUpc/Ji03vz4T5AYlHF9DcoFNfJ/y3UAclRsfGiE2gLfXtyC+JRA3trR7cR296TEb1oiQ==", "dependencies": { "@types/json-schema": "^7.0.7", - "@typescript-eslint/scope-manager": "4.27.0", - "@typescript-eslint/types": "4.27.0", - "@typescript-eslint/typescript-estree": "4.27.0", + "@typescript-eslint/scope-manager": "4.28.0", + "@typescript-eslint/types": "4.28.0", + "@typescript-eslint/typescript-estree": "4.28.0", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0" }, @@ -2650,13 +2649,13 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "4.27.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.27.0.tgz", - "integrity": "sha512-XpbxL+M+gClmJcJ5kHnUpBGmlGdgNvy6cehgR6ufyxkEJMGP25tZKCaKyC0W/JVpuhU3VU1RBn7SYUPKSMqQvQ==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.0.tgz", + "integrity": "sha512-7x4D22oPY8fDaOCvkuXtYYTQ6mTMmkivwEzS+7iml9F9VkHGbbZ3x4fHRwxAb5KeuSkLqfnYjs46tGx2Nour4A==", "dependencies": { - "@typescript-eslint/scope-manager": "4.27.0", - "@typescript-eslint/types": "4.27.0", - "@typescript-eslint/typescript-estree": "4.27.0", + "@typescript-eslint/scope-manager": "4.28.0", + "@typescript-eslint/types": "4.28.0", + "@typescript-eslint/typescript-estree": "4.28.0", "debug": "^4.3.1" }, "engines": { @@ -2676,12 +2675,12 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "4.27.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.27.0.tgz", - "integrity": "sha512-DY73jK6SEH6UDdzc6maF19AHQJBFVRf6fgAXHPXCGEmpqD4vYgPEzqpFz1lf/daSbOcMpPPj9tyXXDPW2XReAw==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.0.tgz", + "integrity": "sha512-eCALCeScs5P/EYjwo6se9bdjtrh8ByWjtHzOkC4Tia6QQWtQr3PHovxh3TdYTuFcurkYI4rmFsRFpucADIkseg==", "dependencies": { - "@typescript-eslint/types": "4.27.0", - "@typescript-eslint/visitor-keys": "4.27.0" + "@typescript-eslint/types": "4.28.0", + "@typescript-eslint/visitor-keys": "4.28.0" }, "engines": { "node": "^8.10.0 || ^10.13.0 || >=11.10.1" @@ -2692,9 +2691,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "4.27.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.27.0.tgz", - "integrity": "sha512-I4ps3SCPFCKclRcvnsVA/7sWzh7naaM/b4pBO2hVxnM3wrU51Lveybdw5WoIktU/V4KfXrTt94V9b065b/0+wA==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.0.tgz", + "integrity": "sha512-p16xMNKKoiJCVZY5PW/AfILw2xe1LfruTcfAKBj3a+wgNYP5I9ZEKNDOItoRt53p4EiPV6iRSICy8EPanG9ZVA==", "engines": { "node": "^8.10.0 || ^10.13.0 || >=11.10.1" }, @@ -2704,12 +2703,12 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "4.27.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.27.0.tgz", - "integrity": "sha512-KH03GUsUj41sRLLEy2JHstnezgpS5VNhrJouRdmh6yNdQ+yl8w5LrSwBkExM+jWwCJa7Ct2c8yl8NdtNRyQO6g==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.0.tgz", + "integrity": "sha512-m19UQTRtxMzKAm8QxfKpvh6OwQSXaW1CdZPoCaQuLwAq7VZMNuhJmZR4g5281s2ECt658sldnJfdpSZZaxUGMQ==", "dependencies": { - "@typescript-eslint/types": "4.27.0", - "@typescript-eslint/visitor-keys": "4.27.0", + "@typescript-eslint/types": "4.28.0", + "@typescript-eslint/visitor-keys": "4.28.0", "debug": "^4.3.1", "globby": "^11.0.3", "is-glob": "^4.0.1", @@ -2730,9 +2729,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/globby": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz", - "integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==", + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", + "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -2749,11 +2748,11 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "4.27.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.27.0.tgz", - "integrity": "sha512-es0GRYNZp0ieckZ938cEANfEhsfHrzuLrePukLKtY3/KPXcq1Xd555Mno9/GOgXhKzn0QfkDLVgqWO3dGY80bg==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.0.tgz", + "integrity": "sha512-PjJyTWwrlrvM5jazxYF5ZPs/nl0kHDZMVbuIcbpawVXaDPelp3+S9zpOz5RmVUfS/fD5l5+ZXNKnWhNYjPzCvw==", "dependencies": { - "@typescript-eslint/types": "4.27.0", + "@typescript-eslint/types": "4.28.0", "eslint-visitor-keys": "^2.0.0" }, "engines": { @@ -3476,9 +3475,9 @@ } }, "node_modules/codemirror": { - "version": "5.61.1", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.61.1.tgz", - "integrity": "sha512-+D1NZjAucuzE93vJGbAaXzvoBHwp9nJZWWWF9utjv25+5AZUiah6CIlfb4ikG4MoDsFsCG8niiJH5++OO2LgIQ==" + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.62.0.tgz", + "integrity": "sha512-Xnl3304iCc8nyVZuRkzDVVwc794uc9QNX0UcPGeNic1fbzkSrO4l4GVXho9tRNKBgPYZXgocUqXyfIv3BILhCQ==" }, "node_modules/collection-visit": { "version": "1.0.0", @@ -3579,9 +3578,9 @@ } }, "node_modules/core-js-compat": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.14.0.tgz", - "integrity": "sha512-R4NS2eupxtiJU+VwgkF9WTpnSfZW4pogwKHd8bclWU2sp93Pr5S1uYJI84cMOubJRou7bcfL0vmwtLslWN5p3A==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.15.0.tgz", + "integrity": "sha512-8X6lWsG+s7IfOKzV93a7fRYfWRZobOfjw5V5rrq43Vh/W+V6qYxl7Akalsvgab4PFT/4L/pjQbdBUEM36NXKrw==", "dependencies": { "browserslist": "^4.16.6", "semver": "7.0.0" @@ -3862,9 +3861,9 @@ } }, "node_modules/eslint": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.28.0.tgz", - "integrity": "sha512-UMfH0VSjP0G4p3EWirscJEQ/cHqnT/iuH6oNZOB94nBjWbMnhGEPxsZm1eyIW0C/9jLI0Fow4W5DXLjEI7mn1g==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.29.0.tgz", + "integrity": "sha512-82G/JToB9qIy/ArBzIWG9xvvwL3R86AlCjtGw+A29OMZDqhTybz/MByORSukGxeI+YPCR4coYyITKk8BFH9nDA==", "dependencies": { "@babel/code-frame": "7.12.11", "@eslint/eslintrc": "^0.4.2", @@ -6771,9 +6770,9 @@ } }, "node_modules/rollup": { - "version": "2.51.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.51.2.tgz", - "integrity": "sha512-ReV2eGEadA7hmXSzjxdDKs10neqH2QURf2RxJ6ayAlq93ugy6qIvXMmbc5cWMGCDh1h5T4thuWO1e2VNbMq8FA==", + "version": "2.52.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.52.2.tgz", + "integrity": "sha512-4RlFC3k2BIHlUsJ9mGd8OO+9Lm2eDF5P7+6DNQOp5sx+7N/1tFM01kELfbxlMX3MxT6owvLB1ln4S3QvvQlbUA==", "bin": { "rollup": "dist/bin/rollup" }, @@ -6781,7 +6780,7 @@ "node": ">=10.0.0" }, "optionalDependencies": { - "fsevents": "~2.3.1" + "fsevents": "~2.3.2" } }, "node_modules/rollup-plugin-commonjs": { @@ -7605,9 +7604,9 @@ } }, "node_modules/typescript": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz", - "integrity": "sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.4.tgz", + "integrity": "sha512-uauPG7XZn9F/mo+7MrsRjyvbxFpzemRjKEZXS4AK83oP2KKOJPvb+9cO/gmnv8arWZvhnjVOXz7B49m1l0e9Ew==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -8066,9 +8065,9 @@ } }, "@babel/compat-data": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.5.tgz", - "integrity": "sha512-kixrYn4JwfAVPa0f2yfzc2AWti6WRRyO3XjWW5PJAvtE11qhSayrrcrEnee05KAtNaPC+EwehE8Qt1UedEVB8w==" + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.7.tgz", + "integrity": "sha512-nS6dZaISCXJ3+518CWiBfEr//gHyMO02uDxBkXTKZDN5POruCnOZ1N4YBRZDCabwF8nZMWBpRxIicmXtBs+fvw==" }, "@babel/core": { "version": "7.14.6", @@ -8380,9 +8379,9 @@ } }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.14.5.tgz", - "integrity": "sha512-tbD/CG3l43FIXxmu4a7RBe4zH7MLJ+S/lFowPFO7HetS2hyOZ/0nnnznegDuzFzfkyQYTxqdTH/hKmuBngaDAA==", + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.14.7.tgz", + "integrity": "sha512-RK8Wj7lXLY3bqei69/cc25gwS5puEc3dknoFPFbqfy3XxYQBQFvu4ioWpafMBAB+L9NyptQK4nMOa5Xz16og8Q==", "requires": { "@babel/helper-plugin-utils": "^7.14.5", "@babel/helper-remap-async-to-generator": "^7.14.5", @@ -8473,11 +8472,11 @@ } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.14.5.tgz", - "integrity": "sha512-VzMyY6PWNPPT3pxc5hi9LloKNr4SSrVCg7Yr6aZpW4Ym07r7KqSU/QXYwjXLVxqwSv0t/XSXkFoKBPUkZ8vb2A==", + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.14.7.tgz", + "integrity": "sha512-082hsZz+sVabfmDWo1Oct1u1AgbKbUAyVgmX4otIc7bdsRgHBXwTwb3DpDmD4Eyyx6DNiuz5UAATT655k+kL5g==", "requires": { - "@babel/compat-data": "^7.14.5", + "@babel/compat-data": "^7.14.7", "@babel/helper-compilation-targets": "^7.14.5", "@babel/helper-plugin-utils": "^7.14.5", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", @@ -8732,9 +8731,9 @@ } }, "@babel/plugin-transform-destructuring": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.14.5.tgz", - "integrity": "sha512-wU9tYisEbRMxqDezKUqC9GleLycCRoUsai9ddlsq54r8QRLaeEhc+d+9DqCG+kV9W2GgQjTZESPTpn5bAFMDww==", + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.14.7.tgz", + "integrity": "sha512-0mDE99nK+kVh3xlc5vKwB6wnP9ecuSj+zQCa/n0voENtP/zymdT4HH6QEb65wjjcbqr1Jb/7z9Qp7TF5FtwYGw==", "requires": { "@babel/helper-plugin-utils": "^7.14.5" } @@ -8841,9 +8840,9 @@ } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.14.5.tgz", - "integrity": "sha512-+Xe5+6MWFo311U8SchgeX5c1+lJM+eZDBZgD+tvXu9VVQPXwwVzeManMMjYX6xw2HczngfOSZjoFYKwdeB/Jvw==", + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.14.7.tgz", + "integrity": "sha512-DTNOTaS7TkW97xsDMrp7nycUVh6sn/eq22VaxWfEdzuEbRsiaOU0pqU7DlyUGHVsbQbSghvjKRpEl+nUCKGQSg==", "requires": { "@babel/helper-create-regexp-features-plugin": "^7.14.5" } @@ -8926,9 +8925,9 @@ } }, "@babel/plugin-transform-spread": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.14.5.tgz", - "integrity": "sha512-/3iqoQdiWergnShZYl0xACb4ADeYCJ7X/RgmwtXshn6cIvautRPAFzhd58frQlokLO6Jb4/3JXvmm6WNTPtiTw==", + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.14.6.tgz", + "integrity": "sha512-Zr0x0YroFJku7n7+/HH3A2eIrGMjbmAIbJSVv0IZ+t3U2WUQUA64S/oeied2e+MaGSjmt4alzBCsK9E8gh+fag==", "requires": { "@babel/helper-plugin-utils": "^7.14.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.14.5" @@ -8986,16 +8985,16 @@ } }, "@babel/preset-env": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.14.5.tgz", - "integrity": "sha512-ci6TsS0bjrdPpWGnQ+m4f+JSSzDKlckqKIJJt9UZ/+g7Zz9k0N8lYU8IeLg/01o2h8LyNZDMLGgRLDTxpudLsA==", + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.14.7.tgz", + "integrity": "sha512-itOGqCKLsSUl0Y+1nSfhbuuOlTs0MJk2Iv7iSH+XT/mR8U1zRLO7NjWlYXB47yhK4J/7j+HYty/EhFZDYKa/VA==", "requires": { - "@babel/compat-data": "^7.14.5", + "@babel/compat-data": "^7.14.7", "@babel/helper-compilation-targets": "^7.14.5", "@babel/helper-plugin-utils": "^7.14.5", "@babel/helper-validator-option": "^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-static-block": "^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-nullish-coalescing-operator": "^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-chaining": "^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-classes": "^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-duplicate-keys": "^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-systemjs": "^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-object-super": "^7.14.5", "@babel/plugin-transform-parameters": "^7.14.5", @@ -9050,7 +9049,7 @@ "@babel/plugin-transform-regenerator": "^7.14.5", "@babel/plugin-transform-reserved-words": "^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-template-literals": "^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-corejs3": "^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" }, "dependencies": { @@ -9431,9 +9430,9 @@ } }, "@lingui/core": { - "version": "3.10.3", - "resolved": "https://registry.npmjs.org/@lingui/core/-/core-3.10.3.tgz", - "integrity": "sha512-BiuWi5xPpQa27oIWWnkOYNx4qTMdMeu7vp5y1AGPYQ/4SO0rHfAtOxXtvRU/ktVwht/lIgx5Ygq5J3F+XLvOQA==", + "version": "3.10.4", + "resolved": "https://registry.npmjs.org/@lingui/core/-/core-3.10.4.tgz", + "integrity": "sha512-V9QKQ9PFMTPrGGz2PaeKHZcxFikQZzJbptyQbVFJdXaKhdE2RH6HhdK1PIziDHqp6ZWPthVIfVLURT3ku8eu5w==", "requires": { "@babel/runtime": "^7.11.2", "make-plural": "^6.2.2", @@ -9670,13 +9669,13 @@ } }, "@sentry/browser": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.7.1.tgz", - "integrity": "sha512-R5PYx4TTvifcU790XkK6JVGwavKaXwycDU0MaAwfc4Vf7BLm5KCNJCsDySu1RPAap/017MVYf54p6dWvKiRviA==", + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.7.2.tgz", + "integrity": "sha512-Lv0Ne1QcesyGAhVcQDfQa3hDPR/MhPSDTMg3xFi+LxqztchVc4w/ynzR0wCZFb8KIHpTj5SpJHfxpDhXYMtS9g==", "requires": { - "@sentry/core": "6.7.1", - "@sentry/types": "6.7.1", - "@sentry/utils": "6.7.1", + "@sentry/core": "6.7.2", + "@sentry/types": "6.7.2", + "@sentry/utils": "6.7.2", "tslib": "^1.9.3" }, "dependencies": { @@ -9688,14 +9687,14 @@ } }, "@sentry/core": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.7.1.tgz", - "integrity": "sha512-VAv8OR/7INn2JfiLcuop4hfDcyC7mfL9fdPndQEhlacjmw8gRrgXjR7qyhnCTgzFLkHI7V5bcdIzA83TRPYQpA==", + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.7.2.tgz", + "integrity": "sha512-NTZqwN5nR94yrXmSfekoPs1mIFuKvf8esdIW/DadwSKWAdLJwQTJY9xK/8PQv+SEzd7wiitPAx+mCw2By1xiNQ==", "requires": { - "@sentry/hub": "6.7.1", - "@sentry/minimal": "6.7.1", - "@sentry/types": "6.7.1", - "@sentry/utils": "6.7.1", + "@sentry/hub": "6.7.2", + "@sentry/minimal": "6.7.2", + "@sentry/types": "6.7.2", + "@sentry/utils": "6.7.2", "tslib": "^1.9.3" }, "dependencies": { @@ -9707,12 +9706,12 @@ } }, "@sentry/hub": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.7.1.tgz", - "integrity": "sha512-eVCTWvvcp6xa0A5GGNHMQEWslmKPlisE5rGmsV/kjvSUv3zSrI0eIDfb51ikdnCiBjHpK2NBWP8Vy8cZOEJegg==", + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.7.2.tgz", + "integrity": "sha512-05qVW6ymChJsXag4+fYCQokW3AcABIgcqrVYZUBf6GMU/Gbz5SJqpV7y1+njwWvnPZydMncP9LaDVpMKbE7UYQ==", "requires": { - "@sentry/types": "6.7.1", - "@sentry/utils": "6.7.1", + "@sentry/types": "6.7.2", + "@sentry/utils": "6.7.2", "tslib": "^1.9.3" }, "dependencies": { @@ -9724,12 +9723,12 @@ } }, "@sentry/minimal": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.7.1.tgz", - "integrity": "sha512-HDDPEnQRD6hC0qaHdqqKDStcdE1KhkFh0RCtJNMCDn0zpav8Dj9AteF70x6kLSlliAJ/JFwi6AmQrLz+FxPexw==", + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.7.2.tgz", + "integrity": "sha512-jkpwFv2GFHoVl5vnK+9/Q+Ea8eVdbJ3hn3/Dqq9MOLFnVK7ED6MhdHKLT79puGSFj+85OuhM5m2Q44mIhyS5mw==", "requires": { - "@sentry/hub": "6.7.1", - "@sentry/types": "6.7.1", + "@sentry/hub": "6.7.2", + "@sentry/types": "6.7.2", "tslib": "^1.9.3" }, "dependencies": { @@ -9741,14 +9740,14 @@ } }, "@sentry/tracing": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.7.1.tgz", - "integrity": "sha512-wyS3nWNl5mzaC1qZ2AIp1hjXnfO9EERjMIJjCihs2LWBz1r3efxrHxJHs8wXlNWvrT3KLhq/7vvF5CdU82uPeQ==", + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.7.2.tgz", + "integrity": "sha512-juKlI7FICKONWJFJxDxerj0A+8mNRhmtrdR+OXFqOkqSAy/QXlSFZcA/j//O19k2CfwK1BrvoMcQ/4gnffUOVg==", "requires": { - "@sentry/hub": "6.7.1", - "@sentry/minimal": "6.7.1", - "@sentry/types": "6.7.1", - "@sentry/utils": "6.7.1", + "@sentry/hub": "6.7.2", + "@sentry/minimal": "6.7.2", + "@sentry/types": "6.7.2", + "@sentry/utils": "6.7.2", "tslib": "^1.9.3" }, "dependencies": { @@ -9760,16 +9759,16 @@ } }, "@sentry/types": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.7.1.tgz", - "integrity": "sha512-9AO7HKoip2MBMNQJEd6+AKtjj2+q9Ze4ooWUdEvdOVSt5drg7BGpK221/p9JEOyJAZwEPEXdcMd3VAIMiOb4MA==" + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.7.2.tgz", + "integrity": "sha512-h21Go/PfstUN+ZV6SbwRSZVg9GXRJWdLfHoO5PSVb3TVEMckuxk8tAE57/u+UZDwX8wu+Xyon2TgsKpiWKxqUg==" }, "@sentry/utils": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.7.1.tgz", - "integrity": "sha512-Tq2otdbWlHAkctD+EWTYKkEx6BL1Qn3Z/imkO06/PvzpWvVhJWQ5qHAzz5XnwwqNHyV03KVzYB6znq1Bea9HuA==", + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.7.2.tgz", + "integrity": "sha512-9COL7aaBbe61Hp5BlArtXZ1o/cxli1NGONLPrVT4fMyeQFmLonhUiy77NdsW19XnvhvaA+2IoV5dg3dnFiF/og==", "requires": { - "@sentry/types": "6.7.1", + "@sentry/types": "6.7.2", "tslib": "^1.9.3" }, "dependencies": { @@ -9926,29 +9925,28 @@ "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==" }, "@typescript-eslint/eslint-plugin": { - "version": "4.27.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.27.0.tgz", - "integrity": "sha512-DsLqxeUfLVNp3AO7PC3JyaddmEHTtI9qTSAs+RB6ja27QvIM0TA8Cizn1qcS6vOu+WDLFJzkwkgweiyFhssDdQ==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.0.tgz", + "integrity": "sha512-KcF6p3zWhf1f8xO84tuBailV5cN92vhS+VT7UJsPzGBm9VnQqfI9AsiMUFUCYHTYPg1uCCo+HyiDnpDuvkAMfQ==", "requires": { - "@typescript-eslint/experimental-utils": "4.27.0", - "@typescript-eslint/scope-manager": "4.27.0", + "@typescript-eslint/experimental-utils": "4.28.0", + "@typescript-eslint/scope-manager": "4.28.0", "debug": "^4.3.1", "functional-red-black-tree": "^1.0.1", - "lodash": "^4.17.21", "regexpp": "^3.1.0", "semver": "^7.3.5", "tsutils": "^3.21.0" } }, "@typescript-eslint/experimental-utils": { - "version": "4.27.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.27.0.tgz", - "integrity": "sha512-n5NlbnmzT2MXlyT+Y0Jf0gsmAQzCnQSWXKy4RGSXVStjDvS5we9IWbh7qRVKdGcxT0WYlgcCYUK/HRg7xFhvjQ==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.0.tgz", + "integrity": "sha512-9XD9s7mt3QWMk82GoyUpc/Ji03vz4T5AYlHF9DcoFNfJ/y3UAclRsfGiE2gLfXtyC+JRA3trR7cR296TEb1oiQ==", "requires": { "@types/json-schema": "^7.0.7", - "@typescript-eslint/scope-manager": "4.27.0", - "@typescript-eslint/types": "4.27.0", - "@typescript-eslint/typescript-estree": "4.27.0", + "@typescript-eslint/scope-manager": "4.28.0", + "@typescript-eslint/types": "4.28.0", + "@typescript-eslint/typescript-estree": "4.28.0", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0" }, @@ -9964,37 +9962,37 @@ } }, "@typescript-eslint/parser": { - "version": "4.27.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.27.0.tgz", - "integrity": "sha512-XpbxL+M+gClmJcJ5kHnUpBGmlGdgNvy6cehgR6ufyxkEJMGP25tZKCaKyC0W/JVpuhU3VU1RBn7SYUPKSMqQvQ==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.0.tgz", + "integrity": "sha512-7x4D22oPY8fDaOCvkuXtYYTQ6mTMmkivwEzS+7iml9F9VkHGbbZ3x4fHRwxAb5KeuSkLqfnYjs46tGx2Nour4A==", "requires": { - "@typescript-eslint/scope-manager": "4.27.0", - "@typescript-eslint/types": "4.27.0", - "@typescript-eslint/typescript-estree": "4.27.0", + "@typescript-eslint/scope-manager": "4.28.0", + "@typescript-eslint/types": "4.28.0", + "@typescript-eslint/typescript-estree": "4.28.0", "debug": "^4.3.1" } }, "@typescript-eslint/scope-manager": { - "version": "4.27.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.27.0.tgz", - "integrity": "sha512-DY73jK6SEH6UDdzc6maF19AHQJBFVRf6fgAXHPXCGEmpqD4vYgPEzqpFz1lf/daSbOcMpPPj9tyXXDPW2XReAw==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.0.tgz", + "integrity": "sha512-eCALCeScs5P/EYjwo6se9bdjtrh8ByWjtHzOkC4Tia6QQWtQr3PHovxh3TdYTuFcurkYI4rmFsRFpucADIkseg==", "requires": { - "@typescript-eslint/types": "4.27.0", - "@typescript-eslint/visitor-keys": "4.27.0" + "@typescript-eslint/types": "4.28.0", + "@typescript-eslint/visitor-keys": "4.28.0" } }, "@typescript-eslint/types": { - "version": "4.27.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.27.0.tgz", - "integrity": "sha512-I4ps3SCPFCKclRcvnsVA/7sWzh7naaM/b4pBO2hVxnM3wrU51Lveybdw5WoIktU/V4KfXrTt94V9b065b/0+wA==" + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.0.tgz", + "integrity": "sha512-p16xMNKKoiJCVZY5PW/AfILw2xe1LfruTcfAKBj3a+wgNYP5I9ZEKNDOItoRt53p4EiPV6iRSICy8EPanG9ZVA==" }, "@typescript-eslint/typescript-estree": { - "version": "4.27.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.27.0.tgz", - "integrity": "sha512-KH03GUsUj41sRLLEy2JHstnezgpS5VNhrJouRdmh6yNdQ+yl8w5LrSwBkExM+jWwCJa7Ct2c8yl8NdtNRyQO6g==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.0.tgz", + "integrity": "sha512-m19UQTRtxMzKAm8QxfKpvh6OwQSXaW1CdZPoCaQuLwAq7VZMNuhJmZR4g5281s2ECt658sldnJfdpSZZaxUGMQ==", "requires": { - "@typescript-eslint/types": "4.27.0", - "@typescript-eslint/visitor-keys": "4.27.0", + "@typescript-eslint/types": "4.28.0", + "@typescript-eslint/visitor-keys": "4.28.0", "debug": "^4.3.1", "globby": "^11.0.3", "is-glob": "^4.0.1", @@ -10003,9 +10001,9 @@ }, "dependencies": { "globby": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz", - "integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==", + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", + "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", "requires": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -10018,11 +10016,11 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "4.27.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.27.0.tgz", - "integrity": "sha512-es0GRYNZp0ieckZ938cEANfEhsfHrzuLrePukLKtY3/KPXcq1Xd555Mno9/GOgXhKzn0QfkDLVgqWO3dGY80bg==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.0.tgz", + "integrity": "sha512-PjJyTWwrlrvM5jazxYF5ZPs/nl0kHDZMVbuIcbpawVXaDPelp3+S9zpOz5RmVUfS/fD5l5+ZXNKnWhNYjPzCvw==", "requires": { - "@typescript-eslint/types": "4.27.0", + "@typescript-eslint/types": "4.28.0", "eslint-visitor-keys": "^2.0.0" } }, @@ -10591,9 +10589,9 @@ "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" }, "codemirror": { - "version": "5.61.1", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.61.1.tgz", - "integrity": "sha512-+D1NZjAucuzE93vJGbAaXzvoBHwp9nJZWWWF9utjv25+5AZUiah6CIlfb4ikG4MoDsFsCG8niiJH5++OO2LgIQ==" + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.62.0.tgz", + "integrity": "sha512-Xnl3304iCc8nyVZuRkzDVVwc794uc9QNX0UcPGeNic1fbzkSrO4l4GVXho9tRNKBgPYZXgocUqXyfIv3BILhCQ==" }, "collection-visit": { "version": "1.0.0", @@ -10681,9 +10679,9 @@ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" }, "core-js-compat": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.14.0.tgz", - "integrity": "sha512-R4NS2eupxtiJU+VwgkF9WTpnSfZW4pogwKHd8bclWU2sp93Pr5S1uYJI84cMOubJRou7bcfL0vmwtLslWN5p3A==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.15.0.tgz", + "integrity": "sha512-8X6lWsG+s7IfOKzV93a7fRYfWRZobOfjw5V5rrq43Vh/W+V6qYxl7Akalsvgab4PFT/4L/pjQbdBUEM36NXKrw==", "requires": { "browserslist": "^4.16.6", "semver": "7.0.0" @@ -10901,9 +10899,9 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "eslint": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.28.0.tgz", - "integrity": "sha512-UMfH0VSjP0G4p3EWirscJEQ/cHqnT/iuH6oNZOB94nBjWbMnhGEPxsZm1eyIW0C/9jLI0Fow4W5DXLjEI7mn1g==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.29.0.tgz", + "integrity": "sha512-82G/JToB9qIy/ArBzIWG9xvvwL3R86AlCjtGw+A29OMZDqhTybz/MByORSukGxeI+YPCR4coYyITKk8BFH9nDA==", "requires": { "@babel/code-frame": "7.12.11", "@eslint/eslintrc": "^0.4.2", @@ -13202,11 +13200,11 @@ } }, "rollup": { - "version": "2.51.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.51.2.tgz", - "integrity": "sha512-ReV2eGEadA7hmXSzjxdDKs10neqH2QURf2RxJ6ayAlq93ugy6qIvXMmbc5cWMGCDh1h5T4thuWO1e2VNbMq8FA==", + "version": "2.52.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.52.2.tgz", + "integrity": "sha512-4RlFC3k2BIHlUsJ9mGd8OO+9Lm2eDF5P7+6DNQOp5sx+7N/1tFM01kELfbxlMX3MxT6owvLB1ln4S3QvvQlbUA==", "requires": { - "fsevents": "~2.3.1" + "fsevents": "~2.3.2" } }, "rollup-plugin-commonjs": { @@ -13898,9 +13896,9 @@ } }, "typescript": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz", - "integrity": "sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==" + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.4.tgz", + "integrity": "sha512-uauPG7XZn9F/mo+7MrsRjyvbxFpzemRjKEZXS4AK83oP2KKOJPvb+9cO/gmnv8arWZvhnjVOXz7B49m1l0e9Ew==" }, "uglify-js": { "version": "3.13.0", diff --git a/web/package.json b/web/package.json index 29ab20e3a..3df620040 100644 --- a/web/package.json +++ b/web/package.json @@ -41,11 +41,11 @@ "@babel/core": "^7.14.6", "@babel/plugin-proposal-decorators": "^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", "@fortawesome/fontawesome-free": "^5.15.3", "@lingui/cli": "^3.10.2", - "@lingui/core": "^3.10.3", + "@lingui/core": "^3.10.4", "@lingui/macro": "^3.10.2", "@patternfly/patternfly": "^4.108.2", "@polymer/iron-form": "^3.0.1", @@ -53,22 +53,22 @@ "@rollup/plugin-babel": "^5.3.0", "@rollup/plugin-replace": "^2.4.2", "@rollup/plugin-typescript": "^8.2.1", - "@sentry/browser": "^6.7.1", - "@sentry/tracing": "^6.7.1", + "@sentry/browser": "^6.7.2", + "@sentry/tracing": "^6.7.2", "@types/chart.js": "^2.9.32", "@types/codemirror": "5.60.0", "@types/grecaptcha": "^3.0.2", - "@typescript-eslint/eslint-plugin": "^4.27.0", - "@typescript-eslint/parser": "^4.27.0", + "@typescript-eslint/eslint-plugin": "^4.28.0", + "@typescript-eslint/parser": "^4.28.0", "@webcomponents/webcomponentsjs": "^2.5.0", "authentik-api": "file:api", "babel-plugin-macros": "^3.1.0", "base64-js": "^1.5.1", "chart.js": "^3.3.2", "chartjs-adapter-moment": "^1.0.0", - "codemirror": "^5.61.1", + "codemirror": "^5.62.0", "construct-style-sheets-polyfill": "^2.4.16", - "eslint": "^7.28.0", + "eslint": "^7.29.0", "eslint-config-google": "^0.14.0", "eslint-plugin-custom-elements": "0.0.2", "eslint-plugin-lit": "^1.5.1", @@ -77,7 +77,7 @@ "lit-html": "^1.4.1", "moment": "^2.29.1", "rapidoc": "^9.0.0", - "rollup": "^2.51.2", + "rollup": "^2.52.2", "rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-copy": "^3.4.0", "rollup-plugin-cssimport": "^1.0.2", @@ -87,7 +87,7 @@ "rollup-plugin-terser": "^7.0.2", "ts-lit-plugin": "^1.2.1", "tslib": "^2.3.0", - "typescript": "^4.3.2", + "typescript": "^4.3.4", "webcomponent-qr-code": "^1.0.5", "yaml": "^1.10.2" }, diff --git a/web/src/authentik.css b/web/src/authentik.css index e4eb584a4..bb409f2f1 100644 --- a/web/src/authentik.css +++ b/web/src/authentik.css @@ -68,7 +68,7 @@ html > form > input { /* ensure background on non-flow pages match */ .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; } diff --git a/web/src/constants.ts b/web/src/constants.ts index cb7a06fd5..03052d0b6 100644 --- a/web/src/constants.ts +++ b/web/src/constants.ts @@ -3,7 +3,7 @@ export const SUCCESS_CLASS = "pf-m-success"; export const ERROR_CLASS = "pf-m-danger"; export const PROGRESS_CLASS = "pf-m-in-progress"; export const CURRENT_CLASS = "pf-m-current"; -export const VERSION = "2021.6.1-rc6"; +export const VERSION = "2021.6.2"; export const PAGE_SIZE = 20; export const EVENT_REFRESH = "ak-refresh"; export const EVENT_NOTIFICATION_TOGGLE = "ak-notification-toggle"; diff --git a/web/src/elements/buttons/ModalButton.ts b/web/src/elements/buttons/ModalButton.ts index cc8a8a18d..bd757bf94 100644 --- a/web/src/elements/buttons/ModalButton.ts +++ b/web/src/elements/buttons/ModalButton.ts @@ -54,7 +54,9 @@ export class ModalButton extends LitElement { resetForms(): void { this.querySelectorAll("[slot=form]").forEach(form => { - form?.resetForm(); + if ("resetForm" in form) { + form?.resetForm(); + } }); } diff --git a/web/src/elements/table/TableModal.ts b/web/src/elements/table/TableModal.ts index 1dae0bda9..6a7bd5bc3 100644 --- a/web/src/elements/table/TableModal.ts +++ b/web/src/elements/table/TableModal.ts @@ -34,7 +34,9 @@ export abstract class TableModal extends Table { resetForms(): void { this.querySelectorAll("[slot=form]").forEach(form => { - form?.resetForm(); + if ("resetForm" in form) { + form?.resetForm(); + } }); } diff --git a/web/src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts b/web/src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts index 60b8b55cb..86e841221 100644 --- a/web/src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts +++ b/web/src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts @@ -67,6 +67,27 @@ export class AuthenticatorValidateStageForm extends ModelForm
+ + +

${t`Device classes which can be used to authenticate.`}

+

${t`Hold control/command to select multiple items.`}

+
- - -

${t`Device classes which can be used to authenticate.`}

-

${t`Hold control/command to select multiple items.`}

-
${this.showConfigurationStage ? html` {

${t`Matching is done based on domain suffix, so if you enter domain.tld, foo.domain.tld will still match.`}

- +