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 2ebcac9  Add a form at the bottom of the sidebar to toggle admin status
2ebcac9 is described below

commit 2ebcac90095272cd24fa850ff8df8b9e4987d3bc
Author: Sean B. Palmer <[email protected]>
AuthorDate: Thu Apr 10 19:57:45 2025 +0100

    Add a form at the bottom of the sidebar to toggle admin status
---
 atr/blueprints/admin/admin.py       | 26 ++++++++++++++++++++++++++
 atr/routes/__init__.py              |  4 ----
 atr/routes/draft.py                 |  1 -
 atr/server.py                       |  1 +
 atr/templates/draft-tools.html      |  2 +-
 atr/templates/includes/sidebar.html | 25 +++++++++++++++++++++----
 atr/util.py                         | 22 ++++++++++++++++++++++
 7 files changed, 71 insertions(+), 10 deletions(-)

diff --git a/atr/blueprints/admin/admin.py b/atr/blueprints/admin/admin.py
index 7545dfd..759e642 100644
--- a/atr/blueprints/admin/admin.py
+++ b/atr/blueprints/admin/admin.py
@@ -449,6 +449,32 @@ async def admin_test_kv() -> str:
 """
 
 
[email protected]("/toggle-admin-view", methods=["POST"])
+async def admin_toggle_view() -> response.Response:
+    web_session = await session.read()
+    if web_session is None:
+        # For the type checker
+        # We should pass this as an argument, then it's guaranteed
+        raise base.ASFQuartException("Not authenticated", 401)
+    user_uid = web_session.uid
+    if user_uid is None:
+        raise base.ASFQuartException("Invalid session, uid is None", 500)
+
+    app = asfquart.APP
+    if not hasattr(app, "app_id") or not isinstance(app.app_id, str):
+        raise TypeError("Internal error: APP has no valid app_id")
+
+    cookie_id = app.app_id
+    session_dict = quart.session.get(cookie_id, {})
+    downgrade = not session_dict.get("downgrade_admin_to_user", False)
+    session_dict["downgrade_admin_to_user"] = downgrade
+
+    message = "Viewing as regular user" if downgrade else "Viewing as admin"
+    await quart.flash(message, "success")
+    referrer = quart.request.referrer
+    return quart.redirect(referrer or quart.url_for("admin.admin_data"))
+
+
 @admin.BLUEPRINT.route("/releases")
 async def admin_releases() -> str:
     """Display a list of all releases across all stages and phases."""
diff --git a/atr/routes/__init__.py b/atr/routes/__init__.py
index 87225d2..872a5ca 100644
--- a/atr/routes/__init__.py
+++ b/atr/routes/__init__.py
@@ -417,10 +417,6 @@ class CommitterSession:
     # async def user_committees(self) -> list[models.Committee]:
     #     return ...
 
-    @property
-    def user_is_admin(self) -> bool:
-        return user.is_admin(self.uid)
-
     @property
     async def user_projects(self) -> list[models.Project]:
         if self._projects is None:
diff --git a/atr/routes/draft.py b/atr/routes/draft.py
index d7cfa47..ffebf23 100644
--- a/atr/routes/draft.py
+++ b/atr/routes/draft.py
@@ -850,7 +850,6 @@ async def tools(session: routes.CommitterSession, 
project_name: str, version_nam
         file_data=file_data,
         release=release,
         format_file_size=routes.format_file_size,
-        user_is_admin=session.user_is_admin,
     )
 
 
diff --git a/atr/server.py b/atr/server.py
index 141408e..a87e45b 100644
--- a/atr/server.py
+++ b/atr/server.py
@@ -144,6 +144,7 @@ def app_setup_context(app: base.QuartApp) -> None:
             "commit": metadata.commit,
             "current_user": await asfquart.session.read(),
             "is_admin_fn": user.is_admin,
+            "is_viewing_as_admin_fn": util.is_user_viewing_as_admin,
             "is_committee_member_fn": user.is_committee_member,
             "routes": modules,
             "version": metadata.version,
diff --git a/atr/templates/draft-tools.html b/atr/templates/draft-tools.html
index ccb85de..31ebb80 100644
--- a/atr/templates/draft-tools.html
+++ b/atr/templates/draft-tools.html
@@ -46,7 +46,7 @@
     </form>
   </div>
 
-  {% if file_path.endswith(".tar.gz") and user_is_admin %}
+  {% if file_path.endswith(".tar.gz") and 
is_viewing_as_admin_fn(current_user.uid) %}
     <h3>Generate SBOM</h3>
     <p>Generate a CycloneDX Software Bill of Materials (SBOM) file for this 
artifact.</p>
     <form method="post"
diff --git a/atr/templates/includes/sidebar.html 
b/atr/templates/includes/sidebar.html
index 34ef7ca..b6e174c 100644
--- a/atr/templates/includes/sidebar.html
+++ b/atr/templates/includes/sidebar.html
@@ -15,10 +15,11 @@
       <div class="user-info">
         <span>{{ current_user.fullname }}</span>
         (<code>{{ current_user.uid }}</code>)
+        <br />
+        <a href="#"
+           onclick="location.href='/auth?logout=/';"
+           class="logout-link"><i class="fa-solid 
fa-right-from-bracket"></i></a>
       </div>
-      <a href="#"
-         onclick="location.href='/auth?logout=/';"
-         class="logout-link"><i class="fa-solid fa-right-from-bracket"></i></a>
     {% else %}
 
       <a href="#"
@@ -102,7 +103,7 @@
         </li>
       </ul>
 
-      {% if is_admin_fn(current_user.uid) %}
+      {% if is_viewing_as_admin_fn(current_user.uid) %}
         <h3>Administration</h3>
         <ul>
           <li>
@@ -138,5 +139,21 @@
         </ul>
       {% endif %}
     {% endif %}
+
+    {% if current_user and is_admin_fn(current_user.uid) %}
+      <h3>Admin actions</h3>
+      <form action="{{ url_for('admin.admin_toggle_view') }}"
+            method="post"
+            class="ms-2 mb-4">
+        <button type="submit" class="btn btn-sm btn-outline-secondary">
+          {% if not is_viewing_as_admin_fn(current_user.uid) %}
+            <i class="fa-solid fa-user-shield"></i> View as admin
+          {% else %}
+            <i class="fa-solid fa-user-ninja"></i> View as user
+          {% endif %}
+        </button>
+      </form>
+    {% endif %}
+
   </nav>
 </aside>
diff --git a/atr/util.py b/atr/util.py
index 1c87eca..117d6fe 100644
--- a/atr/util.py
+++ b/atr/util.py
@@ -32,6 +32,7 @@ from collections.abc import AsyncGenerator, Callable, 
Mapping, Sequence
 from typing import Annotated, Any, TypeVar
 
 import aiofiles.os
+import asfquart
 import asfquart.base as base
 import asfquart.session as session
 import pydantic
@@ -44,6 +45,7 @@ import quart_wtf.typing
 # Therefore, this module must not import atr.db
 import atr.config as config
 import atr.db.models as models
+import atr.user as user
 
 F = TypeVar("F", bound="QuartFormTyped")
 T = TypeVar("T")
@@ -241,6 +243,26 @@ def get_release_dir() -> pathlib.Path:
     return pathlib.Path(config.get().PHASE_STORAGE_DIR) / "release"
 
 
+def is_user_viewing_as_admin(uid: str | None) -> bool:
+    """Check whether a user is currently viewing the site with active admin 
privileges."""
+    if not user.is_admin(uid):
+        return False
+
+    try:
+        app = asfquart.APP
+        if not hasattr(app, "app_id") or not isinstance(app.app_id, str):
+            _LOGGER.error("Cannot get valid app_id to read session for admin 
view check")
+            return True
+
+        cookie_id = app.app_id
+        session_dict = quart.session.get(cookie_id, {})
+        is_downgraded = session_dict.get("downgrade_admin_to_user", False)
+        return not is_downgraded
+    except Exception:
+        _LOGGER.exception(f"Error checking admin downgrade session status for 
{uid}")
+        return True
+
+
 async def paths_recursive(base_path: pathlib.Path, sort: bool = True) -> 
list[pathlib.Path]:
     """List all paths recursively in alphabetical order from a given base 
path."""
     paths: list[pathlib.Path] = []


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to