blueprints: allow setting user's passwords from blueprints (#5797)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
		| @ -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 New Issue
	
	Block a user
	 Jens L
					Jens L