Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
12 changes: 0 additions & 12 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ app:alert_troubleshooting:
- 'app/sep/apps/alert_troubleshooting/**'
- 'frontend/packages/apps/alert_troubleshooting/**'
- 'tests/app/sep/apps/alert_troubleshooting/**'
- 'templates/alert_troubleshooting/**'
- 'frontend/packages/e2e/tests/alert-troubleshooting*.spec.ts'
app:alerts:
- any:
Expand All @@ -57,7 +56,6 @@ app:alerts:
- 'app/sep/apps/alerts/**'
- 'frontend/packages/apps/alerts/**'
- 'tests/app/sep/apps/alerts/**'
- 'templates/alerts/**'
- 'frontend/packages/e2e/tests/alerts*.spec.ts'
app:alters:
- any:
Expand All @@ -66,15 +64,13 @@ app:alters:
- 'app/sep/apps/alters/**'
- 'frontend/packages/apps/alters/**'
- 'tests/app/sep/apps/alters/**'
- 'templates/alters/**'
- 'frontend/packages/e2e/tests/alters*.spec.ts'
app:archives:
- any:
- changed-files:
- any-glob-to-any-file:
- 'app/sep/apps/archives/**'
- 'tests/app/sep/apps/archives/**'
- 'templates/archiver/**'
- 'frontend/packages/e2e/tests/archives*.spec.ts'
app:atw:
- any:
Expand All @@ -91,47 +87,41 @@ app:backup_mongo:
- 'app/sep/apps/backup_mongo/**'
- 'frontend/packages/apps/backup_mongo/**'
- 'tests/app/sep/apps/backup_mongo/**'
- 'templates/backup_mongo/**'
- 'frontend/packages/e2e/tests/backup_mongo*.spec.ts'
app:backup_pg:
- any:
- changed-files:
- any-glob-to-any-file:
- 'app/sep/apps/backup_pg/**'
- 'tests/app/sep/apps/backup_pg/**'
- 'templates/backup_pg/**'
- 'frontend/packages/e2e/tests/backup_pg*.spec.ts'
app:checksums:
- any:
- changed-files:
- any-glob-to-any-file:
- 'app/sep/apps/checksums/**'
- 'tests/app/sep/apps/checksums/**'
- 'templates/checksums/**'
app:dipper:
- any:
- changed-files:
- any-glob-to-any-file:
- 'app/sep/apps/dipper/**'
- 'frontend/packages/apps/dipper/**'
- 'tests/app/sep/apps/dipper/**'
- 'templates/dipper/**'
app:inventory:
- any:
- changed-files:
- any-glob-to-any-file:
- 'app/sep/apps/inventory/**'
- 'frontend/packages/apps/inventory/**'
- 'tests/app/sep/apps/inventory/**'
- 'templates/inventory/**'
- 'frontend/packages/e2e/tests/inventory*.spec.ts'
app:mysql_backups:
- any:
- changed-files:
- any-glob-to-any-file:
- 'app/sep/apps/mysql_backups/**'
- 'tests/app/sep/apps/mysql_backups/**'
- 'templates/mysql_backups/**'
- 'frontend/packages/e2e/tests/mysql-backups*.spec.ts'
app:report:
- any:
Expand All @@ -149,7 +139,6 @@ app:snippets:
- 'app/sep/apps/snippets/**'
- 'frontend/packages/apps/snippets/**'
- 'tests/app/sep/apps/snippets/**'
- 'templates/snippets/**'
- 'frontend/packages/e2e/tests/snippets*.spec.ts'
app:tasks:
- any:
Expand All @@ -158,7 +147,6 @@ app:tasks:
- 'app/sep/apps/tasks/**'
- 'frontend/packages/apps/tasks/**'
- 'tests/app/sep/apps/tasks/**'
- 'templates/tasks/**'
- 'frontend/packages/e2e/tests/tasks*.spec.ts'
app:topology:
- any:
Expand Down
25 changes: 1 addition & 24 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,38 +7,15 @@ repos:
hooks:
- id: ruff
- id: ruff-format
- repo: https://github.com/djlint/djLint
rev: 9112cb64851c95a7802358af285d21ad8b7f6437 # v1.36.4
hooks:
- id: djlint
types_or: [jinja]
- id: djlint-reformat
types_or: [jinja]
- repo: https://github.com/google/addlicense
rev: b289835c0741ba2fb92dd5f61af070e79c69a55e # v1.2.0
hooks:
- id: addlicense
args: ["-f", ".license_header", "-v"]
types_or: [python, javascript, ts, tsx, css, jinja]
exclude: ^(static/(js|css)/vendor/|frontend/(node_modules|.*/dist|packages/[^/]+/src/generated)/)
exclude: ^frontend/(node_modules|.*/dist|packages/[^/]+/src/generated)/
- repo: local
hooks:
- id: js-beautify
name: JS Beautify
description: "Run js-beautify (from jsbeautifier) on .js files"
entry: js-beautify --end-with-newline --replace
language: python
additional_dependencies: ["jsbeautifier==1.15.1"]
files: ^(?!.*\.min\.js$)(?!static/(js|css)/vendor/).*\.js$
exclude: ^frontend/
- id: css-beautify
name: CSS Beautify
description: "Run css-beautify (from cssbeautifier) on .css files"
entry: css-beautify --end-with-newline --replace
language: python
additional_dependencies: ["cssbeautifier==1.15.1"]
files: ^(?!.*\.min\.css$)(?!static/(js|css)/vendor/).*\.css$
exclude: ^frontend/
- id: update-installer-checksum
name: Update sep_installer.sh checksum
description: "Update sep_installer.sha256 when sep_installer.sh changes"
Expand Down
9 changes: 2 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ image-sidecar-embedded: pack

format: venv
@"${VENV_BIN}"/ruff format .
@"${VENV_BIN}"/djlint . --reformat

ruff: venv
@"${VENV_BIN}"/ruff check .
Expand All @@ -99,11 +98,7 @@ ruff: venv
typecheck: venv
@"${VENV_BIN}"/ty check app

djlint: venv
@"${VENV_BIN}"/djlint .
@"${VENV_BIN}"/djlint . --check

lint: ruff djlint
lint: ruff

audit: bandit pip-audit

Expand Down Expand Up @@ -357,4 +352,4 @@ lint-pipelines:
done; \
if [ "$${failures}" -ne 0 ]; then exit 1; fi

.PHONY: venv build pack builder image image-sidecar image-sidecar-embedded format ruff typecheck djlint lint audit run-pre-commit dev-backend dev-frontend backfill-legacy-forms pip-audit bandit makemigrations makemigrations-plugin migrate checkmigrations test regen-specs regen-pbm-payloads regen-pbm-payloads-check release-prep release-rc release-stable trigger-jenkins lint-pipelines changelog-add changelog-check changelog-list startapp startapp-check
.PHONY: venv build pack builder image image-sidecar image-sidecar-embedded format ruff typecheck lint audit run-pre-commit dev-backend dev-frontend backfill-legacy-forms pip-audit bandit makemigrations makemigrations-plugin migrate checkmigrations test regen-specs regen-pbm-payloads regen-pbm-payloads-check release-prep release-rc release-stable trigger-jenkins lint-pipelines changelog-add changelog-check changelog-list startapp startapp-check
3 changes: 1 addition & 2 deletions app/api/routes/oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,7 @@ async def spa_session_exchange(

Carries no auth dependency by design: the caller is not yet SEP-authenticated,
and the ambient session cookie is the credential being presented. No CSRF
primitive applies either -- ``IsCsrfValidated`` guards form data on the
server-rendered login route, and requiring a Bearer token cannot gate an
primitive applies either: requiring a Bearer token cannot gate an
endpoint whose purpose is to issue one. That is safe because a cross-origin
attacker cannot read this response (the CORS posture is an explicit
per-environment origin allowlist, with the middleware absent when none is
Expand Down
19 changes: 0 additions & 19 deletions app/core/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
from typing import Any

from fastapi import HTTPException, status
from starlette.exceptions import HTTPException as StarletteHTTPException


class HTTPNotFoundException(HTTPException):
Expand Down Expand Up @@ -184,21 +183,3 @@ def __init__(
super().__init__(
status_code=status.HTTP_410_GONE, detail=detail, headers=headers
)


class HTTPRedirectException(StarletteHTTPException):
"""Define exception raised for redirects.

:param location: The URL to which the client should be redirected.
:type location: str
:param status_code: The HTTP status code for the redirect response. Defaults to
307 (Temporary Redirect).
:type status_code: int
"""

def __init__(
self, location: str, status_code: int = status.HTTP_307_TEMPORARY_REDIRECT
) -> None:
self.location = location
super().__init__(status_code=status_code)
self.headers = {"Location": location}
7 changes: 3 additions & 4 deletions app/core/settings_override/api/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -826,10 +826,9 @@ def build_settings_router(
dependency, and its admin-auth dependency. ``admin_dep`` is applied at
the router level so every endpoint inherits the admin gate. State-changing
endpoints (PATCH / DELETE) additionally take ``mutation_deps``, which the
SEP wiring uses to require Bearer authentication on mutations: a
cookie-authenticated admin can otherwise be CSRF'd into mutating settings
because :func:`app.sep.deps.validate_csrf` only inspects form bodies, and
JSON mutations carry no form body.
SEP wiring uses to require Bearer authentication on mutations, so a
cross-site JSON request carrying only ambient cookies cannot mutate
settings.

:param classes: One ``(SettingClassEnum, settings_cls, proxy)`` triple per
core settings class to expose on this router.
Expand Down
6 changes: 2 additions & 4 deletions app/core/settings_override/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,8 @@ class SettingClassEnum(StrEnum):
"""Enumerate settings classes that may have HOT override rows.

The wired classes are ``SEPSettings``, ``TasksSettings``,
``SnippetsSettings``, ``MessagesSettings``, the global ``Settings``,
``AlertSettings``, ``AlertsSettings``, ``AnonymizerSettings`` and
``InventorySettings``.
``SnippetsSettings``, the global ``Settings``, ``AlertSettings``,
``AlertsSettings``, ``AnonymizerSettings`` and ``InventorySettings``.

To wire a new settings class:

Expand All @@ -53,7 +52,6 @@ class SettingClassEnum(StrEnum):
SEP_SETTINGS = "SEPSettings"
TASKS_SETTINGS = "TasksSettings"
SNIPPETS_SETTINGS = "SnippetsSettings"
MESSAGES_SETTINGS = "MessagesSettings"
SETTINGS = "Settings"
ALERT_SETTINGS = "AlertSettings"
ANONYMIZER_SETTINGS = "AnonymizerSettings"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Copyright (C) 2026 Percona LLC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

"""drop messages setting overrides

Revision ID: 34e6108ea194
Revises: c4d5e6f7a8b9
Create Date: 2026-08-08 03:06:23.354353

"""

import sqlalchemy as sa
from alembic import op

from app.core.db.utils import (
acquire_pg_advisory_xact_lock,
check_constraint_lists_members,
)
from app.core.settings_override.constants import SETTINGOVERRIDE_MIGRATION_LOCK_KEY

# revision identifiers, used by Alembic.
revision = "34e6108ea194"
down_revision = "c4d5e6f7a8b9"
branch_labels = None
depends_on = None

_OLD_MEMBERS = (
"SEP_SETTINGS",
"TASKS_SETTINGS",
"SNIPPETS_SETTINGS",
"MESSAGES_SETTINGS",
"SETTINGS",
"ALERT_SETTINGS",
"ANONYMIZER_SETTINGS",
"ALERTS_SETTINGS",
"INVENTORY_SETTINGS",
)
_NEW_MEMBERS = tuple(m for m in _OLD_MEMBERS if m != "MESSAGES_SETTINGS")


def upgrade() -> None:
"""Remove ``MESSAGES_SETTINGS`` from the setting_class constraint."""
bind = op.get_bind()
acquire_pg_advisory_xact_lock(bind, SETTINGOVERRIDE_MIGRATION_LOCK_KEY)
if not check_constraint_lists_members(
bind, "settingoverride", "setting_class", ("MESSAGES_SETTINGS",)
):
return
# The narrowed CHECK re-validates existing data, so the rows carrying the
# removed member have to go first or the ALTER aborts.
op.execute("DELETE FROM settingoverride WHERE setting_class = 'MESSAGES_SETTINGS'")
with op.batch_alter_table("settingoverride", schema=None) as batch_op:
batch_op.alter_column(
"setting_class",
existing_type=sa.Enum(
*_OLD_MEMBERS,
name="settingclassenum",
native_enum=False,
create_constraint=True,
),
type_=sa.Enum(
*_NEW_MEMBERS,
name="settingclassenum",
native_enum=False,
create_constraint=True,
),
existing_nullable=False,
)


def downgrade() -> None:
"""Restore ``MESSAGES_SETTINGS`` in the setting_class constraint.

The deleted rows are not recreated: they carried a flash-message level for a
middleware that no longer exists.
"""
bind = op.get_bind()
acquire_pg_advisory_xact_lock(bind, SETTINGOVERRIDE_MIGRATION_LOCK_KEY)
if check_constraint_lists_members(
bind, "settingoverride", "setting_class", ("MESSAGES_SETTINGS",)
):
return
with op.batch_alter_table("settingoverride", schema=None) as batch_op:
batch_op.alter_column(
"setting_class",
existing_type=sa.Enum(
*_NEW_MEMBERS,
name="settingclassenum",
native_enum=False,
create_constraint=True,
),
type_=sa.Enum(
*_OLD_MEMBERS,
name="settingclassenum",
native_enum=False,
create_constraint=True,
),
existing_nullable=False,
)
8 changes: 3 additions & 5 deletions app/sep/api/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,6 @@
def build_apps_router(registry: AppRegistry) -> APIRouter:
"""Build the ``/apps`` sub-router by iterating the app registry.

Mirror the Jinja UI mount loop in ``app/sep/main.py`` so future
runtime enable/disable guards can be applied symmetrically at both
mount points.

:param registry: The app registry, in activation order.
:type registry: AppRegistry
:return: An ``APIRouter`` mounted at ``/apps`` with each app whose
Expand Down Expand Up @@ -92,7 +88,9 @@ def build_apps_router(registry: AppRegistry) -> APIRouter:

apps_router = build_apps_router(get_app_registry())

api_router = APIRouter(prefix="/api", dependencies=[IsApiAuthenticated])
api_router = APIRouter(
prefix="/api", dependencies=[IsApiAuthenticated, RequireBearerForUnsafeMethods]
)
api_router.include_router(apps_router)
api_router.include_router(app_info_router, prefix="/sep/app-info", tags=["sep"])
api_router.include_router(dashboard_router, prefix="/sep/dashboard", tags=["sep"])
Expand Down
2 changes: 0 additions & 2 deletions app/sep/api/routes/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
)
from app.sep.config import sep_settings, SEPSettings
from app.sep.deps import IsApiAdmin, RequireBearerForUnsafeMethods, SessionDep, TaskAPI
from app.sep.middleware.messages.config import messages_settings, MessagesSettings
from app.sep.snippets.config import snippets_settings, SnippetsSettings

# TasksSettings is owned by the Tasks sub-app, so SEP proxies it server-side
Expand All @@ -54,7 +53,6 @@
SEP_ADMIN_SETTINGS_CLASSES: list[ClassEntry] = [
(SettingClassEnum.SEP_SETTINGS, SEPSettings, sep_settings),
(SettingClassEnum.SNIPPETS_SETTINGS, SnippetsSettings, snippets_settings),
(SettingClassEnum.MESSAGES_SETTINGS, MessagesSettings, messages_settings),
(SettingClassEnum.ALERT_SETTINGS, AlertSettings, alert_settings),
# The global ``Settings`` class is refreshed only by the SEP web process, so
# its override-eligible fields (e.g. ``PMM``, ``LOGGING``) are exposed here.
Expand Down
3 changes: 1 addition & 2 deletions app/sep/apps/alert_troubleshooting/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,5 @@
"""Alert Troubleshooting plugin — index page with alert selection by service type."""

from app.sep.apps.alert_troubleshooting.app import app
from app.sep.apps.alert_troubleshooting.routes import router

__all__ = ["app", "router"]
__all__ = ["app"]
Loading
Loading