Skip to content

Commit a786850

Browse files
committed
Introduce CORS support for REST API
The cors can be configured using the following properties in synapse.properties. ``` synapse.rest.CORSConfig.enabled=true synapse.rest.CORSConfig.Access-Control-Allow-Origin=http://localhost:3000,https://example.com synapse.rest.CORSConfig.Access-Control-Allow-Headers=Content-Type,Authorization,X-Requested-With ```
1 parent 2011229 commit a786850

8 files changed

Lines changed: 392 additions & 0 deletions

File tree

modules/core/src/main/java/org/apache/synapse/config/SynapsePropertiesLoader.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,25 @@ public static Properties reloadSynapseProperties() {
9595
public static String getPropertyValue(String key, String defaultValue) {
9696
return MiscellaneousUtil.getProperty(loadSynapseProperties(), key, defaultValue);
9797
}
98+
99+
/**
100+
* Get the boolean value of the property from the synapse properties.
101+
*
102+
* @param name name of the config property
103+
* @param def default value to return if the property is not set
104+
* @return the value of the property to be used
105+
*/
106+
public static Boolean getBooleanProperty(String name, Boolean def) {
107+
String val = MiscellaneousUtil.getProperty(loadSynapseProperties(), name, String.valueOf(def));
108+
if (val == null) {
109+
if (log.isDebugEnabled()) {
110+
log.debug("Parameter : " + name + " is not defined in the synapse.properties file.");
111+
}
112+
return def;
113+
}
114+
if (log.isDebugEnabled()) {
115+
log.debug("synapse.properties parameter : " + name + " = " + val);
116+
}
117+
return Boolean.valueOf(val);
118+
}
98119
}

modules/core/src/main/java/org/apache/synapse/mediators/builtin/RespondMediator.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@
2020
package org.apache.synapse.mediators.builtin;
2121

2222
import org.apache.synapse.MessageContext;
23+
import org.apache.synapse.SynapseConstants;
2324
import org.apache.synapse.SynapseLog;
2425
import org.apache.synapse.core.axis2.Axis2Sender;
2526
import org.apache.synapse.mediators.AbstractMediator;
27+
import org.apache.synapse.rest.cors.CORSHelper;
28+
import org.apache.synapse.rest.cors.SynapseCORSConfiguration;
2629

2730
/**
2831
* Halts further processing/mediation of the current message and return the current message back to client
@@ -51,6 +54,14 @@ public boolean mediate(MessageContext synCtx) {
5154

5255
synCtx.setTo(null);
5356
synCtx.setResponse(true);
57+
58+
// if this is not a response from a proxy service
59+
String proxyName = (String) synCtx.getProperty(SynapseConstants.PROXY_SERVICE);
60+
if (proxyName == null || proxyName.isEmpty()) {
61+
// Add CORS headers for API response
62+
CORSHelper.handleCORSHeadersForResponse(SynapseCORSConfiguration.getInstance(), synCtx);
63+
}
64+
5465
Axis2Sender.sendBack(synCtx);
5566

5667
if (isTraceOrDebugEnabled) {

modules/core/src/main/java/org/apache/synapse/rest/RESTConstants.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,33 @@ public static enum METHODS {
5353
/** The Synapse MC property that marks if the message was denied on the accessed transport */
5454
public static final String REST_API_TRANSPORT_DENIED = "REST_API_TRANSPORT_DENIED";
5555

