From 9ba670f3482909cc46c1aed80a47cfa1aaa984a8 Mon Sep 17 00:00:00 2001 From: "renyuneyun (Rui Zhao)" Date: Sun, 29 Oct 2023 12:09:34 +0000 Subject: [PATCH 1/2] feat: Support OIDC login using solid-oidc-client The library solid-oidc-client currently has caveats for token refresh (etc). Use with caution. --- setup.cfg | 2 ++ src/solid/auth.py | 79 ++++++++++++++++++++++++++++++++++++++++++ src/solid/solid_api.py | 5 ++- 3 files changed, 85 insertions(+), 1 deletion(-) 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..d61fa3e 100644 --- a/src/solid/auth.py +++ b/src/solid/auth.py @@ -1,4 +1,12 @@ import httpx +from httpx import Response + +from solid_oidc_client import SolidOidcClient, SolidAuthSession, MemStore +import flask + +from multiprocessing import Process, Queue + +from typing import Dict class Auth: @@ -27,3 +35,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): + process = Process(target=_run_flask_server, args=(solid_oidc_client, q)) + self._server_process = process + process.start() + + def _stop_server(self): + self._server_process.terminate() + + 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 From ab642bd2dbba98ca0054f5db0b90de385cd1f1ed Mon Sep 17 00:00:00 2001 From: "renyuneyun (Rui Zhao)" Date: Sat, 23 May 2026 16:02:57 +0800 Subject: [PATCH 2/2] fix: use Thread instead of Process to avoid pickling SolidOidcClient multiprocessing.Process requires pickling its arguments, which fails on Python 3.14 (forkserver default) because SolidOidcClient contains an unpicklable RLock. A daemon Thread shares memory directly and needs no serialization. --- src/solid/auth.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/solid/auth.py b/src/solid/auth.py index d61fa3e..03f5465 100644 --- a/src/solid/auth.py +++ b/src/solid/auth.py @@ -4,7 +4,8 @@ from solid_oidc_client import SolidOidcClient, SolidAuthSession, MemStore import flask -from multiprocessing import Process, Queue +from multiprocessing import Queue +from threading import Thread from typing import Dict @@ -63,12 +64,12 @@ def fetch(self, method, url, options: Dict) -> Response: return r def _start_server(self, solid_oidc_client: SolidOidcClient, q: Queue): - process = Process(target=_run_flask_server, args=(solid_oidc_client, q)) - self._server_process = process - process.start() + thread = Thread(target=_run_flask_server, args=(solid_oidc_client, q), daemon=True) + self._server_thread = thread + thread.start() def _stop_server(self): - self._server_process.terminate() + pass # daemon thread exits automatically when login completes def login(self, idp): solid_oidc_client = SolidOidcClient(storage=MemStore())