{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#default_exp auth" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Authentication\n", "> Helpers for creating GitHub API tokens" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "from fastcore.utils import *\n", "from fastcore.foundation import *\n", "from ghapi.core import *\n", "\n", "from urllib.parse import parse_qs,urlsplit" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Scopes" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "_scopes =(\n", " 'repo','repo:status','repo_deployment','public_repo','repo:invite','security_events','admin:repo_hook','write:repo_hook',\n", " 'read:repo_hook','admin:org','write:org','read:org','admin:public_key','write:public_key','read:public_key','admin:org_hook',\n", " 'gist','notifications','user','read:user','user:email','user:follow','delete_repo','write:discussion','read:discussion',\n", " 'write:packages','read:packages','delete:packages','admin:gpg_key','write:gpg_key','read:gpg_key','workflow'\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "Scope = AttrDict({o.replace(':','_'):o for o in _scopes})" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def scope_str(*scopes)->str:\n", " \"Convert `scopes` into a comma-separated string\"\n", " return ','.join(str(o) for o in scopes if o)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'repo,admin:public_key,public_repo'" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "scope_str(Scope.repo,Scope.admin_public_key,Scope.public_repo)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## GhDeviceAuth -" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "_def_clientid = '771f3c3af93face45f52'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "class GhDeviceAuth(GetAttrBase):\n", " \"Get an oauth token using the GitHub API device flow\"\n", " _attr=\"params\"\n", " def __init__(self, client_id=_def_clientid, *scopes):\n", " url = 'https://github.com/login/device/code'\n", " self.client_id = client_id\n", " self.params = parse_qs(urlread(url, client_id=client_id, scope=scope_str(scopes)))\n", "\n", " def _getattr(self,v): return v[0]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Creating a `GhDeviceAuth` will complete the first step in the GitHub API device flow, getting device and user codes." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "('c42d42028140be95ace73753538e14b51fa86177', '2ACD-D414')" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ghauth = GhDeviceAuth()\n", "ghauth.device_code,ghauth.user_code" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "@patch\n", "def url_docs(self:GhDeviceAuth)->str:\n", " \"Default instructions on how to authenticate\"\n", " return f\"\"\"First copy your one-time code: {self.user_code}\n", "Then visit {self.verification_uri} in your browser, and paste the code when prompted.\"\"\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can provide your own instructions on how to authenticate, or just print this out:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "First copy your one-time code: 2ACD-D414\n", "Then visit https://github.com/login/device in your browser, and paste the code when prompted.\n" ] } ], "source": [ "print(ghauth.url_docs())" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "@patch\n", "def open_browser(self:GhDeviceAuth):\n", " \"Open a web browser with the verification URL\"\n", " webbrowser.open(self.verification_uri)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This uses Python's `webbrowser.open`, which will use the user's default web browser. This won't work well if the user is using a remote terminal." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "@patch\n", "def auth(self:GhDeviceAuth)->str:\n", " \"Return token if authentication complete, or `None` otherwise\"\n", " resp = parse_qs(urlread(\n", " 'https://github.com/login/oauth/access_token',\n", " client_id=self.client_id, device_code=self.device_code,\n", " grant_type='urn:ietf:params:oauth:grant-type:device_code'))\n", " err = nested_idx(resp, 'error', 0)\n", " if err == 'authorization_pending': return None\n", " if err: raise Exception(resp['error_description'][0])\n", " return resp['access_token'][0]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Until the user has completed authentication in the browser, this will return None. Normally, you won't call this directly, but will call `wait` (see below), which will repeatedly call `auth` until authentication is complete." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "None\n" ] } ], "source": [ "print(ghauth.auth())" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "@patch\n", "def wait(self:GhDeviceAuth, cb:callable=None, n_polls=9999)->str:\n", " \"Wait up to `n_polls` times for authentication to complete, calling `cb` after each poll, if passed\"\n", " interval = int(self.interval)+1\n", " res = None\n", " for i in range(n_polls):\n", " res = self.auth()\n", " if cb: cb()\n", " time.sleep(interval)\n", " return res" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you pass a callback to `cb`, it will be called after each unsuccessful check for user authentication. For instance, to print a `.` to the screen after each poll, and store the token in a variable `token` when complete, you could use:\n", "\n", "```python\n", "token = auth.wait(lambda: print('.', end=''))\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Export -" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Converted 00_core.ipynb.\n", "Converted 01_actions.ipynb.\n", "Converted 02_auth.ipynb.\n", "Converted 03_page.ipynb.\n", "Converted 04_event.ipynb.\n", "Converted 10_cli.ipynb.\n", "Converted 50_fullapi.ipynb.\n", "Converted 80_tutorial_actions.ipynb.\n", "Converted 90_build_lib.ipynb.\n", "Converted index.ipynb.\n" ] } ], "source": [ "#hide\n", "from nbdev.export import notebook2script\n", "notebook2script()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" } }, "nbformat": 4, "nbformat_minor": 4 }