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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,13 +1,15 @@
"""email utils""" """email utils"""
from django.core.mail import EmailMultiAlternatives from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils import translation
class TemplateEmailMessage(EmailMultiAlternatives): class TemplateEmailMessage(EmailMultiAlternatives):
"""Wrapper around EmailMultiAlternatives with integrated template rendering""" """Wrapper around EmailMultiAlternatives with integrated template rendering"""
def __init__(self, template_name=None, template_context=None, **kwargs): def __init__(self, template_name=None, template_context=None, language="", **kwargs):
html_content = render_to_string(template_name, template_context) with translation.override(language):
html_content = render_to_string(template_name, template_context)
super().__init__(**kwargs) super().__init__(**kwargs)
self.content_subtype = "html" self.content_subtype = "html"
self.attach_alternative(html_content, "text/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.request import HttpRequest
from django.http.response import HttpResponse from django.http.response import HttpResponse
from django.utils.translation import activate
from sentry_sdk.api import set_tag from sentry_sdk.api import set_tag
from authentik.tenants.utils import get_tenant_for_request from authentik.tenants.utils import get_tenant_for_request
@ -22,4 +23,7 @@ class TenantMiddleware:
setattr(request, "tenant", tenant) setattr(request, "tenant", tenant)
set_tag("authentik.tenant_uuid", tenant.tenant_uuid.hex) set_tag("authentik.tenant_uuid", tenant.tenant_uuid.hex)
set_tag("authentik.tenant_domain", tenant.domain) set_tag("authentik.tenant_domain", tenant.domain)
locale = tenant.default_locale
if locale != "":
activate(locale)
return self.get_response(request) return self.get_response(request)