prompt: You will be provided with a partial code base and an issue statement explaining a problem to resolve.
  <issue>
  redirect loop on invalid single-user auth token
  when the single-user server's API request fails with 403, it's handled as failed login (302) causing a redirect loop, when it should be handled as "500: oh noes, I can't do anything!"
  
  
  </issue>
  <code>
  [start of jupyterhub/singleuser.py]
  1 #!/usr/bin/env python
  2 """Extend regular notebook server to be aware of multiuser things."""
  3 
  4 # Copyright (c) Jupyter Development Team.
  5 # Distributed under the terms of the Modified BSD License.
  6 
  7 import os
  8 
  9 import requests
  10 
  11 from tornado import ioloop
  12 
  13 from IPython.utils.traitlets import Unicode
  14 
  15 from IPython.html.notebookapp import NotebookApp
  16 from IPython.html.auth.login import LoginHandler
  17 from IPython.html.auth.logout import LogoutHandler
  18 
  19 from IPython.html.utils import url_path_join
  20 
  21 
  22 from distutils.version import LooseVersion as V
  23 
  24 import IPython
  25 if V(IPython.__version__) < V('3.0'):
  26     raise ImportError("JupyterHub Requires IPython >= 3.0, found %s" % IPython.__version__)
  27 
  28 # Define two methods to attach to AuthenticatedHandler,
  29 # which authenticate via the central auth server.
  30 
  31 class JupyterHubLoginHandler(LoginHandler):
  32     @staticmethod
  33     def login_available(settings):
  34         return True
  35     
  36     @staticmethod
  37     def verify_token(self, cookie_name, encrypted_cookie):
  38         """monkeypatch method for token verification"""
  39         cookie_cache = self.settings['cookie_cache']
  40         if encrypted_cookie in cookie_cache:
  41             # we've seen this token before, don't ask upstream again
  42             return cookie_cache[encrypted_cookie]
  43     
  44         hub_api_url = self.settings['hub_api_url']
  45         hub_api_key = self.settings['hub_api_key']
  46         r = requests.get(url_path_join(
  47             hub_api_url, "authorizations/cookie", cookie_name,
  48         ),
  49             headers = {'Authorization' : 'token %s' % hub_api_key},
  50             data=encrypted_cookie,
  51         )
  52         if r.status_code == 404:
  53             data = {'user' : ''}
  54         elif r.status_code >= 400:
  55             self.log.warn("Failed to check authorization: [%i] %s", r.status_code, r.reason)
  56             data = None
  57         else:
  58             data = r.json()
  59         cookie_cache[encrypted_cookie] = data
  60         return data
  61     
  62     @staticmethod
  63     def get_user(self):
  64         """alternative get_current_user to query the central server"""
  65         my_user = self.settings['user']
  66         encrypted_cookie = self.get_cookie(self.cookie_name)
  67         if encrypted_cookie:
  68             auth_data = JupyterHubLoginHandler.verify_token(self, self.cookie_name, encrypted_cookie)
  69             if not auth_data:
  70                 # treat invalid token the same as no token
  71                 return None
  72             user = auth_data['user']
  73             if user == my_user:
  74                 return user
  75             else:
  76                 return None
  77         else:
  78             self.log.debug("No token cookie")
  79             return None
  80 
  81 
  82 class JupyterHubLogoutHandler(LogoutHandler):
  83     def get(self):
  84         self.redirect(url_path_join(self.settings['hub_prefix'], 'logout'))
  85 
  86 
  87 # register new hub related command-line aliases
  88 aliases = NotebookApp.aliases.get_default_value()
  89 aliases.update({
  90     'user' : 'SingleUserNotebookApp.user',
  91     'cookie-name': 'SingleUserNotebookApp.cookie_name',
  92     'hub-prefix': 'SingleUserNotebookApp.hub_prefix',
  93     'hub-api-url': 'SingleUserNotebookApp.hub_api_url',
  94     'base-url': 'SingleUserNotebookApp.base_url',
  95 })
  96 
  97 class SingleUserNotebookApp(NotebookApp):
  98     """A Subclass of the regular NotebookApp that is aware of the parent multiuser context."""
  99     user = Unicode(config=True)
  100     cookie_name = Unicode(config=True)
  101     hub_prefix = Unicode(config=True)
  102     hub_api_url = Unicode(config=True)
  103     aliases = aliases
  104     open_browser = False
  105     login_handler_class = JupyterHubLoginHandler
  106     logout_handler_class = JupyterHubLogoutHandler
  107     
  108     def _confirm_exit(self):
  109         # disable the exit confirmation for background notebook processes
  110         ioloop.IOLoop.instance().stop()
  111     
  112     def init_webapp(self):
  113         # load the hub related settings into the tornado settings dict
  114         env = os.environ
  115         s = self.tornado_settings
  116         s['cookie_cache'] = {}
  117         s['user'] = self.user
  118         s['hub_api_key'] = env.pop('JPY_API_TOKEN')
  119         s['hub_prefix'] = self.hub_prefix
  120         s['cookie_name'] = self.cookie_name
  121         s['login_url'] = url_path_join(self.hub_prefix, 'login')
  122         s['hub_api_url'] = self.hub_api_url
  123         super(SingleUserNotebookApp, self).init_webapp()
  124 
  125 
  126 def main():
  127     return SingleUserNotebookApp.launch_instance()
  128 
  129 
  130 if __name__ == "__main__":
  131     main()
  132
  [end of jupyterhub/singleuser.py]
  </code>
  
  I need you to solve this issue by generating a single patch file that I can apply directly to this repository using git apply. Please respond with a single patch file in the following format.
  <patch>
  diff --git a/file.py b/file.py
  --- a/file.py
  +++ b/file.py
  @@ -1,27 +1,35 @@
   def euclidean(a, b):
  -    while b:
  -        a, b = b, a % b
  -    return a
  +    if b == 0:
  +        return a
  +    return euclidean(b, a % b)
   
   
   def bresenham(x0, y0, x1, y1):
       points = []
       dx = abs(x1 - x0)
       dy = abs(y1 - y0)
  -    sx = 1 if x0 < x1 else -1
  -    sy = 1 if y0 < y1 else -1
  -    err = dx - dy
  +    x, y = x0, y0
  +    sx = -1 if x0 > x1 else 1
  +    sy = -1 if y0 > y1 else 1
   
  -    while True:
  -        points.append((x0, y0))
  -        if x0 == x1 and y0 == y1:
  -            break
  -        e2 = 2 * err
  -        if e2 > -dy:
  -            err -= dy
  -            x0 += sx
  -        if e2 < dx:
  -            err += dx
  -            y0 += sy
  +    if dx > dy:
  +        err = dx / 2.0
  +        while x != x1:
  +            points.append((x, y))
  +            err -= dy
  +            if err < 0:
  +                y += sy
  +                err += dx
  +            x += sx
  +    else:
  +        err = dy / 2.0
  +        while y != y1:
  +            points.append((x, y))
  +            err -= dx
  +            if err < 0:
  +                x += sx
  +                err += dy
  +            y += sy
  +
  +    points.append((x, y))
       return points
  
  </patch>
