You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tooling.apache.org by sb...@apache.org on 2025/10/14 17:17:48 UTC
(tooling-trusted-releases) branch main updated: Allow everyone to view vote pages
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 cb9a367 Allow everyone to view vote pages
cb9a367 is described below
commit cb9a367e36999979947829e5c822a34959c17f41
Author: Sean B. Palmer <se...@miscoranda.com>
AuthorDate: Tue Oct 14 18:17:25 2025 +0100
Allow everyone to view vote pages
---
atr/routes/compose.py | 27 ++++++--
atr/routes/vote.py | 81 ++++++++++++++---------
atr/storage/readers/checks.py | 6 +-
atr/storage/readers/releases.py | 6 +-
atr/storage/readers/tokens.py | 1 +
atr/templates/check-selected-candidate-forms.html | 2 +-
atr/templates/check-selected-release-info.html | 4 +-
7 files changed, 76 insertions(+), 51 deletions(-)
diff --git a/atr/routes/compose.py b/atr/routes/compose.py
index 9e91c20..88803b3 100644
--- a/atr/routes/compose.py
+++ b/atr/routes/compose.py
@@ -38,13 +38,15 @@ if TYPE_CHECKING:
async def check(
- session: route.CommitterSession,
+ session: route.CommitterSession | None,
release: sql.Release,
task_mid: str | None = None,
form: wtforms.Form | None = None,
hidden_form: wtforms.Form | None = None,
archive_url: str | None = None,
vote_task: sql.Task | None = None,
+ can_vote: bool = False,
+ can_resolve: bool = False,
) -> response.Response | str:
base_path = util.release_directory(release)
@@ -53,13 +55,22 @@ async def check(
paths = [path async for path in util.paths_recursive(base_path)]
paths.sort()
- async with storage.read() as read:
+ asf_uid = session.uid if (session is not None) else None
+ async with storage.read(asf_uid) as read:
ragp = read.as_general_public()
info = await ragp.releases.path_info(release, paths)
user_ssh_keys: Sequence[sql.SSHKey] = []
- async with db.session() as data:
- user_ssh_keys = await data.ssh_key(asf_uid=session.uid).all()
+ asf_id: str | None = None
+ server_domain: str | None = None
+ server_host: str | None = None
+
+ if session is not None:
+ asf_id = session.uid
+ server_domain = session.app_host.split(":", 1)[0]
+ server_host = session.app_host
+ async with db.session() as data:
+ user_ssh_keys = await data.ssh_key(asf_uid=session.uid).all()
# Get the number of ongoing tasks for the current revision
ongoing_tasks_count = 0
@@ -100,9 +111,9 @@ async def check(
ongoing_tasks_count=ongoing_tasks_count,
delete_form=delete_draft_form,
delete_file_form=delete_file_form,
- asf_id=session.uid,
- server_domain=session.app_host.split(":", 1)[0],
- server_host=session.app_host,
+ asf_id=asf_id,
+ server_domain=server_domain,
+ server_host=server_host,
user_ssh_keys=user_ssh_keys,
format_datetime=util.format_datetime,
models=sql,
@@ -115,6 +126,8 @@ async def check(
hidden_form=hidden_form,
has_files=has_files,
strict_checking_errors=strict_checking_errors,
+ can_vote=can_vote,
+ can_resolve=can_resolve,
)
diff --git a/atr/routes/vote.py b/atr/routes/vote.py
index 634ecda..d4999b2 100644
--- a/atr/routes/vote.py
+++ b/atr/routes/vote.py
@@ -29,6 +29,7 @@ import atr.route as route
import atr.routes.compose as compose
import atr.routes.mapping as mapping
import atr.storage as storage
+import atr.user as user
import atr.util as util
@@ -40,11 +41,11 @@ class CastVoteForm(forms.Typed):
submit = forms.submit("Submit vote")
-@route.committer("/vote/<project_name>/<version_name>")
-async def selected(session: route.CommitterSession, project_name: str, version_name: str) -> response.Response | str:
+@route.public("/vote/<project_name>/<version_name>")
+async def selected(
+ session: route.CommitterSession | None, project_name: str, version_name: str
+) -> response.Response | str:
"""Show the contents of the release candidate draft."""
- await session.check_access(project_name)
-
async with db.session() as data:
release = await data.release(
project_name=project_name,
@@ -52,8 +53,22 @@ async def selected(session: route.CommitterSession, project_name: str, version_n
_committee=True,
_project_release_policy=True,
).demand(base.ASFQuartException("Release does not exist", errorcode=404))
+
if release.phase != sql.ReleasePhase.RELEASE_CANDIDATE:
+ if session is None:
+ raise base.ASFQuartException("Release is not a candidate", errorcode=404)
return await mapping.release_as_redirect(session, release)
+
+ if release.committee is None:
+ raise ValueError("Release has no committee")
+
+ is_authenticated = session is not None
+ is_committee_member = is_authenticated and (
+ user.is_committee_member(release.committee, session.uid) or user.is_admin(session.uid)
+ )
+ can_vote = is_committee_member
+ can_resolve = is_committee_member
+
latest_vote_task = await interaction.release_latest_vote_task(release)
archive_url = None
task_mid = None
@@ -74,37 +89,38 @@ async def selected(session: route.CommitterSession, project_name: str, version_n
# Move task_mid_get here?
task_mid = interaction.task_mid_get(latest_vote_task)
- async with storage.write() as write:
+ asf_uid = session.uid if (session is not None) else None
+ async with storage.write(asf_uid) as write:
wagp = write.as_general_public()
archive_url = await wagp.cache.get_message_archive_url(task_mid)
- # Special form for the [ Resolve vote ] button, to make it POST
- hidden_form = await forms.Hidden.create_form()
- hidden_form.hidden_field.data = archive_url or ""
- hidden_form.submit.label.text = "Resolve vote"
-
- if release.committee is None:
- raise ValueError("Release has no committee")
+ hidden_form = None
+ if can_resolve:
+ # Special form for the [ Resolve vote ] button, to make it POST
+ hidden_form = await forms.Hidden.create_form()
+ hidden_form.hidden_field.data = archive_url or ""
+ hidden_form.submit.label.text = "Resolve vote"
- # Form to cast a vote
- form = await CastVoteForm.create_form()
- async with storage.write() as write:
- try:
- if release.committee.is_podling:
- _wacm = write.as_committee_member("incubator")
- else:
- _wacm = write.as_committee_member(release.committee.name)
- potency = "Binding"
- except storage.AccessError:
- potency = "Non-binding"
- forms.choices(
- form.vote_value,
- choices=[
- ("+1", f"+1 ({potency})"),
- ("0", "0"),
- ("-1", f"-1 ({potency})"),
- ],
- )
+ form = None
+ if can_vote:
+ form = await CastVoteForm.create_form()
+ async with storage.write() as write:
+ try:
+ if release.committee.is_podling:
+ _wacm = write.as_committee_member("incubator")
+ else:
+ _wacm = write.as_committee_member(release.committee.name)
+ potency = "Binding"
+ except storage.AccessError:
+ potency = "Non-binding"
+ forms.choices(
+ form.vote_value,
+ choices=[
+ ("+1", f"+1 ({potency})"),
+ ("0", "0"),
+ ("-1", f"-1 ({potency})"),
+ ],
+ )
return await compose.check(
session,
@@ -114,6 +130,8 @@ async def selected(session: route.CommitterSession, project_name: str, version_n
hidden_form=hidden_form,
archive_url=archive_url,
vote_task=latest_vote_task,
+ can_vote=can_vote,
+ can_resolve=can_resolve,
)
@@ -137,6 +155,7 @@ async def selected_post(session: route.CommitterSession, project_name: str, vers
_wacm = write.as_committee_member(release.committee.name)
potency = "Binding"
except storage.AccessError:
+ # Participant, due to session.check_access above
potency = "Non-binding"
form = await CastVoteForm.create_form(data=await quart.request.form)
diff --git a/atr/storage/readers/checks.py b/atr/storage/readers/checks.py
index 079a2f5..8cf44b1 100644
--- a/atr/storage/readers/checks.py
+++ b/atr/storage/readers/checks.py
@@ -42,11 +42,7 @@ class GeneralPublic:
self.__read = read
self.__read_as = read_as
self.__data = data
- if asf_uid is None:
- asf_uid = read.authorisation.asf_uid
- if asf_uid is None:
- raise ValueError("An ASF UID is required")
- self.__asf_uid = asf_uid
+ self.__asf_uid = read.authorisation.asf_uid
async def by_release_path(self, release: sql.Release, rel_path: pathlib.Path) -> types.CheckResults:
if release.committee is None:
diff --git a/atr/storage/readers/releases.py b/atr/storage/readers/releases.py
index 193ab51..b1ecf6a 100644
--- a/atr/storage/readers/releases.py
+++ b/atr/storage/readers/releases.py
@@ -39,11 +39,7 @@ class GeneralPublic:
self.__read = read
self.__read_as = read_as
self.__data = data
- if asf_uid is None:
- asf_uid = read.authorisation.asf_uid
- if asf_uid is None:
- raise ValueError("An ASF UID is required")
- self.__asf_uid = asf_uid
+ self.__asf_uid = read.authorisation.asf_uid
async def path_info(self, release: sql.Release, paths: list[pathlib.Path]) -> types.PathInfo | None:
info = types.PathInfo()
diff --git a/atr/storage/readers/tokens.py b/atr/storage/readers/tokens.py
index 5bff63d..d892042 100644
--- a/atr/storage/readers/tokens.py
+++ b/atr/storage/readers/tokens.py
@@ -30,6 +30,7 @@ class GeneralPublic:
self.__read = read
self.__read_as = read_as
self.__data = data
+ self.__asf_uid = read.authorisation.asf_uid
class FoundationCommitter(GeneralPublic):
diff --git a/atr/templates/check-selected-candidate-forms.html b/atr/templates/check-selected-candidate-forms.html
index 17ca5fd..8cc096e 100644
--- a/atr/templates/check-selected-candidate-forms.html
+++ b/atr/templates/check-selected-candidate-forms.html
@@ -3,7 +3,7 @@
{% endif %}
-{% if form %}
+{% if can_vote and form %}
<h2>Cast your vote</h2>
<form method="post"
diff --git a/atr/templates/check-selected-release-info.html b/atr/templates/check-selected-release-info.html
index 9b5fdc7..869fa22 100644
--- a/atr/templates/check-selected-release-info.html
+++ b/atr/templates/check-selected-release-info.html
@@ -77,10 +77,10 @@
class="btn btn-primary"><i class="bi bi-download me-1"></i> Download files</a>
<a href="{{ as_url(routes.candidate.view, project_name=release.project.name, version_name=release.version) }}"
class="btn btn-secondary"><i class="bi bi-eye me-1"></i> View files</a>
- {% if release.vote_manual %}
+ {% if can_resolve and release.vote_manual %}
<a href="{{ as_url(routes.resolve.manual_selected, project_name=release.project.name, version_name=release.version) }}"
class="btn btn-success"><i class="bi bi-clipboard-check me-1"></i> Resolve vote</a>
- {% elif hidden_form %}
+ {% elif can_resolve and hidden_form %}
<form action="{{ as_url(routes.resolve.tabulated_selected_post, project_name=release.project.name, version_name=release.version) }}"
method="post"
class="mb-0">
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org