Skip to content

Commit

Permalink
hwkalerts-174 Add CORS support (#232)
Browse files Browse the repository at this point in the history
- Implement CORS support for alerting REST API using the
  new support in Hcommons.
- add new CORS Itest
  • Loading branch information
jshaughn authored and pilhuhn committed Oct 17, 2016
1 parent 0453360 commit 308d8e4
Show file tree
Hide file tree
Showing 9 changed files with 347 additions and 3 deletions.
22 changes: 20 additions & 2 deletions hawkular-alerts-rest-tests/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,13 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.hawkular.commons</groupId>
<artifactId>hawkular-cors-jaxrs-filter</artifactId>
<version>${version.org.hawkular.commons}</version>
<scope>test</scope>
</dependency>

</dependencies>

<build>
Expand Down Expand Up @@ -219,6 +226,7 @@
<excludes>
<excludeFile>org/hawkular/alerts/rest/ClusterITest.class</excludeFile>
<excludeFile>org/hawkular/alerts/rest/PerfCrudITest.class</excludeFile>
<excludeFile>org/hawkular/alerts/rest/CORSITest.class</excludeFile>
<exclude>${hawkular.itest-exclude}</exclude>
</excludes>
<systemPropertyVariables>
Expand Down Expand Up @@ -435,8 +443,10 @@
<javaOpt>-Dmail.smtp.port=2525</javaOpt>
<javaOpt>-Dhawkular.backend=embedded_cassandra</javaOpt>
<javaOpt>-Dhawkular.log.alerts=${hawkular.log.alerts}</javaOpt>
<!--<javaOpt>-Xdebug</javaOpt>-->
<!--<javaOpt>-Xrunjdwp:transport=dt_socket,address=8787,server=y,suspend=y</javaOpt>-->
<javaOpt>-Dhawkular.allowed-cors-origins=http://test.hawkular.org,https://secure.hawkular.io</javaOpt>
<javaOpt>-Dhawkular.allowed-cors-access-control-allow-headers=random-header1,random-header2</javaOpt>
<!-- <javaOpt>-Xdebug</javaOpt> -->
<!-- <javaOpt>-Xrunjdwp:transport=dt_socket,address=8787,server=y,suspend=y</javaOpt> -->
</javaOpts>
</configuration>
<executions>
Expand Down Expand Up @@ -469,6 +479,8 @@
<hawkular.host>${hawkular.host}</hawkular.host>
<hawkular.port>${hawkular.port}</hawkular.port>
<hawkular.base-uri>${hawkular.base-uri}</hawkular.base-uri>
<hawkular.test.origin>http://test.hawkular.org</hawkular.test.origin>
<hawkular.test.access-control-allow-headers>random-header1,random-header2</hawkular.test.access-control-allow-headers>
</systemPropertyVariables>
</configuration>
<executions>
Expand Down Expand Up @@ -696,6 +708,8 @@
<javaOpt>-Dmail.smtp.port=2525</javaOpt>
<javaOpt>-Dhawkular.backend=embedded_cassandra</javaOpt>
<javaOpt>-Dhawkular.log.alerts=${hawkular.log.alerts}</javaOpt>
<javaOpt>-Dhawkular.allowed-cors-origins=http://test.hawkular.org,https://secure.hawkular.io</javaOpt>
<javaOpt>-Dhawkular.allowed-cors-access-control-allow-headers=random-header1,random-header2</javaOpt>
<!--<javaOpt>-Xdebug</javaOpt>-->
<!--<javaOpt>-Xrunjdwp:transport=dt_socket,address=8787,server=y,suspend=y</javaOpt>-->
</javaOpts>
Expand Down Expand Up @@ -730,6 +744,8 @@
<hawkular.host>${hawkular.host}</hawkular.host>
<hawkular.port>${hawkular.port}</hawkular.port>
<hawkular.base-uri>${hawkular.base-uri}</hawkular.base-uri>
<hawkular.test.origin>http://test.hawkular.org</hawkular.test.origin>
<hawkular.test.access-control-allow-headers>random-header1,random-header2</hawkular.test.access-control-allow-headers>
</systemPropertyVariables>
</configuration>
<executions>
Expand Down Expand Up @@ -1143,6 +1159,8 @@
<hawkular.host>${hawkular.host}</hawkular.host>
<hawkular.port>${hawkular.port}</hawkular.port>
<hawkular.base-uri>${hawkular.base-uri}</hawkular.base-uri>
<hawkular.test.origin>http://test.hawkular.org</hawkular.test.origin>
<hawkular.test.access-control-allow-headers>random-header1,random-header2</hawkular.test.access-control-allow-headers>
</systemPropertyVariables>
</configuration>
<executions>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ import org.junit.BeforeClass

import groovyx.net.http.ContentType
import groovyx.net.http.RESTClient
import groovyx.net.http.HttpResponseDecorator
import groovyx.net.http.HttpResponseException

import java.util.concurrent.atomic.AtomicInteger

/**
* Base class for REST tests.
Expand All @@ -30,14 +34,18 @@ class AbstractITestBase {

static baseURI = System.getProperty('hawkular.base-uri') ?: 'http://127.0.0.1:8080/hawkular/alerts/'
static RESTClient client
static String tenantHeaderName = "Hawkular-Tenant"
static testTenant = "28026b36-8fe4-4332-84c8-524e173a68bf"
static final String TENANT_PREFIX = UUID.randomUUID().toString()
static final AtomicInteger TENANT_ID_COUNTER = new AtomicInteger(0)

@BeforeClass
static void initClient() {

client = new RESTClient(baseURI, ContentType.JSON)
// this prevents 404 from being wrapped in an Exception, just return the response, better for testing
client.handler.failure = { it }
/*
client.handler.failure = { resp, data ->
resp.setData(data)
String headers = ""
Expand All @@ -46,6 +54,7 @@ class AbstractITestBase {
}
return resp
}
*/
/*
User: jdoe
Password: password
Expand All @@ -63,4 +72,8 @@ class AbstractITestBase {
tries--
}
}

static String nextTenantId() {
return "T${TENANT_PREFIX}${TENANT_ID_COUNTER.incrementAndGet()}"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
/*
* Copyright 2015-2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.hawkular.alerts.rest

import static org.hawkular.jaxrs.filter.cors.Headers.ACCESS_CONTROL_ALLOW_CREDENTIALS
import static org.hawkular.jaxrs.filter.cors.Headers.ACCESS_CONTROL_ALLOW_HEADERS
import static org.hawkular.jaxrs.filter.cors.Headers.ACCESS_CONTROL_ALLOW_METHODS
import static org.hawkular.jaxrs.filter.cors.Headers.ACCESS_CONTROL_ALLOW_ORIGIN
import static org.hawkular.jaxrs.filter.cors.Headers.ACCESS_CONTROL_MAX_AGE
import static org.hawkular.jaxrs.filter.cors.Headers.ACCESS_CONTROL_REQUEST_METHOD
import static org.hawkular.jaxrs.filter.cors.Headers.DEFAULT_CORS_ACCESS_CONTROL_ALLOW_HEADERS
import static org.hawkular.jaxrs.filter.cors.Headers.DEFAULT_CORS_ACCESS_CONTROL_ALLOW_METHODS
import static org.hawkular.jaxrs.filter.cors.Headers.ORIGIN
import static org.junit.Assert.assertEquals
import static org.junit.Assume.assumeTrue

import org.hawkular.alerts.api.model.event.Event

import org.junit.Before
import org.junit.Test
import org.junit.Ignore

class CORSITest extends AbstractITestBase {
static final String testOrigin = System.getProperty("hawkular.test.origin", "http://test.hawkular.org")
static final String testAccessControlAllowHeaders = DEFAULT_CORS_ACCESS_CONTROL_ALLOW_HEADERS + ',' +
System.getProperty("hawkular.test.access-control-allow-headers");

@Test
void testOptionsWithOrigin() {
def response = client.options(path: "ping",
headers: [
(ACCESS_CONTROL_REQUEST_METHOD): "POST",
//this should be ignored by the container and reply with pre-configured headers
(ACCESS_CONTROL_ALLOW_HEADERS): "test-header",
(ORIGIN): testOrigin,
])

def responseHeaders = "==== Response Headers = Start ====\n"
response.headers.each { responseHeaders += "${it.name} : ${it.value}\n" }
responseHeaders += "==== Response Headers = End ====\n"

//Expected a 200 because this is be a pre-flight call that should never reach the resource router
assertEquals(200, response.status)
assertEquals(null, response.getData())
assertEquals(responseHeaders, DEFAULT_CORS_ACCESS_CONTROL_ALLOW_METHODS, response.headers[ACCESS_CONTROL_ALLOW_METHODS].value)
assertEquals(responseHeaders, testAccessControlAllowHeaders, response.headers[ACCESS_CONTROL_ALLOW_HEADERS].value)
assertEquals(responseHeaders, testOrigin, response.headers[ACCESS_CONTROL_ALLOW_ORIGIN].value)
assertEquals(responseHeaders, "true", response.headers[ACCESS_CONTROL_ALLOW_CREDENTIALS].value)
assertEquals(responseHeaders, (72 * 60 * 60) + "", response.headers[ACCESS_CONTROL_MAX_AGE].value)
}

@Test
void testOptionsWithBadOrigin() {
def response = client.options(path: "events/test", headers: [
(ACCESS_CONTROL_REQUEST_METHOD): "OPTIONS",
(ORIGIN): "*"
])
assertEquals(400, response.status)

def wrongSchemeOrigin = testOrigin.replaceAll("http://", "https://")
response = client.options(path: "events/test", headers: [
(ACCESS_CONTROL_REQUEST_METHOD): "GET",
(ORIGIN): wrongSchemeOrigin
])
assertEquals(400, response.status)
}

@Test
void testOptionsWithSubdomainOrigin() {
//construct a subdomain with "tester." as prefix
def subDomainOrigin = testOrigin.substring(0, testOrigin.indexOf("/") + 2) + "tester." + testOrigin.substring(testOrigin.indexOf("/") + 2)
def response = client.options(path: "gauges/test/raw",
headers: [
(ACCESS_CONTROL_REQUEST_METHOD): "GET",
(ORIGIN): subDomainOrigin
])

def responseHeaders = "==== Response Headers = Start ====\n"
response.headers.each { responseHeaders += "${it.name} : ${it.value}\n" }
responseHeaders += "==== Response Headers = End ====\n"

assertEquals(200, response.status)
assertEquals(null, response.getData())
assertEquals(responseHeaders, DEFAULT_CORS_ACCESS_CONTROL_ALLOW_METHODS, response.headers[ACCESS_CONTROL_ALLOW_METHODS].value)
assertEquals(responseHeaders, testAccessControlAllowHeaders, response.headers[ACCESS_CONTROL_ALLOW_HEADERS].value)
assertEquals(responseHeaders, subDomainOrigin, response.headers[ACCESS_CONTROL_ALLOW_ORIGIN].value)
assertEquals(responseHeaders, "true", response.headers[ACCESS_CONTROL_ALLOW_CREDENTIALS].value)
assertEquals(responseHeaders, (72 * 60 * 60) + "", response.headers[ACCESS_CONTROL_MAX_AGE].value)
}

@Test
void testOptionsWithoutTenantIDAndData() {
long start = System.currentTimeMillis() - (20 * 60000);
def tenantId = nextTenantId()

// First just create a test Event
Map context = new java.util.HashMap();
context.put("cors-test-context-name", "cors-test-context-value");
Map tags = new java.util.HashMap();
tags.put("cors-test-tag-name", "cors-test-tag-value");
Event event = new Event(tenantId, "cors-test-event-id", System.currentTimeMillis(), "cors-test-event-data-id",
"cors-test-category", "cors test event text", context, tags);

def response = client.post(path: "events", body: event, headers: [(tenantHeaderName): tenantId])
assertEquals(200, response.status)

// Now query for the event
response = client.get(path: "events",
query: [ids: "cors-test-event-id",tags: "cors-test-tag-name|cors-test-tag-value"],
headers: [(tenantHeaderName): tenantId])

assertEquals(200, response.status)
Event e = response.data
assertEquals(event, e)

//Send a CORS pre-flight request and make sure it returns no data
response = client.options(path: "events",
query: [ids: "cors-test-event-id"],
headers: [
(ACCESS_CONTROL_REQUEST_METHOD): "GET",
(ORIGIN): testOrigin
])

def responseHeaders = "==== Response Headers = Start ====\n"
response.headers.each { responseHeaders += "${it.name} : ${it.value}\n" }
responseHeaders += "==== Response Headers = End ====\n"

//Expected a 200 because this is be a pre-flight call that should never reach the resource router
assertEquals(200, response.status)
assertEquals(null, response.getData())
assertEquals(responseHeaders, DEFAULT_CORS_ACCESS_CONTROL_ALLOW_METHODS, response.headers[ACCESS_CONTROL_ALLOW_METHODS].value)
assertEquals(responseHeaders, testAccessControlAllowHeaders, response.headers[ACCESS_CONTROL_ALLOW_HEADERS].value)
assertEquals(responseHeaders, testOrigin, response.headers[ACCESS_CONTROL_ALLOW_ORIGIN].value)
assertEquals(responseHeaders, "true", response.headers[ACCESS_CONTROL_ALLOW_CREDENTIALS].value)
assertEquals(responseHeaders, (72 * 60 * 60) + "", response.headers[ACCESS_CONTROL_MAX_AGE].value)

//Requery "metrics" endpoint to make sure data gets returned and check headers
response = client.get(path: "events", query: [ids: "cors-test-event-id"],
headers: [
(tenantHeaderName): tenantId,
(ORIGIN): testOrigin
])

responseHeaders = "==== Response Headers = Start ====\n"
response.headers.each { responseHeaders += "${it.name} : ${it.value}\n" }
responseHeaders += "==== Response Headers = End ====\n"

assertEquals(200, response.status)
e = response.data
assertEquals(event, e)
assertEquals(responseHeaders, DEFAULT_CORS_ACCESS_CONTROL_ALLOW_METHODS, response.headers[ACCESS_CONTROL_ALLOW_METHODS].value)
assertEquals(responseHeaders, testAccessControlAllowHeaders, response.headers[ACCESS_CONTROL_ALLOW_HEADERS].value)
assertEquals(responseHeaders, testOrigin, response.headers[ACCESS_CONTROL_ALLOW_ORIGIN].value)
assertEquals(responseHeaders, "true", response.headers[ACCESS_CONTROL_ALLOW_CREDENTIALS].value)
assertEquals(responseHeaders, (72 * 60 * 60) + "", response.headers[ACCESS_CONTROL_MAX_AGE].value)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.icegreen.greenmail.util.ServerSetup
import org.hawkular.alerts.rest.ActionsITest
import org.hawkular.alerts.rest.AlertsITest
import org.hawkular.alerts.rest.ConditionsITest
import org.hawkular.alerts.rest.CORSITest
import org.hawkular.alerts.rest.DampeningITest
import org.hawkular.alerts.rest.EventsITest
import org.hawkular.alerts.rest.EventsLifecycleITest
Expand All @@ -46,6 +47,7 @@ import org.junit.runners.Suite;
ActionsITest.class,
AlertsITest.class,
ConditionsITest.class,
CORSITest.class,
DampeningITest.class,
EventsITest.class,
EventsLifecycleITest.class,
Expand Down
6 changes: 6 additions & 0 deletions hawkular-alerts-rest/hawkular-alerts-rest-api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@
<version>${version.org.hawkular.commons}</version>
</dependency>

<dependency>
<groupId>org.hawkular.commons</groupId>
<artifactId>hawkular-cors-jaxrs-filter</artifactId>
<version>${version.org.hawkular.commons}</version>
</dependency>

<!-- Wildfly dependencies -->
<dependency>
<groupId>org.jboss.logging</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright 2015-2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.hawkular.alerts.rest.filter;

import javax.inject.Inject;
import javax.ws.rs.ext.Provider;

import org.hawkular.alerts.engine.impl.AlertProperties;
import org.hawkular.jaxrs.filter.cors.AbstractCorsResponseFilter;

/**
* @author Jay Shaughnessy
*/
@Provider
public class CorsResponseFilter extends AbstractCorsResponseFilter {

// Note, this is prefixed as 'hawkular,' because eventually this may become a hawkular-wide setting.
private static final String ALLOWED_CORS_ACCESS_CONTROL_ALLOW_HEADERS_PROP = "hawkular.allowed-cors-access-control-allow-headers";
private static final String ALLOWED_CORS_ACCESS_CONTROL_ALLOW_HEADERS_ENV = "ALLOWED_CORS_ACCESS_CONTROL_ALLOW_HEADERS";

private String extraAccesControlAllowHeaders = "";

@Inject
OriginValidation validator;

@Override
protected boolean isAllowedOrigin(String requestOrigin) {
return validator.isAllowedOrigin(requestOrigin);
}

@Override
protected String getExtraAccessControlAllowHeaders() {
if ("".equals(extraAccesControlAllowHeaders)) {
extraAccesControlAllowHeaders = AlertProperties.getProperty(ALLOWED_CORS_ACCESS_CONTROL_ALLOW_HEADERS_PROP,
ALLOWED_CORS_ACCESS_CONTROL_ALLOW_HEADERS_ENV, null);
}

return extraAccesControlAllowHeaders;
}
}

0 comments on commit 308d8e4

Please sign in to comment.