blueprints: allow setting user's passwords from blueprints (#5797)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
parent
d09bee7bf9
commit
f0619814f9
|
@ -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"
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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] == "/":
|
||||
|
|
|
@ -8228,6 +8228,11 @@
|
|||
"type": "string",
|
||||
"minLength": 1,
|
||||
"title": "Path"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"title": "Password"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
|
|
|
@ -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
|
||||
|
|
Reference in a new issue