Compare commits
34 Commits
core/b2c-i
...
providers/
Author | SHA1 | Date | |
---|---|---|---|
739acf50f4 | |||
ac1f3332dc | |||
2c64f72ebc | |||
51a8670a13 | |||
b9f6cd9226 | |||
7010682122 | |||
0e82facfb4 | |||
afdff95453 | |||
b11f12b1db | |||
4df906e32c | |||
fee7abed7c | |||
d1a5d0dd7e | |||
d1e06b1c7e | |||
458b2b5c55 | |||
c0b1cd7674 | |||
8305a52ae2 | |||
b77cdfe96b | |||
0dcb261b4c | |||
46bddbf067 | |||
b8b6c0cd98 | |||
64fbbcf3e8 | |||
a4c6b76686 | |||
c8c7f77813 | |||
dde4314127 | |||
0b620f54f3 | |||
dc10ab0e66 | |||
8d92e3d78d | |||
ae66df6d9a | |||
ed3108fbd4 | |||
f2199f1712 | |||
e5810b31c5 | |||
d8b6a06522 | |||
c8ab6c728d | |||
e854623967 |
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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"""
|
||||
|
82
authentik/events/logs.py
Normal file
82
authentik/events/logs.py
Normal file
@ -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)
|
@ -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,
|
||||
|
@ -47,3 +47,4 @@ class FlowStageBindingViewSet(UsedByMixin, ModelViewSet):
|
||||
filterset_fields = "__all__"
|
||||
search_fields = ["stage__name"]
|
||||
ordering = ["order"]
|
||||
ordering_fields = ["order", "stage__name"]
|
||||
|
@ -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:
|
||||
|
@ -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())
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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"]
|
||||
|
2
go.mod
2
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.5
|
||||
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
|
||||
|
4
go.sum
4
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.5 h1:z1ZaVY/UpwpHAghf/PyYRSOQT7U9g8E2N23YlRB5BJQ=
|
||||
goauthentik.io/api/v3 v3.2024022.5/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=
|
||||
|
@ -35,7 +35,7 @@ type ProviderInstance struct {
|
||||
cert *tls.Certificate
|
||||
certUUID string
|
||||
outpostName string
|
||||
outpostPk int32
|
||||
providerPk int32
|
||||
searchAllowedGroups []*strfmt.UUID
|
||||
boundUsersMutex *sync.RWMutex
|
||||
boundUsers map[string]*flags.UserFlags
|
||||
|
@ -22,7 +22,7 @@ import (
|
||||
|
||||
func (ls *LDAPServer) getCurrentProvider(pk int32) *ProviderInstance {
|
||||
for _, p := range ls.providers {
|
||||
if p.outpostPk == pk {
|
||||
if p.providerPk == pk {
|
||||
return p
|
||||
}
|
||||
}
|
||||
@ -83,7 +83,7 @@ func (ls *LDAPServer) Refresh() error {
|
||||
gidStartNumber: provider.GetGidStartNumber(),
|
||||
mfaSupport: provider.GetMfaSupport(),
|
||||
outpostName: ls.ac.Outpost.Name,
|
||||
outpostPk: provider.Pk,
|
||||
providerPk: provider.Pk,
|
||||
}
|
||||
if kp := provider.Certificate.Get(); kp != nil {
|
||||
err := ls.cs.AddKeypair(*kp)
|
||||
|
@ -6,8 +6,10 @@ import (
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"goauthentik.io/internal/outpost/ldap/flags"
|
||||
)
|
||||
|
||||
func parseCIDRs(raw string) []*net.IPNet {
|
||||
@ -29,6 +31,25 @@ func parseCIDRs(raw string) []*net.IPNet {
|
||||
return cidrs
|
||||
}
|
||||
|
||||
func (rs *RadiusServer) getCurrentProvider(pk int32) *ProviderInstance {
|
||||
for _, p := range rs.providers {
|
||||
if p.providerPk == pk {
|
||||
return p
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rs *RadiusServer) getInvalidationFlow() string {
|
||||
req, _, err := rs.ac.Client.CoreApi.CoreBrandsCurrentRetrieve(context.Background()).Execute()
|
||||
if err != nil {
|
||||
rs.log.WithError(err).Warning("failed to fetch brand config")
|
||||
return ""
|
||||
}
|
||||
flow := req.GetFlowInvalidation()
|
||||
return flow
|
||||
}
|
||||
|
||||
func (rs *RadiusServer) Refresh() error {
|
||||
outposts, _, err := rs.ac.Client.OutpostsApi.OutpostsRadiusList(context.Background()).Execute()
|
||||
if err != nil {
|
||||
@ -37,17 +58,33 @@ func (rs *RadiusServer) Refresh() error {
|
||||
if len(outposts.Results) < 1 {
|
||||
return errors.New("no radius provider defined")
|
||||
}
|
||||
invalidationFlow := rs.getInvalidationFlow()
|
||||
providers := make([]*ProviderInstance, len(outposts.Results))
|
||||
for idx, provider := range outposts.Results {
|
||||
logger := log.WithField("logger", "authentik.outpost.radius").WithField("provider", provider.Name)
|
||||
|
||||
// Get existing instance so we can transfer boundUsers
|
||||
existing := rs.getCurrentProvider(provider.Pk)
|
||||
usersMutex := &sync.RWMutex{}
|
||||
users := make(map[string]*flags.UserFlags)
|
||||
if existing != nil {
|
||||
usersMutex = existing.boundUsersMutex
|
||||
// Shallow copy, no need to lock
|
||||
users = existing.boundUsers
|
||||
}
|
||||
|
||||
providers[idx] = &ProviderInstance{
|
||||
SharedSecret: []byte(provider.GetSharedSecret()),
|
||||
ClientNetworks: parseCIDRs(provider.GetClientNetworks()),
|
||||
MFASupport: provider.GetMfaSupport(),
|
||||
appSlug: provider.ApplicationSlug,
|
||||
flowSlug: provider.AuthFlowSlug,
|
||||
s: rs,
|
||||
log: logger,
|
||||
SharedSecret: []byte(provider.GetSharedSecret()),
|
||||
ClientNetworks: parseCIDRs(provider.GetClientNetworks()),
|
||||
MFASupport: provider.GetMfaSupport(),
|
||||
appSlug: provider.ApplicationSlug,
|
||||
authenticationFlowSlug: provider.AuthFlowSlug,
|
||||
invalidationFlowSlug: invalidationFlow,
|
||||
s: rs,
|
||||
log: logger,
|
||||
providerPk: provider.Pk,
|
||||
boundUsersMutex: usersMutex,
|
||||
boundUsers: users,
|
||||
}
|
||||
}
|
||||
rs.providers = providers
|
||||
|
@ -4,15 +4,17 @@ import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"goauthentik.io/internal/outpost/flow"
|
||||
"goauthentik.io/internal/outpost/ldap/flags"
|
||||
"goauthentik.io/internal/outpost/radius/metrics"
|
||||
"layeh.com/radius"
|
||||
"layeh.com/radius/rfc2865"
|
||||
"layeh.com/radius/rfc2866"
|
||||
)
|
||||
|
||||
func (rs *RadiusServer) Handle_AccessRequest(w radius.ResponseWriter, r *RadiusRequest) {
|
||||
username := rfc2865.UserName_GetString(r.Packet)
|
||||
|
||||
fe := flow.NewFlowExecutor(r.Context(), r.pi.flowSlug, r.pi.s.ac.Client.GetConfig(), log.Fields{
|
||||
fe := flow.NewFlowExecutor(r.Context(), r.pi.authenticationFlowSlug, r.pi.s.ac.Client.GetConfig(), log.Fields{
|
||||
"username": username,
|
||||
"client": r.RemoteAddr(),
|
||||
"requestId": r.ID(),
|
||||
@ -64,5 +66,28 @@ func (rs *RadiusServer) Handle_AccessRequest(w radius.ResponseWriter, r *RadiusR
|
||||
}).Inc()
|
||||
return
|
||||
}
|
||||
_ = w.Write(r.Response(radius.CodeAccessAccept))
|
||||
// Get user info to store in context
|
||||
userInfo, _, err := fe.ApiClient().CoreApi.CoreUsersMeRetrieve(r.Context()).Execute()
|
||||
if err != nil {
|
||||
metrics.RequestsRejected.With(prometheus.Labels{
|
||||
"outpost_name": rs.ac.Outpost.Name,
|
||||
"type": "bind",
|
||||
"reason": "user_info_fail",
|
||||
}).Inc()
|
||||
r.Log().WithError(err).Warning("failed to get user info")
|
||||
return
|
||||
}
|
||||
|
||||
response := r.Response(radius.CodeAccessAccept)
|
||||
_ = rfc2866.AcctSessionID_SetString(response, fe.GetSession().String())
|
||||
r.pi.boundUsersMutex.Lock()
|
||||
r.pi.boundUsers[fe.GetSession().String()] = &flags.UserFlags{
|
||||
Session: fe.GetSession(),
|
||||
UserPk: userInfo.Original.Pk,
|
||||
}
|
||||
r.pi.boundUsersMutex.Unlock()
|
||||
err = w.Write(response)
|
||||
if err != nil {
|
||||
r.Log().WithError(err).Warning("failed to write response")
|
||||
}
|
||||
}
|
||||
|
54
internal/outpost/radius/handle_disconnect_request.go
Normal file
54
internal/outpost/radius/handle_disconnect_request.go
Normal file
@ -0,0 +1,54 @@
|
||||
package radius
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"goauthentik.io/internal/outpost/flow"
|
||||
"goauthentik.io/internal/outpost/ldap/flags"
|
||||
"layeh.com/radius"
|
||||
"layeh.com/radius/rfc2866"
|
||||
)
|
||||
|
||||
func (rs *RadiusServer) Handle_DisconnectRequest(w radius.ResponseWriter, r *RadiusRequest) {
|
||||
session := rfc2866.AcctSessionID_GetString(r.Packet)
|
||||
|
||||
sendFailResponse := func() {
|
||||
failResponse := r.Response(radius.CodeDisconnectACK)
|
||||
err := w.Write(failResponse)
|
||||
if err != nil {
|
||||
r.Log().WithError(err).Warning("failed to write response")
|
||||
}
|
||||
}
|
||||
|
||||
r.pi.boundUsersMutex.Lock()
|
||||
var f *flags.UserFlags
|
||||
if ff, ok := r.pi.boundUsers[session]; !ok {
|
||||
r.pi.boundUsersMutex.Unlock()
|
||||
sendFailResponse()
|
||||
return
|
||||
} else {
|
||||
f = ff
|
||||
}
|
||||
r.pi.boundUsersMutex.Unlock()
|
||||
|
||||
fe := flow.NewFlowExecutor(r.Context(), r.pi.invalidationFlowSlug, rs.ac.Client.GetConfig(), log.Fields{
|
||||
"client": r.RemoteAddr(),
|
||||
"requestId": r.ID(),
|
||||
})
|
||||
fe.SetSession(f.Session)
|
||||
fe.DelegateClientIP(r.RemoteAddr())
|
||||
fe.Params.Add("goauthentik.io/outpost/radius", "true")
|
||||
_, err := fe.Execute()
|
||||
if err != nil {
|
||||
r.log.WithError(err).Warning("failed to logout user")
|
||||
sendFailResponse()
|
||||
return
|
||||
}
|
||||
r.pi.boundUsersMutex.Lock()
|
||||
delete(r.pi.boundUsers, session)
|
||||
r.pi.boundUsersMutex.Unlock()
|
||||
response := r.Response(radius.CodeDisconnectACK)
|
||||
err = w.Write(response)
|
||||
if err != nil {
|
||||
r.Log().WithError(err).Warning("failed to write response")
|
||||
}
|
||||
}
|
@ -74,7 +74,12 @@ func (rs *RadiusServer) ServeRADIUS(w radius.ResponseWriter, r *radius.Request)
|
||||
}
|
||||
nr.pi = pi
|
||||
|
||||
if nr.Code == radius.CodeAccessRequest {
|
||||
switch nr.Code {
|
||||
case radius.CodeAccessRequest:
|
||||
rs.Handle_AccessRequest(w, nr)
|
||||
case radius.CodeDisconnectRequest:
|
||||
rs.Handle_DisconnectRequest(w, nr)
|
||||
default:
|
||||
nr.Log().WithField("code", nr.Code.String()).Debug("Unsupported packet code")
|
||||
}
|
||||
}
|
||||
|
@ -9,20 +9,25 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"goauthentik.io/internal/config"
|
||||
"goauthentik.io/internal/outpost/ak"
|
||||
"goauthentik.io/internal/outpost/ldap/flags"
|
||||
"goauthentik.io/internal/outpost/radius/metrics"
|
||||
|
||||
"layeh.com/radius"
|
||||
)
|
||||
|
||||
type ProviderInstance struct {
|
||||
ClientNetworks []*net.IPNet
|
||||
SharedSecret []byte
|
||||
MFASupport bool
|
||||
ClientNetworks []*net.IPNet
|
||||
SharedSecret []byte
|
||||
MFASupport bool
|
||||
boundUsersMutex *sync.RWMutex
|
||||
boundUsers map[string]*flags.UserFlags
|
||||
providerPk int32
|
||||
|
||||
appSlug string
|
||||
flowSlug string
|
||||
s *RadiusServer
|
||||
log *log.Entry
|
||||
appSlug string
|
||||
authenticationFlowSlug string
|
||||
invalidationFlowSlug string
|
||||
s *RadiusServer
|
||||
log *log.Entry
|
||||
}
|
||||
|
||||
type RadiusServer struct {
|
||||
|
36
poetry.lock
generated
36
poetry.lock
generated
@ -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]
|
||||
|
50
schema.yml
50
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
|
||||
|
@ -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"""
|
||||
|
16
tests/wdio/package-lock.json
generated
16
tests/wdio/package-lock.json
generated
@ -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"
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
17
web/docs/Changelog.md
Normal file
17
web/docs/Changelog.md
Normal file
@ -0,0 +1,17 @@
|
||||
### 2024-03-26T09:25:06-0700
|
||||
|
||||
Split the tsconfig file into a base and build variant.
|
||||
|
||||
Lesson: This lesson is stored here and not in a comment in tsconfig.json because
|
||||
JSON doesn't like comments. Doug Crockford's purity requirement has doomed an
|
||||
entire generation to keeping its human-facing meta somewhere other than in the
|
||||
file where it belongs.
|
||||
|
||||
Lesson: The `extend` command of tsconfig has an unexpected behavior. It is
|
||||
neither a merge or a replace, but some mixture of the two. The buildfile's
|
||||
`compilerOptions` is not a full replacement; instead, each of _its_ top-level
|
||||
fields is a replacement for what is found in the basefile. So while you don't
|
||||
need to include _everything_ in a `compilerOptions` field if you want to change
|
||||
one thing, if you want to modify _one_ path in `compilerOptions.path`, you must
|
||||
include the entire `compilerOptions.path` collection in your buildfile.
|
||||
g
|
193
web/package-lock.json
generated
193
web/package-lock.json
generated
@ -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-1711369360",
|
||||
"@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-1711369360",
|
||||
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2024.2.2-1711369360.tgz",
|
||||
"integrity": "sha512-8/J6cfxzpaUyz+piZUXrxPZuAlJ9SxwNrH+Z8xSRLAVavmEjmRM+Oy2XJEIZLDbcBKhNEuE99xdOxq6il/FJVw=="
|
||||
"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"
|
||||
}
|
||||
|
@ -14,8 +14,8 @@
|
||||
"build": "run-s build-locales esbuild:build",
|
||||
"build-proxy": "run-s build-locales esbuild:build-proxy",
|
||||
"watch": "run-s build-locales esbuild:watch",
|
||||
"lint": "eslint . --max-warnings 0 --fix",
|
||||
"lint:precommit": "node scripts/eslint-precommit.mjs",
|
||||
"lint": "cross-env NODE_OPTIONS='--max_old_space_size=8192' eslint . --max-warnings 0 --fix",
|
||||
"lint:precommit": "cross-env NODE_OPTIONS='--max_old_space_size=8192' node scripts/eslint-precommit.mjs",
|
||||
"lint:spelling": "node scripts/check-spelling.mjs",
|
||||
"lit-analyse": "lit-analyzer src",
|
||||
"precommit": "npm-run-all --parallel tsc lit-analyse lint:spelling --sequential lint:precommit prettier",
|
||||
@ -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-1711369360",
|
||||
"@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"
|
||||
|
@ -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 }> {
|
||||
<div class="pf-c-form__group-label">
|
||||
<div class="c-form__horizontal-group">
|
||||
<dl class="pf-c-description-list pf-m-horizontal">
|
||||
${(this.result?.logMessages || []).length > 0
|
||||
? this.result?.logMessages?.map((m) => {
|
||||
return html`<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text"
|
||||
>${m.log_level}</span
|
||||
>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
${m.event}
|
||||
</div>
|
||||
</dd>
|
||||
</div>`;
|
||||
})
|
||||
: html`<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text"
|
||||
>${msg("No log messages.")}</span
|
||||
>
|
||||
</dt>
|
||||
</div>`}
|
||||
<ak-log-viewer .logs=${this.result?.logMessages}></ak-log-viewer>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -23,13 +23,15 @@ export class BoundStagesList extends Table<FlowStageBinding> {
|
||||
checkbox = true;
|
||||
clearOnRefresh = true;
|
||||
|
||||
order = "order";
|
||||
|
||||
@property()
|
||||
target?: string;
|
||||
|
||||
async apiEndpoint(page: number): Promise<PaginatedResponse<FlowStageBinding>> {
|
||||
return new FlowsApi(DEFAULT_CONFIG).flowsBindingsList({
|
||||
target: this.target || "",
|
||||
ordering: "order",
|
||||
ordering: this.order,
|
||||
page: page,
|
||||
pageSize: (await uiConfig()).pagination.perPage,
|
||||
});
|
||||
@ -37,8 +39,8 @@ export class BoundStagesList extends Table<FlowStageBinding> {
|
||||
|
||||
columns(): TableColumn[] {
|
||||
return [
|
||||
new TableColumn(msg("Order")),
|
||||
new TableColumn(msg("Name")),
|
||||
new TableColumn(msg("Order"), "order"),
|
||||
new TableColumn(msg("Name"), "stage__name"),
|
||||
new TableColumn(msg("Type")),
|
||||
new TableColumn(msg("Actions")),
|
||||
];
|
||||
|
@ -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<Flow> {
|
||||
<div class="pf-c-form__group-label">
|
||||
<div class="c-form__horizontal-group">
|
||||
<dl class="pf-c-description-list pf-m-horizontal">
|
||||
${(this.result?.logs || []).length > 0
|
||||
? this.result?.logs?.map((m) => {
|
||||
return html`<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text"
|
||||
>${m.log_level}</span
|
||||
>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
${m.event}
|
||||
</div>
|
||||
</dd>
|
||||
</div>`;
|
||||
})
|
||||
: html`<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text"
|
||||
>${msg("No log messages.")}</span
|
||||
>
|
||||
</dt>
|
||||
</div>`}
|
||||
<ak-log-viewer .logs=${this.result?.logs}></ak-log-viewer>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -31,10 +31,12 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
|
||||
checkbox = true;
|
||||
clearOnRefresh = true;
|
||||
|
||||
order = "order";
|
||||
|
||||
async apiEndpoint(page: number): Promise<PaginatedResponse<PolicyBinding>> {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsList({
|
||||
target: this.target || "",
|
||||
ordering: "order",
|
||||
ordering: this.order,
|
||||
page: page,
|
||||
pageSize: (await uiConfig()).pagination.perPage,
|
||||
});
|
||||
|
@ -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<PolicyTestRequest> {
|
||||
<div class="pf-c-form__group-label">
|
||||
<div class="c-form__horizontal-group">
|
||||
<dl class="pf-c-description-list pf-m-horizontal">
|
||||
${(this.result?.logMessages || []).length > 0
|
||||
? this.result?.logMessages?.map((m) => {
|
||||
return html`<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text"
|
||||
>${m.log_level}</span
|
||||
>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
${m.event}
|
||||
</div>
|
||||
</dd>
|
||||
</div>`;
|
||||
})
|
||||
: html`<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text"
|
||||
>${msg("No log messages.")}</span
|
||||
>
|
||||
</dt>
|
||||
</div>`}
|
||||
<ak-log-viewer .logs=${this.result?.logMessages}></ak-log-viewer>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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<SystemTask> {
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
${item.messages.map((m) => {
|
||||
return html`<li>${m}</li>`;
|
||||
})}
|
||||
<ak-log-viewer .logs=${item?.messages}></ak-log-viewer>
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
|
@ -3,7 +3,7 @@ import {
|
||||
EventMiddleware,
|
||||
LoggingMiddleware,
|
||||
} from "@goauthentik/common/api/middleware";
|
||||
import { EVENT_LOCALE_REQUEST, EVENT_REFRESH, VERSION } from "@goauthentik/common/constants";
|
||||
import { EVENT_LOCALE_REQUEST, VERSION } from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
|
||||
import { Config, Configuration, CoreApi, CurrentBrand, RootApi } from "@goauthentik/api";
|
||||
@ -86,13 +86,4 @@ export function AndNext(url: string): string {
|
||||
return `?next=${encodeURIComponent(url)}`;
|
||||
}
|
||||
|
||||
window.addEventListener(EVENT_REFRESH, () => {
|
||||
// Upon global refresh, disregard whatever was pre-hydrated and
|
||||
// actually load info from API
|
||||
globalConfigPromise = undefined;
|
||||
globalBrandPromise = undefined;
|
||||
config();
|
||||
brand();
|
||||
});
|
||||
|
||||
console.debug(`authentik(early): version ${VERSION}, apiBase ${DEFAULT_CONFIG.basePath}`);
|
||||
|
@ -16,7 +16,7 @@ export async function parseAPIError(error: Error): Promise<APIErrorTypes> {
|
||||
if (!(error instanceof ResponseError)) {
|
||||
return error;
|
||||
}
|
||||
if (error.response.status < 400 && error.response.status > 499) {
|
||||
if (error.response.status < 400 || error.response.status > 499) {
|
||||
return error;
|
||||
}
|
||||
const body = await error.response.json();
|
||||
|
@ -55,9 +55,7 @@ export class PlexAPIClient {
|
||||
): Promise<{ authUrl: string; pin: PlexPinResponse }> {
|
||||
const headers = {
|
||||
...DEFAULT_HEADERS,
|
||||
...{
|
||||
"X-Plex-Client-Identifier": clientIdentifier,
|
||||
},
|
||||
"X-Plex-Client-Identifier": clientIdentifier,
|
||||
};
|
||||
const pinResponse = await fetch("https://plex.tv/api/v2/pins.json?strong=true", {
|
||||
method: "POST",
|
||||
@ -75,9 +73,7 @@ export class PlexAPIClient {
|
||||
static async pinStatus(clientIdentifier: string, id: number): Promise<string | undefined> {
|
||||
const headers = {
|
||||
...DEFAULT_HEADERS,
|
||||
...{
|
||||
"X-Plex-Client-Identifier": clientIdentifier,
|
||||
},
|
||||
"X-Plex-Client-Identifier": clientIdentifier,
|
||||
};
|
||||
const pinResponse = await fetch(`https://plex.tv/api/v2/pins/${id}`, {
|
||||
headers: headers,
|
||||
|
@ -123,7 +123,7 @@ const isCSSResult = (v: unknown): v is CSSResult =>
|
||||
|
||||
// prettier-ignore
|
||||
export const _adaptCSS = (sheet: AdaptableStylesheet): CSSStyleSheet =>
|
||||
(typeof sheet === "string" ? css([sheet] as unknown as TemplateStringsArray, ...[]).styleSheet
|
||||
(typeof sheet === "string" ? css([sheet] as unknown as TemplateStringsArray, []).styleSheet
|
||||
: isCSSResult(sheet) ? sheet.styleSheet
|
||||
: sheet) as CSSStyleSheet;
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { createContext } from "@lit/context";
|
||||
|
||||
import type { Config, CurrentBrand, LicenseSummary } from "@goauthentik/api";
|
||||
import type { Config, CurrentBrand, LicenseSummary, SessionUser } from "@goauthentik/api";
|
||||
|
||||
export const authentikConfigContext = createContext<Config>(Symbol("authentik-config-context"));
|
||||
|
||||
export const authentikUserContext = createContext<SessionUser>(Symbol("authentik-user-context"));
|
||||
|
||||
export const authentikEnterpriseContext = createContext<LicenseSummary>(
|
||||
Symbol("authentik-enterprise-context"),
|
||||
);
|
||||
|
52
web/src/elements/Interface/BrandContextController.ts
Normal file
52
web/src/elements/Interface/BrandContextController.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { EVENT_REFRESH } from "@goauthentik/authentik/common/constants";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { authentikBrandContext } from "@goauthentik/elements/AuthentikContexts";
|
||||
|
||||
import { ContextProvider } from "@lit/context";
|
||||
import { ReactiveController, ReactiveControllerHost } from "lit";
|
||||
|
||||
import type { CurrentBrand } from "@goauthentik/api";
|
||||
import { CoreApi } from "@goauthentik/api";
|
||||
|
||||
import type { AkInterface } from "./Interface";
|
||||
|
||||
type ReactiveElementHost = Partial<ReactiveControllerHost> & AkInterface;
|
||||
|
||||
export class BrandContextController implements ReactiveController {
|
||||
host!: ReactiveElementHost;
|
||||
|
||||
context!: ContextProvider<{ __context__: CurrentBrand | undefined }>;
|
||||
|
||||
constructor(host: ReactiveElementHost) {
|
||||
this.host = host;
|
||||
this.context = new ContextProvider(this.host, {
|
||||
context: authentikBrandContext,
|
||||
initialValue: undefined,
|
||||
});
|
||||
this.fetch = this.fetch.bind(this);
|
||||
this.fetch();
|
||||
}
|
||||
|
||||
fetch() {
|
||||
new CoreApi(DEFAULT_CONFIG).coreBrandsCurrentRetrieve().then((brand) => {
|
||||
this.context.setValue(brand);
|
||||
this.host.brand = brand;
|
||||
});
|
||||
}
|
||||
|
||||
hostConnected() {
|
||||
window.addEventListener(EVENT_REFRESH, this.fetch);
|
||||
}
|
||||
|
||||
hostDisconnected() {
|
||||
window.removeEventListener(EVENT_REFRESH, this.fetch);
|
||||
}
|
||||
|
||||
hostUpdate() {
|
||||
// If the Interface changes its brand information for some reason,
|
||||
// we should notify all users of the context of that change. doesn't
|
||||
if (this.host.brand !== this.context.value) {
|
||||
this.context.setValue(this.host.brand);
|
||||
}
|
||||
}
|
||||
}
|
53
web/src/elements/Interface/ConfigContextController.ts
Normal file
53
web/src/elements/Interface/ConfigContextController.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { EVENT_REFRESH } from "@goauthentik/authentik/common/constants";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { authentikConfigContext } from "@goauthentik/elements/AuthentikContexts";
|
||||
|
||||
import { ContextProvider } from "@lit/context";
|
||||
import { ReactiveController, ReactiveControllerHost } from "lit";
|
||||
|
||||
import type { Config } from "@goauthentik/api";
|
||||
import { RootApi } from "@goauthentik/api";
|
||||
|
||||
import type { AkInterface } from "./Interface";
|
||||
|
||||
type ReactiveElementHost = Partial<ReactiveControllerHost> & AkInterface;
|
||||
|
||||
export class ConfigContextController implements ReactiveController {
|
||||
host!: ReactiveElementHost;
|
||||
|
||||
context!: ContextProvider<{ __context__: Config | undefined }>;
|
||||
|
||||
constructor(host: ReactiveElementHost) {
|
||||
this.host = host;
|
||||
this.context = new ContextProvider(this.host, {
|
||||
context: authentikConfigContext,
|
||||
initialValue: undefined,
|
||||
});
|
||||
this.fetch = this.fetch.bind(this);
|
||||
this.fetch();
|
||||
}
|
||||
|
||||
fetch() {
|
||||
new RootApi(DEFAULT_CONFIG).rootConfigRetrieve().then((config) => {
|
||||
this.context.setValue(config);
|
||||
this.host.config = config;
|
||||
});
|
||||
}
|
||||
|
||||
hostConnected() {
|
||||
window.addEventListener(EVENT_REFRESH, this.fetch);
|
||||
}
|
||||
|
||||
hostDisconnected() {
|
||||
window.removeEventListener(EVENT_REFRESH, this.fetch);
|
||||
}
|
||||
|
||||
hostUpdate() {
|
||||
// If the Interface changes its config information, we should notify all
|
||||
// users of the context of that change, without creating an infinite
|
||||
// loop of resets.
|
||||
if (this.host.config !== this.context.value) {
|
||||
this.context.setValue(this.host.config);
|
||||
}
|
||||
}
|
||||
}
|
53
web/src/elements/Interface/EnterpriseContextController.ts
Normal file
53
web/src/elements/Interface/EnterpriseContextController.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { EVENT_REFRESH_ENTERPRISE } from "@goauthentik/authentik/common/constants";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { authentikEnterpriseContext } from "@goauthentik/elements/AuthentikContexts";
|
||||
|
||||
import { ContextProvider } from "@lit/context";
|
||||
import { ReactiveController, ReactiveControllerHost } from "lit";
|
||||
|
||||
import type { LicenseSummary } from "@goauthentik/api";
|
||||
import { EnterpriseApi } from "@goauthentik/api";
|
||||
|
||||
import type { AkEnterpriseInterface } from "./Interface";
|
||||
|
||||
type ReactiveElementHost = Partial<ReactiveControllerHost> & AkEnterpriseInterface;
|
||||
|
||||
export class EnterpriseContextController implements ReactiveController {
|
||||
host!: ReactiveElementHost;
|
||||
|
||||
context!: ContextProvider<{ __context__: LicenseSummary | undefined }>;
|
||||
|
||||
constructor(host: ReactiveElementHost) {
|
||||
this.host = host;
|
||||
this.context = new ContextProvider(this.host, {
|
||||
context: authentikEnterpriseContext,
|
||||
initialValue: undefined,
|
||||
});
|
||||
this.fetch = this.fetch.bind(this);
|
||||
this.fetch();
|
||||
}
|
||||
|
||||
fetch() {
|
||||
new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseSummaryRetrieve().then((enterprise) => {
|
||||
this.context.setValue(enterprise);
|
||||
this.host.licenseSummary = enterprise;
|
||||
});
|
||||
}
|
||||
|
||||
hostConnected() {
|
||||
window.addEventListener(EVENT_REFRESH_ENTERPRISE, this.fetch);
|
||||
}
|
||||
|
||||
hostDisconnected() {
|
||||
window.removeEventListener(EVENT_REFRESH_ENTERPRISE, this.fetch);
|
||||
}
|
||||
|
||||
hostUpdate() {
|
||||
// If the Interface changes its config information, we should notify all
|
||||
// users of the context of that change, without creating an infinite
|
||||
// loop of resets.
|
||||
if (this.host.licenseSummary !== this.context.value) {
|
||||
this.context.setValue(this.host.licenseSummary);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,77 +1,47 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { brand, config } from "@goauthentik/common/api/config";
|
||||
import { EVENT_REFRESH_ENTERPRISE } from "@goauthentik/common/constants";
|
||||
import { UIConfig, uiConfig } from "@goauthentik/common/ui/config";
|
||||
import {
|
||||
authentikBrandContext,
|
||||
authentikConfigContext,
|
||||
authentikEnterpriseContext,
|
||||
} from "@goauthentik/elements/AuthentikContexts";
|
||||
import { ensureCSSStyleSheet } from "@goauthentik/elements/utils/ensureCSSStyleSheet";
|
||||
|
||||
import { ContextProvider } from "@lit/context";
|
||||
import { state } from "lit/decorators.js";
|
||||
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import type { Config, CurrentBrand, LicenseSummary } from "@goauthentik/api";
|
||||
import { EnterpriseApi, UiThemeEnum } from "@goauthentik/api";
|
||||
import { UiThemeEnum } from "@goauthentik/api";
|
||||
|
||||
import { AKElement } from "../Base";
|
||||
import { BrandContextController } from "./BrandContextController";
|
||||
import { ConfigContextController } from "./ConfigContextController";
|
||||
import { EnterpriseContextController } from "./EnterpriseContextController";
|
||||
|
||||
type AkInterface = HTMLElement & {
|
||||
export type AkInterface = HTMLElement & {
|
||||
getTheme: () => Promise<UiThemeEnum>;
|
||||
brand?: CurrentBrand;
|
||||
uiConfig?: UIConfig;
|
||||
config?: Config;
|
||||
};
|
||||
|
||||
const brandContext = Symbol("brandContext");
|
||||
const configContext = Symbol("configContext");
|
||||
|
||||
export class Interface extends AKElement implements AkInterface {
|
||||
@state()
|
||||
uiConfig?: UIConfig;
|
||||
|
||||
_configContext = new ContextProvider(this, {
|
||||
context: authentikConfigContext,
|
||||
initialValue: undefined,
|
||||
});
|
||||
[brandContext]!: BrandContextController;
|
||||
|
||||
_config?: Config;
|
||||
[configContext]!: ConfigContextController;
|
||||
|
||||
@state()
|
||||
set config(c: Config) {
|
||||
this._config = c;
|
||||
this._configContext.setValue(c);
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
get config(): Config | undefined {
|
||||
return this._config;
|
||||
}
|
||||
|
||||
_brandContext = new ContextProvider(this, {
|
||||
context: authentikBrandContext,
|
||||
initialValue: undefined,
|
||||
});
|
||||
|
||||
_brand?: CurrentBrand;
|
||||
config?: Config;
|
||||
|
||||
@state()
|
||||
set brand(c: CurrentBrand) {
|
||||
this._brand = c;
|
||||
this._brandContext.setValue(c);
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
get brand(): CurrentBrand | undefined {
|
||||
return this._brand;
|
||||
}
|
||||
brand?: CurrentBrand;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
document.adoptedStyleSheets = [...document.adoptedStyleSheets, ensureCSSStyleSheet(PFBase)];
|
||||
brand().then((brand) => (this.brand = brand));
|
||||
config().then((config) => (this.config = config));
|
||||
|
||||
this[brandContext] = new BrandContextController(this);
|
||||
this[configContext] = new ConfigContextController(this);
|
||||
this.dataset.akInterfaceRoot = "true";
|
||||
}
|
||||
|
||||
@ -88,37 +58,20 @@ export class Interface extends AKElement implements AkInterface {
|
||||
}
|
||||
}
|
||||
|
||||
export class EnterpriseAwareInterface extends Interface {
|
||||
_licenseSummaryContext = new ContextProvider(this, {
|
||||
context: authentikEnterpriseContext,
|
||||
initialValue: undefined,
|
||||
});
|
||||
export type AkEnterpriseInterface = AkInterface & {
|
||||
licenseSummary?: LicenseSummary;
|
||||
};
|
||||
|
||||
_licenseSummary?: LicenseSummary;
|
||||
const enterpriseContext = Symbol("enterpriseContext");
|
||||
|
||||
export class EnterpriseAwareInterface extends Interface {
|
||||
[enterpriseContext]!: EnterpriseContextController;
|
||||
|
||||
@state()
|
||||
set licenseSummary(c: LicenseSummary) {
|
||||
this._licenseSummary = c;
|
||||
this._licenseSummaryContext.setValue(c);
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
get licenseSummary(): LicenseSummary | undefined {
|
||||
return this._licenseSummary;
|
||||
}
|
||||
licenseSummary?: LicenseSummary;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const refreshStatus = () => {
|
||||
new EnterpriseApi(DEFAULT_CONFIG)
|
||||
.enterpriseLicenseSummaryRetrieve()
|
||||
.then((enterprise) => {
|
||||
this.licenseSummary = enterprise;
|
||||
});
|
||||
};
|
||||
refreshStatus();
|
||||
window.addEventListener(EVENT_REFRESH_ENTERPRISE, () => {
|
||||
refreshStatus();
|
||||
});
|
||||
this[enterpriseContext] = new EnterpriseContextController(this);
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ export class Markdown extends AKElement {
|
||||
converter = new showdown.Converter({ metadata: true, tables: true });
|
||||
|
||||
replaceAdmonitions(input: string): string {
|
||||
const admonitionStart = /:::(\w+)<br\s\/>/gm;
|
||||
const admonitionStart = /:::(\w+)(<br\s*\/>|\s*$)/gm;
|
||||
const admonitionEnd = /:::/gm;
|
||||
return (
|
||||
input
|
||||
|
@ -12,7 +12,7 @@ import AKTokenCopyButton from "./ak-token-copy-button";
|
||||
|
||||
function makeid(length: number) {
|
||||
const sample = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
return new Array(length)
|
||||
return Array.from({ length })
|
||||
.fill(" ")
|
||||
.map(() => sample.charAt(Math.floor(Math.random() * sample.length)))
|
||||
.join("");
|
||||
|
114
web/src/elements/events/LogViewer.ts
Normal file
114
web/src/elements/events/LogViewer.ts
Normal file
@ -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<LogEvent> {
|
||||
@property({ attribute: false })
|
||||
logs?: LogEvent[] = [];
|
||||
|
||||
expandable = true;
|
||||
paginated = false;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return super.styles.concat(PFDescriptionList);
|
||||
}
|
||||
|
||||
async apiEndpoint(_page: number): Promise<PaginatedResponse<LogEvent>> {
|
||||
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`<ak-empty-state header=${msg("No log messages.")}> </ak-empty-state>`,
|
||||
);
|
||||
}
|
||||
|
||||
renderExpanded(item: LogEvent): TemplateResult {
|
||||
return html`<td role="cell" colspan="4">
|
||||
<div class="pf-c-table__expandable-row-content">
|
||||
<dl class="pf-c-description-list pf-m-horizontal">
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text">${msg("Timestamp")}</span>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
${item.timestamp.toLocaleString()}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text">${msg("Attributes")}</span>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
<pre>${JSON.stringify(item.attributes, null, 4)}</pre>
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</td>`;
|
||||
}
|
||||
|
||||
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`<ak-status-label
|
||||
type=${this.statusForItem(item)}
|
||||
bad-label=${item.logLevel}
|
||||
></ak-status-label>`,
|
||||
html`${item.event}`,
|
||||
html`${item.logger}`,
|
||||
];
|
||||
}
|
||||
}
|
@ -91,7 +91,7 @@ export class RouterOutlet extends AKElement {
|
||||
let matchedRoute: RouteMatch | null = null;
|
||||
this.routes.some((route) => {
|
||||
const match = route.url.exec(activeUrl);
|
||||
if (match != null) {
|
||||
if (match !== null) {
|
||||
matchedRoute = new RouteMatch(route);
|
||||
matchedRoute.arguments = match.groups || {};
|
||||
matchedRoute.fullUrl = activeUrl;
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { docLink } from "@goauthentik/common/global";
|
||||
import { adaptCSS } from "@goauthentik/common/utils";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import { paramURL } from "@goauthentik/elements/router/RouterOutlet";
|
||||
|
||||
@ -20,23 +19,23 @@ import PFSpacing from "@patternfly/patternfly/utilities/Spacing/spacing.css";
|
||||
* administrator, provide a link to the "Create a new application" page.
|
||||
*/
|
||||
|
||||
const styles = adaptCSS([
|
||||
PFBase,
|
||||
PFEmptyState,
|
||||
PFButton,
|
||||
PFContent,
|
||||
PFSpacing,
|
||||
css`
|
||||
.cta {
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
}
|
||||
`,
|
||||
]);
|
||||
|
||||
@customElement("ak-library-application-empty-list")
|
||||
export class LibraryPageApplicationEmptyList extends AKElement {
|
||||
static styles = styles;
|
||||
static get styles() {
|
||||
return [
|
||||
PFBase,
|
||||
PFEmptyState,
|
||||
PFButton,
|
||||
PFContent,
|
||||
PFSpacing,
|
||||
css`
|
||||
.cta {
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@property({ attribute: "isadmin", type: Boolean })
|
||||
isAdmin = false;
|
||||
|
@ -31,22 +31,22 @@ const LAYOUTS = new Map<string, [string, string]>([
|
||||
],
|
||||
]);
|
||||
|
||||
const styles = [
|
||||
PFBase,
|
||||
PFEmptyState,
|
||||
PFContent,
|
||||
PFGrid,
|
||||
css`
|
||||
.app-group-header {
|
||||
margin-bottom: 1em;
|
||||
margin-top: 1.2em;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@customElement("ak-library-application-list")
|
||||
export class LibraryPageApplicationList extends AKElement {
|
||||
static styles = styles;
|
||||
static get styles() {
|
||||
return [
|
||||
PFBase,
|
||||
PFEmptyState,
|
||||
PFContent,
|
||||
PFGrid,
|
||||
css`
|
||||
.app-group-header {
|
||||
margin-bottom: 1em;
|
||||
margin-top: 1.2em;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@property({ attribute: true })
|
||||
layout = "row" as LayoutType;
|
||||
|
@ -18,24 +18,26 @@ import { customEvent } from "./helpers";
|
||||
|
||||
@customElement("ak-library-list-search")
|
||||
export class LibraryPageApplicationList extends AKElement {
|
||||
static styles = [
|
||||
PFBase,
|
||||
PFDisplay,
|
||||
css`
|
||||
input {
|
||||
width: 30ch;
|
||||
box-sizing: border-box;
|
||||
border: 0;
|
||||
border-bottom: 1px solid;
|
||||
border-bottom-color: var(--ak-accent);
|
||||
background-color: transparent;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
input:focus {
|
||||
outline: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
static get styles() {
|
||||
return [
|
||||
PFBase,
|
||||
PFDisplay,
|
||||
css`
|
||||
input {
|
||||
width: 30ch;
|
||||
box-sizing: border-box;
|
||||
border: 0;
|
||||
border-bottom: 1px solid;
|
||||
border-bottom-color: var(--ak-accent);
|
||||
background-color: transparent;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
input:focus {
|
||||
outline: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
set apps(value: Application[]) {
|
||||
|
@ -35,7 +35,9 @@ import type { AppGroupList, PageUIConfig } from "./types";
|
||||
|
||||
@customElement("ak-library-impl")
|
||||
export class LibraryPage extends AKElement {
|
||||
static styles = styles;
|
||||
static get styles() {
|
||||
return styles;
|
||||
}
|
||||
|
||||
@property({ attribute: "isadmin", type: Boolean })
|
||||
isAdmin = false;
|
||||
|
54
web/tsconfig.base.json
Normal file
54
web/tsconfig.base.json
Normal file
@ -0,0 +1,54 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"baseUrl": ".",
|
||||
"esModuleInterop": true,
|
||||
"paths": {
|
||||
"@goauthentik/docs/*": ["../website/docs/*"]
|
||||
},
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"experimentalDecorators": true,
|
||||
"sourceMap": true,
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"lib": [
|
||||
"ES5",
|
||||
"ES2015",
|
||||
"ES2016",
|
||||
"ES2017",
|
||||
"ES2018",
|
||||
"ES2019",
|
||||
"ES2020",
|
||||
"ESNext",
|
||||
"DOM",
|
||||
"DOM.Iterable",
|
||||
"WebWorker"
|
||||
],
|
||||
"noUnusedLocals": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"strictBindCallApply": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictNullChecks": true,
|
||||
"allowUnreachableCode": false,
|
||||
"allowUnusedLabels": false,
|
||||
"useDefineForClassFields": false,
|
||||
"alwaysStrict": true,
|
||||
"noImplicitAny": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "ts-lit-plugin",
|
||||
"strict": true,
|
||||
"rules": {
|
||||
"no-unknown-tag-name": "off",
|
||||
"no-missing-import": "off",
|
||||
"no-incompatible-type-binding": "off",
|
||||
"no-unknown-property": "off",
|
||||
"no-unknown-attribute": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"paths": {
|
||||
"@goauthentik/authentik/*": ["src/*"],
|
||||
"@goauthentik/admin/*": ["src/admin/*"],
|
||||
@ -14,51 +14,5 @@
|
||||
"@goauthentik/standalone/*": ["src/standalone/*"],
|
||||
"@goauthentik/user/*": ["src/user/*"]
|
||||
},
|
||||
"baseUrl": ".",
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"experimentalDecorators": true,
|
||||
"sourceMap": true,
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"lib": [
|
||||
"ES5",
|
||||
"ES2015",
|
||||
"ES2016",
|
||||
"ES2017",
|
||||
"ES2018",
|
||||
"ES2019",
|
||||
"ES2020",
|
||||
"ESNext",
|
||||
"DOM",
|
||||
"DOM.Iterable",
|
||||
"WebWorker"
|
||||
],
|
||||
"noUnusedLocals": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"strictBindCallApply": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictNullChecks": true,
|
||||
"allowUnreachableCode": false,
|
||||
"allowUnusedLabels": false,
|
||||
"useDefineForClassFields": false,
|
||||
"alwaysStrict": true,
|
||||
"noImplicitAny": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "ts-lit-plugin",
|
||||
"strict": true,
|
||||
"rules": {
|
||||
"no-unknown-tag-name": "off",
|
||||
"no-missing-import": "off",
|
||||
"no-incompatible-type-binding": "off",
|
||||
"no-unknown-property": "off",
|
||||
"no-unknown-attribute": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -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)
|
68
website/docs/installation/air-gapped.mdx
Normal file
68
website/docs/installation/air-gapped.mdx
Normal file
@ -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";
|
||||
|
||||
<Tabs
|
||||
defaultValue="docker-compose"
|
||||
values={[
|
||||
{label: 'docker-compose', value: 'docker-compose'},
|
||||
{label: 'Kubernetes', value: 'kubernetes'},
|
||||
]}>
|
||||
<TabItem value="docker-compose">
|
||||
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.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="kubernetes">
|
||||
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.
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## 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)
|
@ -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.
|
||||
|
@ -49,8 +49,8 @@ environment: OAUTH2_ENABLED=true
|
||||
OAUTH2_USERINFO_ENDPOINT=/application/o/userinfo/
|
||||
OAUTH2_TOKEN_ENDPOINT=/application/o/token/
|
||||
OAUTH2_SECRET=<Client Secret from above>
|
||||
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='<Client Secret from above>'
|
||||
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'
|
||||
```
|
||||
|
69
website/integrations/services/xen-orchestra/index.md
Normal file
69
website/integrations/services/xen-orchestra/index.md
Normal file
@ -0,0 +1,69 @@
|
||||
---
|
||||
title: Xen Orchestra
|
||||
---
|
||||
|
||||
<span class="badge badge--secondary">Support level: Community</span>
|
||||
|
||||
## What is Xen Orchestra
|
||||
|
||||
> Xen Orchestra provides a user friendly web interface for every Xen based hypervisor (XenServer, xcp-ng, etc.).
|
||||
>
|
||||
> -- https://xen-orchestra.com/
|
||||
|
||||
:::note
|
||||
Xen Orchestra offers authentication plugins for OpenID Connect, SAML and LDAP. This guide is using the OpenID Connect plugin.
|
||||
If you are using the Xen Orchestra Appliance, the OIDC Plugin should be present. If you are using Xen Orchestra compiled from sources, make sure the plugin `auth-oidc` is installed.
|
||||
:::
|
||||
|
||||
## Preparation
|
||||
|
||||
The following placeholders will be used:
|
||||
|
||||
- `xenorchestra.company` is the FQDN of the Xen Orchestra instance.
|
||||
- `authentik.company` is the FQDN of the authentik install.
|
||||
|
||||
## authentik configuration
|
||||
|
||||
### 1. Provider
|
||||
|
||||
Under _Providers_, create an OAuth2/OpenID provider with these settings:
|
||||
|
||||
- Name: Provider for XenOrchestra
|
||||
- Authorization Flow: Select one of the available Flows.
|
||||
- Client type: Confidential
|
||||
- Redirect URIs/Origins: `https://xenorchestra.company/signin/oidc/callback`
|
||||
|
||||
Take note of the Client ID and the Client Secret, because we need them for the configuration of Xen Orchestra.
|
||||
|
||||
### 2. Application
|
||||
|
||||
Create an application with the following details:
|
||||
|
||||
- Slug: `xenorchestra` (If you want to choose a different slug, your URLs for the Xen Orchestra Configuration may vary.)
|
||||
- Provider: Select the one we have created in Step 1
|
||||
- Set the Launch URL to `https://xenorchestra.company/`
|
||||
|
||||
Optionally apply access restrictions to the application.
|
||||
|
||||
## Xen Orchestra configuration
|
||||
|
||||
Xen Orchestra allows the configuration of the OpenID Connect authentication in the plugin-section.
|
||||
All of the URLs mentioned below can be copied & pasted from authentik (_Applications -> Providers -> *the provider created earlier*_).
|
||||
|
||||
1. Navigate to Settings -> Plugins
|
||||
2. Scroll to **auth-oidc** and click on the **+** icon on the right hand side.
|
||||
3. Configure the auth-oidc plugin with the following configuration values:
|
||||
|
||||
- Set the `Auto-discovery URL` to `https://authentik.company/application/o/xenorchestra/.well-known/openid-configuration`.
|
||||
- Set the `Client identifier (key)` to the Client ID from your notes.
|
||||
- Set the `Client secret` to the Client Secret from your notes.
|
||||
- Check the `Fill information (optional)`-Checkbox to open the advanced menu.
|
||||
- Set the `Username field` to `username`
|
||||
- Set the `Scopes` to `openid profile email`
|
||||
|
||||
4. Enable the `auth-oidc`-Plugin by toggling the switch above the configuration.
|
||||
5. You should be able to login with OIDC.
|
||||
|
||||
:::note
|
||||
The first time a user signs in, Xen Orchesta will create a new user with the same username used in authentik. If you want to map the users by their e-mail-address instead of their username, you have to set the `Username field` to `email` in the Xen Orchestra plugin configuration.
|
||||
:::
|
14
website/package-lock.json
generated
14
website/package-lock.json
generated
@ -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.70",
|
||||
"@types/react": "^18.2.73",
|
||||
"prettier": "3.2.5",
|
||||
"typescript": "~5.4.3"
|
||||
},
|
||||
@ -3999,12 +3999,11 @@
|
||||
"integrity": "sha512-+0autS93xyXizIYiyL02FCY8N+KkKPhILhcUSA276HxzreZ16kl+cmwvV2qAM/PuCCwPXzOXOWhiPcw20uSFcA=="
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.2.70",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.70.tgz",
|
||||
"integrity": "sha512-hjlM2hho2vqklPhopNkXkdkeq6Lv8WSZTpr7956zY+3WS5cfYUewtCzsJLsbW5dEv3lfSeQ4W14ZFeKC437JRQ==",
|
||||
"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": "*",
|
||||
"@types/scheduler": "*",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
@ -4050,11 +4049,6 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/scheduler": {
|
||||
"version": "0.16.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.5.tgz",
|
||||
"integrity": "sha512-s/FPdYRmZR8SjLWGMCuax7r3qCWQw9QKHzXVukAuuIJkXkDRwp+Pu5LMIVFi0Fxbav35WURicYr8u1QsoybnQw=="
|
||||
},
|
||||
"node_modules/@types/send": {
|
||||
"version": "0.17.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.3.tgz",
|
||||
|
@ -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.70",
|
||||
"@types/react": "^18.2.73",
|
||||
"prettier": "3.2.5",
|
||||
"typescript": "~5.4.3"
|
||||
},
|
||||
|
@ -63,6 +63,7 @@ module.exports = {
|
||||
"services/portainer/index",
|
||||
"services/proxmox-ve/index",
|
||||
"services/rancher/index",
|
||||
"services/xen-orchestra/index",
|
||||
"services/vmware-vcenter/index",
|
||||
],
|
||||
},
|
||||
|
Reference in New Issue
Block a user