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 4301d9b Allow admins to disassociate a committee with all of its keys
4301d9b is described below
commit 4301d9b737f5567047872788c77e536faa28b31f
Author: Sean B. Palmer <[email protected]>
AuthorDate: Mon May 26 19:24:56 2025 +0100
Allow admins to disassociate a committee with all of its keys
---
atr/blueprints/admin/admin.py | 63 ++++++++++++++++++++++++++++++++
atr/templates/delete-committee-keys.html | 36 ++++++++++++++++++
2 files changed, 99 insertions(+)
diff --git a/atr/blueprints/admin/admin.py b/atr/blueprints/admin/admin.py
index 9c5cb8d..743c4d3 100644
--- a/atr/blueprints/admin/admin.py
+++ b/atr/blueprints/admin/admin.py
@@ -31,6 +31,7 @@ import asfquart.base as base
import asfquart.session
import httpx
import quart
+import sqlalchemy.orm as orm
import werkzeug.wrappers.response as response
import wtforms
@@ -47,6 +48,16 @@ import atr.util as util
_LOGGER: Final = logging.getLogger(__name__)
+class DeleteCommitteeKeysForm(util.QuartFormTyped):
+ committee_name = wtforms.SelectField("Committee",
validators=[wtforms.validators.InputRequired()])
+ confirm_delete = wtforms.StringField(
+ "Confirmation",
+ validators=[wtforms.validators.InputRequired(),
wtforms.validators.Regexp("^DELETE KEYS$")],
+ render_kw={"placeholder": "DELETE KEYS"},
+ )
+ submit = wtforms.SubmitField("Delete all keys for selected committee")
+
+
class DeleteReleaseForm(util.QuartFormTyped):
"""Form for deleting releases."""
@@ -122,6 +133,58 @@ async def admin_data(model: str = "Committee") -> str:
)
[email protected]("/delete-committee-keys", methods=["GET", "POST"])
+async def admin_delete_committee_keys() -> str | response.Response:
+ form = await DeleteCommitteeKeysForm.create_form()
+ async with db.session() as data:
+ all_committees = await
data.committee(_public_signing_keys=True).order_by(models.Committee.name).all()
+ committees_with_keys = [c for c in all_committees if
c.public_signing_keys]
+ form.committee_name.choices = [(c.name, c.display_name) for c in
committees_with_keys]
+
+ if await form.validate_on_submit():
+ committee_name = form.committee_name.data
+ async with db.session() as data:
+ committee_query = data.committee(name=committee_name)
+ via = models.validate_instrumented_attribute
+ committee_query.query = committee_query.query.options(
+
orm.selectinload(via(models.Committee.public_signing_keys)).selectinload(
+ via(models.PublicSigningKey.committees)
+ )
+ )
+ committee = await committee_query.get()
+
+ if not committee:
+ await quart.flash(f"Committee '{committee_name}' not found.",
"error")
+ return
quart.redirect(quart.url_for("admin.admin_delete_committee_keys"))
+
+ keys_to_check = list(committee.public_signing_keys)
+ if not keys_to_check:
+ await quart.flash(f"Committee '{committee_name}' has no
keys.", "info")
+ return
quart.redirect(quart.url_for("admin.admin_delete_committee_keys"))
+
+ num_removed = len(committee.public_signing_keys)
+ committee.public_signing_keys.clear()
+ await data.flush()
+
+ unused_deleted = 0
+ for key_obj in keys_to_check:
+ if not key_obj.committees:
+ await data.delete(key_obj)
+ unused_deleted += 1
+
+ await data.commit()
+ await quart.flash(
+ f"Removed {num_removed} key links for '{committee_name}'.
Deleted {unused_deleted} unused keys.",
+ "success",
+ )
+ return
quart.redirect(quart.url_for("admin.admin_delete_committee_keys"))
+
+ elif quart.request.method == "POST":
+ await quart.flash("Form validation failed. Select committee and type
DELETE KEYS.", "warning")
+
+ return await template.render("delete-committee-keys.html", form=form)
+
+
@admin.BLUEPRINT.route("/delete-release", methods=["GET", "POST"])
async def admin_delete_release() -> str | response.Response:
"""Page to delete selected releases and their associated data and files."""
diff --git a/atr/templates/delete-committee-keys.html
b/atr/templates/delete-committee-keys.html
new file mode 100644
index 0000000..3d33141
--- /dev/null
+++ b/atr/templates/delete-committee-keys.html
@@ -0,0 +1,36 @@
+{% extends "layouts/base.html" %}
+
+{% import "macros/forms.html" as forms %}
+
+{% block title %}
+ Delete committee keys
+{% endblock title %}
+
+{% block content %}
+ <div class="container mx-auto p-4">
+ <h1 class="mb-4">Delete all keys for a committee</h1>
+
+ <form method="post"
+ action="{{ url_for('admin.admin_delete_committee_keys') }}"
+ class="atr-canary py-4 px-5 border rounded">
+ {{ form.csrf_token }}
+
+ <div class="mb-3">
+ {{ forms.label(form.committee_name) }}
+ {{ forms.widget(form.committee_name, classes="form-select",
id=form.committee_name.id) }}
+ {{ forms.errors(form.committee_name) }}
+ {{ forms.description(form.committee_name) }}
+ </div>
+
+ <div class="mb-3">
+ {{ forms.label(form.confirm_delete) }}
+ {{ forms.widget(form.confirm_delete, classes="form-control",
id=form.confirm_delete.id) }}
+ {{ forms.errors(form.confirm_delete) }}
+ {{ forms.description(form.confirm_delete) }}
+ </div>
+
+ <div class="mt-4">{{ form.submit(class_="btn btn-danger") }}</div>
+ </form>
+
+ </div>
+{% endblock content %}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]