diff --git a/pom.xml b/pom.xml index 9f00670e423b8a545d04eff3f3d58ca0f4b7e38f..7e8627c9eeb73329ba9122be087751bbd9542278 100644 --- a/pom.xml +++ b/pom.xml @@ -111,6 +111,12 @@ + + com.googlecode.libphonenumber + libphonenumber + 8.9.12 + + com.fasterxml.jackson.core jackson-databind diff --git a/src/main/java/biz/nynja/account/components/Validator.java b/src/main/java/biz/nynja/account/components/Validator.java index 769307bbb72ddc5cab152613b6cf2d42d4423c40..ff8899b0280ce69cc66557a9cfbadcf1346b4640 100644 --- a/src/main/java/biz/nynja/account/components/Validator.java +++ b/src/main/java/biz/nynja/account/components/Validator.java @@ -20,8 +20,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.stereotype.Component; - import biz.nynja.account.grpc.AddAuthenticationProviderRequest; +import com.google.i18n.phonenumbers.NumberParseException; +import com.google.i18n.phonenumbers.PhoneNumberUtil; +import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber; import biz.nynja.account.grpc.AuthProviderDetails; import biz.nynja.account.grpc.AuthenticationType; import biz.nynja.account.grpc.CompletePendingAccountCreationRequest; @@ -126,6 +128,27 @@ public class Validator { return isValid; } + + public String getNormalizedPhoneNumber(String authenticationProvider) { + String[] provider = authenticationProvider.split(":"); + String country = provider[0]; + String phoneNumber = provider[1]; + logger.info("libphone: New phone number normalization request received - phone number: {}, country code: {}", phoneNumber, + country); + + PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance(); + String normalizedPhoneNumber = ""; + try { + PhoneNumber pn = phoneUtil.parse(phoneNumber, country); + normalizedPhoneNumber = Integer.toString(pn.getCountryCode()) + + Long.toString(pn.getNationalNumber()); + logger.info("libphone: Normalized phone number: " + normalizedPhoneNumber); + } catch (NumberParseException e) { + logger.error("libphone: NumberParseException was thrown: {}", e.toString()); + logger.debug("libphone: NumberParseException was thrown: {}", e.getCause()); + } + return normalizedPhoneNumber; + } public boolean isUsernameValid(String username) { diff --git a/src/main/java/biz/nynja/account/services/AccountServiceImpl.java b/src/main/java/biz/nynja/account/services/AccountServiceImpl.java index 437066074ec60cb48b576d808bd0dbab3bdfa37f..c1acf9d644d934d07a0a1c48c10712fda003b57d 100644 --- a/src/main/java/biz/nynja/account/services/AccountServiceImpl.java +++ b/src/main/java/biz/nynja/account/services/AccountServiceImpl.java @@ -12,7 +12,6 @@ import org.lognet.springboot.grpc.GRpcService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; - import biz.nynja.account.components.AccountServiceHelper; import biz.nynja.account.components.Validator; import biz.nynja.account.grpc.AccountByAccountIdRequest; @@ -24,6 +23,8 @@ import biz.nynja.account.grpc.AccountsByProfileIdRequest; import biz.nynja.account.grpc.AccountsList; import biz.nynja.account.grpc.AccountsResponse; import biz.nynja.account.grpc.AddAuthenticationProviderRequest; +import biz.nynja.account.grpc.AuthProviderDetails; +import biz.nynja.account.grpc.AuthenticationType; import biz.nynja.account.grpc.CompletePendingAccountCreationRequest; import biz.nynja.account.grpc.CreateAccountRequest; import biz.nynja.account.grpc.CreatePendingAccountRequest; @@ -119,17 +120,26 @@ public class AccountServiceImpl extends AccountServiceGrpc.AccountServiceImplBas responseObserver.onCompleted(); return; } + if (request.getAuthenticationType() == AuthenticationType.PHONE) { + // Get the normalized phone number from libphone + AccountByAuthenticationProviderRequest newRequest = AccountByAuthenticationProviderRequest.newBuilder() + .setAuthenticationType(request.getAuthenticationType()) + .setAuthenticationIdentifier(validator.getNormalizedPhoneNumber(request.getAuthenticationIdentifier())).build(); + request = newRequest; + } Account account = accountServiceHelper.getAccountByAuthenticationProviderHelper( request.getAuthenticationIdentifier(), request.getAuthenticationType().toString()); + if (account == null) { + logger.debug("No matching accounts found for authetntication provider {}: {}", request.getAuthenticationType(), request.getAuthenticationIdentifier()); responseObserver.onNext(AccountResponse.newBuilder() .setError(ErrorResponse.newBuilder().setCause(Cause.ACCOUNT_NOT_FOUND)).build()); responseObserver.onCompleted(); return; - } + } AccountResponse response = AccountResponse.newBuilder().setAccountDetails(account.toProto()).build(); logger.debug("Found result for account by authentication provider {}: {}: \"{}\"", @@ -258,6 +268,11 @@ public class AccountServiceImpl extends AccountServiceGrpc.AccountServiceImplBas PendingAccount pendingAccount = PendingAccount.fromProto(request); + if (request.getAuthenticationType() == AuthenticationType.PHONE) { + // Get the normalized phone number from libphone + pendingAccount.setAuthenticationProvider(validator.getNormalizedPhoneNumber(request.getAuthenticationProvider())); + } + pendingAccount.setAccountId(UUID.randomUUID()); pendingAccount.setProfileId(UUID.randomUUID()); pendingAccount.setCreationTimestamp(new Date().getTime()); @@ -290,9 +305,8 @@ public class AccountServiceImpl extends AccountServiceGrpc.AccountServiceImplBas return; } - if (request.getUsername() != null && !request.getUsername().trim().isEmpty() - && accountRepositoryAdditional.foundExistingNotOwnUsername(UUID.fromString(request.getAccountId()), - request.getUsername())) { + if (request.getUsername() != null && !request.getUsername().trim().isEmpty() && accountRepositoryAdditional + .foundExistingNotOwnUsername(UUID.fromString(request.getAccountId()), request.getUsername())) { responseObserver.onNext(AccountResponse.newBuilder() .setError(ErrorResponse.newBuilder().setCause(Cause.USERNAME_ALREADY_USED)).build()); responseObserver.onCompleted(); @@ -340,8 +354,8 @@ public class AccountServiceImpl extends AccountServiceGrpc.AccountServiceImplBas Cause cause = validator.validateUpdateProfileRequest(request); if (cause != null) { - responseObserver - .onNext(UpdateProfileResponse.newBuilder().setError(ErrorResponse.newBuilder().setCause(cause)).build()); + responseObserver.onNext( + UpdateProfileResponse.newBuilder().setError(ErrorResponse.newBuilder().setCause(cause)).build()); responseObserver.onCompleted(); return; } @@ -383,9 +397,8 @@ public class AccountServiceImpl extends AccountServiceGrpc.AccountServiceImplBas return; } - if (request.getUsername() != null && !request.getUsername().trim().isEmpty() - && accountRepositoryAdditional.foundExistingNotOwnUsername(UUID.fromString(request.getAccountId()), - request.getUsername())) { + if (request.getUsername() != null && !request.getUsername().trim().isEmpty() && accountRepositoryAdditional + .foundExistingNotOwnUsername(UUID.fromString(request.getAccountId()), request.getUsername())) { responseObserver.onNext(AccountResponse.newBuilder() .setError(ErrorResponse.newBuilder().setCause(Cause.USERNAME_ALREADY_USED)).build()); responseObserver.onCompleted(); @@ -422,8 +435,7 @@ public class AccountServiceImpl extends AccountServiceGrpc.AccountServiceImplBas boolean wasAccountDeleted = accountRepositoryAdditional.deleteAccount(UUID.fromString(request.getAccountId())); if (wasAccountDeleted) { - responseObserver.onNext(StatusResponse.newBuilder() - .setStatus("SUCCESS").build()); + responseObserver.onNext(StatusResponse.newBuilder().setStatus("SUCCESS").build()); responseObserver.onCompleted(); return; } @@ -447,8 +459,7 @@ public class AccountServiceImpl extends AccountServiceGrpc.AccountServiceImplBas boolean wasProfileDeleted = accountRepositoryAdditional.deleteProfile(UUID.fromString(request.getProfileId())); if (wasProfileDeleted) { logger.info("The profile was deleted successfully."); - responseObserver.onNext(StatusResponse.newBuilder() - .setStatus("SUCCESS").build()); + responseObserver.onNext(StatusResponse.newBuilder().setStatus("SUCCESS").build()); responseObserver.onCompleted(); return; } @@ -491,6 +502,20 @@ public class AccountServiceImpl extends AccountServiceGrpc.AccountServiceImplBas responseObserver.onCompleted(); return; } + + if (request.getAuthenticationProvider().getAuthenticationType() == AuthenticationType.PHONE) { + // Get the normalized phone number from libphone + AuthProviderDetails newAuthProviderDetails = AuthProviderDetails.newBuilder() + .setAuthenticationType(request.getAuthenticationProvider().getAuthenticationType()) + .setAuthenticationProvider(validator.getNormalizedPhoneNumber(request.getAuthenticationProvider().getAuthenticationProvider())) + .build(); + AddAuthenticationProviderRequest newRequest = AddAuthenticationProviderRequest.newBuilder() + .setProfileId(request.getProfileId()) + .setAuthenticationProvider(newAuthProviderDetails).build(); + request = newRequest; + } + + // Make sure that the requested profile id for update exists in DB. Profile profile = profileRepository.findByProfileId(UUID.fromString(request.getProfileId())); if (profile == null) { diff --git a/src/test/java/biz/nynja/account/components/LibphoneNormalizationParameterizedTest.java b/src/test/java/biz/nynja/account/components/LibphoneNormalizationParameterizedTest.java new file mode 100644 index 0000000000000000000000000000000000000000..19a76cd06f730585b89a6c201e62a681a1e932bc --- /dev/null +++ b/src/test/java/biz/nynja/account/components/LibphoneNormalizationParameterizedTest.java @@ -0,0 +1,71 @@ +package biz.nynja.account.components; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.util.Arrays; +import java.util.Collection; + +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestContextManager; +import org.springframework.test.context.TestExecutionListeners; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.context.junit4.rules.SpringClassRule; +import org.springframework.test.context.junit4.rules.SpringMethodRule; + +import biz.nynja.account.repositories.AccountRepository; +import biz.nynja.account.services.AccountServiceImpl; + +// ================================================================ +// This test is not part of the build but we consider it useful +// and would like to keep it for future use. +// =============================================================== + +@RunWith(Parameterized.class) +@TestExecutionListeners({}) +@ContextConfiguration(classes = { AccountServiceImpl.class }) +public class LibphoneNormalizationParameterizedTest { + + @Autowired + private Validator validator; + + private String expectedPhoneNumber; + private String inputPhoneNumber; + + @ClassRule + public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule(); + + @Rule + public final SpringMethodRule springMethodRule = new SpringMethodRule(); + + public LibphoneNormalizationParameterizedTest(String expectedPhoneNumber, String inputPhoneNumber) { + this.expectedPhoneNumber = expectedPhoneNumber; + this.inputPhoneNumber = inputPhoneNumber; + } + + @Parameterized.Parameters + public static Collection phoneNumbers() { + return Arrays.asList(new Object[][] { { "359887345234", "BG:+359887345234" }, + { "359887345234", "BG:00359887345234" }, + { "359887345234", "BG:359 887 345 234" }, + { "359887345234", "BG:359887345234567" }, + { "359887345234", "BG:359 887 345" }, + { "359887345234", "BG:359-887-345-234" } }); + } + + @Test + public void testNormalizedPhoneNumber() { + System.out.println("Input Phone Number is : " + inputPhoneNumber); + + String normalized = validator.getNormalizedPhoneNumber(inputPhoneNumber); + System.out.println("Normalized Phone Number is : " + normalized); + assertEquals(expectedPhoneNumber, normalized); + } +} diff --git a/src/test/java/biz/nynja/account/components/ValidatorTests.java b/src/test/java/biz/nynja/account/components/ValidatorTests.java index 67648182a4fc04e02dfc1b5da8bb3e4753e47f05..1c6f789ee3bb5eecaf3305bb3a0acb959442781b 100644 --- a/src/test/java/biz/nynja/account/components/ValidatorTests.java +++ b/src/test/java/biz/nynja/account/components/ValidatorTests.java @@ -6,6 +6,7 @@ package biz.nynja.account.components; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -16,7 +17,6 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; -import biz.nynja.account.components.Validator; import biz.nynja.account.grpc.AuthProviderDetails; import biz.nynja.account.grpc.AuthenticationType; import biz.nynja.account.grpc.CompletePendingAccountCreationRequest; @@ -60,7 +60,69 @@ public class ValidatorTests { isValid == true); } + + @Test + public void validNormalizedPhoneNumberTest1() { + + String expectedPhoneNumber = "359887345234"; + String inputPhoneNumber = "BG:+359887345234"; + String normalizedPhoneNumber = validator.getNormalizedPhoneNumber(inputPhoneNumber); + assertEquals(String.format("Phone number: '%s' should be normalized to '%s'", inputPhoneNumber, expectedPhoneNumber), + expectedPhoneNumber, normalizedPhoneNumber); + } + + @Test + public void validNormalizedPhoneNumberTest2() { + + String expectedPhoneNumber = "359887345234"; + String inputPhoneNumber = "BG:00359887345234"; + String normalizedPhoneNumber = validator.getNormalizedPhoneNumber(inputPhoneNumber); + assertEquals(String.format("Phone number: '%s' should be normalized to '%s'", inputPhoneNumber, expectedPhoneNumber), + expectedPhoneNumber, normalizedPhoneNumber); + } + + @Test + public void validNormalizedPhoneNumberTest3() { + + String expectedPhoneNumber = "359887345234"; + String inputPhoneNumber = "BG:359-887-345-234"; + String normalizedPhoneNumber = validator.getNormalizedPhoneNumber(inputPhoneNumber); + assertEquals(String.format("Phone number: '%s' should be normalized to '%s'", inputPhoneNumber, expectedPhoneNumber), + expectedPhoneNumber, normalizedPhoneNumber); + } + @Test + public void validNormalizedPhoneNumberTest4() { + + String expectedPhoneNumber = "359887345234"; + String inputPhoneNumber = "BG:359 887 345 234"; + String normalizedPhoneNumber = validator.getNormalizedPhoneNumber(inputPhoneNumber); + assertEquals(String.format("Phone number: '%s' should be normalized to '%s'", inputPhoneNumber, expectedPhoneNumber), + expectedPhoneNumber, normalizedPhoneNumber); + } + + @Test + public void invalidNormalizedPhoneNumberTest1() { + + String expectedPhoneNumber = "359887345234"; + String inputPhoneNumber = "BG:359887345234567"; + String normalizedPhoneNumber = validator.getNormalizedPhoneNumber(inputPhoneNumber); + assertNotEquals(String.format("Phone number: '%s' should be normalized to '%s'", inputPhoneNumber, expectedPhoneNumber), + expectedPhoneNumber, normalizedPhoneNumber); + } + + @Test + public void invalidNormalizedPhoneNumberTest2() { + + String expectedPhoneNumber = "359887345234"; + String inputPhoneNumber = "BG:35988734523"; + String normalizedPhoneNumber = validator.getNormalizedPhoneNumber(inputPhoneNumber); + assertNotEquals(String.format("Phone number: '%s' should be normalized to '%s'", inputPhoneNumber, expectedPhoneNumber), + expectedPhoneNumber, normalizedPhoneNumber); + } + + + @Test public void validUsernameTest() {