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 a672daf Add CodeQL configuration and make the code compatible
a672daf is described below
commit a672daf910db685ad9863db802ae5789cd192f49
Author: Sean B. Palmer <[email protected]>
AuthorDate: Tue Dec 9 16:18:47 2025 +0000
Add CodeQL configuration and make the code compatible
---
atr/admin/__init__.py | 370 +++++++++++++++++++++++++++++---------------------
atr/util.py | 2 +
codeql-config.yml | 5 +
3 files changed, 223 insertions(+), 154 deletions(-)
diff --git a/atr/admin/__init__.py b/atr/admin/__init__.py
index 38cf906..5808e6a 100644
--- a/atr/admin/__init__.py
+++ b/atr/admin/__init__.py
@@ -24,7 +24,7 @@ import statistics
import sys
import time
from collections.abc import Callable, Mapping
-from typing import Any, Final, Literal
+from typing import Any, Final, Literal, NamedTuple
import aiofiles.os
import aiohttp
@@ -102,6 +102,17 @@ class LdapLookupForm(form.Form):
email: str = form.label("Email address (optional)", "Enter email address,
e.g. [email protected]")
+class SessionDataCommon(NamedTuple):
+ uid: str
+ fullname: str
+ email: str
+ is_member: bool
+ is_chair: bool
+ is_root: bool
+ pmcs: list[str]
+ projects: list[str]
+
+
@admin.get("/all-releases")
async def all_releases(session: web.Committer) -> str:
"""Display a list of all releases across all phases."""
@@ -156,7 +167,15 @@ async def browse_as_post(session: web.Committer,
browse_form: BrowseAsUserForm)
bind_dn,
bind_password,
)
- log.info(f"New Quart cookie (not ASFQuart session) data:
{new_session_data}")
+ log_safe_data = _log_safe_session_data(
+ ldap_data,
+ new_uid,
+ ldap_projects_data,
+ committee_data,
+ bind_dn,
+ bind_password,
+ )
+ log.info(f"New Quart cookie (not ASFQuart session) data: {log_safe_data}")
asfquart.session.write(new_session_data)
await quart.flash(
@@ -215,24 +234,22 @@ async def consistency(session: web.Committer) ->
web.TextResponse:
filesystem_dirs.remove(filesystem_dir)
break
return web.TextResponse(
- f"""\
-=== BROKEN ===
-
-DATABASE ONLY:
-
-{"\n".join(sorted(database_dirs or ["-"]))}
-
-FILESYSTEM ONLY:
-
-{"\n".join(sorted(filesystem_dirs or ["-"]))}
-
-
-== Okay ==
-
-Paired correctly:
-
-{"\n".join(sorted(paired_dirs or ["-"]))}
-"""
+ "=== BROKEN ===\n"
+ "\n"
+ "DATABASE ONLY:\n"
+ "\n"
+ f"{chr(10).join(sorted(database_dirs or ['-']))}\n"
+ "\n"
+ "FILESYSTEM ONLY:\n"
+ "\n"
+ f"{chr(10).join(sorted(filesystem_dirs or ['-']))}\n"
+ "\n"
+ "\n"
+ "== Okay ==\n"
+ "\n"
+ "Paired correctly:\n"
+ "\n"
+ f"{chr(10).join(sorted(paired_dirs or ['-']))}\n"
)
@@ -246,93 +263,6 @@ async def data_model(session: web.Committer, model: str =
"Committee") -> str:
return await _data(session, model)
-async def _data(session: web.Committer, model: str = "Committee") -> str:
- """Browse all records in the database."""
- async with db.session() as data:
- # Map of model names to their classes
- # TODO: Add distribution channel, key link, and any others
- model_methods: dict[str, Callable[[], db.Query[Any]]] = {
- "CheckResult": data.check_result,
- "CheckResultIgnore": data.check_result_ignore,
- "Committee": data.committee,
- "Project": data.project,
- "PublicSigningKey": data.public_signing_key,
- "Release": data.release,
- "ReleasePolicy": data.release_policy,
- "Revision": data.revision,
- "SSHKey": data.ssh_key,
- "Task": data.task,
- "TextValue": data.text_value,
- }
-
- if model not in model_methods:
- raise base.ASFQuartException(f"Model type '{model}' not found",
404)
-
- # Get all records for the selected model
- records = await model_methods[model]().all()
-
- # Convert records to dictionaries for JSON serialization
- records_dict = []
- for record in records:
- if hasattr(record, "dict"):
- record_dict = record.dict()
- else:
- # Fallback for models without dict() method
- record_dict = {}
- # record_dict = {
- # "id": getattr(record, "id", None),
- # "name": getattr(record, "name", None),
- # }
- for key in record.__dict__:
- if not key.startswith("_"):
- record_dict[key] = getattr(record, key)
- records_dict.append(record_dict)
-
- return await template.render(
- "data-browser.html", models=list(model_methods.keys()),
model=model, records=records_dict
- )
-
-
[email protected]("/delete-test-openpgp-keys")
-async def delete_test_openpgp_keys_get(session: web.Committer) -> web.Response:
- """Display the form to delete test user OpenPGP keys."""
- if not config.get().ALLOW_TESTS:
- raise base.ASFQuartException("Test operations are disabled in this
environment", errorcode=403)
-
- rendered_form = form.render(
- model_cls=form.Empty,
- submit_label="Delete all OpenPGP keys for test user",
- empty=True,
- )
- return web.ElementResponse(rendered_form)
-
-
[email protected]("/delete-test-openpgp-keys")
[email protected]()
-async def delete_test_openpgp_keys_post(session: web.Committer) ->
web.Response:
- """Delete all test user OpenPGP keys and their links."""
- if not config.get().ALLOW_TESTS:
- raise base.ASFQuartException("Test operations are disabled in this
environment", errorcode=403)
-
- test_uid = "test"
- try:
- async with storage.write() as write:
- wafc = write.as_foundation_committer()
- delete_outcome = await wafc.keys.test_user_delete_all(test_uid)
- deleted_count = delete_outcome.result_or_raise()
-
- suffix = "s" if deleted_count != 1 else ""
- await quart.flash(
- f"Successfully deleted {deleted_count} OpenPGP key{suffix} and
their associated links for test user.",
- "success",
- )
- except Exception as e:
- log.exception("Error deleting test user keys:")
- await quart.flash(f"Error deleting test user keys: {e!s}", "error")
-
- return await session.redirect(get.keys.keys)
-
-
@admin.get("/delete-committee-keys")
async def delete_committee_keys_get(session: web.Committer) -> str |
web.WerkzeugResponse:
"""Display the form to delete committee keys."""
@@ -445,6 +375,46 @@ async def delete_release_post(session: web.Committer,
delete_form: DeleteRelease
return await session.redirect(delete_release_get)
[email protected]("/delete-test-openpgp-keys")
+async def delete_test_openpgp_keys_get(session: web.Committer) -> web.Response:
+ """Display the form to delete test user OpenPGP keys."""
+ if not config.get().ALLOW_TESTS:
+ raise base.ASFQuartException("Test operations are disabled in this
environment", errorcode=403)
+
+ rendered_form = form.render(
+ model_cls=form.Empty,
+ submit_label="Delete all OpenPGP keys for test user",
+ empty=True,
+ )
+ return web.ElementResponse(rendered_form)
+
+
[email protected]("/delete-test-openpgp-keys")
[email protected]()
+async def delete_test_openpgp_keys_post(session: web.Committer) ->
web.Response:
+ """Delete all test user OpenPGP keys and their links."""
+ if not config.get().ALLOW_TESTS:
+ raise base.ASFQuartException("Test operations are disabled in this
environment", errorcode=403)
+
+ test_uid = "test"
+ try:
+ async with storage.write() as write:
+ wafc = write.as_foundation_committer()
+ delete_outcome = await wafc.keys.test_user_delete_all(test_uid)
+ deleted_count = delete_outcome.result_or_raise()
+
+ suffix = "s" if deleted_count != 1 else ""
+ await quart.flash(
+ f"Successfully deleted {deleted_count} OpenPGP key{suffix} and
their associated links for test user.",
+ "success",
+ )
+ except Exception as e:
+ log.exception("Error deleting test user keys:")
+ await quart.flash(f"Error deleting test user keys: {e!s}", "error")
+
+ return await session.redirect(get.keys.keys)
+
+
@admin.get("/env")
async def env(session: web.Committer) -> web.QuartResponse:
"""Display the environment variables."""
@@ -617,17 +587,6 @@ async def ongoing_tasks_post(
return await _ongoing_tasks(session, project_name, version_name, revision)
-async def _ongoing_tasks(
- session: web.Committer, project_name: str, version_name: str, revision: str
-) -> web.QuartResponse:
- try:
- ongoing = await interaction.tasks_ongoing(project_name, version_name,
revision)
- return web.TextResponse(str(ongoing))
- except Exception:
- log.exception(f"Error fetching ongoing task count for {project_name}
{version_name} rev {revision}:")
- return web.TextResponse("")
-
-
@admin.get("/performance")
async def performance(session: web.Committer) -> str:
"""Display performance statistics for all routes."""
@@ -738,6 +697,24 @@ async def projects_update_post(session: web.Committer) ->
str | web.WerkzeugResp
}, 200
[email protected]("/task-times/<project_name>/<version_name>/<revision_number>")
+async def task_times(
+ session: web.Committer, project_name: str, version_name: str,
revision_number: str
+) -> web.QuartResponse:
+ values = []
+ async with db.session() as data:
+ tasks = await data.task(
+ project_name=project_name, version_name=version_name,
revision_number=revision_number
+ ).all()
+ for task in tasks:
+ if (task.started is None) or (task.completed is None):
+ continue
+ ms_elapsed = (task.completed - task.started).total_seconds() * 1000
+ values.append(f"{task.task_type} {ms_elapsed:.2f}ms")
+
+ return web.TextResponse("\n".join(values))
+
+
@admin.get("/tasks")
async def tasks_(session: web.Committer) -> str:
return await template.render("tasks.html")
@@ -803,24 +780,6 @@ async def tasks_recent(session: web.Committer, minutes:
int) -> str:
return await template.blank(f"Recent Tasks ({minutes}m)",
content=page.collect())
[email protected]("/task-times/<project_name>/<version_name>/<revision_number>")
-async def task_times(
- session: web.Committer, project_name: str, version_name: str,
revision_number: str
-) -> web.QuartResponse:
- values = []
- async with db.session() as data:
- tasks = await data.task(
- project_name=project_name, version_name=version_name,
revision_number=revision_number
- ).all()
- for task in tasks:
- if (task.started is None) or (task.completed is None):
- continue
- ms_elapsed = (task.completed - task.started).total_seconds() * 1000
- values.append(f"{task.task_type} {ms_elapsed:.2f}ms")
-
- return web.TextResponse("\n".join(values))
-
-
@admin.get("/test")
async def test(session: web.Committer) -> web.QuartResponse:
"""Test the storage layer."""
@@ -921,6 +880,53 @@ async def _check_keys(fix: bool = False) -> str:
return message
+async def _data(session: web.Committer, model: str = "Committee") -> str:
+ """Browse all records in the database."""
+ async with db.session() as data:
+ # Map of model names to their classes
+ # TODO: Add distribution channel, key link, and any others
+ model_methods: dict[str, Callable[[], db.Query[Any]]] = {
+ "CheckResult": data.check_result,
+ "CheckResultIgnore": data.check_result_ignore,
+ "Committee": data.committee,
+ "Project": data.project,
+ "PublicSigningKey": data.public_signing_key,
+ "Release": data.release,
+ "ReleasePolicy": data.release_policy,
+ "Revision": data.revision,
+ "SSHKey": data.ssh_key,
+ "Task": data.task,
+ "TextValue": data.text_value,
+ }
+
+ if model not in model_methods:
+ raise base.ASFQuartException(f"Model type '{model}' not found",
404)
+
+ # Get all records for the selected model
+ records = await model_methods[model]().all()
+
+ # Convert records to dictionaries for JSON serialization
+ records_dict = []
+ for record in records:
+ if hasattr(record, "dict"):
+ record_dict = record.dict()
+ else:
+ # Fallback for models without dict() method
+ record_dict = {}
+ # record_dict = {
+ # "id": getattr(record, "id", None),
+ # "name": getattr(record, "name", None),
+ # }
+ for key in record.__dict__:
+ if not key.startswith("_"):
+ record_dict[key] = getattr(record, key)
+ records_dict.append(record_dict)
+
+ return await template.render(
+ "data-browser.html", models=list(model_methods.keys()),
model=model, records=records_dict
+ )
+
+
async def _delete_releases(session: web.Committer, releases_to_delete:
list[str]) -> None:
success_count = 0
fail_count = 0
@@ -1026,6 +1032,43 @@ def _get_user_committees_from_ldap(uid: str, bind_dn:
str, bind_password: str) -
return committees
+def _log_safe_session_data(
+ ldap_data: dict[str, Any],
+ new_uid: str,
+ ldap_projects: apache.LDAPProjectsData,
+ committee_data: apache.CommitteeData,
+ bind_dn: str,
+ bind_password: str,
+) -> dict[str, Any]:
+ # This version excludes "mfa" which CodeQL treats as sensitive
+ # It's just a bool, but we can't suppress the error at source
+ common = _session_data_common(ldap_data, new_uid, ldap_projects,
committee_data, bind_dn, bind_password)
+ return {
+ "uid": common.uid,
+ "dn": None,
+ "fullname": common.fullname,
+ "email": common.email,
+ "isMember": common.is_member,
+ "isChair": common.is_chair,
+ "isRoot": common.is_root,
+ "pmcs": common.pmcs,
+ "projects": common.projects,
+ "isRole": False,
+ "metadata": {},
+ }
+
+
+async def _ongoing_tasks(
+ session: web.Committer, project_name: str, version_name: str, revision: str
+) -> web.QuartResponse:
+ try:
+ ongoing = await interaction.tasks_ongoing(project_name, version_name,
revision)
+ return web.TextResponse(str(ongoing))
+ except Exception:
+ log.exception(f"Error fetching ongoing task count for {project_name}
{version_name} rev {revision}:")
+ return web.TextResponse("")
+
+
def _session_data(
ldap_data: dict[str, Any],
new_uid: str,
@@ -1035,6 +1078,33 @@ def _session_data(
bind_dn: str,
bind_password: str,
) -> dict[str, Any]:
+ common = _session_data_common(ldap_data, new_uid, ldap_projects,
committee_data, bind_dn, bind_password)
+ return {
+ "uid": common.uid,
+ "dn": None,
+ "fullname": common.fullname,
+ "email": common.email,
+ "isMember": common.is_member,
+ "isChair": common.is_chair,
+ "isRoot": common.is_root,
+ # WARNING: ASFQuart session.ClientSession uses "committees"
+ # But this is cookie, not ClientSession, data, and requires "pmcs"
+ "pmcs": common.pmcs,
+ "projects": common.projects,
+ "mfa": current_session.mfa,
+ "isRole": False,
+ "metadata": {},
+ }
+
+
+def _session_data_common(
+ ldap_data: dict[str, Any],
+ new_uid: str,
+ ldap_projects: apache.LDAPProjectsData,
+ committee_data: apache.CommitteeData,
+ bind_dn: str,
+ bind_password: str,
+) -> SessionDataCommon:
# This is not quite accurate
# For example, this misses "tooling" for tooling members
projects = {p.name for p in ldap_projects.projects if (new_uid in
p.members) or (new_uid in p.owners)}
@@ -1046,24 +1116,16 @@ def _session_data(
is_root = False
is_chair = any(new_uid in (user.id for user in c.chair) for c in
committee_data.committees)
- return {
- "uid": ldap_data.get("uid", [new_uid])[0],
- "dn": None,
- "fullname": ldap_data.get("cn", [new_uid])[0],
- # "email": ldap_user.get("mail", [""])[0],
- # Or asf-committer-email?
- "email": f"{new_uid}@apache.org",
- "isMember": is_member,
- "isChair": is_chair,
- "isRoot": is_root,
- # WARNING: ASFQuart session.ClientSession uses "committees"
- # But this is cookie, not ClientSession, data, and requires "pmcs"
- "pmcs": sorted(list(committees)),
- "projects": sorted(list(projects)),
- "mfa": current_session.mfa,
- "isRole": False,
- "metadata": {},
- }
+ return SessionDataCommon(
+ uid=ldap_data.get("uid", [new_uid])[0],
+ fullname=ldap_data.get("cn", [new_uid])[0],
+ email=f"{new_uid}@apache.org",
+ is_member=is_member,
+ is_chair=is_chair,
+ is_root=is_root,
+ pmcs=sorted(list(committees)),
+ projects=sorted(list(projects)),
+ )
async def _update_keys(asf_uid: str) -> int:
diff --git a/atr/util.py b/atr/util.py
index 701560d..0c6ebff 100644
--- a/atr/util.py
+++ b/atr/util.py
@@ -184,9 +184,11 @@ async def async_temporary_directory(
def chmod_directories(path: pathlib.Path, permissions: int = 0o755) -> None:
+ # codeql[py/overly-permissive-file]
os.chmod(path, permissions)
for dir_path in path.rglob("*"):
if dir_path.is_dir():
+ # codeql[py/overly-permissive-file]
os.chmod(dir_path, permissions)
diff --git a/codeql-config.yml b/codeql-config.yml
new file mode 100644
index 0000000..a0cf9fe
--- /dev/null
+++ b/codeql-config.yml
@@ -0,0 +1,5 @@
+paths-ignore:
+ - '**/.venv*'
+ - '**/node_modules'
+ - dev
+ - state
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]