internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: jupyterhub/jupyterhub

Issue:
Passwords beginning or ending with a whitespace are not supported
Due to POST argument stripping, passwords with a beginning or ending whitespace are not allowed.

**How to reproduce the issue**
Set up a user password with an ending or beginning whitespace.

**What you expected to happen**
The user should be allowed to login with the password, given that the password should be any complicated sequence of characters the user can reproduce.

**What actually happens**
The user is denied access, because the LoginHandler will strip all posted values before considering the password for authentication (line 81, get_argument has a default "strip=True")

**Share what version of JupyterHub you are using**
HEAD (006488fc749923851df97d47d8850bdf5fd157cf)





Discussion:

Resolving patch:
diff --git a/jupyterhub/handlers/login.py b/jupyterhub/handlers/login.py
--- a/jupyterhub/handlers/login.py
+++ b/jupyterhub/handlers/login.py
@@ -78,7 +78,7 @@ def post(self):
         # parse the arguments dict
         data = {}
         for arg in self.request.arguments:
-            data[arg] = self.get_argument(arg)
+            data[arg] = self.get_argument(arg, strip=False)
 
         auth_timer = self.statsd.timer('login.authenticate').start()
         username = yield self.authenticate(data)


Test patch:
diff --git a/jupyterhub/tests/test_pages.py b/jupyterhub/tests/test_pages.py
--- a/jupyterhub/tests/test_pages.py
+++ b/jupyterhub/tests/test_pages.py
@@ -3,6 +3,7 @@
 from urllib.parse import urlencode, urlparse
 
 import requests
+from tornado import gen
 
 from ..handlers import BaseHandler
 from ..utils import url_path_join as ujoin
@@ -231,6 +232,27 @@ def test_login_fail(app):
     assert not r.cookies
 
 
+def test_login_strip(app):
+    """Test that login form doesn't strip whitespace from passwords"""
+    form_data = {
+        'username': 'spiff',
+        'password': ' space man ',
+    }
+    base_url = public_url(app)
+    called_with = []
+    @gen.coroutine
+    def mock_authenticate(handler, data):
+        called_with.append(data)
+
+    with mock.patch.object(app.authenticator, 'authenticate', mock_authenticate):
+        r = requests.post(base_url + 'hub/login',
+            data=form_data,
+            allow_redirects=False,
+        )
+    
+    assert called_with == [form_data]
+
+
 def test_login_redirect(app, io_loop):
     cookies = app.login_user('river')
     user = app.users['river']
Repository: jupyterhub/jupyterhub

Issue:
check_routes should be more aggressive + be called 
Now that the proxy implementations should only return routes that are associated with JupyterHub, we should prune routes we don't know about (this helps with 'redirected too many times' error perhaps?).

We should also do this once on startup.


Discussion:

Resolving patch:
diff --git a/jupyterhub/app.py b/jupyterhub/app.py
--- a/jupyterhub/app.py
+++ b/jupyterhub/app.py
@@ -1473,9 +1473,8 @@ def start(self):
                         break
                 else:
                     self.log.error("Cannot connect to %s service %s at %s. Is it running?", service.kind, service_name, service.url)
-
-        loop.add_callback(self.proxy.add_all_users, self.users)
-        loop.add_callback(self.proxy.add_all_services, self._service_map)
+        
+        yield self.proxy.check_routes(self.users, self._service_map)
 
 
         if self.service_check_interval and any(s.url for s in self._service_map.values()):
diff --git a/jupyterhub/proxy.py b/jupyterhub/proxy.py
--- a/jupyterhub/proxy.py
+++ b/jupyterhub/proxy.py
@@ -290,6 +290,8 @@ def check_routes(self, user_dict, service_dict, routes=None):
         futures = []
         db = self.db
 
+        good_routes = {'/'}
+
         if '/' not in routes:
             self.log.warning("Adding missing default route")
             self.add_hub_route(self.app.hub)
@@ -297,16 +299,11 @@ def check_routes(self, user_dict, service_dict, routes=None):
         for orm_user in db.query(User):
             user = user_dict[orm_user]
             if user.running:
+                good_routes.add(user.proxy_spec)
                 if user.name not in user_routes:
                     self.log.warning(
                         "Adding missing route for %s (%s)", user.name, user.server)
                     futures.append(self.add_user(user))
-            else:
-                # User not running, make sure it's not in the table
-                if user.name in user_routes:
-                    self.log.warning(
-                        "Removing route for not running %s", user.name)
-                    futures.append(self.delete_user(user))
 
         # check service routes
         service_routes = {r['data']['service']
@@ -321,10 +318,18 @@ def check_routes(self, user_dict, service_dict, routes=None):
                 self.log.error(
                     "Service %s has no server, but wasn't filtered out.", service)
                 continue
+            good_routes.add(service.proxy_spec)
             if service.name not in service_routes:
                 self.log.warning("Adding missing route for %s (%s)",
                                  service.name, service.server)
                 futures.append(self.add_service(service))
+
+        # Now delete the routes that shouldn't be there
+        for routespec in routes:
+            if routespec not in good_routes:
+                self.log.warning("Deleting stale route %s", routespec)
+                futures.append(self.delete_route(routespec))
+
         for f in futures:
             yield f
 


Test patch:
Repository: jupyterhub/jupyterhub

Issue:
Hub should know which route actually points back to it in proxy.add_hub_route
Currently it is:

```
        return self.add_route('/', self.hub.host, {'hub': True})
```

which when running on k8s leads to the hub telling the proxy:

```
[I 2017-07-14 02:56:03.191 JupyterHub proxy:338] Adding default route for Hub: / => http://hub-deployment-1084503180-rzv5m:8081
```

But that's the wrong IP to connect to, since it's using a hostname rather than an IP address. In fact, the current IP address is also wrong, since it needs to be the hub service IP rather than the hub pod IP (since the hub pod can be restarted independently of the proxy pod).

I *think* hub_connect_ip is the right thing to use here?


Discussion:
It looks like hub_connect_ip should be used here already according to the code, but it isn't.

Resolving patch:
diff --git a/jupyterhub/app.py b/jupyterhub/app.py
--- a/jupyterhub/app.py
+++ b/jupyterhub/app.py
@@ -412,7 +412,7 @@ def _deprecated_proxy_api(self, change):
 
         .. versionadded:: 0.8
         """
-    )
+    ).tag(config=True)
     hub_prefix = URLPrefix('/hub/',
         help="The prefix for the hub server.  Always /base_url/hub/"
     )


Test patch:
Repository: jupyterhub/jupyterhub

Issue:
Error messages about managed cull-idle server
Lots of messages in log about 
```
[E 2017-07-14 03:18:49.501 JupyterHub proxy:319] Service <Service(name=cull-idle managed)> has no server, but wasn't filtered out.
```

Not sure why - the cull-idle service doesn't make an entry into the proxy...


Discussion:

Resolving patch:
diff --git a/jupyterhub/proxy.py b/jupyterhub/proxy.py
--- a/jupyterhub/proxy.py
+++ b/jupyterhub/proxy.py
@@ -308,8 +308,7 @@ def check_routes(self, user_dict, service_dict, routes=None):
         # check service routes
         service_routes = {r['data']['service']
                           for r in routes.values() if 'service' in r['data']}
-        for orm_service in db.query(Service).filter(
-                Service.server is not None):
+        for orm_service in db.query(Service).filter(Service.server != None):
             service = service_dict[orm_service.name]
             if service.server is None:
                 # This should never be True, but seems to be on rare occasion.


Test patch:
Repository: jupyterhub/jupyterhub

Issue:
Handle case where JupyterHub.hub_connect_ip or port changes between restarts
At startup we check if we have a proxy route for the hub and if not, we add one. However, we don't check if the route is *accurate*. So if we change hub_connect_ip or port between restarts, the proxy route will never be updated.


Discussion:

Resolving patch:
diff --git a/jupyterhub/proxy.py b/jupyterhub/proxy.py
--- a/jupyterhub/proxy.py
+++ b/jupyterhub/proxy.py
@@ -295,9 +295,15 @@ def check_routes(self, user_dict, service_dict, routes=None):
 
         good_routes = {'/'}
 
+        hub = self.app.hub
         if '/' not in routes:
             self.log.warning("Adding missing default route")
-            self.add_hub_route(self.app.hub)
+            futures.append(self.add_hub_route(hub))
+        else:
+            route = routes['/']
+            if route['target'] != hub.host:
+                self.log.warning("Updating default route %s → %s", route['target'], hub.host)
+                futures.append(self.add_hub_route(hub))
 
         for orm_user in db.query(User):
             user = user_dict[orm_user]
@@ -309,11 +315,19 @@ def check_routes(self, user_dict, service_dict, routes=None):
                         self.log.warning(
                             "Adding missing route for %s (%s)", spec, spawner.server)
                         futures.append(self.add_user(user, name))
+                    else:
+                        route = routes[spec]
+                        if route['target'] != spawner.server.host:
+                            self.log.warning(
+                                "Updating route for %s (%s → %s)",
+                                spec, route['target'], spawner.server,
+                            )
+                            futures.append(self.add_user(user, name))
                 elif spawner._proxy_pending:
                     good_routes.add(user.proxy_spec(name))
 
         # check service routes
-        service_routes = {r['data']['service']
+        service_routes = {r['data']['service']: r
                           for r in routes.values() if 'service' in r['data']}
         for orm_service in db.query(Service).filter(Service.server != None):
             service = service_dict[orm_service.name]
@@ -329,6 +343,14 @@ def check_routes(self, user_dict, service_dict, routes=None):
                 self.log.warning("Adding missing route for %s (%s)",
                                  service.name, service.server)
                 futures.append(self.add_service(service))
+            else:
+                route = service_routes[service.name]
+                if route['target'] != service.server.host:
+                    self.log.warning(
+                        "Updating route for %s (%s → %s)",
+                        route['routespec'], route['target'], spawner.server.host,
+                    )
+                    futures.append(self.add_service(service))
 
         # Now delete the routes that shouldn't be there
         for routespec in routes:


Test patch:
Repository: jupyterhub/jupyterhub

Issue:
Use of scoped_session instead of plain session
Since `jupyterhub` uses only one instance of session [produced by](https://github.com/jupyterhub/jupyterhub/blob/0b34e13dd48bc73c8696eb63cde11b318b8f05a5/jupyterhub/app.py#L883) `scoped_session`, why not to use plain session produced by `session_maker`? 
@zzzeek in [this](https://stackoverflow.com/questions/8674984/sqlalchemytornado-how-to-create-a-scopefunc-for-sqlalchemys-scopedsession/8705676#comment10785115_8674984) comment dissuades using `scoped_session`s if not needed. 


Discussion:
This was added purely because the JupyterHub tests were running in a different thread and also talking to the database. scoped_session was used to ensure that sessions were thread-local. That was just reverted in #1281, so we no longer need scoped_session.

Resolving patch:
diff --git a/jupyterhub/app.py b/jupyterhub/app.py
--- a/jupyterhub/app.py
+++ b/jupyterhub/app.py
@@ -24,7 +24,6 @@
 from jinja2 import Environment, FileSystemLoader
 
 from sqlalchemy.exc import OperationalError
-from sqlalchemy.orm import scoped_session
 
 from tornado.httpclient import AsyncHTTPClient
 import tornado.httpserver
@@ -879,8 +878,7 @@ def init_db(self):
                 echo=self.debug_db,
                 **self.db_kwargs
             )
-            # trigger constructing thread local db property
-            self.db = scoped_session(self.session_factory)()
+            self.db = self.session_factory()
         except OperationalError as e:
             self.log.error("Failed to connect to db: %s", self.db_url)
             self.log.debug("Database error was:", exc_info=True)


Test patch:
Repository: jupyterhub/jupyterhub

Issue:
update docs for oauth
- [x] update descriptions of how auth works
- [x] update service docs, pointing to new OAuth classes (existing docs / cookie-based auth still works)
- [ ] add new services.auth.OAuth classes to API docs



Discussion:

Resolving patch:
diff --git a/jupyterhub/services/auth.py b/jupyterhub/services/auth.py
--- a/jupyterhub/services/auth.py
+++ b/jupyterhub/services/auth.py
@@ -1,12 +1,14 @@
-"""Authenticating services with JupyterHub
+"""Authenticating services with JupyterHub.
 
-Cookies are sent to the Hub for verification, replying with a JSON model describing the authenticated user.
+Cookies are sent to the Hub for verification. The Hub replies with a JSON
+model describing the authenticated user.
 
-HubAuth can be used in any application, even outside tornado.
+``HubAuth`` can be used in any application, even outside tornado.
 
-HubAuthenticated is a mixin class for tornado handlers that should authenticate with the Hub.
-"""
+``HubAuthenticated`` is a mixin class for tornado handlers that should
+authenticate with the Hub.
 
+"""
 import os
 import re
 import socket
diff --git a/jupyterhub/services/service.py b/jupyterhub/services/service.py
--- a/jupyterhub/services/service.py
+++ b/jupyterhub/services/service.py
@@ -1,15 +1,14 @@
-"""A service is a process that talks to JupyterHub
+"""A service is a process that talks to JupyterHub.
 
-Cases:
-
-Managed:
-  - managed by JupyterHub (always subprocess, no custom Spawners)
-  - always a long-running process
-  - managed services are restarted automatically if they exit unexpectedly
-Unmanaged:
-  - managed by external service (docker, systemd, etc.)
-  - do not need to be long-running processes, or processes at all
+Types of services:
+    Managed:
+      - managed by JupyterHub (always subprocess, no custom Spawners)
+      - always a long-running process
+      - managed services are restarted automatically if they exit unexpectedly
 
+    Unmanaged:
+      - managed by external service (docker, systemd, etc.)
+      - do not need to be long-running processes, or processes at all
 
 URL: needs a route added to the proxy.
   - Public route will always be /services/service-name
@@ -30,13 +29,14 @@
         'api_token': 'super-secret',
     }
 
-A hub-managed service with no URL:
+A hub-managed service with no URL::
 
     {
         'name': 'cull-idle',
         'command': ['python', '/path/to/cull-idle']
         'admin': True,
     }
+
 """
 
 import pipes


Test patch:
Repository: jupyterhub/jupyterhub

Issue:
PrefixRedirectUrl redirect `/hub` to `/hub/hub`
It might be an edge case, which is not really important, 
but I expected `/hub` -> `/hub/` and not `/hub/hub`. This is to to `uri.startswith(self.base_url)`, and `base_url` is guarantied to end with a `/`. Now of course we can't just strip the trailing slash from `base_url` or things like `/hubot` will not be redirected to `/hub/hubot`, and doing nothing may be the right answer. 


Discussion:

Resolving patch:
diff --git a/jupyterhub/handlers/pages.py b/jupyterhub/handlers/pages.py
--- a/jupyterhub/handlers/pages.py
+++ b/jupyterhub/handlers/pages.py
@@ -237,7 +237,7 @@ def get(self, status_code_s):
 
 
 default_handlers = [
-    (r'/', RootHandler),
+    (r'/?', RootHandler),
     (r'/home', HomeHandler),
     (r'/admin', AdminHandler),
     (r'/spawn', SpawnHandler),


Test patch:
Repository: jupyterhub/jupyterhub

Issue:
Singleuser errors with usernames containing special chars
Using DummyAuthenticator and username (gunjan_baid@berkeley.edu), singleuser server fails with:

```
   Traceback (most recent call last):
      File "/srv/app/venv/lib/python3.6/site-packages/tornado/web.py", line 1509, in _execute
        result = method(*self.path_args, **self.path_kwargs)
      File "/srv/app/venv/lib/python3.6/site-packages/jupyterhub/singleuser.py", line 129, in get
        self.hub_auth.set_cookie(self, token)
      File "/srv/app/venv/lib/python3.6/site-packages/jupyterhub/services/auth.py", line 492, in set_cookie
        **kwargs
      File "/srv/app/venv/lib/python3.6/site-packages/tornado/web.py", line 619, in set_secure_cookie
        expires_days=expires_days, **kwargs)
      File "/srv/app/venv/lib/python3.6/site-packages/tornado/web.py", line 548, in set_cookie
        self._new_cookie[name] = value
      File "/usr/lib/python3.6/http/cookies.py", line 521, in __setitem__
        self.__set(key, rval, cval)
      File "/usr/lib/python3.6/http/cookies.py", line 511, in __set
        M.set(key, real_value, coded_value)
      File "/usr/lib/python3.6/http/cookies.py", line 380, in set
        raise CookieError('Illegal key %r' % (key,))
    http.cookies.CookieError: Illegal key 'user-gunjan_baid@berkeley.edu'
```

repeatedly.


Discussion:
Is this with 0.7 or master? What Spawner? I don't think usernames are used in cookie names in master.
Yup, this is master and kubespawner.
I can repro this on master with the following simple config: 


```
c.JupyterHub.spawner_class = 'simplespawner.SimpleLocalProcessSpawner'

c.JupyterHub.authenticator_class = 'dummyauthenticator.DummyAuthenticator'
```
The cookie name it was trying to set was `user-yuvipanda@gmail.com`. Not sure where that's being set from...

Resolving patch:
diff --git a/jupyterhub/user.py b/jupyterhub/user.py
--- a/jupyterhub/user.py
+++ b/jupyterhub/user.py
@@ -343,9 +343,11 @@ def spawn(self, server_name='', options=None):
         # create API and OAuth tokens
         spawner.api_token = api_token
         spawner.admin_access = self.settings.get('admin_access', False)
-        client_id = 'user-%s' % self.escaped_name
+        # use fully quoted name for client_id because it will be used in cookie-name
+        # self.escaped_name may contain @ which is legal in URLs but not cookie keys
+        client_id = 'user-%s' % quote(self.name)
         if server_name:
-            client_id = '%s-%s' % (client_id, server_name)
+            client_id = '%s-%s' % (client_id, quote(server_name))
         spawner.oauth_client_id = client_id
         oauth_provider = self.settings.get('oauth_provider')
         if oauth_provider:


Test patch:
Repository: jupyterhub/jupyterhub

Issue:
Control Panel button is slightly bigger than Logout button in notebook and terminal view
**How to reproduce the issue**
Login as admin and open a notebook or terminal window

**What you expected to happen**
All buttons in header-container have the same height.

**What actually happens**
Control Panel button is 4 pixels higher than the Logout button.

![image](https://user-images.githubusercontent.com/160577/30484059-778017b2-9a29-11e7-8bf9-a5bb22cc0f4c.png)


**Share what version of JupyterHub you are using**

```
jupyterhub==0.7.2
notebook==5.1.0
```




Discussion:

Resolving patch:
diff --git a/jupyterhub/singleuser.py b/jupyterhub/singleuser.py
--- a/jupyterhub/singleuser.py
+++ b/jupyterhub/singleuser.py
@@ -144,11 +144,13 @@ def hub_auth(self):
 {% block header_buttons %}
 {{super()}}
 
-<a href='{{hub_control_panel_url}}'
- class='btn btn-default btn-sm navbar-btn pull-right'
- style='margin-right: 4px; margin-left: 2px;'
->
-Control Panel</a>
+<span>
+    <a href='{{hub_control_panel_url}}'
+       class='btn btn-default btn-sm navbar-btn pull-right'
+       style='margin-right: 4px; margin-left: 2px;'>
+        Control Panel
+    </a>
+</span>
 {% endblock %}
 {% block logo %}
 <img src='{{logo_url}}' alt='Jupyter Notebook'/>


Test patch:
Repository: jupyterhub/jupyterhub

Issue:
Exception when logout with auto_login=True
**How to reproduce the issue**
Press logout button

**What you expected to happen**
Shows logout.html

**What actually happens**
Exception:
```
    Traceback (most recent call last):
      File "/usr/local/lib/python3.5/dist-packages/tornado/web.py", line 1509, in _execute
        result = method(*self.path_args, **self.path_kwargs)
      File "/usr/local/lib/python3.5/dist-packages/jupyterhub/handlers/login.py", line 23, in get
        self.render('logout.html')
      File "/usr/local/lib/python3.5/dist-packages/tornado/web.py", line 724, in render
        html = self.render_string(template_name, **kwargs)
      File "/usr/local/lib/python3.5/dist-packages/tornado/web.py", line 857, in render_string
        if template_path not in RequestHandler._template_loaders:
    TypeError: unhashable type: 'list'
```

**Share what version of JupyterHub you are using**
Jupyterhub 0.8 with auto_login=True

Running `jupyter troubleshoot` from the command line, if possible, and posting
its output would also be helpful.

```
$PATH:
	/usr/local/bin
	/usr/local/sbin
	/usr/local/bin
	/usr/sbin
	/usr/bin
	/sbin
	/bin

sys.path:
	/usr/local/bin
	/srv/jupyterhub
	/usr/lib/python35.zip
	/usr/lib/python3.5
	/usr/lib/python3.5/plat-x86_64-linux-gnu
	/usr/lib/python3.5/lib-dynload
	/usr/local/lib/python3.5/dist-packages
	/usr/lib/python3/dist-packages

sys.executable:
	/usr/bin/python3

sys.version:
	3.5.3 (default, Jan 19 2017, 14:11:04)
	[GCC 6.3.0 20170118]

platform.platform():
	Linux-4.4.52+-x86_64-with-Ubuntu-17.04-zesty

which -a jupyter:
	/usr/local/bin/jupyter
	/usr/local/bin/jupyter
```



Discussion:

Resolving patch:
diff --git a/jupyterhub/handlers/login.py b/jupyterhub/handlers/login.py
--- a/jupyterhub/handlers/login.py
+++ b/jupyterhub/handlers/login.py
@@ -20,7 +20,7 @@ def get(self):
             self.clear_login_cookie()
             self.statsd.incr('logout')
         if self.authenticator.auto_login:
-            self.render('logout.html')
+            self.render_template('logout.html')
         else:
             self.redirect(self.settings['login_url'], permanent=False)
 


Test patch:
diff --git a/jupyterhub/tests/test_pages.py b/jupyterhub/tests/test_pages.py
--- a/jupyterhub/tests/test_pages.py
+++ b/jupyterhub/tests/test_pages.py
@@ -344,6 +344,19 @@ def get(self):
         r = yield async_requests.get(base_url)
     assert r.url == public_url(app, path='hub/dummy')
 
+@pytest.mark.gen_test
+def test_auto_login_logout(app):
+    name = 'burnham'
+    cookies = yield app.login_user(name)
+
+    with mock.patch.dict(app.tornado_application.settings, {
+        'authenticator': Authenticator(auto_login=True),
+    }):
+        r = yield async_requests.get(public_host(app) + app.tornado_settings['logout_url'], cookies=cookies)
+    r.raise_for_status()
+    logout_url = public_host(app) + app.tornado_settings['logout_url']
+    assert r.url == logout_url
+    assert r.cookies == {}
 
 @pytest.mark.gen_test
 def test_logout(app):