diff --git a/src/main/java/biz/nynja/account/repositories/AccountRepositoryAdditional.java b/src/main/java/biz/nynja/account/repositories/AccountRepositoryAdditional.java index c6f039ded6af908b5e99180e2582a4636263996f..dd43e407a55c46dfc69069ab6be04c1093689c81 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; @@ -58,4 +59,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..bcfdf0460259a6fbae48a2c0c36a920750fa2a45 100644 --- a/src/main/java/biz/nynja/account/repositories/AccountRepositoryAdditionalImpl.java +++ b/src/main/java/biz/nynja/account/repositories/AccountRepositoryAdditionalImpl.java @@ -36,6 +36,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; @@ -747,6 +748,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 +950,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 +979,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 +994,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..a300fb87feebb8d81370e7f79ec15aa91e092143 100644 --- a/src/main/java/biz/nynja/account/services/AccountServiceImpl.java +++ b/src/main/java/biz/nynja/account/services/AccountServiceImpl.java @@ -50,6 +50,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; @@ -1184,4 +1185,43 @@ 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) { + + Validation validation = authenticationProvider.validateUpdateSearchableOptionRequest(request.getProfileId(), + request.getAuthenticationType(), request.getAuthenticationIdentifier()); + if (validation.hasErrors()) { + logAndBuildGrpcStatusResponse(responseObserver, StatusResponse.newBuilder(), validation.getErrorMessage(), + "", validation.getCause().get()); + 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/validation/Validators.java b/src/main/java/biz/nynja/account/validation/Validators.java index 930b836ad497429ef6461dc7f9655adf9fae24ca..f1f1689cd3946a3b4931a2180b6bb90a13671d61 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.toString()) + && !authenticationType.equals(AuthenticationType.PHONE.toString())) { + 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}$"); }