Merge branch 'main' into web/lage
* main: (21 commits) web: bump API Client version (#7964) release: 2023.10.5 web: bump API Client version (#7962) providers/scim: use lock for sync (#7948) stages/email: prevent authentik emails from being marked as spam (also add text template support) (#7949) website/docs: prepare 2023.10.5 (#7947) web: bump the sentry group in /web with 2 updates (#7955) web: bump the wdio group in /tests/wdio with 4 updates (#7957) core: bump goauthentik.io/api/v3 from 3.2023104.4 to 3.2023104.5 (#7958) web: bump API Client version (#7953) events: add ASN Database reader (#7793) translate: Updates for file web/xliff/en.xlf in fr (#7951) website/docs: add expression example for geoip (#7739) website: fix hosted API browser (#7946) core: bump github.com/redis/go-redis/v9 from 9.3.0 to 9.3.1 (#7942) web: bump the sentry group in /web with 2 updates (#7944) web: bump the storybook group in /web with 7 updates (#7945) core: bump goauthentik.io/api/v3 from 3.2023104.3 to 3.2023104.4 (#7943) providers/scim: set timeout based on page and page count (#7941) translate: Updates for file web/xliff/en.xlf in zh_CN (#7928) ...
|
@ -1,5 +1,5 @@
|
||||||
[bumpversion]
|
[bumpversion]
|
||||||
current_version = 2023.10.4
|
current_version = 2023.10.5
|
||||||
tag = True
|
tag = True
|
||||||
commit = True
|
commit = True
|
||||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)
|
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)
|
||||||
|
|
|
@ -71,7 +71,7 @@ RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \
|
||||||
# Stage 4: MaxMind GeoIP
|
# Stage 4: MaxMind GeoIP
|
||||||
FROM --platform=${BUILDPLATFORM} ghcr.io/maxmind/geoipupdate:v6.0 as geoip
|
FROM --platform=${BUILDPLATFORM} ghcr.io/maxmind/geoipupdate:v6.0 as geoip
|
||||||
|
|
||||||
ENV GEOIPUPDATE_EDITION_IDS="GeoLite2-City"
|
ENV GEOIPUPDATE_EDITION_IDS="GeoLite2-City GeoLite2-ASN"
|
||||||
ENV GEOIPUPDATE_VERBOSE="true"
|
ENV GEOIPUPDATE_VERBOSE="true"
|
||||||
ENV GEOIPUPDATE_ACCOUNT_ID_FILE="/run/secrets/GEOIPUPDATE_ACCOUNT_ID"
|
ENV GEOIPUPDATE_ACCOUNT_ID_FILE="/run/secrets/GEOIPUPDATE_ACCOUNT_ID"
|
||||||
ENV GEOIPUPDATE_LICENSE_KEY_FILE="/run/secrets/GEOIPUPDATE_LICENSE_KEY"
|
ENV GEOIPUPDATE_LICENSE_KEY_FILE="/run/secrets/GEOIPUPDATE_LICENSE_KEY"
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
from os import environ
|
from os import environ
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
__version__ = "2023.10.4"
|
__version__ = "2023.10.5"
|
||||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ from rest_framework.response import Response
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
from authentik.core.api.utils import PassiveSerializer
|
from authentik.core.api.utils import PassiveSerializer
|
||||||
from authentik.events.geo import GEOIP_READER
|
from authentik.events.context_processors.base import get_context_processors
|
||||||
from authentik.lib.config import CONFIG
|
from authentik.lib.config import CONFIG
|
||||||
|
|
||||||
capabilities = Signal()
|
capabilities = Signal()
|
||||||
|
@ -30,6 +30,7 @@ class Capabilities(models.TextChoices):
|
||||||
|
|
||||||
CAN_SAVE_MEDIA = "can_save_media"
|
CAN_SAVE_MEDIA = "can_save_media"
|
||||||
CAN_GEO_IP = "can_geo_ip"
|
CAN_GEO_IP = "can_geo_ip"
|
||||||
|
CAN_ASN = "can_asn"
|
||||||
CAN_IMPERSONATE = "can_impersonate"
|
CAN_IMPERSONATE = "can_impersonate"
|
||||||
CAN_DEBUG = "can_debug"
|
CAN_DEBUG = "can_debug"
|
||||||
IS_ENTERPRISE = "is_enterprise"
|
IS_ENTERPRISE = "is_enterprise"
|
||||||
|
@ -68,8 +69,9 @@ class ConfigView(APIView):
|
||||||
deb_test = settings.DEBUG or settings.TEST
|
deb_test = settings.DEBUG or settings.TEST
|
||||||
if Path(settings.MEDIA_ROOT).is_mount() or deb_test:
|
if Path(settings.MEDIA_ROOT).is_mount() or deb_test:
|
||||||
caps.append(Capabilities.CAN_SAVE_MEDIA)
|
caps.append(Capabilities.CAN_SAVE_MEDIA)
|
||||||
if GEOIP_READER.enabled:
|
for processor in get_context_processors():
|
||||||
caps.append(Capabilities.CAN_GEO_IP)
|
if cap := processor.capability():
|
||||||
|
caps.append(cap)
|
||||||
if CONFIG.get_bool("impersonation"):
|
if CONFIG.get_bool("impersonation"):
|
||||||
caps.append(Capabilities.CAN_IMPERSONATE)
|
caps.append(Capabilities.CAN_IMPERSONATE)
|
||||||
if settings.DEBUG: # pragma: no cover
|
if settings.DEBUG: # pragma: no cover
|
||||||
|
|
|
@ -14,7 +14,8 @@ from ua_parser import user_agent_parser
|
||||||
from authentik.api.authorization import OwnerSuperuserPermissions
|
from authentik.api.authorization import OwnerSuperuserPermissions
|
||||||
from authentik.core.api.used_by import UsedByMixin
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
from authentik.core.models import AuthenticatedSession
|
from authentik.core.models import AuthenticatedSession
|
||||||
from authentik.events.geo import GEOIP_READER, GeoIPDict
|
from authentik.events.context_processors.asn import ASN_CONTEXT_PROCESSOR, ASNDict
|
||||||
|
from authentik.events.context_processors.geoip import GEOIP_CONTEXT_PROCESSOR, GeoIPDict
|
||||||
|
|
||||||
|
|
||||||
class UserAgentDeviceDict(TypedDict):
|
class UserAgentDeviceDict(TypedDict):
|
||||||
|
@ -59,6 +60,7 @@ class AuthenticatedSessionSerializer(ModelSerializer):
|
||||||
current = SerializerMethodField()
|
current = SerializerMethodField()
|
||||||
user_agent = SerializerMethodField()
|
user_agent = SerializerMethodField()
|
||||||
geo_ip = SerializerMethodField()
|
geo_ip = SerializerMethodField()
|
||||||
|
asn = SerializerMethodField()
|
||||||
|
|
||||||
def get_current(self, instance: AuthenticatedSession) -> bool:
|
def get_current(self, instance: AuthenticatedSession) -> bool:
|
||||||
"""Check if session is currently active session"""
|
"""Check if session is currently active session"""
|
||||||
|
@ -70,8 +72,12 @@ class AuthenticatedSessionSerializer(ModelSerializer):
|
||||||
return user_agent_parser.Parse(instance.last_user_agent)
|
return user_agent_parser.Parse(instance.last_user_agent)
|
||||||
|
|
||||||
def get_geo_ip(self, instance: AuthenticatedSession) -> Optional[GeoIPDict]: # pragma: no cover
|
def get_geo_ip(self, instance: AuthenticatedSession) -> Optional[GeoIPDict]: # pragma: no cover
|
||||||
"""Get parsed user agent"""
|
"""Get GeoIP Data"""
|
||||||
return GEOIP_READER.city_dict(instance.last_ip)
|
return GEOIP_CONTEXT_PROCESSOR.city_dict(instance.last_ip)
|
||||||
|
|
||||||
|
def get_asn(self, instance: AuthenticatedSession) -> Optional[ASNDict]: # pragma: no cover
|
||||||
|
"""Get ASN Data"""
|
||||||
|
return ASN_CONTEXT_PROCESSOR.asn_dict(instance.last_ip)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AuthenticatedSession
|
model = AuthenticatedSession
|
||||||
|
@ -80,6 +86,7 @@ class AuthenticatedSessionSerializer(ModelSerializer):
|
||||||
"current",
|
"current",
|
||||||
"user_agent",
|
"user_agent",
|
||||||
"geo_ip",
|
"geo_ip",
|
||||||
|
"asn",
|
||||||
"user",
|
"user",
|
||||||
"last_ip",
|
"last_ip",
|
||||||
"last_user_agent",
|
"last_user_agent",
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
"""ASN Enricher"""
|
||||||
|
from typing import TYPE_CHECKING, Optional, TypedDict
|
||||||
|
|
||||||
|
from django.http import HttpRequest
|
||||||
|
from geoip2.errors import GeoIP2Error
|
||||||
|
from geoip2.models import ASN
|
||||||
|
from sentry_sdk import Hub
|
||||||
|
|
||||||
|
from authentik.events.context_processors.mmdb import MMDBContextProcessor
|
||||||
|
from authentik.lib.config import CONFIG
|
||||||
|
from authentik.root.middleware import ClientIPMiddleware
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from authentik.api.v3.config import Capabilities
|
||||||
|
from authentik.events.models import Event
|
||||||
|
|
||||||
|
|
||||||
|
class ASNDict(TypedDict):
|
||||||
|
"""ASN Details"""
|
||||||
|
|
||||||
|
asn: int
|
||||||
|
as_org: str | None
|
||||||
|
network: str | None
|
||||||
|
|
||||||
|
|
||||||
|
class ASNContextProcessor(MMDBContextProcessor):
|
||||||
|
"""ASN Database reader wrapper"""
|
||||||
|
|
||||||
|
def capability(self) -> Optional["Capabilities"]:
|
||||||
|
from authentik.api.v3.config import Capabilities
|
||||||
|
|
||||||
|
return Capabilities.CAN_ASN
|
||||||
|
|
||||||
|
def path(self) -> str | None:
|
||||||
|
return CONFIG.get("events.context_processors.asn")
|
||||||
|
|
||||||
|
def enrich_event(self, event: "Event"):
|
||||||
|
asn = self.asn_dict(event.client_ip)
|
||||||
|
if not asn:
|
||||||
|
return
|
||||||
|
event.context["asn"] = asn
|
||||||
|
|
||||||
|
def enrich_context(self, request: HttpRequest) -> dict:
|
||||||
|
return {
|
||||||
|
"asn": self.asn_dict(ClientIPMiddleware.get_client_ip(request)),
|
||||||
|
}
|
||||||
|
|
||||||
|
def asn(self, ip_address: str) -> Optional[ASN]:
|
||||||
|
"""Wrapper for Reader.asn"""
|
||||||
|
with Hub.current.start_span(
|
||||||
|
op="authentik.events.asn.asn",
|
||||||
|
description=ip_address,
|
||||||
|
):
|
||||||
|
if not self.enabled:
|
||||||
|
return None
|
||||||
|
self.check_expired()
|
||||||
|
try:
|
||||||
|
return self.reader.asn(ip_address)
|
||||||
|
except (GeoIP2Error, ValueError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def asn_to_dict(self, asn: ASN) -> ASNDict:
|
||||||
|
"""Convert ASN to dict"""
|
||||||
|
asn_dict: ASNDict = {
|
||||||
|
"asn": asn.autonomous_system_number,
|
||||||
|
"as_org": asn.autonomous_system_organization,
|
||||||
|
"network": str(asn.network) if asn.network else None,
|
||||||
|
}
|
||||||
|
return asn_dict
|
||||||
|
|
||||||
|
def asn_dict(self, ip_address: str) -> Optional[ASNDict]:
|
||||||
|
"""Wrapper for self.asn that returns a dict"""
|
||||||
|
asn = self.asn(ip_address)
|
||||||
|
if not asn:
|
||||||
|
return None
|
||||||
|
return self.asn_to_dict(asn)
|
||||||
|
|
||||||
|
|
||||||
|
ASN_CONTEXT_PROCESSOR = ASNContextProcessor()
|
|
@ -0,0 +1,43 @@
|
||||||
|
"""Base event enricher"""
|
||||||
|
from functools import cache
|
||||||
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
|
from django.http import HttpRequest
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from authentik.api.v3.config import Capabilities
|
||||||
|
from authentik.events.models import Event
|
||||||
|
|
||||||
|
|
||||||
|
class EventContextProcessor:
|
||||||
|
"""Base event enricher"""
|
||||||
|
|
||||||
|
def capability(self) -> Optional["Capabilities"]:
|
||||||
|
"""Return the capability this context processor provides"""
|
||||||
|
return None
|
||||||
|
|
||||||
|
def configured(self) -> bool:
|
||||||
|
"""Return true if this context processor is configured"""
|
||||||
|
return False
|
||||||
|
|
||||||
|
def enrich_event(self, event: "Event"):
|
||||||
|
"""Modify event"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def enrich_context(self, request: HttpRequest) -> dict:
|
||||||
|
"""Modify context"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
@cache
|
||||||
|
def get_context_processors() -> list[EventContextProcessor]:
|
||||||
|
"""Get a list of all configured context processors"""
|
||||||
|
from authentik.events.context_processors.asn import ASN_CONTEXT_PROCESSOR
|
||||||
|
from authentik.events.context_processors.geoip import GEOIP_CONTEXT_PROCESSOR
|
||||||
|
|
||||||
|
processors_types = [ASN_CONTEXT_PROCESSOR, GEOIP_CONTEXT_PROCESSOR]
|
||||||
|
processors = []
|
||||||
|
for _type in processors_types:
|
||||||
|
if _type.configured():
|
||||||
|
processors.append(_type)
|
||||||
|
return processors
|
|
@ -0,0 +1,84 @@
|
||||||
|
"""events GeoIP Reader"""
|
||||||
|
from typing import TYPE_CHECKING, Optional, TypedDict
|
||||||
|
|
||||||
|
from django.http import HttpRequest
|
||||||
|
from geoip2.errors import GeoIP2Error
|
||||||
|
from geoip2.models import City
|
||||||
|
from sentry_sdk.hub import Hub
|
||||||
|
|
||||||
|
from authentik.events.context_processors.mmdb import MMDBContextProcessor
|
||||||
|
from authentik.lib.config import CONFIG
|
||||||
|
from authentik.root.middleware import ClientIPMiddleware
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from authentik.api.v3.config import Capabilities
|
||||||
|
from authentik.events.models import Event
|
||||||
|
|
||||||
|
|
||||||
|
class GeoIPDict(TypedDict):
|
||||||
|
"""GeoIP Details"""
|
||||||
|
|
||||||
|
continent: str
|
||||||
|
country: str
|
||||||
|
lat: float
|
||||||
|
long: float
|
||||||
|
city: str
|
||||||
|
|
||||||
|
|
||||||
|
class GeoIPContextProcessor(MMDBContextProcessor):
|
||||||
|
"""Slim wrapper around GeoIP API"""
|
||||||
|
|
||||||
|
def capability(self) -> Optional["Capabilities"]:
|
||||||
|
from authentik.api.v3.config import Capabilities
|
||||||
|
|
||||||
|
return Capabilities.CAN_GEO_IP
|
||||||
|
|
||||||
|
def path(self) -> str | None:
|
||||||
|
return CONFIG.get("events.context_processors.geoip")
|
||||||
|
|
||||||
|
def enrich_event(self, event: "Event"):
|
||||||
|
city = self.city_dict(event.client_ip)
|
||||||
|
if not city:
|
||||||
|
return
|
||||||
|
event.context["geo"] = city
|
||||||
|
|
||||||
|
def enrich_context(self, request: HttpRequest) -> dict:
|
||||||
|
# Different key `geoip` vs `geo` for legacy reasons
|
||||||
|
return {"geoip": self.city(ClientIPMiddleware.get_client_ip(request))}
|
||||||
|
|
||||||
|
def city(self, ip_address: str) -> Optional[City]:
|
||||||
|
"""Wrapper for Reader.city"""
|
||||||
|
with Hub.current.start_span(
|
||||||
|
op="authentik.events.geo.city",
|
||||||
|
description=ip_address,
|
||||||
|
):
|
||||||
|
if not self.enabled:
|
||||||
|
return None
|
||||||
|
self.check_expired()
|
||||||
|
try:
|
||||||
|
return self.reader.city(ip_address)
|
||||||
|
except (GeoIP2Error, ValueError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def city_to_dict(self, city: City) -> GeoIPDict:
|
||||||
|
"""Convert City to dict"""
|
||||||
|
city_dict: GeoIPDict = {
|
||||||
|
"continent": city.continent.code,
|
||||||
|
"country": city.country.iso_code,
|
||||||
|
"lat": city.location.latitude,
|
||||||
|
"long": city.location.longitude,
|
||||||
|
"city": "",
|
||||||
|
}
|
||||||
|
if city.city.name:
|
||||||
|
city_dict["city"] = city.city.name
|
||||||
|
return city_dict
|
||||||
|
|
||||||
|
def city_dict(self, ip_address: str) -> Optional[GeoIPDict]:
|
||||||
|
"""Wrapper for self.city that returns a dict"""
|
||||||
|
city = self.city(ip_address)
|
||||||
|
if not city:
|
||||||
|
return None
|
||||||
|
return self.city_to_dict(city)
|
||||||
|
|
||||||
|
|
||||||
|
GEOIP_CONTEXT_PROCESSOR = GeoIPContextProcessor()
|
|
@ -0,0 +1,54 @@
|
||||||
|
"""Common logic for reading MMDB files"""
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from geoip2.database import Reader
|
||||||
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
|
from authentik.events.context_processors.base import EventContextProcessor
|
||||||
|
|
||||||
|
|
||||||
|
class MMDBContextProcessor(EventContextProcessor):
|
||||||
|
"""Common logic for reading MaxMind DB files, including re-loading if the file has changed"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.reader: Optional[Reader] = None
|
||||||
|
self._last_mtime: float = 0.0
|
||||||
|
self.logger = get_logger()
|
||||||
|
self.open()
|
||||||
|
|
||||||
|
def path(self) -> str | None:
|
||||||
|
"""Get the path to the MMDB file to load"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def open(self):
|
||||||
|
"""Get GeoIP Reader, if configured, otherwise none"""
|
||||||
|
path = self.path()
|
||||||
|
if path == "" or not path:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
self.reader = Reader(path)
|
||||||
|
self._last_mtime = Path(path).stat().st_mtime
|
||||||
|
self.logger.info("Loaded MMDB database", last_write=self._last_mtime, file=path)
|
||||||
|
except OSError as exc:
|
||||||
|
self.logger.warning("Failed to load MMDB database", exc=exc)
|
||||||
|
|
||||||
|
def check_expired(self):
|
||||||
|
"""Check if the modification date of the MMDB database has
|
||||||
|
changed, and reload it if so"""
|
||||||
|
path = self.path()
|
||||||
|
if path == "" or not path:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
mtime = Path(path).stat().st_mtime
|
||||||
|
diff = self._last_mtime < mtime
|
||||||
|
if diff > 0:
|
||||||
|
self.logger.info("Found new MMDB Database, reopening", diff=diff, path=path)
|
||||||
|
self.open()
|
||||||
|
except OSError as exc:
|
||||||
|
self.logger.warning("Failed to check MMDB age", exc=exc)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def enabled(self) -> bool:
|
||||||
|
"""Check if MMDB is enabled"""
|
||||||
|
return bool(self.reader)
|
|
@ -1,100 +0,0 @@
|
||||||
"""events GeoIP Reader"""
|
|
||||||
from os import stat
|
|
||||||
from typing import Optional, TypedDict
|
|
||||||
|
|
||||||
from geoip2.database import Reader
|
|
||||||
from geoip2.errors import GeoIP2Error
|
|
||||||
from geoip2.models import City
|
|
||||||
from sentry_sdk.hub import Hub
|
|
||||||
from structlog.stdlib import get_logger
|
|
||||||
|
|
||||||
from authentik.lib.config import CONFIG
|
|
||||||
|
|
||||||
LOGGER = get_logger()
|
|
||||||
|
|
||||||
|
|
||||||
class GeoIPDict(TypedDict):
|
|
||||||
"""GeoIP Details"""
|
|
||||||
|
|
||||||
continent: str
|
|
||||||
country: str
|
|
||||||
lat: float
|
|
||||||
long: float
|
|
||||||
city: str
|
|
||||||
|
|
||||||
|
|
||||||
class GeoIPReader:
|
|
||||||
"""Slim wrapper around GeoIP API"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.__reader: Optional[Reader] = None
|
|
||||||
self.__last_mtime: float = 0.0
|
|
||||||
self.__open()
|
|
||||||
|
|
||||||
def __open(self):
|
|
||||||
"""Get GeoIP Reader, if configured, otherwise none"""
|
|
||||||
path = CONFIG.get("geoip")
|
|
||||||
if path == "" or not path:
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
self.__reader = Reader(path)
|
|
||||||
self.__last_mtime = stat(path).st_mtime
|
|
||||||
LOGGER.info("Loaded GeoIP database", last_write=self.__last_mtime)
|
|
||||||
except OSError as exc:
|
|
||||||
LOGGER.warning("Failed to load GeoIP database", exc=exc)
|
|
||||||
|
|
||||||
def __check_expired(self):
|
|
||||||
"""Check if the modification date of the GeoIP database has
|
|
||||||
changed, and reload it if so"""
|
|
||||||
path = CONFIG.get("geoip")
|
|
||||||
try:
|
|
||||||
mtime = stat(path).st_mtime
|
|
||||||
diff = self.__last_mtime < mtime
|
|
||||||
if diff > 0:
|
|
||||||
LOGGER.info("Found new GeoIP Database, reopening", diff=diff)
|
|
||||||
self.__open()
|
|
||||||
except OSError as exc:
|
|
||||||
LOGGER.warning("Failed to check GeoIP age", exc=exc)
|
|
||||||
return
|
|
||||||
|
|
||||||
@property
|
|
||||||
def enabled(self) -> bool:
|
|
||||||
"""Check if GeoIP is enabled"""
|
|
||||||
return bool(self.__reader)
|
|
||||||
|
|
||||||
def city(self, ip_address: str) -> Optional[City]:
|
|
||||||
"""Wrapper for Reader.city"""
|
|
||||||
with Hub.current.start_span(
|
|
||||||
op="authentik.events.geo.city",
|
|
||||||
description=ip_address,
|
|
||||||
):
|
|
||||||
if not self.enabled:
|
|
||||||
return None
|
|
||||||
self.__check_expired()
|
|
||||||
try:
|
|
||||||
return self.__reader.city(ip_address)
|
|
||||||
except (GeoIP2Error, ValueError):
|
|
||||||
return None
|
|
||||||
|
|
||||||
def city_to_dict(self, city: City) -> GeoIPDict:
|
|
||||||
"""Convert City to dict"""
|
|
||||||
city_dict: GeoIPDict = {
|
|
||||||
"continent": city.continent.code,
|
|
||||||
"country": city.country.iso_code,
|
|
||||||
"lat": city.location.latitude,
|
|
||||||
"long": city.location.longitude,
|
|
||||||
"city": "",
|
|
||||||
}
|
|
||||||
if city.city.name:
|
|
||||||
city_dict["city"] = city.city.name
|
|
||||||
return city_dict
|
|
||||||
|
|
||||||
def city_dict(self, ip_address: str) -> Optional[GeoIPDict]:
|
|
||||||
"""Wrapper for self.city that returns a dict"""
|
|
||||||
city = self.city(ip_address)
|
|
||||||
if not city:
|
|
||||||
return None
|
|
||||||
return self.city_to_dict(city)
|
|
||||||
|
|
||||||
|
|
||||||
GEOIP_READER = GeoIPReader()
|
|
|
@ -26,7 +26,7 @@ from authentik.core.middleware import (
|
||||||
SESSION_KEY_IMPERSONATE_USER,
|
SESSION_KEY_IMPERSONATE_USER,
|
||||||
)
|
)
|
||||||
from authentik.core.models import ExpiringModel, Group, PropertyMapping, User
|
from authentik.core.models import ExpiringModel, Group, PropertyMapping, User
|
||||||
from authentik.events.geo import GEOIP_READER
|
from authentik.events.context_processors.base import get_context_processors
|
||||||
from authentik.events.utils import (
|
from authentik.events.utils import (
|
||||||
cleanse_dict,
|
cleanse_dict,
|
||||||
get_user,
|
get_user,
|
||||||
|
@ -246,21 +246,15 @@ class Event(SerializerModel, ExpiringModel):
|
||||||
self.user["on_behalf_of"] = get_user(request.session[SESSION_KEY_IMPERSONATE_USER])
|
self.user["on_behalf_of"] = get_user(request.session[SESSION_KEY_IMPERSONATE_USER])
|
||||||
# User 255.255.255.255 as fallback if IP cannot be determined
|
# User 255.255.255.255 as fallback if IP cannot be determined
|
||||||
self.client_ip = ClientIPMiddleware.get_client_ip(request)
|
self.client_ip = ClientIPMiddleware.get_client_ip(request)
|
||||||
# Apply GeoIP Data, when enabled
|
# Enrich event data
|
||||||
self.with_geoip()
|
for processor in get_context_processors():
|
||||||
|
processor.enrich_event(self)
|
||||||
# If there's no app set, we get it from the requests too
|
# If there's no app set, we get it from the requests too
|
||||||
if not self.app:
|
if not self.app:
|
||||||
self.app = Event._get_app_from_request(request)
|
self.app = Event._get_app_from_request(request)
|
||||||
self.save()
|
self.save()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def with_geoip(self): # pragma: no cover
|
|
||||||
"""Apply GeoIP Data, when enabled"""
|
|
||||||
city = GEOIP_READER.city_dict(self.client_ip)
|
|
||||||
if not city:
|
|
||||||
return
|
|
||||||
self.context["geo"] = city
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
if self._state.adding:
|
if self._state.adding:
|
||||||
LOGGER.info(
|
LOGGER.info(
|
||||||
|
@ -467,7 +461,7 @@ class NotificationTransport(SerializerModel):
|
||||||
}
|
}
|
||||||
mail = TemplateEmailMessage(
|
mail = TemplateEmailMessage(
|
||||||
subject=subject_prefix + context["title"],
|
subject=subject_prefix + context["title"],
|
||||||
to=[notification.user.email],
|
to=[f"{notification.user.name} <{notification.user.email}>"],
|
||||||
language=notification.user.locale(),
|
language=notification.user.locale(),
|
||||||
template_name="email/event_notification.html",
|
template_name="email/event_notification.html",
|
||||||
template_context=context,
|
template_context=context,
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
"""Test ASN Wrapper"""
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from authentik.events.context_processors.asn import ASNContextProcessor
|
||||||
|
|
||||||
|
|
||||||
|
class TestASN(TestCase):
|
||||||
|
"""Test ASN Wrapper"""
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
self.reader = ASNContextProcessor()
|
||||||
|
|
||||||
|
def test_simple(self):
|
||||||
|
"""Test simple asn wrapper"""
|
||||||
|
# IPs from
|
||||||
|
# https://github.com/maxmind/MaxMind-DB/blob/main/source-data/GeoLite2-ASN-Test.json
|
||||||
|
self.assertEqual(
|
||||||
|
self.reader.asn_dict("1.0.0.1"),
|
||||||
|
{
|
||||||
|
"asn": 15169,
|
||||||
|
"as_org": "Google Inc.",
|
||||||
|
"network": "1.0.0.0/24",
|
||||||
|
},
|
||||||
|
)
|
|
@ -1,14 +1,14 @@
|
||||||
"""Test GeoIP Wrapper"""
|
"""Test GeoIP Wrapper"""
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from authentik.events.geo import GeoIPReader
|
from authentik.events.context_processors.geoip import GeoIPContextProcessor
|
||||||
|
|
||||||
|
|
||||||
class TestGeoIP(TestCase):
|
class TestGeoIP(TestCase):
|
||||||
"""Test GeoIP Wrapper"""
|
"""Test GeoIP Wrapper"""
|
||||||
|
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
self.reader = GeoIPReader()
|
self.reader = GeoIPContextProcessor()
|
||||||
|
|
||||||
def test_simple(self):
|
def test_simple(self):
|
||||||
"""Test simple city wrapper"""
|
"""Test simple city wrapper"""
|
|
@ -17,12 +17,13 @@ from django.db.models.base import Model
|
||||||
from django.http.request import HttpRequest
|
from django.http.request import HttpRequest
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.views.debug import SafeExceptionReporterFilter
|
from django.views.debug import SafeExceptionReporterFilter
|
||||||
from geoip2.models import City
|
from geoip2.models import ASN, City
|
||||||
from guardian.utils import get_anonymous_user
|
from guardian.utils import get_anonymous_user
|
||||||
|
|
||||||
from authentik.blueprints.v1.common import YAMLTag
|
from authentik.blueprints.v1.common import YAMLTag
|
||||||
from authentik.core.models import User
|
from authentik.core.models import User
|
||||||
from authentik.events.geo import GEOIP_READER
|
from authentik.events.context_processors.asn import ASN_CONTEXT_PROCESSOR
|
||||||
|
from authentik.events.context_processors.geoip import GEOIP_CONTEXT_PROCESSOR
|
||||||
from authentik.policies.types import PolicyRequest
|
from authentik.policies.types import PolicyRequest
|
||||||
|
|
||||||
# Special keys which are *not* cleaned, even when the default filter
|
# Special keys which are *not* cleaned, even when the default filter
|
||||||
|
@ -123,7 +124,9 @@ def sanitize_item(value: Any) -> Any:
|
||||||
if isinstance(value, (HttpRequest, WSGIRequest)):
|
if isinstance(value, (HttpRequest, WSGIRequest)):
|
||||||
return ...
|
return ...
|
||||||
if isinstance(value, City):
|
if isinstance(value, City):
|
||||||
return GEOIP_READER.city_to_dict(value)
|
return GEOIP_CONTEXT_PROCESSOR.city_to_dict(value)
|
||||||
|
if isinstance(value, ASN):
|
||||||
|
return ASN_CONTEXT_PROCESSOR.asn_to_dict(value)
|
||||||
if isinstance(value, Path):
|
if isinstance(value, Path):
|
||||||
return str(value)
|
return str(value)
|
||||||
if isinstance(value, Exception):
|
if isinstance(value, Exception):
|
||||||
|
|
|
@ -35,6 +35,7 @@ REDIS_ENV_KEYS = [
|
||||||
]
|
]
|
||||||
|
|
||||||
DEPRECATIONS = {
|
DEPRECATIONS = {
|
||||||
|
"geoip": "events.context_processors.geoip",
|
||||||
"redis.broker_url": "broker.url",
|
"redis.broker_url": "broker.url",
|
||||||
"redis.broker_transport_options": "broker.transport_options",
|
"redis.broker_transport_options": "broker.transport_options",
|
||||||
"redis.cache_timeout": "cache.timeout",
|
"redis.cache_timeout": "cache.timeout",
|
||||||
|
|
|
@ -108,7 +108,10 @@ cookie_domain: null
|
||||||
disable_update_check: false
|
disable_update_check: false
|
||||||
disable_startup_analytics: false
|
disable_startup_analytics: false
|
||||||
avatars: env://AUTHENTIK_AUTHENTIK__AVATARS?gravatar,initials
|
avatars: env://AUTHENTIK_AUTHENTIK__AVATARS?gravatar,initials
|
||||||
geoip: "/geoip/GeoLite2-City.mmdb"
|
events:
|
||||||
|
context_processors:
|
||||||
|
geoip: "/geoip/GeoLite2-City.mmdb"
|
||||||
|
asn: "/geoip/GeoLite2-ASN.mmdb"
|
||||||
|
|
||||||
footer_links: []
|
footer_links: []
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,7 @@ class ReputationSerializer(ModelSerializer):
|
||||||
"identifier",
|
"identifier",
|
||||||
"ip",
|
"ip",
|
||||||
"ip_geo_data",
|
"ip_geo_data",
|
||||||
|
"ip_asn_data",
|
||||||
"score",
|
"score",
|
||||||
"updated",
|
"updated",
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 4.2.7 on 2023-12-05 22:20
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("authentik_policies_reputation", "0005_reputation_expires_reputation_expiring"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="reputation",
|
||||||
|
name="ip_asn_data",
|
||||||
|
field=models.JSONField(default=dict),
|
||||||
|
),
|
||||||
|
]
|
|
@ -76,6 +76,7 @@ class Reputation(ExpiringModel, SerializerModel):
|
||||||
identifier = models.TextField()
|
identifier = models.TextField()
|
||||||
ip = models.GenericIPAddressField()
|
ip = models.GenericIPAddressField()
|
||||||
ip_geo_data = models.JSONField(default=dict)
|
ip_geo_data = models.JSONField(default=dict)
|
||||||
|
ip_asn_data = models.JSONField(default=dict)
|
||||||
score = models.BigIntegerField(default=0)
|
score = models.BigIntegerField(default=0)
|
||||||
|
|
||||||
expires = models.DateTimeField(default=reputation_expiry)
|
expires = models.DateTimeField(default=reputation_expiry)
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
from authentik.events.geo import GEOIP_READER
|
from authentik.events.context_processors.asn import ASN_CONTEXT_PROCESSOR
|
||||||
|
from authentik.events.context_processors.geoip import GEOIP_CONTEXT_PROCESSOR
|
||||||
from authentik.events.monitored_tasks import (
|
from authentik.events.monitored_tasks import (
|
||||||
MonitoredTask,
|
MonitoredTask,
|
||||||
TaskResult,
|
TaskResult,
|
||||||
|
@ -26,7 +27,8 @@ def save_reputation(self: MonitoredTask):
|
||||||
ip=score["ip"],
|
ip=score["ip"],
|
||||||
identifier=score["identifier"],
|
identifier=score["identifier"],
|
||||||
)
|
)
|
||||||
rep.ip_geo_data = GEOIP_READER.city_dict(score["ip"]) or {}
|
rep.ip_geo_data = GEOIP_CONTEXT_PROCESSOR.city_dict(score["ip"]) or {}
|
||||||
|
rep.ip_asn_data = ASN_CONTEXT_PROCESSOR.asn_dict(score["ip"]) or {}
|
||||||
rep.score = score["score"]
|
rep.score = score["score"]
|
||||||
objects_to_update.append(rep)
|
objects_to_update.append(rep)
|
||||||
Reputation.objects.bulk_update(objects_to_update, ["score", "ip_geo_data"])
|
Reputation.objects.bulk_update(objects_to_update, ["score", "ip_geo_data"])
|
||||||
|
|
|
@ -8,7 +8,7 @@ from django.db.models import Model
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
from authentik.events.geo import GEOIP_READER
|
from authentik.events.context_processors.base import get_context_processors
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from authentik.core.models import User
|
from authentik.core.models import User
|
||||||
|
@ -37,15 +37,9 @@ class PolicyRequest:
|
||||||
|
|
||||||
def set_http_request(self, request: HttpRequest): # pragma: no cover
|
def set_http_request(self, request: HttpRequest): # pragma: no cover
|
||||||
"""Load data from HTTP request, including geoip when enabled"""
|
"""Load data from HTTP request, including geoip when enabled"""
|
||||||
from authentik.root.middleware import ClientIPMiddleware
|
|
||||||
|
|
||||||
self.http_request = request
|
self.http_request = request
|
||||||
if not GEOIP_READER.enabled:
|
for processor in get_context_processors():
|
||||||
return
|
self.context.update(processor.enrich_context(request))
|
||||||
client_ip = ClientIPMiddleware.get_client_ip(request)
|
|
||||||
if not client_ip:
|
|
||||||
return
|
|
||||||
self.context["geoip"] = GEOIP_READER.city(client_ip)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def should_cache(self) -> bool:
|
def should_cache(self) -> bool:
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
from drf_spectacular.utils import OpenApiResponse, extend_schema
|
from drf_spectacular.utils import OpenApiResponse, extend_schema
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.fields import BooleanField
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
@ -9,6 +10,7 @@ from rest_framework.viewsets import ModelViewSet
|
||||||
from authentik.admin.api.tasks import TaskSerializer
|
from authentik.admin.api.tasks import TaskSerializer
|
||||||
from authentik.core.api.providers import ProviderSerializer
|
from authentik.core.api.providers import ProviderSerializer
|
||||||
from authentik.core.api.used_by import UsedByMixin
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
|
from authentik.core.api.utils import PassiveSerializer
|
||||||
from authentik.events.monitored_tasks import TaskInfo
|
from authentik.events.monitored_tasks import TaskInfo
|
||||||
from authentik.providers.scim.models import SCIMProvider
|
from authentik.providers.scim.models import SCIMProvider
|
||||||
|
|
||||||
|
@ -37,6 +39,13 @@ class SCIMProviderSerializer(ProviderSerializer):
|
||||||
extra_kwargs = {}
|
extra_kwargs = {}
|
||||||
|
|
||||||
|
|
||||||
|
class SCIMSyncStatusSerializer(PassiveSerializer):
|
||||||
|
"""SCIM Provider sync status"""
|
||||||
|
|
||||||
|
is_running = BooleanField(read_only=True)
|
||||||
|
tasks = TaskSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
|
|
||||||
class SCIMProviderViewSet(UsedByMixin, ModelViewSet):
|
class SCIMProviderViewSet(UsedByMixin, ModelViewSet):
|
||||||
"""SCIMProvider Viewset"""
|
"""SCIMProvider Viewset"""
|
||||||
|
|
||||||
|
@ -48,15 +57,18 @@ class SCIMProviderViewSet(UsedByMixin, ModelViewSet):
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
responses={
|
responses={
|
||||||
200: TaskSerializer(),
|
200: SCIMSyncStatusSerializer(),
|
||||||
404: OpenApiResponse(description="Task not found"),
|
404: OpenApiResponse(description="Task not found"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@action(methods=["GET"], detail=True, pagination_class=None, filter_backends=[])
|
@action(methods=["GET"], detail=True, pagination_class=None, filter_backends=[])
|
||||||
def sync_status(self, request: Request, pk: int) -> Response:
|
def sync_status(self, request: Request, pk: int) -> Response:
|
||||||
"""Get provider's sync status"""
|
"""Get provider's sync status"""
|
||||||
provider = self.get_object()
|
provider: SCIMProvider = self.get_object()
|
||||||
task = TaskInfo.by_name(f"scim_sync:{slugify(provider.name)}")
|
task = TaskInfo.by_name(f"scim_sync:{slugify(provider.name)}")
|
||||||
if not task:
|
tasks = [task] if task else []
|
||||||
return Response(status=404)
|
status = {
|
||||||
return Response(TaskSerializer(task).data)
|
"tasks": tasks,
|
||||||
|
"is_running": provider.sync_lock.locked(),
|
||||||
|
}
|
||||||
|
return Response(SCIMSyncStatusSerializer(status).data)
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
"""SCIM constants"""
|
"""SCIM constants"""
|
||||||
PAGE_SIZE = 100
|
PAGE_SIZE = 100
|
||||||
|
PAGE_TIMEOUT = 60 * 60 * 0.5 # Half an hour
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
"""SCIM Provider models"""
|
"""SCIM Provider models"""
|
||||||
|
from django.core.cache import cache
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from guardian.shortcuts import get_anonymous_user
|
from guardian.shortcuts import get_anonymous_user
|
||||||
|
from redis.lock import Lock
|
||||||
from rest_framework.serializers import Serializer
|
from rest_framework.serializers import Serializer
|
||||||
|
|
||||||
from authentik.core.models import BackchannelProvider, Group, PropertyMapping, User, UserTypes
|
from authentik.core.models import BackchannelProvider, Group, PropertyMapping, User, UserTypes
|
||||||
|
from authentik.providers.scim.clients import PAGE_TIMEOUT
|
||||||
|
|
||||||
|
|
||||||
class SCIMProvider(BackchannelProvider):
|
class SCIMProvider(BackchannelProvider):
|
||||||
|
@ -27,6 +30,15 @@ class SCIMProvider(BackchannelProvider):
|
||||||
help_text=_("Property mappings used for group creation/updating."),
|
help_text=_("Property mappings used for group creation/updating."),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sync_lock(self) -> Lock:
|
||||||
|
"""Redis lock for syncing SCIM to prevent multiple parallel syncs happening"""
|
||||||
|
return Lock(
|
||||||
|
cache.client.get_client(),
|
||||||
|
name=f"goauthentik.io/providers/scim/sync-{str(self.pk)}",
|
||||||
|
timeout=(60 * 60 * PAGE_TIMEOUT) * 3,
|
||||||
|
)
|
||||||
|
|
||||||
def get_user_qs(self) -> QuerySet[User]:
|
def get_user_qs(self) -> QuerySet[User]:
|
||||||
"""Get queryset of all users with consistent ordering
|
"""Get queryset of all users with consistent ordering
|
||||||
according to the provider's settings"""
|
according to the provider's settings"""
|
||||||
|
|
|
@ -12,7 +12,7 @@ from structlog.stdlib import get_logger
|
||||||
from authentik.core.models import Group, User
|
from authentik.core.models import Group, User
|
||||||
from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
|
from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
|
||||||
from authentik.lib.utils.reflection import path_to_class
|
from authentik.lib.utils.reflection import path_to_class
|
||||||
from authentik.providers.scim.clients import PAGE_SIZE
|
from authentik.providers.scim.clients import PAGE_SIZE, PAGE_TIMEOUT
|
||||||
from authentik.providers.scim.clients.base import SCIMClient
|
from authentik.providers.scim.clients.base import SCIMClient
|
||||||
from authentik.providers.scim.clients.exceptions import SCIMRequestException, StopSync
|
from authentik.providers.scim.clients.exceptions import SCIMRequestException, StopSync
|
||||||
from authentik.providers.scim.clients.group import SCIMGroupClient
|
from authentik.providers.scim.clients.group import SCIMGroupClient
|
||||||
|
@ -47,12 +47,19 @@ def scim_sync(self: MonitoredTask, provider_pk: int) -> None:
|
||||||
).first()
|
).first()
|
||||||
if not provider:
|
if not provider:
|
||||||
return
|
return
|
||||||
|
lock = provider.sync_lock
|
||||||
|
if lock.locked():
|
||||||
|
LOGGER.debug("SCIM sync locked, skipping task", source=provider.name)
|
||||||
|
return
|
||||||
self.set_uid(slugify(provider.name))
|
self.set_uid(slugify(provider.name))
|
||||||
result = TaskResult(TaskResultStatus.SUCCESSFUL, [])
|
result = TaskResult(TaskResultStatus.SUCCESSFUL, [])
|
||||||
result.messages.append(_("Starting full SCIM sync"))
|
result.messages.append(_("Starting full SCIM sync"))
|
||||||
LOGGER.debug("Starting SCIM sync")
|
LOGGER.debug("Starting SCIM sync")
|
||||||
users_paginator = Paginator(provider.get_user_qs(), PAGE_SIZE)
|
users_paginator = Paginator(provider.get_user_qs(), PAGE_SIZE)
|
||||||
groups_paginator = Paginator(provider.get_group_qs(), PAGE_SIZE)
|
groups_paginator = Paginator(provider.get_group_qs(), PAGE_SIZE)
|
||||||
|
self.soft_time_limit = self.time_limit = (
|
||||||
|
users_paginator.count + groups_paginator.count
|
||||||
|
) * PAGE_TIMEOUT
|
||||||
with allow_join_result():
|
with allow_join_result():
|
||||||
try:
|
try:
|
||||||
for page in users_paginator.page_range:
|
for page in users_paginator.page_range:
|
||||||
|
@ -69,7 +76,10 @@ def scim_sync(self: MonitoredTask, provider_pk: int) -> None:
|
||||||
self.set_status(result)
|
self.set_status(result)
|
||||||
|
|
||||||
|
|
||||||
@CELERY_APP.task()
|
@CELERY_APP.task(
|
||||||
|
soft_time_limit=PAGE_TIMEOUT,
|
||||||
|
task_time_limit=PAGE_TIMEOUT,
|
||||||
|
)
|
||||||
def scim_sync_users(page: int, provider_pk: int):
|
def scim_sync_users(page: int, provider_pk: int):
|
||||||
"""Sync single or multiple users to SCIM"""
|
"""Sync single or multiple users to SCIM"""
|
||||||
messages = []
|
messages = []
|
||||||
|
|
|
@ -32,7 +32,8 @@ class PytestTestRunner(DiscoverRunner): # pragma: no cover
|
||||||
settings.TEST = True
|
settings.TEST = True
|
||||||
settings.CELERY["task_always_eager"] = True
|
settings.CELERY["task_always_eager"] = True
|
||||||
CONFIG.set("avatars", "none")
|
CONFIG.set("avatars", "none")
|
||||||
CONFIG.set("geoip", "tests/GeoLite2-City-Test.mmdb")
|
CONFIG.set("events.context_processors.geoip", "tests/GeoLite2-City-Test.mmdb")
|
||||||
|
CONFIG.set("events.context_processors.asn", "tests/GeoLite2-ASN-Test.mmdb")
|
||||||
CONFIG.set("blueprints_dir", "./blueprints")
|
CONFIG.set("blueprints_dir", "./blueprints")
|
||||||
CONFIG.set(
|
CONFIG.set(
|
||||||
"outposts.container_image_base",
|
"outposts.container_image_base",
|
||||||
|
|
|
@ -110,7 +110,7 @@ class EmailStageView(ChallengeStageView):
|
||||||
try:
|
try:
|
||||||
message = TemplateEmailMessage(
|
message = TemplateEmailMessage(
|
||||||
subject=_(current_stage.subject),
|
subject=_(current_stage.subject),
|
||||||
to=[email],
|
to=[f"{pending_user.name} <{email}>"],
|
||||||
language=pending_user.locale(self.request),
|
language=pending_user.locale(self.request),
|
||||||
template_name=current_stage.template,
|
template_name=current_stage.template,
|
||||||
template_context={
|
template_context={
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
{% load i18n %}{% translate "Welcome!" %}
|
||||||
|
|
||||||
|
{% translate "We're excited to have you get started. First, you need to confirm your account. Just open the link below." %}
|
||||||
|
|
||||||
|
{{ url }}
|
||||||
|
|
||||||
|
--
|
||||||
|
Powered by goauthentik.io.
|
|
@ -44,7 +44,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding: 20px; font-size: 12px; color: #212124;" align="center">
|
<td style="padding: 20px; font-size: 12px; color: #212124;" align="center">
|
||||||
{% blocktranslate with name=source.from %}
|
{% blocktranslate with name=source.from %}
|
||||||
This email was sent from the notification transport <code>{{name}}</code>.
|
This email was sent from the notification transport <code>{{ name }}</code>.
|
||||||
{% endblocktranslate %}
|
{% endblocktranslate %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
{% load authentik_stages_email %}{% load i18n %}{% translate "Dear authentik user," %}
|
||||||
|
|
||||||
|
{% translate "The following notification was created:" %}
|
||||||
|
|
||||||
|
{{ body|indent }}
|
||||||
|
|
||||||
|
{% if key_value %}
|
||||||
|
{% translate "Additional attributes:" %}
|
||||||
|
{% for key, value in key_value.items %}
|
||||||
|
{{ key }}: {{ value|indent }}{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if source %}{% blocktranslate with name=source.from %}
|
||||||
|
This email was sent from the notification transport {{ name }}.
|
||||||
|
{% endblocktranslate %}{% endif %}
|
||||||
|
|
||||||
|
--
|
||||||
|
Powered by goauthentik.io.
|
|
@ -0,0 +1,12 @@
|
||||||
|
{% load i18n %}{% load humanize %}{% blocktrans with username=user.username %}Hi {{ username }},{% endblocktrans %}
|
||||||
|
|
||||||
|
{% blocktrans %}
|
||||||
|
You recently requested to change your password for your authentik account. Use the link below to set a new password.
|
||||||
|
{% endblocktrans %}
|
||||||
|
{{ url }}
|
||||||
|
{% blocktrans with expires=expires|naturaltime %}
|
||||||
|
If you did not request a password change, please ignore this Email. The link above is valid for {{ expires }}.
|
||||||
|
{% endblocktrans %}
|
||||||
|
|
||||||
|
--
|
||||||
|
Powered by goauthentik.io.
|
|
@ -0,0 +1,7 @@
|
||||||
|
{% load i18n %}authentik Test-Email
|
||||||
|
{% blocktrans %}
|
||||||
|
This is a test email to inform you, that you've successfully configured authentik emails.
|
||||||
|
{% endblocktrans %}
|
||||||
|
|
||||||
|
--
|
||||||
|
Powered by goauthentik.io.
|
|
@ -29,3 +29,9 @@ def inline_static_binary(path: str) -> str:
|
||||||
b64content = b64encode(_file.read().encode())
|
b64content = b64encode(_file.read().encode())
|
||||||
return f"data:image/{result.suffix};base64,{b64content.decode('utf-8')}"
|
return f"data:image/{result.suffix};base64,{b64content.decode('utf-8')}"
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter(name="indent")
|
||||||
|
def indent_string(val, num_spaces=4):
|
||||||
|
"""Intent text by a given amount of spaces"""
|
||||||
|
return val.replace("\n", "\n" + " " * num_spaces)
|
||||||
|
|
|
@ -58,9 +58,11 @@ class TestEmailStageSending(FlowTestCase):
|
||||||
events = Event.objects.filter(action=EventAction.EMAIL_SENT)
|
events = Event.objects.filter(action=EventAction.EMAIL_SENT)
|
||||||
self.assertEqual(len(events), 1)
|
self.assertEqual(len(events), 1)
|
||||||
event = events.first()
|
event = events.first()
|
||||||
self.assertEqual(event.context["message"], f"Email to {self.user.email} sent")
|
self.assertEqual(
|
||||||
|
event.context["message"], f"Email to {self.user.name} <{self.user.email}> sent"
|
||||||
|
)
|
||||||
self.assertEqual(event.context["subject"], "authentik")
|
self.assertEqual(event.context["subject"], "authentik")
|
||||||
self.assertEqual(event.context["to_email"], [self.user.email])
|
self.assertEqual(event.context["to_email"], [f"{self.user.name} <{self.user.email}>"])
|
||||||
self.assertEqual(event.context["from_email"], "system@authentik.local")
|
self.assertEqual(event.context["from_email"], "system@authentik.local")
|
||||||
|
|
||||||
def test_pending_fake_user(self):
|
def test_pending_fake_user(self):
|
||||||
|
|
|
@ -94,7 +94,7 @@ class TestEmailStage(FlowTestCase):
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(len(mail.outbox), 1)
|
self.assertEqual(len(mail.outbox), 1)
|
||||||
self.assertEqual(mail.outbox[0].subject, "authentik")
|
self.assertEqual(mail.outbox[0].subject, "authentik")
|
||||||
self.assertEqual(mail.outbox[0].to, [self.user.email])
|
self.assertEqual(mail.outbox[0].to, [f"{self.user.name} <{self.user.email}>"])
|
||||||
|
|
||||||
@patch(
|
@patch(
|
||||||
"authentik.stages.email.models.EmailStage.backend_class",
|
"authentik.stages.email.models.EmailStage.backend_class",
|
||||||
|
@ -114,7 +114,7 @@ class TestEmailStage(FlowTestCase):
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(len(mail.outbox), 1)
|
self.assertEqual(len(mail.outbox), 1)
|
||||||
self.assertEqual(mail.outbox[0].subject, "authentik")
|
self.assertEqual(mail.outbox[0].subject, "authentik")
|
||||||
self.assertEqual(mail.outbox[0].to, ["foo@bar.baz"])
|
self.assertEqual(mail.outbox[0].to, [f"{self.user.name} <foo@bar.baz>"])
|
||||||
|
|
||||||
@patch(
|
@patch(
|
||||||
"authentik.stages.email.models.EmailStage.backend_class",
|
"authentik.stages.email.models.EmailStage.backend_class",
|
||||||
|
|
|
@ -4,6 +4,7 @@ from functools import lru_cache
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from django.core.mail import EmailMultiAlternatives
|
from django.core.mail import EmailMultiAlternatives
|
||||||
|
from django.template.exceptions import TemplateDoesNotExist
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.utils import translation
|
from django.utils import translation
|
||||||
|
|
||||||
|
@ -24,9 +25,15 @@ class TemplateEmailMessage(EmailMultiAlternatives):
|
||||||
"""Wrapper around EmailMultiAlternatives with integrated template rendering"""
|
"""Wrapper around EmailMultiAlternatives with integrated template rendering"""
|
||||||
|
|
||||||
def __init__(self, template_name=None, template_context=None, language="", **kwargs):
|
def __init__(self, template_name=None, template_context=None, language="", **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
with translation.override(language):
|
with translation.override(language):
|
||||||
html_content = render_to_string(template_name, template_context)
|
html_content = render_to_string(template_name, template_context)
|
||||||
super().__init__(**kwargs)
|
try:
|
||||||
self.content_subtype = "html"
|
text_content = render_to_string(
|
||||||
|
template_name.replace("html", "txt"), template_context
|
||||||
|
)
|
||||||
|
self.body = text_content
|
||||||
|
except TemplateDoesNotExist:
|
||||||
|
pass
|
||||||
self.mixed_subtype = "related"
|
self.mixed_subtype = "related"
|
||||||
self.attach_alternative(html_content, "text/html")
|
self.attach_alternative(html_content, "text/html")
|
||||||
|
|
|
@ -32,7 +32,7 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- redis:/data
|
- redis:/data
|
||||||
server:
|
server:
|
||||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.10.4}
|
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.10.5}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: server
|
command: server
|
||||||
environment:
|
environment:
|
||||||
|
@ -53,7 +53,7 @@ services:
|
||||||
- postgresql
|
- postgresql
|
||||||
- redis
|
- redis
|
||||||
worker:
|
worker:
|
||||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.10.4}
|
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.10.5}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: worker
|
command: worker
|
||||||
environment:
|
environment:
|
||||||
|
|
4
go.mod
|
@ -23,11 +23,11 @@ require (
|
||||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
|
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
|
||||||
github.com/pires/go-proxyproto v0.7.0
|
github.com/pires/go-proxyproto v0.7.0
|
||||||
github.com/prometheus/client_golang v1.17.0
|
github.com/prometheus/client_golang v1.17.0
|
||||||
github.com/redis/go-redis/v9 v9.3.0
|
github.com/redis/go-redis/v9 v9.3.1
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/spf13/cobra v1.8.0
|
github.com/spf13/cobra v1.8.0
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.8.4
|
||||||
goauthentik.io/api/v3 v3.2023104.3
|
goauthentik.io/api/v3 v3.2023104.5
|
||||||
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
||||||
golang.org/x/oauth2 v0.15.0
|
golang.org/x/oauth2 v0.15.0
|
||||||
golang.org/x/sync v0.5.0
|
golang.org/x/sync v0.5.0
|
||||||
|
|
8
go.sum
|
@ -256,8 +256,8 @@ github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdO
|
||||||
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
|
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
|
||||||
github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
|
github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
|
||||||
github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
|
github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
|
||||||
github.com/redis/go-redis/v9 v9.3.0 h1:RiVDjmig62jIWp7Kk4XVLs0hzV6pI3PyTnnL0cnn0u0=
|
github.com/redis/go-redis/v9 v9.3.1 h1:KqdY8U+3X6z+iACvumCNxnoluToB+9Me+TvyFa21Mds=
|
||||||
github.com/redis/go-redis/v9 v9.3.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
github.com/redis/go-redis/v9 v9.3.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||||
|
@ -309,8 +309,8 @@ go.opentelemetry.io/otel/trace v1.17.0 h1:/SWhSRHmDPOImIAetP1QAeMnZYiQXrTy4fMMYO
|
||||||
go.opentelemetry.io/otel/trace v1.17.0/go.mod h1:I/4vKTgFclIsXRVucpH25X0mpFSczM7aHeaz0ZBLWjY=
|
go.opentelemetry.io/otel/trace v1.17.0/go.mod h1:I/4vKTgFclIsXRVucpH25X0mpFSczM7aHeaz0ZBLWjY=
|
||||||
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
||||||
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
||||||
goauthentik.io/api/v3 v3.2023104.3 h1:MzwdB21Q+G+wACEZiX0T1iVV4l7PjopjaVv6muqJE1M=
|
goauthentik.io/api/v3 v3.2023104.5 h1:CWaQq44DxElyqMvZVjqMhWIr1vXzobf4eqFUas0lMgs=
|
||||||
goauthentik.io/api/v3 v3.2023104.3/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
goauthentik.io/api/v3 v3.2023104.5/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-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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
|
|
@ -29,4 +29,4 @@ func UserAgent() string {
|
||||||
return fmt.Sprintf("authentik@%s", FullVersion())
|
return fmt.Sprintf("authentik@%s", FullVersion())
|
||||||
}
|
}
|
||||||
|
|
||||||
const VERSION = "2023.10.4"
|
const VERSION = "2023.10.5"
|
||||||
|
|
|
@ -113,7 +113,7 @@ filterwarnings = [
|
||||||
|
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "authentik"
|
name = "authentik"
|
||||||
version = "2023.10.4"
|
version = "2023.10.5"
|
||||||
description = ""
|
description = ""
|
||||||
authors = ["authentik Team <hello@goauthentik.io>"]
|
authors = ["authentik Team <hello@goauthentik.io>"]
|
||||||
|
|
||||||
|
|
43
schema.yml
|
@ -1,7 +1,7 @@
|
||||||
openapi: 3.0.3
|
openapi: 3.0.3
|
||||||
info:
|
info:
|
||||||
title: authentik
|
title: authentik
|
||||||
version: 2023.10.4
|
version: 2023.10.5
|
||||||
description: Making authentication simple.
|
description: Making authentication simple.
|
||||||
contact:
|
contact:
|
||||||
email: hello@goauthentik.io
|
email: hello@goauthentik.io
|
||||||
|
@ -17079,7 +17079,7 @@ paths:
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Task'
|
$ref: '#/components/schemas/SCIMSyncStatus'
|
||||||
description: ''
|
description: ''
|
||||||
'404':
|
'404':
|
||||||
description: Task not found
|
description: Task not found
|
||||||
|
@ -28280,7 +28280,7 @@ components:
|
||||||
readOnly: true
|
readOnly: true
|
||||||
geo_ip:
|
geo_ip:
|
||||||
type: object
|
type: object
|
||||||
description: Get parsed user agent
|
description: Get GeoIP Data
|
||||||
properties:
|
properties:
|
||||||
continent:
|
continent:
|
||||||
type: string
|
type: string
|
||||||
|
@ -28302,6 +28302,24 @@ components:
|
||||||
- long
|
- long
|
||||||
nullable: true
|
nullable: true
|
||||||
readOnly: true
|
readOnly: true
|
||||||
|
asn:
|
||||||
|
type: object
|
||||||
|
description: Get ASN Data
|
||||||
|
properties:
|
||||||
|
asn:
|
||||||
|
type: integer
|
||||||
|
as_org:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
network:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
required:
|
||||||
|
- as_org
|
||||||
|
- asn
|
||||||
|
- network
|
||||||
|
nullable: true
|
||||||
|
readOnly: true
|
||||||
user:
|
user:
|
||||||
type: integer
|
type: integer
|
||||||
last_ip:
|
last_ip:
|
||||||
|
@ -28316,6 +28334,7 @@ components:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
required:
|
required:
|
||||||
|
- asn
|
||||||
- current
|
- current
|
||||||
- geo_ip
|
- geo_ip
|
||||||
- last_ip
|
- last_ip
|
||||||
|
@ -29283,6 +29302,7 @@ components:
|
||||||
enum:
|
enum:
|
||||||
- can_save_media
|
- can_save_media
|
||||||
- can_geo_ip
|
- can_geo_ip
|
||||||
|
- can_asn
|
||||||
- can_impersonate
|
- can_impersonate
|
||||||
- can_debug
|
- can_debug
|
||||||
- is_enterprise
|
- is_enterprise
|
||||||
|
@ -29290,6 +29310,7 @@ components:
|
||||||
description: |-
|
description: |-
|
||||||
* `can_save_media` - Can Save Media
|
* `can_save_media` - Can Save Media
|
||||||
* `can_geo_ip` - Can Geo Ip
|
* `can_geo_ip` - Can Geo Ip
|
||||||
|
* `can_asn` - Can Asn
|
||||||
* `can_impersonate` - Can Impersonate
|
* `can_impersonate` - Can Impersonate
|
||||||
* `can_debug` - Can Debug
|
* `can_debug` - Can Debug
|
||||||
* `is_enterprise` - Is Enterprise
|
* `is_enterprise` - Is Enterprise
|
||||||
|
@ -39667,6 +39688,7 @@ components:
|
||||||
ip:
|
ip:
|
||||||
type: string
|
type: string
|
||||||
ip_geo_data: {}
|
ip_geo_data: {}
|
||||||
|
ip_asn_data: {}
|
||||||
score:
|
score:
|
||||||
type: integer
|
type: integer
|
||||||
maximum: 9223372036854775807
|
maximum: 9223372036854775807
|
||||||
|
@ -40623,6 +40645,21 @@ components:
|
||||||
- name
|
- name
|
||||||
- token
|
- token
|
||||||
- url
|
- url
|
||||||
|
SCIMSyncStatus:
|
||||||
|
type: object
|
||||||
|
description: SCIM Provider sync status
|
||||||
|
properties:
|
||||||
|
is_running:
|
||||||
|
type: boolean
|
||||||
|
readOnly: true
|
||||||
|
tasks:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Task'
|
||||||
|
readOnly: true
|
||||||
|
required:
|
||||||
|
- is_running
|
||||||
|
- tasks
|
||||||
SMSDevice:
|
SMSDevice:
|
||||||
type: object
|
type: object
|
||||||
description: Serializer for sms authenticator devices
|
description: Serializer for sms authenticator devices
|
||||||
|
|
|
@ -17,7 +17,12 @@ with open("local.env.yml", "w", encoding="utf-8") as _config:
|
||||||
},
|
},
|
||||||
"blueprints_dir": "./blueprints",
|
"blueprints_dir": "./blueprints",
|
||||||
"cert_discovery_dir": "./certs",
|
"cert_discovery_dir": "./certs",
|
||||||
"geoip": "tests/GeoLite2-City-Test.mmdb",
|
"events": {
|
||||||
|
"processors": {
|
||||||
|
"geoip": "tests/GeoLite2-City-Test.mmdb",
|
||||||
|
"asn": "tests/GeoLite2-ASN-Test.mmdb",
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
_config,
|
_config,
|
||||||
default_flow_style=False,
|
default_flow_style=False,
|
||||||
|
|
After Width: | Height: | Size: 12 KiB |
|
@ -9,10 +9,10 @@
|
||||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.15.0",
|
"@typescript-eslint/eslint-plugin": "^6.15.0",
|
||||||
"@typescript-eslint/parser": "^6.15.0",
|
"@typescript-eslint/parser": "^6.15.0",
|
||||||
"@wdio/cli": "^8.26.3",
|
"@wdio/cli": "^8.27.0",
|
||||||
"@wdio/local-runner": "^8.26.3",
|
"@wdio/local-runner": "^8.27.0",
|
||||||
"@wdio/mocha-framework": "^8.26.3",
|
"@wdio/mocha-framework": "^8.27.0",
|
||||||
"@wdio/spec-reporter": "^8.26.3",
|
"@wdio/spec-reporter": "^8.27.0",
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^8.56.0",
|
||||||
"eslint-config-google": "^0.14.0",
|
"eslint-config-google": "^0.14.0",
|
||||||
"eslint-plugin-sonarjs": "^0.23.0",
|
"eslint-plugin-sonarjs": "^0.23.0",
|
||||||
|
@ -1141,18 +1141,18 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@wdio/cli": {
|
"node_modules/@wdio/cli": {
|
||||||
"version": "8.26.3",
|
"version": "8.27.0",
|
||||||
"resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.26.3.tgz",
|
"resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.27.0.tgz",
|
||||||
"integrity": "sha512-wrq145sNBw4DrsF5GEK8TBxqVWln7GZpNpM5QeDqCcZzVHIqDud4f7nADgZGbR8dJ96NVfC3rby6wbeRQUA+eg==",
|
"integrity": "sha512-wdNYNvu52XxOqNHqDMGAtexBz+MM0RE2Z5U5ljyllbP3ed5vcvvK9vswURtI4cFGoqobVeoC7wif3VeD3aN+aQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "^20.1.1",
|
"@types/node": "^20.1.1",
|
||||||
"@wdio/config": "8.26.3",
|
"@wdio/config": "8.27.0",
|
||||||
"@wdio/globals": "8.26.3",
|
"@wdio/globals": "8.27.0",
|
||||||
"@wdio/logger": "8.24.12",
|
"@wdio/logger": "8.24.12",
|
||||||
"@wdio/protocols": "8.24.12",
|
"@wdio/protocols": "8.24.12",
|
||||||
"@wdio/types": "8.26.3",
|
"@wdio/types": "8.27.0",
|
||||||
"@wdio/utils": "8.26.3",
|
"@wdio/utils": "8.27.0",
|
||||||
"async-exit-hook": "^2.0.1",
|
"async-exit-hook": "^2.0.1",
|
||||||
"chalk": "^5.2.0",
|
"chalk": "^5.2.0",
|
||||||
"chokidar": "^3.5.3",
|
"chokidar": "^3.5.3",
|
||||||
|
@ -1167,7 +1167,7 @@
|
||||||
"lodash.union": "^4.6.0",
|
"lodash.union": "^4.6.0",
|
||||||
"read-pkg-up": "^10.0.0",
|
"read-pkg-up": "^10.0.0",
|
||||||
"recursive-readdir": "^2.2.3",
|
"recursive-readdir": "^2.2.3",
|
||||||
"webdriverio": "8.26.3",
|
"webdriverio": "8.27.0",
|
||||||
"yargs": "^17.7.2"
|
"yargs": "^17.7.2"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
|
@ -1190,14 +1190,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@wdio/config": {
|
"node_modules/@wdio/config": {
|
||||||
"version": "8.26.3",
|
"version": "8.27.0",
|
||||||
"resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.26.3.tgz",
|
"resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.27.0.tgz",
|
||||||
"integrity": "sha512-NWh2JXRSyP4gY+jeC79u0L3hSXW/s3rOWez4M6qAglT91fZTXFbIl1GM8lnZlCq03ye2qFPqYrZ+4tGNQj7YxQ==",
|
"integrity": "sha512-zYM5daeiBVVAbQj0ASymAt0RUsocLVIwKiUHNa8gg/1GsZnztGjetXExSp1gXlxtMVM5xWUSKjh6ceFK79gWDQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wdio/logger": "8.24.12",
|
"@wdio/logger": "8.24.12",
|
||||||
"@wdio/types": "8.26.3",
|
"@wdio/types": "8.27.0",
|
||||||
"@wdio/utils": "8.26.3",
|
"@wdio/utils": "8.27.0",
|
||||||
"decamelize": "^6.0.0",
|
"decamelize": "^6.0.0",
|
||||||
"deepmerge-ts": "^5.0.0",
|
"deepmerge-ts": "^5.0.0",
|
||||||
"glob": "^10.2.2",
|
"glob": "^10.2.2",
|
||||||
|
@ -1208,29 +1208,29 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@wdio/globals": {
|
"node_modules/@wdio/globals": {
|
||||||
"version": "8.26.3",
|
"version": "8.27.0",
|
||||||
"resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.26.3.tgz",
|
"resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.27.0.tgz",
|
||||||
"integrity": "sha512-RW3UsvnUb4DjxVOqIngXQMcDJlbH+QL/LeChznUF0FW+Mqg/mZWukBld5/dDwgQHk9F2TOzc8ctk5FM3s1AoWQ==",
|
"integrity": "sha512-HUPOIsrmxfF0LhU68lVsNGQGZkW/bWOvcCd8WxeaggTAH9JyxasxxfwzeCceAuhAvwtlwoMXITOpjAXO2mj38Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^16.13 || >=18"
|
"node": "^16.13 || >=18"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"expect-webdriverio": "^4.6.1",
|
"expect-webdriverio": "^4.6.1",
|
||||||
"webdriverio": "8.26.3"
|
"webdriverio": "8.27.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@wdio/local-runner": {
|
"node_modules/@wdio/local-runner": {
|
||||||
"version": "8.26.3",
|
"version": "8.27.0",
|
||||||
"resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.26.3.tgz",
|
"resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.27.0.tgz",
|
||||||
"integrity": "sha512-YWxTBp6tc8Dlz09rnRhV2GXV4b3w5G0WyYEf81D+kXDI3QxDvYn6QujByr6Q7fQ9yLwJU4ptnT2uL5IbAwVo2A==",
|
"integrity": "sha512-nxS17mhoLkXP20eoPMkz7tbMFMOQejSw0hZfkEvuDCNhJokr8ugp6IjYXL9f7yV9IB9UDGHox8WGY4ArSrOeBA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "^20.1.0",
|
"@types/node": "^20.1.0",
|
||||||
"@wdio/logger": "8.24.12",
|
"@wdio/logger": "8.24.12",
|
||||||
"@wdio/repl": "8.24.12",
|
"@wdio/repl": "8.24.12",
|
||||||
"@wdio/runner": "8.26.3",
|
"@wdio/runner": "8.27.0",
|
||||||
"@wdio/types": "8.26.3",
|
"@wdio/types": "8.27.0",
|
||||||
"async-exit-hook": "^2.0.1",
|
"async-exit-hook": "^2.0.1",
|
||||||
"split2": "^4.1.0",
|
"split2": "^4.1.0",
|
||||||
"stream-buffers": "^3.0.2"
|
"stream-buffers": "^3.0.2"
|
||||||
|
@ -1267,16 +1267,16 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@wdio/mocha-framework": {
|
"node_modules/@wdio/mocha-framework": {
|
||||||
"version": "8.26.3",
|
"version": "8.27.0",
|
||||||
"resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.26.3.tgz",
|
"resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.27.0.tgz",
|
||||||
"integrity": "sha512-r9uHUcXhh6TKFqTBCacnbtQx0nZjFsnV9Pony8CY9qcNCn2q666ucQbrtMfZdjuevhn5N0E710+El4eAvK3jyw==",
|
"integrity": "sha512-NaFUPv90ks1XlZy0qdUaJ5/ilBtiCCgTIxaPexshJiaVDT5cV+Igjag/O80HIcvqknOZpdKAR0I1ArQzhJrmcA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/mocha": "^10.0.0",
|
"@types/mocha": "^10.0.0",
|
||||||
"@types/node": "^20.1.0",
|
"@types/node": "^20.1.0",
|
||||||
"@wdio/logger": "8.24.12",
|
"@wdio/logger": "8.24.12",
|
||||||
"@wdio/types": "8.26.3",
|
"@wdio/types": "8.27.0",
|
||||||
"@wdio/utils": "8.26.3",
|
"@wdio/utils": "8.27.0",
|
||||||
"mocha": "^10.0.0"
|
"mocha": "^10.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
@ -1302,14 +1302,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@wdio/reporter": {
|
"node_modules/@wdio/reporter": {
|
||||||
"version": "8.26.3",
|
"version": "8.27.0",
|
||||||
"resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.26.3.tgz",
|
"resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.27.0.tgz",
|
||||||
"integrity": "sha512-F/sF1Hwxp1osM2wto4JydONQGxqkbOhwbLM0o4Y8eHPgK7/m+Kn9uygBDqPVpgQnpf0kUfhpICe9gaZzG4Jt+g==",
|
"integrity": "sha512-kBwsrHbsblmXfHSWlaOKXjPRPeT29WSKTUoCmzuTcCkhvbjY4TrEB0p04cpaM7uNqdIZTxHng54gZVaG/nZPiw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "^20.1.0",
|
"@types/node": "^20.1.0",
|
||||||
"@wdio/logger": "8.24.12",
|
"@wdio/logger": "8.24.12",
|
||||||
"@wdio/types": "8.26.3",
|
"@wdio/types": "8.27.0",
|
||||||
"diff": "^5.0.0",
|
"diff": "^5.0.0",
|
||||||
"object-inspect": "^1.12.0"
|
"object-inspect": "^1.12.0"
|
||||||
},
|
},
|
||||||
|
@ -1318,35 +1318,35 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@wdio/runner": {
|
"node_modules/@wdio/runner": {
|
||||||
"version": "8.26.3",
|
"version": "8.27.0",
|
||||||
"resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.26.3.tgz",
|
"resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.27.0.tgz",
|
||||||
"integrity": "sha512-mbZGkBbXTRtj1hL5QUbNxpJvhE4rkXvYlUuea1uOVk3e2/+k2dZeGeKPgh1Q7Dt07118dfujCB7pQCYldE/dGg==",
|
"integrity": "sha512-da332r2d1QXdRhMhsDxMObcqLZS0l/u14pHICNTvEHp+72gOttbjUDvdMHPQY6Ae5ul7AVVQ05qpmz9CX7TzOg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "^20.1.0",
|
"@types/node": "^20.1.0",
|
||||||
"@wdio/config": "8.26.3",
|
"@wdio/config": "8.27.0",
|
||||||
"@wdio/globals": "8.26.3",
|
"@wdio/globals": "8.27.0",
|
||||||
"@wdio/logger": "8.24.12",
|
"@wdio/logger": "8.24.12",
|
||||||
"@wdio/types": "8.26.3",
|
"@wdio/types": "8.27.0",
|
||||||
"@wdio/utils": "8.26.3",
|
"@wdio/utils": "8.27.0",
|
||||||
"deepmerge-ts": "^5.0.0",
|
"deepmerge-ts": "^5.0.0",
|
||||||
"expect-webdriverio": "^4.6.1",
|
"expect-webdriverio": "^4.6.1",
|
||||||
"gaze": "^1.1.2",
|
"gaze": "^1.1.2",
|
||||||
"webdriver": "8.26.3",
|
"webdriver": "8.27.0",
|
||||||
"webdriverio": "8.26.3"
|
"webdriverio": "8.27.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^16.13 || >=18"
|
"node": "^16.13 || >=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@wdio/spec-reporter": {
|
"node_modules/@wdio/spec-reporter": {
|
||||||
"version": "8.26.3",
|
"version": "8.27.0",
|
||||||
"resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.26.3.tgz",
|
"resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.27.0.tgz",
|
||||||
"integrity": "sha512-YfKlBOmxGyJk08BpDnsjPsp85XyhG7Cu2qoAVxtJ8kkJOZaGfUg9TBV9DXDqvdZcxCMnPfDfQIda6LzfkZf58Q==",
|
"integrity": "sha512-EOXLBIr4oLzSDp/BQ86IqCulSF0jwEAj2EiMeY6dh9WXzBBtoR8WnoX/27xFoZ8GU2zetWC3EVnLJ0Ex8Up1mA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wdio/reporter": "8.26.3",
|
"@wdio/reporter": "8.27.0",
|
||||||
"@wdio/types": "8.26.3",
|
"@wdio/types": "8.27.0",
|
||||||
"chalk": "^5.1.2",
|
"chalk": "^5.1.2",
|
||||||
"easy-table": "^1.2.0",
|
"easy-table": "^1.2.0",
|
||||||
"pretty-ms": "^7.0.0"
|
"pretty-ms": "^7.0.0"
|
||||||
|
@ -1368,9 +1368,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@wdio/types": {
|
"node_modules/@wdio/types": {
|
||||||
"version": "8.26.3",
|
"version": "8.27.0",
|
||||||
"resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.26.3.tgz",
|
"resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.27.0.tgz",
|
||||||
"integrity": "sha512-WOxvSV4sKJ5QCRNTJHeKCzgO2TZmcK1eDlJ+FObt9Pnt+4pCRy/881eVY/Aj2bozn2hhzq0AK/h6oPAUV/gjCg==",
|
"integrity": "sha512-LbP9FKh8r0uW9/dKhTIUCC1Su8PsP9TmzGKXkWt6/IMacgJiB/zW3u1CgyaLw9lG0UiQORHGoeJX9zB2HZAh4w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "^20.1.0"
|
"@types/node": "^20.1.0"
|
||||||
|
@ -1380,14 +1380,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@wdio/utils": {
|
"node_modules/@wdio/utils": {
|
||||||
"version": "8.26.3",
|
"version": "8.27.0",
|
||||||
"resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.26.3.tgz",
|
"resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.27.0.tgz",
|
||||||
"integrity": "sha512-LA/iCKgJQemAAXoN6vHyKBtngdkFUWEnjB8Yd1Xm3gUQTvY4GVlvcqOxC2RF5Th7/L2LNxc6TWuErYv/mm5H+w==",
|
"integrity": "sha512-4BY+JBQssVn003P5lA289uDMie3LtGinHze5btkcW9timB6VaU+EeZS4eKTPC0pziizLhteVvXYxv3YTpeeRfA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@puppeteer/browsers": "^1.6.0",
|
"@puppeteer/browsers": "^1.6.0",
|
||||||
"@wdio/logger": "8.24.12",
|
"@wdio/logger": "8.24.12",
|
||||||
"@wdio/types": "8.26.3",
|
"@wdio/types": "8.27.0",
|
||||||
"decamelize": "^6.0.0",
|
"decamelize": "^6.0.0",
|
||||||
"deepmerge-ts": "^5.1.0",
|
"deepmerge-ts": "^5.1.0",
|
||||||
"edgedriver": "^5.3.5",
|
"edgedriver": "^5.3.5",
|
||||||
|
@ -8495,18 +8495,18 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/webdriver": {
|
"node_modules/webdriver": {
|
||||||
"version": "8.26.3",
|
"version": "8.27.0",
|
||||||
"resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.26.3.tgz",
|
"resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.27.0.tgz",
|
||||||
"integrity": "sha512-vHbMj0BFXPMtKVmJsVIkFVbdOT8eXkjFeJ7LmJL8cMMe1S5Lt44DqRjSBBoGsqYoYgIBmKpqAQcDrHrv9m7smQ==",
|
"integrity": "sha512-n1IA+rR3u84XxU9swiKUM06BkEC0GDimfZkBML57cny+utQOUbdM/mBpqCUnkWX/RBz/p2EfHdKNyOs3/REaog==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "^20.1.0",
|
"@types/node": "^20.1.0",
|
||||||
"@types/ws": "^8.5.3",
|
"@types/ws": "^8.5.3",
|
||||||
"@wdio/config": "8.26.3",
|
"@wdio/config": "8.27.0",
|
||||||
"@wdio/logger": "8.24.12",
|
"@wdio/logger": "8.24.12",
|
||||||
"@wdio/protocols": "8.24.12",
|
"@wdio/protocols": "8.24.12",
|
||||||
"@wdio/types": "8.26.3",
|
"@wdio/types": "8.27.0",
|
||||||
"@wdio/utils": "8.26.3",
|
"@wdio/utils": "8.27.0",
|
||||||
"deepmerge-ts": "^5.1.0",
|
"deepmerge-ts": "^5.1.0",
|
||||||
"got": "^12.6.1",
|
"got": "^12.6.1",
|
||||||
"ky": "^0.33.0",
|
"ky": "^0.33.0",
|
||||||
|
@ -8517,18 +8517,18 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/webdriverio": {
|
"node_modules/webdriverio": {
|
||||||
"version": "8.26.3",
|
"version": "8.27.0",
|
||||||
"resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.26.3.tgz",
|
"resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.27.0.tgz",
|
||||||
"integrity": "sha512-5Ka8MOQoK866EI3whiCvzD1IiKFBq9niWF3lh92uMt6ZjbUZZoe5esWIHhFsHFxT6dOOU8uXR/Gr6qsBJFZReA==",
|
"integrity": "sha512-Qh5VCiBjEmxnmXcL1QEFoDzFqTtaWKrXriuU5G0yHKCModGAt2G7IHTkAok3CpmkVJfZpEvY630aP1MvgDtFhw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "^20.1.0",
|
"@types/node": "^20.1.0",
|
||||||
"@wdio/config": "8.26.3",
|
"@wdio/config": "8.27.0",
|
||||||
"@wdio/logger": "8.24.12",
|
"@wdio/logger": "8.24.12",
|
||||||
"@wdio/protocols": "8.24.12",
|
"@wdio/protocols": "8.24.12",
|
||||||
"@wdio/repl": "8.24.12",
|
"@wdio/repl": "8.24.12",
|
||||||
"@wdio/types": "8.26.3",
|
"@wdio/types": "8.27.0",
|
||||||
"@wdio/utils": "8.26.3",
|
"@wdio/utils": "8.27.0",
|
||||||
"archiver": "^6.0.0",
|
"archiver": "^6.0.0",
|
||||||
"aria-query": "^5.0.0",
|
"aria-query": "^5.0.0",
|
||||||
"css-shorthand-properties": "^1.1.1",
|
"css-shorthand-properties": "^1.1.1",
|
||||||
|
@ -8545,7 +8545,7 @@
|
||||||
"resq": "^1.9.1",
|
"resq": "^1.9.1",
|
||||||
"rgb2hex": "0.2.5",
|
"rgb2hex": "0.2.5",
|
||||||
"serialize-error": "^11.0.1",
|
"serialize-error": "^11.0.1",
|
||||||
"webdriver": "8.26.3"
|
"webdriver": "8.27.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^16.13 || >=18"
|
"node": "^16.13 || >=18"
|
||||||
|
|
|
@ -6,10 +6,10 @@
|
||||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.15.0",
|
"@typescript-eslint/eslint-plugin": "^6.15.0",
|
||||||
"@typescript-eslint/parser": "^6.15.0",
|
"@typescript-eslint/parser": "^6.15.0",
|
||||||
"@wdio/cli": "^8.26.3",
|
"@wdio/cli": "^8.27.0",
|
||||||
"@wdio/local-runner": "^8.26.3",
|
"@wdio/local-runner": "^8.27.0",
|
||||||
"@wdio/mocha-framework": "^8.26.3",
|
"@wdio/mocha-framework": "^8.27.0",
|
||||||
"@wdio/spec-reporter": "^8.26.3",
|
"@wdio/spec-reporter": "^8.27.0",
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^8.56.0",
|
||||||
"eslint-config-google": "^0.14.0",
|
"eslint-config-google": "^0.14.0",
|
||||||
"eslint-plugin-sonarjs": "^0.23.0",
|
"eslint-plugin-sonarjs": "^0.23.0",
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
"lage": "^2.7.9"
|
"lage": "^2.7.9"
|
||||||
},
|
},
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/web"
|
"packages/monolith"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20"
|
"node": ">=20"
|
||||||
|
|
Before Width: | Height: | Size: 663 B After Width: | Height: | Size: 663 B |
Before Width: | Height: | Size: 254 B After Width: | Height: | Size: 254 B |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 351 B After Width: | Height: | Size: 351 B |
Before Width: | Height: | Size: 402 B After Width: | Height: | Size: 402 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 543 B After Width: | Height: | Size: 543 B |
Before Width: | Height: | Size: 368 B After Width: | Height: | Size: 368 B |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 878 B After Width: | Height: | Size: 878 B |
Before Width: | Height: | Size: 587 B After Width: | Height: | Size: 587 B |
Before Width: | Height: | Size: 175 B After Width: | Height: | Size: 175 B |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 887 B After Width: | Height: | Size: 887 B |
Before Width: | Height: | Size: 732 B After Width: | Height: | Size: 732 B |
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.0 KiB |
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.0 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.0 KiB |
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "@goauthentik/web",
|
"name": "@goauthentik/monolith",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
"watch": "run-s build-locales rollup:watch",
|
"watch": "run-s build-locales rollup:watch",
|
||||||
"lint": "eslint . --max-warnings 0 --fix",
|
"lint": "eslint . --max-warnings 0 --fix",
|
||||||
"lint:precommit": "eslint --max-warnings 0 --config ./.eslintrc.precommit.json $(git status --porcelain . | grep '^[M?][M?]' | cut -c8- | grep -E '\\.(ts|js|tsx|jsx)$') ",
|
"lint:precommit": "eslint --max-warnings 0 --config ./.eslintrc.precommit.json $(git status --porcelain . | grep '^[M?][M?]' | cut -c8- | grep -E '\\.(ts|js|tsx|jsx)$') ",
|
||||||
"lint:spelling": "codespell -D - -D ../.github/codespell-dictionary.txt -I ../.github/codespell-words.txt -S './src/locales/**' ./src -s",
|
"lint:spelling": "codespell -D - -D ../../../.github/codespell-dictionary.txt -I ../../../.github/codespell-words.txt -S './src/locales/**' ./src -s",
|
||||||
"lit-analyse": "lit-analyzer src",
|
"lit-analyse": "lit-analyzer src",
|
||||||
"precommit": "run-s tsc lit-analyse lint:precommit lint:spelling prettier",
|
"precommit": "run-s tsc lit-analyse lint:precommit lint:spelling prettier",
|
||||||
"prequick": "run-s tsc:execute lit-analyse lint:precommit lint:spelling",
|
"prequick": "run-s tsc:execute lit-analyse lint:precommit lint:spelling",
|
||||||
|
@ -42,15 +42,15 @@
|
||||||
"@codemirror/theme-one-dark": "^6.1.2",
|
"@codemirror/theme-one-dark": "^6.1.2",
|
||||||
"@formatjs/intl-listformat": "^7.5.3",
|
"@formatjs/intl-listformat": "^7.5.3",
|
||||||
"@fortawesome/fontawesome-free": "^6.5.1",
|
"@fortawesome/fontawesome-free": "^6.5.1",
|
||||||
"@goauthentik/api": "^2023.10.4-1702989148",
|
"@goauthentik/api": "^2023.10.5-1703167718",
|
||||||
"@lit-labs/context": "^0.4.0",
|
"@lit-labs/context": "^0.4.0",
|
||||||
"@lit-labs/task": "^3.1.0",
|
"@lit-labs/task": "^3.1.0",
|
||||||
"@lit/localize": "^0.11.4",
|
"@lit/localize": "^0.11.4",
|
||||||
"@open-wc/lit-helpers": "^0.6.0",
|
"@open-wc/lit-helpers": "^0.6.0",
|
||||||
"@patternfly/elements": "^2.4.0",
|
"@patternfly/elements": "^2.4.0",
|
||||||
"@patternfly/patternfly": "^4.224.2",
|
"@patternfly/patternfly": "^4.224.2",
|
||||||
"@sentry/browser": "^7.88.0",
|
"@sentry/browser": "^7.90.0",
|
||||||
"@sentry/tracing": "^7.88.0",
|
"@sentry/tracing": "^7.90.0",
|
||||||
"@webcomponents/webcomponentsjs": "^2.8.0",
|
"@webcomponents/webcomponentsjs": "^2.8.0",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
"chart.js": "^4.4.1",
|
"chart.js": "^4.4.1",
|
||||||
|
@ -86,13 +86,13 @@
|
||||||
"@rollup/plugin-replace": "^5.0.5",
|
"@rollup/plugin-replace": "^5.0.5",
|
||||||
"@rollup/plugin-terser": "^0.4.4",
|
"@rollup/plugin-terser": "^0.4.4",
|
||||||
"@rollup/plugin-typescript": "^11.1.5",
|
"@rollup/plugin-typescript": "^11.1.5",
|
||||||
"@storybook/addon-essentials": "^7.6.5",
|
"@storybook/addon-essentials": "^7.6.6",
|
||||||
"@storybook/addon-links": "^7.6.5",
|
"@storybook/addon-links": "^7.6.6",
|
||||||
"@storybook/api": "^7.6.5",
|
"@storybook/api": "^7.6.6",
|
||||||
"@storybook/blocks": "^7.6.4",
|
"@storybook/blocks": "^7.6.4",
|
||||||
"@storybook/manager-api": "^7.6.5",
|
"@storybook/manager-api": "^7.6.6",
|
||||||
"@storybook/web-components": "^7.6.5",
|
"@storybook/web-components": "^7.6.6",
|
||||||
"@storybook/web-components-vite": "^7.6.5",
|
"@storybook/web-components-vite": "^7.6.6",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||||
"@types/chart.js": "^2.9.41",
|
"@types/chart.js": "^2.9.41",
|
||||||
"@types/codemirror": "5.60.15",
|
"@types/codemirror": "5.60.15",
|
||||||
|
@ -120,7 +120,7 @@
|
||||||
"rollup-plugin-cssimport": "^1.0.3",
|
"rollup-plugin-cssimport": "^1.0.3",
|
||||||
"rollup-plugin-modify": "^3.0.0",
|
"rollup-plugin-modify": "^3.0.0",
|
||||||
"rollup-plugin-postcss-lit": "^2.1.0",
|
"rollup-plugin-postcss-lit": "^2.1.0",
|
||||||
"storybook": "^7.6.5",
|
"storybook": "^7.6.6",
|
||||||
"storybook-addon-mock": "^4.3.0",
|
"storybook-addon-mock": "^4.3.0",
|
||||||
"ts-lit-plugin": "^2.0.1",
|
"ts-lit-plugin": "^2.0.1",
|
||||||
"tslib": "^2.6.2",
|
"tslib": "^2.6.2",
|