This is an automated email from the ASF dual-hosted git repository.
sbp pushed a commit to branch sbp
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git
The following commit(s) were added to refs/heads/sbp by this push:
new 0dd29c63 Distinguish admins writing for committees from those writing
generally
0dd29c63 is described below
commit 0dd29c6335d65681cffcdbac1572e0995dd7315f
Author: Sean B. Palmer <[email protected]>
AuthorDate: Wed Mar 4 19:39:40 2026 +0000
Distinguish admins writing for committees from those writing generally
---
atr/admin/__init__.py | 6 ++---
atr/api/__init__.py | 4 +--
atr/storage/__init__.py | 60 +++++++++++++++++++++++++++++++++---------
atr/storage/writers/keys.py | 2 +-
atr/storage/writers/release.py | 9 +++----
atr/storage/writers/tokens.py | 5 ++--
scripts/keys_import.py | 4 +--
7 files changed, 60 insertions(+), 30 deletions(-)
diff --git a/atr/admin/__init__.py b/atr/admin/__init__.py
index 8fa120fd..6a759d93 100644
--- a/atr/admin/__init__.py
+++ b/atr/admin/__init__.py
@@ -742,7 +742,7 @@ async def revoke_user_tokens_post(
target_uid = revoke_form.asf_uid
async with storage.write(session) as write:
- wafa = write.as_foundation_admin(session.asf_uid)
+ wafa = write.as_foundation_admin()
count = await wafa.tokens.revoke_all_user_tokens(target_uid)
if count > 0:
@@ -1014,8 +1014,8 @@ async def _delete_releases(session: web.Committer,
releases_to_delete: list[str]
if release.committee is None:
raise RuntimeError(f"Release {release_name} has no
committee")
async with storage.write(session) as write:
- wafa = write.as_foundation_admin(release.committee.name)
- error = await wafa.release.delete(release.project.name,
release.version)
+ waca = write.as_committee_admin(release.committee.name)
+ error = await waca.release.delete(release.project.name,
release.version)
# Ensure that deletion errors are reported to the user
if error is not None:
raise RuntimeError(error)
diff --git a/atr/api/__init__.py b/atr/api/__init__.py
index ad2c628b..d911a7d9 100644
--- a/atr/api/__init__.py
+++ b/atr/api/__init__.py
@@ -912,8 +912,8 @@ async def release_delete(data:
models.api.ReleaseDeleteArgs) -> DictResponse:
raise exceptions.Forbidden("You do not have permission to delete a
release")
async with storage.write(asf_uid) as write:
- wafa = write.as_foundation_admin(data.project)
- error = await wafa.release.delete(data.project, data.version)
+ waca = await write.as_project_committee_admin(data.project)
+ error = await waca.release.delete(data.project, data.version)
# Ensure that deletion errors are reported to the user
if error is not None:
raise RuntimeError(error)
diff --git a/atr/storage/__init__.py b/atr/storage/__init__.py
index 15314b26..4b8caa0f 100644
--- a/atr/storage/__init__.py
+++ b/atr/storage/__init__.py
@@ -220,13 +220,11 @@ class WriteAsCommitteeMember(WriteAsCommitteeParticipant):
return self.__committee_name
-class WriteAsFoundationAdmin(WriteAsCommitteeMember):
- def __init__(self, write: Write, data: db.Session, committee_name: str):
+class WriteAsFoundationAdmin(WriteAsFoundationCommitter):
+ def __init__(self, write: Write, data: db.Session):
self.__asf_uid = write.authorisation.asf_uid
- self.__committee_name = committee_name
- self.keys = writers.keys.FoundationAdmin(write, self, data,
committee_name)
- self.release = writers.release.FoundationAdmin(write, self, data,
committee_name)
- self.tokens = writers.tokens.FoundationAdmin(write, self, data,
committee_name)
+ self.release = writers.release.FoundationAdmin(write, self, data)
+ self.tokens = writers.tokens.FoundationAdmin(write, self, data)
@property
def asf_uid(self) -> str:
@@ -234,9 +232,11 @@ class WriteAsFoundationAdmin(WriteAsCommitteeMember):
raise AccessError("Not authorized")
return self.__asf_uid
- @property
- def committee_name(self) -> str:
- return self.__committee_name
+
+class WriteAsCommitteeAdmin(WriteAsCommitteeMember):
+ def __init__(self, write: Write, data: db.Session, committee_name: str):
+ super().__init__(write, data, committee_name)
+ self.keys = writers.keys.FoundationAdmin(write, self, data,
committee_name)
# TODO: Could name this WriteDispatcher
@@ -251,6 +251,20 @@ class Write:
def authorisation(self) -> principal.Authorisation:
return self.__authorisation
+ def as_committee_admin(self, committee_name: str) -> WriteAsCommitteeAdmin:
+ return
self.as_committee_admin_outcome(committee_name).result_or_raise()
+
+ def as_committee_admin_outcome(self, committee_name: str) ->
outcome.Outcome[WriteAsCommitteeAdmin]:
+ if self.__authorisation.asf_uid is None:
+ return outcome.Error(AccessError("Not authorized"))
+ if not user.is_admin(self.__authorisation.asf_uid):
+ return outcome.Error(AccessError("Not an admin"))
+ try:
+ waca = WriteAsCommitteeAdmin(self, self.__data, committee_name)
+ except Exception as e:
+ return outcome.Error(e)
+ return outcome.Result(waca)
+
def as_committee_member(self, committee_name: str) ->
WriteAsCommitteeMember:
return
self.as_committee_member_outcome(committee_name).result_or_raise()
@@ -291,16 +305,16 @@ class Write:
return outcome.Error(e)
return outcome.Result(wafm)
- def as_foundation_admin(self, committee_name: str) ->
WriteAsFoundationAdmin:
- return
self.as_foundation_admin_outcome(committee_name).result_or_raise()
+ def as_foundation_admin(self) -> WriteAsFoundationAdmin:
+ return self.as_foundation_admin_outcome().result_or_raise()
- def as_foundation_admin_outcome(self, committee_name: str) ->
outcome.Outcome[WriteAsFoundationAdmin]:
+ def as_foundation_admin_outcome(self) ->
outcome.Outcome[WriteAsFoundationAdmin]:
if self.__authorisation.asf_uid is None:
return outcome.Error(AccessError("Not authorized"))
if not user.is_admin(self.__authorisation.asf_uid):
return outcome.Error(AccessError("Not an admin"))
try:
- wafa = WriteAsFoundationAdmin(self, self.__data, committee_name)
+ wafa = WriteAsFoundationAdmin(self, self.__data)
except Exception as e:
return outcome.Error(e)
return outcome.Result(wafa)
@@ -318,6 +332,26 @@ class Write:
# async def as_key_owner(self) -> types.Outcome[WriteAsKeyOwner]:
# ...
+ async def as_project_committee_admin(self, project_name: str) ->
WriteAsCommitteeAdmin:
+ write_as_outcome = await
self.as_project_committee_admin_outcome(project_name)
+ return write_as_outcome.result_or_raise()
+
+ async def as_project_committee_admin_outcome(self, project_name: str) ->
outcome.Outcome[WriteAsCommitteeAdmin]:
+ project = await self.__data.project(project_name,
_committee=True).demand(
+ AccessError(f"Project not found: {project_name}")
+ )
+ if project.committee is None:
+ return outcome.Error(AccessError("No committee found for project -
Invalid state"))
+ if self.__authorisation.asf_uid is None:
+ return outcome.Error(AccessError("Not authorized"))
+ if not user.is_admin(self.__authorisation.asf_uid):
+ return outcome.Error(AccessError("Not an admin"))
+ try:
+ waca = WriteAsCommitteeAdmin(self, self.__data,
project.committee.name)
+ except Exception as e:
+ return outcome.Error(e)
+ return outcome.Result(waca)
+
async def as_project_committee_member(self, project_name: str) ->
WriteAsCommitteeMember:
write_as_outcome = await
self.as_project_committee_member_outcome(project_name)
return write_as_outcome.result_or_raise()
diff --git a/atr/storage/writers/keys.py b/atr/storage/writers/keys.py
index ca1e4f58..b0cb6ee4 100644
--- a/atr/storage/writers/keys.py
+++ b/atr/storage/writers/keys.py
@@ -655,7 +655,7 @@ class FoundationAdmin(CommitteeMember):
def __init__(
self,
write: storage.Write,
- write_as: storage.WriteAsFoundationAdmin,
+ write_as: storage.WriteAsCommitteeAdmin,
data: db.Session,
committee_name: str,
):
diff --git a/atr/storage/writers/release.py b/atr/storage/writers/release.py
index 8f244172..10188b5f 100644
--- a/atr/storage/writers/release.py
+++ b/atr/storage/writers/release.py
@@ -779,11 +779,9 @@ class CommitteeMember(CommitteeParticipant):
self.__committee_name = committee_name
-class FoundationAdmin(CommitteeMember):
- def __init__(
- self, write: storage.Write, write_as: storage.WriteAsFoundationAdmin,
data: db.Session, committee_name: str
- ) -> None:
- super().__init__(write, write_as, data, committee_name)
+class FoundationAdmin(FoundationCommitter):
+ def __init__(self, write: storage.Write, write_as:
storage.WriteAsFoundationAdmin, data: db.Session) -> None:
+ super().__init__(write, write_as, data)
self.__write = write
self.__write_as = write_as
self.__data = data
@@ -791,4 +789,3 @@ class FoundationAdmin(CommitteeMember):
if asf_uid is None:
raise storage.AccessError("Not authorized")
self.__asf_uid = asf_uid
- self.__committee_name = committee_name
diff --git a/atr/storage/writers/tokens.py b/atr/storage/writers/tokens.py
index 620e9d3b..cfb28d1c 100644
--- a/atr/storage/writers/tokens.py
+++ b/atr/storage/writers/tokens.py
@@ -179,15 +179,14 @@ class CommitteeMember(CommitteeParticipant):
self.__committee_name = committee_name
-class FoundationAdmin(CommitteeMember):
+class FoundationAdmin(FoundationCommitter):
def __init__(
self,
write: storage.Write,
write_as: storage.WriteAsFoundationAdmin,
data: db.Session,
- committee_name: str,
):
- super().__init__(write, write_as, data, committee_name)
+ super().__init__(write, write_as, data)
self.__write = write
self.__write_as = write_as
self.__data = data
diff --git a/scripts/keys_import.py b/scripts/keys_import.py
index c12d8b58..13c37709 100755
--- a/scripts/keys_import.py
+++ b/scripts/keys_import.py
@@ -179,9 +179,9 @@ async def keys_import(conf: config.AppConfig, asf_uid: str)
-> None:
# Parse the KEYS file and add it to the database
# We use a separate storage.write() context for each committee to
avoid transaction conflicts
async with storage.write(asf_uid) as write:
- wafa = write.as_foundation_admin(committee_name)
+ waca = write.as_committee_admin(committee_name)
keys_file_text = content.decode("utf-8", errors="replace")
- outcomes = await wafa.keys.ensure_associated(keys_file_text)
+ outcomes = await waca.keys.ensure_associated(keys_file_text)
log_outcome_errors(outcomes, committee_name)
yes = outcomes.result_count
no = outcomes.error_count
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]