Add an example Jupyter Notebook to refresh a token a call a D4Science HTTP service

This commit is contained in:
Antonio Calanducci 2024-03-19 09:46:02 +01:00
commit 21d9907553
1 changed files with 221 additions and 0 deletions

View File

@ -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 = \"<YOUR_PERSONAL_OAUTH_ACCESS_TOKEN>\" # Get your Personal Access (UMA) Token from the portlet\n",
"REFRESH_TOKEN = \"<YOUR_PERSONAL_OAUTH2_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"
]
}
]
}
]
}