authentik.blueprints.tests.test_v1

Test blueprints v1

  1"""Test blueprints v1"""
  2
  3from os import chmod, environ, unlink, write
  4from tempfile import mkstemp
  5
  6from django.test import TransactionTestCase
  7
  8from authentik.blueprints.tests import apply_blueprint
  9from authentik.blueprints.v1.exporter import FlowExporter
 10from authentik.blueprints.v1.importer import Importer, transaction_rollback
 11from authentik.core.models import Group
 12from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
 13from authentik.lib.generators import generate_id
 14from authentik.lib.tests.utils import load_fixture
 15from authentik.policies.expression.models import ExpressionPolicy
 16from authentik.policies.models import PolicyBinding
 17from authentik.sources.oauth.models import OAuthSource
 18from authentik.stages.prompt.models import FieldTypes, Prompt, PromptStage
 19from authentik.stages.user_login.models import UserLoginStage
 20
 21
 22class TestBlueprintsV1(TransactionTestCase):
 23    """Test Blueprints"""
 24
 25    def test_blueprint_invalid_format(self):
 26        """Test blueprint with invalid format"""
 27        importer = Importer.from_string('{"version": 3}')
 28        self.assertFalse(importer.validate()[0])
 29        importer = Importer.from_string(
 30            '{"version": 1,"entries":[{"identifiers":{},"attrs":{},'
 31            '"model": "authentik_core.User"}]}'
 32        )
 33        self.assertFalse(importer.validate()[0])
 34        importer = Importer.from_string(
 35            '{"version": 1, "entries": [{"attrs": {"name": "test"}, '
 36            '"identifiers": {}, '
 37            '"model": "authentik_core.Group"}]}'
 38        )
 39        self.assertFalse(importer.validate()[0])
 40
 41    def test_validated_import_dict_identifiers(self):
 42        """Test importing blueprints with dict identifiers."""
 43        Group.objects.filter(name__istartswith="test").delete()
 44
 45        Group.objects.create(
 46            name="test1",
 47            attributes={
 48                "key": ["value"],
 49                "other_key": ["a_value", "other_value"],
 50            },
 51        )
 52        Group.objects.create(
 53            name="test2",
 54            attributes={
 55                "key": ["value"],
 56                "other_key": ["diff_value", "other_diff_value"],
 57            },
 58        )
 59
 60        importer = Importer.from_string(
 61            '{"version": 1, "entries": [{"attrs": {"name": "test999", "attributes": '
 62            '{"key": ["updated_value"]}}, "identifiers": {"attributes": {"other_key": '
 63            '["other_value"]}}, "model": "authentik_core.Group"}]}'
 64        )
 65        self.assertTrue(importer.validate()[0])
 66        self.assertTrue(importer.apply())
 67        self.assertTrue(
 68            Group.objects.filter(
 69                name="test2",
 70                attributes={
 71                    "key": ["value"],
 72                    "other_key": ["diff_value", "other_diff_value"],
 73                },
 74            )
 75        )
 76        self.assertTrue(
 77            Group.objects.filter(
 78                name="test999",
 79                # All attributes used as identifiers are kept and merged with the
 80                # new attributes declared in the blueprint
 81                attributes={"key": ["updated_value"], "other_key": ["other_value"]},
 82            )
 83        )
 84        self.assertFalse(Group.objects.filter(name="test1"))
 85
 86    def test_export_validate_import(self):
 87        """Test export and validate it"""
 88        flow_slug = generate_id()
 89        with transaction_rollback():
 90            login_stage = UserLoginStage.objects.create(name=generate_id())
 91
 92            flow = Flow.objects.create(
 93                slug=flow_slug,
 94                designation=FlowDesignation.AUTHENTICATION,
 95                name=generate_id(),
 96                title=generate_id(),
 97            )
 98            FlowStageBinding.objects.update_or_create(
 99                target=flow,
100                stage=login_stage,
101                order=0,
102            )
103
104            exporter = FlowExporter(flow)
105            export = exporter.export()
106            self.assertEqual(len(export.entries), 3)
107            export_yaml = exporter.export_to_string()
108
109        importer = Importer.from_string(export_yaml)
110        self.assertTrue(importer.validate()[0])
111        self.assertTrue(importer.apply())
112
113        self.assertTrue(Flow.objects.filter(slug=flow_slug).exists())
114
115    def test_export_validate_import_re_import(self):
116        """Test export and import it twice"""
117        count_initial = Prompt.objects.filter(field_key="username").count()
118
119        importer = Importer.from_string(load_fixture("fixtures/static_prompt_export.yaml"))
120        self.assertTrue(importer.validate()[0])
121        self.assertTrue(importer.apply())
122
123        count_before = Prompt.objects.filter(field_key="username").count()
124        self.assertEqual(count_initial + 1, count_before)
125
126        importer = Importer.from_string(load_fixture("fixtures/static_prompt_export.yaml"))
127        self.assertTrue(importer.apply())
128
129        self.assertEqual(Prompt.objects.filter(field_key="username").count(), count_before)
130
131    @apply_blueprint("system/providers-oauth2.yaml")
132    def test_import_yaml_tags(self):
133        """Test some yaml tags"""
134        ExpressionPolicy.objects.filter(name="foo-bar-baz-qux").delete()
135        Group.objects.filter(name="test").delete()
136        environ["foo"] = generate_id()
137        file, file_name = mkstemp()
138        write(file, b"foo")
139        _, file_default_name = mkstemp()
140        chmod(file_default_name, 0o000)  # Remove all permissions so we can't read the file
141        importer = Importer.from_string(
142            load_fixture(
143                "fixtures/tags.yaml",
144                file_name=file_name,
145                file_default_name=file_default_name,
146            ),
147            {"bar": "baz"},
148        )
149        self.assertTrue(importer.validate()[0])
150        self.assertTrue(importer.apply())
151        policy = ExpressionPolicy.objects.filter(name="foo-bar-baz-qux").first()
152        self.assertTrue(policy)
153        group = Group.objects.filter(name="test").first()
154        self.assertIsNotNone(group)
155        self.assertEqual(
156            group.attributes,
157            {
158                "policy_pk1": str(policy.pk) + "-suffix",
159                "policy_pk2": str(policy.pk) + "-suffix",
160                "boolAnd": True,
161                "boolNand": False,
162                "boolOr": True,
163                "boolNor": False,
164                "boolXor": True,
165                "boolXnor": False,
166                "boolComplex": True,
167                "if_true_complex": {
168                    "dictionary": {
169                        "with": {"keys": "and_values"},
170                        "and_nested_custom_tags": "foo-bar",
171                    }
172                },
173                "if_false_complex": ["list", "with", "items", "foo-bar"],
174                "if_true_simple": True,
175                "if_short": True,
176                "if_false_simple": 2,
177                "enumerate_mapping_to_mapping": {
178                    "prefix-key1": "other-prefix-value",
179                    "prefix-key2": "other-prefix-2",
180                },
181                "enumerate_mapping_to_sequence": [
182                    "prefixed-pair-key1-value",
183                    "prefixed-pair-key2-2",
184                ],
185                "enumerate_sequence_to_sequence": [
186                    "prefixed-items-0-foo",
187                    "prefixed-items-1-bar",
188                ],
189                "enumerate_sequence_to_mapping": {"index: 0": "foo", "index: 1": "bar"},
190                "nested_complex_enumeration": {
191                    "0": {
192                        "key1": [
193                            ["prefixed-f", "prefixed-o", "prefixed-o"],
194                            {
195                                "outer_value": "foo",
196                                "outer_index": 0,
197                                "middle_value": "value",
198                                "middle_index": "key1",
199                            },
200                        ],
201                        "key2": [
202                            ["prefixed-f", "prefixed-o", "prefixed-o"],
203                            {
204                                "outer_value": "foo",
205                                "outer_index": 0,
206                                "middle_value": 2,
207                                "middle_index": "key2",
208                            },
209                        ],
210                    },
211                    "1": {
212                        "key1": [
213                            ["prefixed-b", "prefixed-a", "prefixed-r"],
214                            {
215                                "outer_value": "bar",
216                                "outer_index": 1,
217                                "middle_value": "value",
218                                "middle_index": "key1",
219                            },
220                        ],
221                        "key2": [
222                            ["prefixed-b", "prefixed-a", "prefixed-r"],
223                            {
224                                "outer_value": "bar",
225                                "outer_index": 1,
226                                "middle_value": 2,
227                                "middle_index": "key2",
228                            },
229                        ],
230                    },
231                },
232                "nested_context": "context-nested-value",
233                "env_null": None,
234                "file_content": "foo",
235                "file_default": "default",
236                "file_non_existent": None,
237                "json_parse": {"foo": "bar"},
238                "at_index_sequence": "foo",
239                "at_index_sequence_default": "non existent",
240                "at_index_mapping": 2,
241                "at_index_mapping_default": "non existent",
242                "find_object": "goauthentik.io/providers/oauth2/scope-openid",
243            },
244        )
245        self.assertTrue(
246            OAuthSource.objects.filter(
247                slug="test",
248                consumer_key=environ["foo"],
249            )
250        )
251        unlink(file_name)
252        unlink(file_default_name)
253
254    def test_export_validate_import_policies(self):
255        """Test export and validate it"""
256        flow_slug = generate_id()
257        stage_name = generate_id()
258        with transaction_rollback():
259            flow_policy = ExpressionPolicy.objects.create(
260                name=generate_id(),
261                expression="return True",
262            )
263            flow = Flow.objects.create(
264                slug=flow_slug,
265                designation=FlowDesignation.AUTHENTICATION,
266                name=generate_id(),
267                title=generate_id(),
268            )
269            PolicyBinding.objects.create(policy=flow_policy, target=flow, order=0)
270
271            user_login = UserLoginStage.objects.create(name=stage_name)
272            fsb = FlowStageBinding.objects.create(target=flow, stage=user_login, order=0)
273            PolicyBinding.objects.create(policy=flow_policy, target=fsb, order=0)
274
275            exporter = FlowExporter(flow)
276            export_yaml = exporter.export_to_string()
277
278        importer = Importer.from_string(export_yaml)
279        self.assertTrue(importer.validate()[0])
280        self.assertTrue(importer.apply())
281        self.assertTrue(UserLoginStage.objects.filter(name=stage_name).exists())
282        self.assertTrue(Flow.objects.filter(slug=flow_slug).exists())
283
284    def test_export_validate_import_prompt(self):
285        """Test export and validate it"""
286        with transaction_rollback():
287            # First stage fields
288            username_prompt = Prompt.objects.create(
289                name=generate_id(),
290                field_key="username",
291                label="Username",
292                order=0,
293                type=FieldTypes.TEXT,
294            )
295            password = Prompt.objects.create(
296                name=generate_id(),
297                field_key="password",
298                label="Password",
299                order=1,
300                type=FieldTypes.PASSWORD,
301            )
302            password_repeat = Prompt.objects.create(
303                name=generate_id(),
304                field_key="password_repeat",
305                label="Password (repeat)",
306                order=2,
307                type=FieldTypes.PASSWORD,
308            )
309
310            # Stages
311            first_stage = PromptStage.objects.create(name=generate_id())
312            first_stage.fields.set([username_prompt, password, password_repeat])
313            first_stage.save()
314
315            flow = Flow.objects.create(
316                name=generate_id(),
317                slug=generate_id(),
318                designation=FlowDesignation.ENROLLMENT,
319                title=generate_id(),
320            )
321
322            FlowStageBinding.objects.create(target=flow, stage=first_stage, order=0)
323
324            exporter = FlowExporter(flow)
325            export_yaml = exporter.export_to_string()
326
327        importer = Importer.from_string(export_yaml)
328
329        self.assertTrue(importer.validate()[0])
330        self.assertTrue(importer.apply())
class TestBlueprintsV1(django.test.testcases.TransactionTestCase):
 23class TestBlueprintsV1(TransactionTestCase):
 24    """Test Blueprints"""
 25
 26    def test_blueprint_invalid_format(self):
 27        """Test blueprint with invalid format"""
 28        importer = Importer.from_string('{"version": 3}')
 29        self.assertFalse(importer.validate()[0])
 30        importer = Importer.from_string(
 31            '{"version": 1,"entries":[{"identifiers":{},"attrs":{},'
 32            '"model": "authentik_core.User"}]}'
 33        )
 34        self.assertFalse(importer.validate()[0])
 35        importer = Importer.from_string(
 36            '{"version": 1, "entries": [{"attrs": {"name": "test"}, '
 37            '"identifiers": {}, '
 38            '"model": "authentik_core.Group"}]}'
 39        )
 40        self.assertFalse(importer.validate()[0])
 41
 42    def test_validated_import_dict_identifiers(self):
 43        """Test importing blueprints with dict identifiers."""
 44        Group.objects.filter(name__istartswith="test").delete()
 45
 46        Group.objects.create(
 47            name="test1",
 48            attributes={
 49                "key": ["value"],
 50                "other_key": ["a_value", "other_value"],
 51            },
 52        )
 53        Group.objects.create(
 54            name="test2",
 55            attributes={
 56                "key": ["value"],
 57                "other_key": ["diff_value", "other_diff_value"],
 58            },
 59        )
 60
 61        importer = Importer.from_string(
 62            '{"version": 1, "entries": [{"attrs": {"name": "test999", "attributes": '
 63            '{"key": ["updated_value"]}}, "identifiers": {"attributes": {"other_key": '
 64            '["other_value"]}}, "model": "authentik_core.Group"}]}'
 65        )
 66        self.assertTrue(importer.validate()[0])
 67        self.assertTrue(importer.apply())
 68        self.assertTrue(
 69            Group.objects.filter(
 70                name="test2",
 71                attributes={
 72                    "key": ["value"],
 73                    "other_key": ["diff_value", "other_diff_value"],
 74                },
 75            )
 76        )
 77        self.assertTrue(
 78            Group.objects.filter(
 79                name="test999",
 80                # All attributes used as identifiers are kept and merged with the
 81                # new attributes declared in the blueprint
 82                attributes={"key": ["updated_value"], "other_key": ["other_value"]},
 83            )
 84        )
 85        self.assertFalse(Group.objects.filter(name="test1"))
 86
 87    def test_export_validate_import(self):
 88        """Test export and validate it"""
 89        flow_slug = generate_id()
 90        with transaction_rollback():
 91            login_stage = UserLoginStage.objects.create(name=generate_id())
 92
 93            flow = Flow.objects.create(
 94                slug=flow_slug,
 95                designation=FlowDesignation.AUTHENTICATION,
 96                name=generate_id(),
 97                title=generate_id(),
 98            )
 99            FlowStageBinding.objects.update_or_create(
100                target=flow,
101                stage=login_stage,
102                order=0,
103            )
104
105            exporter = FlowExporter(flow)
106            export = exporter.export()
107            self.assertEqual(len(export.entries), 3)
108            export_yaml = exporter.export_to_string()
109
110        importer = Importer.from_string(export_yaml)
111        self.assertTrue(importer.validate()[0])
112        self.assertTrue(importer.apply())
113
114        self.assertTrue(Flow.objects.filter(slug=flow_slug).exists())
115
116    def test_export_validate_import_re_import(self):
117        """Test export and import it twice"""
118        count_initial = Prompt.objects.filter(field_key="username").count()
119
120        importer = Importer.from_string(load_fixture("fixtures/static_prompt_export.yaml"))
121        self.assertTrue(importer.validate()[0])
122        self.assertTrue(importer.apply())
123
124        count_before = Prompt.objects.filter(field_key="username").count()
125        self.assertEqual(count_initial + 1, count_before)
126
127        importer = Importer.from_string(load_fixture("fixtures/static_prompt_export.yaml"))
128        self.assertTrue(importer.apply())
129
130        self.assertEqual(Prompt.objects.filter(field_key="username").count(), count_before)
131
132    @apply_blueprint("system/providers-oauth2.yaml")
133    def test_import_yaml_tags(self):
134        """Test some yaml tags"""
135        ExpressionPolicy.objects.filter(name="foo-bar-baz-qux").delete()
136        Group.objects.filter(name="test").delete()
137        environ["foo"] = generate_id()
138        file, file_name = mkstemp()
139        write(file, b"foo")
140        _, file_default_name = mkstemp()
141        chmod(file_default_name, 0o000)  # Remove all permissions so we can't read the file
142        importer = Importer.from_string(
143            load_fixture(
144                "fixtures/tags.yaml",
145                file_name=file_name,
146                file_default_name=file_default_name,
147            ),
148            {"bar": "baz"},
149        )
150        self.assertTrue(importer.validate()[0])
151        self.assertTrue(importer.apply())
152        policy = ExpressionPolicy.objects.filter(name="foo-bar-baz-qux").first()
153        self.assertTrue(policy)
154        group = Group.objects.filter(name="test").first()
155        self.assertIsNotNone(group)
156        self.assertEqual(
157            group.attributes,
158            {
159                "policy_pk1": str(policy.pk) + "-suffix",
160                "policy_pk2": str(policy.pk) + "-suffix",
161                "boolAnd": True,
162                "boolNand": False,
163                "boolOr": True,
164                "boolNor": False,
165                "boolXor": True,
166                "boolXnor": False,
167                "boolComplex": True,
168                "if_true_complex": {
169                    "dictionary": {
170                        "with": {"keys": "and_values"},
171                        "and_nested_custom_tags": "foo-bar",
172                    }
173                },
174                "if_false_complex": ["list", "with", "items", "foo-bar"],
175                "if_true_simple": True,
176                "if_short": True,
177                "if_false_simple": 2,
178                "enumerate_mapping_to_mapping": {
179                    "prefix-key1": "other-prefix-value",
180                    "prefix-key2": "other-prefix-2",
181                },
182                "enumerate_mapping_to_sequence": [
183                    "prefixed-pair-key1-value",
184                    "prefixed-pair-key2-2",
185                ],
186                "enumerate_sequence_to_sequence": [
187                    "prefixed-items-0-foo",
188                    "prefixed-items-1-bar",
189                ],
190                "enumerate_sequence_to_mapping": {"index: 0": "foo", "index: 1": "bar"},
191                "nested_complex_enumeration": {
192                    "0": {
193                        "key1": [
194                            ["prefixed-f", "prefixed-o", "prefixed-o"],
195                            {
196                                "outer_value": "foo",
197                                "outer_index": 0,
198                                "middle_value": "value",
199                                "middle_index": "key1",
200                            },
201                        ],
202                        "key2": [
203                            ["prefixed-f", "prefixed-o", "prefixed-o"],
204                            {
205                                "outer_value": "foo",
206                                "outer_index": 0,
207                                "middle_value": 2,
208                                "middle_index": "key2",
209                            },
210                        ],
211                    },
212                    "1": {
213                        "key1": [
214                            ["prefixed-b", "prefixed-a", "prefixed-r"],
215                            {
216                                "outer_value": "bar",
217                                "outer_index": 1,
218                                "middle_value": "value",
219                                "middle_index": "key1",
220                            },
221                        ],
222                        "key2": [
223                            ["prefixed-b", "prefixed-a", "prefixed-r"],
224                            {
225                                "outer_value": "bar",
226                                "outer_index": 1,
227                                "middle_value": 2,
228                                "middle_index": "key2",
229                            },
230                        ],
231                    },
232                },
233                "nested_context": "context-nested-value",
234                "env_null": None,
235                "file_content": "foo",
236                "file_default": "default",
237                "file_non_existent": None,
238                "json_parse": {"foo": "bar"},
239                "at_index_sequence": "foo",
240                "at_index_sequence_default": "non existent",
241                "at_index_mapping": 2,
242                "at_index_mapping_default": "non existent",
243                "find_object": "goauthentik.io/providers/oauth2/scope-openid",
244            },
245        )
246        self.assertTrue(
247            OAuthSource.objects.filter(
248                slug="test",
249                consumer_key=environ["foo"],
250            )
251        )
252        unlink(file_name)
253        unlink(file_default_name)
254
255    def test_export_validate_import_policies(self):
256        """Test export and validate it"""
257        flow_slug = generate_id()
258        stage_name = generate_id()
259        with transaction_rollback():
260            flow_policy = ExpressionPolicy.objects.create(
261                name=generate_id(),
262                expression="return True",
263            )
264            flow = Flow.objects.create(
265                slug=flow_slug,
266                designation=FlowDesignation.AUTHENTICATION,
267                name=generate_id(),
268                title=generate_id(),
269            )
270            PolicyBinding.objects.create(policy=flow_policy, target=flow, order=0)
271
272            user_login = UserLoginStage.objects.create(name=stage_name)
273            fsb = FlowStageBinding.objects.create(target=flow, stage=user_login, order=0)
274            PolicyBinding.objects.create(policy=flow_policy, target=fsb, order=0)
275
276            exporter = FlowExporter(flow)
277            export_yaml = exporter.export_to_string()
278
279        importer = Importer.from_string(export_yaml)
280        self.assertTrue(importer.validate()[0])
281        self.assertTrue(importer.apply())
282        self.assertTrue(UserLoginStage.objects.filter(name=stage_name).exists())
283        self.assertTrue(Flow.objects.filter(slug=flow_slug).exists())
284
285    def test_export_validate_import_prompt(self):
286        """Test export and validate it"""
287        with transaction_rollback():
288            # First stage fields
289            username_prompt = Prompt.objects.create(
290                name=generate_id(),
291                field_key="username",
292                label="Username",
293                order=0,
294                type=FieldTypes.TEXT,
295            )
296            password = Prompt.objects.create(
297                name=generate_id(),
298                field_key="password",
299                label="Password",
300                order=1,
301                type=FieldTypes.PASSWORD,
302            )
303            password_repeat = Prompt.objects.create(
304                name=generate_id(),
305                field_key="password_repeat",
306                label="Password (repeat)",
307                order=2,
308                type=FieldTypes.PASSWORD,
309            )
310
311            # Stages
312            first_stage = PromptStage.objects.create(name=generate_id())
313            first_stage.fields.set([username_prompt, password, password_repeat])
314            first_stage.save()
315
316            flow = Flow.objects.create(
317                name=generate_id(),
318                slug=generate_id(),
319                designation=FlowDesignation.ENROLLMENT,
320                title=generate_id(),
321            )
322
323            FlowStageBinding.objects.create(target=flow, stage=first_stage, order=0)
324
325            exporter = FlowExporter(flow)
326            export_yaml = exporter.export_to_string()
327
328        importer = Importer.from_string(export_yaml)
329
330        self.assertTrue(importer.validate()[0])
331        self.assertTrue(importer.apply())

