Repository: plotly/dash

Issue:
500 instead of 404 when requested path is not registered
During security/pen testing `/_dash-component-suites/dash_renderer/foo.js [GET]` request resulted in **500 Internal Server Error** instead of **404 Not Found** which should be returned for not existing paths.

```
Exception on /_dash-component-suites/dash_renderer/foo.js [GET]
Traceback (most recent call last):
  File "/app/venv/lib/python3.6/site-packages/flask/app.py", line 2292, in wsgi_app
    response = self.full_dispatch_request()
  File "/app/venv/lib/python3.6/site-packages/flask/app.py", line 1815, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/app/venv/lib/python3.6/site-packages/flask/app.py", line 1718, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/app/venv/lib/python3.6/site-packages/flask/_compat.py", line 35, in reraise
    raise value
  File "/app/venv/lib/python3.6/site-packages/flask/app.py", line 1813, in full_dispatch_request
    rv = self.dispatch_request()
  File "/app/venv/lib/python3.6/site-packages/flask/app.py", line 1799, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/app/venv/lib/python3.6/site-packages/dash/dash.py", line 417, in serve_component_suites
    self.registered_paths
Exception: "dash_renderer" is registered but the path requested is not valid.
The path requested: "foo.js"
List of registered paths: {'dash_renderer': ['react@15.4.2.min.js', 'react-dom@15.4.2.min.js', 'bundle.js', 'react@15.4.2.min.js', 'react-dom@15.4.2.min.js', 'bundle.js', 'react@15.4.2.min.js', 'react-dom@15.4.2.min.js', 'bundle.js'], 'dash_html_components': ['bundle.js', 'bundle.js', 'bundle.js'], 'dash_table_experiments': ['bundle.js', 'dash_table_experiments.css', 'bundle.js', 'dash_table_experiments.css', 'bundle.js', 'dash_table_experiments.css'], 'dash_core_components': ['plotly-1.41.0.min.js', 'bundle.js', 'rc-slider@6.1.2.css', 'react-select@1.0.0-rc.3.min.css', 'react-virtualized@9.9.0.css', 'react-virtualized-select@3.1.0.css', 'react-dates@12.3.0.css', 'plotly-1.41.0.min.js', 'bundle.js', 'rc-slider@6.1.2.css', 'react-select@1.0.0-rc.3.min.css', 'react-virtualized@9.9.0.css', 'react-virtualized-select@3.1.0.css', 'react-dates@12.3.0.css', 'plotly-1.41.0.min.js', 'bundle.js', 'rc-slider@6.1.2.css', 'react-select@1.0.0-rc.3.min.css', 'react-virtualized@9.9.0.css', 'react-virtualized-select@3.1.0.css', 'react-dates@12.3.0.css']}
```

First reported in https://github.com/plotly/dash-renderer/issues/80


Discussion:

Resolving patch:
diff --git a/dash/dash.py b/dash/dash.py
--- a/dash/dash.py
+++ b/dash/dash.py
@@ -222,6 +222,10 @@ def add_url(name, view_func, methods=('GET',)):
         self._cached_layout = None
         self.routes = []
 
+        # add a handler for components suites errors to return 404
+        self.server.errorhandler(exceptions.InvalidResourceError)(
+            self._invalid_resources_handler)
+
     @property
     def layout(self):
         return self._layout
@@ -411,14 +415,14 @@ def _generate_meta_html(self):
     # Serve the JS bundles for each package
     def serve_component_suites(self, package_name, path_in_package_dist):
         if package_name not in self.registered_paths:
-            raise Exception(
+            raise exceptions.InvalidResourceError(
                 'Error loading dependency.\n'
                 '"{}" is not a registered library.\n'
                 'Registered libraries are: {}'
                 .format(package_name, list(self.registered_paths.keys())))
 
         elif path_in_package_dist not in self.registered_paths[package_name]:
-            raise Exception(
+            raise exceptions.InvalidResourceError(
                 '"{}" is registered but the path requested is not valid.\n'
                 'The path requested: "{}"\n'
                 'List of registered paths: {}'
@@ -962,6 +966,9 @@ def add_resource(p, filepath):
                 elif f == 'favicon.ico':
                     self._favicon = path
 
+    def _invalid_resources_handler(self, err):
+        return err.args[0], 404
+
     def get_asset_url(self, path):
         asset = _get_asset_path(
             self.config.requests_pathname_prefix,
diff --git a/dash/exceptions.py b/dash/exceptions.py
--- a/dash/exceptions.py
+++ b/dash/exceptions.py
@@ -60,3 +60,7 @@ class InvalidCallbackReturnValue(CallbackException):
 
 class InvalidConfig(DashException):
     pass
+
+
+class InvalidResourceError(DashException):
+    pass


Test patch: