mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-07 10:04:07 +00:00

Squashed merge of https://github.com/jupyterhub/jupyterhub/pull/4594 Co-authored-by: Samuel Gaist <samuel.gaist@idiap.ch>
404 lines
13 KiB
Plaintext
404 lines
13 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "7609a68a-c01b-43a8-90aa-3d004af614dd",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Sharing access to a server\n",
|
|
"\n",
|
|
"This notebook executes some javascript in the browser, using the user's OAuth token.\n",
|
|
"\n",
|
|
"This code would normally reside in a jupyterlab extension.\n",
|
|
"The notebook serves only for demonstration purposes.\n",
|
|
"\n",
|
|
"First, collect some configuration from the page, so we can talk to the JupyterHub API:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"id": "b3cf16bd-ff9b-4140-a394-5a7ca5d96d88",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"application/javascript": [
|
|
"// define some globals to share\n",
|
|
"\n",
|
|
"var configElement = document.getElementById(\"jupyter-config-data\");\n",
|
|
"var jupyterConfig = JSON.parse(configElement.innerHTML);\n",
|
|
"\n",
|
|
"window.token = jupyterConfig.token;\n",
|
|
"window.hubOrigin = `${document.location.protocol}//${jupyterConfig.hubHost || window.location.host}`\n",
|
|
"window.hubUrl = `${hubOrigin}${jupyterConfig.hubPrefix}`;\n",
|
|
"window.shareCodesUrl = `${hubUrl}api/share-codes/${jupyterConfig.hubServerUser}/${jupyterConfig.hubServerName}`\n",
|
|
"window.sharesUrl = `${hubUrl}api/shares/${jupyterConfig.hubServerUser}/${jupyterConfig.hubServerName}`\n",
|
|
"console.log(shareCodesUrl);\n",
|
|
"\n",
|
|
"// utility function to make API requests and parse errors\n",
|
|
"window.apiRequest = async function (url, options) {\n",
|
|
" var element = options.element;\n",
|
|
" var okStatus = options.ok || 200;\n",
|
|
" var resp = await fetch(url, {headers: {Authorization: `Bearer ${token}`}, method: options.method || 'GET'});\n",
|
|
" var replyText = await resp.text();\n",
|
|
" var replyJSON = {};\n",
|
|
" if (replyText.length) {\n",
|
|
" replyJSON = JSON.parse(replyText);\n",
|
|
" }\n",
|
|
" \n",
|
|
" if (resp.status != okStatus) {\n",
|
|
" var p = document.createElement('p');\n",
|
|
" p.innerText = `Error ${resp.status}: ${replyJSON.message}`;\n",
|
|
" element.appendChild(p);\n",
|
|
" return;\n",
|
|
" }\n",
|
|
" return replyJSON;\n",
|
|
"}\n",
|
|
"\n",
|
|
"// `element` is a special variable for the current cell's output area\n",
|
|
"element.innerText = `API URL for sharing codes is: ${shareCodesUrl}`;\n"
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.Javascript object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
}
|
|
],
|
|
"source": [
|
|
"%%javascript\n",
|
|
"// define some globals to share\n",
|
|
"\n",
|
|
"var configElement = document.getElementById(\"jupyter-config-data\");\n",
|
|
"var jupyterConfig = JSON.parse(configElement.innerHTML);\n",
|
|
"\n",
|
|
"window.token = jupyterConfig.token;\n",
|
|
"window.hubOrigin = `${document.location.protocol}//${jupyterConfig.hubHost || window.location.host}`\n",
|
|
"window.hubUrl = `${hubOrigin}${jupyterConfig.hubPrefix}`;\n",
|
|
"window.shareCodesUrl = `${hubUrl}api/share-codes/${jupyterConfig.hubServerUser}/${jupyterConfig.hubServerName}`\n",
|
|
"window.sharesUrl = `${hubUrl}api/shares/${jupyterConfig.hubServerUser}/${jupyterConfig.hubServerName}`\n",
|
|
"console.log(shareCodesUrl);\n",
|
|
"\n",
|
|
"// utility function to make API requests and parse errors\n",
|
|
"window.apiRequest = async function (url, options) {\n",
|
|
" var element = options.element;\n",
|
|
" var okStatus = options.ok || 200;\n",
|
|
" var resp = await fetch(url, {headers: {Authorization: `Bearer ${token}`}, method: options.method || 'GET'});\n",
|
|
" var replyText = await resp.text();\n",
|
|
" var replyJSON = {};\n",
|
|
" if (replyText.length) {\n",
|
|
" replyJSON = JSON.parse(replyText);\n",
|
|
" }\n",
|
|
" \n",
|
|
" if (resp.status != okStatus) {\n",
|
|
" var p = document.createElement('p');\n",
|
|
" p.innerText = `Error ${resp.status}: ${replyJSON.message}`;\n",
|
|
" element.appendChild(p);\n",
|
|
" return;\n",
|
|
" }\n",
|
|
" return replyJSON;\n",
|
|
"}\n",
|
|
"\n",
|
|
"// `element` is a special variable for the current cell's output area\n",
|
|
"element.innerText = `API URL for sharing codes is: ${shareCodesUrl}`;"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "b2049fa1-bb60-4073-9167-2e116b198f0e",
|
|
"metadata": {},
|
|
"source": [
|
|
"Next, we can request a share code with\n",
|
|
"\n",
|
|
"```\n",
|
|
"POST $hub/api/share-codes/$user/$server\n",
|
|
"```\n",
|
|
"\n",
|
|
"The URL for _accepting_ a sharing invitation code is `/hub/accept-share?code=abc123...`:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"id": "ee9430f3-4866-41f7-942d-423bd48dc6b8",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"application/javascript": [
|
|
"\n",
|
|
"(async function f() {\n",
|
|
" var shareCode = await apiRequest(shareCodesUrl, {method: 'POST', element: element});\n",
|
|
"\n",
|
|
" // laziest way to display\n",
|
|
" var shareCodeUrl = `${hubOrigin}${shareCode.accept_url}`\n",
|
|
" var a = document.createElement('a');\n",
|
|
" a.href = shareCodeUrl;\n",
|
|
" a.innerText = shareCodeUrl;\n",
|
|
" var p = document.createElement(p);\n",
|
|
" p.append(\"Share this URL to grant access to this server: \");\n",
|
|
" p.appendChild(a);\n",
|
|
" element.appendChild(p);\n",
|
|
"})();\n",
|
|
"\n"
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.Javascript object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
}
|
|
],
|
|
"source": [
|
|
"%%javascript\n",
|
|
"\n",
|
|
"(async function f() {\n",
|
|
" var shareCode = await apiRequest(shareCodesUrl, {method: 'POST', element: element});\n",
|
|
"\n",
|
|
" // laziest way to display\n",
|
|
" var shareCodeUrl = `${hubOrigin}${shareCode.accept_url}`\n",
|
|
" var a = document.createElement('a');\n",
|
|
" a.href = shareCodeUrl;\n",
|
|
" a.innerText = shareCodeUrl;\n",
|
|
" var p = document.createElement(p);\n",
|
|
" p.append(\"Share this URL to grant access to this server: \");\n",
|
|
" p.appendChild(a);\n",
|
|
" element.appendChild(p);\n",
|
|
"})();\n",
|
|
"\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "418152a0-2e0f-4ca6-8b53-9295d15c4345",
|
|
"metadata": {},
|
|
"source": [
|
|
"Share this URL to grant access to your server (e.g. visit the URL in a private window and login as the user `shared-with`).\n",
|
|
"\n",
|
|
"After our code has been used, we can see who has access to this server:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"id": "5c9c6e1b-ccab-4c85-8afb-db141651236a",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"application/javascript": [
|
|
"\n",
|
|
"(async function f() {\n",
|
|
"\n",
|
|
" var shares = await apiRequest(sharesUrl, {element: element});\n",
|
|
"\n",
|
|
" var list = document.createElement('ul');\n",
|
|
" for (var share of shares.items) {\n",
|
|
" var p = document.createElement('li');\n",
|
|
" p.append(`${share.kind} ${share[share.kind].name} has access: `)\n",
|
|
" var scopes = document.createElement('tt');\n",
|
|
" scopes.innerText = share.scopes.join(',');\n",
|
|
" p.appendChild(scopes);\n",
|
|
" list.append(p);\n",
|
|
" }\n",
|
|
" var p = document.createElement('p');\n",
|
|
" p.innerText = `Shared with ${shares.items.length} users:`;\n",
|
|
" element.appendChild(p);\n",
|
|
" element.appendChild(list);\n",
|
|
" return;\n",
|
|
"})();\n"
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.Javascript object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
}
|
|
],
|
|
"source": [
|
|
"%%javascript\n",
|
|
"\n",
|
|
"(async function f() {\n",
|
|
"\n",
|
|
" var shares = await apiRequest(sharesUrl, {element: element});\n",
|
|
"\n",
|
|
" var list = document.createElement('ul');\n",
|
|
" for (var share of shares.items) {\n",
|
|
" var p = document.createElement('li');\n",
|
|
" p.append(`${share.kind} ${share[share.kind].name} has access: `)\n",
|
|
" var scopes = document.createElement('tt');\n",
|
|
" scopes.innerText = share.scopes.join(',');\n",
|
|
" p.appendChild(scopes);\n",
|
|
" list.append(p);\n",
|
|
" }\n",
|
|
" var p = document.createElement('p');\n",
|
|
" p.innerText = `Shared with ${shares.items.length} users:`;\n",
|
|
" element.appendChild(p);\n",
|
|
" element.appendChild(list);\n",
|
|
" return;\n",
|
|
"})();\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "18165205-67f3-44d4-ad6b-40ab27ec469c",
|
|
"metadata": {},
|
|
"source": [
|
|
"We could also use this info to revoke permissions, or share with individuals by name.\n",
|
|
"\n",
|
|
"We can also review outstanding sharing _codes_:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 5,
|
|
"id": "40cd1e2d-fc21-4238-8cbc-9ae45be248c9",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"application/javascript": [
|
|
"\n",
|
|
"(async function f() {\n",
|
|
" var shareCodes = await apiRequest(shareCodesUrl, {element: element});\n",
|
|
" var p = document.createElement('pre');\n",
|
|
" p.innerText = JSON.stringify(shareCodes.items, null, ' ');\n",
|
|
" element.appendChild(p);\n",
|
|
"})();\n"
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.Javascript object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
}
|
|
],
|
|
"source": [
|
|
"%%javascript\n",
|
|
"\n",
|
|
"(async function f() {\n",
|
|
" var shareCodes = await apiRequest(shareCodesUrl, {element: element});\n",
|
|
" var p = document.createElement('pre');\n",
|
|
" p.innerText = JSON.stringify(shareCodes.items, null, ' ');\n",
|
|
" element.appendChild(p);\n",
|
|
"})();\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "0effee22-c132-4b44-a677-c4375594c462",
|
|
"metadata": {},
|
|
"source": [
|
|
"And finally, when we're done, we can revoke the codes, at which point nobody _new_ can use the code to gain access to this server,\n",
|
|
"but anyone who has accepted the code will still have access:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"id": "51a795f5-9a74-49b1-9ef3-e7978da0cc44",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"application/javascript": [
|
|
"\n",
|
|
"(async function f() {\n",
|
|
" await apiRequest(shareCodesUrl, {method: 'DELETE', element: element, ok: 204});\n",
|
|
" var p = document.createElement('p');\n",
|
|
" p.innerText = `Deleted all share codes`;\n",
|
|
" element.appendChild(p); \n",
|
|
"})();\n"
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.Javascript object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
}
|
|
],
|
|
"source": [
|
|
"%%javascript\n",
|
|
"\n",
|
|
"(async function f() {\n",
|
|
" await apiRequest(shareCodesUrl, {method: 'DELETE', element: element, ok: 204});\n",
|
|
" var p = document.createElement('p');\n",
|
|
" p.innerText = `Deleted all share codes`;\n",
|
|
" element.appendChild(p); \n",
|
|
"})();"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "2ad12718-1f68-4d2f-9934-dd6bd4555a1d",
|
|
"metadata": {},
|
|
"source": [
|
|
"Or even revoke all shared access, so anyone who may have used the code no longer has any access:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 7,
|
|
"id": "efa5ae15-ac99-4bf4-b7b4-3d93747dba8c",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"application/javascript": [
|
|
"\n",
|
|
"(async function f() {\n",
|
|
" var resp = await apiRequest(sharesUrl, {method: 'DELETE', element: element, ok: 204});\n",
|
|
" var p = document.createElement('p');\n",
|
|
" p.innerText = `Deleted all shared access`;\n",
|
|
" element.appendChild(p); \n",
|
|
"})();\n"
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.Javascript object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
}
|
|
],
|
|
"source": [
|
|
"%%javascript\n",
|
|
"\n",
|
|
"(async function f() {\n",
|
|
" var resp = await apiRequest(sharesUrl, {method: 'DELETE', element: element, ok: 204});\n",
|
|
" var p = document.createElement('p');\n",
|
|
" p.innerText = `Deleted all shared access`;\n",
|
|
" element.appendChild(p); \n",
|
|
"})();"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "Python 3 (ipykernel)",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython",
|
|
"version": 3
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"version": "3.10.13"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5
|
|
}
|