From d8dbf0c4e44cf45f00ffe2785d0778658029b825 Mon Sep 17 00:00:00 2001 From: Bennett Goble Date: Tue, 22 May 2012 11:57:56 -0400 Subject: [PATCH] LP#1002028: Cross Origin Resource Sharing for OpenSRF 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 Signed-off-by: Galen Charlton --- examples/opensrf_core.xml.example | 8 +++++++ src/gateway/apachetools.c | 36 ++++++++++++++++++++++++++++++ src/gateway/apachetools.h | 5 +++++ src/gateway/osrf_http_translator.c | 5 +++++ src/gateway/osrf_json_gateway.c | 5 +++++ 5 files changed, 59 insertions(+) diff --git a/examples/opensrf_core.xml.example b/examples/opensrf_core.xml.example index aacf933..8c99cf8 100644 --- a/examples/opensrf_core.xml.example +++ b/examples/opensrf_core.xml.example @@ -95,6 +95,14 @@ vim:et:ts=2:sw=2: LOCALSTATEDIR/log/gateway.log 3 + + + + + + + + diff --git a/src/gateway/apachetools.c b/src/gateway/apachetools.c index ca41832..f1fca1b 100644 --- a/src/gateway/apachetools.c +++ b/src/gateway/apachetools.c @@ -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... diff --git a/src/gateway/apachetools.h b/src/gateway/apachetools.h index ac85bb2..f108df4 100644 --- a/src/gateway/apachetools.h +++ b/src/gateway/apachetools.h @@ -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 */ diff --git a/src/gateway/osrf_http_translator.c b/src/gateway/osrf_http_translator.c index ab46db4..fd2bf23 100644 --- a/src/gateway/osrf_http_translator.c +++ b/src/gateway/osrf_http_translator.c @@ -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); diff --git a/src/gateway/osrf_json_gateway.c b/src/gateway/osrf_json_gateway.c index 7d5f3f7..a015e53 100644 --- a/src/gateway/osrf_json_gateway.c +++ b/src/gateway/osrf_json_gateway.c @@ -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); -- 2.43.2