{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# OAuth examples\n",
    "\n",
    "The OAuth implementation provided requires additional dependencies to be installed. To install the required dependencies, run the following command:\n",
    "\n",
    "```\n",
    "pip install evo-sdk-common[notebooks]\n",
    "```\n",
    "\n",
    "## Configure the connector\n",
    "\n",
    "The `OAuthConnector` is the central component for all OAuth workflows."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from evo.aio import AioTransport\n",
    "from evo.oauth import OAuthConnector\n",
    "\n",
    "# OAuth client app credentials\n",
    "# See: https://developer.seequent.com/docs/guides/getting-started/apps-and-tokens\n",
    "REDIRECT_URL = \"http://localhost:3000/signin-callback\"\n",
    "CLIENT_NAME = \"Your Client Name\"\n",
    "CLIENT_ID = \"your-client-id\"\n",
    "\n",
    "connector = OAuthConnector(\n",
    "    client_id=CLIENT_ID,\n",
    "    transport=AioTransport(user_agent=CLIENT_NAME),\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Manage access tokens\n",
    "\n",
    "The `OAuth` library provides authorizer classes to handle different OAuth flows. The `AuthorizationCodeAuthorizer` can be used for user access tokens, and the `ClientCredientialsAuthorizer` can be used for service-to-service authentication.\n",
    "### Simplest way to manage user access tokens\n",
    "\n",
    "`AuthorizationCodeAuthorizer` is the simplest way to manage user access tokens. Logging in will open a browser window to the authorisation URL and wait for the user to authenticate and authorise the application. The `AuthorizationCodeAuthorizer` object allows the user access token to be used in API requests."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from evo.oauth import AuthorizationCodeAuthorizer, EvoScopes\n",
    "\n",
    "authorizer = AuthorizationCodeAuthorizer(\n",
    "    oauth_connector=connector,\n",
    "    redirect_url=REDIRECT_URL,\n",
    "    scopes=EvoScopes.all_evo | EvoScopes.offline_access,\n",
    ")\n",
    "await authorizer.login()\n",
    "print(await authorizer.get_default_headers())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Refreshing the access token\n",
    "\n",
    "`AuthorizationCodeAuthorizer.refresh_token()` can be used to refresh the access token when it expires. If the authorization server did not return a refresh token, the function will raise `OAuthError`. If you try refreshing the token within 5 minutes of the last refresh, the token will not be refreshed and the method will return `False`. Similarly, if there is any error during the refresh, the method will return `False`.\n",
    "\n",
    "This is how `APIConnector` automatically refreshes the access token when it expires.\n",
    "\n",
    "> Note: You MUST request the `offline_access` scope at login to get a refresh token. Offline access is not included by default in any of the predefined scope groups."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "refreshed = await authorizer.refresh_token()\n",
    "print(f\"The token was {'' if refreshed else 'not '}refreshed.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Using the OAuthRedirectHandler\n",
    "\n",
    "The `OAuthRedirectHandler` wraps the `OAuthConnector` and implements a localhost HTTP server to handle the OAuth redirect. This is useful for applications that cannot open a browser window, such as a command-line application. The `OAuthRedirectHandler` is an asynchronous context manager that manages the lifecycle of the HTTP server."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from evo.oauth import EvoScopes, OAuthRedirectHandler\n",
    "\n",
    "async with OAuthRedirectHandler(connector, REDIRECT_URL) as handler:\n",
    "    result = await handler.login(EvoScopes.all_evo | EvoScopes.offline_access)\n",
    "\n",
    "print(f\"Access token: {result.access_token}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Client credentials authentication\n",
    "\n",
    "The `ClientCredientialsAuthorizer` allows you to handle service-to-service authentication."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from evo.aio import AioTransport\n",
    "from evo.oauth import ClientCredentialsAuthorizer, EvoScopes, OAuthConnector\n",
    "\n",
    "# OAuth client app credentials\n",
    "# See: https://developer.seequent.com/docs/guides/getting-started/apps-and-tokens\n",
    "CLIENT_NAME = \"Your Client Name\"\n",
    "CLIENT_ID = \"your-client-id\"\n",
    "CLIENT_SECRET = \"your-client-secret\"\n",
    "\n",
    "authorizer = ClientCredentialsAuthorizer(\n",
    "    oauth_connector=OAuthConnector(\n",
    "        transport=AioTransport(user_agent=CLIENT_NAME),\n",
    "        client_id=CLIENT_ID,\n",
    "        client_secret=CLIENT_SECRET,\n",
    "    ),\n",
    "    scopes=EvoScopes.evo_discovery | EvoScopes.evo_workspace,\n",
    ")\n",
    "\n",
    "print(await authorizer.get_default_headers())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Custom OAuth Scopes\n",
    "\n",
    "The `Scopes` type makes it easy to request custom OAuth scopes. You can combine predefined scope groups with custom scopes using the bitwise OR operator (`|`)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from evo.oauth import EvoScopes, Scopes\n",
    "\n",
    "print(EvoScopes.default)  # Predefined default scopes\n",
    "\n",
    "# Predefined scopes can be combined with Scopes instances:\n",
    "print(EvoScopes.default | Scopes(\"custom-scope1 custom-scope2\"))\n",
    "\n",
    "# Predefined scopes can be combined with str instances:\n",
    "print(EvoScopes.default | \"custom-scope1 custom-scope2\")\n",
    "\n",
    "# Scopes instances can be combined with str instances:\n",
    "print(Scopes(\"custom-scope1\") | \"custom-scope2\")\n",
    "\n",
    "# Combining scopes is commutative:\n",
    "print(EvoScopes.default | \"custom-scope1 custom-scope2\")\n",
    "print(\"custom-scope1 custom-scope2\" | EvoScopes.default)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "evo-sdk",
   "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.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
