blueprints: allow setting user's passwords from blueprints (#5797)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L 2023-05-29 21:28:44 +02:00 committed by GitHub
parent d09bee7bf9
commit f0619814f9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 80 additions and 15 deletions

View file

@ -11,31 +11,37 @@ metadata:
entries:
- model: authentik_core.token
identifiers:
identifier: %(uid)s-token
identifier: "%(uid)s-token"
attrs:
key: %(uid)s
user: %(user)s
key: "%(uid)s"
user: "%(user)s"
intent: api
- model: authentik_core.application
identifiers:
slug: %(uid)s-app
slug: "%(uid)s-app"
attrs:
name: %(uid)s-app
name: "%(uid)s-app"
icon: https://goauthentik.io/img/icon.png
- model: authentik_sources_oauth.oauthsource
identifiers:
slug: %(uid)s-source
slug: "%(uid)s-source"
attrs:
name: %(uid)s-source
name: "%(uid)s-source"
provider_type: azuread
consumer_key: %(uid)s
consumer_secret: %(uid)s
consumer_key: "%(uid)s"
consumer_secret: "%(uid)s"
icon: https://goauthentik.io/img/icon.png
- model: authentik_flows.flow
identifiers:
slug: %(uid)s-flow
slug: "%(uid)s-flow"
attrs:
name: %(uid)s-flow
title: %(uid)s-flow
name: "%(uid)s-flow"
title: "%(uid)s-flow"
designation: authentication
background: https://goauthentik.io/img/icon.png
- model: authentik_core.user
identifiers:
username: "%(uid)s"
attrs:
name: "%(uid)s"
password: "%(uid)s"

View file

@ -2,7 +2,7 @@
from django.test import TransactionTestCase
from authentik.blueprints.v1.importer import Importer
from authentik.core.models import Application, Token
from authentik.core.models import Application, Token, User
from authentik.core.tests.utils import create_test_admin_user
from authentik.flows.models import Flow
from authentik.lib.generators import generate_id
@ -45,3 +45,9 @@ class TestBlueprintsV1ConditionalFields(TransactionTestCase):
flow = Flow.objects.filter(slug=f"{self.uid}-flow").first()
self.assertIsNotNone(flow)
self.assertEqual(flow.background, "https://goauthentik.io/img/icon.png")
def test_user(self):
"""Test user"""
user: User = User.objects.filter(username=self.uid).first()
self.assertIsNotNone(user)
self.assertTrue(user.check_password(self.uid))

View file

@ -184,9 +184,9 @@ def apply_blueprint(self: MonitoredTask, instance_pk: str):
instance: Optional[BlueprintInstance] = None
try:
instance: BlueprintInstance = BlueprintInstance.objects.filter(pk=instance_pk).first()
self.set_uid(slugify(instance.name))
if not instance or not instance.enabled:
return
self.set_uid(slugify(instance.name))
blueprint_content = instance.retrieve()
file_hash = sha512(blueprint_content.encode()).hexdigest()
importer = Importer(blueprint_content, instance.context)

View file

@ -33,7 +33,7 @@ class TokenSerializer(ManagedSerializer, ModelSerializer):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
if SERIALIZER_CONTEXT_BLUEPRINT in self.context:
self.fields["key"] = CharField()
self.fields["key"] = CharField(required=False)
def validate(self, attrs: dict[Any, str]) -> dict[Any, str]:
"""Ensure only API or App password tokens are created."""

View file

@ -51,6 +51,7 @@ from structlog.stdlib import get_logger
from authentik.admin.api.metrics import CoordinateSerializer
from authentik.api.decorators import permission_required
from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import LinkSerializer, PassiveSerializer, is_dict
from authentik.core.middleware import (
@ -112,6 +113,30 @@ class UserSerializer(ModelSerializer):
uid = CharField(read_only=True)
username = CharField(max_length=150, validators=[UniqueValidator(queryset=User.objects.all())])
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if SERIALIZER_CONTEXT_BLUEPRINT in self.context:
self.fields["password"] = CharField(required=False)
def create(self, validated_data: dict) -> User:
"""If this serializer is used in the blueprint context, we allow for
directly setting a password. However should be done via the `set_password`
method instead of directly setting it like rest_framework."""
instance: User = super().create(validated_data)
if SERIALIZER_CONTEXT_BLUEPRINT in self.context and "password" in validated_data:
instance.set_password(validated_data["password"])
instance.save()
return instance
def update(self, instance: User, validated_data: dict) -> User:
"""Same as `create` above, set the password directly if we're in a blueprint
context"""
instance = super().update(instance, validated_data)
if SERIALIZER_CONTEXT_BLUEPRINT in self.context and "password" in validated_data:
instance.set_password(validated_data["password"])
instance.save()
return instance
def validate_path(self, path: str) -> str:
"""Validate path"""
if path[:1] == "/" or path[-1] == "/":

View file

@ -8228,6 +8228,11 @@
"type": "string",
"minLength": 1,
"title": "Path"
},
"password": {
"type": "string",
"minLength": 1,
"title": "Password"
}
},
"required": []

View file

@ -26,6 +26,29 @@ For example:
intent: api
```
### `authentik_core.user`
:::info
Requires authentik 2023.6
:::
Via the standard API, a user's password can only be set via the separate `/api/v3/core/users/<id>/set_password/` endpoint. In blueprints, the password of a user can be set using the `password` field.
Keep in mind that if an LDAP Source is configured and the user maps to an LDAP user, this password change will be propagated to the LDAP server.
For example:
```yaml
# [...]
- model: authentik_core.user
state: present
identifiers:
username: test-user
attrs:
name: test user
password: this-should-be-a-long-value
```
### `authentik_core.application`
:::info