LP#1002028: Cross Origin Resource Sharing for OpenSRF
authorBennett Goble <nivardus@gmail.com>
Tue, 22 May 2012 15:57:56 +0000 (11:57 -0400)
committerGalen Charlton <gmc@esilibrary.com>
Thu, 21 Aug 2014 16:09:40 +0000 (09:09 -0700)
Background
----------
Browsers' same-origin policy currently restricts requests to the current
website's domain to prevent various nefarious scenarios. However,
because APIs and other web resources need to remain open to cross-site
use Cross Origin Resource Sharing (CORS) was created to allow services
to formally authorize cross-origin requests. CORS makes it simple to use
OpenSRF's HTTP translator and gateway APIs on websites using separate
domains.

Example Scenarios
-----------------
1) A library would like an AJAX-driven "quicksearch" box on their main
site, which is hosted on a different domain than their catalog.
2) A developer wants to create new web applications and services that
tie into Evergreen, but does not wish to install EG locally or
configure a proxy.

Implementation
--------------
The function crossOriginHeaders() has been added to apachetools.c.
Incoming requests are checked to see if they have an Origin header. The
value of the Origin header is checked against a whitelist defined in
opensrf_core.xml config (XPath: /config/gateway/cross_origin/origin).
The function returns 1 if CORS headers have been added to the response.

Notes
-----
* The OpenSRF Javascript client library (opensrf.js) defaults to the root
of the current web host "/osrf-http-translator." In addition, synchronous
requests are presumed in some situations: resulting in the oncomplete
method never returning (Blocking requests are not possible with cross-
domain XHR.)
* It is also possible to enable CORS with the Apache "set header"
configuration directive. However, this means that the necessary headers
would be appended to every response.

Links
-----
Specification - http://www.w3.org/TR/cors/
Wikipedia Article - http://en.wikipedia.org/wiki/Cross-origin_resource_sharing

Signed-off-by: Bennett Goble <nivardus@gmail.com>
Signed-off-by: Galen Charlton <gmc@esilibrary.com>

examples/opensrf_core.xml.example
src/gateway/apachetools.c
src/gateway/apachetools.h
src/gateway/osrf_http_translator.c
src/gateway/osrf_json_gateway.c

index aacf933..8c99cf8 100644 (file)
@@ -95,6 +95,14 @@ vim:et:ts=2:sw=2:
     <logfile>LOCALSTATEDIR/log/gateway.log</logfile>
     <loglevel>3</loglevel>
 
+    <!-- cross origin HTTP settings http://en.wikipedia.org/wiki/Cross-origin_resource_sharing -->
+    <cross_origin>
+        <!-- specify individual hosts -->
+        <!-- <origin>example.com</origin> -->
+        <!-- ...or use the * wildcard to match all -->
+        <!-- <origin>*</origin> -->
+    </cross_origin>
+
   </gateway>
 
   <!-- ======================================================================================== -->
index ca41832..f1fca1b 100644 (file)
@@ -170,6 +170,42 @@ int apacheError( char* msg, ... ) {
        return HTTP_INTERNAL_SERVER_ERROR; 
 }
 
+int crossOriginHeaders(request_rec* r, osrfStringArray* allowedOrigins) {
+       const char *origin = apr_table_get(r->headers_in, "Origin");
+       if (!origin)
+               return 0;
+
+       /* remove scheme from address */
+       char *host = origin;
+       if ( !strncmp(origin, "http://", 7) )
+               host = origin + 7;
+
+       int found = 0;
+       int i;
+       for ( i = 0; i < allowedOrigins->size; i++ ) {
+               const char* allowedOrigin = osrfStringArrayGetString(allowedOrigins, i);
+               if ( !strcmp(host, allowedOrigin) || !strcmp("*", allowedOrigin) ) {
+                       found = 1;
+                       break;
+               }
+       }
+
+       if (!found)
+               return 0;
+
+       /* allow CORS response to be cached for 24 hours */
+       apr_table_set(r->headers_out, "Access-Control-Max-Age", "86400");
+       apr_table_set(r->headers_out, "Access-Control-Allow-Credentials", "true");
+       apr_table_set(r->headers_out, "Access-Control-Allow-Origin", origin);
+       apr_table_set(r->headers_out, "Access-Control-Allow-Methods", "POST,OPTIONS");
+       apr_table_set(r->headers_out, "Access-Control-Allow-Headers", OSRF_HTTP_ALL_HEADERS);
+
+       osrfLogInfo(OSRF_LOG_MARK, "Set cross-origin headers for request from %s", origin);
+
+       return 1;
+}
+
+
 
 /* taken more or less directly from O'Reillly - Writing Apache Modules in Perl and C */
 /* needs updating...
index ac85bb2..f108df4 100644 (file)
@@ -20,6 +20,7 @@ extern "C" {
 #endif
 
 #define APACHE_TOOLS_MAX_POST_SIZE 10485760 /* 10 MB */
