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 <jens@goauthentik.io>

* format

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix invalid blueprint

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix flaky test, improve pytest output

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* format

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L
2024-04-12 14:59:48 +02:00
committed by GitHub
parent e03c25a600
commit 7ef14eb86d
6 changed files with 140 additions and 80 deletions

View File

@ -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()}"]

View File

@ -4,6 +4,7 @@ import os
from argparse import ArgumentParser from argparse import ArgumentParser
from unittest import TestCase from unittest import TestCase
import pytest
from django.conf import settings from django.conf import settings
from django.test.runner import DiscoverRunner from django.test.runner import DiscoverRunner
@ -105,6 +106,4 @@ class PytestTestRunner(DiscoverRunner): # pragma: no cover
f"path instead." f"path instead."
) )
import pytest
return pytest.main(self.args) return pytest.main(self.args)

View File

@ -133,13 +133,14 @@ class TestUserLoginStage(FlowTestCase):
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
data={"remember_me": True}, data={"remember_me": True},
) )
_now = now().timestamp()
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect")) self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
self.assertNotEqual(list(self.client.session.keys()), []) self.assertNotEqual(list(self.client.session.keys()), [])
session_key = self.client.session.session_key session_key = self.client.session.session_key
session = AuthenticatedSession.objects.filter(session_key=session_key).first() session = AuthenticatedSession.objects.filter(session_key=session_key).first()
self.assertAlmostEqual( 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.session_duration).total_seconds()
+ timedelta_from_string(self.stage.remember_me_offset).total_seconds(), + timedelta_from_string(self.stage.remember_me_offset).total_seconds(),
delta=1, delta=1,

View File

@ -26,6 +26,9 @@ entries:
!Find [authentik_flows.flow, [slug, default-user-settings-flow]] !Find [authentik_flows.flow, [slug, default-user-settings-flow]]
identifiers: identifiers:
domain: authentik-default domain: authentik-default
default: True default: true
state: created 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 model: authentik_brands.brand

View File

