From 7ef14eb86d742d7f902674f183462ece1f2d834a Mon Sep 17 00:00:00 2001 From: Jens L Date: Fri, 12 Apr 2024 14:59:48 +0200 Subject: [PATCH] blueprints: only create default brand if no other default brand exists (#9222) * blueprints: only create default brand if no other default brand exists Signed-off-by: Jens Langhammer * format Signed-off-by: Jens Langhammer * fix invalid blueprint Signed-off-by: Jens Langhammer * fix flaky test, improve pytest output Signed-off-by: Jens Langhammer * format Signed-off-by: Jens Langhammer --------- Signed-off-by: Jens Langhammer --- authentik/root/test_plugin.py | 21 +++ authentik/root/test_runner.py | 3 +- authentik/stages/user_login/tests.py | 3 +- blueprints/default/default-brand.yaml | 5 +- pyproject.toml | 2 +- website/developer-docs/blueprints/v1/tags.md | 186 +++++++++++-------- 6 files changed, 140 insertions(+), 80 deletions(-) create mode 100644 authentik/root/test_plugin.py diff --git a/authentik/root/test_plugin.py b/authentik/root/test_plugin.py new file mode 100644 index 0000000000..2360a5021d --- /dev/null +++ b/authentik/root/test_plugin.py @@ -0,0 +1,21 @@ +from os import environ + +import pytest + +from authentik import get_full_version + +IS_CI = "CI" in environ + + +@pytest.hookimpl(hookwrapper=True) +def pytest_sessionstart(*_, **__): + """Clear the console ahead of the pytest output starting""" + if not IS_CI: + print("\x1b[2J\x1b[H") + yield + + +@pytest.hookimpl(trylast=True) +def pytest_report_header(*_, **__): + """Add authentik version to pytest output""" + return [f"authentik version: {get_full_version()}"] diff --git a/authentik/root/test_runner.py b/authentik/root/test_runner.py index 30b68b5ecc..867469d2b0 100644 --- a/authentik/root/test_runner.py +++ b/authentik/root/test_runner.py @@ -4,6 +4,7 @@ import os from argparse import ArgumentParser from unittest import TestCase +import pytest from django.conf import settings from django.test.runner import DiscoverRunner @@ -105,6 +106,4 @@ class PytestTestRunner(DiscoverRunner): # pragma: no cover f"path instead." ) - import pytest - return pytest.main(self.args) diff --git a/authentik/stages/user_login/tests.py b/authentik/stages/user_login/tests.py index d5dd7c05f1..e9b02b483c 100644 --- a/authentik/stages/user_login/tests.py +++ b/authentik/stages/user_login/tests.py @@ -133,13 +133,14 @@ class TestUserLoginStage(FlowTestCase): reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), data={"remember_me": True}, ) + _now = now().timestamp() self.assertEqual(response.status_code, 200) self.assertStageRedirects(response, reverse("authentik_core:root-redirect")) self.assertNotEqual(list(self.client.session.keys()), []) session_key = self.client.session.session_key session = AuthenticatedSession.objects.filter(session_key=session_key).first() self.assertAlmostEqual( - session.expires.timestamp() - now().timestamp(), + session.expires.timestamp() - _now, timedelta_from_string(self.stage.session_duration).total_seconds() + timedelta_from_string(self.stage.remember_me_offset).total_seconds(), delta=1, diff --git a/blueprints/default/default-brand.yaml b/blueprints/default/default-brand.yaml index c9c3a3ac6d..835427a294 100644 --- a/blueprints/default/default-brand.yaml +++ b/blueprints/default/default-brand.yaml @@ -26,6 +26,9 @@ entries: !Find [authentik_flows.flow, [slug, default-user-settings-flow]] identifiers: domain: authentik-default - default: True + default: true state: created + conditions: + # Only create default brand if no other default brand exists + - !Condition [NOR, !Find [authentik_brands.brand, [default, True]]] model: authentik_brands.brand diff --git a/pyproject.toml b/pyproject.toml index 686565cd5d..646b6c0e29 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,7 +76,7 @@ show_missing = true DJANGO_SETTINGS_MODULE = "authentik.root.settings" python_files = ["tests.py", "test_*.py", "*_tests.py"] junit_family = "xunit2" -addopts = "-p no:celery --junitxml=unittest.xml -vv --full-trace --doctest-modules" +addopts = "-p no:celery -p authentik.root.test_plugin --junitxml=unittest.xml -vv --full-trace --doctest-modules" filterwarnings = [ "ignore:defusedxml.lxml is no longer supported and will be removed in a future release.:DeprecationWarning", "ignore:SelectableGroups dict interface is deprecated. Use select.:DeprecationWarning", diff --git a/website/developer-docs/blueprints/v1/tags.md b/website/developer-docs/blueprints/v1/tags.md index 79edbdca3d..045092274b 100644 --- a/website/developer-docs/blueprints/v1/tags.md +++ b/website/developer-docs/blueprints/v1/tags.md @@ -2,7 +2,11 @@ #### `!KeyOf` -Example: `policy: !KeyOf my-policy-id` +Example: + +```yaml +policy: !KeyOf my-policy-id +``` Resolves to the primary key of the model instance defined by id _my-policy-id_. @@ -10,7 +14,11 @@ If no matching entry can be found, an error is raised and the blueprint is inval #### `!Env` -Example: `password: !Env my_env_var` +Example: + +```yaml +password: !Env my_env_var +``` Returns the value of the given environment variable. Can be used as a scalar with `!Env my_env_var, default` to return a default value. @@ -18,16 +26,16 @@ Returns the value of the given environment variable. Can be used as a scalar wit Examples: -`configure_flow: !Find [authentik_flows.flow, [slug, default-password-change]]` - +```yaml +configure_flow: !Find [authentik_flows.flow, [slug, default-password-change]] ``` -configure_flow: !Find [ - authentik_flows.flow, - [ - !Context property_name, - !Context property_value - ] -] + +```yaml +configure_flow: + !Find [ + authentik_flows.flow, + [!Context property_name, !Context property_value], + ] ``` Looks up any model and resolves to the the matches' primary key. @@ -35,13 +43,21 @@ First argument is the model to be queried, remaining arguments are expected to b #### `!Context` -Example: `configure_flow: !Context foo` +Example: + +```yaml +configure_flow: !Context foo +``` Find values from the context. Can optionally be called with a default like `!Context [foo, default-value]`. #### `!Format` -Example: `name: !Format [my-policy-%s, !Context instance_name]` +Example: + +```yaml +name: !Format [my-policy-%s, !Context instance_name] +``` Format a string using python's % formatting. First argument is the format string, any remaining arguments are used for formatting. @@ -63,26 +79,25 @@ required: !If [true, true, false] Full example: -``` +```yaml attributes: !If [ - !Condition [...], # Or any valid YAML or custom tag. Evaluated as boolean in Python - { # When condition evaluates to true - dictionary: + !Condition [...], # Or any valid YAML or custom tag. Evaluated as boolean in Python { - with: - { - keys: "and_values" - }, - and_nested_custom_tags: !Format ["foo-%s", !Context foo] - } - }, - [ # When condition evaluates to false - list, - with, - items, - !Format ["foo-%s", !Context foo] + # When condition evaluates to true + dictionary: + { + with: { keys: "and_values" }, + and_nested_custom_tags: !Format ["foo-%s", !Context foo], + }, + }, + [ + # When condition evaluates to false + list, + with, + items, + !Format ["foo-%s", !Context foo], + ], ] -] ``` Conditionally add YAML to a blueprint. @@ -95,11 +110,13 @@ The second argument is used when the condition is `true`, and the third - when ` Minimal example: -`required: !Condition [OR, true]` +```yaml +required: !Condition [OR, true] +``` Full example: -``` +```yaml required: !Condition [ AND, # Valid modes are: AND, NAND, OR, NOR, XOR, XNOR !Context instance_name, @@ -124,7 +141,7 @@ These tags collectively make it possible to iterate over objects which support i This tag takes 3 arguments: -``` +```yaml !Enumerate [, , ] ``` @@ -140,7 +157,7 @@ This tag is only valid inside an `!Enumerate` tag This tag takes 1 argument: -``` +```yaml !Index ``` @@ -158,7 +175,7 @@ This tag is only valid inside an `!Enumerate` tag This tag takes 1 argument: -``` +```yaml !Value ``` @@ -170,41 +187,54 @@ For example, given a sequence like this - `["a", "b", "c"]`, this tag will resol Minimal examples: -``` +```yaml configuration_stages: !Enumerate [ - !Context map_of_totp_stage_names_and_types, - SEQ, # Output a sequence - !Find [!Format ["authentik_stages_authenticator_%s.authenticator%sstage", !Index 0, !Index 0], [name, !Value 0]] # The value of each item in the sequence -] + !Context map_of_totp_stage_names_and_types, + SEQ, # Output a sequence + !Find [ + !Format [ + "authentik_stages_authenticator_%s.authenticator%sstage", + !Index 0, + !Index 0, + ], + [name, !Value 0], + ], # The value of each item in the sequence + ] ``` The above example will resolve to something like this: -``` +```yaml configuration_stages: -- !Find [authentik_stages_authenticator_.authenticatorstage, [name, ]] -- !Find [authentik_stages_authenticator_.authenticatorstage, [name, ]] + - !Find [ + authentik_stages_authenticator_.authenticatorstage, + [name, ], + ] + - !Find [ + authentik_stages_authenticator_.authenticatorstage, + [name, ], + ] ``` Similarly, a mapping can be generated like so: -``` +```yaml example: !Enumerate [ - !Context list_of_totp_stage_names, - MAP, # Output a map - [ - !Index 0, # The key to assign to each entry - !Value 0, # The value to assign to each entry + !Context list_of_totp_stage_names, + MAP, # Output a map + [ + !Index 0, # The key to assign to each entry + !Value 0, # The value to assign to each entry + ], ] -] ``` The above example will resolve to something like this: -``` +```yaml example: - 0: - 1: + 0: + 1: ``` Full example: @@ -213,32 +243,38 @@ Full example: Note that an `!Enumeration` tag's iterable can never be an `!Item` or `!Value` tag with a depth of `0`. Minimum depth allowed is `1`. This is because a depth of `0` refers to the `!Enumeration` tag the `!Item` or `!Value` tag is in, and an `!Enumeration` tag cannot iterate over itself. ::: -``` +```yaml example: !Enumerate [ - !Context sequence, # ["foo", "bar"] - MAP, # Output a map - [ - !Index 0, # Use the indexes of the items in the sequence as keys - !Enumerate [ # Nested enumeration - # Iterate over each item of the parent enumerate tag. - # Notice depth is 1, not 0, since we are inside the nested enumeration tag! - !Value 1, - SEQ, # Output a sequence - !Format ["%s: (index: %d, letter: %s)", !Value 1, !Index 0, !Value 0] - ] + !Context sequence, # ["foo", "bar"] + MAP, # Output a map + [ + !Index 0, # Use the indexes of the items in the sequence as keys + !Enumerate [ + # Nested enumeration + # Iterate over each item of the parent enumerate tag. + # Notice depth is 1, not 0, since we are inside the nested enumeration tag! + !Value 1, + SEQ, # Output a sequence + !Format [ + "%s: (index: %d, letter: %s)", + !Value 1, + !Index 0, + !Value 0, + ], + ], + ], ] -] ``` The above example will resolve to something like this: -``` -'0': -- 'foo: (index: 0, letter: f)' -- 'foo: (index: 1, letter: o)' -- 'foo: (index: 2, letter: o)' -'1': -- 'bar: (index: 0, letter: b)' -- 'bar: (index: 1, letter: a)' -- 'bar: (index: 2, letter: r)' +```yaml +"0": + - "foo: (index: 0, letter: f)" + - "foo: (index: 1, letter: o)" + - "foo: (index: 2, letter: o)" +"1": + - "bar: (index: 0, letter: b)" + - "bar: (index: 1, letter: a)" + - "bar: (index: 2, letter: r)" ```