From a370f228b53b627e4e1d057967e06168bfc72670 Mon Sep 17 00:00:00 2001 From: Roman Puchkovskiy Date: Wed, 27 Apr 2022 22:50:43 +0400 Subject: [PATCH 1/3] Send Origin header with https:// schema for secure requests Otherwise, Origin validation fails for such requests Signed-off-by: Roman Puchkovskiy --- .../org/glassfish/tyrus/core/Handshake.java | 4 +- .../tyrus/core/HandshakeUnitTest.java | 57 +++++++++ pom.xml | 11 ++ tests/e2e/standard-config/pom.xml | 10 ++ .../test/standard_config/WssApplication.java | 32 +++++ .../test/standard_config/WssOriginTest.java | 114 ++++++++++++++++++ .../springboot/WebSocketConfig.java | 34 ++++++ .../src/test/resources/application.yml | 21 ++++ .../src/test/resources/keystore_server | Bin 0 -> 2629 bytes .../test/resources/note-about-keystore.txt | 18 +++ 10 files changed, 300 insertions(+), 1 deletion(-) create mode 100644 core/src/test/java/org/glassfish/tyrus/core/HandshakeUnitTest.java create mode 100644 tests/e2e/standard-config/src/test/java/org/glassfish/tyrus/test/standard_config/WssApplication.java create mode 100644 tests/e2e/standard-config/src/test/java/org/glassfish/tyrus/test/standard_config/WssOriginTest.java create mode 100644 tests/e2e/standard-config/src/test/java/org/glassfish/tyrus/test/standard_config/springboot/WebSocketConfig.java create mode 100644 tests/e2e/standard-config/src/test/resources/application.yml create mode 100644 tests/e2e/standard-config/src/test/resources/keystore_server create mode 100644 tests/e2e/standard-config/src/test/resources/note-about-keystore.txt diff --git a/core/src/main/java/org/glassfish/tyrus/core/Handshake.java b/core/src/main/java/org/glassfish/tyrus/core/Handshake.java index 7faeb001..bc717945 100644 --- a/core/src/main/java/org/glassfish/tyrus/core/Handshake.java +++ b/core/src/main/java/org/glassfish/tyrus/core/Handshake.java @@ -185,7 +185,9 @@ public static void updateHostAndOrigin(final UpgradeRequest upgradeRequest) { Map> requestHeaders = upgradeRequest.getHeaders(); requestHeaders.put(UpgradeRequest.HOST, Collections.singletonList(host)); - requestHeaders.put(UpgradeRequest.ORIGIN_HEADER, Collections.singletonList("http://" + host)); + + String originSchema = upgradeRequest.isSecure() ? "https" : "http"; + requestHeaders.put(UpgradeRequest.ORIGIN_HEADER, Collections.singletonList(originSchema + "://" + host)); } /** diff --git a/core/src/test/java/org/glassfish/tyrus/core/HandshakeUnitTest.java b/core/src/test/java/org/glassfish/tyrus/core/HandshakeUnitTest.java new file mode 100644 index 00000000..9271ea4d --- /dev/null +++ b/core/src/test/java/org/glassfish/tyrus/core/HandshakeUnitTest.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.tyrus.core; + +import java.net.URI; + +import org.glassfish.tyrus.spi.UpgradeRequest; + +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Unit tests for {@link Handshake}. + * + * @author Roman Puchkovskiy + */ +public class HandshakeUnitTest { + @Test + public void originForPlainConnectionShouldStartWithHttp() throws Exception { + UpgradeRequest upgradeRequest = new RequestContext.Builder() + .requestURI(new URI("ws://localhost:8443/echo")) + .secure(false) + .build(); + + Handshake.updateHostAndOrigin(upgradeRequest); + + assertThat(upgradeRequest.getHeader("Origin"), is("http://localhost:8443")); + } + + @Test + public void originForSecureConnectionShouldStartWithHttps() throws Exception { + UpgradeRequest upgradeRequest = new RequestContext.Builder() + .requestURI(new URI("wss://localhost:8443/echo")) + .secure(true) + .build(); + + Handshake.updateHostAndOrigin(upgradeRequest); + + assertThat(upgradeRequest.getHeader("Origin"), is("https://localhost:8443")); + } +} diff --git a/pom.xml b/pom.xml index b845cb38..4762a990 100644 --- a/pom.xml +++ b/pom.xml @@ -107,6 +107,7 @@ 1.2 1.1.6 1.1.6 + 2.6.7 javax.websocket org.glassfish @@ -630,6 +631,16 @@ jakarta.annotation-api ${javax.annotation.version} + + org.springframework.boot + spring-boot-starter-websocket + ${spring.boot.version} + + + org.springframework.boot + spring-boot-starter-test + ${spring.boot.version} + diff --git a/tests/e2e/standard-config/pom.xml b/tests/e2e/standard-config/pom.xml index d04cfa70..27553559 100755 --- a/tests/e2e/standard-config/pom.xml +++ b/tests/e2e/standard-config/pom.xml @@ -57,6 +57,16 @@ org.glassfish.tyrus.tests tyrus-test-tools + + org.springframework.boot + spring-boot-starter-websocket + test + + + org.springframework.boot + spring-boot-starter-test + test + diff --git a/tests/e2e/standard-config/src/test/java/org/glassfish/tyrus/test/standard_config/WssApplication.java b/tests/e2e/standard-config/src/test/java/org/glassfish/tyrus/test/standard_config/WssApplication.java new file mode 100644 index 00000000..df5ca48f --- /dev/null +++ b/tests/e2e/standard-config/src/test/java/org/glassfish/tyrus/test/standard_config/WssApplication.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.tyrus.test.standard_config; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; + +/** + * @author Roman Puchkovskiy + */ +@SpringBootApplication +@EnableWebSocketMessageBroker +public class WssApplication { + public static void main(String[] args) { + SpringApplication.run(WssApplication.class, args); + } +} diff --git a/tests/e2e/standard-config/src/test/java/org/glassfish/tyrus/test/standard_config/WssOriginTest.java b/tests/e2e/standard-config/src/test/java/org/glassfish/tyrus/test/standard_config/WssOriginTest.java new file mode 100644 index 00000000..0bb6af38 --- /dev/null +++ b/tests/e2e/standard-config/src/test/java/org/glassfish/tyrus/test/standard_config/WssOriginTest.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.tyrus.test.standard_config; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import javax.websocket.ClientEndpointConfig; +import javax.websocket.Endpoint; +import javax.websocket.EndpointConfig; +import javax.websocket.Session; +import java.net.URI; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import org.glassfish.tyrus.client.ClientManager; +import org.glassfish.tyrus.client.SslEngineConfigurator; +import org.glassfish.tyrus.test.standard_config.springboot.WebSocketConfig; +import org.glassfish.tyrus.test.tools.TestContainer; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.glassfish.tyrus.client.ClientProperties.SSL_ENGINE_CONFIGURATOR; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +/** + * This tests that a correct Origin is sent when working with a wss:// connection. + * A Spring Boot application is started to easily start a TLS-protected Websocket server that + * verifies Origin header. + * + * @author Roman Puchkovskiy + */ +@SpringBootTest(classes = {WssApplication.class, WebSocketConfig.class}, webEnvironment = RANDOM_PORT) +@RunWith(SpringRunner.class) +public class WssOriginTest extends TestContainer { + @LocalServerPort + private int serverPort; + + @Test + public void wssConnectionToServerValidatingOriginShouldWork() throws Exception { + final ClientEndpointConfig cec = ClientEndpointConfig.Builder.create() + .build(); + + ClientManager client = createClient(); + client.getProperties().put(SSL_ENGINE_CONFIGURATOR, createSslEngineConfigurator()); + + client.connectToServer(new Endpoint() { + @Override + public void onOpen(final Session session, EndpointConfig EndpointConfig) { + } + }, cec, new URI("wss://localhost:" + serverPort + "/hello")); + } + + private SslEngineConfigurator createSslEngineConfigurator() { + return createTrustAllSslEngineConfigurator(); + } + + private SslEngineConfigurator createTrustAllSslEngineConfigurator() { + try { + SSLContext ctx = SSLContext.getInstance("TLS"); + + ctx.init(null, new TrustManager[]{new DisabledX509TrustManager()}, null); + + SslEngineConfigurator sslEngineConfigurator = new SslEngineConfigurator(ctx, true, false, false); + + sslEngineConfigurator.setHostVerificationEnabled(false); + + return sslEngineConfigurator; + } catch (NoSuchAlgorithmException | KeyManagementException e) { + throw new IllegalStateException(e); + } + } + + private static class DisabledX509TrustManager implements X509TrustManager { + private static final X509Certificate[] CERTS = new X509Certificate[0]; + + @Override + public void checkClientTrusted(X509Certificate[] x509Certificates, String s) + throws CertificateException { + // No-op, all clients are trusted. + } + + @Override + public void checkServerTrusted(X509Certificate[] x509Certificates, String s) + throws CertificateException { + // No-op, all servers are trusted. + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return CERTS; + } + } +} diff --git a/tests/e2e/standard-config/src/test/java/org/glassfish/tyrus/test/standard_config/springboot/WebSocketConfig.java b/tests/e2e/standard-config/src/test/java/org/glassfish/tyrus/test/standard_config/springboot/WebSocketConfig.java new file mode 100644 index 00000000..70e10802 --- /dev/null +++ b/tests/e2e/standard-config/src/test/java/org/glassfish/tyrus/test/standard_config/springboot/WebSocketConfig.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.tyrus.test.standard_config.springboot; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.config.annotation.StompEndpointRegistry; +import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; + +/** + * Spring Config for {@link org.glassfish.tyrus.test.standard_config.WssOriginTest}. + * + * @author Roman Puchkovskiy + */ +@Configuration +public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { + @Override + public void registerStompEndpoints(StompEndpointRegistry registry) { + registry.addEndpoint("/hello"); + } +} diff --git a/tests/e2e/standard-config/src/test/resources/application.yml b/tests/e2e/standard-config/src/test/resources/application.yml new file mode 100644 index 00000000..50355287 --- /dev/null +++ b/tests/e2e/standard-config/src/test/resources/application.yml @@ -0,0 +1,21 @@ +# +# Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0, which is available at +# http://www.eclipse.org/legal/epl-2.0. +# +# This Source Code may also be made available under the following Secondary +# Licenses when the conditions for such availability set forth in the +# Eclipse Public License v. 2.0 are satisfied: GNU General Public License, +# version 2 with the GNU Classpath Exception, which is available at +# https://www.gnu.org/software/classpath/license.html. +# +# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 +# + +server: + ssl: + key-store-type: JKS + key-store: classpath:keystore_server + key-store-password: asdfgh diff --git a/tests/e2e/standard-config/src/test/resources/keystore_server b/tests/e2e/standard-config/src/test/resources/keystore_server new file mode 100644 index 0000000000000000000000000000000000000000..88039a2d101fa6cfe90832495a9ba8fcee935062 GIT binary patch literal 2629 zcmY+Ec{mh`8pelNG$R$pkS!uLMaD3MLdZHA``ELDkY#KmPL?5=>><0x8fuK)389oU zYHV3TXtFPZBw5OJ&vWm&_x$m_-}Aih_q~7qP$bw@b~Yf21l@%|&&TS;Zga75uu(|R zWe^EEe`tS2k-)zHh`^2@64>t0wmKYh26d3DJ0MyiUit5AtA8;+K1&rAb4LD z>S+`}Lfg~pQCmo_+jD-Alq7Z_pwk#c0(CEQirk(w&_^#`c)NtKZ75pmBeRd5b$Bg9 z|6;s_2q8$|ewfp~xR4swOxxsmTaNpU+jKv{#`*xYA}{at+ydKmNO=b+6RK~&j|#2} zHT;uD{3k`pkM$mJ;qDoK%>l39fcQ|0oT4rB}ez%C%8 zK_`1&<{Zcw7pUX(jH}dR(z=qyf~2g}P>i?20adnx8b4kdS5JgLCuR=V!N|v#wG{7n z^RnDm_xpvFk(`PwEdbUu-?r%drL5iM2cywg(cMo+sTMmL)b`b@(@J3@;67NaB~>Dk zeaw@nB6;#u<8oV4go^DGdMg=PU0xcIl16NTR@eeF!819b ztJOCj%=l1q3{zLOjwtl$L>PFh3&O2i952<}RNC*InZNZbeVl$?=Znre_;>1-rnJw_ zr$yV#?V?fq7r-L1`)s?Sraz;zjR_(z;=K9RmBq@oMw=I2OrjIF(}P4CaFZ_6bCB6A zVH!1@Q{S$!C-kmH(Qh>NxWR9e%5G|*^o6>JP@D92SH;*6d)|}NM)36(|H@*D-7g6xpejS3Uv`B#T(#T}40*|u<|~xl)kb8vsO6+miDv|GXD@{a79wZuA$JgGoW!bo z_iEENM0G=*qPIbXVOGSD@ti85=(V=KxV_3@#Ueb#Y{h}$kkO(aCMOsw6gTmv?O6b_ zgL%cwR_A)U9yiy(8retdtog~dQ@ZicMVtKJG;L+o&&ewwerc)>)stZDwDCc=A$yde zit5Y%2#_?F^|s%0_fSlJr&?%4PQusv%^~T8hgtS{YAYg3mQ}_IWsUutT@}^5Dc7C$ zWO$~rR;#a?*DD8W7t7+$`Yck;O6|0z5N~W-d zsL&cn9iRRbYOfs`6yIBg<4bxx30Nzf&wU$1iQ68Vfp-8?7&4C=O4f-V3}gn%&&lba z#Q#Pm6fOrBNlNB$#%oW2Qg78cjri^0%b@%)(g3kprAYy@Fh5uG5hHKsfZ!e986Ti_eev|aULM%6nin`BDDc*SALip1J?mcJf{kr6PvXr`q z#AE&um8RrV&lu{HM{84htF;@e(=u8%hbnKrc*Joc!7>i2H*x>^!#9qj?RRQ!VXWs^ z2p$#2o$-NsSPoa%4dUI`s}7U+4Tea|tDrCbu8dXMx%!ljRr{I!n;f6lnwveBdzmaX zz3+L?FVDCn2XXOMS{`u-jf-$Vh;QUw#ca;AbnA*!r|%Buz_mlqT+OAPzTUpdGnJlXHE2RH0BpI|#pCvnCD6$aD2rhnT9{dw=ccF&#N#0QEUeUaf;px|dIUR05U{W4)?`zRs>MnJ%*MsnYQ|Lr~r7e(&`t6%RS&8%>Hp zeU1a&V?uV$HCO@pWX4FeUMp_U#)_N0d_P!x!{gPNQd)%ai}}ki{AzAY4}Y%TXddH{ z_KA+vVRdw5+*tv!)x3uD7+@F^kQYUB`CPJ85MIkATXnNHj7*GS!vs3)ylr9B(tb~()sK6x(yXT(mi*uId zD%2*hJ?5&ZSq#fV5ZnyBM8bDBx_7(I?TeW1p_lrB?hs*H<#4dql9_R_l4E^L&F4g0T`W$elJ+k6)}j&-#Kbz5n>ZSEA$CMsg~tb*=0no$TxB8szfz;h>Ugb9yV`IBw! z?3P0#`KuQ;dSb4qGQ@t(`uC*ex$fn~t0);>VX#T_MTf{7D+XvZeG{0(9YtSpVb8W8 zwnZ2yZP~s>k1bpEbkX;ZA0Kv}_#sT Date: Tue, 10 May 2022 13:57:46 +0200 Subject: [PATCH 2/3] Postpone SpringBoot tests until CQ is approved. Signed-off-by: jansupol --- .../org/glassfish/tyrus/core/Handshake.java | 2 +- pom.xml | 22 +- tests/e2e/standard-config/pom.xml | 22 +- .../test/standard_config/WssApplication.java | 28 +-- .../test/standard_config/WssOriginTest.java | 192 +++++++++--------- .../springboot/WebSocketConfig.java | 32 +-- 6 files changed, 149 insertions(+), 149 deletions(-) diff --git a/core/src/main/java/org/glassfish/tyrus/core/Handshake.java b/core/src/main/java/org/glassfish/tyrus/core/Handshake.java index bc717945..29ed623b 100644 --- a/core/src/main/java/org/glassfish/tyrus/core/Handshake.java +++ b/core/src/main/java/org/glassfish/tyrus/core/Handshake.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2017 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at diff --git a/pom.xml b/pom.xml index 4762a990..544b843f 100644 --- a/pom.xml +++ b/pom.xml @@ -1,6 +1,6 @@ + + + + + + + + + diff --git a/tests/e2e/standard-config/pom.xml b/tests/e2e/standard-config/pom.xml index 27553559..33976276 100755 --- a/tests/e2e/standard-config/pom.xml +++ b/tests/e2e/standard-config/pom.xml @@ -1,6 +1,6 @@ + + + + + + + + + diff --git a/tests/e2e/standard-config/src/test/java/org/glassfish/tyrus/test/standard_config/WssApplication.java b/tests/e2e/standard-config/src/test/java/org/glassfish/tyrus/test/standard_config/WssApplication.java index df5ca48f..f5011b0e 100644 --- a/tests/e2e/standard-config/src/test/java/org/glassfish/tyrus/test/standard_config/WssApplication.java +++ b/tests/e2e/standard-config/src/test/java/org/glassfish/tyrus/test/standard_config/WssApplication.java @@ -16,17 +16,17 @@ package org.glassfish.tyrus.test.standard_config; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; - -/** - * @author Roman Puchkovskiy - */ -@SpringBootApplication -@EnableWebSocketMessageBroker -public class WssApplication { - public static void main(String[] args) { - SpringApplication.run(WssApplication.class, args); - } -} +//import org.springframework.boot.SpringApplication; +//import org.springframework.boot.autoconfigure.SpringBootApplication; +//import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; +// +///** +// * @author Roman Puchkovskiy +// */ +//@SpringBootApplication +//@EnableWebSocketMessageBroker +//public class WssApplication { +// public static void main(String[] args) { +// SpringApplication.run(WssApplication.class, args); +// } +//} diff --git a/tests/e2e/standard-config/src/test/java/org/glassfish/tyrus/test/standard_config/WssOriginTest.java b/tests/e2e/standard-config/src/test/java/org/glassfish/tyrus/test/standard_config/WssOriginTest.java index 0bb6af38..376adda9 100644 --- a/tests/e2e/standard-config/src/test/java/org/glassfish/tyrus/test/standard_config/WssOriginTest.java +++ b/tests/e2e/standard-config/src/test/java/org/glassfish/tyrus/test/standard_config/WssOriginTest.java @@ -16,99 +16,99 @@ package org.glassfish.tyrus.test.standard_config; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; -import javax.websocket.ClientEndpointConfig; -import javax.websocket.Endpoint; -import javax.websocket.EndpointConfig; -import javax.websocket.Session; -import java.net.URI; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; - -import org.glassfish.tyrus.client.ClientManager; -import org.glassfish.tyrus.client.SslEngineConfigurator; -import org.glassfish.tyrus.test.standard_config.springboot.WebSocketConfig; -import org.glassfish.tyrus.test.tools.TestContainer; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.web.server.LocalServerPort; -import org.springframework.test.context.junit4.SpringRunner; - -import static org.glassfish.tyrus.client.ClientProperties.SSL_ENGINE_CONFIGURATOR; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -/** - * This tests that a correct Origin is sent when working with a wss:// connection. - * A Spring Boot application is started to easily start a TLS-protected Websocket server that - * verifies Origin header. - * - * @author Roman Puchkovskiy - */ -@SpringBootTest(classes = {WssApplication.class, WebSocketConfig.class}, webEnvironment = RANDOM_PORT) -@RunWith(SpringRunner.class) -public class WssOriginTest extends TestContainer { - @LocalServerPort - private int serverPort; - - @Test - public void wssConnectionToServerValidatingOriginShouldWork() throws Exception { - final ClientEndpointConfig cec = ClientEndpointConfig.Builder.create() - .build(); - - ClientManager client = createClient(); - client.getProperties().put(SSL_ENGINE_CONFIGURATOR, createSslEngineConfigurator()); - - client.connectToServer(new Endpoint() { - @Override - public void onOpen(final Session session, EndpointConfig EndpointConfig) { - } - }, cec, new URI("wss://localhost:" + serverPort + "/hello")); - } - - private SslEngineConfigurator createSslEngineConfigurator() { - return createTrustAllSslEngineConfigurator(); - } - - private SslEngineConfigurator createTrustAllSslEngineConfigurator() { - try { - SSLContext ctx = SSLContext.getInstance("TLS"); - - ctx.init(null, new TrustManager[]{new DisabledX509TrustManager()}, null); - - SslEngineConfigurator sslEngineConfigurator = new SslEngineConfigurator(ctx, true, false, false); - - sslEngineConfigurator.setHostVerificationEnabled(false); - - return sslEngineConfigurator; - } catch (NoSuchAlgorithmException | KeyManagementException e) { - throw new IllegalStateException(e); - } - } - - private static class DisabledX509TrustManager implements X509TrustManager { - private static final X509Certificate[] CERTS = new X509Certificate[0]; - - @Override - public void checkClientTrusted(X509Certificate[] x509Certificates, String s) - throws CertificateException { - // No-op, all clients are trusted. - } - - @Override - public void checkServerTrusted(X509Certificate[] x509Certificates, String s) - throws CertificateException { - // No-op, all servers are trusted. - } - - @Override - public X509Certificate[] getAcceptedIssuers() { - return CERTS; - } - } -} +//import javax.net.ssl.SSLContext; +//import javax.net.ssl.TrustManager; +//import javax.net.ssl.X509TrustManager; +//import javax.websocket.ClientEndpointConfig; +//import javax.websocket.Endpoint; +//import javax.websocket.EndpointConfig; +//import javax.websocket.Session; +//import java.net.URI; +//import java.security.KeyManagementException; +//import java.security.NoSuchAlgorithmException; +//import java.security.cert.CertificateException; +//import java.security.cert.X509Certificate; +// +//import org.glassfish.tyrus.client.ClientManager; +//import org.glassfish.tyrus.client.SslEngineConfigurator; +//import org.glassfish.tyrus.test.standard_config.springboot.WebSocketConfig; +//import org.glassfish.tyrus.test.tools.TestContainer; +// +//import org.junit.Test; +//import org.junit.runner.RunWith; +//import org.springframework.boot.test.context.SpringBootTest; +//import org.springframework.boot.web.server.LocalServerPort; +//import org.springframework.test.context.junit4.SpringRunner; +// +//import static org.glassfish.tyrus.client.ClientProperties.SSL_ENGINE_CONFIGURATOR; +//import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; +// +///** +// * This tests that a correct Origin is sent when working with a wss:// connection. +// * A Spring Boot application is started to easily start a TLS-protected Websocket server that +// * verifies Origin header. +// * +// * @author Roman Puchkovskiy +// */ +//@SpringBootTest(classes = {WssApplication.class, WebSocketConfig.class}, webEnvironment = RANDOM_PORT) +//@RunWith(SpringRunner.class) +//public class WssOriginTest extends TestContainer { +// @LocalServerPort +// private int serverPort; +// +// @Test +// public void wssConnectionToServerValidatingOriginShouldWork() throws Exception { +// final ClientEndpointConfig cec = ClientEndpointConfig.Builder.create() +// .build(); +// +// ClientManager client = createClient(); +// client.getProperties().put(SSL_ENGINE_CONFIGURATOR, createSslEngineConfigurator()); +// +// client.connectToServer(new Endpoint() { +// @Override +// public void onOpen(final Session session, EndpointConfig EndpointConfig) { +// } +// }, cec, new URI("wss://localhost:" + serverPort + "/hello")); +// } +// +// private SslEngineConfigurator createSslEngineConfigurator() { +// return createTrustAllSslEngineConfigurator(); +// } +// +// private SslEngineConfigurator createTrustAllSslEngineConfigurator() { +// try { +// SSLContext ctx = SSLContext.getInstance("TLS"); +// +// ctx.init(null, new TrustManager[]{new DisabledX509TrustManager()}, null); +// +// SslEngineConfigurator sslEngineConfigurator = new SslEngineConfigurator(ctx, true, false, false); +// +// sslEngineConfigurator.setHostVerificationEnabled(false); +// +// return sslEngineConfigurator; +// } catch (NoSuchAlgorithmException | KeyManagementException e) { +// throw new IllegalStateException(e); +// } +// } +// +// private static class DisabledX509TrustManager implements X509TrustManager { +// private static final X509Certificate[] CERTS = new X509Certificate[0]; +// +// @Override +// public void checkClientTrusted(X509Certificate[] x509Certificates, String s) +// throws CertificateException { +// // No-op, all clients are trusted. +// } +// +// @Override +// public void checkServerTrusted(X509Certificate[] x509Certificates, String s) +// throws CertificateException { +// // No-op, all servers are trusted. +// } +// +// @Override +// public X509Certificate[] getAcceptedIssuers() { +// return CERTS; +// } +// } +//} diff --git a/tests/e2e/standard-config/src/test/java/org/glassfish/tyrus/test/standard_config/springboot/WebSocketConfig.java b/tests/e2e/standard-config/src/test/java/org/glassfish/tyrus/test/standard_config/springboot/WebSocketConfig.java index 70e10802..3a9389fd 100644 --- a/tests/e2e/standard-config/src/test/java/org/glassfish/tyrus/test/standard_config/springboot/WebSocketConfig.java +++ b/tests/e2e/standard-config/src/test/java/org/glassfish/tyrus/test/standard_config/springboot/WebSocketConfig.java @@ -16,19 +16,19 @@ package org.glassfish.tyrus.test.standard_config.springboot; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.socket.config.annotation.StompEndpointRegistry; -import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; - -/** - * Spring Config for {@link org.glassfish.tyrus.test.standard_config.WssOriginTest}. - * - * @author Roman Puchkovskiy - */ -@Configuration -public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { - @Override - public void registerStompEndpoints(StompEndpointRegistry registry) { - registry.addEndpoint("/hello"); - } -} +//import org.springframework.context.annotation.Configuration; +//import org.springframework.web.socket.config.annotation.StompEndpointRegistry; +//import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; +// +///** +// * Spring Config for {@link org.glassfish.tyrus.test.standard_config.WssOriginTest}. +// * +// * @author Roman Puchkovskiy +// */ +//@Configuration +//public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { +// @Override +// public void registerStompEndpoints(StompEndpointRegistry registry) { +// registry.addEndpoint("/hello"); +// } +//} From 09cf250a34b7485660b15d5d3ef12ef40e8be69c Mon Sep 17 00:00:00 2001 From: jansupol Date: Tue, 10 May 2022 22:00:51 +0200 Subject: [PATCH 3/3] Fix leaking programatic endpoints in the CdiComponentProvider cache. Signed-off-by: jansupol --- .../tyrus/core/TyrusEndpointWrapper.java | 18 +- .../core/TyrusServerEndpointConfigurator.java | 25 +- .../tyrus/core/collection/LazyValue.java | 37 +++ .../tyrus/core/collection/Value.java | 32 +++ .../tyrus/core/collection/Values.java | 221 ++++++++++++++++++ 5 files changed, 322 insertions(+), 11 deletions(-) create mode 100644 core/src/main/java/org/glassfish/tyrus/core/collection/LazyValue.java create mode 100644 core/src/main/java/org/glassfish/tyrus/core/collection/Value.java create mode 100644 core/src/main/java/org/glassfish/tyrus/core/collection/Values.java diff --git a/core/src/main/java/org/glassfish/tyrus/core/TyrusEndpointWrapper.java b/core/src/main/java/org/glassfish/tyrus/core/TyrusEndpointWrapper.java index ccdff9e2..7fe676b4 100755 --- a/core/src/main/java/org/glassfish/tyrus/core/TyrusEndpointWrapper.java +++ b/core/src/main/java/org/glassfish/tyrus/core/TyrusEndpointWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2017 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -86,10 +86,10 @@ *

