From 2c09d72276031d13dbb04e2754b34b053243e300 Mon Sep 17 00:00:00 2001 From: Romain PRIMET Date: Tue, 29 Sep 2020 15:53:38 +0200 Subject: [PATCH 1/4] pass handler to repo provider --- binderhub/base.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/binderhub/base.py b/binderhub/base.py index aadf2aa18..0de505d55 100644 --- a/binderhub/base.py +++ b/binderhub/base.py @@ -50,7 +50,9 @@ def get_provider(self, provider_prefix, spec): raise web.HTTPError(404, "No provider found for prefix %s" % provider_prefix) return providers[provider_prefix]( - config=self.settings['traitlets_config'], spec=spec) + config=self.settings['traitlets_config'], + spec=spec, + handler=self) def get_badge_base_url(self): badge_base_url = self.settings['badge_base_url'] From 44ef87f1e87b5aaa78732a7b981fa1868769f8b8 Mon Sep 17 00:00:00 2001 From: Adrien DELSALLE Date: Fri, 19 Feb 2021 16:40:35 +0100 Subject: [PATCH 2/4] fetch current user credential only once --- binderhub/base.py | 43 +++++++++++++++++++++++++++++++++++++++---- binderhub/builder.py | 2 +- binderhub/main.py | 2 +- 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/binderhub/base.py b/binderhub/base.py index 0de505d55..cd2a30407 100644 --- a/binderhub/base.py +++ b/binderhub/base.py @@ -1,9 +1,13 @@ """Base classes for request handlers""" import json +import os from http.client import responses from tornado import web +from tornado.log import app_log +from tornado.httpclient import AsyncHTTPClient, HTTPRequest, HTTPError + from jupyterhub.services.auth import HubOAuthenticated, HubOAuth from . import __version__ as binder_version @@ -16,6 +20,7 @@ def initialize(self): super().initialize() if self.settings['auth_enabled']: self.hub_auth = HubOAuth.instance(config=self.settings['traitlets_config']) + self.current_user_model = None def get_current_user(self): if not self.settings['auth_enabled']: @@ -43,16 +48,46 @@ def get_spec_from_request(self, prefix): spec = self.request.path[idx + len(prefix) + 1:] return spec - def get_provider(self, provider_prefix, spec): + async def get_provider(self, provider_prefix, spec): """Construct a provider object""" providers = self.settings['repo_providers'] if provider_prefix not in providers: raise web.HTTPError(404, "No provider found for prefix %s" % provider_prefix) + async def api_request(url, *args, **kwargs): + headers = kwargs.setdefault('headers', {}) + headers.update({'Authorization': 'token %s' % self.hub_auth.api_token}) + hub_api_url = os.getenv('JUPYTERHUB_API_URL', '') or self.hub_auth.api_url + request_url = hub_api_url + url + req = HTTPRequest(request_url, *args, **kwargs) + + try: + return await AsyncHTTPClient().fetch(req) + except HTTPError as e: + app_log.error("Error accessing Hub API (using %s): %s", request_url, e) + + async def get_current_user_model(): + """Get the current user model. + The user auth_state is only accessible to admin users. + """ + if not self.settings['auth_enabled']: + return None + + if self.current_user_model is None: + username = self.get_current_user()['name'] + resp = await api_request( + f'/users/{username}', + method='GET', + ) + self.current_user_model = json.loads(resp.body.decode('utf-8')) + + return self.current_user_model + return providers[provider_prefix]( - config=self.settings['traitlets_config'], - spec=spec, - handler=self) + config=self.settings['traitlets_config'], + spec=spec, + user_model=await get_current_user_model() + ) def get_badge_base_url(self): badge_base_url = self.settings['badge_base_url'] diff --git a/binderhub/builder.py b/binderhub/builder.py index bb727823d..325f5deb6 100644 --- a/binderhub/builder.py +++ b/binderhub/builder.py @@ -224,7 +224,7 @@ async def get(self, provider_prefix, _unescaped_spec): # get a provider object that encapsulates the provider and the spec try: - provider = self.get_provider(provider_prefix, spec=spec) + provider = await self.get_provider(provider_prefix, spec=spec) except Exception as e: app_log.exception("Failed to get provider for %s", key) await self.fail(str(e)) diff --git a/binderhub/main.py b/binderhub/main.py index 329634238..0974d1c7a 100755 --- a/binderhub/main.py +++ b/binderhub/main.py @@ -47,7 +47,7 @@ async def get(self, provider_prefix, _unescaped_spec): spec = self.get_spec_from_request(prefix) spec = spec.rstrip("/") try: - self.get_provider(provider_prefix, spec=spec) + await self.get_provider(provider_prefix, spec=spec) except HTTPError: raise except Exception as e: From d0c8c7edea840241ff733e73241d4a073228cf6e Mon Sep 17 00:00:00 2001 From: Romain PRIMET Date: Tue, 29 Sep 2020 15:53:38 +0200 Subject: [PATCH 3/4] pass handler to repo provider --- binderhub/base.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/binderhub/base.py b/binderhub/base.py index 59535a9a7..758e5323c 100644 --- a/binderhub/base.py +++ b/binderhub/base.py @@ -78,7 +78,9 @@ def get_provider(self, provider_prefix, spec): raise web.HTTPError(404, "No provider found for prefix %s" % provider_prefix) return providers[provider_prefix]( - config=self.settings['traitlets_config'], spec=spec) + config=self.settings['traitlets_config'], + spec=spec, + handler=self) def get_badge_base_url(self): badge_base_url = self.settings['badge_base_url'] From 748e13d4cd11485d12cf191ba6101ea6e7df0c9f Mon Sep 17 00:00:00 2001 From: Adrien DELSALLE Date: Fri, 19 Feb 2021 16:40:35 +0100 Subject: [PATCH 4/4] fetch current user credential only once --- binderhub/base.py | 42 ++++++++++++++++++++++++++++++++++++++---- binderhub/builder.py | 2 +- binderhub/main.py | 2 +- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/binderhub/base.py b/binderhub/base.py index 758e5323c..89777dd96 100644 --- a/binderhub/base.py +++ b/binderhub/base.py @@ -2,10 +2,13 @@ import json from ipaddress import ip_address +import os from http.client import responses from tornado import web from tornado.log import app_log +from tornado.httpclient import AsyncHTTPClient, HTTPRequest, HTTPError + from jupyterhub.services.auth import HubOAuthenticated, HubOAuth from . import __version__ as binder_version @@ -19,6 +22,7 @@ def initialize(self): super().initialize() if self.settings['auth_enabled']: self.hub_auth = HubOAuth.instance(config=self.settings['traitlets_config']) + self.current_user_model = None def prepare(self): super().prepare() @@ -71,16 +75,46 @@ def get_spec_from_request(self, prefix): spec = self.request.path[idx + len(prefix) + 1:] return spec - def get_provider(self, provider_prefix, spec): + async def get_provider(self, provider_prefix, spec): """Construct a provider object""" providers = self.settings['repo_providers'] if provider_prefix not in providers: raise web.HTTPError(404, "No provider found for prefix %s" % provider_prefix) + async def api_request(url, *args, **kwargs): + headers = kwargs.setdefault('headers', {}) + headers.update({'Authorization': 'token %s' % self.hub_auth.api_token}) + hub_api_url = os.getenv('JUPYTERHUB_API_URL', '') or self.hub_auth.api_url + request_url = hub_api_url + url + req = HTTPRequest(request_url, *args, **kwargs) + + try: + return await AsyncHTTPClient().fetch(req) + except HTTPError as e: + app_log.error("Error accessing Hub API (using %s): %s", request_url, e) + + async def get_current_user_model(): + """Get the current user model. + The user auth_state is only accessible to admin users. + """ + if not self.settings['auth_enabled']: + return None + + if self.current_user_model is None: + username = self.get_current_user()['name'] + resp = await api_request( + f'/users/{username}', + method='GET', + ) + self.current_user_model = json.loads(resp.body.decode('utf-8')) + + return self.current_user_model + return providers[provider_prefix]( - config=self.settings['traitlets_config'], - spec=spec, - handler=self) + config=self.settings['traitlets_config'], + spec=spec, + user_model=await get_current_user_model() + ) def get_badge_base_url(self): badge_base_url = self.settings['badge_base_url'] diff --git a/binderhub/builder.py b/binderhub/builder.py index 8998aad95..b1fd87e2a 100644 --- a/binderhub/builder.py +++ b/binderhub/builder.py @@ -229,7 +229,7 @@ async def get(self, provider_prefix, _unescaped_spec): # get a provider object that encapsulates the provider and the spec try: - provider = self.get_provider(provider_prefix, spec=spec) + provider = await self.get_provider(provider_prefix, spec=spec) except Exception as e: app_log.exception("Failed to get provider for %s", key) await self.fail(str(e)) diff --git a/binderhub/main.py b/binderhub/main.py index 329634238..0974d1c7a 100755 --- a/binderhub/main.py +++ b/binderhub/main.py @@ -47,7 +47,7 @@ async def get(self, provider_prefix, _unescaped_spec): spec = self.get_spec_from_request(prefix) spec = spec.rstrip("/") try: - self.get_provider(provider_prefix, spec=spec) + await self.get_provider(provider_prefix, spec=spec) except HTTPError: raise except Exception as e: