You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by je...@apache.org on 2015/07/22 16:41:34 UTC

[12/29] allura git commit: [#7927] ticket:821 Implement CORS middleware

[#7927] ticket:821 Implement CORS middleware


Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/0c71798b
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/0c71798b
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/0c71798b

Branch: refs/heads/ib/7685
Commit: 0c71798b2dbe73f68f5912a81606b9ca73f45704
Parents: de47627
Author: Igor Bondarenko <je...@gmail.com>
Authored: Thu Jul 16 15:54:22 2015 +0300
Committer: Igor Bondarenko <je...@gmail.com>
Committed: Thu Jul 16 17:50:54 2015 +0300

----------------------------------------------------------------------
 Allura/allura/config/middleware.py     |  9 ++++-
 Allura/allura/lib/custom_middleware.py | 57 +++++++++++++++++++++++++++++
 Allura/development.ini                 |  9 +++++
 3 files changed, 74 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/allura/blob/0c71798b/Allura/allura/config/middleware.py
----------------------------------------------------------------------
diff --git a/Allura/allura/config/middleware.py b/Allura/allura/config/middleware.py
index ba07cc4..922034d 100644
--- a/Allura/allura/config/middleware.py
+++ b/Allura/allura/config/middleware.py
@@ -25,7 +25,7 @@ import tg
 import tg.error
 import pkg_resources
 from tg import config
-from paste.deploy.converters import asbool
+from paste.deploy.converters import asbool, aslist, asint
 from paste.registry import RegistryManager
 from routes.middleware import RoutesMiddleware
 from pylons.middleware import StatusCodeRedirect
@@ -44,6 +44,7 @@ from allura.lib.custom_middleware import AlluraTimerMiddleware
 from allura.lib.custom_middleware import SSLMiddleware
 from allura.lib.custom_middleware import StaticFilesMiddleware
 from allura.lib.custom_middleware import CSRFMiddleware
+from allura.lib.custom_middleware import CORSMiddleware
 from allura.lib.custom_middleware import LoginRedirectMiddleware
 from allura.lib.custom_middleware import RememberLoginMiddleware
 from allura.lib import patches
@@ -137,6 +138,12 @@ def _make_core_app(root, global_conf, full_stack=True, **app_conf):
     # Clear cookies when the CSRF field isn't posted
     if not app_conf.get('disable_csrf_protection'):
         app = CSRFMiddleware(app, '_session_id')
+    if asbool(config.get('cors.enabled', False)):
+        # Handle CORS requests
+        allowed_methods = aslist(config.get('cors.methods'))
+        allowed_headers = aslist(config.get('cors.headers'))
+        cache_duration = asint(config.get('cors.cache_duration', 0))
+        app = CORSMiddleware(app, allowed_methods, allowed_headers)
     # Setup the allura SOPs
     app = allura_globals_middleware(app)
     # Ensure http and https used per config

http://git-wip-us.apache.org/repos/asf/allura/blob/0c71798b/Allura/allura/lib/custom_middleware.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/custom_middleware.py b/Allura/allura/lib/custom_middleware.py
index 13c3db5..4eef689 100644
--- a/Allura/allura/lib/custom_middleware.py
+++ b/Allura/allura/lib/custom_middleware.py
@@ -84,6 +84,63 @@ class StaticFilesMiddleware(object):
         return fileapp.FileApp(file_path, [
             ('Access-Control-Allow-Origin', '*')])
 
+class CORSMiddleware(object):
+    '''Enables Cross-Origin Resource Sharing for REST API'''
+
+    def __init__(self, app, allowed_methods, allowed_headers, cache=None):
+        self.app = app
+        self.allowed_methods = [m.upper() for m in allowed_methods]
+        self.allowed_headers = set(h.lower() for h in allowed_headers)
+        self.cache_preflight = cache or None
+
+    def __call__(self, environ, start_response):
+        is_api_request = environ.get('PATH_INFO', '').startswith('/rest/')
+        valid_cors = 'HTTP_ORIGIN' in environ
+        if not is_api_request or not valid_cors:
+            return self.app(environ, start_response)
+
+        method = environ.get('REQUEST_METHOD')
+        acrm = environ.get('HTTP_ACCESS_CONTROL_REQUEST_METHOD')
+        if method == 'OPTIONS' and acrm:
+            return self.handle_preflight_request(environ, start_response)
+        else:
+            return self.handle_simple_request(environ, start_response)
+
+    def handle_simple_request(self, environ, start_response):
+        def cors_start_response(status, headers, exc_info=None):
+            headers.extend(self.get_response_headers(preflight=False))
+            return start_response(status, headers, exc_info)
+        return self.app(environ, cors_start_response)
+
+    def handle_preflight_request(self, environ, start_response):
+        method = environ.get('HTTP_ACCESS_CONTROL_REQUEST_METHOD')
+        if method not in self.allowed_methods:
+            return self.app(environ, start_response)
+        headers = self.get_access_control_request_headers(environ)
+        if not headers.issubset(self.allowed_headers):
+            return self.app(environ, start_response)
+        r = exc.HTTPOk(headers=self.get_response_headers(preflight=True))
+        return r(environ, start_response)
+
+    def get_response_headers(self, preflight=False):
+        headers = [('Access-Control-Allow-Origin', '*')]
+        if preflight:
+            ac_methods = ', '.join(self.allowed_methods)
+            ac_headers = ', '.join(self.allowed_headers)
+            headers.extend([
+                ('Access-Control-Allow-Methods', ac_methods),
+                ('Access-Control-Allow-Headers', ac_headers),
+            ])
+            if self.cache_preflight:
+                headers.append(
+                    ('Access-Control-Max-Age', self.cache_preflight)
+                )
+        return headers
+
+    def get_access_control_request_headers(self, environ):
+        headers = environ.get('HTTP_ACCESS_CONTROL_REQUEST_HEADERS', '')
+        return set(h.strip().lower() for h in headers.split(',') if h.strip())
+
 
 class LoginRedirectMiddleware(object):
 

http://git-wip-us.apache.org/repos/asf/allura/blob/0c71798b/Allura/development.ini
----------------------------------------------------------------------
diff --git a/Allura/development.ini b/Allura/development.ini
index 02d3e9f..934ea67 100644
--- a/Allura/development.ini
+++ b/Allura/development.ini
@@ -216,6 +216,15 @@ webhook.repo_push.limit = 30
 ; `WebhookSender.triggered_by`) and values are actual limits (default=3), e.g.:
 webhook.repo_push.max_hooks = {"git": 3, "hg": 3, "svn": 3}
 
+;; Allow Cross-Origin Resource Sharing (CORS) requests to the REST API
+; disabled by default, uncomment the following options to enable:
+;cors.enabled = true
+;cors.methods = GET HEAD POST PUT DELETE
+;cors.headers = Authorization Accept Content-Type
+; Allow clients to cache preflight responses for N seconds
+; Set to 0 or remove completely to disable
+;cors.cache_duration = 86400
+
 ; Additional fields for admin project/user search
 ; Note: whitespace after comma is important!
 ;search.project.additional_search_fields = private, url, title