{
 "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",
    "import webbrowser,time\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": [
       "('4a862fd5d474b58cfad0e2432f47c33a2976facc', '28F1-E3A6')"
      ]
     },
     "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: 28F1-E3A6\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 res: return res\n",
    "        if cb: cb()\n",
    "        time.sleep(interval)"
   ]
  },
  {
   "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 = ghauth.wait(lambda: print('.', end=''))\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#hide\n",
    "# token = ghauth.wait(lambda: print('.', end=''))"
   ]
  },
  {
   "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
}
