From 1054c4dde2036281ec55dfa21efc8a2acfe44668 Mon Sep 17 00:00:00 2001 From: dm Date: Mon, 2 Dec 2024 19:33:30 +0100 Subject: [PATCH] Improve GitLab projects name verification (#16262) * Improve GitLab projects name verification * Update translations * Replace ConsecutiveSpecialCharacters with a regex * Update Gitlab wrong name detection to reduce dependency to Gitlab internal code. * Update translations * Fix bad merge --------- Co-authored-by: William Woodruff Co-authored-by: Dustin Ingram --- tests/unit/oidc/forms/test_gitlab.py | 39 ++++++++++++++++++++++++++++ warehouse/locale/messages.pot | 18 ++++++++----- warehouse/oidc/forms/gitlab.py | 23 ++++++++++++++-- 3 files changed, 71 insertions(+), 9 deletions(-) diff --git a/tests/unit/oidc/forms/test_gitlab.py b/tests/unit/oidc/forms/test_gitlab.py index 08b6473f2adb..b6737a2b1aa6 100644 --- a/tests/unit/oidc/forms/test_gitlab.py +++ b/tests/unit/oidc/forms/test_gitlab.py @@ -133,6 +133,45 @@ def test_validate_basic_invalid_fields(self, monkeypatch, data): # We're testing only the basic validation here. assert not form.validate() + @pytest.mark.parametrize( + "project_name", + ["invalid.git", "invalid.atom", "invalid--project"], + ) + def test_reserved_project_names(self, project_name): + + data = MultiDict( + { + "namespace": "some", + "workflow_filepath": "subfolder/some-workflow.yml", + "project": project_name, + } + ) + + form = gitlab.GitLabPublisherForm(data) + assert not form.validate() + + @pytest.mark.parametrize( + "namespace", + [ + "invalid.git", + "invalid.atom", + "consecutive--special-characters", + "must-end-with-non-special-characters-", + ], + ) + def test_reserved_organization_names(self, namespace): + + data = MultiDict( + { + "namespace": namespace, + "workflow_filepath": "subfolder/some-workflow.yml", + "project": "valid-project", + } + ) + + form = gitlab.GitLabPublisherForm(data) + assert not form.validate() + @pytest.mark.parametrize( "workflow_filepath", [ diff --git a/warehouse/locale/messages.pot b/warehouse/locale/messages.pot index 83079d2cc224..06a0b232b139 100644 --- a/warehouse/locale/messages.pot +++ b/warehouse/locale/messages.pot @@ -374,7 +374,7 @@ msgid "Select project" msgstr "" #: warehouse/manage/forms.py:495 warehouse/oidc/forms/_core.py:23 -#: warehouse/oidc/forms/gitlab.py:43 +#: warehouse/oidc/forms/gitlab.py:57 msgid "Specify project name" msgstr "" @@ -625,7 +625,7 @@ msgid "Expired invitation for '${username}' deleted." msgstr "" #: warehouse/oidc/forms/_core.py:25 warehouse/oidc/forms/_core.py:35 -#: warehouse/oidc/forms/gitlab.py:45 +#: warehouse/oidc/forms/gitlab.py:60 warehouse/oidc/forms/gitlab.py:64 msgid "Invalid project name" msgstr "" @@ -745,26 +745,30 @@ msgid "Workflow filename must be a filename only, without directories" msgstr "" #: warehouse/oidc/forms/gitlab.py:32 +msgid "Name ends with .git or .atom" +msgstr "" + +#: warehouse/oidc/forms/gitlab.py:41 msgid "Specify GitLab namespace (username or group/subgroup)" msgstr "" -#: warehouse/oidc/forms/gitlab.py:36 +#: warehouse/oidc/forms/gitlab.py:46 warehouse/oidc/forms/gitlab.py:50 msgid "Invalid GitLab username or group/subgroup name." msgstr "" -#: warehouse/oidc/forms/gitlab.py:53 +#: warehouse/oidc/forms/gitlab.py:72 msgid "Specify top-level pipeline file path" msgstr "" -#: warehouse/oidc/forms/gitlab.py:62 +#: warehouse/oidc/forms/gitlab.py:81 msgid "Invalid environment name" msgstr "" -#: warehouse/oidc/forms/gitlab.py:77 +#: warehouse/oidc/forms/gitlab.py:96 msgid "Top-level pipeline file path must end with .yml or .yaml" msgstr "" -#: warehouse/oidc/forms/gitlab.py:81 +#: warehouse/oidc/forms/gitlab.py:100 msgid "Top-level pipeline file path cannot start or end with /" msgstr "" diff --git a/warehouse/oidc/forms/gitlab.py b/warehouse/oidc/forms/gitlab.py index 3bc5b3095d65..86fb08f0a936 100644 --- a/warehouse/oidc/forms/gitlab.py +++ b/warehouse/oidc/forms/gitlab.py @@ -11,6 +11,7 @@ # limitations under the License. import re +import typing import wtforms @@ -18,10 +19,18 @@ from warehouse.oidc.forms._core import PendingPublisherMixin # https://docs.gitlab.com/ee/user/reserved_names.html#limitations-on-project-and-group-names -_VALID_GITLAB_PROJECT = re.compile(r"^[a-zA-Z0-9][a-zA-Z0-9-_.]*$") -_VALID_GITLAB_NAMESPACE = re.compile(r"^[a-zA-Z0-9][a-zA-Z0-9-_./]*$") +_VALID_GITLAB_PROJECT = re.compile(r"^[a-zA-Z0-9][a-zA-Z0-9_.-]*[a-zA-Z0-9]$") +_VALID_GITLAB_NAMESPACE = re.compile(r"^[a-zA-Z0-9][a-zA-Z0-9-_./+]*[a-zA-Z0-9]$") _VALID_GITLAB_ENVIRONMENT = re.compile(r"^[a-zA-Z0-9\-_/${} ]+$") +_CONSECUTIVE_SPECIAL_CHARACTERS = re.compile(r"(?!.*[._-]{2})") + + +def ends_with_atom_or_git(form: wtforms.Form, field: wtforms.Field) -> None: + field_value = typing.cast(str, field.data).lower() + if field_value.endswith(".atom") or field_value.endswith(".git"): + raise wtforms.validators.ValidationError(_("Name ends with .git or .atom")) + class GitLabPublisherBase(wtforms.Form): __params__ = ["namespace", "project", "workflow_filepath", "environment"] @@ -31,19 +40,29 @@ class GitLabPublisherBase(wtforms.Form): wtforms.validators.InputRequired( message=_("Specify GitLab namespace (username or group/subgroup)"), ), + ends_with_atom_or_git, wtforms.validators.Regexp( _VALID_GITLAB_NAMESPACE, message=_("Invalid GitLab username or group/subgroup name."), ), + wtforms.validators.Regexp( + _CONSECUTIVE_SPECIAL_CHARACTERS, + message=_("Invalid GitLab username or group/subgroup name."), + ), ] ) project = wtforms.StringField( validators=[ wtforms.validators.InputRequired(message=_("Specify project name")), + ends_with_atom_or_git, wtforms.validators.Regexp( _VALID_GITLAB_PROJECT, message=_("Invalid project name") ), + wtforms.validators.Regexp( + _CONSECUTIVE_SPECIAL_CHARACTERS, + message=_("Invalid project name"), + ), ] )