core: explicitly enable locales (#3889)

* activate locales

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

* set locale for email templates

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

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens L 2022-10-28 19:42:49 +02:00 committed by GitHub
parent 8a50279142
commit 30d708dd1f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 34 additions and 8 deletions

View File

@ -470,7 +470,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
# pylint: disable=invalid-name, unused-argument
def recovery_email(self, request: Request, pk: int) -> Response:
"""Create a temporary link that a user can use to recover their accounts"""
for_user = self.get_object()
for_user: User = self.get_object()
if for_user.email == "":
LOGGER.debug("User doesn't have an email address")
return Response(status=404)
@ -488,8 +488,9 @@ class UserViewSet(UsedByMixin, ModelViewSet):
email_stage: EmailStage = stages.first()
message = TemplateEmailMessage(
subject=_(email_stage.subject),
template_name=email_stage.template,
to=[for_user.email],
template_name=email_stage.template,
language=for_user.locale(request),
template_context={
"url": link,
"user": for_user,

View File

@ -4,6 +4,7 @@ from typing import Callable, Optional
from uuid import uuid4
from django.http import HttpRequest, HttpResponse
from django.utils.translation import activate
from sentry_sdk.api import set_tag
from structlog.contextvars import STRUCTLOG_KEY_PREFIX
@ -29,6 +30,10 @@ class ImpersonateMiddleware:
def __call__(self, request: HttpRequest) -> HttpResponse:
# No permission checks are done here, they need to be checked before
# SESSION_KEY_IMPERSONATE_USER is set.
if request.user.is_authenticated:
locale = request.user.locale(request)
if locale != "":
activate(locale)
if SESSION_KEY_IMPERSONATE_USER in request.session:
request.user = request.session[SESSION_KEY_IMPERSONATE_USER]

View File

@ -220,6 +220,17 @@ class User(SerializerModel, GuardianUserMixin, AbstractUser):
"""Generate a globally unique UID, based on the user ID and the hashed secret key"""
return sha256(f"{self.id}-{settings.SECRET_KEY}".encode("ascii")).hexdigest()
def locale(self, request: Optional[HttpRequest] = None) -> str:
"""Get the locale the user has configured"""
try:
return self.attributes.get("settings", {}).get("locale", "")
# pylint: disable=broad-except
except Exception as exc:
LOGGER.warning("Failed to get default locale", exc=exc)
if request:
return request.tenant.locale
return ""
@property
def avatar(self) -> str:
"""Get avatar, depending on authentik.avatar setting"""

View File

@ -445,8 +445,9 @@ class NotificationTransport(SerializerModel):
subject += notification.body[:75]
mail = TemplateEmailMessage(
subject=subject,
template_name="email/generic.html",
to=[notification.user.email],
language=notification.user.locale(),
template_name="email/generic.html",
template_context={
"title": subject,
"body": notification.body,

View File

@ -28,8 +28,8 @@ class Command(BaseCommand):
delete_stage = True
message = TemplateEmailMessage(
subject="authentik Test-Email",
template_name="email/setup.html",
to=[options["to"]],
template_name="email/setup.html",
template_context={},
)
try:

View File

@ -11,6 +11,7 @@ from django.utils.translation import gettext as _
from rest_framework.fields import CharField
from rest_framework.serializers import ValidationError
from authentik.core.models import User
from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes
from authentik.flows.models import FlowToken
from authentik.flows.planner import PLAN_CONTEXT_IS_RESTORED, PLAN_CONTEXT_PENDING_USER
@ -81,7 +82,7 @@ class EmailStageView(ChallengeStageView):
def send_email(self):
"""Helper function that sends the actual email. Implies that you've
already checked that there is a pending user."""
pending_user = self.executor.plan.context[PLAN_CONTEXT_PENDING_USER]
pending_user: User = self.executor.plan.context[PLAN_CONTEXT_PENDING_USER]
email = self.executor.plan.context.get(PLAN_CONTEXT_EMAIL_OVERRIDE, None)
if not email:
email = pending_user.email
@ -90,8 +91,9 @@ class EmailStageView(ChallengeStageView):
# Send mail to user
message = TemplateEmailMessage(
subject=_(current_stage.subject),
template_name=current_stage.template,
to=[email],
language=pending_user.locale(self.request),
template_name=current_stage.template,
template_context={
"url": self.get_full_url(**{QS_KEY_TOKEN: token.key}),
"user": pending_user,

View File

@ -1,13 +1,15 @@
"""email utils"""
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from django.utils import translation
class TemplateEmailMessage(EmailMultiAlternatives):
"""Wrapper around EmailMultiAlternatives with integrated template rendering"""
def __init__(self, template_name=None, template_context=None, **kwargs):
html_content = render_to_string(template_name, template_context)
def __init__(self, template_name=None, template_context=None, language="", **kwargs):
with translation.override(language):
html_content = render_to_string(template_name, template_context)
super().__init__(**kwargs)
self.content_subtype = "html"
self.attach_alternative(html_content, "text/html")

View File

@ -3,6 +3,7 @@ from typing import Callable
from django.http.request import HttpRequest
from django.http.response import HttpResponse
from django.utils.translation import activate
from sentry_sdk.api import set_tag
from authentik.tenants.utils import get_tenant_for_request
@ -22,4 +23,7 @@ class TenantMiddleware:
setattr(request, "tenant", tenant)
set_tag("authentik.tenant_uuid", tenant.tenant_uuid.hex)
set_tag("authentik.tenant_domain", tenant.domain)
locale = tenant.default_locale
if locale != "":
activate(locale)
return self.get_response(request)