From 21d9907553dd724c2c5b2997f50be362cf4585dd Mon Sep 17 00:00:00 2001 From: Antonio Calanducci Date: Tue, 19 Mar 2024 09:46:02 +0100 Subject: [PATCH] Add an example Jupyter Notebook to refresh a token a call a D4Science HTTP service --- refresh-token-and-call.ipynb | 221 +++++++++++++++++++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 refresh-token-and-call.ipynb diff --git a/refresh-token-and-call.ipynb b/refresh-token-and-call.ipynb new file mode 100644 index 0000000..fac484f --- /dev/null +++ b/refresh-token-and-call.ipynb @@ -0,0 +1,221 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# How to issue a request to a D4Science service\n", + "\n", + "In order to issue a request to an HTTP service hosted on D4Science infrastructure, you need to have a valid *UMA OAuth ACCESS_TOKEN.*\n", + "\n", + "You can get one from the portlet avaiable on the Science Gateway where your VRE is deployed.\n", + "\n", + "However, this token has a limited duration and it needs to be refreshed through a longer lived *REFRESH_TOKEN*, that you can find from the same porlet.\n", + "\n", + "In the following colab, you can find an example on how to deal with the expiration of an Access Token from Python." + ], + "metadata": { + "id": "FyO3MvcJKH7e" + } + }, + { + "cell_type": "markdown", + "source": [ + "Let's start by importing some required libraries:\n", + "\n", + "> *Note: if you try to execute this code outside of Google Colab, you probably need to install `requests` and `pyjwt` with*\n", + "\n", + "> ```pip install requests pyjwt==2.8.0```\n", + "\n", + "\n", + "\n", + "\n" + ], + "metadata": { + "id": "W1lMfjcFL0G4" + } + }, + { + "cell_type": "code", + "source": [ + "import requests\n", + "import jwt\n", + "from datetime import datetime" + ], + "metadata": { + "id": "NhA77gG5L3ez" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "and configure some variabiles with the values copied from the Personal Web Token Portlet and the `service_url` you want to issue a request to." + ], + "metadata": { + "id": "nW6EsJgRL-pL" + } + }, + { + "cell_type": "code", + "source": [ + "ACCESS_TOKEN = \"\" # Get your Personal Access (UMA) Token from the portlet\n", + "REFRESH_TOKEN = \"\" # Get your OAuth2 Refresh Token from the portlet\n", + "CLIENT_ID = \"sobigdata.d4science.org\" # Get from the Refresh parameters section of the porlet\n", + "SERVICE_URL = \"https://netme-sobigdata.d4science.org\" # Base URL of the service you want to get access\n", + "\n", + "refresh_token_url = \"https://accounts.d4science.org/auth/realms/d4science/protocol/openid-connect/token\" # Get from the Refresh token URL section of the porlet" + ], + "metadata": { + "id": "_CjKMHaYMUAl" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "The `refresh_token` function is used to get a new `access token` by means of a `refresh token`" + ], + "metadata": { + "id": "oESq-ZDcM1hy" + } + }, + { + "cell_type": "code", + "source": [ + "def refreshToken(rtoken):\n", + " if tokenIsExpired(rtoken):\n", + " print(f\"Refresh token has expired too {expirationTime(rtoken)}\")\n", + " exit(-1)\n", + " data = { \"grant_type\": \"refresh_token\", \"client_id\": CLIENT_ID, \"refresh_token\": rtoken }\n", + " response = requests.post(refresh_token_url, data=data)\n", + " r = response.json()\n", + " if response.status_code != 200:\n", + " # print(yaml.dump(r))\n", + " exit(-1)\n", + " return r['access_token']" + ], + "metadata": { + "id": "luV7dSc_NDpH" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Two other helper functions, `expirationTime` and `tokenIsExpired` can be used to get the expiration time of a given `token` and check if a `token` is expired or not:" + ], + "metadata": { + "id": "SMRQG9_4NOj2" + } + }, + { + "cell_type": "code", + "source": [ + "def expirationTime(token):\n", + " decoded = jwt.decode(jwt=token, options={\"verify_signature\": False})\n", + " return datetime.fromtimestamp(decoded['exp'])\n", + "\n", + "def tokenIsExpired(token):\n", + " return datetime.now() > expirationTime(token)" + ], + "metadata": { + "id": "zXsgW1_ENcdd" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Finally, the function `executeServiceRequest` will issue the HTTP request to the given service `path`. It will check if the `ACCESS_TOKEN` is expired and in that case it will refresh it. It will return back the response from the service" + ], + "metadata": { + "id": "uMJuxCeONh-Z" + } + }, + { + "cell_type": "code", + "source": [ + "def executeServiceRequest(path):\n", + " global ACCESS_TOKEN\n", + " if tokenIsExpired(ACCESS_TOKEN):\n", + " print(f\"ACCESS token expired in {expirationTime(ACCESS_TOKEN)}\")\n", + " print(\"Refreshing token...\")\n", + " ACCESS_TOKEN = refreshToken(REFRESH_TOKEN)\n", + " print(f\"new ACCESS token expiration {expirationTime(ACCESS_TOKEN)}\")\n", + "\n", + " print(f\"Call to Service {SERVICE_URL}{path}\")\n", + " response = requests.post(f\"{SERVICE_URL}{path}\", headers={\"Authorization\": f\"Bearer {ACCESS_TOKEN}\"})\n", + " r = response.json()\n", + " if response.status_code != 200:\n", + " print(f\"\\nResponse: {r}\")\n", + " else:\n", + " raise Exception(\"Unable Contact service\")\n", + " return r" + ], + "metadata": { + "id": "88mYxCfDNyvh" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "So let's try to use the NetME service, in particular the network generation feature, available at the `network_generation` path.\n", + "\n", + "So we just need to call the `executeServiceRequest` function with the correct path.\n", + "\n", + "**Please also add any other header or payload data the requested service requires**\n" + ], + "metadata": { + "id": "ZZjBzHY_OIMG" + } + }, + { + "cell_type": "code", + "source": [ + "response = executeServiceRequest('/network_generation')" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "Eti_zQ4DO7Ox", + "outputId": "b570e687-d7eb-49f8-b3c9-745998fd2963" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "ACCESS token expired in 2024-03-13 15:07:34\n", + "Refreshing token...\n", + "new ACCESS token expiration 2024-03-14 14:25:28\n", + "Call to Service https://netme-sobigdata.d4science.org/network_generation\n", + "\n", + "Response: {'detail': [{'type': 'missing', 'loc': ['body'], 'msg': 'Field required', 'input': None, 'url': 'https://errors.pydantic.dev/2.6/v/missing'}]}\n" + ] + } + ] + } + ] +} \ No newline at end of file