View Javadoc
1   /*
2    * Copyright 2002-2015 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.web.socket.sockjs.transport.handler;
18  
19  import java.util.Arrays;
20  import java.util.Collections;
21  import java.util.Map;
22  
23  import org.hamcrest.Matchers;
24  import org.junit.Before;
25  import org.junit.Test;
26  import org.mockito.Mock;
27  import org.mockito.MockitoAnnotations;
28  
29  import org.springframework.scheduling.TaskScheduler;
30  import org.springframework.web.socket.AbstractHttpRequestTests;
31  import org.springframework.web.socket.WebSocketHandler;
32  import org.springframework.web.socket.handler.TestPrincipal;
33  import org.springframework.web.socket.server.HandshakeHandler;
34  import org.springframework.web.socket.server.support.OriginHandshakeInterceptor;
35  import org.springframework.web.socket.sockjs.transport.SockJsSessionFactory;
36  import org.springframework.web.socket.sockjs.transport.TransportHandler;
37  import org.springframework.web.socket.sockjs.transport.TransportHandlingSockJsService;
38  import org.springframework.web.socket.sockjs.transport.TransportType;
39  import org.springframework.web.socket.sockjs.transport.session.StubSockJsServiceConfig;
40  import org.springframework.web.socket.sockjs.transport.session.TestSockJsSession;
41  
42  import static org.junit.Assert.*;
43  import static org.mockito.BDDMockito.*;
44  
45  /**
46   * Test fixture for {@link org.springframework.web.socket.sockjs.transport.handler.DefaultSockJsService}.
47   *
48   * @author Rossen Stoyanchev
49   * @author Sebastien Deleuze
50   */
51  public class DefaultSockJsServiceTests extends AbstractHttpRequestTests {
52  
53  	private static final String sockJsPrefix = "/mysockjs";
54  
55  	private static final String sessionId = "session1";
56  
57  	private static final String sessionUrlPrefix = "/server1/" + sessionId + "/";
58  
59  
60  	@Mock private SessionCreatingTransportHandler xhrHandler;
61  
62  	@Mock private TransportHandler xhrSendHandler;
63  
64  	@Mock private SessionCreatingTransportHandler jsonpHandler;
65  
66  	@Mock private TransportHandler jsonpSendHandler;
67  
68  	@Mock private HandshakeTransportHandler wsTransportHandler;
69  
70  	@Mock private WebSocketHandler wsHandler;
71  
72  	@Mock private TaskScheduler taskScheduler;
73  
74  	private TestSockJsSession session;
75  
76  	private TransportHandlingSockJsService service;
77  
78  
79  	@Before
80  	public void setup() {
81  		super.setUp();
82  		MockitoAnnotations.initMocks(this);
83  
84  		Map<String, Object> attributes = Collections.emptyMap();
85  		this.session = new TestSockJsSession(sessionId, new StubSockJsServiceConfig(), this.wsHandler, attributes);
86  
87  		given(this.xhrHandler.getTransportType()).willReturn(TransportType.XHR);
88  		given(this.xhrHandler.createSession(sessionId, this.wsHandler, attributes)).willReturn(this.session);
89  		given(this.xhrSendHandler.getTransportType()).willReturn(TransportType.XHR_SEND);
90  		given(this.jsonpHandler.getTransportType()).willReturn(TransportType.JSONP);
91  		given(this.jsonpHandler.createSession(sessionId, this.wsHandler, attributes)).willReturn(this.session);
92  		given(this.jsonpSendHandler.getTransportType()).willReturn(TransportType.JSONP_SEND);
93  		given(this.wsTransportHandler.getTransportType()).willReturn(TransportType.WEBSOCKET);
94  
95  		this.service = new TransportHandlingSockJsService(this.taskScheduler, this.xhrHandler, this.xhrSendHandler);
96  	}
97  
98  	@Test
99  	public void defaultTransportHandlers() {
100 		DefaultSockJsService service = new DefaultSockJsService(mock(TaskScheduler.class));
101 		Map<TransportType, TransportHandler> handlers = service.getTransportHandlers();
102 
103 		assertEquals(8, handlers.size());
104 		assertNotNull(handlers.get(TransportType.WEBSOCKET));
105 		assertNotNull(handlers.get(TransportType.XHR));
106 		assertNotNull(handlers.get(TransportType.XHR_SEND));
107 		assertNotNull(handlers.get(TransportType.XHR_STREAMING));
108 		assertNotNull(handlers.get(TransportType.JSONP));
109 		assertNotNull(handlers.get(TransportType.JSONP_SEND));
110 		assertNotNull(handlers.get(TransportType.HTML_FILE));
111 		assertNotNull(handlers.get(TransportType.EVENT_SOURCE));
112 	}
113 
114 	@Test
115 	public void defaultTransportHandlersWithOverride() {
116 		XhrReceivingTransportHandler xhrHandler = new XhrReceivingTransportHandler();
117 
118 		DefaultSockJsService service = new DefaultSockJsService(mock(TaskScheduler.class), xhrHandler);
119 		Map<TransportType, TransportHandler> handlers = service.getTransportHandlers();
120 
121 		assertEquals(8, handlers.size());
122 		assertSame(xhrHandler, handlers.get(xhrHandler.getTransportType()));
123 	}
124 
125 	@Test(expected = IllegalArgumentException.class)
126 	public void nullAllowedOriginList() {
127 		this.service.setAllowedOrigins(null);
128 	}
129 
130 	@Test
131 	public void emptyAllowedOriginList() {
132 		this.service.setAllowedOrigins(Arrays.asList());
133 		assertThat(this.service.getAllowedOrigins(), Matchers.empty());
134 	}
135 
136 	@Test(expected = IllegalArgumentException.class)
137 	public void invalidAllowedOrigin() {
138 		this.service.setAllowedOrigins(Arrays.asList("domain.com"));
139 	}
140 
141 	@Test
142 	public void validAllowedOrigins() {
143 		this.service.setAllowedOrigins(Arrays.asList("http://domain.com", "https://domain.com", "*"));
144 	}
145 
146 	@Test
147 	public void customizedTransportHandlerList() {
148 		TransportHandlingSockJsService service = new TransportHandlingSockJsService(
149 				mock(TaskScheduler.class), new XhrPollingTransportHandler(), new XhrReceivingTransportHandler());
150 		Map<TransportType, TransportHandler> actualHandlers = service.getTransportHandlers();
151 
152 		assertEquals(2, actualHandlers.size());
153 	}
154 
155 	@Test
156 	public void handleTransportRequestXhr() throws Exception {
157 		String sockJsPath = sessionUrlPrefix + "xhr";
158 		setRequest("POST", sockJsPrefix + sockJsPath);
159 		this.service.handleRequest(this.request, this.response, sockJsPath, this.wsHandler);
160 
161 		assertEquals(200, this.servletResponse.getStatus());
162 		verify(this.xhrHandler).handleRequest(this.request, this.response, this.wsHandler, this.session);
163 		verify(taskScheduler).scheduleAtFixedRate(any(Runnable.class), eq(service.getDisconnectDelay()));
164 
165 		assertEquals("no-store, no-cache, must-revalidate, max-age=0", this.response.getHeaders().getCacheControl());
166 		assertNull(this.response.getHeaders().getFirst("Access-Control-Allow-Origin"));
167 		assertNull(this.response.getHeaders().getFirst("Access-Control-Allow-Credentials"));
168 	}
169 
170 	@Test  // SPR-12226
171 	public void handleTransportRequestXhrAllowedOriginsMatch() throws Exception {
172 		String sockJsPath = sessionUrlPrefix + "xhr";
173 		setRequest("POST", sockJsPrefix + sockJsPath);
174 		this.service.setAllowedOrigins(Arrays.asList("http://mydomain1.com", "http://mydomain2.com"));
175 		setOrigin("http://mydomain1.com");
176 		this.service.handleRequest(this.request, this.response, sockJsPath, this.wsHandler);
177 
178 		assertEquals(200, this.servletResponse.getStatus());
179 		assertEquals("http://mydomain1.com", this.response.getHeaders().getFirst("Access-Control-Allow-Origin"));
180 		assertEquals("true", this.response.getHeaders().getFirst("Access-Control-Allow-Credentials"));
181 	}
182 
183 	@Test  // SPR-12226
184 	public void handleTransportRequestXhrAllowedOriginsNoMatch() throws Exception {
185 		String sockJsPath = sessionUrlPrefix + "xhr";
186 		setRequest("POST", sockJsPrefix + sockJsPath);
187 		this.service.setAllowedOrigins(Arrays.asList("http://mydomain1.com", "http://mydomain2.com"));
188 		setOrigin("http://mydomain3.com");
189 		this.service.handleRequest(this.request, this.response, sockJsPath, this.wsHandler);
190 
191 		assertEquals(403, this.servletResponse.getStatus());
192 		assertNull(this.response.getHeaders().getFirst("Access-Control-Allow-Origin"));
193 		assertNull(this.response.getHeaders().getFirst("Access-Control-Allow-Credentials"));
194 	}
195 
196 	@Test
197 	public void handleTransportRequestXhrOptions() throws Exception {
198 		String sockJsPath = sessionUrlPrefix + "xhr";
199 		setRequest("OPTIONS", sockJsPrefix + sockJsPath);
200 		this.service.handleRequest(this.request, this.response, sockJsPath, this.wsHandler);
201 
202 		assertEquals(204, this.servletResponse.getStatus());
203 		assertNull(this.response.getHeaders().getFirst("Access-Control-Allow-Origin"));
204 		assertNull(this.response.getHeaders().getFirst("Access-Control-Allow-Credentials"));
205 		assertNull(this.response.getHeaders().getFirst("Access-Control-Allow-Methods"));
206 	}
207 
208 	@Test
209 	public void handleTransportRequestNoSuitableHandler() throws Exception {
210 		String sockJsPath = sessionUrlPrefix + "eventsource";
211 		setRequest("POST", sockJsPrefix + sockJsPath);
212 		this.service.handleRequest(this.request, this.response, sockJsPath, this.wsHandler);
213 
214 		assertEquals(404, this.servletResponse.getStatus());
215 	}
216 
217 	@Test
218 	public void handleTransportRequestXhrSend() throws Exception {
219 		String sockJsPath = sessionUrlPrefix + "xhr_send";
220 		setRequest("POST", sockJsPrefix + sockJsPath);
221 		this.service.handleRequest(this.request, this.response, sockJsPath, this.wsHandler);
222 
223 		assertEquals(404, this.servletResponse.getStatus()); // no session yet
224 
225 		resetResponse();
226 		sockJsPath = sessionUrlPrefix + "xhr";
227 		setRequest("POST", sockJsPrefix + sockJsPath);
228 		this.service.handleRequest(this.request, this.response, sockJsPath, this.wsHandler);
229 
230 		assertEquals(200, this.servletResponse.getStatus()); // session created
231 		verify(this.xhrHandler).handleRequest(this.request, this.response, this.wsHandler, this.session);
232 
233 		resetResponse();
234 		sockJsPath = sessionUrlPrefix + "xhr_send";
235 		setRequest("POST", sockJsPrefix + sockJsPath);
236 		this.service.handleRequest(this.request, this.response, sockJsPath, this.wsHandler);
237 
238 		assertEquals(200, this.servletResponse.getStatus()); // session exists
239 		verify(this.xhrSendHandler).handleRequest(this.request, this.response, this.wsHandler, this.session);
240 	}
241 
242 	@Test
243 	public void handleTransportRequestXhrSendWithDifferentUser() throws Exception {
244 		String sockJsPath = sessionUrlPrefix + "xhr";
245 		setRequest("POST", sockJsPrefix + sockJsPath);
246 		this.service.handleRequest(this.request, this.response, sockJsPath, this.wsHandler);
247 
248 		assertEquals(200, this.servletResponse.getStatus()); // session created
249 		verify(this.xhrHandler).handleRequest(this.request, this.response, this.wsHandler, this.session);
250 
251 		this.session.setPrincipal(new TestPrincipal("little red riding hood"));
252 		this.servletRequest.setUserPrincipal(new TestPrincipal("wolf"));
253 
254 		resetResponse();
255 		reset(this.xhrSendHandler);
256 		sockJsPath = sessionUrlPrefix + "xhr_send";
257 		setRequest("POST", sockJsPrefix + sockJsPath);
258 		this.service.handleRequest(this.request, this.response, sockJsPath, this.wsHandler);
259 
260 		assertEquals(404, this.servletResponse.getStatus());
261 		verifyNoMoreInteractions(this.xhrSendHandler);
262 	}
263 
264 	@Test
265 	 public void handleTransportRequestJsonp() throws Exception {
266 		TransportHandlingSockJsService jsonpService = new TransportHandlingSockJsService(this.taskScheduler, this.jsonpHandler, this.jsonpSendHandler);
267 		String sockJsPath = sessionUrlPrefix+ "jsonp";
268 		setRequest("GET", sockJsPrefix + sockJsPath);
269 		jsonpService.handleRequest(this.request, this.response, sockJsPath, this.wsHandler);
270 		assertEquals(404, this.servletResponse.getStatus());
271 
272 		resetRequestAndResponse();
273 		jsonpService.setAllowedOrigins(Arrays.asList("http://mydomain1.com"));
274 		setRequest("GET", sockJsPrefix + sockJsPath);
275 		jsonpService.handleRequest(this.request, this.response, sockJsPath, this.wsHandler);
276 		assertEquals(404, this.servletResponse.getStatus());
277 
278 		resetRequestAndResponse();
279 		jsonpService.setAllowedOrigins(Arrays.asList("*"));
280 		setRequest("GET", sockJsPrefix + sockJsPath);
281 		jsonpService.handleRequest(this.request, this.response, sockJsPath, this.wsHandler);
282 		assertNotEquals(404, this.servletResponse.getStatus());
283 	}
284 
285 	@Test
286 	public void handleTransportRequestWebsocket() throws Exception {
287 		TransportHandlingSockJsService wsService = new TransportHandlingSockJsService(this.taskScheduler, this.wsTransportHandler);
288 		String sockJsPath = "/websocket";
289 		setRequest("GET", sockJsPrefix + sockJsPath);
290 		wsService.handleRequest(this.request, this.response, sockJsPath, this.wsHandler);
291 		assertNotEquals(403, this.servletResponse.getStatus());
292 
293 		resetRequestAndResponse();
294 		OriginHandshakeInterceptor interceptor = new OriginHandshakeInterceptor(Arrays.asList("http://mydomain1.com"));
295 		wsService.setHandshakeInterceptors(Arrays.asList(interceptor));
296 		setRequest("GET", sockJsPrefix + sockJsPath);
297 		setOrigin("http://mydomain1.com");
298 		wsService.handleRequest(this.request, this.response, sockJsPath, this.wsHandler);
299 		assertNotEquals(403, this.servletResponse.getStatus());
300 
301 		resetRequestAndResponse();
302 		setRequest("GET", sockJsPrefix + sockJsPath);
303 		setOrigin("http://mydomain2.com");
304 		wsService.handleRequest(this.request, this.response, sockJsPath, this.wsHandler);
305 		assertEquals(403, this.servletResponse.getStatus());
306 	}
307 
308 	@Test
309 	public void handleTransportRequestIframe() throws Exception {
310 		String sockJsPath = "/iframe.html";
311 		setRequest("GET", sockJsPrefix + sockJsPath);
312 		this.service.handleRequest(this.request, this.response, sockJsPath, this.wsHandler);
313 		assertNotEquals(404, this.servletResponse.getStatus());
314 		assertEquals("SAMEORIGIN", this.servletResponse.getHeader("X-Frame-Options"));
315 
316 		resetRequestAndResponse();
317 		setRequest("GET", sockJsPrefix + sockJsPath);
318 		this.service.setAllowedOrigins(Arrays.asList("http://mydomain1.com"));
319 		this.service.handleRequest(this.request, this.response, sockJsPath, this.wsHandler);
320 		assertEquals(404, this.servletResponse.getStatus());
321 		assertNull(this.servletResponse.getHeader("X-Frame-Options"));
322 
323 		resetRequestAndResponse();
324 		setRequest("GET", sockJsPrefix + sockJsPath);
325 		this.service.setAllowedOrigins(Arrays.asList("*"));
326 		this.service.handleRequest(this.request, this.response, sockJsPath, this.wsHandler);
327 		assertNotEquals(404, this.servletResponse.getStatus());
328 		assertNull(this.servletResponse.getHeader("X-Frame-Options"));
329 	}
330 
331 
332 	interface SessionCreatingTransportHandler extends TransportHandler, SockJsSessionFactory {
333 	}
334 
335 	interface HandshakeTransportHandler extends TransportHandler, HandshakeHandler {
336 	}
337 
338 }