Do not allow creating a project that has a name or part of the path matching the existing public project

This commit is contained in:
clearml 2024-12-05 22:26:57 +02:00
parent 41b003f328
commit 543c579a2e
3 changed files with 37 additions and 3 deletions

View File

@ -40,7 +40,7 @@ from .sub_projects import (
_ids_with_children, _ids_with_children,
_ids_with_parents, _ids_with_parents,
_get_project_depth, _get_project_depth,
ProjectsChildren, ProjectsChildren, _get_writable_project_from_name,
) )
log = config.logger(__file__) log = config.logger(__file__)
@ -225,6 +225,18 @@ class ProjectBLL:
raise errors.bad_request.ProjectPathExceedsMax(max_depth=max_depth) raise errors.bad_request.ProjectPathExceedsMax(max_depth=max_depth)
name, location = _validate_project_name(name) name, location = _validate_project_name(name)
existing = _get_writable_project_from_name(
company=company,
name=name,
)
if existing:
raise errors.bad_request.ExpectedUniqueData(
replacement_msg="Project with the same name already exists",
name=name,
company=company,
)
now = datetime.utcnow() now = datetime.utcnow()
project = Project( project = Project(
id=database.utils.id(), id=database.utils.id(),

View File

@ -2,6 +2,8 @@ import itertools
from datetime import datetime from datetime import datetime
from typing import Tuple, Optional, Sequence, Mapping from typing import Tuple, Optional, Sequence, Mapping
from boltons.iterutils import first
from apiserver import database from apiserver import database
from apiserver.apierrors import errors from apiserver.apierrors import errors
from apiserver.database.model import EntityVisibility from apiserver.database.model import EntityVisibility
@ -96,10 +98,21 @@ def _get_writable_project_from_name(
""" """
Return a project from name. If the project not found then return None Return a project from name. If the project not found then return None
""" """
qs = Project.objects(company=company, name=name) qs = Project.objects(company__in=[company, ""], name=name)
if _only: if _only:
if "company" not in _only:
_only = ["company", *_only]
qs = qs.only(*_only) qs = qs.only(*_only)
return qs.first() projects = list(qs)
if not projects:
return
project = first(p for p in projects if p.company == company)
if not project:
raise errors.bad_request.PublicProjectExists(name=name)
return project
ProjectsChildren = Mapping[str, Sequence[Project]] ProjectsChildren = Mapping[str, Sequence[Project]]

View File

@ -439,6 +439,15 @@ class TestSubProjects(TestService):
self.assertEqual(res2.own_tasks, 0) self.assertEqual(res2.own_tasks, 0)
self.assertEqual(res2.own_models, 0) self.assertEqual(res2.own_models, 0)
def test_public_names_clash(self):
# cannot create a project with a name that match public existing project
with self.api.raises(errors.bad_request.PublicProjectExists):
project = self._temp_project(name="ClearML Examples")
# cannot create a subproject under a public project
with self.api.raises(errors.bad_request.PublicProjectExists):
project = self._temp_project(name="ClearML Examples/my project")
def test_get_all_with_stats(self): def test_get_all_with_stats(self):
project4, _ = self._temp_project_with_tasks(name="project1/project3/project4") project4, _ = self._temp_project_with_tasks(name="project1/project3/project4")
project5, _ = self._temp_project_with_tasks(name="project1/project3/project5") project5, _ = self._temp_project_with_tasks(name="project1/project3/project5")