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 2026/05/06 15:18:28 UTC

(tooling-trusted-releases) branch altera updated: Warn when OpenPGP keys are uploaded for a different or unknown user

This is an automated email from the ASF dual-hosted git repository.

sbp pushed a commit to branch altera
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git


The following commit(s) were added to refs/heads/altera by this push:
     new 7ea3170c Warn when OpenPGP keys are uploaded for a different or unknown user
7ea3170c is described below

commit 7ea3170ca3ff1f1627caea495990743b5eac0e5c
Author: Sean B. Palmer <se...@miscoranda.com>
AuthorDate: Wed May 6 16:17:35 2026 +0100

    Warn when OpenPGP keys are uploaded for a different or unknown user
---
 atr/post/keys.py             | 30 ++++++++++++++++++++
 tests/unit/test_keys_post.py | 66 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 96 insertions(+)

diff --git a/atr/post/keys.py b/atr/post/keys.py
index 439c6894..25f583a4 100644
--- a/atr/post/keys.py
+++ b/atr/post/keys.py
@@ -85,6 +85,7 @@ async def add(
                 await quart.flash(str(p), "warning")
             else:
                 await quart.flash(f"OpenPGP key {fingerprint_upper} added successfully.", "success")
+            await _flash_openpgp_key_uid_warning(key.key_model, session.asf_uid)
 
     except web.FlashError as e:
         log.warning(f"FlashError adding OpenPGP key: {e}")
@@ -300,6 +301,35 @@ async def _fetch_keys_from_url(keys_url: str) -> str:
         raise base.ASFQuartException(f"Network error while fetching keys: {e}", errorcode=503)
 
 
+async def _flash_openpgp_key_uid_warning(key_model: sql.PublicSigningKey, current_asf_uid: str) -> None:
+    warning = _openpgp_key_uid_warning(key_model, current_asf_uid)
+    if warning is not None:
+        await quart.flash(str(warning), "warning")
+
+
+def _openpgp_key_uid_warning(key_model: sql.PublicSigningKey, current_asf_uid: str) -> htm.Element | None:
+    fingerprint_upper = key_model.fingerprint.upper()
+    if key_model.apache_uid is None:
+        details_url = util.as_url(get.keys.details, fingerprint=key_model.fingerprint)
+        return htm.p[
+            f"OpenPGP key {fingerprint_upper} was uploaded and associated, but ATR could not determine an ASF UID "
+            "from the key user IDs. ",
+            htm.a(href=details_url)["Review key details"],
+            ".",
+        ]
+
+    if key_model.apache_uid.lower() != current_asf_uid.lower():
+        details_url = util.as_url(get.keys.details, fingerprint=key_model.fingerprint)
+        return htm.p[
+            f"OpenPGP key {fingerprint_upper} was uploaded and associated, but it appears to belong to ASF UID "
+            f"{key_model.apache_uid}, not {current_asf_uid}. ",
+            htm.a(href=details_url)["Review key details"],
+            ".",
+        ]
+
+    return None
+
+
 async def _process_keys(keys_text: str, selected_committee: str) -> str:
     """Process keys text and associate with committee."""
     async with storage.write() as write:
diff --git a/tests/unit/test_keys_post.py b/tests/unit/test_keys_post.py
new file mode 100644
index 00000000..cd5c2054
--- /dev/null
+++ b/tests/unit/test_keys_post.py
@@ -0,0 +1,66 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import datetime
+
+import pytest
+
+import atr.models.sql as sql
+import atr.post.keys as keys
+
+
+def test_openpgp_key_uid_warning_allows_matching_asf_uid() -> None:
+    warning = keys._openpgp_key_uid_warning(_public_key(apache_uid="alice"), "Alice")
+
+    assert warning is None
+
+
+def test_openpgp_key_uid_warning_flags_missing_asf_uid(monkeypatch: pytest.MonkeyPatch) -> None:
+    monkeypatch.setattr(keys.util, "as_url", lambda *_args, **_kwargs: "/keys/details/fp")
+
+    warning = keys._openpgp_key_uid_warning(_public_key(apache_uid=None), "alice")
+
+    assert warning is not None
+    warning_html = str(warning)
+    assert "could not determine an ASF UID" in warning_html
+    assert "/keys/details/fp" in warning_html
+
+
+def test_openpgp_key_uid_warning_flags_other_asf_uid(monkeypatch: pytest.MonkeyPatch) -> None:
+    monkeypatch.setattr(keys.util, "as_url", lambda *_args, **_kwargs: "/keys/details/fp")
+
+    warning = keys._openpgp_key_uid_warning(_public_key(apache_uid="bob"), "alice")
+
+    assert warning is not None
+    warning_html = str(warning)
+    assert "appears to belong to ASF UID bob, not alice" in warning_html
+    assert "/keys/details/fp" in warning_html
+
+
+def _public_key(apache_uid: str | None) -> sql.PublicSigningKey:
+    return sql.PublicSigningKey(
+        fingerprint="fp",
+        algorithm=1,
+        length=4096,
+        created=datetime.datetime.now(datetime.UTC),
+        latest_self_signature=None,
+        expires=None,
+        primary_declared_uid="Alice <al...@example.org>",
+        secondary_declared_uids=[],
+        apache_uid=apache_uid,
+        ascii_armored_key="-----BEGIN PGP PUBLIC KEY BLOCK-----\nbody\n-----END PGP PUBLIC KEY BLOCK-----\n",
+    )


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org