core: bump structlog from 21.5.0 to 22.1.0 (#3294)

* core: bump structlog from 21.5.0 to 22.1.0

Bumps [structlog](https://github.com/hynek/structlog) from 21.5.0 to 22.1.0.
- [Release notes](https://github.com/hynek/structlog/releases)
- [Changelog](https://github.com/hynek/structlog/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hynek/structlog/compare/21.5.0...22.1.0)

---
updated-dependencies:
- dependency-name: structlog
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* migrate threaedlocal to contextvars

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
dependabot[bot] 2022-07-23 22:40:56 +02:00 committed by GitHub
parent e0adcd3277
commit bd8794f646
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 67 additions and 67 deletions

View file

@ -7,7 +7,7 @@ from rest_framework.exceptions import AuthenticationFailed
from rest_framework.request import Request
from structlog.stdlib import get_logger
from authentik.core.middleware import KEY_AUTH_VIA, LOCAL
from authentik.core.middleware import CTX_AUTH_VIA
from authentik.core.models import Token, TokenIntents, User
from authentik.outposts.models import Outpost
from authentik.providers.oauth2.constants import SCOPE_AUTHENTIK_API
@ -36,14 +36,12 @@ def bearer_auth(raw_header: bytes) -> Optional[User]:
auth_credentials = validate_auth(raw_header)
if not auth_credentials:
return None
if not hasattr(LOCAL, "authentik"):
LOCAL.authentik = {}
# first, check traditional tokens
key_token = Token.filter_not_expired(
key=auth_credentials, intent=TokenIntents.INTENT_API
).first()
if key_token:
LOCAL.authentik[KEY_AUTH_VIA] = "api_token"
CTX_AUTH_VIA.set("api_token")
return key_token.user
# then try to auth via JWT
jwt_token = RefreshToken.filter_not_expired(
@ -54,12 +52,12 @@ def bearer_auth(raw_header: bytes) -> Optional[User]:
# we want to check the parsed version too
if SCOPE_AUTHENTIK_API not in jwt_token.scope:
raise AuthenticationFailed("Token invalid/expired")
LOCAL.authentik[KEY_AUTH_VIA] = "jwt"
CTX_AUTH_VIA.set("jwt")
return jwt_token.user
# then try to auth via secret key (for embedded outpost/etc)
user = token_secret_key(auth_credentials)
if user:
LOCAL.authentik[KEY_AUTH_VIA] = "secret_key"
CTX_AUTH_VIA.set("secret_key")
return user
raise AuthenticationFailed("Token invalid/expired")

View file

@ -1,19 +1,22 @@
"""authentik admin Middleware to impersonate users"""
from logging import Logger
from threading import local
from contextvars import ContextVar
from typing import Callable
from uuid import uuid4
from django.http import HttpRequest, HttpResponse
from sentry_sdk.api import set_tag
from structlog.contextvars import STRUCTLOG_KEY_PREFIX
SESSION_KEY_IMPERSONATE_USER = "authentik/impersonate/user"
SESSION_KEY_IMPERSONATE_ORIGINAL_USER = "authentik/impersonate/original_user"
LOCAL = local()
RESPONSE_HEADER_ID = "X-authentik-id"
KEY_AUTH_VIA = "auth_via"
KEY_USER = "user"
CTX_REQUEST_ID = ContextVar(STRUCTLOG_KEY_PREFIX + "request_id", default=None)
CTX_HOST = ContextVar(STRUCTLOG_KEY_PREFIX + "host", default=None)
CTX_AUTH_VIA = ContextVar(STRUCTLOG_KEY_PREFIX + KEY_AUTH_VIA, default=None)
class ImpersonateMiddleware:
"""Middleware to impersonate users"""
@ -47,26 +50,20 @@ class RequestIDMiddleware:
if not hasattr(request, "request_id"):
request_id = uuid4().hex
setattr(request, "request_id", request_id)
LOCAL.authentik = {
"request_id": request_id,
"host": request.get_host(),
}
CTX_REQUEST_ID.set(request_id)
CTX_HOST.set(request.get_host())
set_tag("authentik.request_id", request_id)
if hasattr(request, "user") and getattr(request.user, "is_authenticated", False):
CTX_AUTH_VIA.set("session")
else:
CTX_AUTH_VIA.set("unauthenticated")
response = self.get_response(request)
response[RESPONSE_HEADER_ID] = request.request_id
setattr(response, "ak_context", {})
response.ak_context.update(LOCAL.authentik)
response.ak_context.setdefault(KEY_USER, request.user.username)
for key in list(LOCAL.authentik.keys()):
del LOCAL.authentik[key]
response.ak_context["request_id"] = CTX_REQUEST_ID.get()
response.ak_context["host"] = CTX_HOST.get()
response.ak_context[KEY_AUTH_VIA] = CTX_AUTH_VIA.get()
response.ak_context[KEY_USER] = request.user.username
return response
# pylint: disable=unused-argument
def structlog_add_request_id(logger: Logger, method_name: str, event_dict: dict):
"""If threadlocal has authentik defined, add request_id to log"""
if hasattr(LOCAL, "authentik"):
event_dict.update(LOCAL.authentik)
if hasattr(LOCAL, "authentik_task"):
event_dict.update(LOCAL.authentik_task)
return event_dict

View file

@ -11,7 +11,6 @@ from django.http import HttpRequest, HttpResponse
from django_otp.plugins.otp_static.models import StaticToken
from guardian.models import UserObjectPermission
from authentik.core.middleware import LOCAL
from authentik.core.models import AuthenticatedSession, User
from authentik.events.models import Event, EventAction, Notification
from authentik.events.signals import EventNewThread
@ -45,36 +44,46 @@ class AuditMiddleware:
def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]):
self.get_response = get_response
def connect(self, request: HttpRequest):
"""Connect signal for automatic logging"""
if not hasattr(request, "user"):
return
if not getattr(request.user, "is_authenticated", False):
return
if not hasattr(request, "request_id"):
return
post_save_handler = partial(self.post_save_handler, user=request.user, request=request)
pre_delete_handler = partial(self.pre_delete_handler, user=request.user, request=request)
post_save.connect(
post_save_handler,
dispatch_uid=request.request_id,
weak=False,
)
pre_delete.connect(
pre_delete_handler,
dispatch_uid=request.request_id,
weak=False,
)
def disconnect(self, request: HttpRequest):
"""Disconnect signals"""
if not hasattr(request, "request_id"):
return
post_save.disconnect(dispatch_uid=request.request_id)
pre_delete.disconnect(dispatch_uid=request.request_id)
def __call__(self, request: HttpRequest) -> HttpResponse:
# Connect signal for automatic logging
if hasattr(request, "user") and getattr(request.user, "is_authenticated", False):
post_save_handler = partial(self.post_save_handler, user=request.user, request=request)
pre_delete_handler = partial(
self.pre_delete_handler, user=request.user, request=request
)
post_save.connect(
post_save_handler,
dispatch_uid=LOCAL.authentik["request_id"],
weak=False,
)
pre_delete.connect(
pre_delete_handler,
dispatch_uid=LOCAL.authentik["request_id"],
weak=False,
)
self.connect(request)
response = self.get_response(request)
post_save.disconnect(dispatch_uid=LOCAL.authentik["request_id"])
pre_delete.disconnect(dispatch_uid=LOCAL.authentik["request_id"])
self.disconnect(request)
return response
# pylint: disable=unused-argument
def process_exception(self, request: HttpRequest, exception: Exception):
"""Disconnect handlers in case of exception"""
post_save.disconnect(dispatch_uid=LOCAL.authentik["request_id"])
pre_delete.disconnect(dispatch_uid=LOCAL.authentik["request_id"])
self.disconnect(request)
if settings.DEBUG:
return

View file

@ -17,7 +17,7 @@ from django.conf import settings
from django.db import ProgrammingError
from structlog.stdlib import get_logger
from authentik.core.middleware import LOCAL
from authentik.core.middleware import CTX_AUTH_VIA, CTX_HOST, CTX_REQUEST_ID
from authentik.lib.sentry import before_send
from authentik.lib.utils.errors import exception_to_string
@ -48,9 +48,9 @@ def after_task_publish_hook(sender=None, headers=None, body=None, **kwargs):
def task_prerun_hook(task_id: str, task, *args, **kwargs):
"""Log task_id on worker"""
request_id = "task-" + task_id.replace("-", "")
LOCAL.authentik_task = {
"request_id": request_id,
}
CTX_REQUEST_ID.set(request_id)
CTX_AUTH_VIA.set(Ellipsis)
CTX_HOST.set(Ellipsis)
LOGGER.info("Task started", task_id=task_id, task_name=task.__name__)
@ -59,10 +59,6 @@ def task_prerun_hook(task_id: str, task, *args, **kwargs):
def task_postrun_hook(task_id, task, *args, retval=None, state=None, **kwargs):
"""Log task_id on worker"""
LOGGER.info("Task finished", task_id=task_id, task_name=task.__name__, state=state)
if not hasattr(LOCAL, "authentik_task"):
return
for key in list(LOCAL.authentik_task.keys()):
del LOCAL.authentik_task[key]
# pylint: disable=unused-argument

View file

@ -14,7 +14,6 @@ from celery.schedules import crontab
from sentry_sdk import set_tag
from authentik import ENV_GIT_HASH_KEY, __version__
from authentik.core.middleware import structlog_add_request_id
from authentik.lib.config import CONFIG
from authentik.lib.logging import add_process_id
from authentik.lib.sentry import sentry_init
@ -380,12 +379,12 @@ structlog.configure_once(
processors=[
structlog.stdlib.add_log_level,
structlog.stdlib.add_logger_name,
structlog.threadlocal.merge_threadlocal_context,
structlog.contextvars.merge_contextvars,
add_process_id,
structlog_add_request_id,
structlog.stdlib.PositionalArgumentsFormatter(),
structlog.processors.TimeStamper(fmt="iso", utc=False),
structlog.processors.StackInfoRenderer(),
structlog.processors.dict_tracebacks,
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
],
logger_factory=structlog.stdlib.LoggerFactory(),
@ -400,6 +399,7 @@ LOG_PRE_CHAIN = [
# is not from structlog.
structlog.stdlib.add_log_level,
structlog.stdlib.add_logger_name,
structlog.processors.dict_tracebacks,
structlog.processors.TimeStamper(),
structlog.processors.StackInfoRenderer(),
]

14
poetry.lock generated
View file

@ -1715,16 +1715,16 @@ pbr = ">=2.0.0,<2.1.0 || >2.1.0"
[[package]]
name = "structlog"
version = "21.5.0"
version = "22.1.0"
description = "Structured Logging for Python"
category = "main"
optional = false
python-versions = ">=3.6"
python-versions = ">=3.7"
[package.extras]
dev = ["pre-commit", "rich", "cogapp", "tomli", "coverage", "freezegun (>=0.2.8)", "pretend", "pytest-asyncio", "pytest (>=6.0)", "simplejson", "furo", "sphinx", "sphinx-notfound-page", "sphinxcontrib-mermaid", "twisted"]
docs = ["furo", "sphinx", "sphinx-notfound-page", "sphinxcontrib-mermaid", "twisted"]
tests = ["coverage", "freezegun (>=0.2.8)", "pretend", "pytest-asyncio", "pytest (>=6.0)", "simplejson"]
dev = ["pre-commit", "rich", "cogapp", "tomli", "coverage", "freezegun (>=0.2.8)", "pretend", "pytest-asyncio (>=0.17)", "pytest (>=6.0)", "simplejson", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-mermaid", "twisted"]
docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-mermaid", "twisted"]
tests = ["coverage", "freezegun (>=0.2.8)", "pretend", "pytest-asyncio (>=0.17)", "pytest (>=6.0)", "simplejson"]
[[package]]
name = "swagger-spec-validator"
@ -3433,8 +3433,8 @@ stevedore = [
{file = "stevedore-3.5.0.tar.gz", hash = "sha256:f40253887d8712eaa2bb0ea3830374416736dc8ec0e22f5a65092c1174c44335"},
]
structlog = [
{file = "structlog-21.5.0-py3-none-any.whl", hash = "sha256:fd7922e195262b337da85c2a91c84be94ccab1f8fd1957bd6986f6904e3761c8"},
{file = "structlog-21.5.0.tar.gz", hash = "sha256:68c4c29c003714fe86834f347cb107452847ba52414390a7ee583472bde00fc9"},
{file = "structlog-22.1.0-py3-none-any.whl", hash = "sha256:760d37b8839bd4fe1747bed7b80f7f4de160078405f4b6a1db9270ccbfce6c30"},
{file = "structlog-22.1.0.tar.gz", hash = "sha256:94b29b1d62b2659db154f67a9379ec1770183933d6115d21f21aa25cfc9a7393"},
]
swagger-spec-validator = [
{file = "swagger-spec-validator-2.7.4.tar.gz", hash = "sha256:2aee5e1fc0503be9f8299378b10c92169572781573c6de3315e831fd0559ba73"},