56+
public static final String CORS_HEADER_ACCESS_CTL_ALLOW_ORIGIN = "Access-Control-Allow-Origin";
57+
public static final String CORS_HEADER_ACCESS_CTL_ALLOW_METHODS = "Access-Control-Allow-Methods";
58+
public static final String CORS_HEADER_ACCESS_CTL_ALLOW_HEADERS = "Access-Control-Allow-Headers";
59+
public static final String CORS_HEADER_ORIGIN = "Origin";
60+
/**
61+
* CORS related configuration in synapse.properties
62+
*/
63+
// enable/disable CORS support
64+
public static final String CORS_CONFIGURATION_ENABLED = "synapse.rest.CORSConfig.enabled";
65+
// List of allowed origins (comma separated)
66+
public static final String CORS_CONFIGURATION_ACCESS_CTL_ALLOW_ORIGIN =
67+
"synapse.rest.CORSConfig.Access-Control-Allow-Origin";
68+
// List of allowed headers (comma separated)
69+
public static final String CORS_CONFIGURATION_ACCESS_CTL_ALLOW_HEADERS =
70+
"synapse.rest.CORSConfig.Access-Control-Allow-Headers";
71+
72+
/**
73+
* Constant prefix for rest related internal properties
74+
*/
75+
public static final String _SYNAPSE_INTERNAL_ = "_SYNAPSE_INTERNAL_REST_";
76+
77+
public static final String INTERNAL_CORS_HEADER_ACCESS_CTL_ALLOW_ORIGIN =
78+
_SYNAPSE_INTERNAL_ + "Access-Control-Allow-Origin";
79+
public static final String INTERNAL_CORS_HEADER_ACCESS_CTL_ALLOW_METHODS =
80+
_SYNAPSE_INTERNAL_+ "Access-Control-Allow-Methods";
81+
public static final String INTERNAL_CORS_HEADER_ACCESS_CTL_ALLOW_HEADERS =
82+
_SYNAPSE_INTERNAL_+ "Access-Control-Allow-Headers";
83+
public static final String INTERNAL_CORS_HEADER_ORIGIN = _SYNAPSE_INTERNAL_+ "Origin";
84+
5685
}

modules/core/src/main/java/org/apache/synapse/rest/Resource.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
import org.apache.synapse.core.axis2.Axis2Sender;
3333
import org.apache.synapse.mediators.MediatorFaultHandler;
3434
import org.apache.synapse.mediators.base.SequenceMediator;
35+
import org.apache.synapse.rest.cors.CORSHelper;
36+
import org.apache.synapse.rest.cors.SynapseCORSConfiguration;
3537
import org.apache.synapse.rest.dispatch.DispatcherHelper;
3638
import org.apache.synapse.transport.nhttp.NhttpConstants;
3739

@@ -268,6 +270,10 @@ void process(MessageContext synCtx) {
268270
String method = (String) synCtx.getProperty(RESTConstants.REST_METHOD);
269271
if (RESTConstants.METHOD_OPTIONS.equals(method) && sendOptions(synCtx)) {
270272
return;
273+
} else {
274+
// Handle CORS for other HTTP Methods
275+
CORSHelper.handleCORSHeaders(SynapseCORSConfiguration.getInstance(),
276+
synCtx, getSupportedMethods(), false);
271277
}
272278

273279
synCtx.setProperty(RESTConstants.SYNAPSE_RESOURCE, name);
@@ -291,6 +297,9 @@ void process(MessageContext synCtx) {
291297
}
292298
}
293299
}
300+
} else {
301+
// Add CORS headers for response message
302+
CORSHelper.handleCORSHeadersForResponse(SynapseCORSConfiguration.getInstance(), synCtx);
294303
}
295304

