diff --git a/charts/account-service/templates/authentication-policy.yaml b/charts/account-service/templates/authentication-policy.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..8fdcf60b6660bc4d40271e720fb2c244bae626fe
--- /dev/null
+++ b/charts/account-service/templates/authentication-policy.yaml
@@ -0,0 +1,19 @@
+apiVersion: "authentication.istio.io/v1alpha1"
+kind: "Policy"
+metadata:
+ name: {{ template "account-service.fullname" . }}
+ labels:
+ app: {{ template "account-service.name" . }}
+ chart: {{ template "account-service.chart" . }}
+ release: {{ .Release.Name }}
+ heritage: {{ .Release.Service }}
+spec:
+ targets:
+ - name: {{ template "account-service.name" . }}
+ origins:
+ - jwt:
+ issuer: https://auth.nynja.biz/
+ jwksUri: http://auth-service.auth.svc.cluster.local:8008/keys/public
+ audiences:
+ - dGVzdEluc3RhbmNl:NynjaApp:NynjaOrg
+ principalBinding: USE_ORIGIN
diff --git a/charts/account-service/templates/deployment.yaml b/charts/account-service/templates/deployment.yaml
index 8a286f602203e1c0af9b5d705b15abfd9cda0267..05a885c5a826e4bc8f9d34943012d5fc00f7d11b 100644
--- a/charts/account-service/templates/deployment.yaml
+++ b/charts/account-service/templates/deployment.yaml
@@ -42,9 +42,11 @@ spec:
periodSeconds: 5
timeoutSeconds: 5
livenessProbe:
- httpGet:
- path: /actuator/info
- port: {{ .Values.ports.containerPort.http }}
+ exec:
+ command:
+ - /bin/sh
+ - -c
+ - curl --silent http://localhost:{{ .Values.ports.containerPort.http }}/actuator/info
successThreshold: 1
failureThreshold: 10
initialDelaySeconds: 60
diff --git a/pom.xml b/pom.xml
index 7a41f5d74bc1599557bcc375794b4a8ce12f2456..fa3ba37e94e34c43440b270365f6316cde55d59b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -111,6 +111,18 @@
+
+ libs-snapshot-local.biz.nynja.protos
+ bridge-service-intracoldev
+ 1.0-SNAPSHOT
+
+
+ com.google.protobuf
+ protobuf-java
+
+
+
+
com.googlecode.libphonenumber
libphonenumber
diff --git a/releases/staging/account-service.yaml b/releases/staging/account-service.yaml
index 28b8dfb94f77cb4e4ca422481d1e68ce71503679..6adc18ac0870f0b407e1f34c4220f6469a8a39de 100644
--- a/releases/staging/account-service.yaml
+++ b/releases/staging/account-service.yaml
@@ -31,6 +31,7 @@ spec:
http: 8080
grpc: 6565
+ ## CORS policy needs clean up!!! This is temporary solution to allow UI team and others that depend on stable environment to work on staging. ##
# CORS policy
corsPolicy:
allowOrigin:
diff --git a/src/main/java/biz/nynja/account/configuration/ProfileDataConfiguration.java b/src/main/java/biz/nynja/account/configuration/ProfileDataConfiguration.java
new file mode 100644
index 0000000000000000000000000000000000000000..bcb6ad994769cffa4939d867747e73cfeb4ddff6
--- /dev/null
+++ b/src/main/java/biz/nynja/account/configuration/ProfileDataConfiguration.java
@@ -0,0 +1,21 @@
+/**
+ * Copyright (C) 2018 Nynja Inc. All rights reserved.
+ */
+package biz.nynja.account.configuration;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ConfigurationProperties(prefix = "profile-data")
+public class ProfileDataConfiguration {
+ private int maxAuthenticationprovidersPerProfile;
+
+ public int getMaxAuthenticationprovidersPerProfile() {
+ return maxAuthenticationprovidersPerProfile;
+ }
+
+ public void setMaxAuthenticationprovidersPerProfile(int maxAuthenticationprovidersPerProfile) {
+ this.maxAuthenticationprovidersPerProfile = maxAuthenticationprovidersPerProfile;
+ }
+}
diff --git a/src/main/java/biz/nynja/account/grid/ag/AdminServiceImpl.java b/src/main/java/biz/nynja/account/grid/ag/AdminServiceImpl.java
index 1c9272e0fc94755d62b6f767c2394d25c8a3e32c..87fb0faea24abf67638d6b85d69d971f52ee54c6 100644
--- a/src/main/java/biz/nynja/account/grid/ag/AdminServiceImpl.java
+++ b/src/main/java/biz/nynja/account/grid/ag/AdminServiceImpl.java
@@ -3,6 +3,7 @@
*/
package biz.nynja.account.grid.ag;
+import org.apache.commons.lang3.StringUtils;
import org.lognet.springboot.grpc.GRpcService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -13,6 +14,8 @@ import biz.nynja.account.admin.grpc.AccountsCount;
import biz.nynja.account.admin.grpc.AdminAccountServiceGrpc;
import biz.nynja.account.admin.grpc.CreateAccountRequest;
import biz.nynja.account.admin.grpc.EmptyRequest;
+import biz.nynja.account.admin.grpc.FilterModel;
+import biz.nynja.account.admin.grpc.FilterModel.FilterType;
import biz.nynja.account.admin.grpc.GetAllAccountsRequest;
import biz.nynja.account.grpc.AccountResponse;
import biz.nynja.account.grpc.CompletePendingAccountCreationRequest;
@@ -52,10 +55,11 @@ public class AdminServiceImpl extends AdminAccountServiceGrpc.AdminAccountServic
logger.info("Getting accounts ...");
logger.debug("Getting accounts: {} ...", request);
- Validation validation = validateRowInput(request.getStartRow(), request.getEndRow());
+ Validation validation = validateInput(request);
if (validation.hasErrors()) {
responseObserver.onNext(AccountsAdminResponse.newBuilder().setError(ErrorResponse.newBuilder()
- .setCause(validation.getCause().get()).setMessage(validation.getErrorMessage())).build());
+ .setCause(validation.getCause().get()).setMessage(validation.getErrors().get(0).getMessage()))
+ .build());
responseObserver.onCompleted();
return;
}
@@ -67,18 +71,50 @@ public class AdminServiceImpl extends AdminAccountServiceGrpc.AdminAccountServic
responseObserver.onCompleted();
}
- private Validation validateRowInput(int startRow, int endRow) {
+ private Validation validateInput(GetAllAccountsRequest request) {
Validation validation = new Validation();
- if (startRow < 1) {
- validation.addError(new ValidationError(
- "startRow parameter (" + startRow
- + ") should be more than 1 and less or equal to endRow parameter (" + endRow + ").",
+ if (request.getStartRow() < 1) {
+ validation.addError(new ValidationError("startRow parameter (" + request.getStartRow()
+ + ") should be more than 1 and less or equal to endRow parameter (" + request.getStartRow() + ").",
Cause.ADMIN_INVALID_START_ROW));
- } else if (endRow < startRow || (endRow > accountRepository.count() + 100)) {
- validation.addError(new ValidationError("endRow parameter (" + endRow + ") should be more than start row ("
- + startRow + ") and less than all users plus maximum pagination of 100."
- + (accountRepository.count() + 100), Cause.ADMIN_INVALID_END_ROW));
}
+ if (request.getEndRow() < request.getStartRow() || (request.getEndRow() > accountRepository.count() + 100)) {
+ validation.addError(
+ new ValidationError("endRow parameter (" + request.getEndRow() + ") should be more than start row ("
+ + request.getStartRow() + ") and less than all users plus maximum pagination of 100."
+ + (accountRepository.count() + 100), Cause.ADMIN_INVALID_END_ROW));
+ }
+
+ for (FilterModel model : request.getFilterModelList()) {
+ if ("creationTimestamp".equals(model.getColId()) || "lastUpdateTimestamp".equals(model.getColId())) {
+ try {
+ Long.valueOf(model.getFilterValue());
+ if (!StringUtils.isEmpty(model.getAdditionalFilterValue())) {
+ Long.valueOf(model.getAdditionalFilterValue());
+
+ if (Long.valueOf(model.getFilterValue()) > Long.valueOf(model.getAdditionalFilterValue())) {
+ validation.addError(new ValidationError(
+ "When filtering by creationTimestamp and lastUpdateTimestamp filter value should be lower than additional filter value.",
+ Cause.ADMIN_INVALID_FILTER_CRITERIA));
+ }
+ }
+ } catch (NumberFormatException e) {
+ validation.addError(new ValidationError(
+ "When filtering by creationTimestamp and lastUpdateTimestamp filter values should be valid long numbers.",
+ Cause.ADMIN_INVALID_FILTER_CRITERIA));
+ }
+
+ if ((FilterType.EQUALS.equals(model.getFilterType())
+ || FilterType.NOTEQUAL.equals(model.getFilterType()))
+ && (StringUtils.isEmpty(model.getFilterValue())
+ || StringUtils.isEmpty(model.getAdditionalFilterValue()))) {
+ validation.addError(new ValidationError(
+ "When filtering by creationTimestamp and lastUpdateTimestamp with type of equals or nonequal both values should be present.",
+ Cause.ADMIN_INVALID_FILTER_CRITERIA));
+ }
+ }
+ }
+
return validation;
}
diff --git a/src/main/java/biz/nynja/account/grid/ag/AgGridService.java b/src/main/java/biz/nynja/account/grid/ag/AgGridService.java
index 4d706528a68f93b809cc5bdd3776ae916f34439c..88f24196de8d3364ffd0b81a33b973ed3b9c8c99 100644
--- a/src/main/java/biz/nynja/account/grid/ag/AgGridService.java
+++ b/src/main/java/biz/nynja/account/grid/ag/AgGridService.java
@@ -167,7 +167,7 @@ public class AgGridService {
} else {
helper = getFilteredResults(pageable, filterModel);
accounts.retainAll(helper.getAccounts().getContent());
- if (count < helper.getCount()) {
+ if (helper.getCount() < count) {
count = helper.getCount();
}
}
diff --git a/src/main/java/biz/nynja/account/repositories/AccountRepositoryAdditional.java b/src/main/java/biz/nynja/account/repositories/AccountRepositoryAdditional.java
index c6f039ded6af908b5e99180e2582a4636263996f..800a1cde24c7dd695aa9f8077dbcc9d9c0a7239d 100644
--- a/src/main/java/biz/nynja/account/repositories/AccountRepositoryAdditional.java
+++ b/src/main/java/biz/nynja/account/repositories/AccountRepositoryAdditional.java
@@ -12,6 +12,7 @@ import org.springframework.stereotype.Repository;
import biz.nynja.account.grpc.CompletePendingAccountCreationRequest;
import biz.nynja.account.grpc.UpdateAccountRequest;
import biz.nynja.account.grpc.ErrorResponse.Cause;
+import biz.nynja.account.grpc.SearchableOption;
import biz.nynja.account.models.Account;
import biz.nynja.account.models.AccountByProfileId;
import biz.nynja.account.models.AuthenticationProvider;
@@ -36,6 +37,10 @@ public interface AccountRepositoryAdditional {
PendingAccountByAuthenticationProvider findSameAuthenticationProviderInPendingAccount(AuthenticationProvider authProvider);
+ boolean accountIdAlreadyUsed(UUID accountId);
+
+ boolean profileIdAlreadyUsed(UUID profileId);
+
boolean authenticationProviderAlreadyUsedInProfile(AuthenticationProvider authProvider);
boolean addAuthenticationProvider(UUID profileId, AuthenticationProvider authProvider);
@@ -58,4 +63,6 @@ public interface AccountRepositoryAdditional {
Optional searchAccountByLoginOption(AuthenticationProvider loginOption) throws IncorrectAccountCountException;
void removeNullsForSearchableOption();
+
+ boolean updateSearchableOption(UUID profileId, String authProviderType, String authProvider, SearchableOption searchableOption);
}
diff --git a/src/main/java/biz/nynja/account/repositories/AccountRepositoryAdditionalImpl.java b/src/main/java/biz/nynja/account/repositories/AccountRepositoryAdditionalImpl.java
index f0e92ca8806625adebe20bf6438ae6ada9a55574..da4d68398489b25f26a50c15ce89e844396fa510 100644
--- a/src/main/java/biz/nynja/account/repositories/AccountRepositoryAdditionalImpl.java
+++ b/src/main/java/biz/nynja/account/repositories/AccountRepositoryAdditionalImpl.java
@@ -15,6 +15,9 @@ import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
+import biz.nynja.account.repositories.batch.SagaTransaction;
+import biz.nynja.account.repositories.batch.Transaction;
+import biz.nynja.account.services.erlang.ErlangAccountBridge;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.cassandra.core.CassandraBatchOperations;
@@ -36,6 +39,7 @@ import biz.nynja.account.grpc.CompletePendingAccountCreationRequest;
import biz.nynja.account.grpc.ContactType;
import biz.nynja.account.grpc.ErrorResponse.Cause;
import biz.nynja.account.grpc.Role;
+import biz.nynja.account.grpc.SearchableOption;
import biz.nynja.account.grpc.UpdateAccountRequest;
import biz.nynja.account.models.Account;
import biz.nynja.account.models.AccountBuilder;
@@ -70,7 +74,7 @@ public class AccountRepositoryAdditionalImpl implements AccountRepositoryAdditio
private final PendingAccountRepository pendingAccountRepository;
private final AccountServiceHelper accountServiceHelper;
private final Session session;
- private final ErlangAccountHttpBridge erlangAccountBridge;
+ private final ErlangAccountBridge erlangAccountBridge;
private final PermissionsValidator permissionsValidator;
public AccountRepositoryAdditionalImpl(PendingAccountConfiguration pendingAccountConfiguration,
@@ -81,7 +85,7 @@ public class AccountRepositoryAdditionalImpl implements AccountRepositoryAdditio
ProfileByAuthenticationProviderRepository profileByAuthenticationProviderRepository,
PendingAccountByAuthenticationProviderRepository pendingAccountByAuthenticationProviderRepository,
PendingAccountRepository pendingAccountRepository, AccountServiceHelper accountServiceHelper,
- Session session, ErlangAccountHttpBridge erlangAccountBridge, PermissionsValidator permissionsValidator,
+ Session session, ErlangAccountBridge erlangAccountBridge, PermissionsValidator permissionsValidator,
AccountDataConfiguration accountDataConfiguration) {
this.pendingAccountConfiguration = pendingAccountConfiguration;
this.cassandraTemplate = cassandraTemplate;
@@ -102,7 +106,7 @@ public class AccountRepositoryAdditionalImpl implements AccountRepositoryAdditio
}
public Account completePendingAccountCreation(CompletePendingAccountCreationRequest request) {
- CassandraBatchOperations batchOperations = cassandraTemplate.batchOps();
+ Transaction sagaTransaction = new SagaTransaction(cassandraTemplate);
PendingAccount pendingAccount = pendingAccountRepository
.findByAccountId(UUID.fromString(request.getAccountId()));
if (pendingAccount == null) {
@@ -119,14 +123,29 @@ public class AccountRepositoryAdditionalImpl implements AccountRepositoryAdditio
pendingAccount.getAuthenticationProviderType(), pendingAccount.getAuthenticationProvider());
return null;
}
+ if (accountRepository.findByAccountId(pendingAccount.getAccountId()) != null) {
+ logger.error("The account id {} is already used.", pendingAccount.getAccountId());
+ pendingAccountRepository.deleteById(pendingAccount.getAccountId());
+ return null;
+ }
+ if (profileRepository.findByProfileId(pendingAccount.getProfileId()) != null) {
+ logger.error("The profile id {} is already used.", pendingAccount.getProfileId());
+ pendingAccountRepository.deleteById(pendingAccount.getAccountId());
+ return null;
+ }
Long timeCreated = Instant.now().toEpochMilli();
WriteResult wr;
try {
- newAccountInsert(batchOperations, request, pendingAccount, timeCreated);
- newProfileByAuthenticationProviderInsert(batchOperations, pendingAccount);
- newProfileInsert(batchOperations, request, pendingAccount, timeCreated);
- wr = batchOperations.execute();
+ Account account = newAccountInsert(sagaTransaction, request, pendingAccount, timeCreated);
+ newProfileByAuthenticationProviderInsert(sagaTransaction, pendingAccount);
+ Profile profile = newProfileInsert(sagaTransaction, request, pendingAccount, timeCreated);
+ wr = sagaTransaction.execute();
+ if (!erlangAccountBridge.createProfile(profile, account)) {
+ logger.error("Internal error with erlang");
+ sagaTransaction.rollBack();
+ return null;
+ }
} catch (IllegalArgumentException | IllegalStateException e) {
logger.info("Exception while completing pending account creation.");
logger.debug("Exception while completing pending account creation: {} ...", e.getMessage());
@@ -174,7 +193,7 @@ public class AccountRepositoryAdditionalImpl implements AccountRepositoryAdditio
}
public Account updateAccount(UpdateAccountRequest request) {
- CassandraBatchOperations batchOperations = cassandraTemplate.batchOps();
+ Transaction sagaTransaction = new SagaTransaction(cassandraTemplate);
Account existingAccount = accountRepository.findByAccountId(UUID.fromString(request.getAccountId()));
if (existingAccount == null) {
logger.error("Existing account with the provided id {} was not found.", request.getAccountId());
@@ -185,12 +204,17 @@ public class AccountRepositoryAdditionalImpl implements AccountRepositoryAdditio
WriteResult wr = null;
try {
try {
- updateAccountData(batchOperations, request, existingAccount, timeUpdated);
+ updateAccountData(sagaTransaction, request, existingAccount, timeUpdated);
} catch (DateTimeException e) {
logger.error("Exception with birthday date while updating account with id {}", request.getAccountId());
return null;
}
- wr = batchOperations.execute();
+ wr = sagaTransaction.execute();
+ if (!erlangAccountBridge.updateAccount(existingAccount)) {
+ logger.error("Internal error with erlang");
+ sagaTransaction.rollBack();
+ return null;
+ }
} catch (IllegalArgumentException | IllegalStateException e) {
logger.error("Exception while updating account with id {}.", request.getAccountId());
logger.debug("Exception while updating account: {}.", e.getMessage());
@@ -208,7 +232,7 @@ public class AccountRepositoryAdditionalImpl implements AccountRepositoryAdditio
}
public boolean deleteAccount(UUID accountId) {
- CassandraBatchOperations batchOperations = cassandraTemplate.batchOps();
+ Transaction sagaTransaction = new SagaTransaction(cassandraTemplate);
Account existingAccount = accountRepository.findByAccountId(accountId);
if (!doExistAccountAndProfileToDelete(accountId)) {
return false;
@@ -224,23 +248,34 @@ public class AccountRepositoryAdditionalImpl implements AccountRepositoryAdditio
boolean alsoDeleteProfile = existingAccountsForProfile.size() == 1;
WriteResult wr = null;
try {
- deleteAccountData(batchOperations, existingAccount);
- deleteProfileByAuthenticationProvider(batchOperations, existingAccount.getProfileId(),
- AuthenticationProvider.createAuthenticationProviderFromStringsWithDefaultSearchableOption(
- existingAccount.getAuthenticationProviderType(),
- existingAccount.getAuthenticationProvider()));
+ deleteAccountData(sagaTransaction, existingAccount);
if (alsoDeleteProfile) {
- if (existingProfile.getAuthenticationProviders() != null) {
- deleteAuthenticationProvidersFromProfile(batchOperations, existingProfile.getProfileId(),
+ if (existingProfile.getAuthenticationProviders() != null
+ && existingProfile.getAuthenticationProviders().size() > 0) {
+ deleteAuthenticationProvidersFromProfile(sagaTransaction, existingProfile.getProfileId(),
existingProfile.getAuthenticationProviders());
}
- deleteProfileData(batchOperations, existingProfile);
+ deleteProfileData(sagaTransaction, existingProfile);
} else {
- if (!removeCreationProvider(batchOperations, existingAccount, existingProfile)) {
+ if (existingAccount.getAuthenticationProviderType() != null
+ && existingAccount.getAuthenticationProvider() != null) {
+ deleteProfileByAuthenticationProvider(sagaTransaction, existingAccount.getProfileId(),
+ AuthenticationProvider.createAuthenticationProviderFromStringsWithDefaultSearchableOption(
+ existingAccount.getAuthenticationProviderType(),
+ existingAccount.getAuthenticationProvider()));
+ }
+ if (!removeCreationProvider(sagaTransaction, existingAccount, existingProfile)) {
+ logger.error("Error deleting account {}", existingAccount.getAccountId());
return false;
}
}
- wr = batchOperations.execute();
+ wr = sagaTransaction.execute();
+
+ if (!erlangAccountBridge.deleteAccount(existingProfile.getProfileId(), accountId)) {
+ logger.error("Internal error with erlang");
+ sagaTransaction.rollBack();
+ return false;
+ }
} catch (IllegalArgumentException | IllegalStateException e) {
logger.error("Exception while deleting account: {}.", e.getMessage());
return false;
@@ -265,14 +300,22 @@ public class AccountRepositoryAdditionalImpl implements AccountRepositoryAdditio
// update authentication providers of the profile by removing the one of the deleted account (if not
// already manually removed by the user)
Long timeUpdated = Instant.now().toEpochMilli();
- if (existingProfile.getAuthenticationProviders() != null) {
+ if (existingProfile.getAuthenticationProviders() != null
+ && existingProfile.getAuthenticationProviders().size() > 0) {
Set existingAuthenticationProvidersSet = new HashSet(
existingProfile.getAuthenticationProviders());
+ if (existingAccount.getAuthenticationProviderType() == null
+ && existingAccount.getAuthenticationProvider() == null) {
+ // The creation provider is already deleted
+ return true;
+ }
Optional existingCreationProviderToDelete = AuthenticationProvider
.foundExistingAuthenticationProviderByTypeAndValue(existingAccount.getAuthenticationProviderType(),
existingAccount.getAuthenticationProvider(), existingAuthenticationProvidersSet);
if (!existingCreationProviderToDelete.isPresent()) {
- logger.error("Error removing creation provider from account {}.", existingAccount.getAccountId());
+ logger.error(
+ "The creation provider for account {} was not found in the list of the profile's authentication providers.",
+ existingAccount.getAccountId());
return false;
}
@@ -283,6 +326,7 @@ public class AccountRepositoryAdditionalImpl implements AccountRepositoryAdditio
if (existingAuthenticationProvidersSet.size() > 0) {
updateAuthProvidersInProfileWhenDeletingAccount(batchOperations, existingProfile,
existingAuthenticationProvidersSet, timeUpdated);
+ return true;
} else {
logger.error(
"Error deleting account. At least one authentication provider should exist in profile: {}.",
@@ -290,8 +334,11 @@ public class AccountRepositoryAdditionalImpl implements AccountRepositoryAdditio
return false;
}
}
+ logger.error("Error removing creation provider from account {}.", existingAccount.getAccountId());
+ return false;
}
- return true;
+ logger.error("No authentication providers found for profile {}", existingProfile.getProfileId());
+ return false;
}
private void updateAccountData(CassandraBatchOperations batchOps, UpdateAccountRequest request,
@@ -414,8 +461,9 @@ public class AccountRepositoryAdditionalImpl implements AccountRepositoryAdditio
WriteResult wr = null;
try {
- deleteProfileAccountsWhenDeletingProfile(profileId, batchOperations, existingAccountsForProfile);
- if (existingProfile.getAuthenticationProviders() != null) {
+ deleteProfileAccountsWhenDeletingProfile(batchOperations, existingAccountsForProfile);
+ if (existingProfile.getAuthenticationProviders() != null
+ && existingProfile.getAuthenticationProviders().size() > 0) {
deleteAuthenticationProvidersFromProfile(batchOperations, existingProfile.getProfileId(),
existingProfile.getAuthenticationProviders());
}
@@ -432,15 +480,11 @@ public class AccountRepositoryAdditionalImpl implements AccountRepositoryAdditio
return Optional.of(Cause.ERROR_DELETING_PROFILE);
}
- private void deleteProfileAccountsWhenDeletingProfile(UUID profileId, CassandraBatchOperations batchOperations,
+ private void deleteProfileAccountsWhenDeletingProfile(CassandraBatchOperations batchOperations,
List existingAccountsForProfile) {
for (AccountByProfileId accountByProfileId : existingAccountsForProfile) {
Account existingAccount = accountRepository.findByAccountId(accountByProfileId.getAccountId());
deleteAccountData(batchOperations, existingAccount);
- deleteProfileByAuthenticationProvider(batchOperations, profileId,
- AuthenticationProvider.createAuthenticationProviderFromStringsWithDefaultSearchableOption(
- existingAccount.getAuthenticationProviderType(),
- existingAccount.getAuthenticationProvider()));
}
}
@@ -454,6 +498,20 @@ public class AccountRepositoryAdditionalImpl implements AccountRepositoryAdditio
return false;
}
+ public boolean accountIdAlreadyUsed(UUID accountId) {
+ if (accountRepository.findByAccountId(accountId) != null) {
+ return true;
+ }
+ return false;
+ }
+
+ public boolean profileIdAlreadyUsed(UUID profileId) {
+ if (profileRepository.findByProfileId(profileId) != null) {
+ return true;
+ }
+ return false;
+ }
+
@Override
public boolean addAuthenticationProvider(UUID profileId, AuthenticationProvider authProvider) {
BatchStatement batch = new BatchStatement();
@@ -747,6 +805,68 @@ public class AccountRepositoryAdditionalImpl implements AccountRepositoryAdditio
return false;
}
+ @Override
+ public boolean updateSearchableOption(UUID profileId, String authProviderType, String authProvider,
+ SearchableOption searchableOption) {
+
+ if (!haveNeededSearchableOptionData(profileId, authProviderType, authProvider, searchableOption)) {
+ return false;
+ }
+
+ Profile existingProfile = profileRepository.findByProfileId(profileId);
+ if (existingProfile == null) {
+ logger.error(
+ "Error updating searchable option in profile {}. Existing profile with the provided id {} was not found.",
+ profileId);
+ return false;
+ }
+
+ ProfileByAuthenticationProvider existingProfileByAuthenticationProvider = profileByAuthenticationProviderRepository
+ .findByAuthenticationProviderAndAuthenticationProviderType(authProvider, authProviderType);
+ if (existingProfileByAuthenticationProvider == null) {
+ logger.error(
+ "Error updating searchable option in profileByAuthenticationProvider. Existing profileByAuthenticationProvider for {}:{} was not found.",
+ authProviderType, authProvider);
+ return false;
+ }
+
+ CassandraBatchOperations batchOperations = cassandraTemplate.batchOps();
+ WriteResult wr = null;
+ try {
+ updateSearchableInProfileByAuthenticationProvider(batchOperations, existingProfileByAuthenticationProvider,
+ searchableOption);
+ if (!updateSearchableInProfile(batchOperations, existingProfile, authProviderType, authProvider,
+ searchableOption)) {
+ return false;
+ }
+ wr = batchOperations.execute();
+ } catch (IllegalArgumentException | IllegalStateException e) {
+ logger.debug("Exception while updating searchable option for auth provider {}:{} in profile{}: {}.",
+ authProviderType, authProvider, profileId, e.getMessage());
+ }
+ if (wr != null && wr.wasApplied()) {
+ logger.info("Successfully updated searchable option in profile {}.", profileId);
+ return true;
+ }
+ return false;
+ }
+
+ private boolean haveNeededSearchableOptionData(UUID profileId, String authProviderType, String authProvider,
+ SearchableOption searchableOption) {
+ if (authProviderType == null || authProvider == null || authProviderType.isEmpty() || authProvider.isEmpty()) {
+ logger.error(
+ "Error updating searchable option in profile {}. Auth provider info (type and/or identifier) is missing.",
+ profileId);
+ return false;
+ }
+ if (searchableOption == null) {
+ logger.error("Error updating searchable option {}:{} in profile {}. Can not use null for searchableOption.",
+ authProviderType, authProvider, profileId);
+ return false;
+ }
+ return true;
+ }
+
@Override
public Optional getAccountByLoginOption(AuthenticationProvider loginOption)
throws IncorrectAccountCountException {
@@ -887,11 +1007,12 @@ public class AccountRepositoryAdditionalImpl implements AccountRepositoryAdditio
CassandraBatchOperations batchOperations = cassandraTemplate.batchOps();
WriteResult wr;
try {
- setDefaultSearchableInProfileByAuthenticationProvider(batchOperations,
- profilesByAuthenticationProvider.get(i));
- if (!setDefaultSearchableInProfile(batchOperations, profileToUpdate,
+ updateSearchableInProfileByAuthenticationProvider(batchOperations,
+ profilesByAuthenticationProvider.get(i), SearchableOption.SEARCH_ENABLED);
+ if (!updateSearchableInProfile(batchOperations, profileToUpdate,
profilesByAuthenticationProvider.get(i).getAuthenticationProviderType(),
- profilesByAuthenticationProvider.get(i).getAuthenticationProvider())) {
+ profilesByAuthenticationProvider.get(i).getAuthenticationProvider(),
+ SearchableOption.SEARCH_ENABLED)) {
logger.error(
"Error replacing null with default searchable option for profile {}: auth provider {}:{}.",
profilesByAuthenticationProvider.get(i).getProfileId(),
@@ -915,14 +1036,14 @@ public class AccountRepositoryAdditionalImpl implements AccountRepositoryAdditio
}
}
- private void setDefaultSearchableInProfileByAuthenticationProvider(CassandraBatchOperations batchOps,
- ProfileByAuthenticationProvider profileByAuthenticationProvider) {
- profileByAuthenticationProvider.setSearchable(Boolean.TRUE);
+ private void updateSearchableInProfileByAuthenticationProvider(CassandraBatchOperations batchOps,
+ ProfileByAuthenticationProvider profileByAuthenticationProvider, SearchableOption searchableOption) {
+ profileByAuthenticationProvider.setSearchable(AuthenticationProvider.isSearchDisabled(searchableOption));
batchOps.update(profileByAuthenticationProvider);
}
- private boolean setDefaultSearchableInProfile(CassandraBatchOperations batchOps, Profile profileToUpdate,
- String authProviderType, String authProvider) {
+ private boolean updateSearchableInProfile(CassandraBatchOperations batchOps, Profile profileToUpdate,
+ String authProviderType, String authProvider, SearchableOption searchableOption) {
Set authProviderSet = profileToUpdate.getAuthenticationProviders();
if (authProviderSet == null) {
logger.error("No existing auth providers found for profile {}.", profileToUpdate.getProfileId());
@@ -930,7 +1051,8 @@ public class AccountRepositoryAdditionalImpl implements AccountRepositoryAdditio
}
AuthenticationProvider authProviderToUpdate = AuthenticationProvider
- .createAuthenticationProviderWithSearchableOption(authProviderType, authProvider, Boolean.TRUE);
+ .createAuthenticationProviderWithSearchableOption(authProviderType, authProvider,
+ AuthenticationProvider.isSearchDisabled(searchableOption));
Optional currentAuthProvider = AuthenticationProvider
.foundExistingAuthenticationProviderByTypeAndValue(authProviderType, authProvider, authProviderSet);
if (!currentAuthProvider.isPresent()) {
diff --git a/src/main/java/biz/nynja/account/services/AccountServiceImpl.java b/src/main/java/biz/nynja/account/services/AccountServiceImpl.java
index ae63d023c7b8016a3c5cd227aacd77527cceb196..72542ac18d938c727e46fa841fa3ff562b2775ba 100644
--- a/src/main/java/biz/nynja/account/services/AccountServiceImpl.java
+++ b/src/main/java/biz/nynja/account/services/AccountServiceImpl.java
@@ -18,6 +18,8 @@ import org.lognet.springboot.grpc.GRpcService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import biz.nynja.account.configuration.AccountDataConfiguration;
+import biz.nynja.account.configuration.ProfileDataConfiguration;
import biz.nynja.account.grpc.AccountByAccountIdRequest;
import biz.nynja.account.grpc.AccountResponse;
import biz.nynja.account.grpc.AccountServiceGrpc;
@@ -50,6 +52,7 @@ import biz.nynja.account.grpc.SearchResultDetails;
import biz.nynja.account.grpc.StatusResponse;
import biz.nynja.account.grpc.UpdateAccountRequest;
import biz.nynja.account.grpc.UpdateAuthenticationProviderRequest;
+import biz.nynja.account.grpc.UpdateSearchableOptionRequest;
import biz.nynja.account.models.Account;
import biz.nynja.account.models.AccountByProfileId;
import biz.nynja.account.models.AccountByQrCode;
@@ -99,6 +102,7 @@ public class AccountServiceImpl extends AccountServiceGrpc.AccountServiceImplBas
private final AccountCreator accountCreator;
private final ProfileProvider profileProvider;
private final PermissionsValidator permissionsValidator;
+ private final ProfileDataConfiguration profileDataConfiguration;
public AccountServiceImpl(AccountRepositoryAdditional accountRepositoryAdditional,
ProfileRepository profileRepository,
@@ -107,7 +111,7 @@ public class AccountServiceImpl extends AccountServiceGrpc.AccountServiceImplBas
AccountByUsernameRepository accountByUsernameRepository, AccountProvider accountProvider,
AccountByProfileIdRepository accountByProfileIdRepository, PhoneNumberNormalizer phoneNumberNormalizer,
AccountCreator accountCreator, ProfileProvider profileProvider,
- PermissionsValidator permissionsValidator) {
+ PermissionsValidator permissionsValidator, ProfileDataConfiguration profileDataConfiguration) {
this.accountRepositoryAdditional = accountRepositoryAdditional;
this.profileRepository = profileRepository;
this.profileByAutheticationProviderRepository = profileByAutheticationProviderRepository;
@@ -119,6 +123,7 @@ public class AccountServiceImpl extends AccountServiceGrpc.AccountServiceImplBas
this.accountCreator = accountCreator;
this.profileProvider = profileProvider;
this.permissionsValidator = permissionsValidator;
+ this.profileDataConfiguration = profileDataConfiguration;
}
@Override
@@ -420,7 +425,7 @@ public class AccountServiceImpl extends AccountServiceGrpc.AccountServiceImplBas
@Permitted(role = RoleConstants.USER)
public void getAccountByAccountId(AccountByAccountIdRequest request,
StreamObserver responseObserver) {
- logger.info("Getting accounts by account id: {}", request.getAccountId());
+ logger.info("Getting account by account id: {}", request.getAccountId());
if ((request.getAccountId() == null) || (request.getAccountId().isEmpty())) {
logAndBuildGrpcAccountResponse(responseObserver, AccountResponse.newBuilder(), "Missing account id", "",
@@ -471,7 +476,7 @@ public class AccountServiceImpl extends AccountServiceGrpc.AccountServiceImplBas
public void completePendingAccountCreation(CompletePendingAccountCreationRequest request,
StreamObserver responseObserver) {
- logger.info("Complete pending account creation...");
+ logger.info("Complete pending account creation for account id {} ...", request.getAccountId());
logger.debug("Complete pending account creation...: {} ...", request);
AccountResponse response = accountCreator.retrieveCompletePendingAccountResponse(request);
@@ -492,6 +497,12 @@ public class AccountServiceImpl extends AccountServiceGrpc.AccountServiceImplBas
Cause.MISSING_ACCOUNT_ID);
return;
}
+ if (!util.isValidUuid(request.getAccountId())) {
+ logAndBuildGrpcAccountResponse(responseObserver, AccountResponse.newBuilder(), "Invalid account id: {}",
+ request.getAccountId(), Cause.INVALID_ACCOUNT_ID);
+ return;
+ }
+
if (!permissionsValidator.isRpcAllowed(request.getAccountId())) {
logAndBuildGrpcAccountResponse(responseObserver, AccountResponse.newBuilder(), "Can not update account {}.",
request.getAccountId(), Cause.ERROR_PERMISSION_DENIED);
@@ -538,17 +549,17 @@ public class AccountServiceImpl extends AccountServiceGrpc.AccountServiceImplBas
Cause.MISSING_ACCOUNT_ID);
return;
}
- if (!permissionsValidator.isRpcAllowed(request.getAccountId())) {
- logAndBuildGrpcStatusResponse(responseObserver, StatusResponse.newBuilder(), "Can not delete account {}.",
- request.getAccountId(), Cause.ERROR_PERMISSION_DENIED);
- return;
- }
-
if (!util.isValidUuid(request.getAccountId())) {
+ logger.info("Can not delete account. Invalid account id: {}", request.getAccountId());
logAndBuildGrpcStatusResponse(responseObserver, StatusResponse.newBuilder(), "Invalid account id: {}",
request.getAccountId(), Cause.INVALID_ACCOUNT_ID);
return;
}
+ if (!permissionsValidator.isRpcAllowed(request.getAccountId())) {
+ logAndBuildGrpcStatusResponse(responseObserver, StatusResponse.newBuilder(), "Can not delete account {}.",
+ request.getAccountId(), Cause.ERROR_PERMISSION_DENIED);
+ return;
+ }
boolean wasAccountDeleted = accountRepositoryAdditional.deleteAccount(UUID.fromString(request.getAccountId()));
if (wasAccountDeleted) {
@@ -608,6 +619,11 @@ public class AccountServiceImpl extends AccountServiceGrpc.AccountServiceImplBas
Cause.MISSING_PROFILE_ID);
return;
}
+ if (!util.isValidUuid(request.getProfileId())) {
+ logAndBuildGrpcStatusResponse(responseObserver, StatusResponse.newBuilder(), "Invalid profile id: {}",
+ request.getProfileId(), Cause.INVALID_PROFILE_ID);
+ return;
+ }
List existingAccountsForProfile = accountByProfileIdRepository
.findAllByProfileId(UUID.fromString(request.getProfileId()));
@@ -646,6 +662,13 @@ public class AccountServiceImpl extends AccountServiceGrpc.AccountServiceImplBas
return;
}
+ // Make sure there will be no more than providers in this profile
+ if(profile.getAuthenticationProviders().size() >= profileDataConfiguration.getMaxAuthenticationprovidersPerProfile()) {
+ logAndBuildGrpcStatusResponse(responseObserver, StatusResponse.newBuilder(),
+ "Max number of authentication providers reached for profile id {}.", request.getProfileId(), Cause.MAX_PROVIDERS_PER_PROFILE_REACHED);
+ return;
+ }
+
// Make sure that the requested authentication provider is not already used in the system.
ProfileByAuthenticationProvider profileByAuthProvider = profileByAutheticationProviderRepository
.findByAuthenticationProviderAndAuthenticationProviderType(
@@ -690,6 +713,11 @@ public class AccountServiceImpl extends AccountServiceGrpc.AccountServiceImplBas
validationResult.get().getValue(), "", validationResult.get().getKey());
return;
}
+ if (!util.isValidUuid(request.getAccountId())) {
+ logAndBuildGrpcStatusResponse(responseObserver, StatusResponse.newBuilder(), "Invalid account id: {}",
+ request.getAccountId(), Cause.INVALID_ACCOUNT_ID);
+ return;
+ }
if (!permissionsValidator.isRpcAllowed(request.getAccountId())) {
logAndBuildGrpcStatusResponse(responseObserver, StatusResponse.newBuilder(),
@@ -743,6 +771,11 @@ public class AccountServiceImpl extends AccountServiceGrpc.AccountServiceImplBas
validationResult.get().getRight(), validationResult.get().getLeft());
return;
}
+ if (!util.isValidUuid(request.getAccountId())) {
+ logAndBuildGrpcStatusResponse(responseObserver, StatusResponse.newBuilder(), "Invalid account id: {}",
+ request.getAccountId(), Cause.INVALID_ACCOUNT_ID);
+ return;
+ }
if (!permissionsValidator.isRpcAllowed(request.getAccountId())) {
logAndBuildGrpcStatusResponse(responseObserver, StatusResponse.newBuilder(),
@@ -783,6 +816,11 @@ public class AccountServiceImpl extends AccountServiceGrpc.AccountServiceImplBas
Cause.MISSING_PROFILE_ID);
return;
}
+ if (!util.isValidUuid(request.getProfileId())) {
+ logAndBuildGrpcStatusResponse(responseObserver, StatusResponse.newBuilder(), "Invalid profile id: {}",
+ request.getProfileId(), Cause.INVALID_PROFILE_ID);
+ return;
+ }
if (request.getAuthenticationProvider().getAuthenticationTypeValue() == 0) {
logAndBuildGrpcStatusResponse(responseObserver, StatusResponse.newBuilder(), "Missing auth provider type.",
@@ -924,8 +962,8 @@ public class AccountServiceImpl extends AccountServiceGrpc.AccountServiceImplBas
@Permitted(role = RoleConstants.USER)
public void editContactInfoForAccount(EditContactInfoRequest request,
StreamObserver responseObserver) {
- logger.info("Removing contact info from account requested.");
- logger.debug("Removing contact info from account requested: {}", request);
+ logger.info("Editing contact info for account requested.");
+ logger.debug("Editing contact info for account requested: {}", request);
Optional> validationResultEditContactInfo = account.validateEditContactInfoRequest(
request.getAccountId(), request.getOldContactInfo(), request.getEditedContactInfo());
@@ -935,6 +973,11 @@ public class AccountServiceImpl extends AccountServiceGrpc.AccountServiceImplBas
validationResultEditContactInfo.get().getKey());
return;
}
+ if (!util.isValidUuid(request.getAccountId())) {
+ logAndBuildGrpcStatusResponse(responseObserver, StatusResponse.newBuilder(), "Invalid account id: {}",
+ request.getAccountId(), Cause.INVALID_ACCOUNT_ID);
+ return;
+ }
if (!permissionsValidator.isRpcAllowed(request.getAccountId())) {
logAndBuildGrpcStatusResponse(responseObserver, StatusResponse.newBuilder(),
@@ -1017,6 +1060,11 @@ public class AccountServiceImpl extends AccountServiceGrpc.AccountServiceImplBas
validationResultUpdateAuthProvider.get().getKey());
return;
}
+ if (!util.isValidUuid(request.getProfileId())) {
+ logAndBuildGrpcStatusResponse(responseObserver, StatusResponse.newBuilder(), "Invalid profile id: {}.",
+ request.getProfileId(), validationResultUpdateAuthProvider.get().getKey());
+ return;
+ }
List existingAccountsForProfile = accountByProfileIdRepository
.findAllByProfileId(UUID.fromString(request.getProfileId()));
@@ -1184,4 +1232,50 @@ public class AccountServiceImpl extends AccountServiceGrpc.AccountServiceImplBas
responseObserver.onCompleted();
}
+ @Override
+ @PerformPermissionCheck
+ @Permitted(role = RoleConstants.ACCOUNT_ADMIN)
+ @Permitted(role = RoleConstants.USER)
+ public void updateSearchableOption(UpdateSearchableOptionRequest request,
+ StreamObserver responseObserver) {
+ logger.info("Updating searchable option for profile {} requested.", request.getProfileId());
+ logger.debug("Updating searchable option for profile requested: {}", request);
+
+ Validation validation = authenticationProvider.validateUpdateSearchableOptionRequest(request.getProfileId(),
+ request.getAuthenticationType(), request.getAuthenticationIdentifier());
+ if (validation.hasErrors()) {
+ logAndBuildGrpcStatusResponse(responseObserver, StatusResponse.newBuilder(), validation.getErrorMessage(),
+ "", validation.getCause().get());
+ return;
+ }
+ if (!util.isValidUuid(request.getProfileId())) {
+ logAndBuildGrpcStatusResponse(responseObserver, StatusResponse.newBuilder(), "Invalid profile id: {}",
+ request.getProfileId(), Cause.INVALID_PROFILE_ID);
+ return;
+ }
+
+ List existingAccountsForProfile = accountByProfileIdRepository
+ .findAllByProfileId(UUID.fromString(request.getProfileId()));
+
+ if (!permissionsValidator.isRpcAllowed(existingAccountsForProfile)) {
+ logAndBuildGrpcStatusResponse(responseObserver, StatusResponse.newBuilder(),
+ "Can not update searchable option for authentication provider in profile {}.",
+ request.getProfileId(), Cause.ERROR_PERMISSION_DENIED);
+ return;
+ }
+
+ if (!accountRepositoryAdditional.updateSearchableOption(UUID.fromString(request.getProfileId()),
+ request.getAuthenticationType(), request.getAuthenticationIdentifier(), request.getSearchOption())) {
+ logAndBuildGrpcStatusResponse(responseObserver, StatusResponse.newBuilder(),
+ "Can not update searchable option for authentication provider in profile {}.",
+ request.getProfileId(), Cause.ERROR_UPDATING_SEARCHABLE_OPTION);
+ return;
+ }
+
+ logger.info("Successfully updated authentication provider {}:{} with searchable option {} in profile {}",
+ request.getAuthenticationType(), request.getAuthenticationIdentifier(), request.getSearchOption(),
+ request.getProfileId());
+ responseObserver.onNext(StatusResponse.newBuilder().setStatus("SUCCESS").build());
+ responseObserver.onCompleted();
+ }
}
diff --git a/src/main/java/biz/nynja/account/services/decomposition/AccountCreator.java b/src/main/java/biz/nynja/account/services/decomposition/AccountCreator.java
index d4d507d174c6c80839ea4b2896b47529824e4d46..9123af320b46845f216fd640adba58e85373a228 100644
--- a/src/main/java/biz/nynja/account/services/decomposition/AccountCreator.java
+++ b/src/main/java/biz/nynja/account/services/decomposition/AccountCreator.java
@@ -80,6 +80,17 @@ public class AccountCreator {
pendingAccount.setAccountId(UUID.randomUUID());
pendingAccount.setProfileId(UUID.randomUUID());
+ if (pendingAccountRepository.findByAccountId(pendingAccount.getAccountId()) != null
+ || accountRepositoryAdditional.accountIdAlreadyUsed(pendingAccount.getAccountId())) {
+ logger.error("Can not create pending account. The account id {} is already used.", pendingAccount.getAccountId());
+ return CreatePendingAccountResponse.newBuilder()
+ .setError(ErrorResponse.newBuilder().setCause(Cause.INTERNAL_SERVER_ERROR)).build();
+ }
+ if (accountRepositoryAdditional.profileIdAlreadyUsed(pendingAccount.getProfileId())) {
+ logger.error("Can not create pending account. The profile id {} is already used.", pendingAccount.getProfileId());
+ return CreatePendingAccountResponse.newBuilder()
+ .setError(ErrorResponse.newBuilder().setCause(Cause.INTERNAL_SERVER_ERROR)).build();
+ }
pendingAccount.setCreationTimestamp(Instant.now().toEpochMilli());
try {
diff --git a/src/main/java/biz/nynja/account/services/decomposition/AccountProvider.java b/src/main/java/biz/nynja/account/services/decomposition/AccountProvider.java
index e912b9c96fbb62bc4240ac0b7448110ce9231c19..0e5bd087ca0b14507469a98646be31415c51f53a 100644
--- a/src/main/java/biz/nynja/account/services/decomposition/AccountProvider.java
+++ b/src/main/java/biz/nynja/account/services/decomposition/AccountProvider.java
@@ -164,7 +164,6 @@ public class AccountProvider {
}
public Optional getAccountByAccountId(AccountByAccountIdRequest request) {
- logger.info("Getting accounts by account id: {}", request.getAccountId());
Account account = accountRepository.findByAccountId(UUID.fromString(request.getAccountId()));
if (account == null || account.getAccountId() == null) {
return Optional.empty();
diff --git a/src/main/java/biz/nynja/account/services/decomposition/ProfileProvider.java b/src/main/java/biz/nynja/account/services/decomposition/ProfileProvider.java
index 9c138bfb73fc3f8bb0f6de6fe0d9e367d40d70c9..a4436e304c112ac928ae8fd6bb181172bde898da 100644
--- a/src/main/java/biz/nynja/account/services/decomposition/ProfileProvider.java
+++ b/src/main/java/biz/nynja/account/services/decomposition/ProfileProvider.java
@@ -24,7 +24,6 @@ public class ProfileProvider {
}
public Optional getProfileByProfileId(ProfileByProfileIdRequest request) {
- logger.info("Getting profile by profile id: {}", request.getProfileId());
Profile profile = profileRepository.findByProfileId(UUID.fromString(request.getProfileId()));
if (profile == null || profile.getProfileId() == null) {
return Optional.empty();
diff --git a/src/main/java/biz/nynja/account/services/erlang/ErlangAccountBridge.java b/src/main/java/biz/nynja/account/services/erlang/ErlangAccountBridge.java
index 803db0ed0bfad8c77cd95b663424be7e786790a9..f30b96de8e78a525154c2a33ca21fdac1631e159 100644
--- a/src/main/java/biz/nynja/account/services/erlang/ErlangAccountBridge.java
+++ b/src/main/java/biz/nynja/account/services/erlang/ErlangAccountBridge.java
@@ -6,18 +6,18 @@ package biz.nynja.account.services.erlang;
import biz.nynja.account.models.Account;
import biz.nynja.account.models.Profile;
+import java.util.List;
import java.util.UUID;
-
public interface ErlangAccountBridge {
boolean createProfile(Profile profile, Account defaultAccount);
- boolean deleteProfile(UUID profileId);
+ boolean deleteProfile(UUID profileId, List accountsIds);
boolean createAccount(Account account);
boolean updateAccount(Account account);
- boolean deleteAccount(UUID accountId);
+ boolean deleteAccount(UUID profileId, UUID accountId);
}
diff --git a/src/main/java/biz/nynja/account/services/erlang/ErlangAccountHttpBridge.java b/src/main/java/biz/nynja/account/services/erlang/ErlangAccountHttpBridge.java
index aa88d2f435dbf10bc6552b1611965abf54070b8b..24574f35ea38ecc756361d9b13a652bfef73f863 100644
--- a/src/main/java/biz/nynja/account/services/erlang/ErlangAccountHttpBridge.java
+++ b/src/main/java/biz/nynja/account/services/erlang/ErlangAccountHttpBridge.java
@@ -1,11 +1,12 @@
/**
- * Copyright (C) 2018 Nynja Inc. All rights reserved.
+ * Copyright (C) 2018 Nynja Inc. All rights reserved.
*/
package biz.nynja.account.services.erlang;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
+import java.util.List;
import java.util.UUID;
import org.slf4j.Logger;
@@ -22,13 +23,11 @@ import biz.nynja.account.models.Account;
import biz.nynja.account.models.Profile;
import biz.nynja.account.services.erlang.connector.HttpClient;
-
-
@Service
// TODO: 11/19/2018 change boolean response when ENC will implement return error part
public class ErlangAccountHttpBridge implements ErlangAccountBridge {
private static final Logger logger = LoggerFactory.getLogger(ErlangAccountHttpBridge.class);
-
+ private static String PROTOCOL = "https://";
private final JsonParser parser = new JsonParser();
private final ObjectMapper mapper = new ObjectMapper();
@@ -39,6 +38,7 @@ public class ErlangAccountHttpBridge implements ErlangAccountBridge {
public ErlangAccountHttpBridge(HttpClient httpClient, ErlangBridgeConfiguration erlangBridgeConfiguration)
throws MalformedURLException {
+
this.httpClient = httpClient;
this.erlangBridgeConfiguration = erlangBridgeConfiguration;
if (!erlangBridgeConfiguration.isEnabled()) {
@@ -46,9 +46,9 @@ public class ErlangAccountHttpBridge implements ErlangAccountBridge {
profileURL = null;
return;
}
- accountURL = new URL(
+ accountURL = new URL(PROTOCOL +
erlangBridgeConfiguration.getHost() + ":" + erlangBridgeConfiguration.getPort() + "/swm/account");
- profileURL = new URL(
+ profileURL = new URL(PROTOCOL +
erlangBridgeConfiguration.getHost() + ":" + erlangBridgeConfiguration.getPort() + "/swm/profile");
}
@@ -66,7 +66,7 @@ public class ErlangAccountHttpBridge implements ErlangAccountBridge {
}
@Override
- public boolean deleteProfile(UUID profileId) {
+ public boolean deleteProfile(UUID profileId, List accountsIds) {
if (!erlangBridgeConfiguration.isEnabled())
return true;
JsonObject profileObject = new JsonObject();
@@ -77,7 +77,7 @@ public class ErlangAccountHttpBridge implements ErlangAccountBridge {
return false;
}
- return false;
+ return true;
}
@Override
@@ -99,7 +99,7 @@ public class ErlangAccountHttpBridge implements ErlangAccountBridge {
}
@Override
- public boolean deleteAccount(UUID accountId) {
+ public boolean deleteAccount(UUID profileId, UUID accountId) {
if (!erlangBridgeConfiguration.isEnabled())
return true;
JsonObject accountObject = new JsonObject();
@@ -109,7 +109,7 @@ public class ErlangAccountHttpBridge implements ErlangAccountBridge {
} catch (IOException e) {
return false;
}
- return false;
+ return true;
}
private JsonObject prepareProfileJsonObject(Profile profile, Account account) {
diff --git a/src/main/java/biz/nynja/account/services/erlang/ErlangAccountMqttBridge.java b/src/main/java/biz/nynja/account/services/erlang/ErlangAccountMqttBridge.java
new file mode 100644
index 0000000000000000000000000000000000000000..3d0832c20d3c8ecf13cf99f0ae15024249800a99
--- /dev/null
+++ b/src/main/java/biz/nynja/account/services/erlang/ErlangAccountMqttBridge.java
@@ -0,0 +1,128 @@
+/**
+ * Copyright (C) 2018 Nynja Inc. All rights reserved.
+ */
+package biz.nynja.account.services.erlang;
+
+import java.net.MalformedURLException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+import org.springframework.context.annotation.Primary;
+import org.springframework.stereotype.Component;
+
+import biz.nynja.account.configuration.ErlangBridgeConfiguration;
+import biz.nynja.account.models.Account;
+import biz.nynja.account.models.Profile;
+import biz.nynja.account.services.erlang.interceptor.TokenInterceptorConstants;
+import biz.nynja.bridge.grpc.AccountBridgeGrpc;
+import biz.nynja.bridge.grpc.AccountData;
+import biz.nynja.bridge.grpc.BridgeSuccessResponse;
+import biz.nynja.bridge.grpc.ProfileData;
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.Metadata;
+import io.grpc.stub.MetadataUtils;
+
+@Component
+@Primary
+public class ErlangAccountMqttBridge implements ErlangAccountBridge {
+
+ private final ErlangBridgeConfiguration erlangBridgeConfiguration;
+
+ public ErlangAccountMqttBridge(ErlangBridgeConfiguration erlangBridgeConfiguration) throws MalformedURLException {
+ this.erlangBridgeConfiguration = erlangBridgeConfiguration;
+ }
+
+ @Override
+ public boolean createProfile(Profile profile, Account account) {
+
+ if (!erlangBridgeConfiguration.isEnabled())
+ return true;
+ ProfileData profileData = buildProfileData(profile, account);
+ // todo update after testing with real connection
+ BridgeSuccessResponse response = buildGrpcConnection().createProfile(profileData);
+ return true;
+ }
+
+ @Override
+ public boolean deleteProfile(UUID profileId, List accountsIds) {
+ if (!erlangBridgeConfiguration.isEnabled())
+ return true;
+ BridgeSuccessResponse response = buildGrpcConnection()
+ .deleteProfile(buildDeleteProfileData(profileId, (UUID[]) accountsIds.toArray()));
+ return true;
+ }
+
+ @Override
+ public boolean createAccount(Account account) {
+ if (!erlangBridgeConfiguration.isEnabled())
+ return true;
+ BridgeSuccessResponse response = buildGrpcConnection().createAccount(buildAccountData(account));
+
+ return true;
+ }
+
+ @Override
+ public boolean updateAccount(Account account) {
+ if (!erlangBridgeConfiguration.isEnabled())
+ return true;
+
+ return true;
+ }
+
+ @Override
+ public boolean deleteAccount(UUID profileId, UUID accountId) {
+ if (!erlangBridgeConfiguration.isEnabled())
+ return true;
+ BridgeSuccessResponse response = buildGrpcConnection()
+ .deleteAccount(buildDeleteProfileData(profileId, accountId));
+ return true;
+ }
+
+ private ProfileData buildProfileData(Profile profile, Account account) {
+ return ProfileData.newBuilder().setProfileId(profile.getProfileId().toString())
+ .setDefaultAccount(buildAccountData(account))
+ .setLastUpdateTimestamp(profile.getCreationTimestamp().toString()).build();
+ }
+
+ private AccountData buildAccountData(Account account) {
+ return AccountData.newBuilder().setAccountId(account.getAccountId().toString())
+ .setFirstName(account.getFirstName()).setLastName(account.getLastName())
+ .setProfileId(account.getProfileId().toString())
+ .setUsername(account.getUsername()).setAvatar(account.getAvatar())
+ .setLastUpdateTimestamp(
+ Objects.isNull(account.getLastUpdateTimestamp()) ? Long.toString(System.currentTimeMillis())
+ : account.getLastUpdateTimestamp().toString())
+ .build();
+ }
+
+ // Erlang protocol
+ private ProfileData buildDeleteProfileData(UUID profileId, UUID... accountsId) {
+ return ProfileData.newBuilder().setProfileId(profileId.toString())
+ .addAllAccountsIds(Arrays.stream(accountsId).map(UUID::toString).collect(Collectors.toList())).build();
+ }
+
+ private AccountBridgeGrpc.AccountBridgeBlockingStub buildGrpcConnection() {
+ ManagedChannel managedChannel = ManagedChannelBuilder.forAddress(erlangBridgeConfiguration.getHost(),
+ Integer.parseInt(erlangBridgeConfiguration.getPort())).usePlaintext().build();
+ AccountBridgeGrpc.AccountBridgeBlockingStub bridgeServiceBlockingStub = AccountBridgeGrpc
+ .newBlockingStub(managedChannel);
+ return MetadataUtils.attachHeaders(bridgeServiceBlockingStub, getHeaders());
+ }
+
+ /**
+ * Attaches the access token to the rpc header
+ *
+ * @return
+ * @throws InternalError
+ */
+ private Metadata getHeaders() throws InternalError {
+ Metadata headers = new Metadata();
+ Metadata.Key key = Metadata.Key.of("accessToken", Metadata.ASCII_STRING_MARSHALLER);
+ headers.put(key, "Bearer " + TokenInterceptorConstants.ACCESS_TOKEN_CTX.get());
+ return headers;
+ }
+}
diff --git a/src/main/java/biz/nynja/account/services/erlang/interceptor/TokenInterceptor.java b/src/main/java/biz/nynja/account/services/erlang/interceptor/TokenInterceptor.java
new file mode 100644
index 0000000000000000000000000000000000000000..67de571509ab08d80f99da93b900722d325cdaed
--- /dev/null
+++ b/src/main/java/biz/nynja/account/services/erlang/interceptor/TokenInterceptor.java
@@ -0,0 +1,19 @@
+/**
+ * Copyright (C) 2018 Nynja Inc. All rights reserved.
+ */
+package biz.nynja.account.services.erlang.interceptor;
+
+import io.grpc.*;
+import org.lognet.springboot.grpc.GRpcGlobalInterceptor;
+
+@GRpcGlobalInterceptor
+public class TokenInterceptor implements ServerInterceptor {
+
+ @Override
+ public ServerCall.Listener interceptCall(ServerCall serverCall, Metadata metadata,
+ ServerCallHandler serverCallHandler) {
+ String accessToken = metadata.get(TokenInterceptorConstants.ACCESS_TOKEN_METADATA);
+ Context ctx = Context.current().withValue(TokenInterceptorConstants.ACCESS_TOKEN_CTX, accessToken);
+ return Contexts.interceptCall(ctx, serverCall, metadata, serverCallHandler);
+ }
+}
diff --git a/src/main/java/biz/nynja/account/services/erlang/interceptor/TokenInterceptorConstants.java b/src/main/java/biz/nynja/account/services/erlang/interceptor/TokenInterceptorConstants.java
new file mode 100644
index 0000000000000000000000000000000000000000..5df55f124e6950e9266d867261ea6aefe2cf3f02
--- /dev/null
+++ b/src/main/java/biz/nynja/account/services/erlang/interceptor/TokenInterceptorConstants.java
@@ -0,0 +1,11 @@
+package biz.nynja.account.services.erlang.interceptor;
+
+import io.grpc.Context;
+import io.grpc.Metadata;
+
+import static io.grpc.Metadata.ASCII_STRING_MARSHALLER;
+
+public class TokenInterceptorConstants {
+ public static final Metadata.Key ACCESS_TOKEN_METADATA = Metadata.Key.of("accessToken", ASCII_STRING_MARSHALLER);
+ public static final Context.Key ACCESS_TOKEN_CTX = Context.key("accessToken");
+}
diff --git a/src/main/java/biz/nynja/account/validation/Validators.java b/src/main/java/biz/nynja/account/validation/Validators.java
index 930b836ad497429ef6461dc7f9655adf9fae24ca..179d49afcb6aaa776db4806750477d478f4d12b4 100644
--- a/src/main/java/biz/nynja/account/validation/Validators.java
+++ b/src/main/java/biz/nynja/account/validation/Validators.java
@@ -346,6 +346,41 @@ public class Validators {
}
}
+ public Validation validateUpdateSearchableOptionRequest(String profileId, String authenticationType,
+ String authenticationIdentifier) {
+ Validation validation = new Validation();
+ if (authenticationType == null || authenticationType.isEmpty()) {
+ validation.addError(
+ new ValidationError("Missing authentication provider type", Cause.MISSING_AUTH_PROVIDER_TYPE));
+ return validation;
+ }
+
+ if (!authenticationType.equals(AuthenticationType.EMAIL.name())
+ && !authenticationType.equals(AuthenticationType.PHONE.name())) {
+ validation.addError(
+ new ValidationError("Invalid authentication provider type", Cause.INVALID_AUTH_PROVIDER_TYPE));
+ return validation;
+ }
+
+ if (authenticationIdentifier == null || authenticationIdentifier.isEmpty()) {
+ validation.addError(
+ new ValidationError("Missing authentication provider identifier", Cause.MISSING_AUTH_PROVIDER_ID));
+ return validation;
+ }
+
+ if (profileId == null || profileId.isEmpty()) {
+ validation.addError(new ValidationError("Missing profile id", Cause.MISSING_PROFILE_ID));
+ return validation;
+ }
+
+ if (!isValidUuid(profileId)) {
+ validation.addError(new ValidationError("Invalid profile id", Cause.INVALID_PROFILE_ID));
+ return validation;
+ }
+
+ return validation;
+ }
+
private boolean isValidUuid(String id) {
return id.matches("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$");
}
diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml
index 5de0eb4131e96a65d07733a20701b83b0977a70c..4956ef2ba7debe2b9b755cf8708bf9b2330a8c44 100644
--- a/src/main/resources/application-dev.yml
+++ b/src/main/resources/application-dev.yml
@@ -37,10 +37,13 @@ pending-account:
account-data:
max-contact-info-of-type: 10
+profile-data:
+ max-authenticationproviders-per-profile: 20
+
erlang-bridge:
- enable: false;
- ip:
- port:
+ enabled: false
+ host: localhost
+ port: 6580
#Metrics related configurations
management:
diff --git a/src/main/resources/application-production.yml b/src/main/resources/application-production.yml
index e42ba70b961bb1c5d9c885988f45962254660012..178b0ec470491a24fd437ddb0f84d9023d3238bd 100644
--- a/src/main/resources/application-production.yml
+++ b/src/main/resources/application-production.yml
@@ -31,8 +31,11 @@ pending-account:
account-data:
max-contact-info-of-type: ${MAX_CONTACT_INFO_OF_TYPE:10}
+profile-data:
+max-authenticationproviders-per-profile: 20
+
erlang-bridge:
- enable: false;
+ enabled: ${ERLANG_ENABLED:false}
ip: ${ERLANG_IP}
port: ${ERLANG_PORT}
diff --git a/src/test/java/biz/nynja/account/services/AccountServiceTests.java b/src/test/java/biz/nynja/account/services/AccountServiceTests.java
index 116a08376a0eafb75287fbb477a30a63abc0a91a..0f0386d6f8bc2cf33c0566b5e814e28418fafc22 100644
--- a/src/test/java/biz/nynja/account/services/AccountServiceTests.java
+++ b/src/test/java/biz/nynja/account/services/AccountServiceTests.java
@@ -67,9 +67,11 @@ import biz.nynja.account.grpc.ProfileByProfileIdRequest;
import biz.nynja.account.grpc.ProfileResponse;
import biz.nynja.account.grpc.Role;
import biz.nynja.account.grpc.SearchResponse;
+import biz.nynja.account.grpc.SearchableOption;
import biz.nynja.account.grpc.StatusResponse;
import biz.nynja.account.grpc.UpdateAccountRequest;
import biz.nynja.account.grpc.UpdateAuthenticationProviderRequest;
+import biz.nynja.account.grpc.UpdateSearchableOptionRequest;
import biz.nynja.account.models.Account;
import biz.nynja.account.models.AccountByAuthenticationProvider;
import biz.nynja.account.models.AccountByProfileId;
@@ -2019,4 +2021,118 @@ public class AccountServiceTests extends GrpcServerTestBase {
assertNotNull("Reply should not be null", reply);
assertEquals(reply.getError().getCause(), Cause.INVALID_EMAIL);
}
+
+ @Test
+ public void testUpdateSearchableOptionPhoneOK() {
+ final UpdateSearchableOptionRequest request = UpdateSearchableOptionRequest.newBuilder()
+ .setProfileId(Util.PROFILE_ID.toString()).setAuthenticationIdentifier(Util.PHONE_PROVIDER)
+ .setAuthenticationType(AuthenticationType.PHONE.name())
+ .setSearchOption(SearchableOption.SEARCH_DISABLED).build();
+
+ given(accountRepositoryAdditional.updateSearchableOption(Mockito.any(UUID.class), Mockito.any(String.class),
+ Mockito.any(String.class), Mockito.any(SearchableOption.class))).willReturn(true);
+
+ given(profileRepository.findByProfileId(Util.PROFILE_ID)).willReturn(profile2AuthProviders);
+
+ given(profileByAutheticationProviderRepository
+ .findByAuthenticationProviderAndAuthenticationProviderType(Util.PHONE_PROVIDER, "PHONE"))
+ .willReturn(profileByAuthenticationProvider);
+
+ final StatusResponse reply = accountServiceBlockingStub.updateSearchableOption(request);
+ assertNotNull("Reply should not be null", reply);
+ assertEquals("SUCCESS", reply.getStatus());
+ }
+
+ @Test
+ public void testUpdateSearchableOptionEmailOK() {
+ final UpdateSearchableOptionRequest request = UpdateSearchableOptionRequest.newBuilder()
+ .setProfileId(Util.PROFILE_ID.toString()).setAuthenticationIdentifier(Util.EMAIL)
+ .setAuthenticationType(AuthenticationType.EMAIL.name())
+ .setSearchOption(SearchableOption.SEARCH_DISABLED).build();
+
+ given(accountRepositoryAdditional.updateSearchableOption(Mockito.any(UUID.class), Mockito.any(String.class),
+ Mockito.any(String.class), Mockito.any(SearchableOption.class))).willReturn(true);
+
+ given(profileRepository.findByProfileId(Util.PROFILE_ID)).willReturn(profile2AuthProviders);
+
+ given(profileByAutheticationProviderRepository
+ .findByAuthenticationProviderAndAuthenticationProviderType(Util.EMAIL, "EMAIL"))
+ .willReturn(profileByAuthenticationProvider);
+
+ final StatusResponse reply = accountServiceBlockingStub.updateSearchableOption(request);
+ assertNotNull("Reply should not be null", reply);
+ assertEquals("SUCCESS", reply.getStatus());
+ }
+
+ @Test
+ public void testUpdateSearchableOptionErrorUpdatingSearchableOption() {
+ final UpdateSearchableOptionRequest request = UpdateSearchableOptionRequest.newBuilder()
+ .setProfileId(Util.PROFILE_ID.toString()).setAuthenticationIdentifier(Util.PHONE_PROVIDER)
+ .setAuthenticationType(AuthenticationType.PHONE.name())
+ .setSearchOption(SearchableOption.SEARCH_DISABLED).build();
+
+ given(accountRepositoryAdditional.updateSearchableOption(Mockito.any(UUID.class), Mockito.any(String.class),
+ Mockito.any(String.class), Mockito.any(SearchableOption.class))).willReturn(false);
+
+ final StatusResponse reply = accountServiceBlockingStub.updateSearchableOption(request);
+ assertNotNull("Reply should not be null", reply);
+ assertEquals(reply.getError().getCause(), Cause.ERROR_UPDATING_SEARCHABLE_OPTION);
+ }
+
+ @Test
+ public void testUpdateSearchableOptionMissingAuthenticationProviderType() {
+ final UpdateSearchableOptionRequest request = UpdateSearchableOptionRequest.newBuilder()
+ .setProfileId(Util.PROFILE_ID.toString()).setAuthenticationIdentifier(Util.PHONE_PROVIDER)
+ .setSearchOption(SearchableOption.SEARCH_DISABLED).build();
+
+ final StatusResponse reply = accountServiceBlockingStub.updateSearchableOption(request);
+ assertNotNull("Reply should not be null", reply);
+ assertEquals(reply.getError().getCause(), Cause.MISSING_AUTH_PROVIDER_TYPE);
+ }
+
+ @Test
+ public void testUpdateSearchableOptionMissingAuthenticationProviderIdentifier() {
+ final UpdateSearchableOptionRequest request = UpdateSearchableOptionRequest.newBuilder()
+ .setProfileId(Util.PROFILE_ID.toString()).setAuthenticationType(AuthenticationType.PHONE.name())
+ .setSearchOption(SearchableOption.SEARCH_DISABLED).build();
+
+ final StatusResponse reply = accountServiceBlockingStub.updateSearchableOption(request);
+ assertNotNull("Reply should not be null", reply);
+ assertEquals(reply.getError().getCause(), Cause.MISSING_AUTH_PROVIDER_ID);
+ }
+
+ @Test
+ public void testUpdateSearchableOptionMissingProfileId() {
+ final UpdateSearchableOptionRequest request = UpdateSearchableOptionRequest.newBuilder()
+ .setAuthenticationIdentifier(Util.PHONE_PROVIDER).setAuthenticationType(AuthenticationType.PHONE.name())
+ .setSearchOption(SearchableOption.SEARCH_DISABLED).build();
+
+ final StatusResponse reply = accountServiceBlockingStub.updateSearchableOption(request);
+ assertNotNull("Reply should not be null", reply);
+ assertEquals(reply.getError().getCause(), Cause.MISSING_PROFILE_ID);
+ }
+
+ @Test
+ public void testUpdateSearchableOptionInvalidProfileId() {
+ final UpdateSearchableOptionRequest request = UpdateSearchableOptionRequest.newBuilder()
+ .setProfileId(Util.INVALID_PROFILE_ID).setAuthenticationIdentifier(Util.PHONE_PROVIDER)
+ .setAuthenticationType(AuthenticationType.PHONE.name())
+ .setSearchOption(SearchableOption.SEARCH_DISABLED).build();
+
+ final StatusResponse reply = accountServiceBlockingStub.updateSearchableOption(request);
+ assertNotNull("Reply should not be null", reply);
+ assertEquals(reply.getError().getCause(), Cause.INVALID_PROFILE_ID);
+ }
+
+ @Test
+ public void testUpdateSearchableOptionInvalidAuthenticationProviderType() {
+ final UpdateSearchableOptionRequest request = UpdateSearchableOptionRequest.newBuilder()
+ .setProfileId(Util.PROFILE_ID.toString()).setAuthenticationIdentifier("Facebook_provider")
+ .setAuthenticationType(AuthenticationType.FACEBOOK.name())
+ .setSearchOption(SearchableOption.SEARCH_DISABLED).build();
+
+ final StatusResponse reply = accountServiceBlockingStub.updateSearchableOption(request);
+ assertNotNull("Reply should not be null", reply);
+ assertEquals(reply.getError().getCause(), Cause.INVALID_AUTH_PROVIDER_TYPE);
+ }
}