From 46620a748599aa357a85bec7abef5129e829ae86 Mon Sep 17 00:00:00 2001 From: Vallari Agrawal Date: Wed, 24 Apr 2024 19:36:33 +0530 Subject: [PATCH 01/11] services/kill.py: Allow admins to kill any run Signed-off-by: Vallari Agrawal --- .env.dev | 1 + src/teuthology_api/routes/kill.py | 20 +++++++++++--- src/teuthology_api/services/kill.py | 41 ++++++++++++++++++++++------- 3 files changed, 48 insertions(+), 14 deletions(-) diff --git a/.env.dev b/.env.dev index d086a11..e0f1e0c 100644 --- a/.env.dev +++ b/.env.dev @@ -8,6 +8,7 @@ GH_CLIENT_SECRET= GH_AUTHORIZATION_BASE_URL='https://github.com/login/oauth/authorize' GH_TOKEN_URL='https://github.com/login/oauth/access_token' GH_FETCH_MEMBERSHIP_URL='https://api.github.com/user/memberships/orgs/ceph' +ADMIN_TEAM='Ceph' #Session Related Stuff ## SESSION_SECRET_KEY is used to encrypt session data diff --git a/src/teuthology_api/routes/kill.py b/src/teuthology_api/routes/kill.py index 472bde0..40ded0d 100644 --- a/src/teuthology_api/routes/kill.py +++ b/src/teuthology_api/routes/kill.py @@ -1,6 +1,7 @@ import logging -from fastapi import APIRouter, Depends, Request +from fastapi import APIRouter, Depends, Request, HTTPException +from requests.exceptions import HTTPError from teuthology_api.services.kill import run from teuthology_api.services.helpers import get_token @@ -15,7 +16,7 @@ @router.post("/", status_code=200) -def create_run( +async def create_run( request: Request, args: KillArgs, logs: bool = False, @@ -28,5 +29,16 @@ def create_run( or else it will SyntaxError: non-dafault argument follows default argument error. """ - args = args.model_dump(by_alias=True, exclude_unset=True) - return run(args, logs, access_token, request) + try: + args = args.model_dump(by_alias=True, exclude_unset=True) + return await run(args, logs, access_token, request) + except HTTPException: + raise + except HTTPError as http_err: + log.error(http_err) + raise HTTPException( + status_code=http_err.response.status_code, detail=repr(http_err) + ) from http_err + except Exception as err: + log.error(err) + raise HTTPException(status_code=500, detail=repr(err)) from err diff --git a/src/teuthology_api/services/kill.py b/src/teuthology_api/services/kill.py index 4188d23..812a807 100644 --- a/src/teuthology_api/services/kill.py +++ b/src/teuthology_api/services/kill.py @@ -1,6 +1,7 @@ import logging import os import subprocess +import httpx from fastapi import HTTPException, Request @@ -8,10 +9,11 @@ TEUTHOLOGY_PATH = os.getenv("TEUTHOLOGY_PATH") +ADMIN_TEAM = os.getenv("ADMIN_TEAM") log = logging.getLogger(__name__) -def run(args, send_logs: bool, access_token: str, request: Request): +async def run(args, send_logs: bool, access_token: dict, request: Request): """ Kill running teuthology jobs. """ @@ -30,16 +32,19 @@ def run(args, send_logs: bool, access_token: str, request: Request): else: log.error("teuthology-kill is missing --run") raise HTTPException(status_code=400, detail="--run is a required argument") - # TODO if user has admin priviledge, then they can kill any run/job. + if run_owner.lower() != username.lower(): - log.error( - "%s doesn't have permission to kill a job scheduled by: %s", - username, - run_owner, - ) - raise HTTPException( - status_code=401, detail="You don't have permission to kill this run/job" - ) + isUserAdmin = await isAdmin(username, access_token) + if not isUserAdmin: + log.error( + "%s doesn't have permission to kill a job scheduled by: %s", + username, + run_owner, + ) + raise HTTPException( + status_code=401, detail="You don't have permission to kill this run/job" + ) + log.info("Killing with admin privileges") try: kill_cmd = [f"{TEUTHOLOGY_PATH}/virtualenv/bin/teuthology-kill"] for flag, flag_value in args.items(): @@ -61,3 +66,19 @@ def run(args, send_logs: bool, access_token: str, request: Request): except Exception as exc: log.error("teuthology-kill command failed with the error: %s", repr(exc)) raise HTTPException(status_code=500, detail=repr(exc)) from exc + + +async def isAdmin(username, token): + TEAM_MEMBER_URL = ( + f"https://api.github.com/orgs/ceph/teams/{ADMIN_TEAM}/memberships/{username}" + ) + async with httpx.AsyncClient() as client: + headers = { + "Authorization": "token " + token["access_token"], + "Accept": "application/json", + } + response_org = await client.get(url=TEAM_MEMBER_URL, headers=headers) + response_org_dic = dict(response_org.json()) + if response_org_dic.get("state") == "active": + return True + return False From 038acddf2cbf386d45dbc005d256e20897116c45 Mon Sep 17 00:00:00 2001 From: Vallari Agrawal Date: Thu, 25 Apr 2024 15:15:06 +0530 Subject: [PATCH 02/11] services/kill.py: Owner can also be "scheduled_@teuthology" "scheduled_@teuthology" is the default owner name if run is scheduled from teuthology CLI tool. This commit allows users of same github username to recognize it as their jobs. Also fix how run_owner is determined. In teuthology, runs ownership is deteremined by job's "owner" and not run's "user" value. Signed-off-by: Vallari Agrawal --- src/teuthology_api/services/kill.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/teuthology_api/services/kill.py b/src/teuthology_api/services/kill.py index 812a807..70fdddb 100644 --- a/src/teuthology_api/services/kill.py +++ b/src/teuthology_api/services/kill.py @@ -28,12 +28,16 @@ async def run(args, send_logs: bool, access_token: dict, request: Request): run_name = args.get("--run") if run_name: run_details = get_run_details(run_name) - run_owner = run_details.get("user") + jobs_details = run_details.get("jobs", []) + if jobs_details: + run_owner = jobs_details[0].get("owner") else: log.error("teuthology-kill is missing --run") raise HTTPException(status_code=400, detail="--run is a required argument") - if run_owner.lower() != username.lower(): + if (run_owner.lower() != username.lower()) or ( + run_owner.lower() != f"scheduled_{username.lower()}@teuthology" + ): isUserAdmin = await isAdmin(username, access_token) if not isUserAdmin: log.error( From 94591a2fa2e29cc7397e0622d7170d72389127d5 Mon Sep 17 00:00:00 2001 From: Vallari Agrawal Date: Fri, 10 May 2024 15:48:50 +0530 Subject: [PATCH 03/11] login: add 'isUserAdmin' to session data Signed-off-by: Vallari Agrawal --- src/teuthology_api/routes/kill.py | 4 ++-- src/teuthology_api/routes/login.py | 5 +++++ src/teuthology_api/services/helpers.py | 19 +++++++++++++++++++ src/teuthology_api/services/kill.py | 26 +++++--------------------- 4 files changed, 31 insertions(+), 23 deletions(-) diff --git a/src/teuthology_api/routes/kill.py b/src/teuthology_api/routes/kill.py index 40ded0d..7ad7a4a 100644 --- a/src/teuthology_api/routes/kill.py +++ b/src/teuthology_api/routes/kill.py @@ -20,7 +20,7 @@ async def create_run( request: Request, args: KillArgs, logs: bool = False, - access_token: str = Depends(get_token), + token: str = Depends(get_token), ): """ POST route for killing a run or a job. @@ -31,7 +31,7 @@ async def create_run( """ try: args = args.model_dump(by_alias=True, exclude_unset=True) - return await run(args, logs, access_token, request) + return await run(args, logs, token, request) except HTTPException: raise except HTTPError as http_err: diff --git a/src/teuthology_api/routes/login.py b/src/teuthology_api/routes/login.py index c4916a2..dab99d2 100644 --- a/src/teuthology_api/routes/login.py +++ b/src/teuthology_api/routes/login.py @@ -5,6 +5,8 @@ from dotenv import load_dotenv import httpx +from teuthology_api.services.helpers import isAdmin + load_dotenv() GH_CLIENT_ID = os.getenv("GH_CLIENT_ID") @@ -85,9 +87,12 @@ async def handle_callback(code: str, request: Request): "access_token": token, } request.session["user"] = data + isUserAdmin = await isAdmin(data["username"], data["access_token"]) + data["isUserAdmin"] = isUserAdmin cookie_data = { "username": data["username"], "avatar_url": response_org_dic.get("user", {}).get("avatar_url"), + "isUserAdmin": isUserAdmin, } cookie = "; ".join( [f"{str(key)}={str(value)}" for key, value in cookie_data.items()] diff --git a/src/teuthology_api/services/helpers.py b/src/teuthology_api/services/helpers.py index a86d22c..6b49f9f 100644 --- a/src/teuthology_api/services/helpers.py +++ b/src/teuthology_api/services/helpers.py @@ -2,6 +2,7 @@ import logging import os import uuid +import httpx from pathlib import Path from fastapi import HTTPException, Request @@ -15,6 +16,8 @@ PADDLES_URL = os.getenv("PADDLES_URL") ARCHIVE_DIR = os.getenv("ARCHIVE_DIR") +TEUTHOLOGY_PATH = os.getenv("TEUTHOLOGY_PATH") +ADMIN_TEAM = os.getenv("ADMIN_TEAM") log = logging.getLogger(__name__) @@ -96,3 +99,19 @@ def get_token(request: Request): detail="You need to be logged in", headers={"WWW-Authenticate": "Bearer"}, ) + + +async def isAdmin(username, token): + TEAM_MEMBER_URL = ( + f"https://api.github.com/orgs/ceph/teams/{ADMIN_TEAM}/memberships/{username}" + ) + async with httpx.AsyncClient() as client: + headers = { + "Authorization": "token " + token, + "Accept": "application/json", + } + response_org = await client.get(url=TEAM_MEMBER_URL, headers=headers) + response_org_dic = dict(response_org.json()) + if response_org_dic.get("state") == "active": + return True + return False diff --git a/src/teuthology_api/services/kill.py b/src/teuthology_api/services/kill.py index 70fdddb..165e031 100644 --- a/src/teuthology_api/services/kill.py +++ b/src/teuthology_api/services/kill.py @@ -1,23 +1,23 @@ import logging import os import subprocess -import httpx from fastapi import HTTPException, Request -from teuthology_api.services.helpers import get_username, get_run_details +from teuthology_api.services.helpers import get_username, get_run_details, isAdmin TEUTHOLOGY_PATH = os.getenv("TEUTHOLOGY_PATH") ADMIN_TEAM = os.getenv("ADMIN_TEAM") + log = logging.getLogger(__name__) -async def run(args, send_logs: bool, access_token: dict, request: Request): +async def run(args, send_logs: bool, token: dict, request: Request): """ Kill running teuthology jobs. """ - if not access_token: + if not token: log.error("access_token empty, user probably is not logged in.") raise HTTPException( status_code=401, @@ -38,7 +38,7 @@ async def run(args, send_logs: bool, access_token: dict, request: Request): if (run_owner.lower() != username.lower()) or ( run_owner.lower() != f"scheduled_{username.lower()}@teuthology" ): - isUserAdmin = await isAdmin(username, access_token) + isUserAdmin = await isAdmin(username, token["access_token"]) if not isUserAdmin: log.error( "%s doesn't have permission to kill a job scheduled by: %s", @@ -70,19 +70,3 @@ async def run(args, send_logs: bool, access_token: dict, request: Request): except Exception as exc: log.error("teuthology-kill command failed with the error: %s", repr(exc)) raise HTTPException(status_code=500, detail=repr(exc)) from exc - - -async def isAdmin(username, token): - TEAM_MEMBER_URL = ( - f"https://api.github.com/orgs/ceph/teams/{ADMIN_TEAM}/memberships/{username}" - ) - async with httpx.AsyncClient() as client: - headers = { - "Authorization": "token " + token["access_token"], - "Accept": "application/json", - } - response_org = await client.get(url=TEAM_MEMBER_URL, headers=headers) - response_org_dic = dict(response_org.json()) - if response_org_dic.get("state") == "active": - return True - return False From cca4ff5ffdbce14418229283a0914764aa0d2674 Mon Sep 17 00:00:00 2001 From: Vallari Agrawal Date: Fri, 10 May 2024 17:42:40 +0530 Subject: [PATCH 04/11] kill: add tests + fix run owner logic Signed-off-by: Vallari Agrawal --- .env.dev | 1 + src/teuthology_api/services/helpers.py | 17 ++++---- src/teuthology_api/services/kill.py | 8 ++-- tests/test_kill.py | 55 +++++++++++++++++++++++--- 4 files changed, 64 insertions(+), 17 deletions(-) diff --git a/.env.dev b/.env.dev index e0f1e0c..7de46d4 100644 --- a/.env.dev +++ b/.env.dev @@ -8,6 +8,7 @@ GH_CLIENT_SECRET= GH_AUTHORIZATION_BASE_URL='https://github.com/login/oauth/authorize' GH_TOKEN_URL='https://github.com/login/oauth/access_token' GH_FETCH_MEMBERSHIP_URL='https://api.github.com/user/memberships/orgs/ceph' +GH_ORG_TEAM_URL='https://api.github.com/orgs/ceph/teams' ADMIN_TEAM='Ceph' #Session Related Stuff diff --git a/src/teuthology_api/services/helpers.py b/src/teuthology_api/services/helpers.py index 6b49f9f..da2cfe3 100644 --- a/src/teuthology_api/services/helpers.py +++ b/src/teuthology_api/services/helpers.py @@ -16,8 +16,10 @@ PADDLES_URL = os.getenv("PADDLES_URL") ARCHIVE_DIR = os.getenv("ARCHIVE_DIR") -TEUTHOLOGY_PATH = os.getenv("TEUTHOLOGY_PATH") -ADMIN_TEAM = os.getenv("ADMIN_TEAM") +TEUTHOLOGY_PATH = os.getenv("TEUTHOLOGY_PATH") + +ADMIN_TEAM = os.getenv("ADMIN_TEAM") +GH_ORG_TEAM_URL = os.getenv("GH_ORG_TEAM_URL") log = logging.getLogger(__name__) @@ -102,16 +104,15 @@ def get_token(request: Request): async def isAdmin(username, token): - TEAM_MEMBER_URL = ( - f"https://api.github.com/orgs/ceph/teams/{ADMIN_TEAM}/memberships/{username}" - ) + TEAM_MEMBER_URL = f"{GH_ORG_TEAM_URL}/{ADMIN_TEAM}/memberships/{username}" async with httpx.AsyncClient() as client: headers = { "Authorization": "token " + token, "Accept": "application/json", } response_org = await client.get(url=TEAM_MEMBER_URL, headers=headers) - response_org_dic = dict(response_org.json()) - if response_org_dic.get("state") == "active": - return True + if response_org: + response_org_dict = dict(response_org.json()) + if response_org_dict.get("state", "") == "active": + return True return False diff --git a/src/teuthology_api/services/kill.py b/src/teuthology_api/services/kill.py index 165e031..0cfa46e 100644 --- a/src/teuthology_api/services/kill.py +++ b/src/teuthology_api/services/kill.py @@ -8,7 +8,7 @@ TEUTHOLOGY_PATH = os.getenv("TEUTHOLOGY_PATH") -ADMIN_TEAM = os.getenv("ADMIN_TEAM") +ADMIN_TEAM = os.getenv("ADMIN_TEAM") log = logging.getLogger(__name__) @@ -25,17 +25,17 @@ async def run(args, send_logs: bool, token: dict, request: Request): headers={"WWW-Authenticate": "Bearer"}, ) username = get_username(request) - run_name = args.get("--run") + run_name = args.get("--run", "") if run_name: run_details = get_run_details(run_name) jobs_details = run_details.get("jobs", []) if jobs_details: - run_owner = jobs_details[0].get("owner") + run_owner = jobs_details[0].get("owner", "") else: log.error("teuthology-kill is missing --run") raise HTTPException(status_code=400, detail="--run is a required argument") - if (run_owner.lower() != username.lower()) or ( + if (run_owner.lower() != username.lower()) and ( run_owner.lower() != f"scheduled_{username.lower()}@teuthology" ): isUserAdmin = await isAdmin(username, token["access_token"]) diff --git a/tests/test_kill.py b/tests/test_kill.py index 7a30953..71ad49a 100644 --- a/tests/test_kill.py +++ b/tests/test_kill.py @@ -10,7 +10,7 @@ async def override_get_token(): - return {"access_token": "token_123", "token_type": "bearer"} + return {"access_token": "token_123", "token_type": "bearer", "isUserAdmin": False} app.dependency_overrides[get_token] = override_get_token @@ -30,16 +30,22 @@ async def override_get_token(): } +@patch("teuthology_api.services.kill.isAdmin") @patch("subprocess.Popen") @patch("teuthology_api.services.kill.get_run_details") @patch("teuthology_api.services.kill.get_username") -def test_kill_run_success(m_get_username, m_get_run_details, m_popen): +def test_kill_run_success(m_get_username, m_get_run_details, m_popen, m_isAdmin): m_get_username.return_value = "user1" - m_get_run_details.return_value = {"id": "7451978", "user": "user1"} + m_isAdmin.return_value = False + m_get_run_details.return_value = { + "id": "7451978", + "user": "user1", + "jobs": [{"owner": "user1"}], + } mock_process = m_popen.return_value - mock_process.communicate.return_value = ("logs", "") + mock_process.communicate.return_value = (b"logs", "") mock_process.wait.return_value = 0 - response = client.post("/kill", data=json.dumps(mock_kill_args)) + response = client.post("kill/", data=json.dumps(mock_kill_args)) assert response.status_code == 200 assert response.json() == {"kill": "success"} @@ -48,3 +54,42 @@ def test_kill_run_fail(): response = client.post("/kill", data=json.dumps(mock_kill_args)) assert response.status_code == 401 assert response.json() == {"detail": "You need to be logged in"} + + +@patch("teuthology_api.services.kill.isAdmin") +@patch("subprocess.Popen") +@patch("teuthology_api.services.kill.get_run_details") +@patch("teuthology_api.services.kill.get_username") +def test_admin_kill_run_success(m_get_username, m_get_run_details, m_popen, m_isAdmin): + m_get_username.return_value = "user1" + m_isAdmin.return_value = True + m_get_run_details.return_value = { + "id": "7451978", + "user": "user1", + "jobs": [{"owner": "someone_else"}], + } + mock_process = m_popen.return_value + mock_process.communicate.return_value = (b"logs", "") + mock_process.wait.return_value = 0 + response = client.post("kill/", data=json.dumps(mock_kill_args)) + assert response.status_code == 200 + assert response.json() == {"kill": "success"} + + +@patch("teuthology_api.services.kill.isAdmin") +@patch("subprocess.Popen") +@patch("teuthology_api.services.kill.get_run_details") +@patch("teuthology_api.services.kill.get_username") +def test_non_admin_kill_run_fail(m_get_username, m_get_run_details, m_popen, m_isAdmin): + m_get_username.return_value = "user1" + m_isAdmin.return_value = False + m_get_run_details.return_value = { + "id": "7451978", + "user": "user1", + "jobs": [{"owner": "someone_else"}], + } + mock_process = m_popen.return_value + mock_process.communicate.return_value = (b"logs", "") + mock_process.wait.return_value = 0 + response = client.post("kill/", data=json.dumps(mock_kill_args)) + assert response.status_code == 401 # run doesn't belong to user + user is not admin From 5f579154142bbcea4abd291301bb23254a48d9dd Mon Sep 17 00:00:00 2001 From: Vallari Agrawal Date: Tue, 11 Jun 2024 17:40:49 +0530 Subject: [PATCH 05/11] Improve error handling And decode kill-cmd output logs. Signed-off-by: Vallari Agrawal --- src/teuthology_api/routes/kill.py | 4 ++-- src/teuthology_api/services/helpers.py | 4 ++-- src/teuthology_api/services/kill.py | 7 ++++--- src/teuthology_api/services/suite.py | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/teuthology_api/routes/kill.py b/src/teuthology_api/routes/kill.py index 7ad7a4a..4e2c364 100644 --- a/src/teuthology_api/routes/kill.py +++ b/src/teuthology_api/routes/kill.py @@ -37,8 +37,8 @@ async def create_run( except HTTPError as http_err: log.error(http_err) raise HTTPException( - status_code=http_err.response.status_code, detail=repr(http_err) + status_code=http_err.response.status_code, detail=str(http_err) ) from http_err except Exception as err: log.error(err) - raise HTTPException(status_code=500, detail=repr(err)) from err + raise HTTPException(status_code=500, detail=str(err)) from err diff --git a/src/teuthology_api/services/helpers.py b/src/teuthology_api/services/helpers.py index da2cfe3..a7e1d6e 100644 --- a/src/teuthology_api/services/helpers.py +++ b/src/teuthology_api/services/helpers.py @@ -66,11 +66,11 @@ def get_run_details(run_name: str): except HTTPError as http_err: log.error(http_err) raise HTTPException( - status_code=http_err.response.status_code, detail=repr(http_err) + status_code=http_err.response.status_code, detail=str(http_err) ) from http_err except Exception as err: log.error(err) - raise HTTPException(status_code=500, detail=repr(err)) from err + raise HTTPException(status_code=500, detail=str(err)) from err def get_username(request: Request): diff --git a/src/teuthology_api/services/kill.py b/src/teuthology_api/services/kill.py index 0cfa46e..099436e 100644 --- a/src/teuthology_api/services/kill.py +++ b/src/teuthology_api/services/kill.py @@ -61,12 +61,13 @@ async def run(args, send_logs: bool, token: dict, request: Request): ) stdout, stderr = proc.communicate() returncode = proc.wait(timeout=120) - log.info(stdout) + output_logs = stdout.decode() + log.info(output_logs) if returncode != 0: - raise Exception(stdout) + raise Exception(output_logs) if send_logs: return {"kill": "success", "logs": stdout} return {"kill": "success"} except Exception as exc: log.error("teuthology-kill command failed with the error: %s", repr(exc)) - raise HTTPException(status_code=500, detail=repr(exc)) from exc + raise HTTPException(status_code=500, detail=str(exc)) from exc diff --git a/src/teuthology_api/services/suite.py b/src/teuthology_api/services/suite.py index 99d2d6d..956f344 100644 --- a/src/teuthology_api/services/suite.py +++ b/src/teuthology_api/services/suite.py @@ -43,7 +43,7 @@ def run(args, send_logs: bool, access_token: str): return {"run": run_details} except Exception as exc: log.error("teuthology.suite.main failed with the error: %s", repr(exc)) - raise HTTPException(status_code=500, detail=repr(exc)) from exc + raise HTTPException(status_code=500, detail=str(exc)) from exc def make_run_name(run_dic): From cf89ac97797d7f5301c146cd41ff225de90fb407 Mon Sep 17 00:00:00 2001 From: Vallari Agrawal Date: Mon, 8 Jul 2024 14:13:12 +0530 Subject: [PATCH 06/11] services/kill.py: optionally add boolean value flag in kill cmd For example, value of "--preserve-queues" is boolean in /kill request schema. If it's true, add it to the kill cmd, otherwise don't add the flag. Signed-off-by: Vallari Agrawal --- src/teuthology_api/services/kill.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/teuthology_api/services/kill.py b/src/teuthology_api/services/kill.py index 099436e..6477901 100644 --- a/src/teuthology_api/services/kill.py +++ b/src/teuthology_api/services/kill.py @@ -52,9 +52,11 @@ async def run(args, send_logs: bool, token: dict, request: Request): try: kill_cmd = [f"{TEUTHOLOGY_PATH}/virtualenv/bin/teuthology-kill"] for flag, flag_value in args.items(): - if isinstance(flag_value, bool): - flag_value = int(flag_value) - kill_cmd += [flag, str(flag_value)] + if isinstance(flag_value, bool): # check for --preserve-queues + if flag_value == True: + kill_cmd += [flag] + else: + kill_cmd += [flag, str(flag_value)] log.info(kill_cmd) proc = subprocess.Popen( kill_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT From c403f4620e86d95c6ab2aa03ee8fddd2c7ed175a Mon Sep 17 00:00:00 2001 From: Vallari Agrawal Date: Wed, 21 Aug 2024 23:39:11 +0530 Subject: [PATCH 07/11] services/helpers.py: handle empty GH_ORG_TEAM_URL and ADMIN_TEAM Signed-off-by: Vallari Agrawal --- src/teuthology_api/services/helpers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/teuthology_api/services/helpers.py b/src/teuthology_api/services/helpers.py index a7e1d6e..c4bcd33 100644 --- a/src/teuthology_api/services/helpers.py +++ b/src/teuthology_api/services/helpers.py @@ -104,6 +104,9 @@ def get_token(request: Request): async def isAdmin(username, token): + if not (GH_ORG_TEAM_URL and ADMIN_TEAM): + log.error("GH_ORG_TEAM_URL or ADMIN_TEAM is not set in .env") + return False TEAM_MEMBER_URL = f"{GH_ORG_TEAM_URL}/{ADMIN_TEAM}/memberships/{username}" async with httpx.AsyncClient() as client: headers = { From de3daf738f829a99966b6abbea4c632bd2bd41ec Mon Sep 17 00:00:00 2001 From: Vallari Agrawal Date: Fri, 30 Aug 2024 14:09:41 +0530 Subject: [PATCH 08/11] main.py: add CORSMiddleware for non-dev env Signed-off-by: Vallari Agrawal --- src/teuthology_api/main.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/teuthology_api/main.py b/src/teuthology_api/main.py index c1d8afb..1f4e200 100644 --- a/src/teuthology_api/main.py +++ b/src/teuthology_api/main.py @@ -34,6 +34,14 @@ def read_root(request: Request): allow_methods=["*"], allow_headers=["*"], ) +else: + app.add_middleware( + CORSMiddleware, + allow_origins=[PULPITO_URL, PADDLES_URL], + allow_credentials=True, + allow_methods=["GET", "POST", "OPTIONS"], + allow_headers=["Cookie"], + ) app.add_middleware(SessionMiddleware, secret_key=SESSION_SECRET_KEY) app.include_router(suite.router) From 20a8fc94fc5854610edb265f60d4e5a126f81c8c Mon Sep 17 00:00:00 2001 From: Vallari Agrawal Date: Sat, 14 Sep 2024 12:37:27 +0530 Subject: [PATCH 09/11] Send user avatar in session data Signed-off-by: Vallari Agrawal --- src/teuthology_api/routes/login.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/teuthology_api/routes/login.py b/src/teuthology_api/routes/login.py index dab99d2..e318882 100644 --- a/src/teuthology_api/routes/login.py +++ b/src/teuthology_api/routes/login.py @@ -82,6 +82,7 @@ async def handle_callback(code: str, request: Request): data = { "id": response_org_dic.get("user", {}).get("id"), "username": response_org_dic.get("user", {}).get("login"), + "avatar_url": response_org_dic.get("user", {}).get("avatar_url"), "state": response_org_dic.get("state"), "role": response_org_dic.get("role"), "access_token": token, From 8f8ea00e1376d280f4dc8485f6da766f74d68468 Mon Sep 17 00:00:00 2001 From: Vallari Agrawal Date: Mon, 7 Oct 2024 19:14:31 +0530 Subject: [PATCH 10/11] Handle missing token in isAdmin() func Signed-off-by: Vallari Agrawal --- src/teuthology_api/services/helpers.py | 6 ++++++ src/teuthology_api/services/kill.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/teuthology_api/services/helpers.py b/src/teuthology_api/services/helpers.py index c4bcd33..6e4ebeb 100644 --- a/src/teuthology_api/services/helpers.py +++ b/src/teuthology_api/services/helpers.py @@ -107,6 +107,12 @@ async def isAdmin(username, token): if not (GH_ORG_TEAM_URL and ADMIN_TEAM): log.error("GH_ORG_TEAM_URL or ADMIN_TEAM is not set in .env") return False + if not (token and username): + raise HTTPException( + status_code=401, + detail="You are probably not logged in (username or token missing)", + headers={"WWW-Authenticate": "Bearer"}, + ) TEAM_MEMBER_URL = f"{GH_ORG_TEAM_URL}/{ADMIN_TEAM}/memberships/{username}" async with httpx.AsyncClient() as client: headers = { diff --git a/src/teuthology_api/services/kill.py b/src/teuthology_api/services/kill.py index 6477901..5252ac0 100644 --- a/src/teuthology_api/services/kill.py +++ b/src/teuthology_api/services/kill.py @@ -38,7 +38,7 @@ async def run(args, send_logs: bool, token: dict, request: Request): if (run_owner.lower() != username.lower()) and ( run_owner.lower() != f"scheduled_{username.lower()}@teuthology" ): - isUserAdmin = await isAdmin(username, token["access_token"]) + isUserAdmin = await isAdmin(username, token.get("access_token")) if not isUserAdmin: log.error( "%s doesn't have permission to kill a job scheduled by: %s", From eb621704650415e01df13cfc61fd3a611529abf4 Mon Sep 17 00:00:00 2001 From: Vallari Agrawal Date: Tue, 26 Nov 2024 09:32:56 +0530 Subject: [PATCH 11/11] routes/kill.py: log error for HTTPException Errors are logged for HTTPError and Exception, also log them for HTTPException before raising the exception. Signed-off-by: Vallari Agrawal --- src/teuthology_api/routes/kill.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/teuthology_api/routes/kill.py b/src/teuthology_api/routes/kill.py index 4e2c364..ba35f4f 100644 --- a/src/teuthology_api/routes/kill.py +++ b/src/teuthology_api/routes/kill.py @@ -32,7 +32,8 @@ async def create_run( try: args = args.model_dump(by_alias=True, exclude_unset=True) return await run(args, logs, token, request) - except HTTPException: + except HTTPException as http_exp: + log.error(http_exp) raise except HTTPError as http_err: log.error(http_err)