From 2a94b6f7127acfc97dcc9b1f5506f6e3d626cd26 Mon Sep 17 00:00:00 2001 From: npalaska Date: Tue, 14 Feb 2023 16:01:39 -0500 Subject: [PATCH] refactor server client and oidc_admin --- lib/pbench/client/__init__.py | 49 ++++++++++--------- lib/pbench/client/oidc_admin.py | 5 +- lib/pbench/test/functional/server/conftest.py | 38 ++++++-------- server/pbenchinacan/load_keycloak.sh | 8 +-- 4 files changed, 48 insertions(+), 52 deletions(-) diff --git a/lib/pbench/client/__init__.py b/lib/pbench/client/__init__.py index 7d4ca3c82d..b40b8ef4dd 100644 --- a/lib/pbench/client/__init__.py +++ b/lib/pbench/client/__init__.py @@ -8,7 +8,8 @@ import requests from requests.structures import CaseInsensitiveDict -from pbench.client.types import Dataset, JSONMap, JSONOBJECT +from pbench.client.oidc_admin import OIDCAdmin +from pbench.client.types import Dataset, JSONOBJECT class PbenchClientError(Exception): @@ -86,6 +87,7 @@ def __init__(self, host: str): self.auth_token: Optional[str] = None self.session: Optional[requests.Session] = None self.endpoints: Optional[JSONOBJECT] = None + self.oidc_admin: Optional[OIDCAdmin] = None def _headers( self, user_headers: Optional[dict[str, str]] = None @@ -302,8 +304,13 @@ def delete( return response def connect(self, headers: Optional[dict[str, str]] = None) -> None: - """Connect to the Pbench Server host using the endpoints API to be sure - that it responds, and cache the endpoints response payload. + """Performs some pre-requisite actions to make server client usable. + + 1. Connect to the Pbench Server host using the endpoints API to be + sure that it responds, and cache the endpoints response payload. + + 2. Creates an OIDCAdmin object that server client can use to + perform admin privileged actions on OIDC server. This also allows the client to add default HTTP headers to the session which will be used for all operations unless overridden for specific @@ -321,31 +328,27 @@ def connect(self, headers: Optional[dict[str, str]] = None) -> None: self.endpoints = response.json() assert self.endpoints - def login(self, user: str, password: str) -> JSONMap: - """Login to a specified username with the password, and store the - resulting authentication token. + # Create an OIDCAdmin object and confirm the connection was successful + self.oidc_admin = OIDCAdmin(server_url=self.endpoints["openid"]["server"]) + assert self.oidc_admin.get_admin_token()["access_token"] + + def login(self, user: str, password: str): + """Login to a specified username with the password on OIDC server, + and store the resulting authentication token. Args: user: Account username password: Account password - - Returns: - The login response - """ - response = self.post(API.LOGIN, json={"username": user, "password": password}) - response.raise_for_status() - json = response.json() - self.username = json["username"] - self.auth_token = json["auth_token"] - return JSONMap(json) - - def logout(self) -> None: - """Logout the currently authenticated user and remove the - authentication token. """ - self.post(API.LOGOUT) - self.username = None - self.auth_token = None + response = self.oidc_admin.user_login( + client_id=self.endpoints["openid"]["client"], + username=user, + password=password, + ) + assert response["access_token"] + auth_token = response["access_token"] + self.username = user + self.auth_token = auth_token def upload(self, tarball: Path, **kwargs) -> requests.Response: """Upload a tarball to the server. diff --git a/lib/pbench/client/oidc_admin.py b/lib/pbench/client/oidc_admin.py index 84e68b5004..23ce4761de 100644 --- a/lib/pbench/client/oidc_admin.py +++ b/lib/pbench/client/oidc_admin.py @@ -8,7 +8,7 @@ class OIDCAdmin(Connection): - OIDC_REALM = "pbench-server" + OIDC_REALM = os.getenv("OIDC_REALM", "pbench-server") ADMIN_USERNAME = os.getenv("ADMIN_USERNAME", "admin") ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD", "admin") @@ -111,8 +111,7 @@ def user_login(self, client_id: str, username: str, password: str) -> dict: "username": username, "password": password, } - response = self.post(path=url_path, data=data, headers=headers) - return response.json() + return self.post(path=url_path, data=data, headers=headers).json() def get_user(self, username: str, token: str) -> dict: """Get the OIDC user representation dict. diff --git a/lib/pbench/test/functional/server/conftest.py b/lib/pbench/test/functional/server/conftest.py index 848bf661c8..0a61d83757 100644 --- a/lib/pbench/test/functional/server/conftest.py +++ b/lib/pbench/test/functional/server/conftest.py @@ -5,7 +5,12 @@ from pbench.client import PbenchServerClient from pbench.client.oidc_admin import OIDCAdmin -from pbench.client.types import JSONMap + +USERNAME: str = "tester" +EMAIL: str = "tester@gmail.com" +PASSWORD: str = "123456" +FIRST_NAME: str = "Test" +LAST_NAME: str = "User" @pytest.fixture(scope="module") @@ -32,20 +37,18 @@ def oidc_admin(server_client: PbenchServerClient): Used by Pbench Server functional tests to get admin access on OIDC server. """ - oidc_endpoints = server_client.endpoints["openid"] - oidc_server = OIDCAdmin(server_url=oidc_endpoints["server"]) - return oidc_server + return OIDCAdmin(server_url=server_client.endpoints["openid"]["server"]) @pytest.fixture(scope="module") def register_test_user(oidc_admin: OIDCAdmin): """Create a test user for functional tests.""" response = oidc_admin.create_new_user( - username="tester", - email="tester@gmail.com", - password="123456", - first_name="Test", - last_name="User", + username=USERNAME, + email=EMAIL, + password=PASSWORD, + first_name=FIRST_NAME, + last_name=LAST_NAME, ) # To allow testing outside our transient CI containers, allow the tester @@ -56,17 +59,8 @@ def register_test_user(oidc_admin: OIDCAdmin): @pytest.fixture -def login_user( - server_client: PbenchServerClient, oidc_admin: OIDCAdmin, register_test_user -): +def login_user(server_client: PbenchServerClient, register_test_user): """Log in the test user and return the authentication token""" - oidc_endpoints = server_client.endpoints["openid"] - response = oidc_admin.user_login( - client_id=oidc_endpoints["client"], username="tester", password="123456" - ) - auth_token = response["access_token"] - assert auth_token - json = {"username": "tester", "auth_token": auth_token} - server_client.username = "tester" - server_client.auth_token = auth_token - yield JSONMap(json) + server_client.login(USERNAME, PASSWORD) + assert server_client.auth_token + yield diff --git a/server/pbenchinacan/load_keycloak.sh b/server/pbenchinacan/load_keycloak.sh index 1f999720be..9fbab02ee0 100755 --- a/server/pbenchinacan/load_keycloak.sh +++ b/server/pbenchinacan/load_keycloak.sh @@ -23,8 +23,8 @@ ADMIN_USERNAME=${ADMIN_USERNAME:-"admin"} ADMIN_PASSWORD=${ADMIN_PASSWORD:-"admin"} # These values must match the options "realm" and "client in the # "openid-connect" section of the pbench server configuration file. -REALM=${KEYCLOAK_REALM:-"pbench-server"} -CLIENT=${KEYCLOAK_CLIENT:-"pbench-dashboard"} +REALM=${KEYCLOAK_REALM:-"pbench-server-test"} +CLIENT=${KEYCLOAK_CLIENT:-"pbench-dashboard-test"} end_in_epoch_secs=$(date --date "2 minutes" +%s) @@ -66,7 +66,7 @@ else fi # Create a client scope with custom mapper that will instruct Keycloak -# to include the (pbench-dashboard) when someone request +# to include the (pbench-dashboard) when someone requests # a token from Keycloak using a . # Having in the aud claim of the token is essential for the token # to be validated. @@ -158,7 +158,7 @@ else echo "Assigned an 'ADMIN' client role to the user 'admin' created above" fi -# Verify that the user id has a role 'ADMIN' assigned to it +# Verify that the user id has an 'ADMIN' role assigned to it USER_ROLES=$(curl -s "${KEYCLOAK_HOST_PORT}/admin/realms/${REALM}/users/${USER_ID}/role-mappings/clients/${CLIENT_ID}" \ -H "Authorization: Bearer ${ADMIN_TOKEN}" \ -H "Content-Type: application/json" | jq -r '.[].name')