Test Blueprints

def test_blueprint_invalid_format(self):
26    def test_blueprint_invalid_format(self):
27        """Test blueprint with invalid format"""
28        importer = Importer.from_string('{"version": 3}')
29        self.assertFalse(importer.validate()[0])
30        importer = Importer.from_string(
31            '{"version": 1,"entries":[{"identifiers":{},"attrs":{},'
32            '"model": "authentik_core.User"}]}'
33        )
34        self.assertFalse(importer.validate()[0])
35        importer = Importer.from_string(
36            '{"version": 1, "entries": [{"attrs": {"name": "test"}, '
37            '"identifiers": {}, '
38            '"model": "authentik_core.Group"}]}'
39        )
40        self.assertFalse(importer.validate()[0])

Test blueprint with invalid format

def test_validated_import_dict_identifiers(self):
42    def test_validated_import_dict_identifiers(self):
43        """Test importing blueprints with dict identifiers."""
44        Group.objects.filter(name__istartswith="test").delete()
45
46        Group.objects.create(
47            name="test1",
48            attributes={
49                "key": ["value"],
50                "other_key": ["a_value", "other_value"],
51            },
52        )
53        Group.objects.create(
54            name="test2",
55            attributes={
56                "key": ["value"],
57                "other_key": ["diff_value", "other_diff_value"],
58            },
59        )
60
61        importer = Importer.from_string(
62            '{"version": 1, "entries": [{"attrs": {"name": "test999", "attributes": '
63            '{"key": ["updated_value"]}}, "identifiers": {"attributes": {"other_key": '
64            '["other_value"]}}, "model": "authentik_core.Group"}]}'
65        )
66        self.assertTrue(importer.validate()[0])
67        self.assertTrue(importer.apply())
68        self.assertTrue(
69            Group.objects.filter(
70                name="test2",
71                attributes={
72                    "key": ["value"],
73                    "other_key": ["diff_value", "other_diff_value"],
74                },
75            )
76        )
77        self.assertTrue(
78            Group.objects.filter(
79                name="test999",
80                # All attributes used as identifiers are kept and merged with the
81                # new attributes declared in the blueprint
82                attributes={"key": ["updated_value"], "other_key": ["other_value"]},
83            )
84        )
85        self.assertFalse(Group.objects.filter(name="test1"))

