Repository: jupyterhub/jupyterhub

Issue:
Setup allowing any Github user access
Hi there,

Trying to use Jupyterhub for a talk I'm giving on Thursday. I've basically setup a server and want anyone who is in the room to be able to login to the server using their github credentials. Is this possible? It seems possible to automatically add system users but not github users. Having no whitelist allows any Github user to login but once they hit "_Start my Server_" they will get a 500 and I'll see the error `KeyError: 'getpwnam(): name not found: GITHUB_USERNAME'`

Here is my current config:

```
# Github Settings
c.GitHubOAuthenticator.client_id = os.environ['GITHUB_CLIENT_ID']
c.GitHubOAuthenticator.client_secret = os.environ['GITHUB_CLIENT_SECRET']
c.GitHubOAuthenticator.oauth_callback_url = os.environ['OAUTH_CALLBACK_URL']

# Authentication Setup
c.JupyterHub.authenticator_class = 'oauthenticator.LocalGitHubOAuthenticator'
c.LocalGitHubOAuthenticator.create_system_users = True

# Create empty whitelist and setup admin users
c.Authenticator.whitelist = whitelist = set()
c.Authenticator.admin_users = admin = set()
c.JupyterHub.admin_access = True

# Use userlist to determine administrators
with open(join(here, 'userlist')) as f:
    for line in f:
        if not line:
            continue
        parts = line.split()
        name = parts[0]
        if len(parts) > 1 and parts[1] == 'admin':
            admin.add(name)
```

I'd appreciate any feedback of how to approach achieving what I want.



Discussion:

Resolving patch:
diff --git a/jupyterhub/app.py b/jupyterhub/app.py
--- a/jupyterhub/app.py
+++ b/jupyterhub/app.py
@@ -677,6 +677,7 @@ def init_users(self):
             self.authenticator.normalize_username(name)
             for name in self.authenticator.admin_users
         ]
+        self.authenticator.admin_users = set(admin_users) # force normalization
         for username in admin_users:
             if not self.authenticator.validate_username(username):
                 raise ValueError("username %r is not valid" % username)
@@ -704,6 +705,7 @@ def init_users(self):
             self.authenticator.normalize_username(name)
             for name in self.authenticator.whitelist
         ]
+        self.authenticator.whitelist = set(whitelist) # force normalization
         for username in whitelist:
             if not self.authenticator.validate_username(username):
                 raise ValueError("username %r is not valid" % username)
@@ -719,24 +721,21 @@ def init_users(self):
                 new_users.append(user)
                 db.add(user)
 
-        if whitelist:
-            # fill the whitelist with any users loaded from the db,
-            # so we are consistent in both directions.
-            # This lets whitelist be used to set up initial list,
-            # but changes to the whitelist can occur in the database,
-            # and persist across sessions.
-            for user in db.query(orm.User):
-                self.authenticator.whitelist.add(user.name)
+        db.commit()
+
+        # Notify authenticator of all users.
+        # This ensures Auth whitelist is up-to-date with the database.
+        # This lets whitelist be used to set up initial list,
+        # but changes to the whitelist can occur in the database,
+        # and persist across sessions.
+        for user in db.query(orm.User):
+            yield gen.maybe_future(self.authenticator.add_user(user))
+        db.commit() # can add_user touch the db?
 
         # The whitelist set and the users in the db are now the same.
         # From this point on, any user changes should be done simultaneously
         # to the whitelist set and user db, unless the whitelist is empty (all users allowed).
 
-        db.commit()
-        
-        for user in new_users:
-            yield gen.maybe_future(self.authenticator.add_user(user))
-        db.commit()
     
     @gen.coroutine
     def init_spawners(self):
diff --git a/jupyterhub/handlers/base.py b/jupyterhub/handlers/base.py
--- a/jupyterhub/handlers/base.py
+++ b/jupyterhub/handlers/base.py
@@ -200,6 +200,7 @@ def user_from_username(self, username):
             self.db.add(u)
             self.db.commit()
             user = self._user_from_orm(u)
+            self.authenticator.add_user(user)
         return user
     
     def clear_login_cookie(self, name=None):


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
@@ -222,6 +222,16 @@ def test_logout(app):
     assert r.cookies == {}
 
 
+def test_login_no_whitelist_adds_user(app):
+    auth = app.authenticator
+    mock_add_user = mock.Mock()
+    with mock.patch.object(auth, 'add_user', mock_add_user):
+        cookies = app.login_user('jubal')
+
+    user = app.users['jubal']
+    assert mock_add_user.mock_calls == [mock.call(user)]
+
+
 def test_static_files(app):
     base_url = ujoin(public_url(app), app.hub.server.base_url)
     print(base_url)