Skip to content

Commit d85d62d

Browse files
wy193777Vladimir Kotal
authored andcommitted
Enable CORS for RESTful API (#2711)
fixes #2710
1 parent 30bba29 commit d85d62d

File tree

8 files changed

+182
-3
lines changed

8 files changed

+182
-3
lines changed

opengrok-indexer/src/test/java/org/opengrok/indexer/index/IndexerTest.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
import java.io.FileWriter;
3434
import java.io.IOException;
3535
import java.io.StringWriter;
36-
import java.nio.file.Paths;
3736
import java.util.ArrayList;
3837
import java.util.Arrays;
3938
import java.util.Collections;
@@ -46,7 +45,6 @@
4645
import java.util.concurrent.ConcurrentLinkedQueue;
4746
import java.util.stream.Collectors;
4847
import org.junit.After;
49-
import org.junit.AfterClass;
5048
import org.junit.Assert;
5149
import org.junit.Before;
5250
import org.junit.BeforeClass;

opengrok-web/src/main/java/org/opengrok/web/api/v1/controller/SearchController.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.apache.lucene.search.Query;
2626
import org.opengrok.indexer.search.Hit;
2727
import org.opengrok.indexer.search.SearchEngine;
28+
import org.opengrok.web.api.v1.filter.CorsEnable;
2829
import org.opengrok.web.api.v1.suggester.provider.service.SuggesterService;
2930

3031
import javax.inject.Inject;
@@ -57,6 +58,7 @@ public class SearchController {
5758
private SuggesterService suggester;
5859

5960
@GET
61+
@CorsEnable
6062
@Produces(MediaType.APPLICATION_JSON)
6163
public SearchResult search(
6264
@Context final HttpServletRequest req,

opengrok-web/src/main/java/org/opengrok/web/api/v1/controller/SuggesterController.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.opengrok.indexer.logger.LoggerFactory;
3636
import org.opengrok.indexer.search.QueryBuilder;
3737
import org.opengrok.indexer.web.Util;
38+
import org.opengrok.web.api.v1.filter.CorsEnable;
3839
import org.opengrok.web.api.v1.suggester.model.SuggesterData;
3940
import org.opengrok.web.api.v1.suggester.model.SuggesterQueryData;
4041
import org.opengrok.web.api.v1.suggester.parser.SuggesterQueryDataParser;
@@ -96,6 +97,7 @@ public final class SuggesterController {
9697
*/
9798
@GET
9899
@Authorized
100+
@CorsEnable
99101
@Produces(MediaType.APPLICATION_JSON)
100102
public Result getSuggestions(@Valid @BeanParam final SuggesterQueryData data) throws ParseException {
101103
Instant start = Instant.now();
@@ -160,6 +162,7 @@ private boolean satisfiesConfiguration(final SuggesterData data, final Suggester
160162
*/
161163
@GET
162164
@Path("/config")
165+
@CorsEnable
163166
@Produces(MediaType.APPLICATION_JSON)
164167
public SuggesterConfig getConfig() {
165168
return env.getSuggesterConfig();
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.opengrok.web.api.v1.filter;
2+
3+
import javax.ws.rs.NameBinding;
4+
import java.lang.annotation.ElementType;
5+
import java.lang.annotation.Retention;
6+
import java.lang.annotation.RetentionPolicy;
7+
import java.lang.annotation.Target;
8+
9+
/**
10+
* This decorator is to enable CORS for paths.
11+
*/
12+
@NameBinding
13+
@Target({ElementType.METHOD, ElementType.TYPE})
14+
@Retention(RetentionPolicy.RUNTIME)
15+
public @interface CorsEnable { }
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.opengrok.web.api.v1.filter;
2+
3+
import javax.ws.rs.container.*;
4+
import javax.ws.rs.ext.Provider;
5+
6+
@Provider
7+
@CorsEnable
8+
public class CorsFilter implements ContainerResponseFilter {
9+
10+
public static final String ALLOW_CORS_HEADER = "Access-Control-Allow-Origin";
11+
public static final String CORS_REQUEST_HEADER = "Origin";
12+
13+
/**
14+
* Method for ContainerResponseFilter.
15+
*/
16+
@Override
17+
public void filter(ContainerRequestContext request, ContainerResponseContext response) {
18+
// if there is no Origin header, then it is not a
19+
// cross origin request. We don't do anything.
20+
if (request.getHeaderString(CORS_REQUEST_HEADER) == null) {
21+
return;
22+
}
23+
24+
response.getHeaders().add(ALLOW_CORS_HEADER, "*");
25+
}
26+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package org.opengrok.web;
2+
3+
import org.junit.Test;
4+
import org.opengrok.web.api.v1.filter.CorsFilter;
5+
6+
import javax.ws.rs.container.ContainerRequestContext;
7+
import javax.ws.rs.container.ContainerResponseContext;
8+
import javax.ws.rs.core.MultivaluedHashMap;
9+
import javax.ws.rs.core.MultivaluedMap;
10+
11+
import java.util.Arrays;
12+
import java.util.List;
13+
14+
import static org.junit.Assert.assertEquals;
15+
import static org.mockito.Mockito.mock;
16+
import static org.mockito.Mockito.when;
17+
import static org.opengrok.web.api.v1.filter.CorsFilter.ALLOW_CORS_HEADER;
18+
import static org.opengrok.web.api.v1.filter.CorsFilter.CORS_REQUEST_HEADER;
19+
20+
public class CorsFilterTest {
21+
@Test
22+
public void nonCorsTest() {
23+
testBoth(null, null);
24+
}
25+
26+
@Test
27+
public void CorsTest() {
28+
testBoth("https://example.org", Arrays.asList("*"));
29+
}
30+
31+
private void testBoth(String origin, List<Object> headerValue) {
32+
CorsFilter filter = new CorsFilter();
33+
ContainerRequestContext request = mock(ContainerRequestContext.class);
34+
when(request.getHeaderString(CORS_REQUEST_HEADER)).thenReturn(origin);
35+
36+
ContainerResponseContext response = mock(ContainerResponseContext.class);
37+
MultivaluedMap<String, Object> headers = new MultivaluedHashMap<>();
38+
when(response.getHeaders()).thenReturn(headers);
39+
40+
filter.filter(request, response);
41+
assertEquals(headerValue, headers.get(ALLOW_CORS_HEADER));
42+
}
43+
44+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package org.opengrok.web.api.v1.controller;
2+
3+
import org.glassfish.jersey.test.JerseyTest;
4+
import org.junit.*;
5+
import org.opengrok.indexer.condition.ConditionalRunRule;
6+
import org.opengrok.indexer.configuration.RuntimeEnvironment;
7+
import org.opengrok.indexer.index.Indexer;
8+
import org.opengrok.indexer.util.TestRepository;
9+
import org.opengrok.web.api.v1.RestApp;
10+
11+
import javax.ws.rs.core.Application;
12+
import javax.ws.rs.core.Response;
13+
import java.util.Collections;
14+
15+
import static org.junit.Assert.assertEquals;
16+
import static org.opengrok.web.api.v1.filter.CorsFilter.ALLOW_CORS_HEADER;
17+
import static org.opengrok.web.api.v1.filter.CorsFilter.CORS_REQUEST_HEADER;
18+
19+
public class SearchControllerTest extends JerseyTest {
20+
@ClassRule
21+
public static ConditionalRunRule rule = new ConditionalRunRule();
22+
23+
private static final RuntimeEnvironment env = RuntimeEnvironment.getInstance();
24+
25+
26+
private static TestRepository repository;
27+
28+
@Override
29+
protected Application configure() {
30+
return new RestApp();
31+
}
32+
33+
@BeforeClass
34+
public static void setUpClass() throws Exception {
35+
System.setProperty("sun.net.http.allowRestrictedHeaders", "true"); // necessary to test CORS from controllers
36+
repository = new TestRepository();
37+
38+
repository.create(SearchControllerTest.class.getResourceAsStream("/org/opengrok/indexer/index/source.zip"));
39+
40+
env.setHistoryEnabled(false);
41+
env.setProjectsEnabled(true);
42+
env.setDefaultProjectsFromNames(Collections.singleton("__all__"));
43+
44+
env.getSuggesterConfig().setRebuildCronConfig(null);
45+
}
46+
47+
@AfterClass
48+
public static void tearDownClass() {
49+
repository.destroy();
50+
}
51+
52+
@Before
53+
public void before() {
54+
55+
}
56+
57+
@Test
58+
public void testSearchCors() {
59+
Response response = target(SearchController.PATH)
60+
.request()
61+
.header(CORS_REQUEST_HEADER, "http://example.com")
62+
.get();
63+
assertEquals("*", response.getHeaderString(ALLOW_CORS_HEADER));
64+
}
65+
66+
}

opengrok-web/src/test/java/org/opengrok/web/api/v1/controller/SuggesterControllerTest.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545
import javax.ws.rs.core.Response;
4646
import java.lang.reflect.Field;
4747
import java.util.AbstractMap.SimpleEntry;
48-
import java.util.ArrayList;
4948
import java.util.Arrays;
5049
import java.util.Collections;
5150
import java.util.List;
@@ -60,6 +59,8 @@
6059
import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
6160
import static org.junit.Assert.assertEquals;
6261
import static org.junit.Assert.assertThat;
62+
import static org.opengrok.web.api.v1.filter.CorsFilter.ALLOW_CORS_HEADER;
63+
import static org.opengrok.web.api.v1.filter.CorsFilter.CORS_REQUEST_HEADER;
6364

6465
@ConditionalRun(CtagsInstalled.class)
6566
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@@ -104,6 +105,7 @@ protected Application configure() {
104105

105106
@BeforeClass
106107
public static void setUpClass() throws Exception {
108+
System.setProperty("sun.net.http.allowRestrictedHeaders", "true"); // necessary to test CORS from controllers
107109
repository = new TestRepository();
108110

109111
repository.create(SuggesterControllerTest.class.getResourceAsStream("/org/opengrok/indexer/index/source.zip"));
@@ -152,6 +154,16 @@ public void testGetSuggesterConfig() {
152154
assertEquals(env.getSuggesterConfig(), config);
153155
}
154156

157+
@Test
158+
public void testGetSuggesterConfigCors() {
159+
Response response = target(SuggesterController.PATH)
160+
.path("config")
161+
.request()
162+
.header(CORS_REQUEST_HEADER, "http://example.com")
163+
.get();
164+
assertEquals("*", response.getHeaderString(ALLOW_CORS_HEADER));
165+
}
166+
155167
@Test
156168
public void testGetSuggestionsSimpleFull() {
157169
Result res = target(SuggesterController.PATH)
@@ -165,6 +177,19 @@ public void testGetSuggestionsSimpleFull() {
165177
containsInAnyOrder("innermethod", "innerclass"));
166178
}
167179

180+
@Test
181+
public void testGetSuggestionsCors() {
182+
Response response = target(SuggesterController.PATH)
183+
.queryParam(AuthorizationFilter.PROJECTS_PARAM, "java")
184+
.queryParam("field", QueryBuilder.FULL)
185+
.queryParam(QueryBuilder.FULL, "inner")
186+
.request()
187+
.header(CORS_REQUEST_HEADER, "http://example.com")
188+
.get();
189+
190+
assertEquals("*", response.getHeaderString(ALLOW_CORS_HEADER));
191+
}
192+
168193
@Test
169194
public void testGetSuggestionsSimpleDefs() {
170195
Result res = target(SuggesterController.PATH)

0 commit comments

Comments
 (0)