Test importing blueprints with dict identifiers.

def test_export_validate_import(self):
 87    def test_export_validate_import(self):
 88        """Test export and validate it"""
 89        flow_slug = generate_id()
 90        with transaction_rollback():
 91            login_stage = UserLoginStage.objects.create(name=generate_id())
 92
 93            flow = Flow.objects.create(
 94                slug=flow_slug,
 95                designation=FlowDesignation.AUTHENTICATION,
 96                name=generate_id(),
 97                title=generate_id(),
 98            )
 99            FlowStageBinding.objects.update_or_create(
100                target=flow,
101                stage=login_stage,
102                order=0,
103            )
104
105            exporter = FlowExporter(flow)
106            export = exporter.export()
107            self.assertEqual(len(export.entries), 3)
108            export_yaml = exporter.export_to_string()
109
110        importer = Importer.from_string(export_yaml)
111        self.assertTrue(importer.validate()[0])
112        self.assertTrue(importer.apply())
113
114        self.assertTrue(Flow.objects.filter(slug=flow_slug).exists())

Test export and validate it

def test_export_validate_import_re_import(self):
116    def test_export_validate_import_re_import(self):
117        """Test export and import it twice"""
118        count_initial = Prompt.objects.filter(field_key="username").count()
119
120        importer = Importer.from_string(load_fixture("fixtures/static_prompt_export.yaml"))
121        self.assertTrue(importer.validate()[0])
122        self.assertTrue(importer.apply())
123
124        count_before = Prompt.objects.filter(field_key="username").count()
125        self.assertEqual(count_initial + 1, count_before)
126
127        importer = Importer.from_string(load_fixture("fixtures/static_prompt_export.yaml"))
128        self.assertTrue(importer.apply())
129
130        self.assertEqual(Prompt.objects.filter(field_key="username").count(), count_before)

