Skip to content
This repository has been archived by the owner on Sep 8, 2021. It is now read-only.

Fix CORS #1790

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,6 @@ public class HLSController {

@GetMapping
public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {

response.setHeader("Access-Control-Allow-Origin", "*");

int id = ServletRequestUtils.getIntParameter(request, "id", 0);
MediaFile mediaFile = mediaFileService.getMediaFile(id);
Player player = playerService.getPlayer(request, response);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,6 @@ public void handleRequest(HttpServletRequest request, HttpServletResponse respon
LOG.info("{}: Incoming Podcast request for playlist {}", request.getRemoteAddr(), playlistId);
}

response.setHeader("Access-Control-Allow-Origin", "*");

String contentType = StringUtil.getMimeType(request.getParameter("suffix"));
response.setContentType(contentType);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@
/**
* Intercepts exceptions thrown by RESTController.
*
* Also adds the CORS response header (http://enable-cors.org)
*
* @author Sindre Mehus
* @version $Revision: 1.1 $ $Date: 2006/03/01 16:58:08 $
*/
Expand All @@ -46,8 +44,6 @@ public class RESTFilter implements Filter {

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
try {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
chain.doFilter(req, res);
} catch (Throwable x) {
handleException(x, (HttpServletRequest) req, (HttpServletResponse) res);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.security.SecureRandom;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

Expand Down Expand Up @@ -151,12 +155,10 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {

RESTRequestParameterProcessingFilter restAuthenticationFilter = new RESTRequestParameterProcessingFilter();
restAuthenticationFilter.setAuthenticationManager(authenticationManagerBean());
restAuthenticationFilter.setSecurityService(securityService);
restAuthenticationFilter.setEventPublisher(eventPublisher);
http = http.addFilterBefore(restAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

// Try to load the 'remember me' key.
//
Expand Down Expand Up @@ -185,6 +187,9 @@ protected void configure(HttpSecurity http) throws Exception {
}

http
.cors()
.and()
.addFilterBefore(restAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.csrf()
.requireCsrfProtectionMatcher(csrfSecurityRequestMatcher)
.and().headers()
Expand Down Expand Up @@ -232,4 +237,18 @@ protected void configure(HttpSecurity http) throws Exception {
}

}

@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Collections.singletonList("*"));
configuration.setAllowedMethods(Collections.singletonList("*"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/rest/**", configuration);
source.registerCorsConfiguration("/stream/**", configuration);
source.registerCorsConfiguration("/ext/stream/**", configuration);
source.registerCorsConfiguration("/ext/hls/**", configuration);
source.registerCorsConfiguration("/ext/hls/**", configuration);
return source;
}
}
103 changes: 103 additions & 0 deletions airsonic-main/src/test/java/org/airsonic/player/api/CORSTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package org.airsonic.player.api;

import org.airsonic.player.TestCaseUtils;
import org.airsonic.player.util.HomeRule;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;


@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class CORSTest {

private static final String CLIENT_NAME = "airsonic";
private static final String AIRSONIC_USER = "admin";
private static final String AIRSONIC_PASSWORD = "admin";
private static final String EXPECTED_FORMAT = "json";
private static String AIRSONIC_API_VERSION;

@Autowired
private WebApplicationContext wac;

private MockMvc mvc;

@Before
public void setup() {
AIRSONIC_API_VERSION = TestCaseUtils.restApiVersion();
mvc = MockMvcBuilders
.webAppContextSetup(wac)
.apply(springSecurity())
.dispatchOptions(true)
.alwaysDo(print())
.build();
}

@ClassRule
public static final HomeRule classRule = new HomeRule();

@Test
public void corsHeadersShouldBeAddedToSuccessResponses() throws Exception {
mvc.perform(get("/rest/ping")
.param("v", AIRSONIC_API_VERSION)
.param("c", CLIENT_NAME)
.param("u", AIRSONIC_USER)
.param("p", AIRSONIC_PASSWORD)
.param("f", EXPECTED_FORMAT)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.subsonic-response.status").value("ok"));
}

@Test
public void corsHeadersShouldBeAddedToErrorResponses() throws Exception {
mvc.perform(get("/rest/ping")
.header("Access-Control-Request-Method", "GET")
.header("Origin", "https://example.com")
.param("v", AIRSONIC_API_VERSION)
.param("c", CLIENT_NAME)
.param("u", AIRSONIC_USER)
.param("p", "incorrect password")
.param("f", EXPECTED_FORMAT)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.subsonic-response.status").value("failed"))
.andExpect(header().exists(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN));
}

@Test
public void corsShouldNotBeEnabledForOtherPaths() throws Exception {
mvc.perform(get("/login")
.header("Access-Control-Request-Method", "GET")
.header("Origin", "https://example.com"))
.andExpect(status().isOk())
.andExpect(header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN));
}

@Test
public void testOptionRequest() throws Exception {
mvc.perform(options("/rest/ping")
.header("Access-Control-Request-Method", "GET")
.header("Origin", "https://example.com")
.param("v", AIRSONIC_API_VERSION)
.param("c", CLIENT_NAME)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(header().exists(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN));
}

}