blueprints: fix tag values not resolved correctly (#6653)
* blueprints: fix tag values not resolved correctly this lead to `null` in an `!Env` tag being returned as `"null"` Signed-off-by: Jens Langhammer <jens@goauthentik.io> * make blueprint user password optional Signed-off-by: Jens Langhammer <jens@goauthentik.io> * ensure user doesn't have a usable password set when its an empty string Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
parent
799b9c09de
commit
30cb38ac6d
|
@ -7,3 +7,4 @@ build/**
|
||||||
build_docs/**
|
build_docs/**
|
||||||
Dockerfile
|
Dockerfile
|
||||||
authentik/enterprise
|
authentik/enterprise
|
||||||
|
blueprints/local
|
||||||
|
|
|
@ -45,3 +45,8 @@ entries:
|
||||||
attrs:
|
attrs:
|
||||||
name: "%(uid)s"
|
name: "%(uid)s"
|
||||||
password: "%(uid)s"
|
password: "%(uid)s"
|
||||||
|
- model: authentik_core.user
|
||||||
|
identifiers:
|
||||||
|
username: "%(uid)s-no-password"
|
||||||
|
attrs:
|
||||||
|
name: "%(uid)s"
|
||||||
|
|
|
@ -36,6 +36,7 @@ entries:
|
||||||
model: authentik_policies_expression.expressionpolicy
|
model: authentik_policies_expression.expressionpolicy
|
||||||
- attrs:
|
- attrs:
|
||||||
attributes:
|
attributes:
|
||||||
|
env_null: !Env [bar-baz, null]
|
||||||
policy_pk1:
|
policy_pk1:
|
||||||
!Format [
|
!Format [
|
||||||
"%s-%s",
|
"%s-%s",
|
||||||
|
|
|
@ -213,8 +213,9 @@ class TestBlueprintsV1(TransactionTestCase):
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"nested_context": "context-nested-value",
|
"nested_context": "context-nested-value",
|
||||||
|
"env_null": None,
|
||||||
}
|
}
|
||||||
)
|
).exists()
|
||||||
)
|
)
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
OAuthSource.objects.filter(
|
OAuthSource.objects.filter(
|
||||||
|
|
|
@ -51,3 +51,9 @@ class TestBlueprintsV1ConditionalFields(TransactionTestCase):
|
||||||
user: User = User.objects.filter(username=self.uid).first()
|
user: User = User.objects.filter(username=self.uid).first()
|
||||||
self.assertIsNotNone(user)
|
self.assertIsNotNone(user)
|
||||||
self.assertTrue(user.check_password(self.uid))
|
self.assertTrue(user.check_password(self.uid))
|
||||||
|
|
||||||
|
def test_user_null(self):
|
||||||
|
"""Test user"""
|
||||||
|
user: User = User.objects.filter(username=f"{self.uid}-no-password").first()
|
||||||
|
self.assertIsNotNone(user)
|
||||||
|
self.assertFalse(user.has_usable_password())
|
||||||
|
|
|
@ -223,11 +223,11 @@ class Env(YAMLTag):
|
||||||
if isinstance(node, ScalarNode):
|
if isinstance(node, ScalarNode):
|
||||||
self.key = node.value
|
self.key = node.value
|
||||||
if isinstance(node, SequenceNode):
|
if isinstance(node, SequenceNode):
|
||||||
self.key = node.value[0].value
|
self.key = loader.construct_object(node.value[0])
|
||||||
self.default = node.value[1].value
|
self.default = loader.construct_object(node.value[1])
|
||||||
|
|
||||||
def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any:
|
def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any:
|
||||||
return getenv(self.key, self.default)
|
return getenv(self.key) or self.default
|
||||||
|
|
||||||
|
|
||||||
class Context(YAMLTag):
|
class Context(YAMLTag):
|
||||||
|
@ -242,8 +242,8 @@ class Context(YAMLTag):
|
||||||
if isinstance(node, ScalarNode):
|
if isinstance(node, ScalarNode):
|
||||||
self.key = node.value
|
self.key = node.value
|
||||||
if isinstance(node, SequenceNode):
|
if isinstance(node, SequenceNode):
|
||||||
self.key = node.value[0].value
|
self.key = loader.construct_object(node.value[0])
|
||||||
self.default = node.value[1].value
|
self.default = loader.construct_object(node.value[1])
|
||||||
|
|
||||||
def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any:
|
def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any:
|
||||||
value = self.default
|
value = self.default
|
||||||
|
@ -262,7 +262,7 @@ class Format(YAMLTag):
|
||||||
|
|
||||||
def __init__(self, loader: "BlueprintLoader", node: SequenceNode) -> None:
|
def __init__(self, loader: "BlueprintLoader", node: SequenceNode) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.format_string = node.value[0].value
|
self.format_string = loader.construct_object(node.value[0])
|
||||||
self.args = []
|
self.args = []
|
||||||
for raw_node in node.value[1:]:
|
for raw_node in node.value[1:]:
|
||||||
self.args.append(loader.construct_object(raw_node))
|
self.args.append(loader.construct_object(raw_node))
|
||||||
|
@ -341,7 +341,7 @@ class Condition(YAMLTag):
|
||||||
|
|
||||||
def __init__(self, loader: "BlueprintLoader", node: SequenceNode) -> None:
|
def __init__(self, loader: "BlueprintLoader", node: SequenceNode) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.mode = node.value[0].value
|
self.mode = loader.construct_object(node.value[0])
|
||||||
self.args = []
|
self.args = []
|
||||||
for raw_node in node.value[1:]:
|
for raw_node in node.value[1:]:
|
||||||
self.args.append(loader.construct_object(raw_node))
|
self.args.append(loader.construct_object(raw_node))
|
||||||
|
@ -416,7 +416,7 @@ class Enumerate(YAMLTag, YAMLTagContext):
|
||||||
def __init__(self, loader: "BlueprintLoader", node: SequenceNode) -> None:
|
def __init__(self, loader: "BlueprintLoader", node: SequenceNode) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.iterable = loader.construct_object(node.value[0])
|
self.iterable = loader.construct_object(node.value[0])
|
||||||
self.output_body = node.value[1].value
|
self.output_body = loader.construct_object(node.value[1])
|
||||||
self.item_body = loader.construct_object(node.value[2])
|
self.item_body = loader.construct_object(node.value[2])
|
||||||
self.__current_context: tuple[Any, Any] = tuple()
|
self.__current_context: tuple[Any, Any] = tuple()
|
||||||
|
|
||||||
|
|
|
@ -123,27 +123,35 @@ class UserSerializer(ModelSerializer):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if SERIALIZER_CONTEXT_BLUEPRINT in self.context:
|
if SERIALIZER_CONTEXT_BLUEPRINT in self.context:
|
||||||
self.fields["password"] = CharField(required=False)
|
self.fields["password"] = CharField(required=False, allow_null=True)
|
||||||
|
|
||||||
def create(self, validated_data: dict) -> User:
|
def create(self, validated_data: dict) -> User:
|
||||||
"""If this serializer is used in the blueprint context, we allow for
|
"""If this serializer is used in the blueprint context, we allow for
|
||||||
directly setting a password. However should be done via the `set_password`
|
directly setting a password. However should be done via the `set_password`
|
||||||
method instead of directly setting it like rest_framework."""
|
method instead of directly setting it like rest_framework."""
|
||||||
|
password = validated_data.pop("password", None)
|
||||||
instance: User = super().create(validated_data)
|
instance: User = super().create(validated_data)
|
||||||
if SERIALIZER_CONTEXT_BLUEPRINT in self.context and "password" in validated_data:
|
self._set_password(instance, password)
|
||||||
instance.set_password(validated_data["password"])
|
|
||||||
instance.save()
|
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
def update(self, instance: User, validated_data: dict) -> User:
|
def update(self, instance: User, validated_data: dict) -> User:
|
||||||
"""Same as `create` above, set the password directly if we're in a blueprint
|
"""Same as `create` above, set the password directly if we're in a blueprint
|
||||||
context"""
|
context"""
|
||||||
|
password = validated_data.pop("password", None)
|
||||||
instance = super().update(instance, validated_data)
|
instance = super().update(instance, validated_data)
|
||||||
if SERIALIZER_CONTEXT_BLUEPRINT in self.context and "password" in validated_data:
|
self._set_password(instance, password)
|
||||||
instance.set_password(validated_data["password"])
|
|
||||||
instance.save()
|
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
def _set_password(self, instance: User, password: Optional[str]):
|
||||||
|
"""Set password of user if we're in a blueprint context, and if it's an empty
|
||||||
|
string then use an unusable password"""
|
||||||
|
if SERIALIZER_CONTEXT_BLUEPRINT in self.context and password:
|
||||||
|
instance.set_password(password)
|
||||||
|
instance.save()
|
||||||
|
if len(instance.password) == 0:
|
||||||
|
instance.set_unusable_password()
|
||||||
|
instance.save()
|
||||||
|
|
||||||
def validate_path(self, path: str) -> str:
|
def validate_path(self, path: str) -> str:
|
||||||
"""Validate path"""
|
"""Validate path"""
|
||||||
if path[:1] == "/" or path[-1] == "/":
|
if path[:1] == "/" or path[-1] == "/":
|
||||||
|
|
|
@ -8400,7 +8400,10 @@
|
||||||
"title": "Type"
|
"title": "Type"
|
||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"type": "string",
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"title": "Password"
|
"title": "Password"
|
||||||
}
|
}
|
||||||
|
|
Reference in New Issue