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

View file

@ -1,19 +1,22 @@
"""authentik admin Middleware to impersonate users""" """authentik admin Middleware to impersonate users"""
from logging import Logger from contextvars import ContextVar
from threading import local
from typing import Callable from typing import Callable
from uuid import uuid4 from uuid import uuid4
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from sentry_sdk.api import set_tag from sentry_sdk.api import set_tag
from structlog.contextvars import STRUCTLOG_KEY_PREFIX
SESSION_KEY_IMPERSONATE_USER = "authentik/impersonate/user" SESSION_KEY_IMPERSONATE_USER = "authentik/impersonate/user"
SESSION_KEY_IMPERSONATE_ORIGINAL_USER = "authentik/impersonate/original_user" SESSION_KEY_IMPERSONATE_ORIGINAL_USER = "authentik/impersonate/original_user"
LOCAL = local()
RESPONSE_HEADER_ID = "X-authentik-id" RESPONSE_HEADER_ID = "X-authentik-id"
KEY_AUTH_VIA = "auth_via" KEY_AUTH_VIA = "auth_via"
KEY_USER = "user" 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: class ImpersonateMiddleware:
"""Middleware to impersonate users""" """Middleware to impersonate users"""
@ -47,26 +50,20 @@ class RequestIDMiddleware:
if not hasattr(request, "request_id"): if not hasattr(request, "request_id"):
request_id = uuid4().hex request_id = uuid4().hex
setattr(request, "request_id", request_id) setattr(request, "request_id", request_id)
LOCAL.authentik = { CTX_REQUEST_ID.set(request_id)
"request_id": request_id, CTX_HOST.set(request.get_host())
"host": request.get_host(),
}
set_tag("authentik.request_id", request_id) 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 = self.get_response(request)
response[RESPONSE_HEADER_ID] = request.request_id response[RESPONSE_HEADER_ID] = request.request_id
setattr(response, "ak_context", {}) setattr(response, "ak_context", {})
response.ak_context.update(LOCAL.authentik) response.ak_context["request_id"] = CTX_REQUEST_ID.get()
response.ak_context.setdefault(KEY_USER, request.user.username) response.ak_context["host"] = CTX_HOST.get()
for key in list(LOCAL.authentik.keys()): response.ak_context[KEY_AUTH_VIA] = CTX_AUTH_VIA.get()
del LOCAL.authentik[key] response.ak_context[KEY_USER] = request.user.username
return response 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 django_otp.plugins.otp_static.models import StaticToken
from guardian.models import UserObjectPermission from guardian.models import UserObjectPermission
from authentik.core.middleware import LOCAL
from authentik.core.models import AuthenticatedSession, User from authentik.core.models import AuthenticatedSession, User
from authentik.events.models import Event, EventAction, Notification from authentik.events.models import Event, EventAction, Notification
from authentik.events.signals import EventNewThread from authentik.events.signals import EventNewThread
@ -45,36 +44,46 @@ class AuditMiddleware:
def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]): def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]):
self.get_response = get_response 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: def __call__(self, request: HttpRequest) -> HttpResponse:
# Connect signal for automatic logging self.connect(request)
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,
)
response = self.get_response(request) response = self.get_response(request)
post_save.disconnect(dispatch_uid=LOCAL.authentik["request_id"]) self.disconnect(request)
pre_delete.disconnect(dispatch_uid=LOCAL.authentik["request_id"])
return response return response
# pylint: disable=unused-argument # pylint: disable=unused-argument
def process_exception(self, request: HttpRequest, exception: Exception): def process_exception(self, request: HttpRequest, exception: Exception):
"""Disconnect handlers in case of exception""" """Disconnect handlers in case of exception"""
post_save.disconnect(dispatch_uid=LOCAL.authentik["request_id"]) self.disconnect(request)
pre_delete.disconnect(dispatch_uid=LOCAL.authentik["request_id"])
if settings.DEBUG: if settings.DEBUG:
return return

View file

@ -17,7 +17,7 @@ from django.conf import settings
from django.db import ProgrammingError from django.db import ProgrammingError
from structlog.stdlib import get_logger 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.sentry import before_send
from authentik.lib.utils.errors import exception_to_string 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): def task_prerun_hook(task_id: str, task, *args, **kwargs):
"""Log task_id on worker""" """Log task_id on worker"""
request_id = "task-" + task_id.replace("-", "") request_id = "task-" + task_id.replace("-", "")
LOCAL.authentik_task = { CTX_REQUEST_ID.set(request_id)
"request_id": request_id, CTX_AUTH_VIA.set(Ellipsis)
} CTX_HOST.set(Ellipsis)
LOGGER.info("Task started", task_id=task_id, task_name=task.__name__) 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): def task_postrun_hook(task_id, task, *args, retval=None, state=None, **kwargs):
"""Log task_id on worker""" """Log task_id on worker"""
LOGGER.info("Task finished", task_id=task_id, task_name=task.__name__, state=state) 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 # pylint: disable=unused-argument

View file

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

14
poetry.lock generated
View file

@ -1715,16 +1715,16 @@ pbr = ">=2.0.0,<2.1.0 || >2.1.0"
[[package]] [[package]]
name = "structlog" name = "structlog"
version = "21.5.0" version = "22.1.0"
description = "Structured Logging for Python" description = "Structured Logging for Python"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.7"
[package.extras] [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"] 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", "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", "pytest (>=6.0)", "simplejson"] tests = ["coverage", "freezegun (>=0.2.8)", "pretend", "pytest-asyncio (>=0.17)", "pytest (>=6.0)", "simplejson"]
[[package]] [[package]]
name = "swagger-spec-validator" name = "swagger-spec-validator"
@ -3433,8 +3433,8 @@ stevedore = [
{file = "stevedore-3.5.0.tar.gz", hash = "sha256:f40253887d8712eaa2bb0ea3830374416736dc8ec0e22f5a65092c1174c44335"}, {file = "stevedore-3.5.0.tar.gz", hash = "sha256:f40253887d8712eaa2bb0ea3830374416736dc8ec0e22f5a65092c1174c44335"},
] ]
structlog = [ structlog = [
{file = "structlog-21.5.0-py3-none-any.whl", hash = "sha256:fd7922e195262b337da85c2a91c84be94ccab1f8fd1957bd6986f6904e3761c8"}, {file = "structlog-22.1.0-py3-none-any.whl", hash = "sha256:760d37b8839bd4fe1747bed7b80f7f4de160078405f4b6a1db9270ccbfce6c30"},
{file = "structlog-21.5.0.tar.gz", hash = "sha256:68c4c29c003714fe86834f347cb107452847ba52414390a7ee583472bde00fc9"}, {file = "structlog-22.1.0.tar.gz", hash = "sha256:94b29b1d62b2659db154f67a9379ec1770183933d6115d21f21aa25cfc9a7393"},
] ]
swagger-spec-validator = [ swagger-spec-validator = [
{file = "swagger-spec-validator-2.7.4.tar.gz", hash = "sha256:2aee5e1fc0503be9f8299378b10c92169572781573c6de3315e831fd0559ba73"}, {file = "swagger-spec-validator-2.7.4.tar.gz", hash = "sha256:2aee5e1fc0503be9f8299378b10c92169572781573c6de3315e831fd0559ba73"},