Repository: jupyterhub/jupyterhub

Issue:
whoami service example not working
**How to reproduce the issue**

+ Have a running local jupyterhub deployment
+ Validate the `examples/cull-idle` service example is working fine on your en
+ cd to `examples/service-whoami` and simply launch jupyterhub (it will pick the local `jupyterhub_config.py` with the definition of the hub managed whoami services

**What you expected to happen**

+ Authenticate on the hub for a user
+ Ensure the notebook is displayed (you are authenticated - in my case http://localhost:8000/user/datalayer/tree? shows a running notebook)
+ Go to `http://localhost:8000/services/whoami` - It should display the JSON

**What actually happens**

+ In my case, I see `403: Forbidden`

I tried with hub managed or isolated process without luck.

The log on hub side

```
[I 2018-05-01 17:56:37.352 SingleUserNotebookApp log:122] 200 GET /user/datalayer/api/sessions?_=1525189785516&redirects=1 (datalayer@::ffff:127.0.0.1) 6.95ms
[I 2018-05-01 17:56:37.374 SingleUserNotebookApp log:122] 200 GET /user/datalayer/api/contents?type=directory&_=1525189785518 (datalayer@::ffff:127.0.0.1) 5.51ms
[D 2018-05-01 17:56:56.480 JupyterHub log:122] 304 GET /favicon.ico (@::ffff:127.0.0.1) 0.69ms
[W 2018-05-01 17:57:19.430 JupyterHub log:122] 403 GET /hub/api/users (@127.0.0.1) 1.38ms
[D 2018-05-01 17:57:25.974 JupyterHub app:1182] External service whoami running at http://localhost:10101
[D 2018-05-01 17:57:25.975 JupyterHub app:1182] Managed service whoami-oauth running at http://127.0.0.1:10102
```

On service side (after digging a bit into the code, it sounds strange to me that the log says `user is not allowed` - should it not be `service` is not allowed?

```
WARNING:tornado.application:Not allowing Hub user datalayer
WARNING:tornado.general:403 GET /services/whoami (127.0.0.1): user datalayer is not allowed.
WARNING:tornado.access:403 GET /services/whoami (127.0.0.1) 9.34ms
WARNING:tornado.application:Not allowing Hub user datalayer
WARNING:tornado.general:403 GET /services/whoami (127.0.0.1): user datalayer is not allowed.
WARNING:tornado.access:403 GET /services/whoami (127.0.0.1) 14.20ms
```

**Share what version of JupyterHub you are using**

v0.8.1

```
jupyter troubleshoot
DEPRECATION: The default format will switch to columns in the future. You can use --format=(legacy|columns) (or define a format=(legacy|columns) in your pip.conf under the [list] section) to disable this warning.
You are using pip version 9.0.1, however version 10.0.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
$PATH:
	/root/miniconda3/bin
	/root/miniconda3/bin
	/src/platform/sdk/bin
	/hadoop-kerberos/bin
	/opt/jdk/bin
	/usr/local/sbin
	/usr/local/bin
	/usr/sbin
	/usr/bin
	/sbin
	/bin
	/usr/games
	/usr/local/games

sys.path:
	/root/miniconda3/bin
	/root/miniconda3/lib/python36.zip
	/root/miniconda3/lib/python3.6
	/root/miniconda3/lib/python3.6/lib-dynload
	/root/miniconda3/lib/python3.6/site-packages

sys.executable:
	/root/miniconda3/bin/python

sys.version:
	3.6.4 |Anaconda, Inc.| (default, Jan 16 2018, 18:10:19) 
	[GCC 7.2.0]

platform.platform():
	Linux-4.4.0-62-generic-x86_64-with-debian-stretch-sid

which -a jupyter:
	/root/miniconda3/bin/jupyter
	/root/miniconda3/bin/jupyter
	/usr/local/bin/jupyter

pip list:
	alembic (0.9.9)
	asn1crypto (0.24.0)
	backcall (0.1.0)
	bleach (2.1.3)
	certifi (2018.4.16)
	cffi (1.11.4)
	chardet (3.0.4)
	conda (4.4.10)
	cryptography (2.1.4)
	decorator (4.3.0)
	entrypoints (0.2.3)
	html5lib (1.0.1)
	idna (2.6)
	ipykernel (4.8.2)
	ipython (6.3.1)
	ipython-genutils (0.2.0)
	ipywidgets (7.2.1)
	jedi (0.12.0)
	Jinja2 (2.10)
	jsonschema (2.6.0)
	jupyter (1.0.0)
	jupyter-client (5.2.3)
	jupyter-console (5.2.0)
	jupyter-core (4.4.0)
	jupyterhub (0.8.1)
	Mako (1.0.7)
	MarkupSafe (1.0)
	mistune (0.8.3)
	nbconvert (5.3.1)
	nbformat (4.4.0)
	notebook (5.4.1)
	pamela (0.3.0)
	pandocfilters (1.4.2)
	parso (0.2.0)
	pexpect (4.5.0)
	pickleshare (0.7.4)
	pip (9.0.1)
	prompt-toolkit (1.0.15)
	ptyprocess (0.5.2)
	pycosat (0.6.3)
	pycparser (2.18)
	pycurl (7.43.0.1)
	Pygments (2.2.0)
	pyOpenSSL (17.5.0)
	PySocks (1.6.7)
	python-dateutil (2.7.2)
	python-editor (1.0.3)
	python-oauth2 (1.1.0)
	pyzmq (17.0.0)
	qtconsole (4.3.1)
	requests (2.18.4)
	ruamel-yaml (0.15.35)
	Send2Trash (1.5.0)
	setuptools (39.1.0)
	simplegeneric (0.8.1)
	six (1.11.0)
	SQLAlchemy (1.2.7)
	terminado (0.8.1)
	testpath (0.3.1)
	tornado (5.0.2)
	traitlets (4.3.2)
	urllib3 (1.22)
	wcwidth (0.1.7)
	webencodings (0.5.1)
	wheel (0.30.0)
	widgetsnbextension (3.2.1)

conda list:
	# packages in environment at /root/miniconda3:
	#
	# Name                    Version                   Build  Channel
	alembic                   0.9.9                     <pip>
	asn1crypto                0.24.0                   py36_0  
	backcall                  0.1.0                     <pip>
	bleach                    2.1.3                     <pip>
	ca-certificates           2017.08.26           h1d4fec5_0  
	certifi                   2018.1.18                py36_0  
	certifi                   2018.4.16                 <pip>
	cffi                      1.11.4           py36h9745a5d_0  
	chardet                   3.0.4            py36h0f667ec_1  
	conda                     4.4.10                   py36_0  
	conda-env                 2.6.0                h36134e3_1  
	cryptography              2.1.4            py36hd09be54_0  
	decorator                 4.3.0                     <pip>
	entrypoints               0.2.3                     <pip>
	html5lib                  1.0.1                     <pip>
	idna                      2.6              py36h82fb2a8_1  
	ipykernel                 4.8.2                     <pip>
	ipython                   6.3.1                     <pip>
	ipython-genutils          0.2.0                     <pip>
	ipywidgets                7.2.1                     <pip>
	jedi                      0.12.0                    <pip>
	Jinja2                    2.10                      <pip>
	jsonschema                2.6.0                     <pip>
	jupyter                   1.0.0                     <pip>
	jupyter-client            5.2.3                     <pip>
	jupyter-console           5.2.0                     <pip>
	jupyter-core              4.4.0                     <pip>
	jupyterhub                0.8.1                     <pip>
	libedit                   3.1                  heed3624_0  
	libffi                    3.2.1                hd88cf55_4  
	libgcc-ng                 7.2.0                h7cc24e2_2  
	libstdcxx-ng              7.2.0                h7a57d05_2  
	Mako                      1.0.7                     <pip>
	MarkupSafe                1.0                       <pip>
	mistune                   0.8.3                     <pip>
	nbconvert                 5.3.1                     <pip>
	nbformat                  4.4.0                     <pip>
	ncurses                   6.0                  h9df7e31_2  
	notebook                  5.4.1                     <pip>
	openssl                   1.0.2n               hb7f436b_0  
	pamela                    0.3.0                     <pip>
	pandocfilters             1.4.2                     <pip>
	parso                     0.2.0                     <pip>
	pexpect                   4.5.0                     <pip>
	pickleshare               0.7.4                     <pip>
	pip                       9.0.1            py36h6c6f9ce_4  
	prompt-toolkit            1.0.15                    <pip>
	ptyprocess                0.5.2                     <pip>
	pycosat                   0.6.3            py36h0a5515d_0  
	pycparser                 2.18             py36hf9f622e_1  
	pycurl                    7.43.0.1                  <pip>
	Pygments                  2.2.0                     <pip>
	pyopenssl                 17.5.0           py36h20ba746_0  
	pysocks                   1.6.7            py36hd97a5b1_1  
	python                    3.6.4                hc3d631a_1  
	python-dateutil           2.7.2                     <pip>
	python-editor             1.0.3                     <pip>
	python-oauth2             1.1.0                     <pip>
	pyzmq                     17.0.0                    <pip>
	qtconsole                 4.3.1                     <pip>
	readline                  7.0                  ha6073c6_4  
	requests                  2.18.4           py36he2e5f8d_1  
	ruamel_yaml               0.15.35          py36h14c3975_1  
	Send2Trash                1.5.0                     <pip>
	setuptools                38.4.0                   py36_0  
	setuptools                39.1.0                    <pip>
	simplegeneric             0.8.1                     <pip>
	six                       1.11.0           py36h372c433_1  
	SQLAlchemy                1.2.7                     <pip>
	sqlite                    3.22.0               h1bed415_0  
	terminado                 0.8.1                     <pip>
	testpath                  0.3.1                     <pip>
	tk                        8.6.7                hc745277_3  
	tornado                   5.0.2                     <pip>
	traitlets                 4.3.2                     <pip>
	urllib3                   1.22             py36hbe7ace6_0  
	wcwidth                   0.1.7                     <pip>
	webencodings              0.5.1                     <pip>
	wheel                     0.30.0           py36hfd4bba0_1  
	widgetsnbextension        3.2.1                     <pip>
	xz                        5.2.3                h55aa19d_2  
	yaml                      0.1.7                had09818_2  
	zlib                      1.2.11               ha838bed_2

```



Discussion:
Just tried to get a better understanding of how it is supposed to work reading the `jupyterhub.services.auth.HubAuthenticated` class. I have logged the hub_services, hub_users and hub_groups in the `check_hub_user` method while the services were doing their requests.

The `hub_users` contains the username under which hub is run (root in my case). My authenticated is `datalayer`, so the condition `if self.hub_users and name in self.hub_users:` is not met, and the `UserNotAllowed` is finally throwed.

I have hacked to avoid the UserNotAllowed, and in that case, the JSON is correctly displayed in my browser showing the information on my current authenticated notebook (the `datalayer` user).

As I don't find any trace of `hub_groups` nor `hub_users` in the default `jupyterhub_config.py`, I conclude that these attributes are generated by the server (not based on config).

Before digging further, I am happy to hear feedback: is it something with my env I should fix or do other users/devs have that same issue?


I now run under a normal user account (no more root) and it works fine.

To make it run under normal user account, #1026 helped me with

```
sudo usermod -a -G shadow jupyterhub
sudo usermod -a -G shadow $USER
```

It may be specific to debian/ubuntu, but I don't think I read anything about this in the docs. Should I a PR (on the ./docs folder).

Now I also find information on https://github.com/jupyterhub/jupyterhub/wiki/Using-sudo-to-run-JupyterHub-without-root-privileges 

What about moving all the wiki rest to https://jupyterhub.readthedocs.io ?

I will wait a few more days before closing this.

Resolving patch:
diff --git a/examples/external-oauth/whoami-oauth.py b/examples/external-oauth/whoami-oauth.py
deleted file mode 100644
--- a/examples/external-oauth/whoami-oauth.py
+++ /dev/null
@@ -1,46 +0,0 @@
-"""An example service authenticating with the Hub.
-
-This example service serves `/services/whoami/`,
-authenticated with the Hub,
-showing the user their own info.
-"""
-from getpass import getuser
-import json
-import os
-from urllib.parse import urlparse
-
-from tornado.ioloop import IOLoop
-from tornado import log
-from tornado.httpserver import HTTPServer
-from tornado.web import RequestHandler, Application, authenticated
-
-from jupyterhub.services.auth import HubOAuthenticated, HubOAuthCallbackHandler
-from jupyterhub.utils import url_path_join
-
-class WhoAmIHandler(HubOAuthenticated, RequestHandler):
-    hub_users = {getuser()} # the users allowed to access this service
-
-    @authenticated
-    def get(self):
-        user_model = self.get_current_user()
-        self.set_header('content-type', 'application/json')
-        self.write(json.dumps(user_model, indent=1, sort_keys=True))
-
-def main():
-    log.enable_pretty_logging()
-    app = Application([
-        (os.environ['JUPYTERHUB_SERVICE_PREFIX'], WhoAmIHandler),
-        (url_path_join(os.environ['JUPYTERHUB_SERVICE_PREFIX'], 'oauth_callback'), HubOAuthCallbackHandler),
-        (r'.*', WhoAmIHandler),
-    ], cookie_secret=os.urandom(32))
-
-    http_server = HTTPServer(app)
-    url = urlparse(os.environ['JUPYTERHUB_SERVICE_URL'])
-    log.app_log.info("Running whoami service on %s", os.environ['JUPYTERHUB_SERVICE_URL'])
-
-    http_server.listen(url.port, url.hostname)
-
-    IOLoop.current().start()
-
-if __name__ == '__main__':
-    main()
diff --git a/examples/service-whoami/whoami-oauth.py b/examples/service-whoami/whoami-oauth.py
--- a/examples/service-whoami/whoami-oauth.py
+++ b/examples/service-whoami/whoami-oauth.py
@@ -17,7 +17,11 @@
 from jupyterhub.utils import url_path_join
 
 class WhoAmIHandler(HubOAuthenticated, RequestHandler):
-    hub_users = {getuser()} # the users allowed to access this service
+    # hub_users can be a set of users who are allowed to access the service
+    # `getuser()` here would mean only the user who started the service
+    # can access the service:
+
+    # hub_users = {getuser()}
 
     @authenticated
     def get(self):
diff --git a/examples/service-whoami/whoami.py b/examples/service-whoami/whoami.py
--- a/examples/service-whoami/whoami.py
+++ b/examples/service-whoami/whoami.py
@@ -15,7 +15,11 @@
 
 
 class WhoAmIHandler(HubAuthenticated, RequestHandler):
-    hub_users = {getuser()} # the users allowed to access me
+    # hub_users can be a set of users who are allowed to access the service
+    # `getuser()` here would mean only the user who started the service
+    # can access the service:
+
+    # hub_users = {getuser()}
 
     @authenticated
     def get(self):
@@ -37,4 +41,4 @@ def main():
     IOLoop.current().start()
 
 if __name__ == '__main__':
-    main()
\ No newline at end of file
+    main()


Test patch: