diff --git a/README.rst b/README.rst index eda6a7c..32647eb 100644 --- a/README.rst +++ b/README.rst @@ -100,6 +100,17 @@ the client must specify the expected type: ``kinto_http.BearerTokenAuth("XYPJTNs In other words, ``kinto_http.Client(auth="Bearer+OIDC XYPJTNsFKV2")`` is equivalent to ``kinto_http.Client(auth=kinto_http.BearerTokenAuth("XYPJTNsFKV2", type="Bearer+OIDC"))`` +Using the browser to authenticate via OAuth +------------------------------------------- + +.. code-block:: python + + import kinto_http + + client = kinto_http.Client(server_url='http://localhost:8888/v1', auth=kinto_http.BrowserOAuth()) + +The client will open a browser page and will catch the Bearer token obtained after the OAuth dance. + Custom headers -------------- diff --git a/src/kinto_http/__init__.py b/src/kinto_http/__init__.py index 0912187..87d1c60 100644 --- a/src/kinto_http/__init__.py +++ b/src/kinto_http/__init__.py @@ -10,12 +10,14 @@ KintoBatchException, KintoException, ) +from kinto_http.login import BrowserOAuth from kinto_http.session import Session, create_session logger = logging.getLogger("kinto_http") __all__ = ( + "BrowserOAuth", "BearerTokenAuth", "Endpoints", "Session", diff --git a/src/kinto_http/client.py b/src/kinto_http/client.py index 10d1cd9..b30c4c0 100644 --- a/src/kinto_http/client.py +++ b/src/kinto_http/client.py @@ -15,6 +15,7 @@ from kinto_http.constants import DO_NOT_OVERWRITE from kinto_http.endpoints import Endpoints from kinto_http.exceptions import BucketNotFound, CollectionNotFound, KintoException +from kinto_http.login import BrowserOAuth from kinto_http.patch_type import BasicPatch, PatchType from kinto_http.session import create_session @@ -45,6 +46,9 @@ def __init__( ): self.endpoints = Endpoints() + if isinstance(auth, BrowserOAuth): + auth.server_url = server_url + session_kwargs = dict( server_url=server_url, auth=auth, diff --git a/src/kinto_http/login.py b/src/kinto_http/login.py new file mode 100644 index 0000000..458b4be --- /dev/null +++ b/src/kinto_http/login.py @@ -0,0 +1,92 @@ +import base64 +import json +import threading +import webbrowser +from http.server import BaseHTTPRequestHandler, HTTPServer +from urllib.parse import unquote + +import requests + + +class RequestHandler(BaseHTTPRequestHandler): + def __init__(self, *args, set_jwt_token_callback=None, **kwargs): + self.set_jwt_token_callback = set_jwt_token_callback + super().__init__(*args, **kwargs) + + def do_GET(self): + # Ignore non-auth requests (eg. favicon.ico). + if "/auth" not in self.path: + self.send_response(404) + self.end_headers() + return + + # Return a basic page to the user inviting them to close the page. + self.send_response(200) + self.send_header("Content-Type", "text/html") + self.end_headers() + self.wfile.write( + b"