* There is one {@link TyrusEndpointWrapper} for each application class, which handles all the methods. * - * @author Danny Coward (danny.coward at oracle.com) - * @author Stepan Kopriva (stepan.kopriva at oracle.com) - * @author Martin Matula (martin.matula at oracle.com) - * @author Pavel Bucek (pavel.bucek at oracle.com) + * @author Danny Coward + * @author Stepan Kopriva + * @author Martin Matula + * @author Pavel Bucek */ public class TyrusEndpointWrapper { @@ -213,6 +213,14 @@ private TyrusEndpointWrapper(Endpoint endpoint, Class endpoi ? contextPath.substring(0, contextPath.length() - 1) : contextPath) + "/" + (serverEndpointPath.startsWith("/") ? serverEndpointPath.substring(1) : serverEndpointPath); + // Make sure the provided ComponentProviderService is passed to TyrusServerEndpointConfigurator. + // Otherwise, configurator.getEndpointInstance(endpointClass) let the other CdiComponentProvider + // (configurator.componentProviderService.providers) to instantiate the Endpoint, but + // ComponentProviderService CdiComponentProvider would be called for removeSession() and cleanup + // (different CdiComponentProvider.cdiBeanToContext ==> leak) + if (TyrusServerEndpointConfigurator.class.isInstance(configurator)) { + ((TyrusServerEndpointConfigurator) configurator).setComponentProviderService(componentProvider); + } } else { this.serverEndpointPath = null; this.endpointPath = null; diff --git a/core/src/main/java/org/glassfish/tyrus/core/TyrusServerEndpointConfigurator.java b/core/src/main/java/org/glassfish/tyrus/core/TyrusServerEndpointConfigurator.java index 14b33db0..bc09f825 100644 --- a/core/src/main/java/org/glassfish/tyrus/core/TyrusServerEndpointConfigurator.java +++ b/core/src/main/java/org/glassfish/tyrus/core/TyrusServerEndpointConfigurator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2017 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -18,26 +18,31 @@ import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; import javax.websocket.Extension; import javax.websocket.HandshakeResponse; +import javax.websocket.Session; import javax.websocket.server.HandshakeRequest; import javax.websocket.server.ServerEndpointConfig; +import org.glassfish.tyrus.core.collection.LazyValue; +import org.glassfish.tyrus.core.collection.Value; +import org.glassfish.tyrus.core.collection.Values; import org.glassfish.tyrus.core.extension.ExtendedExtension; import org.glassfish.tyrus.core.frame.Frame; /** - * Tyrus implementation of {@link ServerEndpointConfig.Configurator}. + * Tyrus' implementation of {@link ServerEndpointConfig.Configurator}. * - * @author Pavel Bucek (pavel.bucek at oracle.com) + * @author Pavel Bucek */ public class TyrusServerEndpointConfigurator extends ServerEndpointConfig.Configurator { - private final ComponentProviderService componentProviderService; + private LazyValue componentProviderService; public TyrusServerEndpointConfigurator() { - this.componentProviderService = ComponentProviderService.create(); + this.componentProviderService = Values.lazy(() -> ComponentProviderService.create()); } @Override @@ -155,6 +160,14 @@ public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, @Override public T getEndpointInstance(Class endpointClass) throws InstantiationException { //noinspection unchecked - return (T) componentProviderService.getEndpointInstance(endpointClass); + return (T) componentProviderService.get().getEndpointInstance(endpointClass); + } + + /** + * Reuse existing {@link ComponentProviderService}. + * @param componentProviderService The reused {@link ComponentProviderService}. + */ + void setComponentProviderService(ComponentProviderService componentProviderService) { + this.componentProviderService = Values.lazy(() -> componentProviderService); } } diff --git a/core/src/main/java/org/glassfish/tyrus/core/collection/LazyValue.java b/core/src/main/java/org/glassfish/tyrus/core/collection/LazyValue.java new file mode 100644 index 00000000..345dff47 --- /dev/null +++ b/core/src/main/java/org/glassfish/tyrus/core/collection/LazyValue.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.tyrus.core.collection; + +/** + * Lazily initialized {@link Value value}. + *

+ * Instances of this interface are initialized lazily during the first call to their + * {@link #get() value retrieval method}. Information about the initialization state + * of a {@code LazyValue} instance is available via {@link #isInitialized()} method. + *

+ * + * @author Marek Potociar + */ +public interface LazyValue extends Value { + /** + * Check if the lazy value has been initialized already (i.e. its {@link #get()} method + * has already been called previously) or not. + * + * @return {@code true} if the lazy value has already been initialized, {@code false} otherwise. + */ + boolean isInitialized(); +} diff --git a/core/src/main/java/org/glassfish/tyrus/core/collection/Value.java b/core/src/main/java/org/glassfish/tyrus/core/collection/Value.java new file mode 100644 index 00000000..3bde044c --- /dev/null +++ b/core/src/main/java/org/glassfish/tyrus/core/collection/Value.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.tyrus.core.collection; + +/** + * A generic value provider. + * + * @param value type. + * @author Marek Potociar + */ +public interface Value { + /** + * Get the stored value. + * + * @return stored value. + */ + public T get(); +} diff --git a/core/src/main/java/org/glassfish/tyrus/core/collection/Values.java b/core/src/main/java/org/glassfish/tyrus/core/collection/Values.java new file mode 100644 index 00000000..ccd7f66a --- /dev/null +++ b/core/src/main/java/org/glassfish/tyrus/core/collection/Values.java @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.tyrus.core.collection; + +/** + * A collection of {@link Value Value provider} factory & utility methods. + * + * @author Marek Potociar + */ +public final class Values { + + private static final LazyValue EMPTY = new LazyValue() { + @Override + public Object get() { + return null; + } + + @Override + public boolean isInitialized() { + return true; + } + }; + + private Values() { + // prevents instantiation. + } + + /** + * Get an empty {@link Value value provider} whose {@link Value#get() get()} + * method always returns {@code null}. + * + * @param value type. + * @return empty value provider. + */ + public static Value empty() { + //noinspection unchecked + return (Value) EMPTY; + } + + /** + * Get a new constant {@link Value value provider} whose {@link Value#get() get()} + * method always returns the instance supplied to the {@code value} parameter. + *

+ * In case the supplied value constant is {@code null}, an {@link #empty() empty} value + * provider is returned. + * + * @param value type. + * @param value value instance to be provided. + * @return constant value provider. + */ + public static Value of(final T value) { + return (value == null) ? Values.empty() : new InstanceValue(value); + } + + private static class InstanceValue implements Value { + + private final T value; + + public InstanceValue(final T value) { + this.value = value; + } + + @Override + public T get() { + return value; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + return value.equals(((InstanceValue) o).value); + } + + @Override + public int hashCode() { + return value != null ? value.hashCode() : 0; + } + + @Override + public String toString() { + return "InstanceValue{value=" + value + '}'; + } + } + + /** + * Get a new lazily initialized {@link Value value provider}. + *

+ * The value returned by its {@link Value#get() get()} method is lazily retrieved during a first + * call to the method from the supplied {@code delegate} value provider and is then cached for + * a subsequent retrieval. + *

+ * The implementation of the returned lazy value provider is thread-safe and is guaranteed to + * invoke the {@code get()} method on the supplied {@code delegate} value provider instance at + * most once. + *

+ *

+ * If the supplied value provider is {@code null}, an {@link #empty() empty} value + * provider is returned. + *

+ * + * @param value type. + * @param delegate value provider delegate that will be used to lazily initialize the value provider. + * @return lazily initialized value provider. + */ + public static LazyValue lazy(final Value delegate) { + //noinspection unchecked + return (delegate == null) ? (LazyValue) EMPTY : new LazyValueImpl(delegate); + } + + /** + * Get a new eagerly initialized {@link Value value provider}. + *

+ * The value returned by its {@link Value#get() get()} method is eagerly computed from the supplied + * {@code delegate} value provider and is then stored in a final field for a subsequent retrieval. + *

+ * The implementation of the returned eager value provider is thread-safe and is guaranteed to + * invoke the {@code get()} method on the supplied {@code delegate} value provider instance once + * and only once. + *

+ *

+ * If the supplied value provider is {@code null}, an {@link #empty() empty} value + * provider is returned. + *

+ * + * @param value type. + * @param delegate value provider delegate that will be used to eagerly initialize the value provider. + * @return eagerly initialized, constant value provider. + */ + public static Value eager(final Value delegate) { + return (delegate == null) ? Values.empty() : new EagerValue(delegate); + } + + private static class EagerValue implements Value { + + private final T result; + + private EagerValue(final Value value) { + this.result = value.get(); + } + + @Override + public T get() { + return result; + } + } + + private static class LazyValueImpl implements LazyValue { + + private final Object lock; + private final Value delegate; + + private volatile Value value; + + public LazyValueImpl(final Value delegate) { + this.delegate = delegate; + this.lock = new Object(); + } + + @Override + public T get() { + Value result = value; + if (result == null) { + synchronized (lock) { + result = value; + if (result == null) { + value = result = Values.of(delegate.get()); + } + } + } + return result.get(); + } + + @Override + public boolean isInitialized() { + return value != null; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + return delegate.equals(((LazyValueImpl) o).delegate); + } + + @Override + public int hashCode() { + return delegate != null ? delegate.hashCode() : 0; + } + + @Override + public String toString() { + return "LazyValue{delegate=" + delegate.toString() + '}'; + } + } + +}