From 6c23fc4b2b6adad9f29ba1c3843db489b3a9e017 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Sat, 4 Dec 2021 20:03:26 +0100 Subject: [PATCH 01/43] webiste/docs: add 2021.12.1-rc2 release notes Signed-off-by: Jens Langhammer --- website/docs/releases/v2021.12.md | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/website/docs/releases/v2021.12.md b/website/docs/releases/v2021.12.md index 3635f13b6..a2556c349 100644 --- a/website/docs/releases/v2021.12.md +++ b/website/docs/releases/v2021.12.md @@ -39,16 +39,11 @@ This release does not have any headline features, and mostly fixes bugs. - stages/email: fix missing component in response when retrying email send - stages/email: minify email css template - stages/email: prevent error with duplicate token -- web: add rollup config for proxy outpost -- web: attempt to drop fetch errors - web: improve dark theme for vertical tabs - web: only show applications with http link -- web: use ak-label for boolean values - web/admin: allow flow edit on flow view page - web/admin: fix actions column on ip reputation page -- web/admin: fix display of banners on provider pages - web/admin: fix Forms with file uploads not handling errors correctly -- web/admin: fix typo in events action - web/admin: make object view pages more consistent - web/admin: make user clickable for bound policies list - web/admin: redesign provider pages to provide more info @@ -62,6 +57,30 @@ This release does not have any headline features, and mostly fixes bugs. - web/user: fix filtering for applications based on launchURL - web/user: fix height issues on user interface +## Fixed in 2021.12.1-rc2 + +- *: don't use go embed to make using custom files easier +- crypto: add certificate discovery to automatically import certificates from lets encrypt +- crypto: fix default API not having an ordering +- outposts: always trigger outpost reconcile on startup +- outposts/ldap: Rework/improve LDAP search logic. (#1687) +- outposts/proxy: make logging fields more consistent +- outposts/proxy: re-add rs256 support +- providers/proxy: fix defaults for traefik integration +- providers/proxy: use wildcard for traefik headers copy +- providers/saml: fix error when using post bindings and user freshly logged in +- providers/saml: fix IndexError in signature check +- sources/ldap: add optional tls verification certificate +- sources/ldap: allow multiple server URIs for loadbalancing and failover +- sources/ldap: don't cache LDAP Connection, use random server +- sources/ldap: handle typeerror during creation of objects when using wrong keyword params +- sources/plex: fix plex token being included in event log +- stages/prompt: fix error when both default and required are set +- web/admin: add spinner to table refresh button to show progress +- web/admin: don't show disabled http basic as red +- web/admin: fix wrong description for reputation policy +- web/flows: fix linting errors +- web/flows: Revise duo authenticator login prompt text (#1872) ## Upgrading From 680ef641fb142adaec0d4cb1dce75afc40aa3923 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Sun, 5 Dec 2021 10:31:16 +0100 Subject: [PATCH 02/43] providers/saml: fix error when propertymapping returns invalid data in list Signed-off-by: Jens Langhammer --- authentik/providers/saml/processors/assertion.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/authentik/providers/saml/processors/assertion.py b/authentik/providers/saml/processors/assertion.py index 5040be054..01e0b9b5f 100644 --- a/authentik/providers/saml/processors/assertion.py +++ b/authentik/providers/saml/processors/assertion.py @@ -10,6 +10,7 @@ from structlog.stdlib import get_logger from authentik.core.exceptions import PropertyMappingExpressionException from authentik.events.models import Event, EventAction +from authentik.lib.utils.errors import exception_to_string from authentik.lib.utils.time import timedelta_from_string from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider from authentik.providers.saml.processors.request_parser import AuthNRequest @@ -101,10 +102,11 @@ class AssertionProcessor: attribute_statement.append(attribute) - except PropertyMappingExpressionException as exc: + except (PropertyMappingExpressionException, ValueError) as exc: + # Value error can be raised when assigning invalid data to an attribute Event.new( EventAction.CONFIGURATION_ERROR, - message=f"Failed to evaluate property-mapping: {str(exc)}", + message=f"Failed to evaluate property-mapping: {exception_to_string(exc)}", mapping=mapping, ).from_http(self.http_request) continue From 2b78c4ba86499a4d31359934cfda6c1e75422cb8 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Sun, 5 Dec 2021 10:51:56 +0100 Subject: [PATCH 03/43] *: use request.query_params instead of accessing the django request Signed-off-by: Jens Langhammer --- authentik/crypto/api.py | 4 ++-- authentik/providers/saml/api.py | 2 +- authentik/providers/saml/processors/assertion.py | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/authentik/crypto/api.py b/authentik/crypto/api.py index fc62285fa..08bbf86f8 100644 --- a/authentik/crypto/api.py +++ b/authentik/crypto/api.py @@ -192,7 +192,7 @@ class CertificateKeyPairViewSet(UsedByMixin, ModelViewSet): secret=certificate, type="certificate", ).from_http(request) - if "download" in request._request.GET: + if "download" in request.query_params: # Mime type from https://pki-tutorial.readthedocs.io/en/latest/mime.html response = HttpResponse( certificate.certificate_data, content_type="application/x-pem-file" @@ -223,7 +223,7 @@ class CertificateKeyPairViewSet(UsedByMixin, ModelViewSet): secret=certificate, type="private_key", ).from_http(request) - if "download" in request._request.GET: + if "download" in request.query_params: # Mime type from https://pki-tutorial.readthedocs.io/en/latest/mime.html response = HttpResponse(certificate.key_data, content_type="application/x-pem-file") response[ diff --git a/authentik/providers/saml/api.py b/authentik/providers/saml/api.py index 8c3bc7257..deda0400c 100644 --- a/authentik/providers/saml/api.py +++ b/authentik/providers/saml/api.py @@ -123,7 +123,7 @@ class SAMLProviderViewSet(UsedByMixin, ModelViewSet): raise Http404 try: metadata = MetadataProcessor(provider, request).build_entity_descriptor() - if "download" in request._request.GET: + if "download" in request.query_params: response = HttpResponse(metadata, content_type="application/xml") response[ "Content-Disposition" diff --git a/authentik/providers/saml/processors/assertion.py b/authentik/providers/saml/processors/assertion.py index 01e0b9b5f..fff3b7b67 100644 --- a/authentik/providers/saml/processors/assertion.py +++ b/authentik/providers/saml/processors/assertion.py @@ -10,7 +10,6 @@ from structlog.stdlib import get_logger from authentik.core.exceptions import PropertyMappingExpressionException from authentik.events.models import Event, EventAction -from authentik.lib.utils.errors import exception_to_string from authentik.lib.utils.time import timedelta_from_string from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider from authentik.providers.saml.processors.request_parser import AuthNRequest @@ -106,7 +105,7 @@ class AssertionProcessor: # Value error can be raised when assigning invalid data to an attribute Event.new( EventAction.CONFIGURATION_ERROR, - message=f"Failed to evaluate property-mapping: {exception_to_string(exc)}", + message=f"Failed to evaluate property-mapping: {str(exc)}", mapping=mapping, ).from_http(self.http_request) continue From 6a3f7e45cfb4b5a2290cde1ad88ae61f91f4f17f Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Sun, 5 Dec 2021 11:14:42 +0100 Subject: [PATCH 04/43] providers/saml: add ?force_binding to limit bindings for metadata endpoint Signed-off-by: Jens Langhammer --- authentik/providers/saml/api.py | 19 +++++++++++++++++-- .../providers/saml/processors/metadata.py | 4 ++++ schema.yml | 8 ++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/authentik/providers/saml/api.py b/authentik/providers/saml/api.py index deda0400c..33a524e0f 100644 --- a/authentik/providers/saml/api.py +++ b/authentik/providers/saml/api.py @@ -36,6 +36,7 @@ from authentik.flows.models import Flow, FlowDesignation from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider from authentik.providers.saml.processors.metadata import MetadataProcessor from authentik.providers.saml.processors.metadata_parser import ServiceProviderMetadataParser +from authentik.sources.saml.processors.constants import SAML_BINDING_POST, SAML_BINDING_REDIRECT LOGGER = get_logger() @@ -109,7 +110,19 @@ class SAMLProviderViewSet(UsedByMixin, ModelViewSet): name="download", location=OpenApiParameter.QUERY, type=OpenApiTypes.BOOL, - ) + ), + OpenApiParameter( + name="force_binding", + location=OpenApiParameter.QUERY, + type=OpenApiTypes.STR, + enum=[ + SAML_BINDING_REDIRECT, + SAML_BINDING_POST, + ], + description=( + "Optionally force the metadata to only include one binding." + ) + ), ], ) @action(methods=["GET"], detail=True, permission_classes=[AllowAny]) @@ -122,7 +135,9 @@ class SAMLProviderViewSet(UsedByMixin, ModelViewSet): except ValueError: raise Http404 try: - metadata = MetadataProcessor(provider, request).build_entity_descriptor() + proc = MetadataProcessor(provider, request) + proc.force_binding = request.query_params.get("force_binding", None) + metadata = proc.build_entity_descriptor() if "download" in request.query_params: response = HttpResponse(metadata, content_type="application/xml") response[ diff --git a/authentik/providers/saml/processors/metadata.py b/authentik/providers/saml/processors/metadata.py index 8a97e5f22..653299a57 100644 --- a/authentik/providers/saml/processors/metadata.py +++ b/authentik/providers/saml/processors/metadata.py @@ -29,10 +29,12 @@ class MetadataProcessor: provider: SAMLProvider http_request: HttpRequest + force_binding: Optional[str] def __init__(self, provider: SAMLProvider, request: HttpRequest): self.provider = provider self.http_request = request + self.force_binding = None self.xml_id = get_random_id() def get_signing_key_descriptor(self) -> Optional[Element]: @@ -79,6 +81,8 @@ class MetadataProcessor: ), } for binding, url in binding_url_map.items(): + if self.force_binding and self.force_binding != binding: + continue element = Element(f"{{{NS_SAML_METADATA}}}SingleSignOnService") element.attrib["Binding"] = binding element.attrib["Location"] = url diff --git a/schema.yml b/schema.yml index 28dae5da6..d241ac93d 100644 --- a/schema.yml +++ b/schema.yml @@ -11645,6 +11645,14 @@ paths: name: download schema: type: boolean + - in: query + name: force_binding + schema: + type: string + enum: + - urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST + - urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect + description: Optionally force the metadata to only include one binding. - in: path name: id schema: From 61f6b0f122edfd9e9822e4635d6ab64e0534871d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 5 Dec 2021 11:17:31 +0100 Subject: [PATCH 05/43] web: Update Web API Client version (#1880) Signed-off-by: GitHub Co-authored-by: BeryJu --- web/package-lock.json | 14 +++++++------- web/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/web/package-lock.json b/web/package-lock.json index 30283ea8b..397cc13b3 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -15,7 +15,7 @@ "@babel/preset-env": "^7.16.4", "@babel/preset-typescript": "^7.16.0", "@fortawesome/fontawesome-free": "^5.15.4", - "@goauthentik/api": "^2021.10.4-1638522576", + "@goauthentik/api": "^2021.10.4-1638699310", "@jackfranklin/rollup-plugin-markdown": "^0.3.0", "@lingui/cli": "^3.13.0", "@lingui/core": "^3.13.0", @@ -1708,9 +1708,9 @@ } }, "node_modules/@goauthentik/api": { - "version": "2021.10.4-1638522576", - "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2021.10.4-1638522576.tgz", - "integrity": "sha512-ojnhGFPnEHXPeMULMtRUBoRVB8k0B73l3O5UL8NSipaY2ZC7jSscIQKDZWz7yvvx9NPMV34kKJ9NK8N+/jzfgw==" + "version": "2021.10.4-1638699310", + "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2021.10.4-1638699310.tgz", + "integrity": "sha512-ZDaIidbgbgb1FPjIlgTD0nXwhlVALmadWPJjlxpZiSnlRWBa1IsbInzeG+EPr5zPi8GECAK5bS7JcsUsIJDBlw==" }, "node_modules/@humanwhocodes/config-array": { "version": "0.6.0", @@ -9895,9 +9895,9 @@ "integrity": "sha512-eYm8vijH/hpzr/6/1CJ/V/Eb1xQFW2nnUKArb3z+yUWv7HTwj6M7SP957oMjfZjAHU6qpoNc2wQvIxBLWYa/Jg==" }, "@goauthentik/api": { - "version": "2021.10.4-1638522576", - "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2021.10.4-1638522576.tgz", - "integrity": "sha512-ojnhGFPnEHXPeMULMtRUBoRVB8k0B73l3O5UL8NSipaY2ZC7jSscIQKDZWz7yvvx9NPMV34kKJ9NK8N+/jzfgw==" + "version": "2021.10.4-1638699310", + "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2021.10.4-1638699310.tgz", + "integrity": "sha512-ZDaIidbgbgb1FPjIlgTD0nXwhlVALmadWPJjlxpZiSnlRWBa1IsbInzeG+EPr5zPi8GECAK5bS7JcsUsIJDBlw==" }, "@humanwhocodes/config-array": { "version": "0.6.0", diff --git a/web/package.json b/web/package.json index 1198f78d5..8383499e8 100644 --- a/web/package.json +++ b/web/package.json @@ -51,7 +51,7 @@ "@babel/preset-env": "^7.16.4", "@babel/preset-typescript": "^7.16.0", "@fortawesome/fontawesome-free": "^5.15.4", - "@goauthentik/api": "^2021.10.4-1638522576", + "@goauthentik/api": "^2021.10.4-1638699310", "@jackfranklin/rollup-plugin-markdown": "^0.3.0", "@lingui/cli": "^3.13.0", "@lingui/core": "^3.13.0", From ada2a16412f8ddc1e230b764e99fbb5b21f602d3 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Sun, 5 Dec 2021 11:17:57 +0100 Subject: [PATCH 06/43] tests/e2e: add post binding test Signed-off-by: Jens Langhammer --- authentik/providers/saml/api.py | 4 +- tests/e2e/test_provider_saml.py | 97 ++++++++++++++++++++++++++++++--- 2 files changed, 90 insertions(+), 11 deletions(-) diff --git a/authentik/providers/saml/api.py b/authentik/providers/saml/api.py index 33a524e0f..71f714bc9 100644 --- a/authentik/providers/saml/api.py +++ b/authentik/providers/saml/api.py @@ -119,9 +119,7 @@ class SAMLProviderViewSet(UsedByMixin, ModelViewSet): SAML_BINDING_REDIRECT, SAML_BINDING_POST, ], - description=( - "Optionally force the metadata to only include one binding." - ) + description=("Optionally force the metadata to only include one binding."), ), ], ) diff --git a/tests/e2e/test_provider_saml.py b/tests/e2e/test_provider_saml.py index 336614d8d..147e742d0 100644 --- a/tests/e2e/test_provider_saml.py +++ b/tests/e2e/test_provider_saml.py @@ -16,6 +16,7 @@ from authentik.flows.models import Flow from authentik.policies.expression.models import ExpressionPolicy from authentik.policies.models import PolicyBinding from authentik.providers.saml.models import SAMLBindings, SAMLPropertyMapping, SAMLProvider +from authentik.sources.saml.processors.constants import SAML_BINDING_POST from tests.e2e.utils import SeleniumTestCase, apply_migration, object_manager, retry @@ -25,9 +26,18 @@ class TestProviderSAML(SeleniumTestCase): container: Container - def setup_client(self, provider: SAMLProvider) -> Container: + def setup_client(self, provider: SAMLProvider, force_post: bool = False) -> Container: """Setup client saml-sp container which we test SAML against""" client: DockerClient = from_env() + metadata_url = ( + self.url( + "authentik_api:samlprovider-metadata", + pk=provider.pk, + ) + + "?download" + ) + if force_post: + metadata_url += f"&force_binding={SAML_BINDING_POST}" container = client.containers.run( image="beryju.org/saml-test-sp:latest", detach=True, @@ -41,13 +51,7 @@ class TestProviderSAML(SeleniumTestCase): environment={ "SP_ENTITY_ID": provider.issuer, "SP_SSO_BINDING": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", - "SP_METADATA_URL": ( - self.url( - "authentik_api:samlprovider-metadata", - pk=provider.pk, - ) - + "?download" - ), + "SP_METADATA_URL": metadata_url, }, ) while True: @@ -197,6 +201,83 @@ class TestProviderSAML(SeleniumTestCase): [self.user.email], ) + @retry() + @apply_migration("authentik_flows", "0008_default_flows") + @apply_migration("authentik_flows", "0011_flow_title") + @apply_migration("authentik_flows", "0010_provider_flows") + @apply_migration("authentik_crypto", "0002_create_self_signed_kp") + @object_manager + def test_sp_initiated_explicit_post(self): + """test SAML Provider flow SP-initiated flow (explicit consent) (POST binding)""" + # Bootstrap all needed objects + authorization_flow = Flow.objects.get( + slug="default-provider-authorization-explicit-consent" + ) + provider: SAMLProvider = SAMLProvider.objects.create( + name="saml-test", + acs_url="http://localhost:9009/saml/acs", + audience="authentik-e2e", + issuer="authentik-e2e", + sp_binding=SAMLBindings.POST, + authorization_flow=authorization_flow, + signing_kp=create_test_cert(), + ) + provider.property_mappings.set(SAMLPropertyMapping.objects.all()) + provider.save() + app = Application.objects.create( + name="SAML", + slug="authentik-saml", + provider=provider, + ) + self.container = self.setup_client(provider, True) + self.driver.get("http://localhost:9009") + self.login() + + self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "ak-flow-executor"))) + + flow_executor = self.get_shadow_root("ak-flow-executor") + consent_stage = self.get_shadow_root("ak-stage-consent", flow_executor) + + self.assertIn( + app.name, + consent_stage.find_element(By.CSS_SELECTOR, "#header-text").text, + ) + consent_stage.find_element( + By.CSS_SELECTOR, + ("[type=submit]"), + ).click() + + self.wait_for_url("http://localhost:9009/") + + body = loads(self.driver.find_element(By.CSS_SELECTOR, "pre").text) + + self.assertEqual( + body["attr"]["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"], + [self.user.name], + ) + self.assertEqual( + body["attr"][ + "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname" + ], + [self.user.username], + ) + self.assertEqual( + body["attr"]["http://schemas.goauthentik.io/2021/02/saml/username"], + [self.user.username], + ) + self.assertEqual( + body["attr"]["http://schemas.goauthentik.io/2021/02/saml/uid"], + [str(self.user.pk)], + ) + self.assertEqual( + body["attr"]["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"], + [self.user.email], + ) + self.assertEqual( + body["attr"]["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"], + [self.user.email], + ) + @retry() @apply_migration("authentik_flows", "0008_default_flows") @apply_migration("authentik_flows", "0011_flow_title") From 317e9ec6053742e17ba74fb6aa38dc15aaf6657f Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Sun, 5 Dec 2021 15:09:06 +0100 Subject: [PATCH 07/43] core: add FlowToken which saves the pickled flow plan, replace standard token in email stage to allow finishing flows in different sessions closes #1801 Signed-off-by: Jens Langhammer --- authentik/core/models.py | 7 ++- authentik/flows/migrations/0020_flowtoken.py | 46 ++++++++++++++++++++ authentik/flows/models.py | 31 +++++++++++++ authentik/flows/planner.py | 3 ++ authentik/flows/views/executor.py | 36 ++++++++++++++- authentik/stages/email/stage.py | 29 ++++++------ 6 files changed, 131 insertions(+), 21 deletions(-) create mode 100644 authentik/flows/migrations/0020_flowtoken.py diff --git a/authentik/core/models.py b/authentik/core/models.py index 63718c2d4..30969f3b0 100644 --- a/authentik/core/models.py +++ b/authentik/core/models.py @@ -25,7 +25,6 @@ from structlog.stdlib import get_logger from authentik.core.exceptions import PropertyMappingExpressionException from authentik.core.signals import password_changed from authentik.core.types import UILoginButton, UserSettingSerializer -from authentik.flows.models import Flow from authentik.lib.config import CONFIG from authentik.lib.generators import generate_id from authentik.lib.models import CreatedUpdatedModel, DomainlessURLValidator, SerializerModel @@ -203,7 +202,7 @@ class Provider(SerializerModel): name = models.TextField() authorization_flow = models.ForeignKey( - Flow, + "authentik_flows.Flow", on_delete=models.CASCADE, help_text=_("Flow used when authorizing this provider."), related_name="provider_authorization", @@ -324,7 +323,7 @@ class Source(ManagedModel, SerializerModel, PolicyBindingModel): property_mappings = models.ManyToManyField("PropertyMapping", default=None, blank=True) authentication_flow = models.ForeignKey( - Flow, + "authentik_flows.Flow", blank=True, null=True, default=None, @@ -333,7 +332,7 @@ class Source(ManagedModel, SerializerModel, PolicyBindingModel): related_name="source_authentication", ) enrollment_flow = models.ForeignKey( - Flow, + "authentik_flows.Flow", blank=True, null=True, default=None, diff --git a/authentik/flows/migrations/0020_flowtoken.py b/authentik/flows/migrations/0020_flowtoken.py new file mode 100644 index 000000000..2de781ca0 --- /dev/null +++ b/authentik/flows/migrations/0020_flowtoken.py @@ -0,0 +1,46 @@ +# Generated by Django 3.2.9 on 2021-12-05 13:50 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("authentik_core", "0018_auto_20210330_1345_squashed_0028_alter_token_intent"), + ( + "authentik_flows", + "0019_alter_flow_background_squashed_0024_alter_flow_compatibility_mode", + ), + ] + + operations = [ + migrations.CreateModel( + name="FlowToken", + fields=[ + ( + "token_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="authentik_core.token", + ), + ), + ("_plan", models.TextField()), + ( + "flow", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="authentik_flows.flow" + ), + ), + ], + options={ + "verbose_name": "Flow Token", + "verbose_name_plural": "Flow Tokens", + }, + bases=("authentik_core.token",), + ), + ] diff --git a/authentik/flows/models.py b/authentik/flows/models.py index aab6c795f..4dfce6bd6 100644 --- a/authentik/flows/models.py +++ b/authentik/flows/models.py @@ -1,4 +1,6 @@ """Flow models""" +from base64 import b64decode, b64encode +from pickle import dumps, loads # nosec from typing import TYPE_CHECKING, Optional, Type from uuid import uuid4 @@ -9,11 +11,13 @@ from model_utils.managers import InheritanceManager from rest_framework.serializers import BaseSerializer from structlog.stdlib import get_logger +from authentik.core.models import Token from authentik.core.types import UserSettingSerializer from authentik.lib.models import InheritanceForeignKey, SerializerModel from authentik.policies.models import PolicyBindingModel if TYPE_CHECKING: + from authentik.flows.planner import FlowPlan from authentik.flows.stage import StageView LOGGER = get_logger() @@ -260,3 +264,30 @@ class ConfigurableStage(models.Model): class Meta: abstract = True + + +class FlowToken(Token): + """Subclass of a standard Token, stores the currently active flow plan upon creation. + Can be used to later resume a flow.""" + + flow = models.ForeignKey(Flow, on_delete=models.CASCADE) + _plan = models.TextField() + + @staticmethod + def pickle(plan) -> str: + """Pickle into string""" + data = dumps(plan) + return b64encode(data).decode() + + @property + def plan(self) -> "FlowPlan": + """Load Flow plan from pickled version""" + return loads(b64decode(self._plan.encode())) # nosec + + def __str__(self) -> str: + return f"Flow Token {super.__str__()}" + + class Meta: + + verbose_name = _("Flow Token") + verbose_name_plural = _("Flow Tokens") diff --git a/authentik/flows/planner.py b/authentik/flows/planner.py index 475d40de2..df8342e54 100644 --- a/authentik/flows/planner.py +++ b/authentik/flows/planner.py @@ -24,6 +24,9 @@ PLAN_CONTEXT_SSO = "is_sso" PLAN_CONTEXT_REDIRECT = "redirect" PLAN_CONTEXT_APPLICATION = "application" PLAN_CONTEXT_SOURCE = "source" +# Is set by the Flow Planner when a FlowToken was used, and the currently active flow plan +# was restored. +PLAN_CONTEXT_IS_RESTORED = "is_restored" GAUGE_FLOWS_CACHED = UpdatingGauge( "authentik_flows_cached", "Cached flows", diff --git a/authentik/flows/views/executor.py b/authentik/flows/views/executor.py index 82707e9fd..f0daf70e7 100644 --- a/authentik/flows/views/executor.py +++ b/authentik/flows/views/executor.py @@ -34,8 +34,16 @@ from authentik.flows.challenge import ( WithUserInfoChallenge, ) from authentik.flows.exceptions import EmptyFlowException, FlowNonApplicableException -from authentik.flows.models import ConfigurableStage, Flow, FlowDesignation, FlowStageBinding, Stage +from authentik.flows.models import ( + ConfigurableStage, + Flow, + FlowDesignation, + FlowStageBinding, + FlowToken, + Stage, +) from authentik.flows.planner import ( + PLAN_CONTEXT_IS_RESTORED, PLAN_CONTEXT_PENDING_USER, PLAN_CONTEXT_REDIRECT, FlowPlan, @@ -55,6 +63,7 @@ SESSION_KEY_APPLICATION_PRE = "authentik_flows_application_pre" SESSION_KEY_GET = "authentik_flows_get" SESSION_KEY_POST = "authentik_flows_post" SESSION_KEY_HISTORY = "authentik_flows_history" +QS_KEY_TOKEN = "flow_token" # nosec def challenge_types(): @@ -127,8 +136,31 @@ class FlowExecutorView(APIView): message = exc.__doc__ if exc.__doc__ else str(exc) return self.stage_invalid(error_message=message) + def _check_flow_token(self, get_params: QueryDict): + """Check if the user is using a flow token to restore a plan""" + tokens = FlowToken.filter_not_expired(key=get_params[QS_KEY_TOKEN]) + if not tokens.exists(): + return False + token: FlowToken = tokens.first() + try: + plan = token.plan + except (AttributeError, EOFError, ImportError, IndexError) as exc: + LOGGER.warning("f(exec): Failed to restore token plan", exc=exc) + finally: + token.delete() + if not isinstance(plan, FlowPlan): + return None + plan.context[PLAN_CONTEXT_IS_RESTORED] = True + self._logger.debug("f(exec): restored flow plan from token", plan=plan) + return plan + # pylint: disable=unused-argument, too-many-return-statements def dispatch(self, request: HttpRequest, flow_slug: str) -> HttpResponse: + get_params = QueryDict(request.GET.get("query", "")) + if QS_KEY_TOKEN in get_params: + plan = self._check_flow_token(get_params) + if plan: + self.request.session[SESSION_KEY_PLAN] = plan # Early check if there's an active Plan for the current session if SESSION_KEY_PLAN in self.request.session: self.plan = self.request.session[SESSION_KEY_PLAN] @@ -156,7 +188,7 @@ class FlowExecutorView(APIView): # we don't show an error message here, but rather call _flow_done() return self._flow_done() # Initial flow request, check if we have an upstream query string passed in - request.session[SESSION_KEY_GET] = QueryDict(request.GET.get("query", "")) + request.session[SESSION_KEY_GET] = get_params # We don't save the Plan after getting the next stage # as it hasn't been successfully passed yet try: diff --git a/authentik/stages/email/stage.py b/authentik/stages/email/stage.py index e94c5d7d5..cfecd672d 100644 --- a/authentik/stages/email/stage.py +++ b/authentik/stages/email/stage.py @@ -12,17 +12,16 @@ from rest_framework.fields import CharField from rest_framework.serializers import ValidationError from structlog.stdlib import get_logger -from authentik.core.models import Token from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes -from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER +from authentik.flows.models import FlowToken +from authentik.flows.planner import PLAN_CONTEXT_IS_RESTORED, PLAN_CONTEXT_PENDING_USER from authentik.flows.stage import ChallengeStageView -from authentik.flows.views.executor import SESSION_KEY_GET +from authentik.flows.views.executor import QS_KEY_TOKEN, SESSION_KEY_GET from authentik.stages.email.models import EmailStage from authentik.stages.email.tasks import send_mails from authentik.stages.email.utils import TemplateEmailMessage LOGGER = get_logger() -QS_KEY_TOKEN = "etoken" # nosec PLAN_CONTEXT_EMAIL_SENT = "email_sent" @@ -56,7 +55,7 @@ class EmailStageView(ChallengeStageView): relative_url = f"{base_url}?{urlencode(kwargs)}" return self.request.build_absolute_uri(relative_url) - def get_token(self) -> Token: + def get_token(self) -> FlowToken: """Get token""" pending_user = self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] current_stage: EmailStage = self.executor.current_stage @@ -65,10 +64,14 @@ class EmailStageView(ChallengeStageView): ) # + 1 because django timesince always rounds down identifier = slugify(f"ak-email-stage-{current_stage.name}-{pending_user}") # Don't check for validity here, we only care if the token exists - tokens = Token.objects.filter(identifier=identifier) + tokens = FlowToken.objects.filter(identifier=identifier) if not tokens.exists(): - return Token.objects.create( - expires=now() + valid_delta, user=pending_user, identifier=identifier + return FlowToken.objects.create( + expires=now() + valid_delta, + user=pending_user, + identifier=identifier, + flow=self.executor.flow, + _plan=FlowToken.pickle(self.executor.plan), ) token = tokens.first() # Check if token is expired and rotate key if so @@ -97,13 +100,9 @@ class EmailStageView(ChallengeStageView): def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: # Check if the user came back from the email link to verify - if QS_KEY_TOKEN in request.session.get(SESSION_KEY_GET, {}): - tokens = Token.filter_not_expired(key=request.session[SESSION_KEY_GET][QS_KEY_TOKEN]) - if not tokens.exists(): - return self.executor.stage_invalid(_("Invalid token")) - token = tokens.first() - self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = token.user - token.delete() + if QS_KEY_TOKEN in request.session.get( + SESSION_KEY_GET, {} + ) and self.executor.plan.context.get(PLAN_CONTEXT_IS_RESTORED, False): messages.success(request, _("Successfully verified Email.")) if self.executor.current_stage.activate_user_on_success: self.executor.plan.context[PLAN_CONTEXT_PENDING_USER].is_active = True From cbb6887983850914beecdc65b5fad120a1deb86f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Dec 2021 08:39:26 +0100 Subject: [PATCH 08/43] build(deps): bump goauthentik.io/api from 0.2021104.7 to 0.2021104.9 (#1887) --- go.mod | 4 ++-- go.sum | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 66d5e72d3..5e304d030 100644 --- a/go.mod +++ b/go.mod @@ -29,11 +29,11 @@ require ( github.com/prometheus/client_golang v1.11.0 github.com/recws-org/recws v1.3.1 github.com/sirupsen/logrus v1.8.1 - goauthentik.io/api v0.2021104.7 + goauthentik.io/api v0.2021104.9 golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect golang.org/x/net v0.0.0-20210510120150-4163338589ed // indirect golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558 - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c google.golang.org/appengine v1.6.7 // indirect gopkg.in/boj/redistore.v1 v1.0.0-20160128113310-fc113767cd6b gopkg.in/square/go-jose.v2 v2.5.1 // indirect diff --git a/go.sum b/go.sum index 0279435df..ed23ca425 100644 --- a/go.sum +++ b/go.sum @@ -561,8 +561,8 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -goauthentik.io/api v0.2021104.7 h1:JWKypuvYWWPqq8c8xLN8qVv5ny8TqsfmLdqNwJM9bZk= -goauthentik.io/api v0.2021104.7/go.mod h1:02nnD4FRd8lu8A1+ZuzqownBgvAhdCKzqkKX8v7JMTE= +goauthentik.io/api v0.2021104.9 h1:/eFCxp1Sk4eviY0SxPm3wgyEO/2YBZJW5LZXGNFDaBA= +goauthentik.io/api v0.2021104.9/go.mod h1:02nnD4FRd8lu8A1+ZuzqownBgvAhdCKzqkKX8v7JMTE= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= From 472256794d3462803d0a7221abc81ff8642daab0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Dec 2021 08:39:52 +0100 Subject: [PATCH 09/43] build(deps): bump prettier from 2.5.0 to 2.5.1 in /web (#1885) --- web/package-lock.json | 14 +++++++------- web/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/web/package-lock.json b/web/package-lock.json index 397cc13b3..ee3750127 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -53,7 +53,7 @@ "fuse.js": "^6.4.6", "lit": "^2.0.2", "moment": "^2.29.1", - "prettier": "^2.5.0", + "prettier": "^2.5.1", "rapidoc": "^9.1.3", "rollup": "^2.60.2", "rollup-plugin-copy": "^3.4.0", @@ -6767,9 +6767,9 @@ } }, "node_modules/prettier": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.0.tgz", - "integrity": "sha512-FM/zAKgWTxj40rH03VxzIPdXmj39SwSjwG0heUcNFwI+EMZJnY93yAiKXM3dObIKAM5TA88werc8T/EwhB45eg==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", + "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", "bin": { "prettier": "bin-prettier.js" }, @@ -13714,9 +13714,9 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" }, "prettier": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.0.tgz", - "integrity": "sha512-FM/zAKgWTxj40rH03VxzIPdXmj39SwSjwG0heUcNFwI+EMZJnY93yAiKXM3dObIKAM5TA88werc8T/EwhB45eg==" + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", + "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==" }, "pretty-format": { "version": "26.6.2", diff --git a/web/package.json b/web/package.json index 8383499e8..405bf8f59 100644 --- a/web/package.json +++ b/web/package.json @@ -89,7 +89,7 @@ "fuse.js": "^6.4.6", "lit": "^2.0.2", "moment": "^2.29.1", - "prettier": "^2.5.0", + "prettier": "^2.5.1", "rapidoc": "^9.1.3", "rollup": "^2.60.2", "rollup-plugin-copy": "^3.4.0", From 44cd4d847d51f7c6a27d892966b34ae28a47a114 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Dec 2021 08:40:18 +0100 Subject: [PATCH 10/43] build(deps): bump golang from 1.17.3-bullseye to 1.17.4-bullseye (#1882) --- Dockerfile | 2 +- ldap.Dockerfile | 2 +- proxy.Dockerfile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 82de5c25a..90fe5fbb8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,7 +28,7 @@ ENV NODE_ENV=production RUN cd /work/web && npm i && npm run build # Stage 4: Build go proxy -FROM docker.io/golang:1.17.3-bullseye AS builder +FROM docker.io/golang:1.17.4-bullseye AS builder WORKDIR /work diff --git a/ldap.Dockerfile b/ldap.Dockerfile index 02ee41dee..2246a8664 100644 --- a/ldap.Dockerfile +++ b/ldap.Dockerfile @@ -1,5 +1,5 @@ # Stage 1: Build -FROM docker.io/golang:1.17.3-bullseye AS builder +FROM docker.io/golang:1.17.4-bullseye AS builder WORKDIR /go/src/goauthentik.io diff --git a/proxy.Dockerfile b/proxy.Dockerfile index 2c626eef2..1d1d70d4a 100644 --- a/proxy.Dockerfile +++ b/proxy.Dockerfile @@ -7,7 +7,7 @@ ENV NODE_ENV=production RUN cd /static && npm i && npm run build-proxy # Stage 2: Build -FROM docker.io/golang:1.17.3-bullseye AS builder +FROM docker.io/golang:1.17.4-bullseye AS builder WORKDIR /go/src/goauthentik.io From 02d27651f361a220afdedcdd0bf26bf507e431fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Dec 2021 08:41:05 +0100 Subject: [PATCH 11/43] build(deps): bump eslint from 8.3.0 to 8.4.0 in /web (#1884) --- web/package-lock.json | 82 +++++++++++++++++++++---------------------- web/package.json | 2 +- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/web/package-lock.json b/web/package-lock.json index ee3750127..f84fe5345 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -45,7 +45,7 @@ "chartjs-adapter-moment": "^1.0.0", "codemirror": "^5.64.0", "construct-style-sheets-polyfill": "^2.4.16", - "eslint": "^8.3.0", + "eslint": "^8.4.0", "eslint-config-google": "^0.14.0", "eslint-plugin-custom-elements": "0.0.4", "eslint-plugin-lit": "^1.6.1", @@ -1655,13 +1655,13 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.4.tgz", - "integrity": "sha512-h8Vx6MdxwWI2WM8/zREHMoqdgLNXEL4QX3MWSVMdyNJGvXVOs+6lp+m2hc3FnuMHDc4poxFNI20vCk0OmI4G0Q==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.5.tgz", + "integrity": "sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ==", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.0.0", + "espree": "^9.2.0", "globals": "^13.9.0", "ignore": "^4.0.6", "import-fresh": "^3.2.1", @@ -1713,11 +1713,11 @@ "integrity": "sha512-ZDaIidbgbgb1FPjIlgTD0nXwhlVALmadWPJjlxpZiSnlRWBa1IsbInzeG+EPr5zPi8GECAK5bS7JcsUsIJDBlw==" }, "node_modules/@humanwhocodes/config-array": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.6.0.tgz", - "integrity": "sha512-JQlEKbcgEUjBFhLIF4iqM7u/9lwgHRBcpHrmUNCALK0Q3amXN6lxdoXLnF0sm11E9VqTmBALR87IlUg1bZ8A9A==", + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.2.tgz", + "integrity": "sha512-UXOuFCGcwciWckOpmfKDq/GyhlTf9pN/BzG//x8p8zTOFEcGuA68ANXheFS0AGvy3qgZqLBUkMs7hqzqCKOVwA==", "dependencies": { - "@humanwhocodes/object-schema": "^1.2.0", + "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", "minimatch": "^3.0.4" }, @@ -1726,9 +1726,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", - "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" }, "node_modules/@jackfranklin/rollup-plugin-markdown": { "version": "0.3.0", @@ -4035,12 +4035,12 @@ } }, "node_modules/eslint": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.3.0.tgz", - "integrity": "sha512-aIay56Ph6RxOTC7xyr59Kt3ewX185SaGnAr8eWukoPLeriCrvGjvAubxuvaXOfsxhtwV5g0uBOsyhAom4qJdww==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.4.0.tgz", + "integrity": "sha512-kv0XQcAQJL/VD9THQKhTQZVqkJKA+tIj/v2ZKNaIHRAADcJWFb+B/BAewUYuF6UVg1s2xC5qXVoDk0G8sKGeTA==", "dependencies": { - "@eslint/eslintrc": "^1.0.4", - "@humanwhocodes/config-array": "^0.6.0", + "@eslint/eslintrc": "^1.0.5", + "@humanwhocodes/config-array": "^0.9.2", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -4051,7 +4051,7 @@ "eslint-scope": "^7.1.0", "eslint-utils": "^3.0.0", "eslint-visitor-keys": "^3.1.0", - "espree": "^9.1.0", + "espree": "^9.2.0", "esquery": "^1.4.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -4314,9 +4314,9 @@ } }, "node_modules/espree": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.1.0.tgz", - "integrity": "sha512-ZgYLvCS1wxOczBYGcQT9DDWgicXwJ4dbocr9uYN+/eresBAUuBu+O4WzB21ufQ/JqQT8gyp7hJ3z8SHii32mTQ==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.2.0.tgz", + "integrity": "sha512-oP3utRkynpZWF/F2x/HZJ+AGtnIclaR7z1pYPxy7NYM2fSO6LgK/Rkny8anRSPK/VwEA1eqm2squui0T7ZMOBg==", "dependencies": { "acorn": "^8.6.0", "acorn-jsx": "^5.3.1", @@ -9859,13 +9859,13 @@ } }, "@eslint/eslintrc": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.4.tgz", - "integrity": "sha512-h8Vx6MdxwWI2WM8/zREHMoqdgLNXEL4QX3MWSVMdyNJGvXVOs+6lp+m2hc3FnuMHDc4poxFNI20vCk0OmI4G0Q==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.5.tgz", + "integrity": "sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ==", "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.0.0", + "espree": "^9.2.0", "globals": "^13.9.0", "ignore": "^4.0.6", "import-fresh": "^3.2.1", @@ -9900,19 +9900,19 @@ "integrity": "sha512-ZDaIidbgbgb1FPjIlgTD0nXwhlVALmadWPJjlxpZiSnlRWBa1IsbInzeG+EPr5zPi8GECAK5bS7JcsUsIJDBlw==" }, "@humanwhocodes/config-array": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.6.0.tgz", - "integrity": "sha512-JQlEKbcgEUjBFhLIF4iqM7u/9lwgHRBcpHrmUNCALK0Q3amXN6lxdoXLnF0sm11E9VqTmBALR87IlUg1bZ8A9A==", + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.2.tgz", + "integrity": "sha512-UXOuFCGcwciWckOpmfKDq/GyhlTf9pN/BzG//x8p8zTOFEcGuA68ANXheFS0AGvy3qgZqLBUkMs7hqzqCKOVwA==", "requires": { - "@humanwhocodes/object-schema": "^1.2.0", + "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", "minimatch": "^3.0.4" } }, "@humanwhocodes/object-schema": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", - "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" }, "@jackfranklin/rollup-plugin-markdown": { "version": "0.3.0", @@ -11666,12 +11666,12 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "eslint": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.3.0.tgz", - "integrity": "sha512-aIay56Ph6RxOTC7xyr59Kt3ewX185SaGnAr8eWukoPLeriCrvGjvAubxuvaXOfsxhtwV5g0uBOsyhAom4qJdww==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.4.0.tgz", + "integrity": "sha512-kv0XQcAQJL/VD9THQKhTQZVqkJKA+tIj/v2ZKNaIHRAADcJWFb+B/BAewUYuF6UVg1s2xC5qXVoDk0G8sKGeTA==", "requires": { - "@eslint/eslintrc": "^1.0.4", - "@humanwhocodes/config-array": "^0.6.0", + "@eslint/eslintrc": "^1.0.5", + "@humanwhocodes/config-array": "^0.9.2", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -11682,7 +11682,7 @@ "eslint-scope": "^7.1.0", "eslint-utils": "^3.0.0", "eslint-visitor-keys": "^3.1.0", - "espree": "^9.1.0", + "espree": "^9.2.0", "esquery": "^1.4.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -11853,9 +11853,9 @@ "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==" }, "espree": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.1.0.tgz", - "integrity": "sha512-ZgYLvCS1wxOczBYGcQT9DDWgicXwJ4dbocr9uYN+/eresBAUuBu+O4WzB21ufQ/JqQT8gyp7hJ3z8SHii32mTQ==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.2.0.tgz", + "integrity": "sha512-oP3utRkynpZWF/F2x/HZJ+AGtnIclaR7z1pYPxy7NYM2fSO6LgK/Rkny8anRSPK/VwEA1eqm2squui0T7ZMOBg==", "requires": { "acorn": "^8.6.0", "acorn-jsx": "^5.3.1", diff --git a/web/package.json b/web/package.json index 405bf8f59..6ba0c3607 100644 --- a/web/package.json +++ b/web/package.json @@ -81,7 +81,7 @@ "chartjs-adapter-moment": "^1.0.0", "codemirror": "^5.64.0", "construct-style-sheets-polyfill": "^2.4.16", - "eslint": "^8.3.0", + "eslint": "^8.4.0", "eslint-config-google": "^0.14.0", "eslint-plugin-custom-elements": "0.0.4", "eslint-plugin-lit": "^1.6.1", From edb5caae9b3bf9a57d63fa23bfd720500b48c0ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Dec 2021 08:41:30 +0100 Subject: [PATCH 12/43] build(deps-dev): bump prettier from 2.5.0 to 2.5.1 in /website (#1883) --- website/package-lock.json | 14 +++++++------- website/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/website/package-lock.json b/website/package-lock.json index 8fa8c17b0..d063341b3 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -21,7 +21,7 @@ "react-toggle": "^4.1.2" }, "devDependencies": { - "prettier": "2.5.0" + "prettier": "2.5.1" } }, "node_modules/@algolia/autocomplete-core": { @@ -9437,9 +9437,9 @@ } }, "node_modules/prettier": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.0.tgz", - "integrity": "sha512-FM/zAKgWTxj40rH03VxzIPdXmj39SwSjwG0heUcNFwI+EMZJnY93yAiKXM3dObIKAM5TA88werc8T/EwhB45eg==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", + "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", "dev": true, "bin": { "prettier": "bin-prettier.js" @@ -19824,9 +19824,9 @@ "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" }, "prettier": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.0.tgz", - "integrity": "sha512-FM/zAKgWTxj40rH03VxzIPdXmj39SwSjwG0heUcNFwI+EMZJnY93yAiKXM3dObIKAM5TA88werc8T/EwhB45eg==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", + "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", "dev": true }, "pretty-error": { diff --git a/website/package.json b/website/package.json index ee0aa7cfe..02edb1e93 100644 --- a/website/package.json +++ b/website/package.json @@ -37,6 +37,6 @@ ] }, "devDependencies": { - "prettier": "2.5.0" + "prettier": "2.5.1" } } From e046000f36fdc4c8e12b55eb48975ef407e3cda2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Dec 2021 08:42:21 +0100 Subject: [PATCH 13/43] build(deps): bump chart.js from 3.6.1 to 3.6.2 in /web (#1886) --- web/package-lock.json | 14 +++++++------- web/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/web/package-lock.json b/web/package-lock.json index f84fe5345..5a27c191a 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -41,7 +41,7 @@ "@webcomponents/webcomponentsjs": "^2.6.0", "babel-plugin-macros": "^3.1.0", "base64-js": "^1.5.1", - "chart.js": "^3.6.1", + "chart.js": "^3.6.2", "chartjs-adapter-moment": "^1.0.0", "codemirror": "^5.64.0", "construct-style-sheets-polyfill": "^2.4.16", @@ -3482,9 +3482,9 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, "node_modules/chart.js": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.6.1.tgz", - "integrity": "sha512-AycnixR0I325Fp3bqQ7wRJbkIJPwz/9IZtUBvdBWMjK5+nKCy6FZ3VejkDTtB9udePEXNt1UYoGTsNL49JoIbg==" + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.6.2.tgz", + "integrity": "sha512-Xz7f/fgtVltfQYWq0zL1Xbv7N2inpG+B54p3D5FSvpCdy3sM+oZhbqa42eNuYXltaVvajgX5UpKCU2GeeJIgxg==" }, "node_modules/chartjs-adapter-moment": { "version": "1.0.0", @@ -11246,9 +11246,9 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, "chart.js": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.6.1.tgz", - "integrity": "sha512-AycnixR0I325Fp3bqQ7wRJbkIJPwz/9IZtUBvdBWMjK5+nKCy6FZ3VejkDTtB9udePEXNt1UYoGTsNL49JoIbg==" + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.6.2.tgz", + "integrity": "sha512-Xz7f/fgtVltfQYWq0zL1Xbv7N2inpG+B54p3D5FSvpCdy3sM+oZhbqa42eNuYXltaVvajgX5UpKCU2GeeJIgxg==" }, "chartjs-adapter-moment": { "version": "1.0.0", diff --git a/web/package.json b/web/package.json index 6ba0c3607..bfd1e3cec 100644 --- a/web/package.json +++ b/web/package.json @@ -77,7 +77,7 @@ "@webcomponents/webcomponentsjs": "^2.6.0", "babel-plugin-macros": "^3.1.0", "base64-js": "^1.5.1", - "chart.js": "^3.6.1", + "chart.js": "^3.6.2", "chartjs-adapter-moment": "^1.0.0", "codemirror": "^5.64.0", "construct-style-sheets-polyfill": "^2.4.16", From b694816e7bc61ce8fa55ae6f5a90bcdbc70a3fa4 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 6 Dec 2021 09:43:29 +0100 Subject: [PATCH 14/43] sources/*: Allow creation of source connections via API closes #1888 Signed-off-by: Jens Langhammer --- .../sources/oauth/api/source_connection.py | 25 +++------ .../sources/plex/api/source_connection.py | 17 +++--- schema.yml | 56 +++++++++++++++++++ 3 files changed, 72 insertions(+), 26 deletions(-) diff --git a/authentik/sources/oauth/api/source_connection.py b/authentik/sources/oauth/api/source_connection.py index 61275db35..9ef4c11ec 100644 --- a/authentik/sources/oauth/api/source_connection.py +++ b/authentik/sources/oauth/api/source_connection.py @@ -1,8 +1,7 @@ """OAuth Source Serializer""" from django_filters.rest_framework import DjangoFilterBackend -from rest_framework import mixins from rest_framework.filters import OrderingFilter, SearchFilter -from rest_framework.viewsets import GenericViewSet +from rest_framework.viewsets import ModelViewSet from authentik.api.authorization import OwnerFilter, OwnerPermissions from authentik.core.api.sources import SourceSerializer @@ -15,25 +14,14 @@ class UserOAuthSourceConnectionSerializer(SourceSerializer): class Meta: model = UserOAuthSourceConnection - fields = [ - "pk", - "user", - "source", - "identifier", - ] + fields = ["pk", "user", "source", "identifier", "access_token"] extra_kwargs = { "user": {"read_only": True}, + "access_token": {"write_only": True}, } -class UserOAuthSourceConnectionViewSet( - mixins.RetrieveModelMixin, - mixins.UpdateModelMixin, - mixins.DestroyModelMixin, - UsedByMixin, - mixins.ListModelMixin, - GenericViewSet, -): +class UserOAuthSourceConnectionViewSet(UsedByMixin, ModelViewSet): """Source Viewset""" queryset = UserOAuthSourceConnection.objects.all() @@ -42,3 +30,8 @@ class UserOAuthSourceConnectionViewSet( permission_classes = [OwnerPermissions] filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter] ordering = ["source__slug"] + + def perform_create(self, serializer: UserOAuthSourceConnectionSerializer): + if not self.request.user.is_superuser: + return serializer.save() + return serializer.save(user=self.request.user) diff --git a/authentik/sources/plex/api/source_connection.py b/authentik/sources/plex/api/source_connection.py index de158cbc8..936afb7f7 100644 --- a/authentik/sources/plex/api/source_connection.py +++ b/authentik/sources/plex/api/source_connection.py @@ -1,8 +1,7 @@ """Plex Source connection Serializer""" from django_filters.rest_framework import DjangoFilterBackend -from rest_framework import mixins from rest_framework.filters import OrderingFilter, SearchFilter -from rest_framework.viewsets import GenericViewSet +from rest_framework.viewsets import ModelViewSet from authentik.api.authorization import OwnerFilter, OwnerPermissions from authentik.core.api.sources import SourceSerializer @@ -27,14 +26,7 @@ class PlexSourceConnectionSerializer(SourceSerializer): } -class PlexSourceConnectionViewSet( - mixins.RetrieveModelMixin, - mixins.UpdateModelMixin, - mixins.DestroyModelMixin, - UsedByMixin, - mixins.ListModelMixin, - GenericViewSet, -): +class PlexSourceConnectionViewSet(UsedByMixin, ModelViewSet): """Plex Source connection Serializer""" queryset = PlexSourceConnection.objects.all() @@ -43,3 +35,8 @@ class PlexSourceConnectionViewSet( permission_classes = [OwnerPermissions] filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter] ordering = ["pk"] + + def perform_create(self, serializer: PlexSourceConnectionSerializer): + if not self.request.user.is_superuser: + return serializer.save() + return serializer.save(user=self.request.user) diff --git a/schema.yml b/schema.yml index d241ac93d..b95863b12 100644 --- a/schema.yml +++ b/schema.yml @@ -13570,6 +13570,30 @@ paths: $ref: '#/components/schemas/ValidationError' '403': $ref: '#/components/schemas/GenericError' + post: + operationId: sources_user_connections_oauth_create + description: Source Viewset + tags: + - sources + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UserOAuthSourceConnectionRequest' + required: true + security: + - authentik: [] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/UserOAuthSourceConnection' + description: '' + '400': + $ref: '#/components/schemas/ValidationError' + '403': + $ref: '#/components/schemas/GenericError' /sources/user_connections/oauth/{id}/: get: operationId: sources_user_connections_oauth_retrieve @@ -13754,6 +13778,30 @@ paths: $ref: '#/components/schemas/ValidationError' '403': $ref: '#/components/schemas/GenericError' + post: + operationId: sources_user_connections_plex_create + description: Plex Source connection Serializer + tags: + - sources + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlexSourceConnectionRequest' + required: true + security: + - authentik: [] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/PlexSourceConnection' + description: '' + '400': + $ref: '#/components/schemas/ValidationError' + '403': + $ref: '#/components/schemas/GenericError' /sources/user_connections/plex/{id}/: get: operationId: sources_user_connections_plex_retrieve @@ -28143,6 +28191,10 @@ components: type: string minLength: 1 maxLength: 255 + access_token: + type: string + writeOnly: true + nullable: true PatchedUserRequest: type: object description: User Serializer @@ -30889,6 +30941,10 @@ components: type: string minLength: 1 maxLength: 255 + access_token: + type: string + writeOnly: true + nullable: true required: - identifier - source From e3e9178cccb733fc3dade184aef1446e22ce3964 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 6 Dec 2021 10:10:44 +0100 Subject: [PATCH 15/43] web/admin: show outpost warning on application page too Signed-off-by: Jens Langhammer --- authentik/outposts/api/outposts.py | 26 +++++++++-- schema.yml | 16 +++++++ web/src/locales/en.po | 4 ++ web/src/locales/fr_FR.po | 4 ++ web/src/locales/pseudo-LOCALE.po | 4 ++ .../pages/applications/ApplicationViewPage.ts | 46 +++++++++++++++++-- 6 files changed, 91 insertions(+), 9 deletions(-) diff --git a/authentik/outposts/api/outposts.py b/authentik/outposts/api/outposts.py index 742e526db..93c184d80 100644 --- a/authentik/outposts/api/outposts.py +++ b/authentik/outposts/api/outposts.py @@ -1,6 +1,8 @@ """Outpost API Views""" from dacite.core import from_dict from dacite.exceptions import DaciteError +from django_filters.filters import ModelMultipleChoiceFilter +from django_filters.filterset import FilterSet from drf_spectacular.utils import extend_schema from rest_framework.decorators import action from rest_framework.fields import BooleanField, CharField, DateTimeField @@ -99,16 +101,30 @@ class OutpostHealthSerializer(PassiveSerializer): version_outdated = BooleanField(read_only=True) +class OutpostFilter(FilterSet): + """Filter for Outposts""" + + providers_by_pk = ModelMultipleChoiceFilter( + field_name="providers", + queryset=Provider.objects.all(), + ) + + class Meta: + + model = Outpost + fields = { + "providers": ["isnull"], + "name": ["iexact", "icontains"], + "service_connection__name": ["iexact", "icontains"], + } + + class OutpostViewSet(UsedByMixin, ModelViewSet): """Outpost Viewset""" queryset = Outpost.objects.all() serializer_class = OutpostSerializer - filterset_fields = { - "providers": ["isnull"], - "name": ["iexact", "icontains"], - "service_connection__name": ["iexact", "icontains"], - } + filterset_class = OutpostFilter search_fields = [ "name", "providers__name", diff --git a/schema.yml b/schema.yml index b95863b12..a0899416e 100644 --- a/schema.yml +++ b/schema.yml @@ -5764,6 +5764,14 @@ paths: name: providers__isnull schema: type: boolean + - in: query + name: providers_by_pk + schema: + type: array + items: + type: integer + explode: true + style: form - name: search required: false in: query @@ -5952,6 +5960,14 @@ paths: name: providers__isnull schema: type: boolean + - in: query + name: providers_by_pk + schema: + type: array + items: + type: integer + explode: true + style: form - name: search required: false in: query diff --git a/web/src/locales/en.po b/web/src/locales/en.po index f76436c1d..c71cb3398 100644 --- a/web/src/locales/en.po +++ b/web/src/locales/en.po @@ -5619,6 +5619,10 @@ msgstr "Wait (min)" msgid "Warning" msgstr "Warning" +#: src/pages/applications/ApplicationViewPage.ts +msgid "Warning: Application is not used by any Outpost." +msgstr "Warning: Application is not used by any Outpost." + #: src/pages/stages/invitation/InvitationListPage.ts msgid "Warning: No invitation stage is bound to any flow. Invitations will not work as expected." msgstr "Warning: No invitation stage is bound to any flow. Invitations will not work as expected." diff --git a/web/src/locales/fr_FR.po b/web/src/locales/fr_FR.po index caccfeeab..2c8e6c5f2 100644 --- a/web/src/locales/fr_FR.po +++ b/web/src/locales/fr_FR.po @@ -5557,6 +5557,10 @@ msgstr "Attente (min)" msgid "Warning" msgstr "Avertissement" +#: src/pages/applications/ApplicationViewPage.ts +msgid "Warning: Application is not used by any Outpost." +msgstr "" + #: src/pages/stages/invitation/InvitationListPage.ts msgid "Warning: No invitation stage is bound to any flow. Invitations will not work as expected." msgstr "" diff --git a/web/src/locales/pseudo-LOCALE.po b/web/src/locales/pseudo-LOCALE.po index 20cb14f2f..291fc2387 100644 --- a/web/src/locales/pseudo-LOCALE.po +++ b/web/src/locales/pseudo-LOCALE.po @@ -5599,6 +5599,10 @@ msgstr "" msgid "Warning" msgstr "" +#: src/pages/applications/ApplicationViewPage.ts +msgid "Warning: Application is not used by any Outpost." +msgstr "" + #: src/pages/stages/invitation/InvitationListPage.ts msgid "Warning: No invitation stage is bound to any flow. Invitations will not work as expected." msgstr "" diff --git a/web/src/pages/applications/ApplicationViewPage.ts b/web/src/pages/applications/ApplicationViewPage.ts index cbd477a59..33ea89cf1 100644 --- a/web/src/pages/applications/ApplicationViewPage.ts +++ b/web/src/pages/applications/ApplicationViewPage.ts @@ -1,10 +1,11 @@ import { t } from "@lingui/macro"; import { CSSResult, LitElement, TemplateResult, html } from "lit"; -import { customElement, property } from "lit/decorators.js"; +import { customElement, property, state } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; import AKGlobal from "../../authentik.css"; +import PFBanner from "@patternfly/patternfly/components/Banner/banner.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFCard from "@patternfly/patternfly/components/Card/card.css"; import PFContent from "@patternfly/patternfly/components/Content/content.css"; @@ -13,7 +14,7 @@ import PFPage from "@patternfly/patternfly/components/Page/page.css"; import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; -import { Application, CoreApi } from "@goauthentik/api"; +import { Application, CoreApi, OutpostsApi } from "@goauthentik/api"; import { DEFAULT_CONFIG } from "../../api/Config"; import "../../elements/EmptyState"; @@ -36,14 +37,45 @@ export class ApplicationViewPage extends LitElement { }) .then((app) => { this.application = app; + if ( + app.providerObj && + [ + "authentik_providers_proxy.proxyprovider", + "authentik_providers_ldap.ldapprovider", + ].includes(app.providerObj.metaModelName) + ) { + new OutpostsApi(DEFAULT_CONFIG) + .outpostsInstancesList({ + providersByPk: [app.provider || 0], + pageSize: 1, + }) + .then((outposts) => { + if (outposts.pagination.count < 1) { + this.missingOutpost = true; + } + }); + } }); } @property({ attribute: false }) application!: Application; + @state() + missingOutpost = false; + static get styles(): CSSResult[] { - return [PFBase, PFPage, PFContent, PFButton, PFDescriptionList, PFGrid, PFCard, AKGlobal]; + return [ + PFBase, + PFBanner, + PFPage, + PFContent, + PFButton, + PFDescriptionList, + PFGrid, + PFCard, + AKGlobal, + ]; } render(): TemplateResult { @@ -61,7 +93,12 @@ export class ApplicationViewPage extends LitElement { if (!this.application) { return html` `; } - return html` + return html` + ${this.missingOutpost + ? html`
+ ${t`Warning: Application is not used by any Outpost.`} +
` + : html``}
${this.application.providerObj?.name} + (${this.application.providerObj?.verboseName}) From f95c06b76f3d23d420049a0224ba06e988b99490 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 6 Dec 2021 10:13:42 +0100 Subject: [PATCH 16/43] web: Update Web API Client version (#1889) Signed-off-by: GitHub Co-authored-by: BeryJu --- web/package-lock.json | 14 +++++++------- web/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/web/package-lock.json b/web/package-lock.json index 5a27c191a..93794df90 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -15,7 +15,7 @@ "@babel/preset-env": "^7.16.4", "@babel/preset-typescript": "^7.16.0", "@fortawesome/fontawesome-free": "^5.15.4", - "@goauthentik/api": "^2021.10.4-1638699310", + "@goauthentik/api": "^2021.10.4-1638781871", "@jackfranklin/rollup-plugin-markdown": "^0.3.0", "@lingui/cli": "^3.13.0", "@lingui/core": "^3.13.0", @@ -1708,9 +1708,9 @@ } }, "node_modules/@goauthentik/api": { - "version": "2021.10.4-1638699310", - "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2021.10.4-1638699310.tgz", - "integrity": "sha512-ZDaIidbgbgb1FPjIlgTD0nXwhlVALmadWPJjlxpZiSnlRWBa1IsbInzeG+EPr5zPi8GECAK5bS7JcsUsIJDBlw==" + "version": "2021.10.4-1638781871", + "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2021.10.4-1638781871.tgz", + "integrity": "sha512-QI/pqVVCt/W+iXZGXXipAYX39CBpuUEPDxFKPCiQyU+G0+rLYt1T1umBjTIlIEXRKC8xKSInLkDS/GEz32kXNA==" }, "node_modules/@humanwhocodes/config-array": { "version": "0.9.2", @@ -9895,9 +9895,9 @@ "integrity": "sha512-eYm8vijH/hpzr/6/1CJ/V/Eb1xQFW2nnUKArb3z+yUWv7HTwj6M7SP957oMjfZjAHU6qpoNc2wQvIxBLWYa/Jg==" }, "@goauthentik/api": { - "version": "2021.10.4-1638699310", - "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2021.10.4-1638699310.tgz", - "integrity": "sha512-ZDaIidbgbgb1FPjIlgTD0nXwhlVALmadWPJjlxpZiSnlRWBa1IsbInzeG+EPr5zPi8GECAK5bS7JcsUsIJDBlw==" + "version": "2021.10.4-1638781871", + "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2021.10.4-1638781871.tgz", + "integrity": "sha512-QI/pqVVCt/W+iXZGXXipAYX39CBpuUEPDxFKPCiQyU+G0+rLYt1T1umBjTIlIEXRKC8xKSInLkDS/GEz32kXNA==" }, "@humanwhocodes/config-array": { "version": "0.9.2", diff --git a/web/package.json b/web/package.json index bfd1e3cec..cfcf6fec6 100644 --- a/web/package.json +++ b/web/package.json @@ -51,7 +51,7 @@ "@babel/preset-env": "^7.16.4", "@babel/preset-typescript": "^7.16.0", "@fortawesome/fontawesome-free": "^5.15.4", - "@goauthentik/api": "^2021.10.4-1638699310", + "@goauthentik/api": "^2021.10.4-1638781871", "@jackfranklin/rollup-plugin-markdown": "^0.3.0", "@lingui/cli": "^3.13.0", "@lingui/core": "^3.13.0", From 4143d3fe285652af1cb3c9141fdfd4701e736be9 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 6 Dec 2021 10:13:30 +0100 Subject: [PATCH 17/43] events: don't set metrics on import Signed-off-by: Jens Langhammer --- authentik/events/monitored_tasks.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/authentik/events/monitored_tasks.py b/authentik/events/monitored_tasks.py index 138b4070c..d7273e0bf 100644 --- a/authentik/events/monitored_tasks.py +++ b/authentik/events/monitored_tasks.py @@ -210,7 +210,3 @@ class PrefilledMonitoredTask(MonitoredTask): def run(self, *args, **kwargs): raise NotImplementedError - - -for task in TaskInfo.all().values(): - task.set_prom_metrics() From b7f94df4d93992883b0f86e332ece63c2008e5ca Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 6 Dec 2021 10:54:21 +0100 Subject: [PATCH 18/43] web: fix text colour for bad request on light mode Signed-off-by: Jens Langhammer --- web/src/authentik.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/src/authentik.css b/web/src/authentik.css index 81b6b5317..eaeae18e8 100644 --- a/web/src/authentik.css +++ b/web/src/authentik.css @@ -83,9 +83,6 @@ html > form > input { color: var(--pf-global--danger-color--100); } -.ak-static-page h1 { - color: var(--ak-dark-foreground); -} .form-help-text { color: var(--pf-global--Color--100); } @@ -96,6 +93,9 @@ html > form > input { } @media (prefers-color-scheme: dark) { + .ak-static-page h1 { + color: var(--ak-dark-foreground); + } body { background-color: var(--ak-dark-background) !important; } From 42a9979d914948244afbccbaf6140886585f3de4 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 6 Dec 2021 11:18:22 +0100 Subject: [PATCH 19/43] web/elements: close dropdown when refresh event is dispatched Signed-off-by: Jens Langhammer --- web/src/elements/buttons/Dropdown.ts | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/web/src/elements/buttons/Dropdown.ts b/web/src/elements/buttons/Dropdown.ts index 7065e086c..995327500 100644 --- a/web/src/elements/buttons/Dropdown.ts +++ b/web/src/elements/buttons/Dropdown.ts @@ -1,17 +1,32 @@ import { LitElement, TemplateResult, html } from "lit"; import { customElement } from "lit/decorators.js"; +import { EVENT_REFRESH } from "../../constants"; + @customElement("ak-dropdown") export class DropdownButton extends LitElement { + menu: HTMLElement | null; + constructor() { super(); - const menu = this.querySelector(".pf-c-dropdown__menu"); + this.menu = this.querySelector(".pf-c-dropdown__menu"); this.querySelectorAll("button.pf-c-dropdown__toggle").forEach((btn) => { btn.addEventListener("click", () => { - if (!menu) return; - menu.hidden = !menu.hidden; + if (!this.menu) return; + this.menu.hidden = !this.menu.hidden; }); }); + window.addEventListener(EVENT_REFRESH, this.clickHandler); + } + + clickHandler = (): void => { + if (!this.menu) return; + this.menu.hidden = true; + }; + + disconnectedCallback(): void { + super.disconnectedCallback(); + window.removeEventListener(EVENT_REFRESH, this.clickHandler); } render(): TemplateResult { From 3c8bbc26216381a93f228f0a437cee4a507ad0e3 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 6 Dec 2021 12:22:40 +0100 Subject: [PATCH 20/43] sources/*: only allow superusers to directly create source connections Signed-off-by: Jens Langhammer --- authentik/sources/oauth/api/source_connection.py | 9 ++------- authentik/sources/plex/api/source_connection.py | 9 ++------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/authentik/sources/oauth/api/source_connection.py b/authentik/sources/oauth/api/source_connection.py index 9ef4c11ec..b0bdc9e8d 100644 --- a/authentik/sources/oauth/api/source_connection.py +++ b/authentik/sources/oauth/api/source_connection.py @@ -3,7 +3,7 @@ from django_filters.rest_framework import DjangoFilterBackend from rest_framework.filters import OrderingFilter, SearchFilter from rest_framework.viewsets import ModelViewSet -from authentik.api.authorization import OwnerFilter, OwnerPermissions +from authentik.api.authorization import OwnerFilter, OwnerSuperuserPermissions from authentik.core.api.sources import SourceSerializer from authentik.core.api.used_by import UsedByMixin from authentik.sources.oauth.models import UserOAuthSourceConnection @@ -27,11 +27,6 @@ class UserOAuthSourceConnectionViewSet(UsedByMixin, ModelViewSet): queryset = UserOAuthSourceConnection.objects.all() serializer_class = UserOAuthSourceConnectionSerializer filterset_fields = ["source__slug"] - permission_classes = [OwnerPermissions] + permission_classes = [OwnerSuperuserPermissions] filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter] ordering = ["source__slug"] - - def perform_create(self, serializer: UserOAuthSourceConnectionSerializer): - if not self.request.user.is_superuser: - return serializer.save() - return serializer.save(user=self.request.user) diff --git a/authentik/sources/plex/api/source_connection.py b/authentik/sources/plex/api/source_connection.py index 936afb7f7..0368adec8 100644 --- a/authentik/sources/plex/api/source_connection.py +++ b/authentik/sources/plex/api/source_connection.py @@ -3,7 +3,7 @@ from django_filters.rest_framework import DjangoFilterBackend from rest_framework.filters import OrderingFilter, SearchFilter from rest_framework.viewsets import ModelViewSet -from authentik.api.authorization import OwnerFilter, OwnerPermissions +from authentik.api.authorization import OwnerFilter, OwnerSuperuserPermissions from authentik.core.api.sources import SourceSerializer from authentik.core.api.used_by import UsedByMixin from authentik.sources.plex.models import PlexSourceConnection @@ -32,11 +32,6 @@ class PlexSourceConnectionViewSet(UsedByMixin, ModelViewSet): queryset = PlexSourceConnection.objects.all() serializer_class = PlexSourceConnectionSerializer filterset_fields = ["source__slug"] - permission_classes = [OwnerPermissions] + permission_classes = [OwnerSuperuserPermissions] filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter] ordering = ["pk"] - - def perform_create(self, serializer: PlexSourceConnectionSerializer): - if not self.request.user.is_superuser: - return serializer.save() - return serializer.save(user=self.request.user) From c1ea78c42293d29a9d9fc4f1a43013ee97c86aaf Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 6 Dec 2021 12:33:29 +0100 Subject: [PATCH 21/43] core: fix missing permission check for group creating when creating service account Signed-off-by: Jens Langhammer --- authentik/core/api/users.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/authentik/core/api/users.py b/authentik/core/api/users.py index cb09f0f05..13d1ad756 100644 --- a/authentik/core/api/users.py +++ b/authentik/core/api/users.py @@ -314,7 +314,7 @@ class UserViewSet(UsedByMixin, ModelViewSet): name=username, attributes={USER_ATTRIBUTE_SA: True, USER_ATTRIBUTE_TOKEN_EXPIRING: False}, ) - if create_group: + if create_group and self.request.user.has_perm("authentik_core.add_group"): group = Group.objects.create( name=username, ) From 873aa4bb226c50700b1b0460b2e2cb33ffec155c Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 6 Dec 2021 12:47:25 +0100 Subject: [PATCH 22/43] providers/saml: remove SESSION_KEY_POST from session after using it Signed-off-by: Jens Langhammer #1873 --- authentik/providers/saml/views/sso.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/authentik/providers/saml/views/sso.py b/authentik/providers/saml/views/sso.py index 4a534c09d..6cb02bbe9 100644 --- a/authentik/providers/saml/views/sso.py +++ b/authentik/providers/saml/views/sso.py @@ -125,7 +125,7 @@ class SAMLSSOBindingPOSTView(SAMLSSOView): # This happens when using POST bindings but the user isn't logged in # (user gets redirected and POST body is 'lost') if SESSION_KEY_POST in self.request.session: - payload = self.request.session[SESSION_KEY_POST] + payload = self.request.session.pop(SESSION_KEY_POST) if REQUEST_KEY_SAML_REQUEST not in payload: LOGGER.info("check_saml_request: SAML payload missing") return bad_request_message(self.request, "The SAML request payload is missing.") From b3e40c6aedfa059a7375129d021ce3635a37e0c0 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 6 Dec 2021 13:54:59 +0100 Subject: [PATCH 23/43] outposts/proxy: don't save raw jwt in cookie to prevent securecookie: the value is too long Signed-off-by: Jens Langhammer --- internal/outpost/proxyv2/application/claims.go | 2 -- internal/outpost/proxyv2/application/mode_common.go | 1 - internal/outpost/proxyv2/application/oauth_callback.go | 1 - 3 files changed, 4 deletions(-) diff --git a/internal/outpost/proxyv2/application/claims.go b/internal/outpost/proxyv2/application/claims.go index 4ff89dbff..40cd148a2 100644 --- a/internal/outpost/proxyv2/application/claims.go +++ b/internal/outpost/proxyv2/application/claims.go @@ -13,6 +13,4 @@ type Claims struct { Name string `json:"name"` PreferredUsername string `json:"preferred_username"` Groups []string `json:"groups"` - - RawToken string } diff --git a/internal/outpost/proxyv2/application/mode_common.go b/internal/outpost/proxyv2/application/mode_common.go index a1430ad1e..cb3aa499b 100644 --- a/internal/outpost/proxyv2/application/mode_common.go +++ b/internal/outpost/proxyv2/application/mode_common.go @@ -25,7 +25,6 @@ func (a *Application) addHeaders(headers http.Header, c *Claims) { headers.Set("X-authentik-email", c.Email) headers.Set("X-authentik-name", c.Name) headers.Set("X-authentik-uid", c.Sub) - headers.Set("X-authentik-jwt", c.RawToken) // System headers headers.Set("X-authentik-meta-jwks", a.proxyConfig.OidcConfiguration.JwksUri) diff --git a/internal/outpost/proxyv2/application/oauth_callback.go b/internal/outpost/proxyv2/application/oauth_callback.go index acd66cf31..7f2937184 100644 --- a/internal/outpost/proxyv2/application/oauth_callback.go +++ b/internal/outpost/proxyv2/application/oauth_callback.go @@ -45,6 +45,5 @@ func (a *Application) redeemCallback(r *http.Request, shouldState string) (*Clai if err := idToken.Claims(&claims); err != nil { return nil, err } - claims.RawToken = rawIDToken return claims, nil } From b14c22cbff99f8141abbd4ed0e072e3eee16b708 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 6 Dec 2021 21:13:04 +0100 Subject: [PATCH 24/43] web: fix duplicate classes, make generic icon clickable Signed-off-by: Jens Langhammer --- Makefile | 3 +++ web/src/elements/notifications/NotificationDrawer.ts | 2 +- .../stages/identification/IdentificationStage.ts | 2 +- web/src/pages/applications/ApplicationListPage.ts | 2 +- web/src/pages/events/EventListPage.ts | 2 +- web/src/user/LibraryApplication.ts | 11 ++++++----- 6 files changed, 13 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index b2792c949..60f9ef558 100644 --- a/Makefile +++ b/Makefile @@ -84,6 +84,9 @@ migrate: run: go run -v cmd/server/main.go +web-watch: + cd web && npm run watch + web: web-lint-fix web-lint web-extract web-lint-fix: diff --git a/web/src/elements/notifications/NotificationDrawer.ts b/web/src/elements/notifications/NotificationDrawer.ts index 1955a4eaf..175a5ae44 100644 --- a/web/src/elements/notifications/NotificationDrawer.ts +++ b/web/src/elements/notifications/NotificationDrawer.ts @@ -95,7 +95,7 @@ export class NotificationDrawer extends LitElement { class="pf-c-dropdown__toggle pf-m-plain" href="#/events/log/${item.event?.pk}" > - + `}