diff --git a/setup.cfg b/setup.cfg index 537fd96..e5ea056 100644 --- a/setup.cfg +++ b/setup.cfg @@ -23,6 +23,8 @@ python_requires = >=3.7 install_requires = httpx rdflib + solid-oidc-client + flask [options.packages.find] where = src diff --git a/src/solid/auth.py b/src/solid/auth.py index 76b55f0..03f5465 100644 --- a/src/solid/auth.py +++ b/src/solid/auth.py @@ -1,4 +1,13 @@ import httpx +from httpx import Response + +from solid_oidc_client import SolidOidcClient, SolidAuthSession, MemStore +import flask + +from multiprocessing import Queue +from threading import Thread + +from typing import Dict class Auth: @@ -27,3 +36,74 @@ def login(self, idp, username, password): if not self.is_login: raise Exception('Cannot login.') + + +class OidcAuth: + + OAUTH_CALLBACK_PATH = '/oauth/callback' + OAUTH_CALLBACK_URI = f"http://localhost:8080{OAUTH_CALLBACK_PATH}" + + def __init__(self): + self.client = httpx.Client() + self.session = None + self._server_process = None + + @property + def is_login(self) -> bool: + return self.session is not None + + def fetch(self, method, url, options: Dict) -> Response: + if 'headers' not in options: + options['headers'] = {} + + if self.session: + auth_headers = self.session.get_auth_headers(url, method) + options['headers'].update(auth_headers) + + r = self.client.request(method, url, **options) + return r + + def _start_server(self, solid_oidc_client: SolidOidcClient, q: Queue): + thread = Thread(target=_run_flask_server, args=(solid_oidc_client, q), daemon=True) + self._server_thread = thread + thread.start() + + def _stop_server(self): + pass # daemon thread exits automatically when login completes + + def login(self, idp): + solid_oidc_client = SolidOidcClient(storage=MemStore()) + solid_oidc_client.register_client(idp, [OidcAuth.OAUTH_CALLBACK_URI]) + login_url = solid_oidc_client.create_login_uri('/', OidcAuth.OAUTH_CALLBACK_URI) + q = Queue(1) + self._start_server(solid_oidc_client, q) + + print(f"Please visit this URL to log-in: {login_url}") + + session = SolidAuthSession.deserialize(q.get()) + self.session = session + + self._stop_server() + + +def _run_flask_server(solid_oidc_client: SolidOidcClient, q: Queue): + app = flask.Flask(__name__) + + @app.get('/oauth/callback') + def login_callback(): + code = flask.request.args['code'] + state = flask.request.args['state'] + + session = solid_oidc_client.finish_login( + code=code, + state=state, + callback_uri=OidcAuth.OAUTH_CALLBACK_URI, + ) + + q.put(session.serialize()) + + return flask.Response( + f"Logged in as {session.get_web_id()}. You can close your browser now.", + mimetype='text/html') + + app.run('localhost', 8080) diff --git a/src/solid/solid_api.py b/src/solid/solid_api.py index 343875e..e38ba4a 100644 --- a/src/solid/solid_api.py +++ b/src/solid/solid_api.py @@ -87,7 +87,10 @@ def fetch(self, method, url, options: Dict = None) -> Response: options = {} # options['verify'] = False - r = self.auth.client.request(method, url, **options) + if hasattr(self.auth, 'fetch'): + r = self.auth.fetch(method, url, options) + else: + r = self.auth.client.request(method, url, **options) # r= httpx.request(method, url, **options) r.raise_for_status() return r