296305
SequenceMediator sequence = synCtx.isResponse() ? outSequence : inSequence;
@@ -357,6 +366,8 @@ private boolean sendOptions(MessageContext synCtx) {
357366
synCtx.setResponse(true);
358367
synCtx.setTo(null);
359368
transportHeaders.put(HttpHeaders.ALLOW, getSupportedMethods());
369+
CORSHelper.handleCORSHeaders(SynapseCORSConfiguration.getInstance(),
370+
synCtx, getSupportedMethods(),true);
360371
Axis2Sender.sendBack(synCtx);
361372
return true;
362373
} else {
@@ -370,6 +381,8 @@ private boolean sendOptions(MessageContext synCtx) {
370381
synCtx.setResponse(true);
371382
synCtx.setTo(null);
372383
transportHeaders.put(HttpHeaders.ALLOW, getSupportedMethods());
384+
CORSHelper.handleCORSHeaders(SynapseCORSConfiguration.getInstance(),
385+
synCtx, getSupportedMethods(), true);
373386
Axis2Sender.sendBack(synCtx);
374387
return true;
375388
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.synapse.rest.cors;
21+
22+
import java.util.Set;
23+
24+
/**
25+
* {@code CORSConfiguration} is the interface that need to be implemented in order hold the CORS configuration information
26+
*/
27+
public interface CORSConfiguration {
28+
29+
/**
30+
* Returns allowed origins in the configuration.
31+
*
32+
* @return allowed origins
33+
*/
34+
Set<String> getAllowedOrigins();
35+
36+
/**
37+
* Returns allowed headers in the configuration.
38+
*
39+
* @return allowed headers
40+
*/
41+
String getAllowedHeaders();
42+
43+
/**
44+
* Returns if CORS is enabled.
45+
*
46+
* @return boolean enabled
47+
*/
48+
boolean isEnabled();
49+
50+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.synapse.rest.cors;
21+
22+
import org.apache.commons.logging.Log;
23+
import org.apache.commons.logging.LogFactory;
24+
import org.apache.http.HttpStatus;
25+
import org.apache.synapse.MessageContext;
26+
import org.apache.synapse.core.axis2.Axis2MessageContext;
27+
import org.apache.synapse.rest.RESTConstants;
28+
import org.apache.synapse.transport.passthru.PassThroughConstants;
29+
30+
import java.util.Map;
31+
import java.util.Set;
32+
33+
/**
34+
* This class provides util functions for all CORS related activities.
35+
*/
36+
public class CORSHelper {
37+
38+
private static final Log log = LogFactory.getLog(CORSHelper.class);
39+
40+
/**
41+
* Function to retrieve allowed origin header string
42+
*
43+
* @param origin Received origin
44+
* @param allowedOrigins allowed origin set
45+
* @return
46+
*/
47+
public static String getAllowedOrigins(String origin, Set<String> allowedOrigins) {
48+
49+
if (allowedOrigins.contains("*")) {
50+
return "*";
51+
} else if (allowedOrigins.contains(origin)) {
52+
return origin;
53+
} else {
54+
return "";
55+
}
56+
}
57+
58+
/**
59+
* Functions to handle CORS Headers
60+
*
61+
* @param synCtx Synapse message context
62+
* @param corsConfiguration of the API
63+
* @param supportedMethods
64+
* @param updateHeaders Boolean
65+
*/
66+
public static void handleCORSHeaders(CORSConfiguration corsConfiguration, MessageContext synCtx,
67+
String supportedMethods, boolean updateHeaders) {
68+
69+
if (corsConfiguration.isEnabled()) {
70+
org.apache.axis2.context.MessageContext msgCtx = ((Axis2MessageContext) synCtx).getAxis2MessageContext();
71+
Map<String, String> transportHeaders = (Map<String, String>) msgCtx.getProperty(
72+
org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);
73+
if (transportHeaders != null) {
74+
String allowedOrigin = getAllowedOrigins(transportHeaders.get(RESTConstants.CORS_HEADER_ORIGIN),
75+
corsConfiguration.getAllowedOrigins());
76+
if (updateHeaders) {
77+
transportHeaders.put(RESTConstants.CORS_HEADER_ACCESS_CTL_ALLOW_METHODS, supportedMethods);
78+
transportHeaders.put(RESTConstants.CORS_HEADER_ACCESS_CTL_ALLOW_ORIGIN, allowedOrigin);
79+
transportHeaders.put(RESTConstants.CORS_HEADER_ACCESS_CTL_ALLOW_HEADERS,
80+
corsConfiguration.getAllowedHeaders());
81+
}
82+
83+
synCtx.setProperty(RESTConstants.INTERNAL_CORS_HEADER_ACCESS_CTL_ALLOW_METHODS, supportedMethods);
84+
synCtx.setProperty(RESTConstants.INTERNAL_CORS_HEADER_ACCESS_CTL_ALLOW_ORIGIN, allowedOrigin);
85+
synCtx.setProperty(RESTConstants.INTERNAL_CORS_HEADER_ACCESS_CTL_ALLOW_HEADERS,
86+
corsConfiguration.getAllowedHeaders());
87+
synCtx.setProperty(RESTConstants.INTERNAL_CORS_HEADER_ORIGIN,
88+
transportHeaders.get(RESTConstants.CORS_HEADER_ORIGIN));
89+
90+
// If the request origin is not allowed, set the status code to 403
91+
if (isOptionsRequest(synCtx) && allowedOrigin.isEmpty()) {
92+
((Axis2MessageContext) synCtx).getAxis2MessageContext()
93+
.setProperty(PassThroughConstants.HTTP_SC, HttpStatus.SC_FORBIDDEN);
94+
}
95+
}
96+
}
97+
98+
}
99+
100+
/**
101+
* Function to set CORS headers to response message transport headers extracting from synapse message context
102+
*
103+
* @param synCtx
104+
* @param corsConfiguration of the API
105+
*/
106+
public static void handleCORSHeadersForResponse(CORSConfiguration corsConfiguration, MessageContext synCtx) {
107+
108+
if (corsConfiguration.isEnabled()) {
109+
org.apache.axis2.context.MessageContext msgCtx = ((Axis2MessageContext) synCtx).getAxis2MessageContext();
110+
Map<String, String> transportHeaders = (Map<String, String>) msgCtx.getProperty(
111+
org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);
112+
if (transportHeaders != null) {
113+
if (synCtx.getProperty(RESTConstants.INTERNAL_CORS_HEADER_ACCESS_CTL_ALLOW_METHODS) != null) {
114+
transportHeaders.put(RESTConstants.CORS_HEADER_ACCESS_CTL_ALLOW_METHODS,
115+
(String) synCtx.getProperty(RESTConstants.INTERNAL_CORS_HEADER_ACCESS_CTL_ALLOW_METHODS));
116+
}
117+
118+
if (synCtx.getProperty(RESTConstants.INTERNAL_CORS_HEADER_ACCESS_CTL_ALLOW_ORIGIN) != null) {
119+
transportHeaders.put(RESTConstants.CORS_HEADER_ACCESS_CTL_ALLOW_ORIGIN,
120+
(String) synCtx.getProperty(RESTConstants.INTERNAL_CORS_HEADER_ACCESS_CTL_ALLOW_ORIGIN));
121+
}
122+
123+
if (synCtx.getProperty(RESTConstants.INTERNAL_CORS_HEADER_ACCESS_CTL_ALLOW_HEADERS) != null) {
124+
transportHeaders.put(RESTConstants.CORS_HEADER_ACCESS_CTL_ALLOW_HEADERS,
125+
(String) synCtx.getProperty(RESTConstants.INTERNAL_CORS_HEADER_ACCESS_CTL_ALLOW_HEADERS));
126+
}
127+
128+
if (synCtx.getProperty(RESTConstants.INTERNAL_CORS_HEADER_ORIGIN) != null) {
129+
transportHeaders.put(RESTConstants.CORS_HEADER_ORIGIN,
130+
(String) synCtx.getProperty(RESTConstants.INTERNAL_CORS_HEADER_ORIGIN));
131+
}
132+
}
133+
}
134+
}
135+
136+
private static boolean isOptionsRequest(MessageContext synCtx) {
137+
138+
String method = (String) synCtx.getProperty(RESTConstants.REST_METHOD);
139+
return RESTConstants.METHOD_OPTIONS.equals(method);
140+
}
141+
}

0 commit comments

Comments
 (0)