Test export and import it twice

@apply_blueprint('system/providers-oauth2.yaml')
def test_import_yaml_tags(self):
132    @apply_blueprint("system/providers-oauth2.yaml")
133    def test_import_yaml_tags(self):
134        """Test some yaml tags"""
135        ExpressionPolicy.objects.filter(name="foo-bar-baz-qux").delete()
136        Group.objects.filter(name="test").delete()
137        environ["foo"] = generate_id()
138        file, file_name = mkstemp()
139        write(file, b"foo")
140        _, file_default_name = mkstemp()
141        chmod(file_default_name, 0o000)  # Remove all permissions so we can't read the file
142        importer = Importer.from_string(
143            load_fixture(
144                "fixtures/tags.yaml",
145                file_name=file_name,
146                file_default_name=file_default_name,
147            ),
148            {"bar": "baz"},
149        )
150        self.assertTrue(importer.validate()[0])
151        self.assertTrue(importer.apply())
152        policy = ExpressionPolicy.objects.filter(name="foo-bar-baz-qux").first()
153        self.assertTrue(policy)
154        group = Group.objects.filter(name="test").first()
155        self.assertIsNotNone(group)
156        self.assertEqual(
157            group.attributes,
158            {
159                "policy_pk1": str(policy.pk) + "-suffix",
160                "policy_pk2": str(policy.pk) + "-suffix",
161                "boolAnd": True,
162                "boolNand": False,
163                "boolOr": True,
164                "boolNor": False,
165                "boolXor": True,
166                "boolXnor": False,
167                "boolComplex": True,
168                "if_true_complex": {
169                    "dictionary": {
170                        "with": {"keys": "and_values"},
171                        "and_nested_custom_tags": "foo-bar",
172                    }
173                },
174                "if_false_complex": ["list", "with", "items", "foo-bar"],
175                "if_true_simple": True,
176                "if_short": True,
177                "if_false_simple": 2,
178                "enumerate_mapping_to_mapping": {
179                    "prefix-key1": "other-prefix-value",
180                    "prefix-key2": "other-prefix-2",
181                },
182                "enumerate_mapping_to_sequence": [
183                    "prefixed-pair-key1-value",
184                    "prefixed-pair-key2-2",
185                ],
186                "enumerate_sequence_to_sequence": [
187                    "prefixed-items-0-foo",
188                    "prefixed-items-1-bar",
189                ],
190                "enumerate_sequence_to_mapping": {"index: 0": "foo", "index: 1": "bar"},
191                "nested_complex_enumeration": {
192                    "0": {
193                        "key1": [
194                            ["prefixed-f", "prefixed-o", "prefixed-o"],
195                            {
196                                "outer_value": "foo",
197                                "outer_index": 0,
198                                "middle_value": "value",
199                                "middle_index": "key1",
200                            },
201                        ],
202                        "key2": [
203                            ["prefixed-f", "prefixed-o", "prefixed-o"],
204                            {
205                                "outer_value": "foo",
206                                "outer_index": 0,
207                                "middle_value": 2,
208                                "middle_index": "key2",
209                            },
210                        ],
211                    },
212                    "1": {
213                        "key1": [
214                            ["prefixed-b", "prefixed-a", "prefixed-r"],
215                            {
216                                "outer_value": "bar",
217                                "outer_index": 1,
218                                "middle_value": "value",
219                                "middle_index": "key1",
220                            },
221                        ],
222                        "key2": [
223                            ["prefixed-b", "prefixed-a", "prefixed-r"],
224                            {
225                                "outer_value": "bar",
226                                "outer_index": 1,
227                                "middle_value": 2,
228                                "middle_index": "key2",
229                            },
230                        ],
231                    },
232                },
233                "nested_context": "context-nested-value",
234                "env_null": None,
235                "file_content": "foo",
236                "file_default": "default",
237                "file_non_existent": None,
238                "json_parse": {"foo": "bar"},
239                "at_index_sequence": "foo",
240                "at_index_sequence_default": "non existent",
241                "at_index_mapping": 2,
242                "at_index_mapping_default": "non existent",
243                "find_object": "goauthentik.io/providers/oauth2/scope-openid",
244            },
245        )
246        self.assertTrue(
247            OAuthSource.objects.filter(
248                slug="test",
249                consumer_key=environ["foo"],
250            )
251        )
252        unlink(file_name)
253        unlink(file_default_name)

