Merge pull request #2860 from yueguobin/feat/project-gns3file-endpoint

api: serve the project .gns3 topology file
This commit is contained in:
Jeremy Grossmann 2026-08-24 20:07:12 +02:00 committed by GitHub
commit 006102c1af
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 61 additions and 3 deletions

File diff suppressed because one or more lines are too long

View File

@ -746,6 +746,21 @@ async def get_file(file_path: str, project: Project = Depends(dep_project)) -> F
return FileResponse(path, media_type="application/octet-stream")
@router.get("/{project_id}/gns3file", dependencies=[Depends(has_privilege("Project.Audit"))])
async def get_project_gns3_file(project: Project = Depends(dep_project)) -> FileResponse:
"""
Return the .gns3 topology file of a project.
Required privilege: Project.Audit
"""
path = project.topology_file
if not os.path.exists(path):
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
return FileResponse(path, media_type="application/json")
@router.post(
"/{project_id}/files/{file_path:path}",
status_code=status.HTTP_204_NO_CONTENT,

View File

@ -1685,6 +1685,14 @@ class Project:
def _topology_file(self):
return os.path.join(self.path, self._filename)
@property
def topology_file(self):
"""
Absolute path of the .gns3 topology file
"""
return self._topology_file()
@locking
async def open(self, auto_start=True):
"""

View File

@ -435,8 +435,43 @@ class TestControllerProjectRoutes:
response = await client.get(app.url_path_for("get_file", project_id=project.id, file_path="../hello"))
assert response.status_code == status.HTTP_404_NOT_FOUND
async def test_get_project_gns3_file(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
response = await client.get(app.url_path_for("get_project_gns3_file", project_id=project.id))
assert response.status_code == status.HTTP_200_OK
assert response.headers["content-type"].startswith("application/json")
topology = response.json()
assert topology["name"] == "test"
assert "topology" in topology
async def test_get_project_gns3_file_raw_content(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
# the endpoint must serve the raw file content, without the project being opened
topology = {
"name": "test",
"topology": {
"nodes": [{"node_id": "abc", "name": "n1", "x": 10, "y": 20}],
"links": [],
"drawings": [{"drawing_id": "def", "svg": "<svg/>"}],
},
}
with open(project.topology_file, "w+") as f:
json.dump(topology, f)
response = await client.get(app.url_path_for("get_project_gns3_file", project_id=project.id))
assert response.status_code == status.HTTP_200_OK
assert response.json() == topology
async def test_get_project_gns3_file_project_not_found(self, app: FastAPI, client: AsyncClient) -> None:
response = await client.get(app.url_path_for("get_project_gns3_file", project_id=str(uuid.uuid4())))
assert response.status_code == status.HTTP_404_NOT_FOUND
async def test_get_file_forbidden_location(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
file_path = "foo/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd"