From 746bec49299eb601c5a5b3ab184fab01b3013248 Mon Sep 17 00:00:00 2001 From: NkwaTambe Date: Thu, 9 Jan 2025 10:57:05 +0100 Subject: [PATCH 1/4] implement mechanism to verify otp --- .../src/main/java/com/adorsys/webank/OtpRestServer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prs/prs-rest-server/src/main/java/com/adorsys/webank/OtpRestServer.java b/prs/prs-rest-server/src/main/java/com/adorsys/webank/OtpRestServer.java index cc78f15..d674eb1 100644 --- a/prs/prs-rest-server/src/main/java/com/adorsys/webank/OtpRestServer.java +++ b/prs/prs-rest-server/src/main/java/com/adorsys/webank/OtpRestServer.java @@ -21,6 +21,6 @@ public String sendOtp(OtpRequest request) { @Override public boolean validateOtp(OtpValidationRequest request) { - return otpService.validateOtp(request.getPhoneNumber(), request.getOtp()); + return otpService.validateOtp(request.getPhoneNumber(), request.getPublicKey() ,request.getOtpInput(), request.getOtpHash() ); } } From 9f7a24202e584e347410c1954dd9dabec3fded83 Mon Sep 17 00:00:00 2001 From: NkwaTambe Date: Thu, 9 Jan 2025 10:57:30 +0100 Subject: [PATCH 2/4] implement mechanism to verify otp --- .../webank/dto/OtpValidationRequest.java | 37 +++++++--- .../adorsys/webank/service/OtpServiceApi.java | 2 +- prs/prs-service-impl/pom.xml | 20 ++++++ .../webank/serviceimpl/OtpServiceImpl.java | 22 +++--- .../webank/serviceimpl/OtpServiceTest.java | 71 ++++++++++++++++++- 5 files changed, 127 insertions(+), 25 deletions(-) diff --git a/prs/prs-service-api/src/main/java/com/adorsys/webank/dto/OtpValidationRequest.java b/prs/prs-service-api/src/main/java/com/adorsys/webank/dto/OtpValidationRequest.java index 0fdd93c..58463fd 100644 --- a/prs/prs-service-api/src/main/java/com/adorsys/webank/dto/OtpValidationRequest.java +++ b/prs/prs-service-api/src/main/java/com/adorsys/webank/dto/OtpValidationRequest.java @@ -2,28 +2,47 @@ public class OtpValidationRequest { private String phoneNumber; - private String otp; + private String otpInput; + private String otpHash; + private String publicKey; public OtpValidationRequest() {} - public OtpValidationRequest(String phoneNumber, String otp) { + public OtpValidationRequest(String phoneNumber, String otpInput , String otpHash, String publicKey) { this.phoneNumber = phoneNumber; - this.otp = otp; + this.otpInput = otpInput; + this.otpHash = otpHash; + this.publicKey = publicKey; } + + public String getPhoneNumber() { return phoneNumber; } + public String getPublicKey(){ + return publicKey; + } + public String getOtpHash() { + return otpHash; + } + public String getOtpInput() { + return otpInput; + } + + + public void setPhoneNumber(String phoneNumber) { this.phoneNumber = phoneNumber; } - - public String getOtp() { - return otp; + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; } - - public void setOtp(String otp) { - this.otp = otp; + public void setOtpHash(String otpHash) { + this.otpHash = otpHash; + } + public void setOtpInput(String otpInput) { + this.otpInput = otpInput; } } diff --git a/prs/prs-service-api/src/main/java/com/adorsys/webank/service/OtpServiceApi.java b/prs/prs-service-api/src/main/java/com/adorsys/webank/service/OtpServiceApi.java index 9e64869..8735422 100644 --- a/prs/prs-service-api/src/main/java/com/adorsys/webank/service/OtpServiceApi.java +++ b/prs/prs-service-api/src/main/java/com/adorsys/webank/service/OtpServiceApi.java @@ -6,5 +6,5 @@ public interface OtpServiceApi { String generateOtp(); String sendOtp(String phoneNumber, String publicKey); String computeHash(String otp, String phoneNumber, String publicKey, String salt); - boolean validateOtp(String phoneNumber, String otp); + boolean validateOtp(String phoneNumber, String publicKey, String otpInput , String otpHash); } diff --git a/prs/prs-service-impl/pom.xml b/prs/prs-service-impl/pom.xml index fbcbad5..d3cd653 100644 --- a/prs/prs-service-impl/pom.xml +++ b/prs/prs-service-impl/pom.xml @@ -25,12 +25,32 @@ 0.0.1-SNAPSHOT compile + + + + org.mockito + mockito-core + 5.15.2 + test + + + org.mockito + mockito-inline + 5.2.0 + test + + com.twilio.sdk twilio 10.6.4 + + org.mockito + mockito-core + test + diff --git a/prs/prs-service-impl/src/main/java/com/adorsys/webank/serviceimpl/OtpServiceImpl.java b/prs/prs-service-impl/src/main/java/com/adorsys/webank/serviceimpl/OtpServiceImpl.java index 3cce3ae..8a8d589 100644 --- a/prs/prs-service-impl/src/main/java/com/adorsys/webank/serviceimpl/OtpServiceImpl.java +++ b/prs/prs-service-impl/src/main/java/com/adorsys/webank/serviceimpl/OtpServiceImpl.java @@ -11,8 +11,6 @@ import java.util.Base64; import com.twilio.Twilio; import com.twilio.rest.api.v2010.account.Message; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import jakarta.annotation.PostConstruct; import com.twilio.type.PhoneNumber; @@ -20,7 +18,6 @@ @Service public class OtpServiceImpl implements OtpServiceApi { - private static final Logger logger = LoggerFactory.getLogger(OtpServiceImpl.class); // Twilio credentials @Value("${twilio.account.sid}") @@ -40,7 +37,6 @@ public void initTwilio() { Twilio.init(accountSid, authToken); // Initialize Twilio once } - @Override public String generateOtp() { SecureRandom secureRandom = new SecureRandom(); @@ -51,14 +47,12 @@ public String generateOtp() { @Override public String sendOtp(String phoneNumber, String publicKey) { if (phoneNumber == null || !phoneNumber.matches("\\+?[1-9]\\d{1,14}")) { - logger.error("Invalid phone number format."); throw new IllegalArgumentException("Invalid phone number format"); } try { - String otp = generateOtp(); - String otpHash = computeHash(otp, phoneNumber,publicKey, salt); + String otpHash = computeHash(otp, phoneNumber, publicKey, salt); // Send OTP via Twilio Message message = Message.creator( @@ -67,19 +61,21 @@ public String sendOtp(String phoneNumber, String publicKey) { "Your OTP is: " + otp ).create(); - logger.info("OTP sent successfully to {}. SID: {}", phoneNumber, message.getSid()); - return otpHash; } catch (Exception e) { - logger.error("Failed to send OTP: {}", e.getMessage()); throw new FailedToSendOTPException("Failed to send OTP"); } } @Override - public boolean validateOtp(String phoneNumber, String otp) { - return false; + public boolean validateOtp(String phoneNumber, String publicKey, String otpInput, String otpHash) { + try { + String newOtpHash = computeHash(otpInput, phoneNumber, publicKey, salt); + return newOtpHash.equals(otpHash); + } catch (Exception e) { + return false; + } } @Override @@ -97,4 +93,4 @@ public String computeHash(String otp, String phoneNumber, String publicKey, Stri throw new HashComputationException("Error computing hash"); } } -} \ No newline at end of file +} diff --git a/prs/prs-service-impl/src/test/java/com/adorsys/webank/serviceimpl/OtpServiceTest.java b/prs/prs-service-impl/src/test/java/com/adorsys/webank/serviceimpl/OtpServiceTest.java index fb99e5a..685ecfb 100644 --- a/prs/prs-service-impl/src/test/java/com/adorsys/webank/serviceimpl/OtpServiceTest.java +++ b/prs/prs-service-impl/src/test/java/com/adorsys/webank/serviceimpl/OtpServiceTest.java @@ -1,13 +1,32 @@ package com.adorsys.webank.serviceimpl; import static org.junit.jupiter.api.Assertions.*; - +import com.twilio.rest.api.v2010.account.MessageCreator; import org.junit.jupiter.api.Test; +import com.adorsys.webank.exceptions.FailedToSendOTPException; +import com.twilio.Twilio; +import com.twilio.rest.api.v2010.account.Message; +import com.twilio.type.PhoneNumber; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; +import static org.mockito.ArgumentMatchers.any; +@ExtendWith(MockitoExtension.class) public class OtpServiceTest { OtpServiceImpl otpServiceImpl = new OtpServiceImpl(); + @InjectMocks + private OtpServiceImpl otpService; + + @Mock + private Message message; + @Test void generateFourDigitOtp() { String otp = otpServiceImpl.generateOtp(); @@ -18,4 +37,52 @@ void generateFourDigitOtp() { assert Integer.parseInt(otp) >= 1000 && Integer.parseInt(otp) <= 9999 : "Otp should be between 1000 and 9999"; } -} + @BeforeEach + void setup() { + // Inject test values for Twilio credentials + ReflectionTestUtils.setField(otpService, "accountSid", "testAccountSid"); + ReflectionTestUtils.setField(otpService, "authToken", "testAuthToken"); + ReflectionTestUtils.setField(otpService, "fromPhoneNumber", "+1234567890"); + ReflectionTestUtils.setField(otpService, "salt", "testSalt"); + Twilio.init("testAccountSid", "testAuthToken"); + } + + @Test + void testTwilioConnection() { + assertDoesNotThrow(() -> Twilio.init("testAccountSid", "testAuthToken")); + } + + + @Test + void testSendOtpSuccessfully() { + // Mock the MessageCreator and Message + Message mockMessage = Mockito.mock(Message.class); + MessageCreator mockMessageCreator = Mockito.mock(MessageCreator.class); + + // Mock the behavior of create() to return a mock Message + Mockito.when(mockMessageCreator.create()).thenReturn(mockMessage); + + // Mock the behavior of Message.creator to return the mock MessageCreator + Mockito.mockStatic(Message.class).when(() -> + Message.creator(any(PhoneNumber.class), any(PhoneNumber.class), any(String.class)) + ).thenReturn(mockMessageCreator); + + String phoneNumber = "+1234567890"; + String publicKey = "testPublicKey"; + + // Call the method + String otpHash = otpService.sendOtp(phoneNumber, publicKey); + + // Debugging: print the actual OTP hash to understand its structure + System.out.println("Generated OTP hash: " + otpHash); + + // Assert the OTP hash is not null + assertNotNull(otpHash, "OTP hash should not be null"); + + // Verify interactions with the mock MessageCreator + Mockito.verify(mockMessageCreator, Mockito.times(1)).create(); + + // Update the assertion to check Base64 format + assertTrue(otpHash.matches("[a-zA-Z0-9+/=]+"), "OTP hash should be a valid Base64 string"); + } +} \ No newline at end of file From aeebcc4c57be8ff2e09d1d09e0831a7f0097c04b Mon Sep 17 00:00:00 2001 From: NkwaTambe Date: Thu, 9 Jan 2025 11:09:09 +0100 Subject: [PATCH 3/4] fix pmd check failure --- .../java/com/adorsys/webank/serviceimpl/OtpServiceImpl.java | 2 +- .../java/com/adorsys/webank/serviceimpl/OtpServiceTest.java | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/prs/prs-service-impl/src/main/java/com/adorsys/webank/serviceimpl/OtpServiceImpl.java b/prs/prs-service-impl/src/main/java/com/adorsys/webank/serviceimpl/OtpServiceImpl.java index 8a8d589..8af3d87 100644 --- a/prs/prs-service-impl/src/main/java/com/adorsys/webank/serviceimpl/OtpServiceImpl.java +++ b/prs/prs-service-impl/src/main/java/com/adorsys/webank/serviceimpl/OtpServiceImpl.java @@ -55,7 +55,7 @@ public String sendOtp(String phoneNumber, String publicKey) { String otpHash = computeHash(otp, phoneNumber, publicKey, salt); // Send OTP via Twilio - Message message = Message.creator( + Message.creator( new PhoneNumber(phoneNumber), new PhoneNumber(fromPhoneNumber), "Your OTP is: " + otp diff --git a/prs/prs-service-impl/src/test/java/com/adorsys/webank/serviceimpl/OtpServiceTest.java b/prs/prs-service-impl/src/test/java/com/adorsys/webank/serviceimpl/OtpServiceTest.java index 685ecfb..4a7b361 100644 --- a/prs/prs-service-impl/src/test/java/com/adorsys/webank/serviceimpl/OtpServiceTest.java +++ b/prs/prs-service-impl/src/test/java/com/adorsys/webank/serviceimpl/OtpServiceTest.java @@ -73,9 +73,6 @@ void testSendOtpSuccessfully() { // Call the method String otpHash = otpService.sendOtp(phoneNumber, publicKey); - // Debugging: print the actual OTP hash to understand its structure - System.out.println("Generated OTP hash: " + otpHash); - // Assert the OTP hash is not null assertNotNull(otpHash, "OTP hash should not be null"); From edaa8171f043d37360fada5a8ba1384a4ee34af2 Mon Sep 17 00:00:00 2001 From: NkwaTambe Date: Thu, 9 Jan 2025 12:12:28 +0100 Subject: [PATCH 4/4] fix test file and merge conflicts locally --- .../webank/serviceimpl/OtpServiceTest.java | 47 ++++++++----------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/prs/prs-service-impl/src/test/java/com/adorsys/webank/serviceimpl/OtpServiceTest.java b/prs/prs-service-impl/src/test/java/com/adorsys/webank/serviceimpl/OtpServiceTest.java index 41f48b5..3624cdf 100644 --- a/prs/prs-service-impl/src/test/java/com/adorsys/webank/serviceimpl/OtpServiceTest.java +++ b/prs/prs-service-impl/src/test/java/com/adorsys/webank/serviceimpl/OtpServiceTest.java @@ -1,33 +1,24 @@ package com.adorsys.webank.serviceimpl; -import static org.junit.jupiter.api.Assertions.*; -import com.twilio.rest.api.v2010.account.MessageCreator; - -import com.adorsys.webank.exceptions.HashComputationException; -import org.junit.jupiter.api.Test; -import com.adorsys.webank.exceptions.FailedToSendOTPException; -import com.twilio.Twilio; -import com.twilio.rest.api.v2010.account.Message; -import com.twilio.type.PhoneNumber; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.test.util.ReflectionTestUtils; -import static org.mockito.ArgumentMatchers.any; +import com.twilio.*; +import com.twilio.rest.api.v2010.account.*; +import com.twilio.type.*; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.*; +import org.mockito.*; +import org.mockito.junit.jupiter.*; +import org.springframework.test.util.*; -@ExtendWith(MockitoExtension.class) +import java.nio.charset.*; +import java.security.*; +import java.util.*; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Base64; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +@ExtendWith(MockitoExtension.class) public class OtpServiceTest { - OtpServiceImpl otpServiceImpl = new OtpServiceImpl(); @InjectMocks @@ -61,7 +52,6 @@ void testTwilioConnection() { assertDoesNotThrow(() -> Twilio.init("testAccountSid", "testAuthToken")); } - @Test void testSendOtpSuccessfully() { // Mock the MessageCreator and Message @@ -91,20 +81,23 @@ void testSendOtpSuccessfully() { // Update the assertion to check Base64 format assertTrue(otpHash.matches("[a-zA-Z0-9+/=]+"), "OTP hash should be a valid Base64 string"); } -} + @Test void testComputeHashWithValidInputs() throws NoSuchAlgorithmException { String otp = "1234"; String phoneNumber = "+237654066316"; String publicKey = "public-key-123"; String salt = "unique-salt"; + // Expected hash computation String input = otp + phoneNumber + publicKey + salt; MessageDigest digest = MessageDigest.getInstance("SHA-256"); byte[] hashBytes = digest.digest(input.getBytes(StandardCharsets.UTF_8)); String expectedHash = Base64.getEncoder().encodeToString(hashBytes); + // Compute hash using the method String actualHash = otpServiceImpl.computeHash(otp, phoneNumber, publicKey, salt); + assertNotNull(actualHash, "Hash should not be null"); assertEquals(expectedHash, actualHash, "Hashes should match"); } @@ -115,10 +108,10 @@ void testComputeHashWithEmptyInputs() { String phoneNumber = ""; String publicKey = ""; String salt = ""; + String actualHash = otpServiceImpl.computeHash(otp, phoneNumber, publicKey, salt); + assertNotNull(actualHash, "Hash should not be null"); assertFalse(actualHash.isEmpty(), "Hash should not be empty"); } - - }