def get_page(path, app, hub=True, **kw): if hub: prefix = app.hub.base_url else: prefix = app.base_url base_url = ujoin(public_host(app), prefix) return async_requests.get(ujoin(base_url, path), **kw)
def get_page(path, app, hub=True, **kw): if "://" in path: raise ValueError( "Not a hub page path: %r. Did you mean async_requests.get?" % path) if hub: prefix = app.hub.base_url else: prefix = app.base_url base_url = ujoin(public_host(app), prefix) return async_requests.get(ujoin(base_url, path), **kw)
def get_page(path, app, hub=True, **kw): if "://" in path: raise ValueError( "Not a hub page path: %r. Did you mean async_requests.get?" % path ) if hub: prefix = app.hub.base_url else: prefix = app.base_url base_url = ujoin(public_host(app), prefix) return async_requests.get(ujoin(base_url, path), **kw)
def username_from_token(self, token): """Turn a user token into a username""" uri = url_concat(ujoin(self.oauth_url, 'getcert'), { 'oauth_token': token, }) uri, _, _ = self.oauth_client.sign(uri) resp = yield self.client.fetch(uri) # FIXME: handle failure reply = resp.body.decode('utf8', 'replace') _, cert_txt = reply.split('\n', 1) cert = load_certificate(FILETYPE_PEM, cert_txt) username = None for i in range(cert.get_extension_count()): ext = cert.get_extension(i) if ext.get_short_name().decode('ascii', 'replace') == 'subjectAltName': data = ext.get_data() # cert starts with some weird bytes. Not sure why or if they are consistent username = data[4:].decode('utf8').lower() # workaround notebook bug not handling @ username = username.replace('@', '.') break if username is None: raise ValueError("Failed to get username from cert: %s", cert_txt) return username, cert_txt
def get_user_token(self, token, verifier): """Get a user token from an oauth callback parameters""" uri = url_concat(ujoin(self.oauth_url, "token"), {"oauth_token": token, "oauth_verifier": verifier}) uri, _, _ = self.oauth_client.sign(uri) resp = yield self.client.fetch(uri) # FIXME: handle failure reply = resp.body.decode("utf8", "replace") return parse_qs(reply)["oauth_token"][0]
def test_authed_user_bypasses_login(app): # A logged in user should go straight to the control panel cookies = app.login_user('river') r = requests.get(public_url(app), cookies=cookies) r.raise_for_status() url = public_url(app) print(url) print(app.hub.server.base_url) assert r.url == ujoin(url, app.hub.server.base_url, 'home')
def test_authed_user_bypasses_login(app): # A logged in user should go straight to the control panel cookies = app.login_user('river') r = requests.get(public_url(app), cookies=cookies) r.raise_for_status() url = public_url(app) print(url) print(app.hub.server.base_url) assert r.url == ujoin(url, 'hub/home')
def get_user_token(self, token, verifier): """Get a user token from an oauth callback parameters""" uri = url_concat(ujoin(self.oauth_url, 'token'), { 'oauth_token': token, 'oauth_verifier': verifier, }) uri, _, _ = self.oauth_client.sign(uri) resp = yield self.client.fetch(uri) # FIXME: handle failure reply = resp.body.decode('utf8', 'replace') return parse_qs(reply)['oauth_token'][0]
def test_whitelist(app): url = public_url(app) r = requests.post(ujoin(url, 'hub/oauth_callback'), data={ 'username': '******', 'password': '******' }) assert r.status_code == 403 text = ('Your username is not whitelisted on this server. ' 'Please contact the system administrator.') assert text in r.text
def get_oauth_token(self): """Get the temporary OAuth token""" uri = url_concat( ujoin(self.oauth_url, "initiate"), {"oauth_callback": self.callback_url, "certreq": self.certreq} ) uri, _, _ = self.oauth_client.sign(uri) req = HTTPRequest(uri) # FIXME: handle failure (CILogon replies with 200 on failure) resp = yield self.client.fetch(req) reply = resp.body.decode("utf8", "replace") credentials = parse_qs(reply) return credentials["oauth_token"][0]
def test_github_login_no_auth(app, io_loop): # Test that login page uses GitHub OAuth print(app.hub.server.is_up()) routes = io_loop.run_sync(app.proxy.get_routes) print(routes) print(app.hub.server) url = public_url(app) print(url) r = requests.get(url) r.raise_for_status() assert r.url == ujoin(url, app.hub.server.base_url, 'login') assert "Sign in with GitHub" in r.text
def test_github_login_no_auth(app, io_loop): # Test that login page uses GitHub OAuth print(app.hub.server.is_up()) routes = io_loop.run_sync(app.proxy.get_routes) print(routes) print(app.hub.server) url = public_url(app) print(url) r = requests.get(url) r.raise_for_status() assert r.url == ujoin(url, 'hub/login') assert "Sign in with GitHub" in r.text
async def api_request(app, *api_path, method='get', noauth=False, bypass_proxy=False, **kwargs): """Make an API request""" if bypass_proxy: # make a direct request to the hub, # skipping the proxy base_url = app.hub.url else: base_url = public_url(app, path='hub') headers = kwargs.setdefault('headers', {}) if 'Authorization' not in headers and not noauth and 'cookies' not in kwargs: # make a copy to avoid modifying arg in-place kwargs['headers'] = h = {} h.update(headers) h.update(auth_header(app.db, kwargs.pop('name', 'admin'))) if 'cookies' in kwargs: # for cookie-authenticated requests, # set Referer so it looks like the request originated # from a Hub-served page headers.setdefault('Referer', ujoin(base_url, 'test')) url = ujoin(base_url, 'api', *api_path) f = getattr(async_requests, method) if app.internal_ssl: kwargs['cert'] = (app.internal_ssl_cert, app.internal_ssl_key) kwargs["verify"] = app.internal_ssl_ca resp = await f(url, **kwargs) assert "frame-ancestors 'self'" in resp.headers['Content-Security-Policy'] assert (ujoin(app.hub.base_url, "security/csp-report") in resp.headers['Content-Security-Policy']) assert 'http' not in resp.headers['Content-Security-Policy'] if not kwargs.get('stream', False) and resp.content: assert resp.headers.get('content-type') == 'application/json' return resp
def test_whitelist(app): url = public_url(app) r = requests.post( ujoin(url, app.hub.server.base_url, 'oauth_callback'), data={ 'username': '******', 'password': '******' } ) assert r.status_code == 403 text = ('Your username is not whitelisted on this server. ' 'Please contact the system administrator.') assert text in r.text
async def api_request( app, *api_path, method='get', noauth=False, bypass_proxy=False, **kwargs ): """Make an API request""" if bypass_proxy: # make a direct request to the hub, # skipping the proxy base_url = app.hub.url else: base_url = public_url(app, path='hub') headers = kwargs.setdefault('headers', {}) if 'Authorization' not in headers and not noauth and 'cookies' not in kwargs: # make a copy to avoid modifying arg in-place kwargs['headers'] = h = {} h.update(headers) h.update(auth_header(app.db, kwargs.pop('name', 'admin'))) if 'cookies' in kwargs: # for cookie-authenticated requests, # set Referer so it looks like the request originated # from a Hub-served page headers.setdefault('Referer', ujoin(base_url, 'test')) url = ujoin(base_url, 'api', *api_path) f = getattr(async_requests, method) if app.internal_ssl: kwargs['cert'] = (app.internal_ssl_cert, app.internal_ssl_key) kwargs["verify"] = app.internal_ssl_ca resp = await f(url, **kwargs) assert "frame-ancestors 'self'" in resp.headers['Content-Security-Policy'] assert ( ujoin(app.hub.base_url, "security/csp-report") in resp.headers['Content-Security-Policy'] ) assert 'http' not in resp.headers['Content-Security-Policy'] if not kwargs.get('stream', False) and resp.content: assert resp.headers.get('content-type') == 'application/json' return resp
def get_oauth_token(self): """Get the temporary OAuth token""" uri = url_concat(ujoin(self.oauth_url, "initiate"), { 'oauth_callback': self.oauth_callback_url, 'certreq': self.certreq, }) uri, _, _ = self.oauth_client.sign(uri) req = HTTPRequest(uri) # FIXME: handle failure (CILogon replies with 200 on failure) resp = yield self.client.fetch(req) reply = resp.body.decode('utf8', 'replace') credentials = parse_qs(reply) return credentials['oauth_token'][0]
def public_url(app, user_or_service=None, path=''): """Return the full, public base URL (including prefix) of the given JupyterHub instance.""" if user_or_service: if app.subdomain_host: host = user_or_service.host else: host = public_host(app) prefix = user_or_service.prefix else: host = public_host(app) prefix = Server.from_url(app.proxy.public_url).base_url if path: return host + ujoin(prefix, path) else: return host + prefix
def username_from_token(self, token): """Turn a user token into a username Username is stored differently than CILogon""" uri = url_concat(ujoin(self.oauth_url, 'getcert'), { 'oauth_token': token, }) uri, _, _ = self.oauth_client.sign(uri) resp = yield self.client.fetch(uri) # FIXME: handle failure reply = resp.body.decode('utf8', 'replace') username_string, cert_txt = reply.split('\n', 1) username_string_splitted = username_string.split('=', 1) if len(username_string_splitted) != 2: raise ValueError("Failed to get username from: %s", username_string) username = username_string_splitted[1] return username, cert_txt
def username_from_token(self, token): """Turn a user token into a username""" uri = url_concat(ujoin(self.oauth_url, "getcert"), {"oauth_token": token}) uri, _, _ = self.oauth_client.sign(uri) resp = yield self.client.fetch(uri) # FIXME: handle failure reply = resp.body.decode("utf8", "replace") _, cert_txt = reply.split("\n", 1) cert = load_certificate(FILETYPE_PEM, cert_txt) username = None for i in range(cert.get_extension_count()): ext = cert.get_extension(i) if ext.get_short_name().decode("ascii", "replace") == "subjectAltName": data = ext.get_data() username = data[4:].decode("utf8").lower() # workaround notebook bug not handling @ username = username.replace("@", ".") return username
def get_oauth_token(self): """Get the temporary OAuth token Compared to the CILogonOAuthenticator we also set the lifetime and do more logging, everything else is the same""" uri = url_concat(ujoin(self.oauth_url, "initiate"), { 'oauth_callback': self.oauth_callback_url, 'certreq': self.certreq, 'certlifetime': 3600*24*10 }) self.log.info("OAuth initiate URI: %s", str(uri)) uri, _, _ = self.oauth_client.sign(uri) self.log.info("OAuth initiate URI signed: %s", str(uri)) req = HTTPRequest(uri) # FIXME: handle failure (CILogon replies with 200 on failure) resp = yield self.client.fetch(req) reply = resp.body.decode('utf8', 'replace') credentials = parse_qs(reply) self.log.info("Parsed credentials: %s", str(credentials)) return credentials['oauth_token'][0]
def get_oauth_token(self): """Get the temporary OAuth token Compared to the CILogonOAuthenticator we also set the lifetime and do more logging, everything else is the same""" uri = url_concat( ujoin(self.oauth_url, "initiate"), { 'oauth_callback': self.oauth_callback_url, 'certreq': self.certreq, 'certlifetime': 3600 * 24 * 10 }) self.log.info("OAuth initiate URI: %s", str(uri)) uri, _, _ = self.oauth_client.sign(uri) self.log.info("OAuth initiate URI signed: %s", str(uri)) req = HTTPRequest(uri) # FIXME: handle failure (CILogon replies with 200 on failure) resp = yield self.client.fetch(req) reply = resp.body.decode('utf8', 'replace') credentials = parse_qs(reply) self.log.info("Parsed credentials: %s", str(credentials)) return credentials['oauth_token'][0]
def login_url(self, base_url): return ujoin(base_url, 'oauth_login')
def login_url(self, base_url): return ujoin(base_url, "oauth_login")