Test some yaml tags

def test_export_validate_import_policies(self):
255    def test_export_validate_import_policies(self):
256        """Test export and validate it"""
257        flow_slug = generate_id()
258        stage_name = generate_id()
259        with transaction_rollback():
260            flow_policy = ExpressionPolicy.objects.create(
261                name=generate_id(),
262                expression="return True",
263            )
264            flow = Flow.objects.create(
265                slug=flow_slug,
266                designation=FlowDesignation.AUTHENTICATION,
267                name=generate_id(),
268                title=generate_id(),
269            )
270            PolicyBinding.objects.create(policy=flow_policy, target=flow, order=0)
271
272            user_login = UserLoginStage.objects.create(name=stage_name)
273            fsb = FlowStageBinding.objects.create(target=flow, stage=user_login, order=0)
274            PolicyBinding.objects.create(policy=flow_policy, target=fsb, order=0)
275
276            exporter = FlowExporter(flow)
277            export_yaml = exporter.export_to_string()
278
279        importer = Importer.from_string(export_yaml)
280        self.assertTrue(importer.validate()[0])
281        self.assertTrue(importer.apply())
282        self.assertTrue(UserLoginStage.objects.filter(name=stage_name).exists())
283        self.assertTrue(Flow.objects.filter(slug=flow_slug).exists())

