Răsfoiți Sursa

HUE-1664 [core] URL Redirection whitelist

Abraham Elmahrek 12 ani în urmă
părinte
comite
d8b2a51

+ 1 - 8
apps/proxy/src/proxy/conf.py

@@ -17,14 +17,7 @@
 """
 Configuration options for the reverse proxy application.
 """
-import re
-
-from desktop.lib.conf import Config
-
-def list_of_compiled_res(list_of_strings):
-  if isinstance(list_of_strings, str):
-    list_of_strings = [ list_of_strings ]
-  return list(re.compile(x) for x in list_of_strings)
+from desktop.lib.conf import Config, list_of_compiled_res
     
 WHITELIST = Config(
   key="whitelist",

+ 5 - 0
desktop/conf.dist/hue.ini

@@ -63,6 +63,11 @@
   # Use Google Analytics to see how many times an application or specific section of an application is used, nothing more.
   ## collect_usage=true
 
+  ## Comma-separated list of regular expressions, which match the redirect URL.
+  ## For example, to restrict to your local domain and FQDN, the following value can be used:
+  ##   ^\/.*$,^http:\/\/www.mydomain.com\/.*$
+  # redirect_whitelist=
+
   # Administrators
   # ----------------
   [[django_admins]]

+ 5 - 0
desktop/conf/pseudo-distributed.ini.tmpl

@@ -72,6 +72,11 @@
   # Use Google Analytics to see how many times an application or specific section of an application is used, nothing more.
   ## collect_usage=true
 
+  ## Comma-separated list of regular expressions, which match the redirect URL.
+  ## For example, to restrict to your local domain and FQDN, the following value can be used:
+  ##   ^\/.*$,^http:\/\/www.mydomain.com\/.*$
+  # redirect_whitelist=
+
   # Administrators
   # ----------------
   [[django_admins]]

+ 8 - 1
desktop/core/src/desktop/conf.py

@@ -23,7 +23,7 @@ from django.utils.translation import ugettext_lazy as _
 
 from desktop.lib.conf import Config, ConfigSection, UnspecifiedConfigSection,\
                              coerce_bool, coerce_csv, coerce_json_dict,\
-                             validate_path
+                             validate_path, list_of_compiled_res
 from desktop.lib.i18n import force_unicode
 from desktop.lib.paths import get_desktop_root
 
@@ -112,6 +112,13 @@ POLL_ENABLED = Config(
   private=True,
   default=True)
 
+REDIRECT_WHITELIST = Config(
+  key="redirect_whitelist",
+  help=_("Comma-separated list of regular expressions, which match the redirect URL."
+         "For example, to restrict to your local domain and FQDN, the following value can be used:"
+         "  ^\/.*$,^http:\/\/www.mydomain.com\/.*$"),
+  type=list_of_compiled_res,
+  default='')
 
 def is_https_enabled():
   return bool(SSL_CERTIFICATE.get() and SSL_PRIVATE_KEY.get())

+ 6 - 0
desktop/core/src/desktop/lib/conf.py

@@ -72,6 +72,7 @@ import json
 import logging
 import os
 import textwrap
+import re
 import sys
 
 # Magical object for use as a "symbol"
@@ -627,6 +628,11 @@ def coerce_json_dict(value):
     return value
   raise Exception("Could not coerce %r to json dictionary." % value)
 
+def list_of_compiled_res(list_of_strings):
+  if isinstance(list_of_strings, str):
+    list_of_strings = [ list_of_strings ]
+  return list(re.compile(x) for x in list_of_strings)
+
 def validate_path(confvar, is_dir=None, fs=os.path, message='Path does not exist on the filesystem.'):
   """
   Validate that the value of confvar is an existent path.

+ 17 - 1
desktop/core/src/desktop/middleware.py

@@ -27,7 +27,7 @@ from django.contrib.auth import REDIRECT_FIELD_NAME, BACKEND_SESSION_KEY, authen
 from django.contrib.auth.middleware import RemoteUserMiddleware
 from django.core import exceptions, urlresolvers
 import django.db
-from django.http import HttpResponseNotAllowed
+from django.http import HttpResponseNotAllowed, HttpResponseForbidden
 from django.core.urlresolvers import resolve
 from django.http import HttpResponseRedirect, HttpResponse
 from django.utils.importlib import import_module
@@ -634,3 +634,19 @@ class EnsureSafeMethodMiddleware(object):
   def process_request(self, request):
     if request.method not in desktop.conf.HTTP_ALLOWED_METHODS.get():
       return HttpResponseNotAllowed(desktop.conf.HTTP_ALLOWED_METHODS.get())
+
+
+class EnsureSafeRedirectURLMiddleware(object):
+  """
+  Middleware to white list configured redirect URLs.
+  """
+  def process_response(self, request, response):
+    if response.status_code == 302:
+      if any([regexp.match(response['Location']) for regexp in desktop.conf.REDIRECT_WHITELIST.get()]):
+        return response
+
+      response = render("error.mako", request, dict(error=_('Redirect to %s is not allowed.') % response['Location']))
+      response.status_code = 403
+      return response
+    else:
+      return response

+ 30 - 0
desktop/core/src/desktop/middleware_test.py

@@ -85,3 +85,33 @@ def test_ensure_safe_method_middleware():
     assert_equal(405, response.status_code)
   finally:
     done()
+
+
+def test_ensure_safe_redirect_middleware():
+  done = []
+  try:
+    # Super user
+    c = make_logged_in_client()
+
+    # GET works
+    response = c.get("/useradmin/")
+    assert_equal(200, response.status_code)
+
+    # Disallow most redirects
+    done.append(desktop.conf.REDIRECT_WHITELIST.set_for_testing('\d+'))
+    response = c.get("")
+    assert_equal(403, response.status_code)
+
+    # Allow all redirects
+    done.append(desktop.conf.REDIRECT_WHITELIST.set_for_testing('.*'))
+    response = c.get("")
+    assert_equal(302, response.status_code)
+
+    # Allow all redirects and disallow most at the same time.
+    # should have a logic OR functionality.
+    done.append(desktop.conf.REDIRECT_WHITELIST.set_for_testing('\d+,.*'))
+    response = c.get("")
+    assert_equal(403, response.status_code)
+  finally:
+    for finish in done:
+      finish()

+ 4 - 0
desktop/core/src/desktop/settings.py

@@ -304,6 +304,10 @@ if SAML_AUTHENTICATION:
   LOGIN_URL = '/saml2/login/'
   SESSION_EXPIRE_AT_BROWSER_CLOSE = True
 
+# URL Redirection white list.
+if desktop.conf.REDIRECT_WHITELIST.get():
+  MIDDLEWARE_CLASSES.append('desktop.middleware.EnsureSafeRedirectURLMiddleware')
+
 ############################################################
 
 # Necessary for South to not fuzz with tests.  Fixed in South 0.7.1