From c34d76d3181efe1078fa099025013f678b133e24 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 20 Mar 2020 11:05:21 +0100 Subject: [PATCH 1/5] update resteasy to v4.5.2 The update is required because this version contains several SSE fixes --- CHANGELOG.md | 3 +++ pom.xml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd6ceb194b..b5b6e83c67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Extension point to add links to the repository cards from plug ins ([#1041](https://github.com/scm-manager/scm-manager/pull/1041)) +### Changed +- Update resteasy to version 4.5.2.Final + ## 2.0.0-rc5 - 2020-03-12 ### Added - Added footer extension points for links and avatar diff --git a/pom.xml b/pom.xml index d3301168cb..ebffdedef6 100644 --- a/pom.xml +++ b/pom.xml @@ -836,7 +836,7 @@ 3.0.1 2.1.1 - 4.4.2.Final + 4.5.2.Final 1.19.4 2.10.0 4.2.2 From f8f5aa2ebd426804d85b24d7f9e6b9ffde8f2445 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 20 Mar 2020 11:10:05 +0100 Subject: [PATCH 2/5] X-SCM-Session-ID and X-SCM-Client could now be send via query parameter The use of query parameters is required for SSE, because the standard does not support header. This works currently only for GET request to avoid parsing of request body. --- .../java/sonia/scm/security/BearerToken.java | 8 +--- .../java/sonia/scm/security/SessionId.java | 30 ++++++------- .../main/java/sonia/scm/util/HttpUtil.java | 34 +++++++++----- .../sonia/scm/security/SessionIdTest.java | 42 +++++++++++++++++ .../java/sonia/scm/util/HttpUtilTest.java | 45 +++++++++++++++++++ .../scm/web/BearerWebTokenGenerator.java | 7 ++- .../web/CookieBearerWebTokenGenerator.java | 17 +++---- .../sonia/scm/security/BearerRealmTest.java | 4 +- .../scm/web/BearerWebTokenGeneratorTest.java | 15 +++---- .../CookieBearerWebTokenGeneratorTest.java | 2 +- 10 files changed, 145 insertions(+), 59 deletions(-) create mode 100644 scm-core/src/test/java/sonia/scm/security/SessionIdTest.java diff --git a/scm-core/src/main/java/sonia/scm/security/BearerToken.java b/scm-core/src/main/java/sonia/scm/security/BearerToken.java index 43a225b5b6..f42147fbe2 100644 --- a/scm-core/src/main/java/sonia/scm/security/BearerToken.java +++ b/scm-core/src/main/java/sonia/scm/security/BearerToken.java @@ -96,17 +96,13 @@ public final class BearerToken implements AuthenticationToken { /** * Creates a new {@link BearerToken} from raw string representation for the given ui session id. * - * @param sessionId session id of the client + * @param session session id of the client * @param rawToken bearer token string representation * * @return new bearer token */ - public static BearerToken create(@Nullable String sessionId, String rawToken) { + public static BearerToken create(@Nullable SessionId session, String rawToken) { Preconditions.checkArgument(!Strings.isNullOrEmpty(rawToken), "raw token is required"); - SessionId session = null; - if (!Strings.isNullOrEmpty(sessionId)) { - session = SessionId.valueOf(sessionId); - } return new BearerToken(session, rawToken); } } diff --git a/scm-core/src/main/java/sonia/scm/security/SessionId.java b/scm-core/src/main/java/sonia/scm/security/SessionId.java index 3c4fb126ff..7d1b657bfb 100644 --- a/scm-core/src/main/java/sonia/scm/security/SessionId.java +++ b/scm-core/src/main/java/sonia/scm/security/SessionId.java @@ -1,14 +1,23 @@ package sonia.scm.security; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.base.Strings; +import lombok.EqualsAndHashCode; +import sonia.scm.util.HttpUtil; -import java.util.Objects; +import javax.servlet.http.HttpServletRequest; +import java.io.Serializable; +import java.util.Optional; /** * Client side session id. */ -public final class SessionId { +@EqualsAndHashCode +public final class SessionId implements Serializable { + + @VisibleForTesting + public static final String PARAMETER = "X-SCM-Session-ID"; private final String value; @@ -16,24 +25,15 @@ public final class SessionId { this.value = value; } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - SessionId sessionID = (SessionId) o; - return Objects.equals(value, sessionID.value); - } - - @Override - public int hashCode() { - return Objects.hash(value); - } - @Override public String toString() { return value; } + public static Optional from(HttpServletRequest request) { + return HttpUtil.getHeaderOrGetParameter(request, PARAMETER).map(SessionId::valueOf); + } + public static SessionId valueOf(String value) { Preconditions.checkArgument(!Strings.isNullOrEmpty(value), "session id could not be empty or null"); return new SessionId(value); diff --git a/scm-core/src/main/java/sonia/scm/util/HttpUtil.java b/scm-core/src/main/java/sonia/scm/util/HttpUtil.java index 8edc0c8e14..820f492b8f 100644 --- a/scm-core/src/main/java/sonia/scm/util/HttpUtil.java +++ b/scm-core/src/main/java/sonia/scm/util/HttpUtil.java @@ -37,7 +37,6 @@ package sonia.scm.util; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.CharMatcher; -import com.google.common.base.MoreObjects; import com.google.common.base.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,6 +51,7 @@ import java.net.URLDecoder; import java.net.URLEncoder; import java.util.Arrays; import java.util.Locale; +import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -116,12 +116,6 @@ public final class HttpUtil */ public static final String HEADER_SCM_CLIENT = "X-SCM-Client"; - /** - * header for identifying the scm-manager client session - * @since 2.0.0 - */ - public static final String HEADER_SCM_SESSION = "X-SCM-Session-ID"; - /** Field description */ public static final String HEADER_USERAGENT = "User-Agent"; @@ -830,6 +824,24 @@ public final class HttpUtil return uri; } + /** + * Returns header value or query parameter if the request is a get request. + * + * @param request http request + * @param parameter name of header/parameter + * + * @return header value or query parameter + * + * @since 2.0.0 + */ + public static Optional getHeaderOrGetParameter(HttpServletRequest request, String parameter) { + String value = request.getHeader(parameter); + if (Strings.isNullOrEmpty(value) && "GET".equalsIgnoreCase(request.getMethod())) { + value = request.getParameter(parameter); + } + return Optional.ofNullable(value); + } + /** * Returns the given uri without leading separator. * @@ -882,16 +894,14 @@ public final class HttpUtil /** * Returns true if the http request is send by the scm-manager web interface. * - * * @param request http request * * @return true if the request comes from the web interface. * @since 1.19 */ - public static boolean isWUIRequest(HttpServletRequest request) - { - return SCM_CLIENT_WUI.equalsIgnoreCase( - request.getHeader(HEADER_SCM_CLIENT)); + public static boolean isWUIRequest(HttpServletRequest request) { + Optional client = getHeaderOrGetParameter(request, HEADER_SCM_CLIENT); + return client.isPresent() && SCM_CLIENT_WUI.equalsIgnoreCase(client.get()); } //~--- methods -------------------------------------------------------------- diff --git a/scm-core/src/test/java/sonia/scm/security/SessionIdTest.java b/scm-core/src/test/java/sonia/scm/security/SessionIdTest.java new file mode 100644 index 0000000000..89bcf5f594 --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/security/SessionIdTest.java @@ -0,0 +1,42 @@ +package sonia.scm.security; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import javax.servlet.http.HttpServletRequest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class SessionIdTest { + + @Mock + private HttpServletRequest request; + + @Test + void shouldReturnSessionIdFromHeader() { + when(request.getHeader(SessionId.PARAMETER)).thenReturn("abc42"); + + assertThat(SessionId.from(request)).contains(SessionId.valueOf("abc42")); + } + + @Test + void shouldReturnSessionIdFromQueryParameter() { + when(request.getMethod()).thenReturn("GET"); + when(request.getParameter(SessionId.PARAMETER)).thenReturn("abc42"); + + assertThat(SessionId.from(request)).contains(SessionId.valueOf("abc42")); + } + + @Test + void shouldReturnSessionIdFromQueryParameterOnlyForGetRequest() { + when(request.getMethod()).thenReturn("POST"); + lenient().when(request.getParameter(SessionId.PARAMETER)).thenReturn("abc42"); + + assertThat(SessionId.from(request)).isEmpty(); + } +} diff --git a/scm-core/src/test/java/sonia/scm/util/HttpUtilTest.java b/scm-core/src/test/java/sonia/scm/util/HttpUtilTest.java index c827764a92..8e2804a749 100644 --- a/scm-core/src/test/java/sonia/scm/util/HttpUtilTest.java +++ b/scm-core/src/test/java/sonia/scm/util/HttpUtilTest.java @@ -38,7 +38,9 @@ package sonia.scm.util; import org.junit.Test; import sonia.scm.config.ScmConfiguration; +import sonia.scm.security.SessionId; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.*; import static org.mockito.Mockito.*; @@ -466,4 +468,47 @@ public class HttpUtilTest assertEquals("test/two/three", HttpUtil.getUriWithoutStartSeperator("test/two/three")); } + + @Test + public void testGetHeaderOrGetParameterWithHeader() { + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getHeader("Domain")).thenReturn("hitchhiker"); + + assertThat(HttpUtil.getHeaderOrGetParameter(request, "Domain")).contains("hitchhiker"); + } + + @Test + public void testGetHeaderOrGetParameterWithParameter() { + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getMethod()).thenReturn("GET"); + when(request.getParameter("Domain")).thenReturn("hitchhiker"); + + assertThat(HttpUtil.getHeaderOrGetParameter(request, "Domain")).contains("hitchhiker"); + } + + @Test + public void testGetHeaderOrGetParameterOnPost() { + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getMethod()).thenReturn("POST"); + lenient().when(request.getParameter("Domain")).thenReturn("hitchhiker"); + + assertThat(HttpUtil.getHeaderOrGetParameter(request, "Domain")).isEmpty(); + } + + @Test + public void testIsWUIRequestWithHeader() { + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getHeader(HttpUtil.HEADER_SCM_CLIENT)).thenReturn(HttpUtil.SCM_CLIENT_WUI); + + assertThat(HttpUtil.isWUIRequest(request)).isTrue(); + } + + @Test + public void testIsWUIRequestWithParameter() { + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getMethod()).thenReturn("GET"); + when(request.getParameter(HttpUtil.HEADER_SCM_CLIENT)).thenReturn(HttpUtil.SCM_CLIENT_WUI); + + assertThat(HttpUtil.isWUIRequest(request)).isTrue(); + } } diff --git a/scm-webapp/src/main/java/sonia/scm/web/BearerWebTokenGenerator.java b/scm-webapp/src/main/java/sonia/scm/web/BearerWebTokenGenerator.java index 4bb79e7b9a..21330c6e17 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/BearerWebTokenGenerator.java +++ b/scm-webapp/src/main/java/sonia/scm/web/BearerWebTokenGenerator.java @@ -35,6 +35,7 @@ package sonia.scm.web; import sonia.scm.plugin.Extension; import sonia.scm.security.BearerToken; +import sonia.scm.security.SessionId; import sonia.scm.util.HttpUtil; //~--- JDK imports ------------------------------------------------------------ @@ -68,10 +69,8 @@ public class BearerWebTokenGenerator extends SchemeBasedWebTokenGenerator { BearerToken token = null; - if (HttpUtil.AUTHORIZATION_SCHEME_BEARER.equalsIgnoreCase(scheme)) - { - String sessionId = request.getHeader(HttpUtil.HEADER_SCM_SESSION); - token = BearerToken.create(sessionId, authorization); + if (HttpUtil.AUTHORIZATION_SCHEME_BEARER.equalsIgnoreCase(scheme)) { + token = BearerToken.create(SessionId.from(request).orElse(null), authorization); } return token; diff --git a/scm-webapp/src/main/java/sonia/scm/web/CookieBearerWebTokenGenerator.java b/scm-webapp/src/main/java/sonia/scm/web/CookieBearerWebTokenGenerator.java index 2a0a97c07e..a6b2a16296 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/CookieBearerWebTokenGenerator.java +++ b/scm-webapp/src/main/java/sonia/scm/web/CookieBearerWebTokenGenerator.java @@ -40,6 +40,8 @@ import sonia.scm.security.BearerToken; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; + +import sonia.scm.security.SessionId; import sonia.scm.util.HttpUtil; /** @@ -62,19 +64,14 @@ public class CookieBearerWebTokenGenerator implements WebTokenGenerator * @return {@link BearerToken} or {@code null} */ @Override - public BearerToken createToken(HttpServletRequest request) - { + public BearerToken createToken(HttpServletRequest request) { BearerToken token = null; Cookie[] cookies = request.getCookies(); - if (cookies != null) - { - for (Cookie cookie : cookies) - { - if (HttpUtil.COOKIE_BEARER_AUTHENTICATION.equals(cookie.getName())) - { - String sessionId = HttpUtil.getHeader(request, HttpUtil.HEADER_SCM_SESSION, null); - token = BearerToken.create(sessionId, cookie.getValue()); + if (cookies != null) { + for (Cookie cookie : cookies) { + if (HttpUtil.COOKIE_BEARER_AUTHENTICATION.equals(cookie.getName())) { + token = BearerToken.create(SessionId.from(request).orElse(null), cookie.getValue()); break; } diff --git a/scm-webapp/src/test/java/sonia/scm/security/BearerRealmTest.java b/scm-webapp/src/test/java/sonia/scm/security/BearerRealmTest.java index 897b251cfa..6213c234bb 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/BearerRealmTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/BearerRealmTest.java @@ -31,7 +31,6 @@ package sonia.scm.security; -import com.google.common.collect.ImmutableSet; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.UsernamePasswordToken; import org.junit.jupiter.api.BeforeEach; @@ -42,7 +41,6 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import java.util.HashMap; -import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -84,7 +82,7 @@ class BearerRealmTest { @Test void shouldDoGetAuthentication() { - BearerToken bearerToken = BearerToken.create("__session__", "__bearer__"); + BearerToken bearerToken = BearerToken.create(SessionId.valueOf("__session__"), "__bearer__"); AccessToken accessToken = mock(AccessToken.class); when(accessToken.getSubject()).thenReturn("trillian"); diff --git a/scm-webapp/src/test/java/sonia/scm/web/BearerWebTokenGeneratorTest.java b/scm-webapp/src/test/java/sonia/scm/web/BearerWebTokenGeneratorTest.java index 1c054111b7..d77d2068b9 100644 --- a/scm-webapp/src/test/java/sonia/scm/web/BearerWebTokenGeneratorTest.java +++ b/scm-webapp/src/test/java/sonia/scm/web/BearerWebTokenGeneratorTest.java @@ -31,20 +31,19 @@ package sonia.scm.web; -import javax.servlet.http.HttpServletRequest; import org.apache.shiro.authc.AuthenticationToken; - - import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; import org.mockito.junit.jupiter.MockitoExtension; import sonia.scm.security.BearerToken; import sonia.scm.security.SessionId; -import sonia.scm.util.HttpUtil; + +import javax.servlet.http.HttpServletRequest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.when; /** * @@ -90,7 +89,7 @@ class BearerWebTokenGeneratorTest { @Test void shouldCreateTokenWithSessionId(){ doReturn("Bearer asd").when(request).getHeader("Authorization"); - doReturn("bcd123").when(request).getHeader(HttpUtil.HEADER_SCM_SESSION); + doReturn("bcd123").when(request).getHeader(SessionId.PARAMETER); AuthenticationToken token = tokenGenerator.createToken(request); assertThat(token) diff --git a/scm-webapp/src/test/java/sonia/scm/web/CookieBearerWebTokenGeneratorTest.java b/scm-webapp/src/test/java/sonia/scm/web/CookieBearerWebTokenGeneratorTest.java index 45d2c46b83..8cfe8c1b31 100644 --- a/scm-webapp/src/test/java/sonia/scm/web/CookieBearerWebTokenGeneratorTest.java +++ b/scm-webapp/src/test/java/sonia/scm/web/CookieBearerWebTokenGeneratorTest.java @@ -76,7 +76,7 @@ class CookieBearerWebTokenGeneratorTest { @Test void shouldCreateTokenWithSessionId() { - when(request.getHeader(HttpUtil.HEADER_SCM_SESSION)).thenReturn("abc123"); + when(request.getHeader(SessionId.PARAMETER)).thenReturn("abc123"); assignBearerCookie("authc"); From 9ff5b26982fcee736e82f762c7afcee40e5433dc Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 20 Mar 2020 11:15:35 +0100 Subject: [PATCH 3/5] Use browser build in EventSource for apiClient subscriptions --- CHANGELOG.md | 4 ++++ scm-ui/ui-components/package.json | 1 - scm-ui/ui-components/src/apiclient.ts | 14 +++++++++----- yarn.lock | 5 ----- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5b6e83c67..cc4dcd3fac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Update resteasy to version 4.5.2.Final +- Use browser build in EventSource for apiClient subscriptions + +### Removed +- EventSource Polyfill ## 2.0.0-rc5 - 2020-03-12 ### Added diff --git a/scm-ui/ui-components/package.json b/scm-ui/ui-components/package.json index 3864805706..e60bbfdea3 100644 --- a/scm-ui/ui-components/package.json +++ b/scm-ui/ui-components/package.json @@ -49,7 +49,6 @@ "@scm-manager/ui-types": "^2.0.0-SNAPSHOT", "classnames": "^2.2.6", "date-fns": "^2.4.1", - "event-source-polyfill": "^1.0.9", "gitdiff-parser": "^0.1.2", "lowlight": "^1.13.0", "query-string": "5", diff --git a/scm-ui/ui-components/src/apiclient.ts b/scm-ui/ui-components/src/apiclient.ts index c8990b000c..f7e32f46ef 100644 --- a/scm-ui/ui-components/src/apiclient.ts +++ b/scm-ui/ui-components/src/apiclient.ts @@ -1,6 +1,4 @@ import { contextPath } from "./urls"; -// @ts-ignore we have not types for event-source-polyfill -import { EventSourcePolyfill } from "event-source-polyfill"; import { createBackendError, ForbiddenError, isBackendError, UnauthorizedError, BackendErrorContent } from "./errors"; type SubscriptionEvent = { @@ -124,6 +122,10 @@ export function createUrl(url: string) { return `${contextPath}/api/v2${urlWithStartingSlash}`; } +export function createUrlWithIdentifiers(url: string): string { + return createUrl(url) + "?X-SCM-Client=WUI&X-SCM-Session-ID=" + sessionId; +} + class ApiClient { get(url: string): Promise { return fetch(createUrl(url), applyFetchOptions({})).then(handleFailure); @@ -218,9 +220,8 @@ class ApiClient { } subscribe(url: string, argument: SubscriptionArgument): Cancel { - const es = new EventSourcePolyfill(createUrl(url), { - withCredentials: true, - headers: createRequestHeaders() + const es = new EventSource(createUrlWithIdentifiers(url), { + withCredentials: true }); let listeners: MessageListeners; @@ -228,9 +229,11 @@ class ApiClient { if ("onMessage" in argument) { listeners = (argument as SubscriptionContext).onMessage; if (argument.onError) { + // @ts-ignore typing of EventSource is weird es.onerror = argument.onError; } if (argument.onOpen) { + // @ts-ignore typing of EventSource is weird es.onopen = argument.onOpen; } } else { @@ -238,6 +241,7 @@ class ApiClient { } for (const type in listeners) { + // @ts-ignore typing of EventSource is weird es.addEventListener(type, listeners[type]); } diff --git a/yarn.lock b/yarn.lock index 42d56ee047..a2d1ea2670 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6580,11 +6580,6 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= -event-source-polyfill@^1.0.9: - version "1.0.12" - resolved "https://registry.yarnpkg.com/event-source-polyfill/-/event-source-polyfill-1.0.12.tgz#38546c4fee76dcadae2560185610ae46c5a39520" - integrity sha512-WjOTn0LIbaN08z/8gNt3GYAomAdm6cZ2lr/QdvhTTEipr5KR6lds2ziUH+p/Iob4Lk6NClKhwPOmn1NjQEcJCg== - eventemitter3@^3.1.0: version "3.1.2" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" From b7d87efdf20607e39310fb1899ef0a03a74baf08 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 20 Mar 2020 13:33:28 +0100 Subject: [PATCH 4/5] ensure smallrye-config is at least 1.6.2 see https://snyk.io/vuln/SNYK-JAVA-IOSMALLRYECONFIG-548898 --- pom.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pom.xml b/pom.xml index 2fddd0e0b3..02e032c772 100644 --- a/pom.xml +++ b/pom.xml @@ -254,6 +254,17 @@ ${resteasy.version} + + + io.smallrye.config + smallrye-config + 1.6.2 + + javax.ws.rs javax.ws.rs-api From ca2f9d151a08215bc8125c31f98ec02c6c68a3a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Mon, 23 Mar 2020 09:57:05 +0100 Subject: [PATCH 5/5] Fix typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a26bb88aa..35cda5ed44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Update resteasy to version 4.5.2.Final -- Use browser build in EventSource for apiClient subscriptions +- Use browser built-in EventSource for apiClient subscriptions ### Removed - EventSource Polyfill