Skip to content

Commit

Permalink
Fix #1668: Add configuration to disable websockets (#1669)
Browse files Browse the repository at this point in the history
  • Loading branch information
romanstrobl authored Jul 31, 2024
1 parent 6e46de9 commit a36adc5
Show file tree
Hide file tree
Showing 14 changed files with 235 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -30,6 +31,7 @@
* @author Roman Strobl
*/
@Controller
@ConditionalOnProperty(name = "powerauth.webflow.websockets.enabled", havingValue = "true")
public class MessageController {

private final WebSocketMessageService webSocketMessageService;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -95,6 +96,12 @@ public class MobileAppApiController extends AuthMethodController<MobileTokenAuth

private final FormDataConverter formDataConverter = new FormDataConverter();

/**
* WebSocket support configuration.
*/
@Value("${powerauth.webflow.websockets.enabled:true}")
private boolean webSocketSupportEnabled;

/**
* Controller constructor.
* @param webSocketMessageService Web Socket message service.
Expand Down Expand Up @@ -305,7 +312,9 @@ private Map<String, GetOperationConfigDetailResponse> getOperationConfigs(List<G
throw new OperationIsAlreadyFailedException("Operation approval has failed");
}
final AuthOperationResponse updateOperationResponse = authorize(operationId, userId, operation.getOrganizationId(), authInstruments, authenticationContext, null);
webSocketMessageService.notifyAuthorizationComplete(operationId, updateOperationResponse.getAuthResult());
if (webSocketSupportEnabled) {
webSocketMessageService.notifyAuthorizationComplete(operationId, updateOperationResponse.getAuthResult());
}
return new Response();
} else {
boolean approvalFailSucceeded = powerAuthOperationService.failApprovalForOperation(operation);
Expand Down Expand Up @@ -349,7 +358,7 @@ private Map<String, GetOperationConfigDetailResponse> getOperationConfigs(List<G
final GetOperationDetailResponse operation = getOperation(operationId);
boolean rejectSucceeded = powerAuthOperationService.rejectOperation(operation, activationId);
final UpdateOperationResponse updateOperationResponse = cancelAuthorization(operationId, userId, OperationCancelReason.fromString(request.getRequestObject().getReason()), null, false);
if (updateOperationResponse != null) {
if (webSocketSupportEnabled && updateOperationResponse != null) {
webSocketMessageService.notifyAuthorizationComplete(operationId, updateOperationResponse.getResult());
}
if (!rejectSucceeded) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ public class WebFlowServicesConfiguration {
@Value("${powerauth.webflow.pa.operations.enabled}")
private boolean powerAuthOperationSupportEnabled;

/**
* WebSocket support configuration.
*/
@Value("${powerauth.webflow.websockets.enabled:true}")
private boolean webSocketSupportEnabled;

/**
* Authentication type which configures how username and password is transferred for verification.
*/
Expand Down Expand Up @@ -219,6 +225,14 @@ public boolean isPowerAuthOperationSupportEnabled() {
return powerAuthOperationSupportEnabled;
}

/**
* Get whether WebSocket support is enabled.
* @return Whether WebSocket support is enabled.
*/
public boolean isWebSocketSupportEnabled() {
return webSocketSupportEnabled;
}

/**
* Get authentication type which configures how username and password is transferred for verification.
* @return Authentication type.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import io.getlime.security.powerauth.lib.webflow.authentication.service.OperationCancellationService;
import io.getlime.security.powerauth.lib.webflow.authentication.service.OperationSessionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
Expand All @@ -34,6 +35,7 @@
* @author Roman Strobl, roman.strobl@wultra.com
*/
@Component
@ConditionalOnProperty(name = "powerauth.webflow.websockets.enabled", havingValue = "true")
public class WebSocketDisconnectListener implements ApplicationListener<SessionDisconnectEvent> {

private final OperationSessionService operationSessionService;
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/
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);

}
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/
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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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;
Expand All @@ -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;
Expand All @@ -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.");
}

/**
Expand All @@ -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);
Expand All @@ -79,38 +80,20 @@ 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);
registrationResponse.setRegistrationSucceeded(registrationSucceeded);
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);
}
Expand Down
33 changes: 33 additions & 0 deletions powerauth-webflow/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,39 @@
</build>

<profiles>
<profile>
<id>websockets-enabled</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
</dependency>
</dependencies>
</profile>

<profile>
<id>websockets-disabled</id>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</profile>

<profile>
<id>test-repository</id>
<activation>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
Loading

0 comments on commit a36adc5

Please sign in to comment.