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-release.git
The following commit(s) were added to refs/heads/main by this push:
new b0920a2 Move functions to add and delete SSH keys to the storage
interface
b0920a2 is described below
commit b0920a2fac0117a9d54fe2c4f535fae7522301fb
Author: Sean B. Palmer <[email protected]>
AuthorDate: Thu Sep 4 20:01:56 2025 +0100
Move functions to add and delete SSH keys to the storage interface
---
atr/blueprints/api/api.py | 9 ++++++---
atr/routes/keys.py | 29 ++++-------------------------
atr/storage/writers/ssh.py | 14 ++++++++++++++
atr/util.py | 11 +++++++++++
4 files changed, 35 insertions(+), 28 deletions(-)
diff --git a/atr/blueprints/api/api.py b/atr/blueprints/api/api.py
index 73e89dd..8a239f3 100644
--- a/atr/blueprints/api/api.py
+++ b/atr/blueprints/api/api.py
@@ -41,7 +41,6 @@ import atr.models.sql as sql
import atr.revision as revision
import atr.routes as routes
import atr.routes.announce as announce
-import atr.routes.keys as keys
import atr.routes.resolve as resolve
import atr.routes.start as start
import atr.routes.vote as vote
@@ -974,7 +973,9 @@ async def ssh_key_add(data: models.api.SshKeyAddArgs) ->
DictResponse:
An SSH key is associated with a single user.
"""
asf_uid = _jwt_asf_uid()
- fingerprint = await keys.ssh_key_add(data.text, asf_uid)
+ async with storage.write(asf_uid) as write:
+ wafc = write.as_foundation_committer()
+ fingerprint = await wafc.ssh.add_key(data.text, asf_uid)
return models.api.SshKeyAddResults(
endpoint="/ssh-key/add",
fingerprint=fingerprint,
@@ -993,7 +994,9 @@ async def ssh_key_delete(data: models.api.SshKeyDeleteArgs)
-> DictResponse:
An SSH key can only be deleted by the user who owns it.
"""
asf_uid = _jwt_asf_uid()
- await keys.ssh_key_delete(data.fingerprint, asf_uid)
+ async with storage.write(asf_uid) as write:
+ wafc = write.as_foundation_committer()
+ await wafc.ssh.delete_key(data.fingerprint)
return models.api.SshKeyDeleteResults(
endpoint="/ssh-key/delete",
success=True,
diff --git a/atr/routes/keys.py b/atr/routes/keys.py
index 5ec0c13..3c033f9 100644
--- a/atr/routes/keys.py
+++ b/atr/routes/keys.py
@@ -26,7 +26,6 @@ import asfquart as asfquart
import asfquart.base as base
import quart
import werkzeug.datastructures as datastructures
-import werkzeug.exceptions as exceptions
import werkzeug.wrappers.response as response
import wtforms
@@ -75,10 +74,6 @@ class DeleteKeyForm(forms.Typed):
submit = forms.submit("Delete key")
-class SshFingerprintError(ValueError):
- pass
-
-
class UpdateCommitteeKeysForm(forms.Typed):
submit = forms.submit("Regenerate KEYS file")
@@ -366,8 +361,10 @@ async def ssh_add(session: routes.CommitterSession) ->
response.Response | str:
if await form.validate_on_submit():
key: str = util.unwrap(form.key.data)
try:
- fingerprint = await ssh_key_add(key, session.uid)
- except SshFingerprintError as e:
+ async with storage.write(session.uid) as write:
+ wafc = write.as_foundation_committer()
+ fingerprint = await wafc.ssh.add_key(key, session.uid)
+ except util.SshFingerprintError as e:
if isinstance(form.key.errors, list):
form.key.errors.append(str(e))
else:
@@ -384,24 +381,6 @@ async def ssh_add(session: routes.CommitterSession) ->
response.Response | str:
)
-async def ssh_key_add(key: str, asf_uid: str) -> str:
- try:
- fingerprint = util.key_ssh_fingerprint(key)
- except Exception as e:
- raise SshFingerprintError(str(e)) from e
- async with db.session() as data:
- data.add(sql.SSHKey(fingerprint=fingerprint, key=key, asf_uid=asf_uid))
- await data.commit()
- return fingerprint
-
-
-async def ssh_key_delete(fingerprint: str, asf_uid: str) -> None:
- async with db.session() as data:
- ssh_key = await data.ssh_key(fingerprint=fingerprint,
asf_uid=asf_uid).demand(exceptions.NotFound())
- await data.delete(ssh_key)
- await data.commit()
-
-
@routes.committer("/keys/update-committee-keys/<committee_name>",
methods=["POST"])
async def update_committee_keys(session: routes.CommitterSession,
committee_name: str) -> response.Response:
"""Generate and save the KEYS file for a specific committee."""
diff --git a/atr/storage/writers/ssh.py b/atr/storage/writers/ssh.py
index 8a65d5e..af8e717 100644
--- a/atr/storage/writers/ssh.py
+++ b/atr/storage/writers/ssh.py
@@ -50,6 +50,20 @@ class FoundationCommitter(GeneralPublic):
raise storage.AccessError("No ASF UID")
self.__asf_uid = asf_uid
+ async def add_key(self, key: str, asf_uid: str) -> str:
+ fingerprint = util.key_ssh_fingerprint(key)
+ self.__data.add(sql.SSHKey(fingerprint=fingerprint, key=key,
asf_uid=asf_uid))
+ await self.__data.commit()
+ return fingerprint
+
+ async def delete_key(self, fingerprint: str) -> None:
+ ssh_key = await self.__data.ssh_key(
+ fingerprint=fingerprint,
+ asf_uid=self.__asf_uid,
+ ).demand(storage.AccessError(f"Key not found: {fingerprint}"))
+ await self.__data.delete(ssh_key)
+ await self.__data.commit()
+
class CommitteeParticipant(FoundationCommitter):
def __init__(
diff --git a/atr/util.py b/atr/util.py
index ba707d6..4afa1d3 100644
--- a/atr/util.py
+++ b/atr/util.py
@@ -59,6 +59,10 @@ T = TypeVar("T")
USER_TESTS_ADDRESS: Final[str] = "[email protected]"
+class SshFingerprintError(ValueError):
+ pass
+
+
@dataclasses.dataclass
class FileStat:
path: str
@@ -525,6 +529,13 @@ def is_user_viewing_as_admin(uid: str | None) -> bool:
def key_ssh_fingerprint(ssh_key_string: str) -> str:
+ try:
+ return key_ssh_fingerprint_core(ssh_key_string)
+ except ValueError as e:
+ raise SshFingerprintError(str(e)) from e
+
+
+def key_ssh_fingerprint_core(ssh_key_string: str) -> str:
# The format should be as in *.pub or authorized_keys files
# I.e. TYPE DATA COMMENT
ssh_key_parts = ssh_key_string.strip().split()
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]