From a36adc5d877b0a375d937472a6816a197340cb37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20=C5=A0trobl?= Date: Wed, 31 Jul 2024 13:06:25 +0200 Subject: [PATCH] Fix #1668: Add configuration to disable websockets (#1669) --- .../mtoken/controller/MessageController.java | 4 +- .../controller/MobileAppApiController.java | 15 ++++- .../WebFlowServicesConfiguration.java | 14 +++++ .../listener/WebSocketDisconnectListener.java | 2 + .../websocket/WebSocketMessageService.java | 63 +++++++++++++++++++ .../WebSocketMessageServiceDummy.java | 56 +++++++++++++++++ .../WebSocketMessageServiceImpl.java} | 43 ++++--------- powerauth-webflow/pom.xml | 33 ++++++++++ .../WebFlowServerConfiguration.java | 14 +++++ .../configuration/WebSocketConfiguration.java | 6 +- .../webflow/controller/HomeController.java | 1 + .../src/main/js/websocket-client.js | 15 +++++ .../src/main/resources/application.properties | 3 + .../src/main/resources/templates/index.html | 2 + 14 files changed, 235 insertions(+), 36 deletions(-) create mode 100644 powerauth-webflow-authentication/src/main/java/io/getlime/security/powerauth/lib/webflow/authentication/service/websocket/WebSocketMessageService.java create mode 100644 powerauth-webflow-authentication/src/main/java/io/getlime/security/powerauth/lib/webflow/authentication/service/websocket/WebSocketMessageServiceDummy.java rename powerauth-webflow-authentication/src/main/java/io/getlime/security/powerauth/lib/webflow/authentication/service/{WebSocketMessageService.java => websocket/WebSocketMessageServiceImpl.java} (78%) diff --git a/powerauth-webflow-authentication-mtoken/src/main/java/io/getlime/security/powerauth/lib/webflow/authentication/mtoken/controller/MessageController.java b/powerauth-webflow-authentication-mtoken/src/main/java/io/getlime/security/powerauth/lib/webflow/authentication/mtoken/controller/MessageController.java index 33e4cff3f..a04247957 100644 --- a/powerauth-webflow-authentication-mtoken/src/main/java/io/getlime/security/powerauth/lib/webflow/authentication/mtoken/controller/MessageController.java +++ b/powerauth-webflow-authentication-mtoken/src/main/java/io/getlime/security/powerauth/lib/webflow/authentication/mtoken/controller/MessageController.java @@ -18,8 +18,9 @@ package io.getlime.security.powerauth.lib.webflow.authentication.mtoken.controller; import io.getlime.security.powerauth.lib.webflow.authentication.model.request.WebSocketRegistrationRequest; -import io.getlime.security.powerauth.lib.webflow.authentication.service.WebSocketMessageService; +import io.getlime.security.powerauth.lib.webflow.authentication.service.websocket.WebSocketMessageService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.simp.SimpMessageHeaderAccessor; import org.springframework.stereotype.Controller; @@ -30,6 +31,7 @@ * @author Roman Strobl */ @Controller +@ConditionalOnProperty(name = "powerauth.webflow.websockets.enabled", havingValue = "true") public class MessageController { private final WebSocketMessageService webSocketMessageService; diff --git a/powerauth-webflow-authentication-mtoken/src/main/java/io/getlime/security/powerauth/lib/webflow/authentication/mtoken/controller/MobileAppApiController.java b/powerauth-webflow-authentication-mtoken/src/main/java/io/getlime/security/powerauth/lib/webflow/authentication/mtoken/controller/MobileAppApiController.java index 871003fb4..43baccb87 100644 --- a/powerauth-webflow-authentication-mtoken/src/main/java/io/getlime/security/powerauth/lib/webflow/authentication/mtoken/controller/MobileAppApiController.java +++ b/powerauth-webflow-authentication-mtoken/src/main/java/io/getlime/security/powerauth/lib/webflow/authentication/mtoken/controller/MobileAppApiController.java @@ -55,7 +55,7 @@ import io.getlime.security.powerauth.lib.webflow.authentication.mtoken.model.response.MobileTokenAuthenticationResponse; import io.getlime.security.powerauth.lib.webflow.authentication.service.AuthMethodQueryService; import io.getlime.security.powerauth.lib.webflow.authentication.service.PowerAuthOperationService; -import io.getlime.security.powerauth.lib.webflow.authentication.service.WebSocketMessageService; +import io.getlime.security.powerauth.lib.webflow.authentication.service.websocket.WebSocketMessageService; import io.getlime.security.powerauth.rest.api.spring.annotation.PowerAuth; import io.getlime.security.powerauth.rest.api.spring.annotation.PowerAuthToken; import io.getlime.security.powerauth.rest.api.spring.authentication.PowerAuthActivation; @@ -65,6 +65,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -95,6 +96,12 @@ public class MobileAppApiController extends AuthMethodController getOperationConfigs(List getOperationConfigs(List { private final OperationSessionService operationSessionService; diff --git a/powerauth-webflow-authentication/src/main/java/io/getlime/security/powerauth/lib/webflow/authentication/service/websocket/WebSocketMessageService.java b/powerauth-webflow-authentication/src/main/java/io/getlime/security/powerauth/lib/webflow/authentication/service/websocket/WebSocketMessageService.java new file mode 100644 index 000000000..9d94f4c7c --- /dev/null +++ b/powerauth-webflow-authentication/src/main/java/io/getlime/security/powerauth/lib/webflow/authentication/service/websocket/WebSocketMessageService.java @@ -0,0 +1,63 @@ +/* + * Copyright 2024 Wultra s.r.o. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package io.getlime.security.powerauth.lib.webflow.authentication.service.websocket; + +import io.getlime.security.powerauth.lib.nextstep.model.enumeration.AuthResult; + +/** + * Interface for WebSocket messages. + * + * @author Roman Strobl, roman.strobl@wultra.com + */ +public interface WebSocketMessageService { + + /** + * Notification of clients about completed authorization. + * + * @param operationId Operation ID. + * @param authResult Authorization result. + */ + void notifyAuthorizationComplete(String operationId, AuthResult authResult); + + /** + * Sends a message about successful websocket registration to the user. + * + * @param operationHash Operation hash. + * @param sessionId Session ID. + * @param registrationSucceeded Whether Web Socket registration was successful. + */ + void sendRegistrationMessage(String operationHash, String sessionId, boolean registrationSucceeded); + + /** + * Get Web Socket session ID for given operation hash. + * + * @param operationHash Operation hash. + * @return Web Socket session ID. + */ + String lookupWebSocketSessionId(String operationHash); + + /** + * Store a mapping for new web socket identifier to the Web Socket session with given ID. + * + * @param operationHash Operation hash. + * @param webSocketSessionId Web Socket Session ID. + * @param clientIpAddress Remote client IP address. + * @return Whether Web Socket registration was successful. + */ + boolean registerWebSocketSession(String operationHash, String webSocketSessionId, String clientIpAddress); + +} diff --git a/powerauth-webflow-authentication/src/main/java/io/getlime/security/powerauth/lib/webflow/authentication/service/websocket/WebSocketMessageServiceDummy.java b/powerauth-webflow-authentication/src/main/java/io/getlime/security/powerauth/lib/webflow/authentication/service/websocket/WebSocketMessageServiceDummy.java new file mode 100644 index 000000000..0f027984d --- /dev/null +++ b/powerauth-webflow-authentication/src/main/java/io/getlime/security/powerauth/lib/webflow/authentication/service/websocket/WebSocketMessageServiceDummy.java @@ -0,0 +1,56 @@ +/* + * Copyright 2024 Wultra s.r.o. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package io.getlime.security.powerauth.lib.webflow.authentication.service.websocket; + +import io.getlime.security.powerauth.lib.nextstep.model.enumeration.AuthResult; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; + +/** + * Dummy WebSocket service. + * + * @author Roman Strobl, roman.strobl@wultra.com + */ +@Service +@Slf4j +@ConditionalOnProperty(name = "powerauth.webflow.websockets.enabled", havingValue = "false", matchIfMissing = true) +public class WebSocketMessageServiceDummy implements WebSocketMessageService { + + { + logger.info("WebSocketMessageServiceDummy was initialized."); + } + + @Override + public void notifyAuthorizationComplete(String operationId, AuthResult authResult) { + } + + @Override + public void sendRegistrationMessage(String operationHash, String sessionId, boolean registrationSucceeded) { + } + + @Override + public String lookupWebSocketSessionId(String operationHash) { + return null; + } + + @Override + public boolean registerWebSocketSession(String operationHash, String webSocketSessionId, String clientIpAddress) { + return false; + } + +} diff --git a/powerauth-webflow-authentication/src/main/java/io/getlime/security/powerauth/lib/webflow/authentication/service/WebSocketMessageService.java b/powerauth-webflow-authentication/src/main/java/io/getlime/security/powerauth/lib/webflow/authentication/service/websocket/WebSocketMessageServiceImpl.java similarity index 78% rename from powerauth-webflow-authentication/src/main/java/io/getlime/security/powerauth/lib/webflow/authentication/service/WebSocketMessageService.java rename to powerauth-webflow-authentication/src/main/java/io/getlime/security/powerauth/lib/webflow/authentication/service/websocket/WebSocketMessageServiceImpl.java index a7c7761eb..1ea2495f9 100644 --- a/powerauth-webflow-authentication/src/main/java/io/getlime/security/powerauth/lib/webflow/authentication/service/WebSocketMessageService.java +++ b/powerauth-webflow-authentication/src/main/java/io/getlime/security/powerauth/lib/webflow/authentication/service/websocket/WebSocketMessageServiceImpl.java @@ -14,12 +14,15 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package io.getlime.security.powerauth.lib.webflow.authentication.service; +package io.getlime.security.powerauth.lib.webflow.authentication.service.websocket; import io.getlime.security.powerauth.lib.nextstep.model.enumeration.AuthResult; import io.getlime.security.powerauth.lib.webflow.authentication.model.response.WebSocketAuthorizationResponse; import io.getlime.security.powerauth.lib.webflow.authentication.model.response.WebSocketRegistrationResponse; +import io.getlime.security.powerauth.lib.webflow.authentication.service.OperationSessionService; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.simp.SimpMessageHeaderAccessor; import org.springframework.messaging.simp.SimpMessageType; @@ -33,7 +36,9 @@ * @author Petr Dvorak, petr@wultra.com */ @Service -public class WebSocketMessageService { +@Slf4j +@ConditionalOnProperty(name = "powerauth.webflow.websockets.enabled", havingValue = "true") +public class WebSocketMessageServiceImpl implements WebSocketMessageService { private final SimpMessagingTemplate websocket; private final OperationSessionService operationSessionService; @@ -44,9 +49,10 @@ public class WebSocketMessageService { * @param operationSessionService Operation to session mapping service. */ @Autowired - public WebSocketMessageService(SimpMessagingTemplate websocket, OperationSessionService operationSessionService) { + public WebSocketMessageServiceImpl(SimpMessagingTemplate websocket, OperationSessionService operationSessionService) { this.websocket = websocket; this.operationSessionService = operationSessionService; + logger.info("WebSocketMessageServiceImpl was initialized."); } /** @@ -62,12 +68,7 @@ private MessageHeaders createHeaders(String webSocketSessionId) { return headerAccessor.getMessageHeaders(); } - /** - * Notification of clients about completed authorization. - * - * @param operationId Operation ID. - * @param authResult Authorization result. - */ + @Override public void notifyAuthorizationComplete(String operationId, AuthResult authResult) { final String webSocketId = operationSessionService.generateOperationHash(operationId); final String sessionId = lookupWebSocketSessionId(webSocketId); @@ -79,13 +80,7 @@ public void notifyAuthorizationComplete(String operationId, AuthResult authResul } } - /** - * Sends a message about successful websocket registration to the user. - * - * @param operationHash Operation hash. - * @param sessionId Session ID. - * @param registrationSucceeded Whether Web Socket registration was successful. - */ + @Override public void sendRegistrationMessage(String operationHash, String sessionId, boolean registrationSucceeded) { WebSocketRegistrationResponse registrationResponse = new WebSocketRegistrationResponse(); registrationResponse.setWebSocketId(operationHash); @@ -93,24 +88,12 @@ public void sendRegistrationMessage(String operationHash, String sessionId, bool websocket.convertAndSendToUser(sessionId, "/topic/registration", registrationResponse, createHeaders(sessionId)); } - /** - * Get Web Socket session ID for given operation hash. - * - * @param operationHash Operation hash. - * @return Web Socket session ID. - */ + @Override public String lookupWebSocketSessionId(String operationHash) { return operationSessionService.lookupWebSocketSessionIdByOperationHash(operationHash); } - /** - * Store a mapping for new web socket identifier to the Web Socket session with given ID. - * - * @param operationHash Operation hash. - * @param webSocketSessionId Web Socket Session ID. - * @param clientIpAddress Remote client IP address. - * @return Whether Web Socket registration was successful. - */ + @Override public boolean registerWebSocketSession(String operationHash, String webSocketSessionId, String clientIpAddress) { return operationSessionService.registerWebSocketSession(operationHash, webSocketSessionId, clientIpAddress); } diff --git a/powerauth-webflow/pom.xml b/powerauth-webflow/pom.xml index e952fcc30..c68742b0a 100644 --- a/powerauth-webflow/pom.xml +++ b/powerauth-webflow/pom.xml @@ -242,6 +242,39 @@ + + websockets-enabled + + true + + + + org.springframework + spring-messaging + + + org.springframework + spring-websocket + + + + + + websockets-disabled + + + org.springframework + spring-messaging + provided + + + org.springframework + spring-websocket + provided + + + + test-repository diff --git a/powerauth-webflow/src/main/java/io/getlime/security/powerauth/app/webflow/configuration/WebFlowServerConfiguration.java b/powerauth-webflow/src/main/java/io/getlime/security/powerauth/app/webflow/configuration/WebFlowServerConfiguration.java index 642fafe71..1406ac561 100644 --- a/powerauth-webflow/src/main/java/io/getlime/security/powerauth/app/webflow/configuration/WebFlowServerConfiguration.java +++ b/powerauth-webflow/src/main/java/io/getlime/security/powerauth/app/webflow/configuration/WebFlowServerConfiguration.java @@ -187,6 +187,12 @@ public class WebFlowServerConfiguration { @Value("${powerauth.webflow.approval.certificate.signer.ica.extensionInstallURLFirefox:}") private String icaExtensionInstallURLFirefox; + /** + * WebSocket support configuration. + */ + @Value("${powerauth.webflow.websockets.enabled:true}") + private boolean webSocketSupportEnabled; + /** * Configuration constructor. * @param auditFactory Audit factory. @@ -392,6 +398,14 @@ public String getIcaExtensionInstallURLFirefox() { return icaExtensionInstallURLFirefox; } + /** + * Get whether WebSocket support is enabled. + * @return Whether WebSocket support is enabled. + */ + public boolean isWebSocketSupportEnabled() { + return webSocketSupportEnabled; + } + /** * Prepare audit interface. * @return Audit interface. diff --git a/powerauth-webflow/src/main/java/io/getlime/security/powerauth/app/webflow/configuration/WebSocketConfiguration.java b/powerauth-webflow/src/main/java/io/getlime/security/powerauth/app/webflow/configuration/WebSocketConfiguration.java index a9e5c6ae7..d961d1398 100644 --- a/powerauth-webflow/src/main/java/io/getlime/security/powerauth/app/webflow/configuration/WebSocketConfiguration.java +++ b/powerauth-webflow/src/main/java/io/getlime/security/powerauth/app/webflow/configuration/WebSocketConfiguration.java @@ -20,8 +20,9 @@ import io.getlime.security.powerauth.lib.webflow.authentication.configuration.WebFlowServicesConfiguration; import io.getlime.security.powerauth.lib.webflow.authentication.interceptor.WebSocketHandshakeInterceptor; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; -import org.springframework.stereotype.Component; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; @@ -31,8 +32,9 @@ * * @author Roman Strobl, roman.strobl@wultra.com */ -@Component +@Configuration @EnableWebSocketMessageBroker +@ConditionalOnProperty(name = "powerauth.webflow.websockets.enabled", havingValue = "true") public class WebSocketConfiguration implements WebSocketMessageBrokerConfigurer { public static final String MESSAGE_PREFIX = "/topic"; diff --git a/powerauth-webflow/src/main/java/io/getlime/security/powerauth/app/webflow/controller/HomeController.java b/powerauth-webflow/src/main/java/io/getlime/security/powerauth/app/webflow/controller/HomeController.java index 6063148de..c33d8fc7a 100644 --- a/powerauth-webflow/src/main/java/io/getlime/security/powerauth/app/webflow/controller/HomeController.java +++ b/powerauth-webflow/src/main/java/io/getlime/security/powerauth/app/webflow/controller/HomeController.java @@ -210,6 +210,7 @@ public String authenticate(Map model, HttpServletRequest request model.put("icaExtensionIDEdge", webFlowConfig.getIcaExtensionIDEdge()); model.put("icaExtensionIDFirefox", webFlowConfig.getIcaExtensionIDFirefox()); model.put("icaExtensionInstallURLFirefox", webFlowConfig.getIcaExtensionInstallURLFirefox()); + model.put("webSocketSupportEnabled", webFlowConfig.isWebSocketSupportEnabled()); logger.info("The /authenticate request succeeded"); return "index"; } diff --git a/powerauth-webflow/src/main/js/websocket-client.js b/powerauth-webflow/src/main/js/websocket-client.js index 10e89d048..c13443ddb 100644 --- a/powerauth-webflow/src/main/js/websocket-client.js +++ b/powerauth-webflow/src/main/js/websocket-client.js @@ -27,6 +27,9 @@ require('stompjs'); * @param webSocketId Web Socket ID. */ function register(registrations, webSocketId) { + if (!webSocketSupportEnabled) { + return; + } try { var msie = document.documentMode; if (msie && msie < 11) { @@ -60,6 +63,9 @@ function register(registrations, webSocketId) { * @param callback Callback function to call on an event. */ function subscribe(route, callback) { + if (!webSocketSupportEnabled) { + return; + } try { if (client !== undefined) { client.subscribe(route, callback); @@ -75,6 +81,9 @@ function subscribe(route, callback) { * @param route Web Socket route. */ function unsubscribe(route) { + if (!webSocketSupportEnabled) { + return; + } try { if (client !== undefined) { client.unsubscribe(route); @@ -92,6 +101,9 @@ function unsubscribe(route) { * @param message Text of the message as JSON. */ function send(destination, params, message) { + if (!webSocketSupportEnabled) { + return; + } try { if (client !== undefined) { client.send(destination, params, message); @@ -106,6 +118,9 @@ function send(destination, params, message) { * Disconnect the WebSocket. */ function disconnect() { + if (!webSocketSupportEnabled) { + return; + } try { if (client !== undefined) { client.disconnect(); diff --git a/powerauth-webflow/src/main/resources/application.properties b/powerauth-webflow/src/main/resources/application.properties index c20569a7d..13b4f957f 100644 --- a/powerauth-webflow/src/main/resources/application.properties +++ b/powerauth-webflow/src/main/resources/application.properties @@ -52,6 +52,9 @@ powerauth.webflow.offlineMode.available=true # Enable or disable operations support in PowerAuth server powerauth.webflow.pa.operations.enabled=false +# Enable or disable WebSocket support +powerauth.webflow.websockets.enabled=true + # Configuration of Android Security Warning powerauth.webflow.android.showSecurityWarning=true diff --git a/powerauth-webflow/src/main/resources/templates/index.html b/powerauth-webflow/src/main/resources/templates/index.html index 8cc5ceb70..c4415da72 100644 --- a/powerauth-webflow/src/main/resources/templates/index.html +++ b/powerauth-webflow/src/main/resources/templates/index.html @@ -70,6 +70,8 @@ const extensionIDFirefox = [[${icaExtensionIDFirefox}]]; const extensionInstallURLFirefox = [[${icaExtensionInstallURLFirefox}]]; + const webSocketSupportEnabled = [[${webSocketSupportEnabled}]]; + window.onbeforeunload = function() { // Modern browsers do not allow custom messages, so text will be only shown in old browsers. // Note that the onbeforeunload dialog does not appear in case user does no action, see: