This is an automated email from the ASF dual-hosted git repository.
sbp pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git
The following commit(s) were added to refs/heads/main by this push:
new 005cf6e Only allow SVN imports from known locations
005cf6e is described below
commit 005cf6ea41defb57fa902b71c947e3c2bd81d306
Author: Sean B. Palmer <[email protected]>
AuthorDate: Tue Dec 16 16:22:35 2025 +0000
Only allow SVN imports from known locations
---
atr/form.py | 26 ++++++++++++++++++++++++++
atr/get/upload.py | 2 +-
atr/post/upload.py | 26 +++++++++++++++++++++++++-
atr/shared/upload.py | 18 ++++++++++++++----
4 files changed, 66 insertions(+), 6 deletions(-)
diff --git a/atr/form.py b/atr/form.py
index ac155dd..0f3dba0 100644
--- a/atr/form.py
+++ b/atr/form.py
@@ -459,6 +459,26 @@ def to_str_list(v: Any) -> list[str]:
raise ValueError(f"Expected a string or list of strings, got
{type(v).__name__}")
+def to_url_path(v: Any) -> str | None:
+ if not v:
+ return None
+
+ url_path = str(v)
+
+ if url_path.startswith("/"):
+ raise ValueError("Absolute paths are not allowed")
+
+ segments = url_path.split("/")
+
+ if "." in segments:
+ raise ValueError("Self directory references (.) are not allowed")
+
+ if ".." in segments:
+ raise ValueError("Parent directory references (..) are not allowed")
+
+ return url_path
+
+
# Validator types come before other functions
# We must not use the "type" keyword here, otherwise Pydantic complains
@@ -521,6 +541,12 @@ StrList = Annotated[
pydantic.Field(default_factory=list),
]
+URLPath = Annotated[
+ str | None,
+ functional_validators.BeforeValidator(to_url_path),
+ pydantic.Field(default=None),
+]
+
class Set[EnumType: enum.Enum]:
def __iter__(self) -> Iterator[EnumType]:
diff --git a/atr/get/upload.py b/atr/get/upload.py
index 8bfa66f..b60c6ef 100644
--- a/atr/get/upload.py
+++ b/atr/get/upload.py
@@ -71,7 +71,7 @@ async def selected(session: web.Committer, project_name: str,
version_name: str)
)
block.h2(id="svn-upload")["SVN upload"]
- block.p["Import files from a world readable Subversion repository URL into
this draft."]
+ block.p["Import files from this project's ASF Subversion repository into
this draft."]
block.p[
"The import will be processed in the background using the ",
htm.code["svn export"],
diff --git a/atr/post/upload.py b/atr/post/upload.py
index f8404e3..f0dc590 100644
--- a/atr/post/upload.py
+++ b/atr/post/upload.py
@@ -16,15 +16,20 @@
# under the License.
+from typing import Final
+
import quart
import atr.blueprints.post as post
+import atr.db as db
import atr.get as get
import atr.log as log
import atr.shared as shared
import atr.storage as storage
import atr.web as web
+_SVN_BASE_URL: Final[str] = "https://dist.apache.org/repos/dist"
+
@post.committer("/upload/<project_name>/<version_name>")
@post.form(shared.upload.UploadForm)
@@ -69,17 +74,36 @@ async def _add_files(
)
+def _construct_svn_url(project_name: str, area: shared.upload.SvnArea, path:
str, *, is_podling: bool) -> str:
+ if is_podling:
+ return f"{_SVN_BASE_URL}/{area.value}/incubator/{project_name}/{path}"
+ return f"{_SVN_BASE_URL}/{area.value}/{project_name}/{path}"
+
+
async def _svn_import(
session: web.Committer, svn_form: shared.upload.SvnImportForm,
project_name: str, version_name: str
) -> web.WerkzeugResponse:
try:
target_subdirectory = str(svn_form.target_subdirectory) if
svn_form.target_subdirectory else None
+ svn_area = svn_form.svn_area
+ svn_path = svn_form.svn_path or ""
+
+ async with db.session() as data:
+ release = await session.release(project_name, version_name,
data=data)
+ is_podling = (release.project.committee is not None) and
release.project.committee.is_podling
+
+ svn_url = _construct_svn_url(
+ project_name,
+ svn_area, # pyright: ignore[reportArgumentType]
+ svn_path,
+ is_podling=is_podling,
+ )
async with storage.write(session) as write:
wacp = await write.as_project_committee_participant(project_name)
await wacp.release.import_from_svn(
project_name,
version_name,
- str(svn_form.svn_url),
+ svn_url,
svn_form.revision,
target_subdirectory,
)
diff --git a/atr/shared/upload.py b/atr/shared/upload.py
index cf4abb2..7fd532d 100644
--- a/atr/shared/upload.py
+++ b/atr/shared/upload.py
@@ -15,6 +15,7 @@
# specific language governing permissions and limitations
# under the License.
+import enum
from typing import Annotated, Literal
import pydantic
@@ -25,6 +26,11 @@ type ADD_FILES = Literal["add_files"]
type SVN_IMPORT = Literal["svn_import"]
+class SvnArea(enum.Enum):
+ DEV = "dev"
+ RELEASE = "release"
+
+
class AddFilesForm(form.Form):
variant: ADD_FILES = form.value(ADD_FILES)
file_data: form.FileList = form.label("Files", "Select the files to
upload.")
@@ -50,10 +56,14 @@ class AddFilesForm(form.Form):
class SvnImportForm(form.Form):
variant: SVN_IMPORT = form.value(SVN_IMPORT)
- svn_url: form.URL = form.label(
- "SVN URL",
- "The HTTP or HTTPS URL to the public SVN directory.",
- widget=form.Widget.URL,
+ svn_area: form.Enum[SvnArea] = form.label(
+ "SVN area",
+ "Select whether to import from dev or release.",
+ widget=form.Widget.RADIO,
+ )
+ svn_path: form.URLPath = form.label(
+ "SVN path",
+ "Path within the project's SVN directory, e.g. 'java-library/4_0_4' or
'3.1.5rc1'.",
)
revision: str = form.label(
"Revision",
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]