@ -76,7 +76,7 @@ show_missing = true
DJANGO_SETTINGS_MODULE = "authentik.root.settings" DJANGO_SETTINGS_MODULE = "authentik.root.settings"
python_files = ["tests.py", "test_*.py", "*_tests.py"] python_files = ["tests.py", "test_*.py", "*_tests.py"]
junit_family = "xunit2" 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 = [ filterwarnings = [
"ignore:defusedxml.lxml is no longer supported and will be removed in a future release.:DeprecationWarning", "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", "ignore:SelectableGroups dict interface is deprecated. Use select.:DeprecationWarning",

View File

@ -2,7 +2,11 @@
#### `!KeyOf` #### `!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_. 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` #### `!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. 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: 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 [
```yaml
configure_flow:
!Find [
authentik_flows.flow, authentik_flows.flow,
[ [!Context property_name, !Context property_value],
!Context property_name,
!Context property_value
] ]
]
``` ```
Looks up any model and resolves to the the matches' primary key. 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` #### `!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]`. Find values from the context. Can optionally be called with a default like `!Context [foo, default-value]`.
#### `!Format` #### `!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. 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: Full example:
``` ```yaml
attributes: !If [ attributes: !If [
!Condition [...], # Or any valid YAML or custom tag. Evaluated as boolean in Python !Condition [...], # Or any valid YAML or custom tag. Evaluated as boolean in Python
{ # When condition evaluates to true {
# When condition evaluates to true
dictionary: dictionary:
{ {
with: with: { keys: "and_values" },
{ and_nested_custom_tags: !Format ["foo-%s", !Context foo],
keys: "and_values"
}, },
and_nested_custom_tags: !Format ["foo-%s", !Context foo]
}
}, },
[ # When condition evaluates to false [
# When condition evaluates to false
list, list,
with, with,
items, items,
!Format ["foo-%s", !Context foo] !Format ["foo-%s", !Context foo],
],
] ]
]
``` ```
Conditionally add YAML to a blueprint. 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: Minimal example:
`required: !Condition [OR, true]` ```yaml
required: !Condition [OR, true]
```
Full example: Full example:
``` ```yaml
required: !Condition [ required: !Condition [
AND, # Valid modes are: AND, NAND, OR, NOR, XOR, XNOR AND, # Valid modes are: AND, NAND, OR, NOR, XOR, XNOR
!Context instance_name, !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: This tag takes 3 arguments:
``` ```yaml
!Enumerate [<iterable>, <output_object_type>, <single_item_yaml>] !Enumerate [<iterable>, <output_object_type>, <single_item_yaml>]
``` ```
@ -140,7 +157,7 @@ This tag is only valid inside an `!Enumerate` tag
This tag takes 1 argument: This tag takes 1 argument:
``` ```yaml
!Index <depth> !Index <depth>
``` ```
@ -158,7 +175,7 @@ This tag is only valid inside an `!Enumerate` tag
This tag takes 1 argument: This tag takes 1 argument:
``` ```yaml
!Value <depth> !Value <depth>
``` ```
@ -170,38 +187,51 @@ For example, given a sequence like this - `["a", "b", "c"]`, this tag will resol
Minimal examples: Minimal examples:
``` ```yaml
configuration_stages: !Enumerate [ configuration_stages: !Enumerate [
!Context map_of_totp_stage_names_and_types, !Context map_of_totp_stage_names_and_types,
SEQ, # Output a sequence 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 !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: The above example will resolve to something like this:
``` ```yaml
configuration_stages: configuration_stages:
- !Find [authentik_stages_authenticator_<stage_type_1>.authenticator<stage_type_1>stage, [name, <stage_name_1>]] - !Find [
- !Find [authentik_stages_authenticator_<stage_type_2>.authenticator<stage_type_2>stage, [name, <stage_name_2>]] authentik_stages_authenticator_<stage_type_1>.authenticator<stage_type_1>stage,
[name, <stage_name_1>],
]
- !Find [
authentik_stages_authenticator_<stage_type_2>.authenticator<stage_type_2>stage,
[name, <stage_name_2>],
]
``` ```
Similarly, a mapping can be generated like so: Similarly, a mapping can be generated like so:
``` ```yaml
example: !Enumerate [ example: !Enumerate [
!Context list_of_totp_stage_names, !Context list_of_totp_stage_names,
MAP, # Output a map MAP, # Output a map
[ [
!Index 0, # The key to assign to each entry !Index 0, # The key to assign to each entry
!Value 0, # The value to assign to each entry !Value 0, # The value to assign to each entry
],
] ]
]
``` ```
The above example will resolve to something like this: The above example will resolve to something like this:
``` ```yaml
example: example:
0: <stage_name_1> 0: <stage_name_1>
1: <stage_name_2> 1: <stage_name_2>
@ -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. 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 [ example: !Enumerate [
!Context sequence, # ["foo", "bar"] !Context sequence, # ["foo", "bar"]
MAP, # Output a map MAP, # Output a map
[ [
!Index 0, # Use the indexes of the items in the sequence as keys !Index 0, # Use the indexes of the items in the sequence as keys
!Enumerate [ # Nested enumeration !Enumerate [
# Nested enumeration
# Iterate over each item of the parent enumerate tag. # Iterate over each item of the parent enumerate tag.
# Notice depth is 1, not 0, since we are inside the nested enumeration tag! # Notice depth is 1, not 0, since we are inside the nested enumeration tag!
!Value 1, !Value 1,
SEQ, # Output a sequence SEQ, # Output a sequence
!Format ["%s: (index: %d, letter: %s)", !Value 1, !Index 0, !Value 0] !Format [
"%s: (index: %d, letter: %s)",
!Value 1,
!Index 0,
!Value 0,
],
],
],
] ]
]
]
``` ```
The above example will resolve to something like this: The above example will resolve to something like this:
``` ```yaml
'0': "0":
- 'foo: (index: 0, letter: f)' - "foo: (index: 0, letter: f)"
- 'foo: (index: 1, letter: o)' - "foo: (index: 1, letter: o)"
- 'foo: (index: 2, letter: o)' - "foo: (index: 2, letter: o)"
'1': "1":
- 'bar: (index: 0, letter: b)' - "bar: (index: 0, letter: b)"
- 'bar: (index: 1, letter: a)' - "bar: (index: 1, letter: a)"
- 'bar: (index: 2, letter: r)' - "bar: (index: 2, letter: r)"
``` ```