+#define OSRF_HTTP_ALL_HEADERS "X-OpenSRF-to,X-OpenSRF-xid,X-OpenSRF-from,X-OpenSRF-thread,X-OpenSRF-timeout,X-OpenSRF-service,X-OpenSRF-multipart"
 
 
 /* parses apache URL params (GET and POST).  
@@ -50,6 +51,10 @@ int apacheDebug( char* msg, ... );
  */
 int apacheError( char* msg, ... );
 
+/* Set headers for Cross Origin Resource Sharing requests
+   as per W3 standard http://www.w3.org/TR/cors/ */
+int crossOriginHeaders(request_rec* r, osrfStringArray* allowedOrigins);
+
 /*
  * Creates an apache table* of cookie name / value pairs 
  */
index ab46db4..fd2bf23 100644 (file)
@@ -44,6 +44,7 @@ char* domainName = NULL;
 int osrfConnected = 0;
 char recipientBuf[128];
 char contentTypeBuf[80];
+osrfStringArray* allowedOrigins = NULL;
 
 #if 0
 // Commented out to avoid compiler warning
@@ -528,6 +529,9 @@ static void childInit(apr_pool_t *p, server_rec *s) {
     osrfCacheInit(servers, 1, 86400);
        osrfConnected = 1;
 
+    allowedOrigins = osrfNewStringArray(4);
+    osrfConfigGetValueList(NULL, allowedOrigins, "/cross_origin/origin");
+
     // at pool destroy time (= child exit time), cleanup
     // XXX causes us to disconnect even for clone()'d process cleanup (as in mod_cgi)
     //apr_pool_cleanup_register(p, NULL, childExit, apr_pool_cleanup_null);
@@ -544,6 +548,7 @@ static int handler(request_rec *r) {
        osrfLogSetAppname("osrf_http_translator");
        osrfAppSessionSetIngress(TRANSLATOR_INGRESS);
     testConnection(r);
+    crossOriginHeaders(r, allowedOrigins);
 
        osrfLogMkXid();
     osrfHttpTranslator* trans = osrfNewHttpTranslator(r);
index 7d5f3f7..a015e53 100644 (file)
@@ -30,6 +30,7 @@ char* osrf_json_default_locale = "en-US";
 char* osrf_json_gateway_config_file = NULL;
 int bootstrapped = 0;
 int numserved = 0;
+osrfStringArray* allowedOrigins = NULL;
 
 static const char* osrf_json_gateway_set_default_locale(cmd_parms *parms,
                void *config, const char *arg) {
@@ -87,6 +88,9 @@ static void osrf_json_gateway_child_init(apr_pool_t *p, server_rec *s) {
                return;
        }
 
+       allowedOrigins = osrfNewStringArray(4);
+       osrfConfigGetValueList(NULL, allowedOrigins, "/cross_origin/origin");
+
        bootstrapped = 1;
        osrfLogInfo(OSRF_LOG_MARK, "Bootstrapping gateway child for requests");
 
@@ -101,6 +105,7 @@ static int osrf_json_gateway_method_handler (request_rec *r) {
        /* make sure we're needed first thing*/
        if (strcmp(r->handler, MODULE_NAME )) return DECLINED;
 
+       crossOriginHeaders(r, allowedOrigins);
 
        osrf_json_gateway_dir_config* dir_conf =
                ap_get_module_config(r->per_dir_config, &osrf_json_gateway_module);