From b21780605e557ba6350563d4275e5531baffa152 Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Tue, 14 Jul 2026 00:10:51 +0800 Subject: [PATCH] fix(marker): reject reserved global-prefix names only on create MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The global-prefix reservation belongs on the create path, where it keeps user-chosen names disjoint from inherited global-{name} markers. It was implemented as a field_validator on MarkerCreate / MarkerDefinitionCreate, which also back the PUT update bodies — so updating an inherited marker while echoing its name in the body tripped the validator with a generic 422 before the controller could return the actionable 409 ("inherited, use the marker-definitions API"). Drop the schema validators and enforce the prefix in the two create routes (ControllerError -> 409). PUT no longer validates the body name (ignored anyway — the target is the {name} path param), so editing an inherited marker now reaches the controller's inheritance guard and returns the clear 409. The name-format regex stays on the schema (still 422). --- gns3server/api/routes/controller/links.py | 2 ++ gns3server/api/routes/controller/projects.py | 2 ++ gns3server/schemas/controller/links.py | 16 +--------------- 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/gns3server/api/routes/controller/links.py b/gns3server/api/routes/controller/links.py index 5a51905e0..9e33b7ba3 100644 --- a/gns3server/api/routes/controller/links.py +++ b/gns3server/api/routes/controller/links.py @@ -458,6 +458,8 @@ async def create_marker( # uuid suffix avoids the collision that `marker-{link.id[:8]}` alone would # cause on the second anonymous marker on the same link (start_marker # rejects duplicate names). + if marker_data.name and marker_data.name.lower().startswith("global"): + raise ControllerError('Names starting with "global" are reserved for inherited markers') name = marker_data.name or f"marker-{link.id[:8]}-{uuid4().hex[:4]}" await link.start_marker( name=name, diff --git a/gns3server/api/routes/controller/projects.py b/gns3server/api/routes/controller/projects.py index 103c7d310..5e6f1c6de 100644 --- a/gns3server/api/routes/controller/projects.py +++ b/gns3server/api/routes/controller/projects.py @@ -260,6 +260,8 @@ async def create_marker_definition( Required privilege: Project.Modify """ + if def_data.name and def_data.name.lower().startswith("global"): + raise ControllerError('Names starting with "global" are reserved for inherited markers') name = def_data.name or f"def-{project.id[:8]}" await project.create_marker_definition( name=name, diff --git a/gns3server/schemas/controller/links.py b/gns3server/schemas/controller/links.py index 82bfc48b9..23a0cd18a 100644 --- a/gns3server/schemas/controller/links.py +++ b/gns3server/schemas/controller/links.py @@ -14,7 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from pydantic import BaseModel, Field, field_validator +from pydantic import BaseModel, Field from typing import List, Optional, Tuple from enum import Enum from uuid import UUID, uuid4 @@ -176,13 +176,6 @@ class MarkerCreate(BaseModel): description="Whether the marker is active. Defaults to true on creation.", ) - @field_validator("name") - @classmethod - def _check_name_not_reserved(cls, v): - if v is not None and v.lower().startswith("global"): - raise ValueError('Names starting with "global" are reserved') - return v - class MarkerDefinitionCreate(BaseModel): """ @@ -215,11 +208,4 @@ class MarkerDefinitionCreate(BaseModel): ), ) - @field_validator("name") - @classmethod - def _check_name_not_reserved(cls, v): - if v is not None and v.lower().startswith("global"): - raise ValueError('Names starting with "global" are reserved') - return v -