Test export and validate it

def test_export_validate_import_prompt(self):
285    def test_export_validate_import_prompt(self):
286        """Test export and validate it"""
287        with transaction_rollback():
288            # First stage fields
289            username_prompt = Prompt.objects.create(
290                name=generate_id(),
291                field_key="username",
292                label="Username",
293                order=0,
294                type=FieldTypes.TEXT,
295            )
296            password = Prompt.objects.create(
297                name=generate_id(),
298                field_key="password",
299                label="Password",
300                order=1,
301                type=FieldTypes.PASSWORD,
302            )
303            password_repeat = Prompt.objects.create(
304                name=generate_id(),
305                field_key="password_repeat",
306                label="Password (repeat)",
307                order=2,
308                type=FieldTypes.PASSWORD,
309            )
310
311            # Stages
312            first_stage = PromptStage.objects.create(name=generate_id())
313            first_stage.fields.set([username_prompt, password, password_repeat])
314            first_stage.save()
315
316            flow = Flow.objects.create(
317                name=generate_id(),
318                slug=generate_id(),
319                designation=FlowDesignation.ENROLLMENT,
320                title=generate_id(),
321            )
322
323            FlowStageBinding.objects.create(target=flow, stage=first_stage, order=0)
324
325            exporter = FlowExporter(flow)
326            export_yaml = exporter.export_to_string()
327
328        importer = Importer.from_string(export_yaml)
329
330        self.assertTrue(importer.validate()[0])
331        self.assertTrue(importer.apply())

Test export and validate it