Skip to content

Commit

Permalink
Merge pull request #403 from ase-101/release-1.1.0
Browse files Browse the repository at this point in the history
ES-842 Added readme, testcases and updated docker compose file
  • Loading branch information
ckm007 authored Oct 28, 2024
2 parents 304ba25 + a203f0c commit a5a0798
Show file tree
Hide file tree
Showing 11 changed files with 382 additions and 93 deletions.
16 changes: 15 additions & 1 deletion docker-compose/dependent-docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,18 @@ services:
depends_on:
- database
- redis
- mock-identity-system
- mock-identity-system

esignet-ui:
image: 'mosipdev/oidc-ui:release-1.5.x'
user: root
ports:
- 3000:3000
environment:
- container_user=mosip
- DEFAULT_WELLKNOWN=%5B%7B%22name%22%3A%22OpenID%20Configuration%22%2C%22value%22%3A%22%2F.well-known%2Fopenid-configuration%22%7D%2C%7B%22name%22%3A%22Jwks%20Json%22%2C%22value%22%3A%22%2F.well-known%2Fjwks.json%22%7D%2C%7B%22name%22%3A%22Authorization%20Server%22%2C%22value%22%3A%22%2F.well-known%2Foauth-authorization-server%22%7D%5D
- SIGN_IN_WITH_ESIGNET_PLUGIN_URL=https://raw.githubusercontent.com/mosip/artifactory-ref-impl/master/artifacts/src/mosip-plugins/sign-in-with-esignet/sign-in-with-esignet.zip
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- esignet
70 changes: 68 additions & 2 deletions postman-collection/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,70 @@
## Usage of [stomp_websocket.py](stomp_websocket.py)
## Usage of [ws_client.py](ws_client.py)

eKYC verification process is carried out through WebSocket connection and as postman currently does not support export of WS
collections, we have created [ws_client.py](ws_client.py) script.

TODO
This script is a simple Python WebSocket client that connects to a specified WebSocket server, subscribes to a topic using
the STOMP protocol, and allows the user to send messages to that topic.

## Overview of the Script
User Input:

The script starts by asking the user for the WebSocket server URL, slot ID, and cookie value.

If you are running eSignet signup service in local, then the url will be "ws://localhost:8089/v1/signup/ws"
Slot ID and IDV_SLOT_ALOTTED cookie value should be taken from 'http://localhost:8088/v1/signup/identity-verification/slot' endpoint response.

## WebSocket Callbacks:

Several callback functions handle different events during the WebSocket connection lifecycle:
on_message: Called when a message is received from the server.
on_error: Called when an error occurs during the WebSocket operation.
on_close: Called when the WebSocket connection is closed.
on_open: Called when the WebSocket connection is successfully established.

## STOMP Protocol Frames:

The script uses the STOMP protocol to communicate with the WebSocket server, sending:
A CONNECT frame to initiate the STOMP connection.
A SUBSCRIBE frame to listen for messages on a specified topic (based on the user-provided slot_id).
A SEND frame to send messages from the user input to the specified destination.

## Threading for User Input:

The send_user_input function runs in a separate thread, allowing the main thread to continue processing incoming messages while waiting for user input.
The user can enter messages to send to the server or type "exit" to close the connection.

## WebSocket Connection Management:

The start_ws_client function sets up the WebSocket connection using the websocket-client library, specifying the URI and headers (including cookies).
The connection is established with ws.run_forever(), which keeps the connection alive and processes incoming messages.


## How to use the script?
1. Install Required Library: Ensure you have the websocket-client library installed. You can install it using:

`pip install websocket-client`

2. Run the Script: Execute the script in your terminal or command prompt:

`python ws_client.py`

3. Provide Input: When prompted, enter the base URL (WebSocket server address), slot ID, and cookie value.

4. Sending Messages: When prompted, to enter message to send, type the message as below, there are 3 different messages

START step message -> `{"slotId":"slotId","stepCode":"START","frames":[{"frame":"","order":"0"}]}`

Other step messages -> `{"slotId":"slotId","stepCode":"<step_name as in the received messages>","frames":[{"frame":"","order":"0"}]}`

END step message -> `{"slotId":"slotId","stepCode":"END","frames":[{"frame":"","order":"0"}]}`

5. Receiving Messages: Any messages sent from the server to the subscribed topic will be printed to the console as they are received.


## Example interaction


![img.png](interaction_1.png)

![img_1.png](interaction_2.png)
Binary file added postman-collection/interaction_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added postman-collection/interaction_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
84 changes: 0 additions & 84 deletions postman-collection/stomp_websocket.py

This file was deleted.

70 changes: 70 additions & 0 deletions postman-collection/ws_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import websocket
import threading
import sys

base_url=input("Enter the base URL (ws://localhost:8089/v1/signup/ws): ")
slot_id=input("Enter the slotId: ")
cookie=input("Enter the cookie value: ")

def on_message(ws, message):
print("===================")
print(f"Received {message}")
print("===================")

def on_error(ws, error):
print("Error:", error)

def on_close(ws, close_status_code, close_msg):
print("Connection closed:", close_status_code, close_msg)

def on_open(ws):
# Send STOMP CONNECT frame
connect_frame = "CONNECT\naccept-version:1.2\n\n\x00"
ws.send(connect_frame)
print(f"{connect_frame}")

# Subscribe to the /topic/slotId destination
subscribe_frame = f"SUBSCRIBE\nid:sub-0\ndestination:/topic/{slot_id}\n\n\x00"
ws.send(subscribe_frame)
print(f"{subscribe_frame}")

# Start a new thread to take user input and send messages to the WebSocket
threading.Thread(target=send_user_input, args=(ws,)).start()

def send_user_input(ws):
try:
while True:
user_input = input("Enter a message to send: ")
if user_input.lower() == "exit":
print("Closing connection...")
ws.close()
break

# Send user input as a message to the WebSocket
send_frame = f"SEND\ndestination:/v1/signup/ws/process-frame\ncontent-type:application/json\n\n{user_input}\x00"
ws.send(send_frame)
print(f"{send_frame}")

except Exception as e:
print("Error sending message:", e)

# WebSocket connection
def start_ws_client():
uri = f"{base_url}?slotId={slot_id}" # Replace with your WebSocket server's URI
headers = {"Cookie": f"IDV_SLOT_ALLOTTED={cookie}"} # Replace with any necessary headers

ws = websocket.WebSocketApp(
uri,
header=headers,
on_open=on_open,
on_message=on_message,
on_error=on_error,
on_close=on_close
)

# Run the WebSocket with a blocking loop
ws.run_forever()

# Run the subscribe function
if __name__ == "__main__":
start_ws_client()
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic");
registry.setApplicationDestinationPrefixes("/v1/signup/ws");
}

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
//By default, only same origin requests are allowed, should take the origin from properties
registry.addEndpoint("/ws").setAllowedOrigins("*").setHandshakeHandler(webSocketHandshakeHandler);
registry.addEndpoint("/ws")
.setAllowedOrigins("*")
.setHandshakeHandler(webSocketHandshakeHandler);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,11 @@ public class IdentityVerifierFactory {


public IdentityVerifierPlugin getIdentityVerifier(String id) {
log.info("Request to fetch identity verifier with id : {}", id);
log.info("List of identity verifiers found : {}", identityVerifiers);
log.debug("Request to fetch identity verifier with id : {} in the available list of verifiers: {}", id, identityVerifiers);
Optional<IdentityVerifierPlugin> result = identityVerifiers.stream()
.filter(idv -> idv.getVerifierId().equals(id) )
.findFirst();

log.info("Identity verifiers result : {}", result);

if(result.isEmpty())
throw new IdentityVerifierException(PLUGIN_NOT_FOUND);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ public void processVerificationResult(IdentityVerificationResult identityVerific
return;
}

log.debug("Analysis result published to /topic/{}", identityVerificationResult.getId());
simpMessagingTemplate.convertAndSend("/topic/"+identityVerificationResult.getId(), identityVerificationResult);

//END step marks verification process completion
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package io.mosip.signup.helper;

import io.mosip.esignet.core.util.IdentityProviderUtil;
import io.mosip.signup.services.CacheUtilService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class CryptoHelperTest {

@InjectMocks
private CryptoHelper cryptoHelper;

@Mock
private CacheUtilService cacheUtilService;

private static String symmetricAlgorithm = "AES/CFB/PKCS5Padding";
private static String symmetricKeyAlgorithm = "AES";
private static int symmetricKeySize = 256;

String keyAlias = "aced6829-63bb-5b28-8898-64efd90a70fa";
private static String secretKey;

static {
KeyGenerator keyGenerator = null;
try {
keyGenerator = KeyGenerator.getInstance(symmetricKeyAlgorithm);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
keyGenerator.init(symmetricKeySize);
secretKey = IdentityProviderUtil.b64Encode(keyGenerator.generateKey().getEncoded());
}

@Before
public void setUp() {
when(cacheUtilService.getSecretKey(Mockito.anyString())).thenReturn(secretKey).thenReturn(secretKey);

ReflectionTestUtils.setField(cryptoHelper, "symmetricAlgorithm", symmetricAlgorithm);
ReflectionTestUtils.setField(cryptoHelper, "symmetricKeyAlgorithm", symmetricKeyAlgorithm);
ReflectionTestUtils.setField(cryptoHelper, "symmetricKeySize", symmetricKeySize);
}

@Test
public void symmetricEncrypt_withValidInput_thenPass() {
String data = "test data test fatata";
String encryptedData = cryptoHelper.symmetricEncrypt(data);

assertNotNull(encryptedData);
verify(cacheUtilService, times(1)).getActiveKeyAlias();
verify(cacheUtilService, times(1)).getSecretKey(keyAlias);

String decryptedData = cryptoHelper.symmetricDecrypt(encryptedData);
assertNotNull(decryptedData);
assertEquals(data, decryptedData);
verify(cacheUtilService, times(1)).getActiveKeyAlias();
verify(cacheUtilService, times(2)).getSecretKey(keyAlias);
}
}

Loading

0 comments on commit a5a0798

Please sign in to comment.