golden_diff: diff --git a/jupyterhub/singleuser.py b/jupyterhub/singleuser.py
  --- a/jupyterhub/singleuser.py
  +++ b/jupyterhub/singleuser.py
  @@ -9,6 +9,7 @@
   import requests
   
   from tornado import ioloop
  +from tornado.web import HTTPError
   
   from IPython.utils.traitlets import Unicode
   
  @@ -35,7 +36,7 @@
       
       @staticmethod
       def verify_token(self, cookie_name, encrypted_cookie):
  -        """monkeypatch method for token verification"""
  +        """method for token verification"""
           cookie_cache = self.settings['cookie_cache']
           if encrypted_cookie in cookie_cache:
               # we've seen this token before, don't ask upstream again
  @@ -51,9 +52,15 @@
           )
           if r.status_code == 404:
               data = {'user' : ''}
  +        if r.status_code == 403:
  +            self.log.error("I don't have permission to verify cookies, my auth token may have expired: [%i] %s", r.status_code, r.reason)
  +            raise HTTPError(500, "Permission failure checking authorization, I may need to be restarted")
  +        elif r.status_code >= 500:
  +            self.log.error("Upstream failure verifying auth token: [%i] %s", r.status_code, r.reason)
  +            raise HTTPError(502, "Failed to check authorization (upstream problem)")
           elif r.status_code >= 400:
               self.log.warn("Failed to check authorization: [%i] %s", r.status_code, r.reason)
  -            data = None
  +            raise HTTPError(500, "Failed to check authorization")
           else:
               data = r.json()
           cookie_cache[encrypted_cookie] = data
  @@ -62,6 +69,13 @@
       @staticmethod
       def get_user(self):
           """alternative get_current_user to query the central server"""
  +        # only allow this to be called once per handler
  +        # avoids issues if an error is raised,
  +        # since this may be called again when trying to render the error page
  +        if hasattr(self, '_cached_user'):
  +            return self._cached_user
  +        
  +        self._cached_user = None
           my_user = self.settings['user']
           encrypted_cookie = self.get_cookie(self.cookie_name)
           if encrypted_cookie:
  @@ -71,6 +85,7 @@
                   return None
               user = auth_data['user']
               if user == my_user:
  +                self._cached_user = user
                   return user
               else:
                   return None
  
verification_info: {"golden_diff": "diff --git a/jupyterhub/singleuser.py b/jupyterhub/singleuser.py\n--- a/jupyterhub/singleuser.py\n+++ b/jupyterhub/singleuser.py\n@@ -9,6 +9,7 @@\n import requests\n \n from tornado import ioloop\n+from tornado.web import HTTPError\n \n from IPython.utils.traitlets import Unicode\n \n@@ -35,7 +36,7 @@\n     \n     @staticmethod\n     def verify_token(self, cookie_name, encrypted_cookie):\n-        \"\"\"monkeypatch method for token verification\"\"\"\n+        \"\"\"method for token verification\"\"\"\n         cookie_cache = self.settings['cookie_cache']\n         if encrypted_cookie in cookie_cache:\n             # we've seen this token before, don't ask upstream again\n@@ -51,9 +52,15 @@\n         )\n         if r.status_code == 404:\n             data = {'user' : ''}\n+        if r.status_code == 403:\n+            self.log.error(\"I don't have permission to verify cookies, my auth token may have expired: [%i] %s\", r.status_code, r.reason)\n+            raise HTTPError(500, \"Permission failure checking authorization, I may need to be restarted\")\n+        elif r.status_code >= 500:\n+            self.log.error(\"Upstream failure verifying auth token: [%i] %s\", r.status_code, r.reason)\n+            raise HTTPError(502, \"Failed to check authorization (upstream problem)\")\n         elif r.status_code >= 400:\n             self.log.warn(\"Failed to check authorization: [%i] %s\", r.status_code, r.reason)\n-            data = None\n+            raise HTTPError(500, \"Failed to check authorization\")\n         else:\n             data = r.json()\n         cookie_cache[encrypted_cookie] = data\n@@ -62,6 +69,13 @@\n     @staticmethod\n     def get_user(self):\n         \"\"\"alternative get_current_user to query the central server\"\"\"\n+        # only allow this to be called once per handler\n+        # avoids issues if an error is raised,\n+        # since this may be called again when trying to render the error page\n+        if hasattr(self, '_cached_user'):\n+            return self._cached_user\n+        \n+        self._cached_user = None\n         my_user = self.settings['user']\n         encrypted_cookie = self.get_cookie(self.cookie_name)\n         if encrypted_cookie:\n@@ -71,6 +85,7 @@\n                 return None\n             user = auth_data['user']\n             if user == my_user:\n+                self._cached_user = user\n                 return user\n             else:\n                 return None\n", "issue": "redirect loop on invalid single-user auth token\nwhen the single-user server's API request fails with 403, it's handled as failed login (302) causing a redirect loop, when it should be handled as \"500: oh noes, I can't do anything!\"\n\n", "before_files": [{"content": "#!/usr/bin/env python\n\"\"\"Extend regular notebook server to be aware of multiuser things.\"\"\"\n\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\nimport os\n\nimport requests\n\nfrom tornado import ioloop\n\nfrom IPython.utils.traitlets import Unicode\n\nfrom IPython.html.notebookapp import NotebookApp\nfrom IPython.html.auth.login import LoginHandler\nfrom IPython.html.auth.logout import LogoutHandler\n\nfrom IPython.html.utils import url_path_join\n\n\nfrom distutils.version import LooseVersion as V\n\nimport IPython\nif V(IPython.__version__) < V('3.0'):\n    raise ImportError(\"JupyterHub Requires IPython >= 3.0, found %s\" % IPython.__version__)\n\n# Define two methods to attach to AuthenticatedHandler,\n# which authenticate via the central auth server.\n\nclass JupyterHubLoginHandler(LoginHandler):\n    @staticmethod\n    def login_available(settings):\n        return True\n    \n    @staticmethod\n    def verify_token(self, cookie_name, encrypted_cookie):\n        \"\"\"monkeypatch method for token verification\"\"\"\n        cookie_cache = self.settings['cookie_cache']\n        if encrypted_cookie in cookie_cache:\n            # we've seen this token before, don't ask upstream again\n            return cookie_cache[encrypted_cookie]\n    \n        hub_api_url = self.settings['hub_api_url']\n        hub_api_key = self.settings['hub_api_key']\n        r = requests.get(url_path_join(\n            hub_api_url, \"authorizations/cookie\", cookie_name,\n        ),\n            headers = {'Authorization' : 'token %s' % hub_api_key},\n            data=encrypted_cookie,\n        )\n        if r.status_code == 404:\n            data = {'user' : ''}\n        elif r.status_code >= 400:\n            self.log.warn(\"Failed to check authorization: [%i] %s\", r.status_code, r.reason)\n            data = None\n        else:\n            data = r.json()\n        cookie_cache[encrypted_cookie] = data\n        return data\n    \n    @staticmethod\n    def get_user(self):\n        \"\"\"alternative get_current_user to query the central server\"\"\"\n        my_user = self.settings['user']\n        encrypted_cookie = self.get_cookie(self.cookie_name)\n        if encrypted_cookie:\n            auth_data = JupyterHubLoginHandler.verify_token(self, self.cookie_name, encrypted_cookie)\n            if not auth_data:\n                # treat invalid token the same as no token\n                return None\n            user = auth_data['user']\n            if user == my_user:\n                return user\n            else:\n                return None\n        else:\n            self.log.debug(\"No token cookie\")\n            return None\n\n\nclass JupyterHubLogoutHandler(LogoutHandler):\n    def get(self):\n        self.redirect(url_path_join(self.settings['hub_prefix'], 'logout'))\n\n\n# register new hub related command-line aliases\naliases = NotebookApp.aliases.get_default_value()\naliases.update({\n    'user' : 'SingleUserNotebookApp.user',\n    'cookie-name': 'SingleUserNotebookApp.cookie_name',\n    'hub-prefix': 'SingleUserNotebookApp.hub_prefix',\n    'hub-api-url': 'SingleUserNotebookApp.hub_api_url',\n    'base-url': 'SingleUserNotebookApp.base_url',\n})\n\nclass SingleUserNotebookApp(NotebookApp):\n    \"\"\"A Subclass of the regular NotebookApp that is aware of the parent multiuser context.\"\"\"\n    user = Unicode(config=True)\n    cookie_name = Unicode(config=True)\n    hub_prefix = Unicode(config=True)\n    hub_api_url = Unicode(config=True)\n    aliases = aliases\n    open_browser = False\n    login_handler_class = JupyterHubLoginHandler\n    logout_handler_class = JupyterHubLogoutHandler\n    \n    def _confirm_exit(self):\n        # disable the exit confirmation for background notebook processes\n        ioloop.IOLoop.instance().stop()\n    \n    def init_webapp(self):\n        # load the hub related settings into the tornado settings dict\n        env = os.environ\n        s = self.tornado_settings\n        s['cookie_cache'] = {}\n        s['user'] = self.user\n        s['hub_api_key'] = env.pop('JPY_API_TOKEN')\n        s['hub_prefix'] = self.hub_prefix\n        s['cookie_name'] = self.cookie_name\n        s['login_url'] = url_path_join(self.hub_prefix, 'login')\n        s['hub_api_url'] = self.hub_api_url\n        super(SingleUserNotebookApp, self).init_webapp()\n\n\ndef main():\n    return SingleUserNotebookApp.launch_instance()\n\n\nif __name__ == \"__main__\":\n    main()\n", "path": "jupyterhub/singleuser.py"}]}