core: customisable user settings (#2397)

* tenants: add user_settings flow, add basic flow and basic new executor

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web/user: use flow PromptStage instead of custom stage

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web/flows: add tenant to StageHost interface

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web/user: fix form missing component

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web/user: re-add success message

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web/user: improve support for multiple error messages

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* stages/prompt: allow expressions in prompt placeholders

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* stages/prompt: add tests

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* flows: always set pending user

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* flows: never cache stage configuration flow plans

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* stages/user_write: fix error when pending user is anonymous user

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web/admin: add checkbox for prompt placeholder expression

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* website/docs: add prompt expression docs

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* stages/prompt: add ak-locale field type

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* tenants: fix default policy

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web/user: add function to do global refresh

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web/flows: fix rendering of ak-locale

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* tenants: fix default policy, add error handling to placeholder, fix locale attribute

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* add tests

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens L
2022-03-03 00:13:06 +01:00
committed by GitHub
parent c57fbcfd89
commit 4f4f954693
41 changed files with 3791 additions and 2853 deletions

View File

@ -24,7 +24,6 @@ from drf_spectacular.utils import (
from guardian.shortcuts import get_anonymous_user, get_objects_for_user
from rest_framework.decorators import action
from rest_framework.fields import CharField, DictField, JSONField, SerializerMethodField
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import (
@ -46,9 +45,6 @@ from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import LinkSerializer, PassiveSerializer, is_dict
from authentik.core.middleware import SESSION_IMPERSONATE_ORIGINAL_USER, SESSION_IMPERSONATE_USER
from authentik.core.models import (
USER_ATTRIBUTE_CHANGE_EMAIL,
USER_ATTRIBUTE_CHANGE_NAME,
USER_ATTRIBUTE_CHANGE_USERNAME,
USER_ATTRIBUTE_SA,
USER_ATTRIBUTE_TOKEN_EXPIRING,
Group,
@ -57,7 +53,6 @@ from authentik.core.models import (
User,
)
from authentik.events.models import EventAction
from authentik.lib.config import CONFIG
from authentik.stages.email.models import EmailStage
from authentik.stages.email.tasks import send_mails
from authentik.stages.email.utils import TemplateEmailMessage
@ -126,43 +121,6 @@ class UserSelfSerializer(ModelSerializer):
"pk": group.pk,
}
def validate_email(self, email: str):
"""Check if the user is allowed to change their email"""
if self.instance.group_attributes().get(
USER_ATTRIBUTE_CHANGE_EMAIL, CONFIG.y_bool("default_user_change_email", True)
):
return email
if email != self.instance.email:
raise ValidationError("Not allowed to change email.")
return email
def validate_name(self, name: str):
"""Check if the user is allowed to change their name"""
if self.instance.group_attributes().get(
USER_ATTRIBUTE_CHANGE_NAME, CONFIG.y_bool("default_user_change_name", True)
):
return name
if name != self.instance.name:
raise ValidationError("Not allowed to change name.")
return name
def validate_username(self, username: str):
"""Check if the user is allowed to change their username"""
if self.instance.group_attributes().get(
USER_ATTRIBUTE_CHANGE_USERNAME, CONFIG.y_bool("default_user_change_username", True)
):
return username
if username != self.instance.username:
raise ValidationError("Not allowed to change username.")
return username
def save(self, **kwargs):
if self.instance:
attributes: dict = self.instance.attributes
attributes.update(self.validated_data.get("attributes", {}))
self.validated_data["attributes"] = attributes
return super().save(**kwargs)
class Meta:
model = User
@ -407,26 +365,6 @@ class UserViewSet(UsedByMixin, ModelViewSet):
update_session_auth_hash(self.request, user)
return Response(status=204)
@extend_schema(request=UserSelfSerializer, responses={200: SessionUserSerializer(many=False)})
@action(
methods=["PUT"],
detail=False,
pagination_class=None,
filter_backends=[],
permission_classes=[IsAuthenticated],
)
def update_self(self, request: Request) -> Response:
"""Allow users to change information on their own profile"""
data = UserSelfSerializer(instance=User.objects.get(pk=request.user.pk), data=request.data)
if not data.is_valid():
return Response(data.errors, status=400)
new_user = data.save()
# If we're impersonating, we need to update that user object
# since it caches the full object
if SESSION_IMPERSONATE_USER in request.session:
request.session[SESSION_IMPERSONATE_USER] = new_user
return Response({"user": data.data})
@permission_required("authentik_core.view_user", ["authentik_events.view_event"])
@extend_schema(responses={200: UserMetricsSerializer(many=False)})
@action(detail=True, pagination_class=None, filter_backends=[])