diff --git a/authentik/blueprints/v1/importer.py b/authentik/blueprints/v1/importer.py index ff1138d6fe..6ab2e14c02 100644 --- a/authentik/blueprints/v1/importer.py +++ b/authentik/blueprints/v1/importer.py @@ -19,8 +19,6 @@ from guardian.models import UserObjectPermission from rest_framework.exceptions import ValidationError from rest_framework.serializers import BaseSerializer, Serializer from structlog.stdlib import BoundLogger, get_logger -from structlog.testing import capture_logs -from structlog.types import EventDict from yaml import load from authentik.blueprints.v1.common import ( @@ -42,6 +40,7 @@ from authentik.core.models import ( from authentik.enterprise.license import LicenseKey from authentik.enterprise.models import LicenseUsage from authentik.enterprise.providers.rac.models import ConnectionToken +from authentik.events.logs import LogEvent, capture_logs from authentik.events.models import SystemTask from authentik.events.utils import cleanse_dict from authentik.flows.models import FlowToken, Stage @@ -161,7 +160,7 @@ class Importer: def updater(value) -> Any: if value in self.__pk_map: - self.logger.debug("updating reference in entry", value=value) + self.logger.debug("Updating reference in entry", value=value) return self.__pk_map[value] return value @@ -250,7 +249,7 @@ class Importer: model_instance = existing_models.first() if not isinstance(model(), BaseMetaModel) and model_instance: self.logger.debug( - "initialise serializer with instance", + "Initialise serializer with instance", model=model, instance=model_instance, pk=model_instance.pk, @@ -260,14 +259,14 @@ class Importer: elif model_instance and entry.state == BlueprintEntryDesiredState.MUST_CREATED: raise EntryInvalidError.from_entry( ( - f"state is set to {BlueprintEntryDesiredState.MUST_CREATED} " + f"State is set to {BlueprintEntryDesiredState.MUST_CREATED} " "and object exists already", ), entry, ) else: self.logger.debug( - "initialised new serializer instance", + "Initialised new serializer instance", model=model, **cleanse_dict(updated_identifiers), ) @@ -324,7 +323,7 @@ class Importer: model: type[SerializerModel] = registry.get_model(model_app_label, model_name) except LookupError: self.logger.warning( - "app or model does not exist", app=model_app_label, model=model_name + "App or Model does not exist", app=model_app_label, model=model_name ) return False # Validate each single entry @@ -336,7 +335,7 @@ class Importer: if entry.get_state(self._import) == BlueprintEntryDesiredState.ABSENT: serializer = exc.serializer else: - self.logger.warning(f"entry invalid: {exc}", entry=entry, error=exc) + self.logger.warning(f"Entry invalid: {exc}", entry=entry, error=exc) if raise_errors: raise exc return False @@ -356,14 +355,14 @@ class Importer: and state == BlueprintEntryDesiredState.CREATED ): self.logger.debug( - "instance exists, skipping", + "Instance exists, skipping", model=model, instance=instance, pk=instance.pk, ) else: instance = serializer.save() - self.logger.debug("updated model", model=instance) + self.logger.debug("Updated model", model=instance) if "pk" in entry.identifiers: self.__pk_map[entry.identifiers["pk"]] = instance.pk entry._state = BlueprintEntryState(instance) @@ -371,12 +370,12 @@ class Importer: instance: Model | None = serializer.instance if instance.pk: instance.delete() - self.logger.debug("deleted model", mode=instance) + self.logger.debug("Deleted model", mode=instance) continue - self.logger.debug("entry to delete with no instance, skipping") + self.logger.debug("Entry to delete with no instance, skipping") return True - def validate(self, raise_validation_errors=False) -> tuple[bool, list[EventDict]]: + def validate(self, raise_validation_errors=False) -> tuple[bool, list[LogEvent]]: """Validate loaded blueprint export, ensure all models are allowed and serializers have no errors""" self.logger.debug("Starting blueprint import validation") @@ -390,9 +389,7 @@ class Importer: ): successful = self._apply_models(raise_errors=raise_validation_errors) if not successful: - self.logger.debug("Blueprint validation failed") - for log in logs: - getattr(self.logger, log.get("log_level"))(**log) + self.logger.warning("Blueprint validation failed") self.logger.debug("Finished blueprint import validation") self._import = orig_import return successful, logs diff --git a/authentik/blueprints/v1/tasks.py b/authentik/blueprints/v1/tasks.py index 058cb4a11c..c8b72a5dc2 100644 --- a/authentik/blueprints/v1/tasks.py +++ b/authentik/blueprints/v1/tasks.py @@ -30,6 +30,7 @@ from authentik.blueprints.v1.common import BlueprintLoader, BlueprintMetadata, E from authentik.blueprints.v1.importer import Importer from authentik.blueprints.v1.labels import LABEL_AUTHENTIK_INSTANTIATE from authentik.blueprints.v1.oci import OCI_PREFIX +from authentik.events.logs import capture_logs from authentik.events.models import TaskStatus from authentik.events.system_tasks import SystemTask, prefill_task from authentik.events.utils import sanitize_dict @@ -211,14 +212,15 @@ def apply_blueprint(self: SystemTask, instance_pk: str): if not valid: instance.status = BlueprintInstanceStatus.ERROR instance.save() - self.set_status(TaskStatus.ERROR, *[x["event"] for x in logs]) - return - applied = importer.apply() - if not applied: - instance.status = BlueprintInstanceStatus.ERROR - instance.save() - self.set_status(TaskStatus.ERROR, "Failed to apply") + self.set_status(TaskStatus.ERROR, *logs) return + with capture_logs() as logs: + applied = importer.apply() + if not applied: + instance.status = BlueprintInstanceStatus.ERROR + instance.save() + self.set_status(TaskStatus.ERROR, *logs) + return instance.status = BlueprintInstanceStatus.SUCCESSFUL instance.last_applied_hash = file_hash instance.last_applied = now() diff --git a/authentik/core/api/applications.py b/authentik/core/api/applications.py index 4ab273e070..413e7777a9 100644 --- a/authentik/core/api/applications.py +++ b/authentik/core/api/applications.py @@ -20,15 +20,14 @@ from rest_framework.response import Response from rest_framework.serializers import ModelSerializer from rest_framework.viewsets import ModelViewSet from structlog.stdlib import get_logger -from structlog.testing import capture_logs from authentik.admin.api.metrics import CoordinateSerializer from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT from authentik.core.api.providers import ProviderSerializer from authentik.core.api.used_by import UsedByMixin from authentik.core.models import Application, User +from authentik.events.logs import LogEventSerializer, capture_logs from authentik.events.models import EventAction -from authentik.events.utils import sanitize_dict from authentik.lib.utils.file import ( FilePathSerializer, FileUploadSerializer, @@ -182,9 +181,9 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet): if request.user.is_superuser: log_messages = [] for log in logs: - if log.get("process", "") == "PolicyProcess": + if log.attributes.get("process", "") == "PolicyProcess": continue - log_messages.append(sanitize_dict(log)) + log_messages.append(LogEventSerializer(log).data) result.log_messages = log_messages response = PolicyTestResultSerializer(result) return Response(response.data) diff --git a/authentik/events/api/tasks.py b/authentik/events/api/tasks.py index 25b6c38273..09cec8457e 100644 --- a/authentik/events/api/tasks.py +++ b/authentik/events/api/tasks.py @@ -12,7 +12,6 @@ from rest_framework.fields import ( ChoiceField, DateTimeField, FloatField, - ListField, SerializerMethodField, ) from rest_framework.request import Request @@ -21,6 +20,7 @@ from rest_framework.serializers import ModelSerializer from rest_framework.viewsets import ReadOnlyModelViewSet from structlog.stdlib import get_logger +from authentik.events.logs import LogEventSerializer from authentik.events.models import SystemTask, TaskStatus from authentik.rbac.decorators import permission_required @@ -39,7 +39,7 @@ class SystemTaskSerializer(ModelSerializer): duration = FloatField(read_only=True) status = ChoiceField(choices=[(x.value, x.name) for x in TaskStatus]) - messages = ListField(child=CharField()) + messages = LogEventSerializer(many=True) def get_full_name(self, instance: SystemTask) -> str: """Get full name with UID""" diff --git a/authentik/events/logs.py b/authentik/events/logs.py new file mode 100644 index 0000000000..30fd757ce2 --- /dev/null +++ b/authentik/events/logs.py @@ -0,0 +1,82 @@ +from collections.abc import Generator +from contextlib import contextmanager +from dataclasses import dataclass, field +from datetime import datetime +from typing import Any + +from django.utils.timezone import now +from rest_framework.fields import CharField, ChoiceField, DateTimeField, DictField +from structlog import configure, get_config +from structlog.stdlib import NAME_TO_LEVEL, ProcessorFormatter +from structlog.testing import LogCapture +from structlog.types import EventDict + +from authentik.core.api.utils import PassiveSerializer +from authentik.events.utils import sanitize_dict + + +@dataclass() +class LogEvent: + + event: str + log_level: str + logger: str + timestamp: datetime = field(default_factory=now) + attributes: dict[str, Any] = field(default_factory=dict) + + @staticmethod + def from_event_dict(item: EventDict) -> "LogEvent": + event = item.pop("event") + log_level = item.pop("level").lower() + timestamp = datetime.fromisoformat(item.pop("timestamp")) + item.pop("pid", None) + # Sometimes log entries have both `level` and `log_level` set, but `level` is always set + item.pop("log_level", None) + return LogEvent( + event, log_level, item.pop("logger"), timestamp, attributes=sanitize_dict(item) + ) + + +class LogEventSerializer(PassiveSerializer): + """Single log message with all context logged.""" + + timestamp = DateTimeField() + log_level = ChoiceField(choices=tuple((x, x) for x in NAME_TO_LEVEL.keys())) + logger = CharField() + event = CharField() + attributes = DictField() + + # TODO(2024.6?): This is a migration helper to return a correct API response for logs that + # have been saved in an older format (mostly just list[str] with just the messages) + def to_representation(self, instance): + if isinstance(instance, str): + instance = LogEvent(instance, "", "") + elif isinstance(instance, list): + instance = [LogEvent(x, "", "") for x in instance] + return super().to_representation(instance) + + +@contextmanager +def capture_logs(log_default_output=True) -> Generator[list[LogEvent], None, None]: + """Capture log entries created""" + logs = [] + cap = LogCapture() + # Modify `_Configuration.default_processors` set via `configure` but always + # keep the list instance intact to not break references held by bound + # loggers. + processors: list = get_config()["processors"] + old_processors = processors.copy() + try: + # clear processors list and use LogCapture for testing + if ProcessorFormatter.wrap_for_formatter in processors: + processors.remove(ProcessorFormatter.wrap_for_formatter) + processors.append(cap) + configure(processors=processors) + yield logs + for raw_log in cap.entries: + logs.append(LogEvent.from_event_dict(raw_log)) + finally: + # remove LogCapture and restore original processors + processors.clear() + processors.extend(old_processors) + configure(processors=processors) diff --git a/authentik/events/system_tasks.py b/authentik/events/system_tasks.py index f8de8530eb..fe1696c2d7 100644 --- a/authentik/events/system_tasks.py +++ b/authentik/events/system_tasks.py @@ -9,6 +9,7 @@ from django.utils.translation import gettext_lazy as _ from structlog.stdlib import get_logger from tenant_schemas_celery.task import TenantTask +from authentik.events.logs import LogEvent from authentik.events.models import Event, EventAction, TaskStatus from authentik.events.models import SystemTask as DBSystemTask from authentik.events.utils import sanitize_item @@ -24,7 +25,7 @@ class SystemTask(TenantTask): save_on_success: bool _status: TaskStatus - _messages: list[str] + _messages: list[LogEvent] _uid: str | None # Precise start time from perf_counter @@ -44,15 +45,20 @@ class SystemTask(TenantTask): """Set UID, so in the case of an unexpected error its saved correctly""" self._uid = uid - def set_status(self, status: TaskStatus, *messages: str): + def set_status(self, status: TaskStatus, *messages: LogEvent): """Set result for current run, will overwrite previous result.""" self._status = status - self._messages = messages + self._messages = list(messages) + for idx, msg in enumerate(self._messages): + if not isinstance(msg, LogEvent): + self._messages[idx] = LogEvent(msg, logger=self.__name__, log_level="info") def set_error(self, exception: Exception): """Set result to error and save exception""" self._status = TaskStatus.ERROR - self._messages = [exception_to_string(exception)] + self._messages = [ + LogEvent(exception_to_string(exception), logger=self.__name__, log_level="error") + ] def before_start(self, task_id, args, kwargs): self._start_precise = perf_counter() @@ -98,8 +104,7 @@ class SystemTask(TenantTask): def on_failure(self, exc, task_id, args, kwargs, einfo): super().on_failure(exc, task_id, args, kwargs, einfo=einfo) if not self._status: - self._status = TaskStatus.ERROR - self._messages = exception_to_string(exc) + self.set_error(exc) DBSystemTask.objects.update_or_create( name=self.__name__, uid=self._uid, diff --git a/authentik/flows/api/flows.py b/authentik/flows/api/flows.py index 7d96778998..0e059c4b2f 100644 --- a/authentik/flows/api/flows.py +++ b/authentik/flows/api/flows.py @@ -7,7 +7,7 @@ from django.utils.translation import gettext as _ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiResponse, extend_schema from rest_framework.decorators import action -from rest_framework.fields import BooleanField, CharField, DictField, ListField, ReadOnlyField +from rest_framework.fields import BooleanField, CharField, ReadOnlyField from rest_framework.parsers import MultiPartParser from rest_framework.request import Request from rest_framework.response import Response @@ -19,7 +19,7 @@ from authentik.blueprints.v1.exporter import FlowExporter from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT, Importer from authentik.core.api.used_by import UsedByMixin from authentik.core.api.utils import CacheSerializer, LinkSerializer, PassiveSerializer -from authentik.events.utils import sanitize_dict +from authentik.events.logs import LogEventSerializer from authentik.flows.api.flows_diagram import FlowDiagram, FlowDiagramSerializer from authentik.flows.exceptions import FlowNonApplicableException from authentik.flows.models import Flow @@ -107,7 +107,7 @@ class FlowSetSerializer(FlowSerializer): class FlowImportResultSerializer(PassiveSerializer): """Logs of an attempted flow import""" - logs = ListField(child=DictField(), read_only=True) + logs = LogEventSerializer(many=True, read_only=True) success = BooleanField(read_only=True) @@ -184,7 +184,7 @@ class FlowViewSet(UsedByMixin, ModelViewSet): importer = Importer.from_string(file.read().decode()) valid, logs = importer.validate() - import_response.initial_data["logs"] = [sanitize_dict(log) for log in logs] + import_response.initial_data["logs"] = [LogEventSerializer(log).data for log in logs] import_response.initial_data["success"] = valid import_response.is_valid() if not valid: diff --git a/authentik/flows/planner.py b/authentik/flows/planner.py index a4c8c0c1ac..167337cc57 100644 --- a/authentik/flows/planner.py +++ b/authentik/flows/planner.py @@ -59,11 +59,11 @@ class FlowPlan: markers: list[StageMarker] = field(default_factory=list) def append_stage(self, stage: Stage, marker: StageMarker | None = None): - """Append `stage` to all stages, optionally with stage marker""" + """Append `stage` to the end of the plan, optionally with stage marker""" return self.append(FlowStageBinding(stage=stage), marker) def append(self, binding: FlowStageBinding, marker: StageMarker | None = None): - """Append `stage` to all stages, optionally with stage marker""" + """Append `stage` to the end of the plan, optionally with stage marker""" self.bindings.append(binding) self.markers.append(marker or StageMarker()) diff --git a/authentik/flows/views/executor.py b/authentik/flows/views/executor.py index c620107467..774206c9d3 100644 --- a/authentik/flows/views/executor.py +++ b/authentik/flows/views/executor.py @@ -450,7 +450,7 @@ class FlowExecutorView(APIView): return to_stage_response(self.request, challenge_view.get(self.request)) def cancel(self): - """Cancel current execution and return a redirect""" + """Cancel current flow execution""" keys_to_delete = [ SESSION_KEY_APPLICATION_PRE, SESSION_KEY_PLAN, diff --git a/authentik/outposts/controllers/base.py b/authentik/outposts/controllers/base.py index 5f7cdafc93..a7321e01cc 100644 --- a/authentik/outposts/controllers/base.py +++ b/authentik/outposts/controllers/base.py @@ -3,9 +3,9 @@ from dataclasses import dataclass from structlog.stdlib import get_logger -from structlog.testing import capture_logs from authentik import __version__, get_build_hash +from authentik.events.logs import LogEvent, capture_logs from authentik.lib.config import CONFIG from authentik.lib.sentry import SentryIgnoredException from authentik.outposts.models import ( @@ -63,21 +63,21 @@ class BaseController: """Called by scheduled task to reconcile deployment/service/etc""" raise NotImplementedError - def up_with_logs(self) -> list[str]: + def up_with_logs(self) -> list[LogEvent]: """Call .up() but capture all log output and return it.""" with capture_logs() as logs: self.up() - return [x["event"] for x in logs] + return logs def down(self): """Handler to delete everything we've created""" raise NotImplementedError - def down_with_logs(self) -> list[str]: + def down_with_logs(self) -> list[LogEvent]: """Call .down() but capture all log output and return it.""" with capture_logs() as logs: self.down() - return [x["event"] for x in logs] + return logs def __enter__(self): return self diff --git a/authentik/outposts/controllers/kubernetes.py b/authentik/outposts/controllers/kubernetes.py index bf8eea2127..5802d154ca 100644 --- a/authentik/outposts/controllers/kubernetes.py +++ b/authentik/outposts/controllers/kubernetes.py @@ -9,10 +9,10 @@ from kubernetes.client.exceptions import OpenApiException from kubernetes.config.config_exception import ConfigException from kubernetes.config.incluster_config import load_incluster_config from kubernetes.config.kube_config import load_kube_config_from_dict -from structlog.testing import capture_logs from urllib3.exceptions import HTTPError from yaml import dump_all +from authentik.events.logs import LogEvent, capture_logs from authentik.outposts.controllers.base import BaseClient, BaseController, ControllerException from authentik.outposts.controllers.k8s.base import KubernetesObjectReconciler from authentik.outposts.controllers.k8s.deployment import DeploymentReconciler @@ -91,7 +91,7 @@ class KubernetesController(BaseController): except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc: raise ControllerException(str(exc)) from exc - def up_with_logs(self) -> list[str]: + def up_with_logs(self) -> list[LogEvent]: try: all_logs = [] for reconcile_key in self.reconcile_order: @@ -104,7 +104,9 @@ class KubernetesController(BaseController): continue reconciler = reconciler_cls(self) reconciler.up() - all_logs += [f"{reconcile_key.title()}: {x['event']}" for x in logs] + for log in logs: + log.logger = reconcile_key.title() + all_logs.extend(logs) return all_logs except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc: raise ControllerException(str(exc)) from exc @@ -122,7 +124,7 @@ class KubernetesController(BaseController): except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc: raise ControllerException(str(exc)) from exc - def down_with_logs(self) -> list[str]: + def down_with_logs(self) -> list[LogEvent]: try: all_logs = [] for reconcile_key in self.reconcile_order: @@ -135,7 +137,9 @@ class KubernetesController(BaseController): continue reconciler = reconciler_cls(self) reconciler.down() - all_logs += [f"{reconcile_key.title()}: {x['event']}" for x in logs] + for log in logs: + log.logger = reconcile_key.title() + all_logs.extend(logs) return all_logs except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc: raise ControllerException(str(exc)) from exc diff --git a/authentik/outposts/tasks.py b/authentik/outposts/tasks.py index d2db180a49..cb4dac236a 100644 --- a/authentik/outposts/tasks.py +++ b/authentik/outposts/tasks.py @@ -149,10 +149,8 @@ def outpost_controller( if not controller_type: return with controller_type(outpost, outpost.service_connection) as controller: - logs = getattr(controller, f"{action}_with_logs")() LOGGER.debug("---------------Outpost Controller logs starting----------------") - for log in logs: - LOGGER.debug(log) + logs = getattr(controller, f"{action}_with_logs")() LOGGER.debug("-----------------Outpost Controller logs end-------------------") except (ControllerException, ServiceConnectionInvalid) as exc: self.set_error(exc) diff --git a/authentik/policies/api/exec.py b/authentik/policies/api/exec.py index 99a911e3e9..a3b6285c3b 100644 --- a/authentik/policies/api/exec.py +++ b/authentik/policies/api/exec.py @@ -1,10 +1,11 @@ """Serializer for policy execution""" -from rest_framework.fields import BooleanField, CharField, DictField, ListField +from rest_framework.fields import BooleanField, CharField, ListField from rest_framework.relations import PrimaryKeyRelatedField from authentik.core.api.utils import JSONDictField, PassiveSerializer from authentik.core.models import User +from authentik.events.logs import LogEventSerializer class PolicyTestSerializer(PassiveSerializer): @@ -19,4 +20,4 @@ class PolicyTestResultSerializer(PassiveSerializer): passing = BooleanField() messages = ListField(child=CharField(), read_only=True) - log_messages = ListField(child=DictField(), read_only=True) + log_messages = LogEventSerializer(many=True, read_only=True) diff --git a/authentik/policies/api/policies.py b/authentik/policies/api/policies.py index b77f37755f..8b1d2eba3e 100644 --- a/authentik/policies/api/policies.py +++ b/authentik/policies/api/policies.py @@ -11,12 +11,11 @@ from rest_framework.response import Response from rest_framework.serializers import ModelSerializer, SerializerMethodField from rest_framework.viewsets import GenericViewSet from structlog.stdlib import get_logger -from structlog.testing import capture_logs from authentik.core.api.applications import user_app_cache_key from authentik.core.api.used_by import UsedByMixin from authentik.core.api.utils import CacheSerializer, MetaNameSerializer, TypeCreateSerializer -from authentik.events.utils import sanitize_dict +from authentik.events.logs import LogEventSerializer, capture_logs from authentik.lib.utils.reflection import all_subclasses from authentik.policies.api.exec import PolicyTestResultSerializer, PolicyTestSerializer from authentik.policies.models import Policy, PolicyBinding @@ -166,9 +165,9 @@ class PolicyViewSet( result = proc.execute() log_messages = [] for log in logs: - if log.get("process", "") == "PolicyProcess": + if log.attributes.get("process", "") == "PolicyProcess": continue - log_messages.append(sanitize_dict(log)) + log_messages.append(LogEventSerializer(log).data) result.log_messages = log_messages response = PolicyTestResultSerializer(result) return Response(response.data) diff --git a/authentik/policies/types.py b/authentik/policies/types.py index d1c06677ae..dd95f6e7d9 100644 --- a/authentik/policies/types.py +++ b/authentik/policies/types.py @@ -13,6 +13,7 @@ from authentik.events.context_processors.base import get_context_processors if TYPE_CHECKING: from authentik.core.models import User + from authentik.events.logs import LogEvent from authentik.policies.models import PolicyBinding LOGGER = get_logger() @@ -74,7 +75,7 @@ class PolicyResult: source_binding: PolicyBinding | None source_results: list[PolicyResult] | None - log_messages: list[dict] | None + log_messages: list[LogEvent] | None def __init__(self, passing: bool, *messages: str): self.passing = passing diff --git a/authentik/providers/oauth2/views/device_finish.py b/authentik/providers/oauth2/views/device_finish.py index e543510956..61bda6ae3e 100644 --- a/authentik/providers/oauth2/views/device_finish.py +++ b/authentik/providers/oauth2/views/device_finish.py @@ -25,7 +25,7 @@ class OAuthDeviceCodeFinishChallengeResponse(ChallengeResponse): class OAuthDeviceCodeFinishStage(ChallengeStageView): - """Stage show at the end of a device flow""" + """Stage to finish the OAuth device code flow""" response_class = OAuthDeviceCodeFinishChallengeResponse diff --git a/authentik/providers/oauth2/views/device_init.py b/authentik/providers/oauth2/views/device_init.py index a758cb1c0e..d1f514c1cf 100644 --- a/authentik/providers/oauth2/views/device_init.py +++ b/authentik/providers/oauth2/views/device_init.py @@ -3,7 +3,7 @@ from django.http import HttpRequest, HttpResponse from django.utils.translation import gettext as _ from django.views import View -from rest_framework.exceptions import ErrorDetail +from rest_framework.exceptions import ValidationError from rest_framework.fields import CharField, IntegerField from structlog.stdlib import get_logger @@ -57,6 +57,7 @@ def validate_code(code: int, request: HttpRequest) -> HttpResponse | None: scope_descriptions = UserInfoView().get_scope_descriptions(token.scope, token.provider) planner = FlowPlanner(token.provider.authorization_flow) planner.allow_empty_flows = True + planner.use_cache = False try: plan = planner.plan( request, @@ -128,6 +129,13 @@ class OAuthDeviceCodeChallengeResponse(ChallengeResponse): code = IntegerField() component = CharField(default="ak-provider-oauth2-device-code") + def validate_code(self, code: int) -> HttpResponse | None: + """Validate code and save the returned http response""" + response = validate_code(code, self.stage.request) + if not response: + raise ValidationError("Invalid code", "invalid") + return response + class OAuthDeviceCodeStage(ChallengeStageView): """Flow challenge for users to enter device codes""" @@ -143,12 +151,4 @@ class OAuthDeviceCodeStage(ChallengeStageView): ) def challenge_valid(self, response: ChallengeResponse) -> HttpResponse: - code = response.validated_data["code"] - validation = validate_code(code, self.request) - if not validation: - response._errors.setdefault("code", []) - response._errors["code"].append(ErrorDetail(_("Invalid code"), "invalid")) - return self.challenge_invalid(response) - # Run cancel to cleanup the current flow - self.executor.cancel() - return validation + return response.validated_data["code"] diff --git a/go.mod b/go.mod index 5ca65c248d..94d1bf9848 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/spf13/cobra v1.8.0 github.com/stretchr/testify v1.9.0 github.com/wwt/guac v1.3.2 - goauthentik.io/api/v3 v3.2024022.6 + goauthentik.io/api/v3 v3.2024022.7 golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab golang.org/x/oauth2 v0.18.0 golang.org/x/sync v0.6.0 diff --git a/go.sum b/go.sum index 68df409314..9553cefd5e 100644 --- a/go.sum +++ b/go.sum @@ -280,8 +280,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= -goauthentik.io/api/v3 v3.2024022.6 h1:Fs6+EEKw8Z1mgmKGMxOqZnepJoRXPXR/QS01FC0JPJ8= -goauthentik.io/api/v3 v3.2024022.6/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw= +goauthentik.io/api/v3 v3.2024022.7 h1:VR9OmcZvTzPSjit2Dx2EoHrLc9v9XRyjPXNpnGISWWM= +goauthentik.io/api/v3 v3.2024022.7/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= diff --git a/poetry.lock b/poetry.lock index ace284b120..179a136e2c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1142,13 +1142,13 @@ bcrypt = ["bcrypt"] [[package]] name = "django-filter" -version = "24.1" +version = "24.2" description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically." optional = false python-versions = ">=3.8" files = [ - {file = "django-filter-24.1.tar.gz", hash = "sha256:65cb43ce272077e5ac6aae1054d76c121cd6b552e296a82a13921e9371baf8c1"}, - {file = "django_filter-24.1-py3-none-any.whl", hash = "sha256:335bcae6cbd3e984b024841070f567b22faea57594f27d37c52f8f131f8d8621"}, + {file = "django-filter-24.2.tar.gz", hash = "sha256:48e5fc1da3ccd6ca0d5f9bb550973518ce977a4edde9d2a8a154a7f4f0b9f96e"}, + {file = "django_filter-24.2-py3-none-any.whl", hash = "sha256:df2ee9857e18d38bed203c8745f62a803fa0f31688c9fe6f8e868120b1848e48"}, ] [package.dependencies] @@ -3332,22 +3332,20 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "requests-mock" -version = "1.11.0" +version = "1.12.1" description = "Mock out responses from the requests package" optional = false -python-versions = "*" +python-versions = ">=3.5" files = [ - {file = "requests-mock-1.11.0.tar.gz", hash = "sha256:ef10b572b489a5f28e09b708697208c4a3b2b89ef80a9f01584340ea357ec3c4"}, - {file = "requests_mock-1.11.0-py2.py3-none-any.whl", hash = "sha256:f7fae383f228633f6bececebdab236c478ace2284d6292c6e7e2867b9ab74d15"}, + {file = "requests-mock-1.12.1.tar.gz", hash = "sha256:e9e12e333b525156e82a3c852f22016b9158220d2f47454de9cae8a77d371401"}, + {file = "requests_mock-1.12.1-py2.py3-none-any.whl", hash = "sha256:b1e37054004cdd5e56c84454cc7df12b25f90f382159087f4b6915aaeef39563"}, ] [package.dependencies] -requests = ">=2.3,<3" -six = "*" +requests = ">=2.22,<3" [package.extras] fixture = ["fixtures"] -test = ["fixtures", "mock", "purl", "pytest", "requests-futures", "sphinx", "testtools"] [[package]] name = "requests-oauthlib" @@ -3552,13 +3550,13 @@ crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"] [[package]] name = "selenium" -version = "4.18.1" +version = "4.19.0" description = "" optional = false python-versions = ">=3.8" files = [ - {file = "selenium-4.18.1-py3-none-any.whl", hash = "sha256:b24a3cdd2d47c29832e81345bfcde0c12bb608738013e53c781b211b418df241"}, - {file = "selenium-4.18.1.tar.gz", hash = "sha256:a11f67afa8bfac6b77e148c987b33f6b14eb1cae4d352722a75de1f26e3f0ae2"}, + {file = "selenium-4.19.0-py3-none-any.whl", hash = "sha256:5b4f49240d61e687a73f7968ae2517d403882aae3550eae2a229c745e619f1d9"}, + {file = "selenium-4.19.0.tar.gz", hash = "sha256:d9dfd6d0b021d71d0a48b865fe7746490ba82b81e9c87b212360006629eb1853"}, ] [package.dependencies] @@ -3570,13 +3568,13 @@ urllib3 = {version = ">=1.26,<3", extras = ["socks"]} [[package]] name = "sentry-sdk" -version = "1.43.0" +version = "1.44.0" description = "Python client for Sentry (https://sentry.io)" optional = false python-versions = "*" files = [ - {file = "sentry-sdk-1.43.0.tar.gz", hash = "sha256:41df73af89d22921d8733714fb0fc5586c3461907e06688e6537d01a27e0e0f6"}, - {file = "sentry_sdk-1.43.0-py2.py3-none-any.whl", hash = "sha256:8d768724839ca18d7b4c7463ef7528c40b7aa2bfbf7fe554d5f9a7c044acfd36"}, + {file = "sentry-sdk-1.44.0.tar.gz", hash = "sha256:f7125a9235795811962d52ff796dc032cd1d0dd98b59beaced8380371cd9c13c"}, + {file = "sentry_sdk-1.44.0-py2.py3-none-any.whl", hash = "sha256:eb65289da013ca92fad2694851ad2f086aa3825e808dc285bd7dcaf63602bb18"}, ] [package.dependencies] @@ -4203,13 +4201,13 @@ files = [ [[package]] name = "webauthn" -version = "2.0.0" +version = "2.1.0" description = "Pythonic WebAuthn" optional = false python-versions = "*" files = [ - {file = "webauthn-2.0.0-py3-none-any.whl", hash = "sha256:644dc68af5caaade06be6a2a2278775e85116e92dd755ad7a49d992d51c82033"}, - {file = "webauthn-2.0.0.tar.gz", hash = "sha256:12cc1759da98668b8242badc37c4129df300f89d89f5c183fac80e7b33c41dfd"}, + {file = "webauthn-2.1.0-py3-none-any.whl", hash = "sha256:9e1cf916e5ed7c01d54a6dfcc19dacbd2b87b81d2648f001b1fcbcb7aa2ff130"}, + {file = "webauthn-2.1.0.tar.gz", hash = "sha256:b196a4246c2818820857ba195c6e6e5398c761117f2269e3d2deab11c7995fc4"}, ] [package.dependencies] diff --git a/schema.yml b/schema.yml index be2ea52f80..01b43f6e52 100644 --- a/schema.yml +++ b/schema.yml @@ -33782,8 +33782,7 @@ components: logs: type: array items: - type: object - additionalProperties: {} + $ref: '#/components/schemas/LogEvent' readOnly: true success: type: boolean @@ -35515,6 +35514,48 @@ components: type: string required: - link + LogEvent: + type: object + description: Single log message with all context logged. + properties: + timestamp: + type: string + format: date-time + log_level: + $ref: '#/components/schemas/LogLevelEnum' + logger: + type: string + event: + type: string + attributes: + type: object + additionalProperties: {} + required: + - attributes + - event + - log_level + - logger + - timestamp + LogLevelEnum: + enum: + - critical + - exception + - error + - warn + - warning + - info + - debug + - notset + type: string + description: |- + * `critical` - critical + * `exception` - exception + * `error` - error + * `warn` - warn + * `warning` - warning + * `info` - info + * `debug` - debug + * `notset` - notset LoginChallengeTypes: oneOf: - $ref: '#/components/schemas/RedirectChallenge' @@ -41309,8 +41350,7 @@ components: log_messages: type: array items: - type: object - additionalProperties: {} + $ref: '#/components/schemas/LogEvent' readOnly: true required: - log_messages @@ -44324,7 +44364,7 @@ components: messages: type: array items: - type: string + $ref: '#/components/schemas/LogEvent' required: - description - duration diff --git a/tests/integration/test_proxy_kubernetes.py b/tests/integration/test_proxy_kubernetes.py index 19b37dcf64..477beb3851 100644 --- a/tests/integration/test_proxy_kubernetes.py +++ b/tests/integration/test_proxy_kubernetes.py @@ -26,12 +26,6 @@ class TestProxyKubernetes(TestCase): outpost_connection_discovery() self.controller = None - def tearDown(self) -> None: - if self.controller: - for log in self.controller.down_with_logs(): - LOGGER.info(log) - return super().tearDown() - @pytest.mark.timeout(120) def test_kubernetes_controller_static(self): """Test Kubernetes Controller""" diff --git a/tests/wdio/package-lock.json b/tests/wdio/package-lock.json index 93ac8546a3..ca4fdabbee 100644 --- a/tests/wdio/package-lock.json +++ b/tests/wdio/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "@goauthentik/web-tests", "dependencies": { - "chromedriver": "^123.0.0" + "chromedriver": "^123.0.1" }, "devDependencies": { "@trivago/prettier-plugin-sort-imports": "^4.3.0", @@ -18,7 +18,7 @@ "@wdio/spec-reporter": "^8.32.4", "eslint": "^8.57.0", "eslint-config-google": "^0.14.0", - "eslint-plugin-sonarjs": "^0.24.0", + "eslint-plugin-sonarjs": "^0.25.0", "npm-run-all": "^4.1.5", "prettier": "^3.2.5", "ts-node": "^10.9.2", @@ -2084,9 +2084,9 @@ } }, "node_modules/chromedriver": { - "version": "123.0.0", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-123.0.0.tgz", - "integrity": "sha512-OE9mpxXwbFy5LncAisqXm1aEzuLPtEMORIxyYIn9uT7N8rquJWyoip6w9Rytub3o2gnynW9+PFOTPVTldaYrtw==", + "version": "123.0.1", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-123.0.1.tgz", + "integrity": "sha512-YQUIP/zdlzDIRCZNCv6rEVDSY4RAxo/tDL0OiGPPuai+z8unRNqJr/9V6XTBypVFyDheXNalKt9QxEqdMPuLAQ==", "hasInstallScript": true, "dependencies": { "@testim/chrome-version": "^1.1.4", @@ -3114,9 +3114,9 @@ } }, "node_modules/eslint-plugin-sonarjs": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.24.0.tgz", - "integrity": "sha512-87zp50mbbNrSTuoEOebdRQBPa0mdejA5UEjyuScyIw8hEpEjfWP89Qhkq5xVZfVyVSRQKZc9alVm7yRKQvvUmg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.25.0.tgz", + "integrity": "sha512-DaZOtpUucEZbvowgKxVFwICV6r0h7jSCAx0IHICvCowP+etFussnhtaiCPSnYAuwVJ+P/6UFUhkv7QJklpXFyA==", "dev": true, "engines": { "node": ">=16" diff --git a/tests/wdio/package.json b/tests/wdio/package.json index a23d93f127..dd632a99f3 100644 --- a/tests/wdio/package.json +++ b/tests/wdio/package.json @@ -12,7 +12,7 @@ "@wdio/spec-reporter": "^8.32.4", "eslint": "^8.57.0", "eslint-config-google": "^0.14.0", - "eslint-plugin-sonarjs": "^0.24.0", + "eslint-plugin-sonarjs": "^0.25.0", "npm-run-all": "^4.1.5", "prettier": "^3.2.5", "ts-node": "^10.9.2", @@ -32,6 +32,6 @@ "node": ">=20" }, "dependencies": { - "chromedriver": "^123.0.0" + "chromedriver": "^123.0.1" } } diff --git a/web/package-lock.json b/web/package-lock.json index 31275a87a9..eb42960fa0 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -11,13 +11,13 @@ "dependencies": { "@codemirror/lang-html": "^6.4.8", "@codemirror/lang-javascript": "^6.2.2", - "@codemirror/lang-python": "^6.1.4", + "@codemirror/lang-python": "^6.1.5", "@codemirror/lang-xml": "^6.1.0", "@codemirror/legacy-modes": "^6.3.3", "@codemirror/theme-one-dark": "^6.1.2", "@formatjs/intl-listformat": "^7.5.5", "@fortawesome/fontawesome-free": "^6.5.1", - "@goauthentik/api": "^2024.2.2-1711460547", + "@goauthentik/api": "^2024.2.2-1711643691", "@lit-labs/task": "^3.1.0", "@lit/context": "^1.1.0", "@lit/localize": "^0.12.1", @@ -25,7 +25,7 @@ "@open-wc/lit-helpers": "^0.7.0", "@patternfly/elements": "^2.4.0", "@patternfly/patternfly": "^4.224.2", - "@sentry/browser": "^7.108.0", + "@sentry/browser": "^7.109.0", "@webcomponents/webcomponentsjs": "^2.8.0", "base64-js": "^1.5.1", "chart.js": "^4.4.2", @@ -59,7 +59,7 @@ "@jeysal/storybook-addon-css-user-preferences": "^0.2.0", "@lit/localize-tools": "^0.7.2", "@rollup/plugin-replace": "^5.0.5", - "@spotlightjs/spotlight": "^1.2.15", + "@spotlightjs/spotlight": "^1.2.16", "@storybook/addon-essentials": "^7.6.17", "@storybook/addon-links": "^7.6.17", "@storybook/api": "^7.6.17", @@ -84,10 +84,10 @@ "eslint-config-google": "^0.14.0", "eslint-plugin-custom-elements": "0.0.8", "eslint-plugin-lit": "^1.11.0", - "eslint-plugin-sonarjs": "^0.24.0", + "eslint-plugin-sonarjs": "^0.25.0", "eslint-plugin-storybook": "^0.8.0", "github-slugger": "^2.0.0", - "glob": "^10.3.10", + "glob": "^10.3.12", "lit-analyzer": "^2.0.3", "npm-run-all": "^4.1.5", "prettier": "^3.2.5", @@ -111,9 +111,9 @@ "@esbuild/darwin-arm64": "^0.20.1", "@esbuild/linux-amd64": "^0.18.11", "@esbuild/linux-arm64": "^0.20.1", - "@rollup/rollup-darwin-arm64": "4.13.0", - "@rollup/rollup-linux-arm64-gnu": "4.13.0", - "@rollup/rollup-linux-x64-gnu": "4.13.0" + "@rollup/rollup-darwin-arm64": "4.13.2", + "@rollup/rollup-linux-arm64-gnu": "4.13.2", + "@rollup/rollup-linux-x64-gnu": "4.13.2" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -2164,8 +2164,9 @@ } }, "node_modules/@codemirror/lang-python": { - "version": "6.1.4", - "license": "MIT", + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/@codemirror/lang-python/-/lang-python-6.1.5.tgz", + "integrity": "sha512-hCm+8X6wrnXJCGf+QhmFu1AXkdTVG7dHy0Ly6SI1N3SRPptaMvwX6oNQonOXOMPvmcjiB0xq342KAxX3BYpijw==", "dependencies": { "@codemirror/autocomplete": "^6.3.2", "@codemirror/language": "^6.8.0", @@ -2820,9 +2821,9 @@ } }, "node_modules/@goauthentik/api": { - "version": "2024.2.2-1711460547", - "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2024.2.2-1711460547.tgz", - "integrity": "sha512-MnU9SVpMtg+Tu0xASgPh0TXVLKo1n7W74z5Yh0WccirsrqlhBTNBgDFbAJKV4/EVt0b+c0/GEa9JxkSt3+DxaA==" + "version": "2024.2.2-1711643691", + "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2024.2.2-1711643691.tgz", + "integrity": "sha512-QHe+3gaNRkId54AuqndYNL0e5kG8nPlH4OjOYOPqOr3u70rxby63PBSPgSRKgqsigBpZufhQGsUBAPmpR8Hv0w==" }, "node_modules/@hcaptcha/types": { "version": "1.0.3", @@ -4259,9 +4260,9 @@ "peer": true }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz", - "integrity": "sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.2.tgz", + "integrity": "sha512-mCMlpzlBgOTdaFs83I4XRr8wNPveJiJX1RLfv4hggyIVhfB5mJfN4P8Z6yKh+oE4Luz+qq1P3kVdWrCKcMYrrA==", "cpu": [ "arm64" ], @@ -4299,9 +4300,9 @@ "peer": true }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz", - "integrity": "sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.2.tgz", + "integrity": "sha512-L1+D8/wqGnKQIlh4Zre9i4R4b4noxzH5DDciyahX4oOz62CphY7WDWqJoQ66zNR4oScLNOqQJfNSIAe/6TPUmQ==", "cpu": [ "arm64" ], @@ -4339,9 +4340,9 @@ "peer": true }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz", - "integrity": "sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.2.tgz", + "integrity": "sha512-xXMLUAMzrtsvh3cZ448vbXqlUa7ZL8z0MwHp63K2IIID2+DeP5iWIT6g1SN7hg1VxPzqx0xZdiDM9l4n9LRU1A==", "cpu": [ "x64" ], @@ -4407,102 +4408,102 @@ "peer": true }, "node_modules/@sentry-internal/feedback": { - "version": "7.108.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.108.0.tgz", - "integrity": "sha512-8JcgZEnk1uWrXJhsd3iRvFtEiVeaWOEhN0NZwhwQXHfvODqep6JtrkY1yCIyxbpA37aZmrPc2JhyotRERGfUjg==", + "version": "7.109.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.109.0.tgz", + "integrity": "sha512-EL7N++poxvJP9rYvh6vSu24tsKkOveNCcCj4IM7+irWPjsuD2GLYYlhp/A/Mtt9l7iqO4plvtiQU5HGk7smcTQ==", "dependencies": { - "@sentry/core": "7.108.0", - "@sentry/types": "7.108.0", - "@sentry/utils": "7.108.0" + "@sentry/core": "7.109.0", + "@sentry/types": "7.109.0", + "@sentry/utils": "7.109.0" }, "engines": { "node": ">=12" } }, "node_modules/@sentry-internal/replay-canvas": { - "version": "7.108.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-7.108.0.tgz", - "integrity": "sha512-R5tvjGqWUV5vSk0N1eBgVW7wIADinrkfDEBZ9FyKP2mXHBobsyNGt30heJDEqYmVqluRqjU2NuIRapsnnrpGnA==", + "version": "7.109.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-7.109.0.tgz", + "integrity": "sha512-Lh/K60kmloR6lkPUcQP0iamw7B/MdEUEx/ImAx4tUSMrLj+IoUEcq/ECgnnVyQkJq59+8nPEKrVLt7x6PUPEjw==", "dependencies": { - "@sentry/core": "7.108.0", - "@sentry/replay": "7.108.0", - "@sentry/types": "7.108.0", - "@sentry/utils": "7.108.0" + "@sentry/core": "7.109.0", + "@sentry/replay": "7.109.0", + "@sentry/types": "7.109.0", + "@sentry/utils": "7.109.0" }, "engines": { "node": ">=12" } }, "node_modules/@sentry-internal/tracing": { - "version": "7.108.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.108.0.tgz", - "integrity": "sha512-zuK5XsTsb+U+hgn3SPetYDAogrXsM16U/LLoMW7+TlC6UjlHGYQvmX3o+M2vntejoU1QZS8m1bCAZSMWEypAEw==", + "version": "7.109.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.109.0.tgz", + "integrity": "sha512-PzK/joC5tCuh2R/PRh+7dp+uuZl7pTsBIjPhVZHMTtb9+ls65WkdZJ1/uKXPouyz8NOo9Xok7aEvEo9seongyw==", "dependencies": { - "@sentry/core": "7.108.0", - "@sentry/types": "7.108.0", - "@sentry/utils": "7.108.0" + "@sentry/core": "7.109.0", + "@sentry/types": "7.109.0", + "@sentry/utils": "7.109.0" }, "engines": { "node": ">=8" } }, "node_modules/@sentry/browser": { - "version": "7.108.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.108.0.tgz", - "integrity": "sha512-FNpzsdTvGvdHJMUelqEouUXMZU7jC+dpN7CdT6IoHVVFEkoAgrjMVUhXZoQ/dmCkdKWHmFSQhJ8Fm6V+e9Aq0A==", + "version": "7.109.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.109.0.tgz", + "integrity": "sha512-yx+OFG+Ab9qUDDgV9ZDv8M9O9Mqr0fjKta/LMlWALYLjzkMvxsPlRPFj7oMBlHqOTVLDeg7lFYmsA8wyWQ8Z8g==", "dependencies": { - "@sentry-internal/feedback": "7.108.0", - "@sentry-internal/replay-canvas": "7.108.0", - "@sentry-internal/tracing": "7.108.0", - "@sentry/core": "7.108.0", - "@sentry/replay": "7.108.0", - "@sentry/types": "7.108.0", - "@sentry/utils": "7.108.0" + "@sentry-internal/feedback": "7.109.0", + "@sentry-internal/replay-canvas": "7.109.0", + "@sentry-internal/tracing": "7.109.0", + "@sentry/core": "7.109.0", + "@sentry/replay": "7.109.0", + "@sentry/types": "7.109.0", + "@sentry/utils": "7.109.0" }, "engines": { "node": ">=8" } }, "node_modules/@sentry/core": { - "version": "7.108.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.108.0.tgz", - "integrity": "sha512-I/VNZCFgLASxHZaD0EtxZRM34WG9w2gozqgrKGNMzAymwmQ3K9g/1qmBy4e6iS3YRptb7J5UhQkZQHrcwBbjWQ==", + "version": "7.109.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.109.0.tgz", + "integrity": "sha512-xwD4U0IlvvlE/x/g/W1I8b4Cfb16SsCMmiEuBf6XxvAa3OfWBxKoqLifb3GyrbxMC4LbIIZCN/SvLlnGJPgszA==", "dependencies": { - "@sentry/types": "7.108.0", - "@sentry/utils": "7.108.0" + "@sentry/types": "7.109.0", + "@sentry/utils": "7.109.0" }, "engines": { "node": ">=8" } }, "node_modules/@sentry/replay": { - "version": "7.108.0", - "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.108.0.tgz", - "integrity": "sha512-jo8fDOzcZJclP1+4n9jUtVxTlBFT9hXwxhAMrhrt70FV/nfmCtYQMD3bzIj79nwbhUtFP6pN39JH1o7Xqt1hxQ==", + "version": "7.109.0", + "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.109.0.tgz", + "integrity": "sha512-hCDjbTNO7ErW/XsaBXlyHFsUhneyBUdTec1Swf98TFEfVqNsTs6q338aUcaR8dGRLbLrJ9YU9D1qKq++v5h2CA==", "dependencies": { - "@sentry-internal/tracing": "7.108.0", - "@sentry/core": "7.108.0", - "@sentry/types": "7.108.0", - "@sentry/utils": "7.108.0" + "@sentry-internal/tracing": "7.109.0", + "@sentry/core": "7.109.0", + "@sentry/types": "7.109.0", + "@sentry/utils": "7.109.0" }, "engines": { "node": ">=12" } }, "node_modules/@sentry/types": { - "version": "7.108.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.108.0.tgz", - "integrity": "sha512-bKtHITmBN3kqtqE5eVvL8mY8znM05vEodENwRpcm6TSrrBjC2RnwNWVwGstYDdHpNfFuKwC8mLY9bgMJcENo8g==", + "version": "7.109.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.109.0.tgz", + "integrity": "sha512-egCBnDv3YpVFoNzRLdP0soVrxVLCQ+rovREKJ1sw3rA2/MFH9WJ+DZZexsX89yeAFzy1IFsCp7/dEqudusml6g==", "engines": { "node": ">=8" } }, "node_modules/@sentry/utils": { - "version": "7.108.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.108.0.tgz", - "integrity": "sha512-a45yEFD5qtgZaIFRAcFkG8C8lnDzn6t4LfLXuV4OafGAy/3ZAN3XN8wDnrruHkiUezSSANGsLg3bXaLW/JLvJw==", + "version": "7.109.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.109.0.tgz", + "integrity": "sha512-3RjxMOLMBwZ5VSiH84+o/3NY2An4Zldjz0EbfEQNRY9yffRiCPJSQiCJID8EoylCFOh/PAhPimBhqbtWJxX6iw==", "dependencies": { - "@sentry/types": "7.108.0" + "@sentry/types": "7.109.0" }, "engines": { "node": ">=8" @@ -4514,9 +4515,9 @@ "license": "MIT" }, "node_modules/@spotlightjs/overlay": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@spotlightjs/overlay/-/overlay-1.8.1.tgz", - "integrity": "sha512-t8S2b6AxgDfDoPls3CU7uABLdKx3g8cCXQWEHOICC1i7MYUSQLFMDpWzFWTEjN0XA8MGwNf/QKNlZ/HhaKTzJw==", + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@spotlightjs/overlay/-/overlay-1.8.2.tgz", + "integrity": "sha512-g3pzaJFKK67pBIl72qSNoFJIfP/dmdFoSPWZZQW6MKAdU7IOY5yf3BB52xEc6iSfeLGG/KpYNThefpobX3hb7Q==", "dev": true }, "node_modules/@spotlightjs/sidecar": { @@ -4528,12 +4529,12 @@ } }, "node_modules/@spotlightjs/spotlight": { - "version": "1.2.15", - "resolved": "https://registry.npmjs.org/@spotlightjs/spotlight/-/spotlight-1.2.15.tgz", - "integrity": "sha512-M0VTAyameAsK9kjI9k31CehTLJMqUdOvv7DSOr27dcioytBV0uC0l8w7ngHWxdqCOTpbruEs8EIrbQ0T9b4YZQ==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@spotlightjs/spotlight/-/spotlight-1.2.16.tgz", + "integrity": "sha512-grqK7Qwzz0zJKaM4+u/0DS81gEGKkUsKwXGY1kA07rXsbp6ilT62JWI1tQDgYHb1i3MbR2ch0EuMT476CAtA+A==", "dev": true, "dependencies": { - "@spotlightjs/overlay": "1.8.1", + "@spotlightjs/overlay": "1.8.2", "@spotlightjs/sidecar": "1.4.0" }, "bin": { @@ -10089,9 +10090,10 @@ "license": "MIT" }, "node_modules/eslint-plugin-sonarjs": { - "version": "0.24.0", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.25.0.tgz", + "integrity": "sha512-DaZOtpUucEZbvowgKxVFwICV6r0h7jSCAx0IHICvCowP+etFussnhtaiCPSnYAuwVJ+P/6UFUhkv7QJklpXFyA==", "dev": true, - "license": "LGPL-3.0-only", "engines": { "node": ">=16" }, @@ -11234,16 +11236,16 @@ "license": "ISC" }, "node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", "dev": true, "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", + "jackspeak": "^2.3.6", "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" }, "bin": { "glob": "dist/esm/bin.mjs" @@ -11271,6 +11273,15 @@ "dev": true, "license": "BSD-2-Clause" }, + "node_modules/glob/node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/globals": { "version": "11.12.0", "dev": true, @@ -14569,11 +14580,12 @@ "license": "MIT" }, "node_modules/path-scurry": { - "version": "1.10.1", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", + "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", "dev": true, - "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", + "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { @@ -14585,8 +14597,9 @@ }, "node_modules/path-scurry/node_modules/lru-cache": { "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", "dev": true, - "license": "ISC", "engines": { "node": "14 || >=16.14" } diff --git a/web/package.json b/web/package.json index 4f3e5910fd..d5b9670092 100644 --- a/web/package.json +++ b/web/package.json @@ -32,13 +32,13 @@ "dependencies": { "@codemirror/lang-html": "^6.4.8", "@codemirror/lang-javascript": "^6.2.2", - "@codemirror/lang-python": "^6.1.4", + "@codemirror/lang-python": "^6.1.5", "@codemirror/lang-xml": "^6.1.0", "@codemirror/legacy-modes": "^6.3.3", "@codemirror/theme-one-dark": "^6.1.2", "@formatjs/intl-listformat": "^7.5.5", "@fortawesome/fontawesome-free": "^6.5.1", - "@goauthentik/api": "^2024.2.2-1711460547", + "@goauthentik/api": "^2024.2.2-1711643691", "@lit-labs/task": "^3.1.0", "@lit/context": "^1.1.0", "@lit/localize": "^0.12.1", @@ -46,7 +46,7 @@ "@open-wc/lit-helpers": "^0.7.0", "@patternfly/elements": "^2.4.0", "@patternfly/patternfly": "^4.224.2", - "@sentry/browser": "^7.108.0", + "@sentry/browser": "^7.109.0", "@webcomponents/webcomponentsjs": "^2.8.0", "base64-js": "^1.5.1", "chart.js": "^4.4.2", @@ -80,7 +80,7 @@ "@jeysal/storybook-addon-css-user-preferences": "^0.2.0", "@lit/localize-tools": "^0.7.2", "@rollup/plugin-replace": "^5.0.5", - "@spotlightjs/spotlight": "^1.2.15", + "@spotlightjs/spotlight": "^1.2.16", "@storybook/addon-essentials": "^7.6.17", "@storybook/addon-links": "^7.6.17", "@storybook/api": "^7.6.17", @@ -105,10 +105,10 @@ "eslint-config-google": "^0.14.0", "eslint-plugin-custom-elements": "0.0.8", "eslint-plugin-lit": "^1.11.0", - "eslint-plugin-sonarjs": "^0.24.0", + "eslint-plugin-sonarjs": "^0.25.0", "eslint-plugin-storybook": "^0.8.0", "github-slugger": "^2.0.0", - "glob": "^10.3.10", + "glob": "^10.3.12", "lit-analyzer": "^2.0.3", "npm-run-all": "^4.1.5", "prettier": "^3.2.5", @@ -129,9 +129,9 @@ "@esbuild/darwin-arm64": "^0.20.1", "@esbuild/linux-amd64": "^0.18.11", "@esbuild/linux-arm64": "^0.20.1", - "@rollup/rollup-darwin-arm64": "4.13.0", - "@rollup/rollup-linux-arm64-gnu": "4.13.0", - "@rollup/rollup-linux-x64-gnu": "4.13.0" + "@rollup/rollup-darwin-arm64": "4.13.2", + "@rollup/rollup-linux-arm64-gnu": "4.13.2", + "@rollup/rollup-linux-x64-gnu": "4.13.2" }, "engines": { "node": ">=20" diff --git a/web/src/admin/applications/ApplicationCheckAccessForm.ts b/web/src/admin/applications/ApplicationCheckAccessForm.ts index 7f1c18e17d..7a2e1f7dab 100644 --- a/web/src/admin/applications/ApplicationCheckAccessForm.ts +++ b/web/src/admin/applications/ApplicationCheckAccessForm.ts @@ -1,5 +1,6 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import "@goauthentik/components/ak-status-label"; +import "@goauthentik/elements/events/LogViewer"; import { Form } from "@goauthentik/elements/forms/Form"; import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/SearchSelect"; @@ -83,28 +84,7 @@ export class ApplicationCheckAccessForm extends Form<{ forUser: number }> {
- ${(this.result?.logMessages || []).length > 0 - ? this.result?.logMessages?.map((m) => { - return html`
-
- ${m.log_level} -
-
-
- ${m.event} -
-
-
`; - }) - : html`
-
- ${msg("No log messages.")} -
-
`} +
diff --git a/web/src/admin/flows/FlowImportForm.ts b/web/src/admin/flows/FlowImportForm.ts index 310269ff88..8aa5a551c3 100644 --- a/web/src/admin/flows/FlowImportForm.ts +++ b/web/src/admin/flows/FlowImportForm.ts @@ -1,6 +1,7 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { SentryIgnoredError } from "@goauthentik/common/errors"; import "@goauthentik/components/ak-status-label"; +import "@goauthentik/elements/events/LogViewer"; import { Form } from "@goauthentik/elements/forms/Form"; import "@goauthentik/elements/forms/HorizontalFormElement"; @@ -55,28 +56,7 @@ export class FlowImportForm extends Form {
- ${(this.result?.logs || []).length > 0 - ? this.result?.logs?.map((m) => { - return html`
-
- ${m.log_level} -
-
-
- ${m.event} -
-
-
`; - }) - : html`
-
- ${msg("No log messages.")} -
-
`} +
diff --git a/web/src/admin/policies/PolicyTestForm.ts b/web/src/admin/policies/PolicyTestForm.ts index 50771abbdb..6e57301676 100644 --- a/web/src/admin/policies/PolicyTestForm.ts +++ b/web/src/admin/policies/PolicyTestForm.ts @@ -3,6 +3,7 @@ import { first } from "@goauthentik/common/utils"; import "@goauthentik/components/ak-status-label"; import "@goauthentik/elements/CodeMirror"; import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror"; +import "@goauthentik/elements/events/LogViewer"; import { Form } from "@goauthentik/elements/forms/Form"; import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/SearchSelect"; @@ -85,28 +86,7 @@ export class PolicyTestForm extends Form {
- ${(this.result?.logMessages || []).length > 0 - ? this.result?.logMessages?.map((m) => { - return html`
-
- ${m.log_level} -
-
-
- ${m.event} -
-
-
`; - }) - : html`
-
- ${msg("No log messages.")} -
-
`} +
diff --git a/web/src/admin/system-tasks/SystemTaskListPage.ts b/web/src/admin/system-tasks/SystemTaskListPage.ts index b2c9044b8f..e05a59c249 100644 --- a/web/src/admin/system-tasks/SystemTaskListPage.ts +++ b/web/src/admin/system-tasks/SystemTaskListPage.ts @@ -5,6 +5,7 @@ import { getRelativeTime } from "@goauthentik/common/utils"; import { PFColor } from "@goauthentik/elements/Label"; import "@goauthentik/elements/buttons/ActionButton"; import "@goauthentik/elements/buttons/SpinnerButton"; +import "@goauthentik/elements/events/LogViewer"; import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import { TableColumn } from "@goauthentik/elements/table/Table"; import { TablePage } from "@goauthentik/elements/table/TablePage"; @@ -95,9 +96,7 @@ export class SystemTaskListPage extends TablePage {
- ${item.messages.map((m) => { - return html`
  • ${m}
  • `; - })} +
    diff --git a/web/src/elements/events/LogViewer.ts b/web/src/elements/events/LogViewer.ts new file mode 100644 index 0000000000..33f9d24b8f --- /dev/null +++ b/web/src/elements/events/LogViewer.ts @@ -0,0 +1,114 @@ +import { getRelativeTime } from "@goauthentik/common/utils"; +import "@goauthentik/components/ak-status-label"; +import "@goauthentik/elements/EmptyState"; +import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table"; + +import { msg } from "@lit/localize"; +import { CSSResult, TemplateResult, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; + +import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; + +import { LogEvent, LogLevelEnum } from "@goauthentik/api"; + +@customElement("ak-log-viewer") +export class LogViewer extends Table { + @property({ attribute: false }) + logs?: LogEvent[] = []; + + expandable = true; + paginated = false; + + static get styles(): CSSResult[] { + return super.styles.concat(PFDescriptionList); + } + + async apiEndpoint(_page: number): Promise> { + return { + pagination: { + next: 0, + previous: 0, + count: 1, + current: 1, + totalPages: 1, + startIndex: 1, + endIndex: 1, + }, + results: this.logs || [], + }; + } + + renderEmpty(): TemplateResult { + return super.renderEmpty( + html` `, + ); + } + + renderExpanded(item: LogEvent): TemplateResult { + return html` +
    +
    +
    +
    + ${msg("Timestamp")} +
    +
    +
    + ${item.timestamp.toLocaleString()} +
    +
    +
    +
    +
    + ${msg("Attributes")} +
    +
    +
    +
    ${JSON.stringify(item.attributes, null, 4)}
    +
    +
    +
    +
    +
    + `; + } + + renderToolbarContainer(): TemplateResult { + return html``; + } + + columns(): TableColumn[] { + return [ + new TableColumn(msg("Time")), + new TableColumn(msg("Level")), + new TableColumn(msg("Event")), + new TableColumn(msg("Logger")), + ]; + } + + statusForItem(item: LogEvent): string { + switch (item.logLevel) { + case LogLevelEnum.Critical: + case LogLevelEnum.Error: + case LogLevelEnum.Exception: + return "error"; + case LogLevelEnum.Warn: + case LogLevelEnum.Warning: + return "warning"; + default: + return "info"; + } + } + + row(item: LogEvent): TemplateResult[] { + return [ + html`${getRelativeTime(item.timestamp)}`, + html``, + html`${item.event}`, + html`${item.logger}`, + ]; + } +} diff --git a/website/docs/events/transports.md b/website/docs/events/transports.md index 3e95fdc6ea..58f543a371 100644 --- a/website/docs/events/transports.md +++ b/website/docs/events/transports.md @@ -27,7 +27,7 @@ Starting in 2021.9, you can also select a Notification mapping. This allows you ```python return { - "foo": context['notification'].body, + "foo": request.context['notification'].body, } ``` diff --git a/website/docs/installation/air-gapped.md b/website/docs/installation/air-gapped.md deleted file mode 100644 index dc98f65904..0000000000 --- a/website/docs/installation/air-gapped.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: Air-gapped environments ---- - -## Outbound connections - -By default, authentik creates outbound connections to the following URLs: - -- https://version.goauthentik.io: Periodic update check -- https://goauthentik.io: Anonymous analytics on startup -- https://secure.gravatar.com: Avatars for users -- https://authentik.error-reporting.a7k.io: Error reporting - -To disable these outbound connections, set the following in your `.env` file: - -``` -AUTHENTIK_DISABLE_UPDATE_CHECK=true -AUTHENTIK_ERROR_REPORTING__ENABLED=false -AUTHENTIK_DISABLE_STARTUP_ANALYTICS=true -AUTHENTIK_AVATARS=initials -``` - -For a Helm-based install, set the following in your values.yaml file: - -```yaml -authentik: - avatars: none - error_reporting: - enabled: false - disable_update_check: true - disable_startup_analytics: true -``` - -## Container images - -Container images can be pulled from the following URLs: - -- ghcr.io/goauthentik/server (https://ghcr.io) -- beryju/authentik (https://index.docker.io) diff --git a/website/docs/installation/air-gapped.mdx b/website/docs/installation/air-gapped.mdx new file mode 100644 index 0000000000..45ef8a4362 --- /dev/null +++ b/website/docs/installation/air-gapped.mdx @@ -0,0 +1,68 @@ +--- +title: Air-gapped environments +--- + +## Outbound connections + +By default, authentik creates outbound connections to the following URLs: + +- https://version.goauthentik.io: Periodic update check +- https://goauthentik.io: Anonymous analytics on startup +- https://secure.gravatar.com: Avatars for users +- https://authentik.error-reporting.a7k.io: Error reporting + +To disable these outbound connections, set the following in your `.env` file: + +## Configuration options + +To see a list of all configuration options, see [here](./configuration.mdx). + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + + + +Add the following block to your `.env` file: + +```shell +AUTHENTIK_DISABLE_STARTUP_ANALYTICS=true +AUTHENTIK_DISABLE_UPDATE_CHECK=true +AUTHENTIK_ERROR_REPORTING__ENABLED=false +``` + +Afterwards, run the upgrade commands from the latest release notes. + + + +Add the following block to your `values.yml` file: + +```yaml +authentik: + error_reporting: + enabled: false + disable_update_check: true + disable_startup_analytics: true +``` + +Afterwards, run the upgrade commands from the latest release notes. + + + + +## Settings + +In addition to the configuration options above, the following [System settings](../core/settings.md) need to also be adjusted: + +- **Avatars**: By default this setting uses [Gravatar](https://secure.gravatar.com/). The option can be set to a combination of any of the other options, for example `initials` + +## Container images + +Container images can be pulled from the following URLs: + +- ghcr.io/goauthentik/server (https://ghcr.io) +- beryju/authentik (https://index.docker.io) diff --git a/website/integrations/services/immich/index.md b/website/integrations/services/immich/index.md index edc0f2b162..37aa27aef9 100644 --- a/website/integrations/services/immich/index.md +++ b/website/integrations/services/immich/index.md @@ -32,7 +32,10 @@ The following placeholders will be used: - **Redirect URIs/Origins (RegEx)**: :::note Please note that the following URIs are just examples. Be sure to include all of the domains / URLs that you will use to access Immich. - ::: - app.immich:/ - https://immich.company/auth/login - https://immich.company/user-settings + ::: + - app.immich:/ + - https://immich.company/auth/login + - https://immich.company/user-settings - **Signing Key**: authentik Self-signed Certificate - Leave everything else as default 2. Open the new provider you've just created. diff --git a/website/integrations/services/wekan/index.mdx b/website/integrations/services/wekan/index.mdx index f615a6a6f5..94d9afb3cd 100644 --- a/website/integrations/services/wekan/index.mdx +++ b/website/integrations/services/wekan/index.mdx @@ -49,8 +49,8 @@ environment: OAUTH2_ENABLED=true OAUTH2_USERINFO_ENDPOINT=/application/o/userinfo/ OAUTH2_TOKEN_ENDPOINT=/application/o/token/ OAUTH2_SECRET= - OAUTH2_ID_MAP=preferred_username - OAUTH2_USERNAME_MAP=preferred_username + OAUTH2_ID_MAP=sub + OAUTH2_USERNAME_MAP=email OAUTH2_FULLNAME_MAP=given_name OAUTH2_EMAIL_MAP=email ``` @@ -70,8 +70,8 @@ edit `.env` and add the following: OAUTH2_USERINFO_ENDPOINT='/application/o/userinfo/' OAUTH2_TOKEN_ENDPOINT='/application/o/token/' OAUTH2_SECRET='' - OAUTH2_ID_MAP='preferred_username' - OAUTH2_USERNAME_MAP='preferred_username' + OAUTH2_ID_MAP='sub' + OAUTH2_USERNAME_MAP='email' OAUTH2_FULLNAME_MAP='given_name' OAUTH2_EMAIL_MAP='email' ``` diff --git a/website/package-lock.json b/website/package-lock.json index 01f336432d..f3d8cf55c5 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -33,7 +33,7 @@ "@docusaurus/module-type-aliases": "3.1.1", "@docusaurus/tsconfig": "3.1.1", "@docusaurus/types": "3.1.1", - "@types/react": "^18.2.72", + "@types/react": "^18.2.73", "prettier": "3.2.5", "typescript": "~5.4.3" }, @@ -3999,9 +3999,9 @@ "integrity": "sha512-+0autS93xyXizIYiyL02FCY8N+KkKPhILhcUSA276HxzreZ16kl+cmwvV2qAM/PuCCwPXzOXOWhiPcw20uSFcA==" }, "node_modules/@types/react": { - "version": "18.2.72", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.72.tgz", - "integrity": "sha512-/e7GWxGzXQF7OJAua7UAYqYi/4VpXEfbGtmYQcAQwP3SjjjAXfybTf/JK5S+SaetB/ChXl8Y2g1hCsj7jDXxcg==", + "version": "18.2.73", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.73.tgz", + "integrity": "sha512-XcGdod0Jjv84HOC7N5ziY3x+qL0AfmubvKOZ9hJjJ2yd5EE+KYjWhdOjt387e9HPheHkdggF9atTifMRtyAaRA==", "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" diff --git a/website/package.json b/website/package.json index b5c0d5c389..4f231424cb 100644 --- a/website/package.json +++ b/website/package.json @@ -52,7 +52,7 @@ "@docusaurus/module-type-aliases": "3.1.1", "@docusaurus/tsconfig": "3.1.1", "@docusaurus/types": "3.1.1", - "@types/react": "^18.2.72", + "@types/react": "^18.2.73", "prettier": "3.2.5", "typescript": "~5.4.3" },