From 96f6e0a374169c57b2bcba2fec090d5ebc019d7d Mon Sep 17 00:00:00 2001 From: tamassoltesz Date: Fri, 22 Nov 2024 17:08:26 +0100 Subject: [PATCH] fix: reworked error handling to comform previous approach with messages --- .../io/supertokens/authRecipe/AuthRecipe.java | 39 +- .../io/supertokens/bulkimport/BulkImport.java | 557 +++++++----------- .../ProcessBulkUsersImportWorker.java | 32 +- .../emailpassword/EmailPassword.java | 12 +- .../java/io/supertokens/inmemorydb/Start.java | 34 +- .../passwordless/Passwordless.java | 2 +- .../storageLayer/StorageLayer.java | 9 +- .../io/supertokens/thirdparty/ThirdParty.java | 13 +- src/main/java/io/supertokens/totp/Totp.java | 1 - .../useridmapping/UserIdMapping.java | 15 +- .../io/supertokens/userroles/UserRoles.java | 22 +- .../api/bulkimport/ImportUserAPI.java | 86 +-- .../test/bulkimport/BulkImportFlowTest.java | 225 ++++++- 13 files changed, 583 insertions(+), 464 deletions(-) diff --git a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java index a0a406d73..1820a32c9 100644 --- a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java +++ b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java @@ -29,6 +29,7 @@ import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.authRecipe.sqlStorage.AuthRecipeSQLStorage; +import io.supertokens.pluginInterface.bulkimport.exceptions.BulkImportBatchInsertException; import io.supertokens.pluginInterface.dashboard.DashboardSearchTags; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; @@ -598,6 +599,7 @@ public static List linkMultipleAccounts(Main main, AppId } AuthRecipeSQLStorage authRecipeStorage = StorageUtils.getAuthRecipeStorage(storage); + Map errorByUserId = new HashMap<>(); try { List linkAccountsResults = authRecipeStorage.startTransaction(con -> { @@ -614,6 +616,7 @@ public static List linkMultipleAccounts(Main main, AppId } else if(canLinkAccountsBulkResult.error != null) { results.add(new LinkAccountsBulkResult( canLinkAccountsBulkResult.authRecipeUserInfo, false, canLinkAccountsBulkResult.error)); // preparing to return the error + errorByUserId.put(canLinkAccountsBulkResult.recipeUserId, canLinkAccountsBulkResult.error); } else { recipeUserByPrimaryUserNeedsLinking.put(canLinkAccountsBulkResult.recipeUserId, canLinkAccountsBulkResult.primaryUserId); } @@ -628,7 +631,9 @@ public static List linkMultipleAccounts(Main main, AppId authRecipeStorage.commitTransaction(con); } - + if(!errorByUserId.isEmpty()) { + throw new StorageQueryException(new BulkImportBatchInsertException("link accounts errors", errorByUserId)); + } return results; }); @@ -802,7 +807,6 @@ private static List canCreatePrimaryUsersHelper(Tra Map thirdpartyUserIdToThirdpartyId) throws StorageQueryException, UnknownUserIdException{ AuthRecipeSQLStorage authRecipeStorage = StorageUtils.getAuthRecipeStorage(storage); - List targetUsers = authRecipeStorage.getPrimaryUsersByIds_Transaction(appIdentifier, con, recipeUserIds); if (targetUsers == null || targetUsers.isEmpty()) { @@ -978,13 +982,13 @@ public static List createPrimaryUsers(Main main, throws StorageQueryException, AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException, RecipeUserIdAlreadyLinkedWithPrimaryUserIdException, UnknownUserIdException, TenantOrAppNotFoundException, FeatureNotEnabledException { - if (!Utils.isAccountLinkingEnabled(main, appIdentifier)) { throw new FeatureNotEnabledException( "Account linking feature is not enabled for this app. Please contact support to enable it."); } AuthRecipeSQLStorage authRecipeStorage = StorageUtils.getAuthRecipeStorage(storage); + Map errorsByUserId = new HashMap<>(); try { return authRecipeStorage.startTransaction(con -> { @@ -993,7 +997,11 @@ public static List createPrimaryUsers(Main main, recipeUserIds, allDistinctEmails, allDistinctPhones, thirdpartyUserIdsToThirdpartyIds); List canMakePrimaryUsers = new ArrayList<>(); for(CreatePrimaryUserBulkResult result : results) { - if (result.wasAlreadyAPrimaryUser || result.error != null) { + if (result.wasAlreadyAPrimaryUser) { + continue; + } + if(result.error != null) { + errorsByUserId.put(result.user.getSupertokensUserId(), result.error); continue; } canMakePrimaryUsers.add(result); @@ -1005,26 +1013,27 @@ public static List createPrimaryUsers(Main main, authRecipeStorage.commitTransaction(con); for(CreatePrimaryUserBulkResult result : results) { - if (result.wasAlreadyAPrimaryUser || result.error != null) { + if (result.wasAlreadyAPrimaryUser) { + continue; + } + if(result.error != null) { + errorsByUserId.put(result.user.getSupertokensUserId(), result.error); continue; } result.user.isPrimaryUser = true; } - return results; + if(!errorsByUserId.isEmpty()) { + throw new StorageTransactionLogicException(new BulkImportBatchInsertException("create primary users errors", errorsByUserId)); + } + + return results; } catch (UnknownUserIdException e) { throw new StorageTransactionLogicException(e); } }); - } catch (StorageTransactionLogicException e) { - if (e.actualException instanceof UnknownUserIdException) { - throw (UnknownUserIdException) e.actualException; - } else if (e.actualException instanceof RecipeUserIdAlreadyLinkedWithPrimaryUserIdException) { - throw (RecipeUserIdAlreadyLinkedWithPrimaryUserIdException) e.actualException; - } else if (e.actualException instanceof AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException) { - throw (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException) e.actualException; - } - throw new StorageQueryException(e); + } catch (StorageTransactionLogicException e) { + throw new StorageQueryException(e.actualException); } } diff --git a/src/main/java/io/supertokens/bulkimport/BulkImport.java b/src/main/java/io/supertokens/bulkimport/BulkImport.java index 37b5ea23f..d148f340b 100644 --- a/src/main/java/io/supertokens/bulkimport/BulkImport.java +++ b/src/main/java/io/supertokens/bulkimport/BulkImport.java @@ -26,7 +26,6 @@ import io.supertokens.authRecipe.exception.RecipeUserIdAlreadyLinkedWithPrimaryUserIdException; import io.supertokens.config.Config; import io.supertokens.emailpassword.EmailPassword; -import io.supertokens.emailpassword.EmailPassword.ImportUserResponse; import io.supertokens.emailpassword.PasswordHashing; import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; import io.supertokens.multitenancy.Multitenancy; @@ -34,7 +33,6 @@ import io.supertokens.multitenancy.exception.AnotherPrimaryUserWithPhoneNumberAlreadyExistsException; import io.supertokens.multitenancy.exception.AnotherPrimaryUserWithThirdPartyInfoAlreadyExistsException; import io.supertokens.passwordless.Passwordless; -import io.supertokens.passwordless.exceptions.RestartFlowException; import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.StorageUtils; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; @@ -44,10 +42,10 @@ import io.supertokens.pluginInterface.bulkimport.BulkImportUser.TotpDevice; import io.supertokens.pluginInterface.bulkimport.BulkImportUser.UserRole; import io.supertokens.pluginInterface.bulkimport.ImportUserBase; +import io.supertokens.pluginInterface.bulkimport.exceptions.BulkImportBatchInsertException; import io.supertokens.pluginInterface.bulkimport.sqlStorage.BulkImportSQLStorage; import io.supertokens.pluginInterface.emailpassword.EmailPasswordImportUser; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; -import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateUserIdException; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.emailverification.sqlStorage.EmailVerificationSQLStorage; import io.supertokens.pluginInterface.exceptions.DbInitException; @@ -65,20 +63,17 @@ import io.supertokens.pluginInterface.thirdparty.ThirdPartyImportUser; import io.supertokens.pluginInterface.thirdparty.exception.DuplicateThirdPartyUserException; import io.supertokens.pluginInterface.totp.TOTPDevice; -import io.supertokens.pluginInterface.totp.exception.DeviceAlreadyExistsException; import io.supertokens.pluginInterface.useridmapping.exception.UnknownSuperTokensUserIdException; import io.supertokens.pluginInterface.useridmapping.exception.UserIdMappingAlreadyExistsException; import io.supertokens.pluginInterface.userroles.exception.UnknownRoleException; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.thirdparty.ThirdParty; -import io.supertokens.thirdparty.ThirdParty.SignInUpResponse; import io.supertokens.totp.Totp; import io.supertokens.useridmapping.UserIdMapping; import io.supertokens.usermetadata.UserMetadata; import io.supertokens.userroles.UserRoles; import io.supertokens.utils.Utils; import jakarta.servlet.ServletException; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -181,8 +176,8 @@ public static synchronized AuthRecipeUserInfo importUser(Main main, AppIdentifie return bulkImportProxyStorage.startTransaction(con -> { try { Storage[] allStoragesForApp = getAllProxyStoragesForApp(main, appIdentifier); - processUserImportSteps(main, con, appIdentifier, bulkImportProxyStorage, user, primaryLM, - allStoragesForApp); + + processUsersImportSteps(main, con, appIdentifier, bulkImportProxyStorage, List.of(user), allStoragesForApp); bulkImportProxyStorage.commitTransactionForBulkImportProxyStorage(); @@ -205,21 +200,6 @@ public static synchronized AuthRecipeUserInfo importUser(Main main, AppIdentifie } } - public static void processUserImportSteps(Main main, TransactionConnection con, AppIdentifier appIdentifier, - Storage bulkImportProxyStorage, BulkImportUser user, LoginMethod primaryLM, Storage[] allStoragesForApp) - throws StorageTransactionLogicException { - for (LoginMethod lm : user.loginMethods) { - processUserLoginMethod(main, appIdentifier, bulkImportProxyStorage, lm); - } - - createPrimaryUserAndLinkAccounts(main, appIdentifier, bulkImportProxyStorage, user, primaryLM); - createUserIdMapping(appIdentifier, user, primaryLM, allStoragesForApp); - verifyEmailForAllLoginMethods(appIdentifier, con, bulkImportProxyStorage, user.loginMethods); - createTotpDevices(main, appIdentifier, bulkImportProxyStorage, user, primaryLM); - createUserMetadata(appIdentifier, bulkImportProxyStorage, user, primaryLM); - createUserRoles(main, appIdentifier, bulkImportProxyStorage, user); - } - public static void processUsersImportSteps(Main main, TransactionConnection connection, AppIdentifier appIdentifier, Storage bulkImportProxyStorage, List users, Storage[] allStoragesForApp) throws StorageTransactionLogicException { @@ -242,7 +222,6 @@ public static void processUsersImportSteps(Main main, TransactionConnection conn public static void processUsersLoginMethods(Main main, AppIdentifier appIdentifier, Storage storage, List users) throws StorageTransactionLogicException { //sort login methods together - System.out.println(Thread.currentThread().getName() + " processUsersLoginMethods"); Map> sortedLoginMethods = new HashMap<>(); for (BulkImportUser user: users) { for(LoginMethod loginMethod : user.loginMethods){ @@ -253,18 +232,42 @@ public static void processUsersLoginMethods(Main main, AppIdentifier appIdentifi } } - List importedUsers = new ArrayList<>(); - importedUsers.addAll(processEmailPasswordLoginMethods(main, storage, sortedLoginMethods.get("emailpassword"), appIdentifier)); - importedUsers.addAll(processThirdpartyLoginMethods(main, storage, sortedLoginMethods.get("thirdparty"), appIdentifier)); - importedUsers.addAll(processPasswordlessLoginMethods(appIdentifier, storage, - sortedLoginMethods.get("passwordless"))); + List importedUsers = new ArrayList<>(); + if (sortedLoginMethods.containsKey("emailpassword")) { + importedUsers.addAll( + processEmailPasswordLoginMethods(main, storage, sortedLoginMethods.get("emailpassword"), + appIdentifier)); + } + if (sortedLoginMethods.containsKey("thirdparty")) { + importedUsers.addAll( + processThirdpartyLoginMethods(main, storage, sortedLoginMethods.get("thirdparty"), + appIdentifier)); + } + if (sortedLoginMethods.containsKey("passwordless")) { + importedUsers.addAll(processPasswordlessLoginMethods(appIdentifier, storage, + sortedLoginMethods.get("passwordless"))); + } + Set actualKeys = new HashSet<>(sortedLoginMethods.keySet()); + List.of("emailpassword", "thirdparty", "passwordless").forEach(actualKeys::remove); + if(!actualKeys.isEmpty()){ + throw new StorageTransactionLogicException( + new IllegalArgumentException("E001: Unknown recipeId(s) [" + + actualKeys.stream().map(s -> s+" ") + "] for loginMethod.")); + } - //TODO - for(Map.Entry> loginMethodEntries : sortedLoginMethods.entrySet()){ - for(LoginMethod loginMethod : loginMethodEntries.getValue()){ - associateUserToTenants(main, appIdentifier, storage, loginMethod, loginMethod.tenantIds.get(0)); + Map errorsById = new HashMap<>(); + for (Map.Entry> loginMethodEntries : sortedLoginMethods.entrySet()) { + for (LoginMethod loginMethod : loginMethodEntries.getValue()) { + try { + associateUserToTenants(main, appIdentifier, storage, loginMethod, loginMethod.tenantIds.get(0)); + } catch (StorageTransactionLogicException e){ + errorsById.put(loginMethod.superTokensUserId, e.actualException); + } + } + } + if(!errorsById.isEmpty()){ + throw new StorageTransactionLogicException(new BulkImportBatchInsertException("tenant association errors", errorsById)); } - } } private static List processPasswordlessLoginMethods(AppIdentifier appIdentifier, Storage storage, @@ -272,10 +275,12 @@ private static List processPasswordlessLoginMethods(Ap throws StorageTransactionLogicException { try { List usersToImport = new ArrayList<>(); - for (LoginMethod loginMethod: loginMethods){ + for (LoginMethod loginMethod : loginMethods) { String userId = Utils.getUUID(); - TenantIdentifier tenantIdentifierForLoginMethod = new TenantIdentifier(appIdentifier.getConnectionUriDomain(), - appIdentifier.getAppId(), loginMethod.tenantIds.get(0)); // the cron runs per app. The app stays the same, the tenant can change + TenantIdentifier tenantIdentifierForLoginMethod = new TenantIdentifier( + appIdentifier.getConnectionUriDomain(), + appIdentifier.getAppId(), loginMethod.tenantIds.get( + 0)); // the cron runs per app. The app stays the same, the tenant can change usersToImport.add(new PasswordlessImportUser(userId, loginMethod.phoneNumber, loginMethod.email, tenantIdentifierForLoginMethod, loginMethod.timeJoinedInMSSinceEpoch)); @@ -283,11 +288,29 @@ private static List processPasswordlessLoginMethods(Ap } Passwordless.createPasswordlessUsers(storage, usersToImport); - return usersToImport; - } catch (RestartFlowException e) { - String errorMessage =""; // TODO - throw new StorageTransactionLogicException(new Exception(errorMessage)); - } catch (StorageQueryException e) { + return usersToImport; + } catch (StorageQueryException | StorageTransactionLogicException e) { + if (e.getCause() instanceof BulkImportBatchInsertException) { + Map errorsByPosition = ((BulkImportBatchInsertException) e.getCause()).exceptionByUserId; + for (String userid : errorsByPosition.keySet()) { + Exception exception = errorsByPosition.get(userid); + if (exception instanceof DuplicateEmailException) { + String message = "E006: A user with email " + + loginMethods.stream() + .filter(loginMethod -> loginMethod.superTokensUserId.equals(userid)) + .findFirst().get().email + " already exists in passwordless loginMethod."; + errorsByPosition.put(userid, new Exception(message)); + } else if (exception instanceof DuplicatePhoneNumberException) { + String message = "E007: A user with phoneNumber " + + loginMethods.stream() + .filter(loginMethod -> loginMethod.superTokensUserId.equals(userid)) + .findFirst().get().phoneNumber + " already exists in passwordless loginMethod."; + errorsByPosition.put(userid, new Exception(message)); + } + } + throw new StorageTransactionLogicException( + new BulkImportBatchInsertException("translated", errorsByPosition)); + } throw new StorageTransactionLogicException(e); } catch (TenantOrAppNotFoundException e) { throw new StorageTransactionLogicException(new Exception("E008: " + e.getMessage())); @@ -308,15 +331,29 @@ private static List processThirdpartyLoginMethods(Main loginMethod.thirdPartyUserId, tenantIdentifierForLoginMethod, loginMethod.timeJoinedInMSSinceEpoch)); loginMethod.superTokensUserId = userId; } - ThirdParty.createThirdPartyUsers(storage, usersToImport); + ThirdParty.createMultipleThirdPartyUsers(storage, usersToImport); return usersToImport; - } catch (StorageQueryException e) { + } catch (StorageQueryException | StorageTransactionLogicException e) { + if (e.getCause() instanceof BulkImportBatchInsertException) { + Map errorsByPosition = ((BulkImportBatchInsertException) e.getCause()).exceptionByUserId; + for (String userid : errorsByPosition.keySet()) { + Exception exception = errorsByPosition.get(userid); + if (exception instanceof DuplicateThirdPartyUserException) { + LoginMethod loginMethodForError = loginMethods.stream() + .filter(loginMethod -> loginMethod.superTokensUserId.equals(userid)) + .findFirst().get(); + String message = "E005: A user with thirdPartyId " + loginMethodForError.thirdPartyId + + " and thirdPartyUserId " + loginMethodForError.thirdPartyUserId + + " already exists in thirdparty loginMethod."; + errorsByPosition.put(userid, new Exception(message)); + } + } + throw new StorageTransactionLogicException( + new BulkImportBatchInsertException("translated", errorsByPosition)); + } throw new StorageTransactionLogicException(e); -// } catch (TenantOrAppNotFoundException e) { -// throw new StorageTransactionLogicException(new Exception("E004: " + e.getMessage())); -// } catch (DuplicateThirdPartyUserException e) { -// throw new StorageTransactionLogicException(new Exception("E005: A user with thirdPartyId " -// + " and thirdPartyUserId already exists in thirdparty loginMethod.")); // TODO + } catch (TenantOrAppNotFoundException e) { + throw new StorageTransactionLogicException(new Exception("E004: " + e.getMessage())); } } @@ -343,104 +380,25 @@ private static List processEmailPasswordLoginMethods( emailPasswordLoginMethod.superTokensUserId = userId; } - EmailPassword.createUsersWithPasswordHash(storage, usersToImport); + EmailPassword.createMultipleUsersWithPasswordHash(storage, usersToImport); return usersToImport; - } catch (StorageQueryException e) { - throw new StorageTransactionLogicException(e); - } catch (TenantOrAppNotFoundException e) { - throw new StorageTransactionLogicException(new Exception("E002: " + e.getMessage())); - } catch (DuplicateEmailException e) { - throw new StorageTransactionLogicException( - new Exception( - "E003: A user with email already exists in emailpassword loginMethod.")); - } catch (DuplicateUserIdException e) { - throw new RuntimeException(e); - } - } - - public static void processUserLoginMethod(Main main, AppIdentifier appIdentifier, Storage storage, - LoginMethod lm) throws StorageTransactionLogicException { - String firstTenant = lm.tenantIds.get(0); - - TenantIdentifier tenantIdentifier = new TenantIdentifier(appIdentifier.getConnectionUriDomain(), - appIdentifier.getAppId(), firstTenant); - - if (lm.recipeId.equals("emailpassword")) { - processEmailPasswordLoginMethod(main, tenantIdentifier, storage, lm); - } else if (lm.recipeId.equals("thirdparty")) { - processThirdPartyLoginMethod(tenantIdentifier, storage, lm); - } else if (lm.recipeId.equals("passwordless")) { - processPasswordlessLoginMethod(tenantIdentifier, storage, lm); - } else { - throw new StorageTransactionLogicException( - new IllegalArgumentException("E001: Unknown recipeId " + lm.recipeId + " for loginMethod.")); - } - - associateUserToTenants(main, appIdentifier, storage, lm, firstTenant); //already associated here. Why this? - // found it: the remaining tenants are not associated, only the first one - } - - private static void processEmailPasswordLoginMethod(Main main, TenantIdentifier tenantIdentifier, Storage storage, - LoginMethod lm) throws StorageTransactionLogicException { - try { - - String passwordHash = lm.passwordHash; - if (passwordHash == null && lm.plainTextPassword != null) { - passwordHash = PasswordHashing.getInstance(main) - .createHashWithSalt(tenantIdentifier.toAppIdentifier(), lm.plainTextPassword); + } catch (StorageQueryException | StorageTransactionLogicException e) { + if(e.getCause() instanceof BulkImportBatchInsertException){ + Map errorsByPosition = ((BulkImportBatchInsertException) e.getCause()).exceptionByUserId; + for(String userid : errorsByPosition.keySet()){ + Exception exception = errorsByPosition.get(userid); + if(exception instanceof DuplicateEmailException){ + String message = "E003: A user with email " + + loginMethods.stream().filter(loginMethod -> loginMethod.superTokensUserId.equals(userid)) + .findFirst().get().email + " already exists in emailpassword loginMethod."; + errorsByPosition.put(userid, new Exception(message)); + } + } + throw new StorageTransactionLogicException(new BulkImportBatchInsertException("translated", errorsByPosition)); } - - ImportUserResponse userInfo = EmailPassword.createUserWithPasswordHash(tenantIdentifier, storage, lm.email, - passwordHash, lm.timeJoinedInMSSinceEpoch); - - lm.superTokensUserId = userInfo.user.getSupertokensUserId(); - } catch (StorageQueryException e) { throw new StorageTransactionLogicException(e); } catch (TenantOrAppNotFoundException e) { throw new StorageTransactionLogicException(new Exception("E002: " + e.getMessage())); - } catch (DuplicateEmailException e) { - throw new StorageTransactionLogicException( - new Exception( - "E003: A user with email " + lm.email + " already exists in emailpassword loginMethod.")); - } - } - - private static void processThirdPartyLoginMethod(TenantIdentifier tenantIdentifier, Storage storage, LoginMethod lm) - throws StorageTransactionLogicException { - try { - SignInUpResponse userInfo = ThirdParty.createThirdPartyUser( - tenantIdentifier, storage, lm.thirdPartyId, lm.thirdPartyUserId, lm.email, - lm.timeJoinedInMSSinceEpoch); - - lm.superTokensUserId = userInfo.user.getSupertokensUserId(); - } catch (StorageQueryException e) { - throw new StorageTransactionLogicException(e); - } catch (TenantOrAppNotFoundException e) { - throw new StorageTransactionLogicException(new Exception("E004: " + e.getMessage())); - } catch (DuplicateThirdPartyUserException e) { - throw new StorageTransactionLogicException(new Exception("E005: A user with thirdPartyId " + lm.thirdPartyId - + " and thirdPartyUserId " + lm.thirdPartyUserId + " already exists in thirdparty loginMethod.")); - } - } - - private static void processPasswordlessLoginMethod(TenantIdentifier tenantIdentifier, Storage storage, - LoginMethod lm) - throws StorageTransactionLogicException { - try { - AuthRecipeUserInfo userInfo = Passwordless.createPasswordlessUser(tenantIdentifier, storage, lm.email, - lm.phoneNumber, lm.timeJoinedInMSSinceEpoch); - - lm.superTokensUserId = userInfo.getSupertokensUserId(); - } catch (RestartFlowException e) { - String errorMessage = lm.email != null - ? "E006: A user with email " + lm.email + " already exists in passwordless loginMethod." - : "E007: A user with phoneNumber " + lm.phoneNumber - + " already exists in passwordless loginMethod."; - throw new StorageTransactionLogicException(new Exception(errorMessage)); - } catch (StorageQueryException e) { - throw new StorageTransactionLogicException(e); - } catch (TenantOrAppNotFoundException e) { - throw new StorageTransactionLogicException(new Exception("E008: " + e.getMessage())); } } @@ -496,110 +454,70 @@ private static void associateUserToTenants(Main main, AppIdentifier appIdentifie } private static void createPrimaryUsersAndLinkAccounts(Main main, - AppIdentifier appIdentifier, Storage storage, List users) + AppIdentifier appIdentifier, Storage storage, + List users) throws StorageTransactionLogicException, AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException, RecipeUserIdAlreadyLinkedWithPrimaryUserIdException, StorageQueryException, FeatureNotEnabledException, TenantOrAppNotFoundException, UnknownUserIdException { - System.out.println(Thread.currentThread().getName() + " createPrimaryUsersAndLinkAccounts"); List userIds = - users.stream().map(bulkImportUser -> getPrimaryLoginMethod(bulkImportUser).getSuperTokenOrExternalUserId()).collect(Collectors.toList()); + users.stream() + .map(bulkImportUser -> getPrimaryLoginMethod(bulkImportUser).getSuperTokenOrExternalUserId()) + .collect(Collectors.toList()); Set allEmails = new HashSet<>(); Set allPhoneNumber = new HashSet<>(); Map allThirdParty = new HashMap<>(); - for(BulkImportUser user: users){ - for(LoginMethod loginMethod : user.loginMethods) { - if(loginMethod.email != null) { + for (BulkImportUser user : users) { + for (LoginMethod loginMethod : user.loginMethods) { + if (loginMethod.email != null) { allEmails.add(loginMethod.email); } - if(loginMethod.phoneNumber != null){ + if (loginMethod.phoneNumber != null) { allPhoneNumber.add(loginMethod.phoneNumber); } - if(loginMethod.thirdPartyId != null && loginMethod.thirdPartyUserId != null){ + if (loginMethod.thirdPartyId != null && loginMethod.thirdPartyUserId != null) { allThirdParty.put(loginMethod.thirdPartyUserId, loginMethod.thirdPartyId); } } } - AuthRecipe.createPrimaryUsers(main, appIdentifier, storage, userIds, new ArrayList<>(allEmails), new ArrayList<>(allPhoneNumber), allThirdParty); - linkAccountsForMultipleUser(main, appIdentifier, storage, users, new ArrayList<>(allEmails), new ArrayList<>(allPhoneNumber), allThirdParty); - } - - - public static void createPrimaryUserAndLinkAccounts(Main main, - AppIdentifier appIdentifier, Storage storage, BulkImportUser user, LoginMethod primaryLM) - throws StorageTransactionLogicException { - if (user.loginMethods.size() == 1) { - return; - } - try { - AuthRecipe.createPrimaryUser(main, appIdentifier, storage, primaryLM.getSuperTokenOrExternalUserId()); - } catch (TenantOrAppNotFoundException e) { - throw new StorageTransactionLogicException(new Exception("E018: " + e.getMessage())); + AuthRecipe.createPrimaryUsers(main, appIdentifier, storage, userIds, new ArrayList<>(allEmails), + new ArrayList<>(allPhoneNumber), allThirdParty); } catch (StorageQueryException e) { + if(e.getCause() instanceof BulkImportBatchInsertException){ + Map errorsByPosition = ((BulkImportBatchInsertException) e.getCause()).exceptionByUserId; + for (String userid : errorsByPosition.keySet()) { + Exception exception = errorsByPosition.get(userid); + if (exception instanceof UnknownUserIdException) { + String message = "E020: We tried to create the primary user for the userId " + + userid + + " but it doesn't exist. This should not happen. Please contact support."; + errorsByPosition.put(userid, new Exception(message)); + } else if (exception instanceof RecipeUserIdAlreadyLinkedWithPrimaryUserIdException) { + String message = "E021: We tried to create the primary user for the userId " + + userid + + " but it is already linked with another primary user."; + errorsByPosition.put(userid, new Exception(message)); + } else if (exception instanceof AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException) { + String message = "E022: We tried to create the primary user for the userId " + + userid + + " but the account info is already associated with another primary user."; + errorsByPosition.put(userid, new Exception(message)); + } + } + throw new StorageTransactionLogicException( + new BulkImportBatchInsertException("translated", errorsByPosition)); + } throw new StorageTransactionLogicException(e); + } catch (TenantOrAppNotFoundException e) { + throw new StorageTransactionLogicException(new Exception("E018: " + e.getMessage())); } catch (FeatureNotEnabledException e) { throw new StorageTransactionLogicException(new Exception("E019: " + e.getMessage())); - } catch (UnknownUserIdException e) { - throw new StorageTransactionLogicException(new Exception( - "E020: We tried to create the primary user for the userId " - + primaryLM.getSuperTokenOrExternalUserId() - + " but it doesn't exist. This should not happen. Please contact support.")); - } catch (RecipeUserIdAlreadyLinkedWithPrimaryUserIdException e) { - throw new StorageTransactionLogicException(new Exception( - "E021: We tried to create the primary user for the userId " - + primaryLM.getSuperTokenOrExternalUserId() - + " but it is already linked with another primary user.")); - } catch (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException e) { - throw new StorageTransactionLogicException(new Exception( - "E022: We tried to create the primary user for the userId " - + primaryLM.getSuperTokenOrExternalUserId() - + " but the account info is already associated with another primary user.")); } - linkAccountsForUser(main, appIdentifier, storage, user, primaryLM); - } - - private static void linkAccountsForUser(Main main, AppIdentifier appIdentifier, Storage storage, BulkImportUser user, - LoginMethod primaryLM) throws StorageTransactionLogicException { - for (LoginMethod lm : user.loginMethods) { - try { - if (lm.getSuperTokenOrExternalUserId().equals(primaryLM.getSuperTokenOrExternalUserId())) { - continue; - } - - AuthRecipe.linkAccounts(main, appIdentifier, storage, lm.getSuperTokenOrExternalUserId(), - primaryLM.getSuperTokenOrExternalUserId()); - - } catch (TenantOrAppNotFoundException e) { - throw new StorageTransactionLogicException(new Exception("E023: " + e.getMessage())); - } catch (StorageQueryException e) { - throw new StorageTransactionLogicException(e); - } catch (FeatureNotEnabledException e) { - throw new StorageTransactionLogicException(new Exception("E024: " + e.getMessage())); - } catch (UnknownUserIdException e) { - throw new StorageTransactionLogicException( - new Exception("E025: We tried to link the userId " + lm.getSuperTokenOrExternalUserId() - + " to the primary userId " + primaryLM.getSuperTokenOrExternalUserId() - + " but it doesn't exist.")); - } catch (InputUserIdIsNotAPrimaryUserException e) { - throw new StorageTransactionLogicException( - new Exception("E026: We tried to link the userId " + lm.getSuperTokenOrExternalUserId() - + " to the primary userId " + primaryLM.getSuperTokenOrExternalUserId() - + " but it is not a primary user.")); - } catch (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException e) { - throw new StorageTransactionLogicException(new Exception( - "E027: We tried to link the userId " + lm.getSuperTokenOrExternalUserId() - + " to the primary userId " + primaryLM.getSuperTokenOrExternalUserId() - + " but the account info is already associated with another primary user.")); - } catch (RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException e) { - throw new StorageTransactionLogicException(new Exception( - "E028: We tried to link the userId " + lm.getSuperTokenOrExternalUserId() - + " to the primary userId " + primaryLM.getSuperTokenOrExternalUserId() - + " but it is already linked with another primary user.")); - } - } + linkAccountsForMultipleUser(main, appIdentifier, storage, users, new ArrayList<>(allEmails), + new ArrayList<>(allPhoneNumber), allThirdParty); } private static void linkAccountsForMultipleUser(Main main, AppIdentifier appIdentifier, Storage storage, @@ -612,13 +530,45 @@ private static void linkAccountsForMultipleUser(Main main, AppIdentifier appIden try { AuthRecipe.linkMultipleAccounts(main, appIdentifier, storage, recipeUserIdByPrimaryUserId, allDistinctEmails, allDistinctPhones, thirdpartyUserIdsToThirdpartyIds); - } catch (StorageQueryException | FeatureNotEnabledException | TenantOrAppNotFoundException e) { + } catch (TenantOrAppNotFoundException e) { + throw new StorageTransactionLogicException(new Exception("E023: " + e.getMessage())); + } catch (FeatureNotEnabledException e) { + throw new StorageTransactionLogicException(new Exception("E024: " + e.getMessage())); + } catch (StorageQueryException e) { + if (e.getCause() instanceof BulkImportBatchInsertException) { + Map errorByPosition = ((BulkImportBatchInsertException) e.getCause()).exceptionByUserId; + for (String userId : errorByPosition.keySet()) { + Exception currentException = errorByPosition.get(userId); + String recipeUID = recipeUserIdByPrimaryUserId.get(userId); + if (currentException instanceof UnknownUserIdException) { + String message = "E025: We tried to link the userId " + recipeUID + + " to the primary userId " + userId + + " but it doesn't exist."; + errorByPosition.put(userId, new Exception(message)); + } else if (currentException instanceof InputUserIdIsNotAPrimaryUserException) { + String message = "E026: We tried to link the userId " + recipeUID + + " to the primary userId " + userId + + " but it is not a primary user."; + errorByPosition.put(userId, new Exception(message)); + } else if (currentException instanceof AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException) { + String message = "E027: We tried to link the userId " + userId + + " to the primary userId " + recipeUID + + " but the account info is already associated with another primary user."; + errorByPosition.put(userId, new Exception(message)); + } else if (currentException instanceof RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException) { + String message = "E028: We tried to link the userId " + recipeUID + + " to the primary userId " + userId + + " but it is already linked with another primary user."; + errorByPosition.put(userId, new Exception(message)); + } + } + throw new StorageTransactionLogicException( + new BulkImportBatchInsertException("link accounts translated", errorByPosition)); + } throw new StorageTransactionLogicException(e); } - //TODO proper error handling } - @NotNull private static Map collectRecipeIdsToPrimaryIds(List users) { Map recipeUserIdByPrimaryUserId = new HashMap<>(); for(BulkImportUser user: users){ @@ -634,37 +584,8 @@ private static Map collectRecipeIdsToPrimaryIds(List users, Storage[] storages) throws StorageTransactionLogicException { - System.out.println(Thread.currentThread().getName() + " createMultipleUserIdMapping"); Map superTokensUserIdToExternalUserId = new HashMap<>(); for(BulkImportUser user: users) { if(user.externalUserId != null) { @@ -678,33 +599,33 @@ public static void createMultipleUserIdMapping(AppIdentifier appIdentifier, superTokensUserIdToExternalUserId, false, true); -// for(UserIdMapping.UserIdBulkMappingResult mappingResult : mappingResults){ -// if(mappingResult.error == null) { // no error means successful mapping -// // TODO -// } -// } - } catch (Exception e) { - //TODO proper error handling - } - } - - public static void createUserMetadata(AppIdentifier appIdentifier, Storage storage, BulkImportUser user, - LoginMethod primaryLM) throws StorageTransactionLogicException { - if (user.userMetadata != null) { - try { - UserMetadata.updateUserMetadata(appIdentifier, storage, primaryLM.getSuperTokenOrExternalUserId(), - user.userMetadata); - } catch (TenantOrAppNotFoundException e) { - throw new StorageTransactionLogicException(new Exception("E040: " + e.getMessage())); - } catch (StorageQueryException e) { - throw new StorageTransactionLogicException(e); + } catch (StorageQueryException e) { + if(e.getCause() instanceof BulkImportBatchInsertException) { + Map errorsByPosition = ((BulkImportBatchInsertException) e.getCause()).exceptionByUserId; + for (String userid : errorsByPosition.keySet()) { + Exception exception = errorsByPosition.get(userid); + if (exception instanceof ServletException) { + String message = "E030: " + e.getMessage(); + errorsByPosition.put(userid, new Exception(message)); + } else if (exception instanceof UserIdMappingAlreadyExistsException) { + String message = "E031: A user with externalId " + superTokensUserIdToExternalUserId.get(userid) + " already exists"; + errorsByPosition.put(userid, new Exception(message)); + } else if (exception instanceof UnknownSuperTokensUserIdException) { + String message = "E032: We tried to create the externalUserId mapping for the superTokenUserId " + + userid + + " but it doesn't exist. This should not happen. Please contact support."; + errorsByPosition.put(userid, new Exception(message)); + } + } + throw new StorageTransactionLogicException( + new BulkImportBatchInsertException("translated", errorsByPosition)); } + throw new StorageTransactionLogicException(e); } } public static void createMultipleUserMetadata(AppIdentifier appIdentifier, Storage storage, List users) throws StorageTransactionLogicException { - System.out.println(Thread.currentThread().getName() + " createMultipleUserMetadata"); Map usersMetadata = new HashMap<>(); for(BulkImportUser user: users) { @@ -722,34 +643,9 @@ public static void createMultipleUserMetadata(AppIdentifier appIdentifier, Stora } } - public static void createUserRoles(Main main, AppIdentifier appIdentifier, Storage storage, - BulkImportUser user) throws StorageTransactionLogicException { - if (user.userRoles != null) { - for (UserRole userRole : user.userRoles) { - try { - for (String tenantId : userRole.tenantIds) { - TenantIdentifier tenantIdentifier = new TenantIdentifier( - appIdentifier.getConnectionUriDomain(), appIdentifier.getAppId(), - tenantId); - - UserRoles.addRoleToUser(main, tenantIdentifier, storage, user.externalUserId, userRole.role); - } - } catch (TenantOrAppNotFoundException e) { - throw new StorageTransactionLogicException(new Exception("E033: " + e.getMessage())); - } catch (StorageQueryException e) { - throw new StorageTransactionLogicException(e); - } catch (UnknownRoleException e) { - throw new StorageTransactionLogicException(new Exception("E034: Role " + userRole.role - + " does not exist! You need pre-create the role before assigning it to the user.")); - } - } - } - } - public static void createMultipleUserRoles(Main main, AppIdentifier appIdentifier, Storage storage, List users) throws StorageTransactionLogicException { Map> rolesToUserByTenant = new HashMap<>(); - System.out.println(Thread.currentThread().getName() + " createMultipleUserRoles"); for (BulkImportUser user : users) { if (user.userRoles != null) { @@ -772,40 +668,21 @@ public static void createMultipleUserRoles(Main main, AppIdentifier appIdentifie UserRoles.addMultipleRolesToMultipleUsers(main, storage, rolesToUserByTenant); } catch (TenantOrAppNotFoundException e) { throw new StorageTransactionLogicException(new Exception("E033: " + e.getMessage())); - } catch (UnknownRoleException e) { - throw new StorageTransactionLogicException(new Exception("E034: Role " - + " does not exist! You need pre-create the role before assigning it to the user.")); - } - - } - - public static void verifyEmailForAllLoginMethods(AppIdentifier appIdentifier, TransactionConnection con, - Storage storage, - List loginMethods) throws StorageTransactionLogicException { - - for (LoginMethod lm : loginMethods) { - try { - - TenantIdentifier tenantIdentifier = new TenantIdentifier(appIdentifier.getConnectionUriDomain(), - appIdentifier.getAppId(), lm.tenantIds.get(0)); - - EmailVerificationSQLStorage emailVerificationSQLStorage = StorageUtils - .getEmailVerificationStorage(storage); - emailVerificationSQLStorage - .updateIsEmailVerified_Transaction(tenantIdentifier.toAppIdentifier(), con, - lm.getSuperTokenOrExternalUserId(), lm.email, true); - } catch (TenantOrAppNotFoundException e) { - throw new StorageTransactionLogicException(new Exception("E035: " + e.getMessage())); - } catch (StorageQueryException e) { + } catch (StorageTransactionLogicException e) { + if(e.actualException instanceof UnknownRoleException){ + throw new StorageTransactionLogicException(new Exception("E034: Role " + + " does not exist! You need pre-create the role before assigning it to the user.")); + } else { throw new StorageTransactionLogicException(e); } + } + } public static void verifyMultipleEmailForAllLoginMethods(AppIdentifier appIdentifier, Storage storage, List users) throws StorageTransactionLogicException { - System.out.println(Thread.currentThread().getName() + " verifyMultipleEmailForAllLoginMethods"); Map emailToUserId = new HashMap<>(); for (BulkImportUser user : users) { for (LoginMethod lm : user.loginMethods) { @@ -831,38 +708,14 @@ public static void verifyMultipleEmailForAllLoginMethods(AppIdentifier appIdenti } } - public static void createTotpDevices(Main main, AppIdentifier appIdentifier, Storage storage, - BulkImportUser user, LoginMethod primaryLM) throws StorageTransactionLogicException { - if (user.totpDevices != null) { - for (TotpDevice totpDevice : user.totpDevices) { - try { - Totp.createDevice(main, appIdentifier, storage, primaryLM.getSuperTokenOrExternalUserId(), - totpDevice.deviceName, totpDevice.skew, totpDevice.period, totpDevice.secretKey, - true, System.currentTimeMillis()); - } catch (TenantOrAppNotFoundException e) { - throw new StorageTransactionLogicException(new Exception("E036: " + e.getMessage())); - } catch (StorageQueryException e) { - throw new StorageTransactionLogicException(e); - } catch (FeatureNotEnabledException e) { - throw new StorageTransactionLogicException(new Exception("E037: " + e.getMessage())); - } catch (DeviceAlreadyExistsException e) { - throw new StorageTransactionLogicException( - new Exception( - "E038: A totp device with name " + totpDevice.deviceName + " already exists")); - } - } - } - } - public static void createMultipleTotpDevices(Main main, AppIdentifier appIdentifier, Storage storage, List users) throws StorageTransactionLogicException { - System.out.println(Thread.currentThread().getName() + " createMultipleTotpDevices"); List devices = new ArrayList<>(); for (BulkImportUser user : users) { if (user.totpDevices != null) { for(TotpDevice device : user.totpDevices){ - TOTPDevice totpDevice = new TOTPDevice(getPrimaryLoginMethod(user).getSuperTokenOrExternalUserId(), //TODO getPrimaryLoginMethod call should be done once in the whole process + TOTPDevice totpDevice = new TOTPDevice(getPrimaryLoginMethod(user).getSuperTokenOrExternalUserId(), device.deviceName, device.secretKey, device.period, device.skew, true, System.currentTimeMillis()); devices.add(totpDevice); @@ -872,7 +725,7 @@ public static void createMultipleTotpDevices(Main main, AppIdentifier appIdentif try { Totp.createDevices(main, appIdentifier, storage, devices); } catch (StorageQueryException e) { - throw new StorageTransactionLogicException(e); + throw new StorageTransactionLogicException(new Exception("E036: " + e.getMessage())); } catch (FeatureNotEnabledException e) { throw new StorageTransactionLogicException(new Exception("E037: " + e.getMessage())); } diff --git a/src/main/java/io/supertokens/cronjobs/bulkimport/ProcessBulkUsersImportWorker.java b/src/main/java/io/supertokens/cronjobs/bulkimport/ProcessBulkUsersImportWorker.java index dd3a9bb09..5b8da78e9 100644 --- a/src/main/java/io/supertokens/cronjobs/bulkimport/ProcessBulkUsersImportWorker.java +++ b/src/main/java/io/supertokens/cronjobs/bulkimport/ProcessBulkUsersImportWorker.java @@ -27,8 +27,8 @@ import io.supertokens.output.Logging; import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; -import io.supertokens.pluginInterface.bulkimport.BulkImportStorage; import io.supertokens.pluginInterface.bulkimport.BulkImportUser; +import io.supertokens.pluginInterface.bulkimport.exceptions.BulkImportBatchInsertException; import io.supertokens.pluginInterface.bulkimport.exceptions.BulkImportTransactionRolledBackException; import io.supertokens.pluginInterface.bulkimport.sqlStorage.BulkImportSQLStorage; import io.supertokens.pluginInterface.exceptions.DbInitException; @@ -93,6 +93,7 @@ private void processMultipleUsers(AppIdentifier appIdentifier, List { try { @@ -128,7 +128,7 @@ private void processMultipleUsers(AppIdentifier appIdentifier, List usersBatch, Exception e, BulkImportSQLStorage baseTenantStorage) throws StorageQueryException { // Java doesn't allow us to reassign local variables inside a lambda expression // so we have to use an array. String[] errorMessage = { e.getMessage() }; + Map bulkImportUserIdToErrorMessage = new HashMap<>(); if (e instanceof StorageTransactionLogicException) { StorageTransactionLogicException exception = (StorageTransactionLogicException) e; @@ -167,7 +168,24 @@ private void handleProcessUserExceptions(AppIdentifier appIdentifier, BulkImport Logging.error(main, null, "We got an StorageQueryException while processing a bulk import user entry. It will be retried again. Error Message: " + e.getMessage(), true); return; } - errorMessage[0] = exception.actualException.getMessage(); + if(exception.actualException instanceof BulkImportBatchInsertException){ + Map userIndexToError = ((BulkImportBatchInsertException) exception.actualException).exceptionByUserId; + for(String userid : userIndexToError.keySet()){ + String id = usersBatch.stream() + .filter(bulkImportUser -> + bulkImportUser.loginMethods.stream() + .map(loginMethod -> loginMethod.superTokensUserId) + .anyMatch(s -> s.equals(userid))).findFirst().get().id; + bulkImportUserIdToErrorMessage.put(id, userIndexToError.get(userid).getMessage()); + } + } else { + //fail the whole batch + errorMessage[0] = exception.actualException.getMessage(); + for(BulkImportUser user : usersBatch){ + bulkImportUserIdToErrorMessage.put(user.id, errorMessage[0]); + } + } + } else if (e instanceof InvalidBulkImportDataException) { errorMessage[0] = ((InvalidBulkImportDataException) e).errors.toString(); } else if (e instanceof InvalidConfigException) { @@ -176,8 +194,8 @@ private void handleProcessUserExceptions(AppIdentifier appIdentifier, BulkImport try { baseTenantStorage.startTransaction(con -> { - baseTenantStorage.updateBulkImportUserStatus_Transaction(appIdentifier, con, user.id, - BulkImportStorage.BULK_IMPORT_USER_STATUS.FAILED, errorMessage[0]); + baseTenantStorage.updateMultipleBulkImportUsersStatusToError_Transaction(appIdentifier, con, + bulkImportUserIdToErrorMessage); return null; }); } catch (StorageTransactionLogicException e1) { diff --git a/src/main/java/io/supertokens/emailpassword/EmailPassword.java b/src/main/java/io/supertokens/emailpassword/EmailPassword.java index 7026d3d27..11b3ec431 100644 --- a/src/main/java/io/supertokens/emailpassword/EmailPassword.java +++ b/src/main/java/io/supertokens/emailpassword/EmailPassword.java @@ -258,12 +258,14 @@ public static ImportUserResponse createUserWithPasswordHash(TenantIdentifier ten } } - public static void createUsersWithPasswordHash(Storage storage, - List usersToImport) - throws StorageQueryException, DuplicateEmailException, TenantOrAppNotFoundException, - DuplicateUserIdException, StorageTransactionLogicException { + public static void createMultipleUsersWithPasswordHash(Storage storage, + List usersToImport) + throws StorageQueryException, TenantOrAppNotFoundException, StorageTransactionLogicException { EmailPasswordSQLStorage epStorage = StorageUtils.getEmailPasswordStorage(storage); - epStorage.signUpMultiple(usersToImport); + epStorage.startTransaction(con -> { + epStorage.signUpMultipleViaBulkImport_Transaction(con, usersToImport); + return null; + }); } @TestOnly diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index 4572049ad..5ca0ecbb1 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -233,7 +233,8 @@ public T startTransaction(TransactionLogic logic, TransactionIsolationLev tries++; try { return startTransactionHelper(logic); - } catch (SQLException | StorageQueryException | StorageTransactionLogicException | TenantOrAppNotFoundException e) { + } catch (SQLException | StorageQueryException | StorageTransactionLogicException | + TenantOrAppNotFoundException e) { if ((e instanceof SQLTransactionRollbackException || (e.getMessage() != null && e.getMessage().toLowerCase().contains("deadlock"))) && tries < 3) { @@ -245,8 +246,6 @@ public T startTransaction(TransactionLogic logic, TransactionIsolationLev throw (StorageQueryException) e; } else if (e instanceof StorageTransactionLogicException) { throw (StorageTransactionLogicException) e; - } else if (e instanceof TenantOrAppNotFoundException) { // TODO this should not be here. - throw new StorageTransactionLogicException(e); } throw new StorageQueryException(e); } @@ -818,13 +817,6 @@ public AuthRecipeUserInfo signUp(TenantIdentifier tenantIdentifier, String id, S } } - @Override - public void signUpMultiple(List users) - throws StorageQueryException, DuplicateUserIdException, DuplicateEmailException, - TenantOrAppNotFoundException, StorageTransactionLogicException { - // TODO - } - @Override public void addPasswordResetToken(AppIdentifier appIdentifier, PasswordResetTokenInfo passwordResetTokenInfo) throws StorageQueryException, UnknownUserIdException, DuplicatePasswordResetTokenException { @@ -937,6 +929,13 @@ public void deleteEmailPasswordUser_Transaction(TransactionConnection con, AppId } } + @Override + public void signUpMultipleViaBulkImport_Transaction(TransactionConnection connection, + List users) + throws StorageQueryException, StorageTransactionLogicException { + //TODO + } + @Override public void deleteExpiredEmailVerificationTokens() throws StorageQueryException { try { @@ -1196,16 +1195,16 @@ public void deleteThirdPartyUser_Transaction(TransactionConnection con, AppIdent @Override public void importThirdPartyUsers_Transaction(TransactionConnection con, - Collection usersToImport) - throws StorageQueryException { + List usersToImport) + throws StorageQueryException, StorageTransactionLogicException { // TODO } @Override public void importPasswordlessUsers_Transaction(TransactionConnection con, - Collection users) + List users) throws StorageQueryException { - //todo + // TODO } @Override @@ -2199,6 +2198,13 @@ public boolean doesRoleExist_Transaction(AppIdentifier appIdentifier, Transactio } } + @Override + public List doesMultipleRoleExist_Transaction(AppIdentifier appIdentifier, TransactionConnection con, + List roles) throws StorageQueryException { + // TODO + return List.of(); + } + @Override public void deleteAllRolesForUser_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId) throws StorageQueryException { diff --git a/src/main/java/io/supertokens/passwordless/Passwordless.java b/src/main/java/io/supertokens/passwordless/Passwordless.java index d2e6c1ef0..a317c6716 100644 --- a/src/main/java/io/supertokens/passwordless/Passwordless.java +++ b/src/main/java/io/supertokens/passwordless/Passwordless.java @@ -552,7 +552,7 @@ public static AuthRecipeUserInfo createPasswordlessUser(TenantIdentifier tenantI public static void createPasswordlessUsers(Storage storage, List importUsers) - throws TenantOrAppNotFoundException, StorageQueryException, RestartFlowException, + throws TenantOrAppNotFoundException, StorageQueryException, StorageTransactionLogicException { PasswordlessSQLStorage passwordlessStorage = StorageUtils.getPasswordlessStorage(storage); diff --git a/src/main/java/io/supertokens/storageLayer/StorageLayer.java b/src/main/java/io/supertokens/storageLayer/StorageLayer.java index e1ef206f9..630110a03 100644 --- a/src/main/java/io/supertokens/storageLayer/StorageLayer.java +++ b/src/main/java/io/supertokens/storageLayer/StorageLayer.java @@ -590,7 +590,11 @@ public static List findStorageAndUserIdMappingForBulkUs if (storages[0].getType() != STORAGE_TYPE.SQL) { // for non sql plugin, there will be only one storage as multitenancy is not supported assert storages.length == 1; - return Collections.singletonList(new StorageAndUserIdMapping(storages[0], null)); + List results = new ArrayList<>(); + for(String userId : userIds) { + results.add(new StorageAndUserIdMapping(storages[0], new UserIdMapping(userId, null, null))); + } + return results; } List allMappingsFromAllStorages = new ArrayList<>(); if (userIdType != UserIdType.ANY) { @@ -605,6 +609,9 @@ public static List findStorageAndUserIdMappingForBulkUs .filter(userIdMapping -> (userIdType == UserIdType.SUPERTOKENS && userIdMapping.superTokensUserId.equals(existingId)) || (userIdType == UserIdType.EXTERNAL && userIdMapping.externalUserId.equals(existingId)) ) .findFirst().orElse(null); + if(mappingForId == null && userIdType == UserIdType.SUPERTOKENS) { + mappingForId = new UserIdMapping(existingId, null, null); + } allMappingsFromAllStorages.add(new StorageAndUserIdMapping(storage, mappingForId)); } } diff --git a/src/main/java/io/supertokens/thirdparty/ThirdParty.java b/src/main/java/io/supertokens/thirdparty/ThirdParty.java index bde6ff129..57aa43819 100644 --- a/src/main/java/io/supertokens/thirdparty/ThirdParty.java +++ b/src/main/java/io/supertokens/thirdparty/ThirdParty.java @@ -43,7 +43,10 @@ import org.jetbrains.annotations.TestOnly; import javax.annotation.Nonnull; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; public class ThirdParty { @@ -353,9 +356,9 @@ public static SignInUpResponse createThirdPartyUser(TenantIdentifier tenantIdent } } - public static void createThirdPartyUsers(Storage storage, - Collection usersToImport) - throws StorageQueryException, StorageTransactionLogicException { + public static void createMultipleThirdPartyUsers(Storage storage, + List usersToImport) + throws StorageQueryException, StorageTransactionLogicException, TenantOrAppNotFoundException { ThirdPartySQLStorage tpStorage = StorageUtils.getThirdPartyStorage(storage); tpStorage.startTransaction(con -> { @@ -363,8 +366,6 @@ public static void createThirdPartyUsers(Storage storage, tpStorage.commitTransaction(con); return null; }); - - // TODO error handling } @Deprecated diff --git a/src/main/java/io/supertokens/totp/Totp.java b/src/main/java/io/supertokens/totp/Totp.java index 61158bce2..c8b705668 100644 --- a/src/main/java/io/supertokens/totp/Totp.java +++ b/src/main/java/io/supertokens/totp/Totp.java @@ -156,7 +156,6 @@ public static void createDevices(Main main, AppIdentifier appIdentifier, Storage totpStorage.startTransaction(con -> { totpStorage.createDevices_Transaction(con, appIdentifier, devices); totpStorage.commitTransaction(con); - System.out.println("Created TOTP devices"); return null; }); diff --git a/src/main/java/io/supertokens/useridmapping/UserIdMapping.java b/src/main/java/io/supertokens/useridmapping/UserIdMapping.java index d2c35ddf4..1e9f2c117 100644 --- a/src/main/java/io/supertokens/useridmapping/UserIdMapping.java +++ b/src/main/java/io/supertokens/useridmapping/UserIdMapping.java @@ -22,6 +22,7 @@ import io.supertokens.pluginInterface.StorageUtils; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; +import io.supertokens.pluginInterface.bulkimport.exceptions.BulkImportBatchInsertException; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.emailverification.EmailVerificationStorage; import io.supertokens.pluginInterface.exceptions.StorageQueryException; @@ -181,9 +182,7 @@ public static void createUserIdMapping(AppIdentifier appIdentifier, Storage[] st public static List createMultipleUserIdMappings(AppIdentifier appIdentifier, Storage[] storages, Map superTokensUserIdToExternalUserId, boolean force, boolean makeExceptionForEmailVerification) - throws UnknownSuperTokensUserIdException, - UserIdMappingAlreadyExistsException, StorageQueryException, ServletException, - TenantOrAppNotFoundException { + throws StorageQueryException { // We first need to check if the external user id exists across all app storages because we do not want // 2 users from different user pool but same app to point to same external user id. @@ -209,7 +208,6 @@ public static List createMultipleUserIdMappings(AppIden List mappingAndStoragesAsInvalid = StorageLayer.findStorageAndUserIdMappingForBulkUserImport( appIdentifier, storages, new ArrayList<>(superTokensUserIdToExternalUserId.values()), UserIdType.SUPERTOKENS); - //TODO does it matter which storage? Map> userIdsUsedInNonAuthRecipes = storages[0].findNonAuthRecipesWhereForUserIdsUsed(appIdentifier, new ArrayList<>(superTokensUserIdToExternalUserId.keySet())); @@ -311,6 +309,15 @@ public static List createMultipleUserIdMappings(AppIden } } } + Map errors = new HashMap<>(); + for(UserIdBulkMappingResult result : mappingResults){ + if(result.error != null) { + errors.put(result.supertokensUserId, result.error); + } + } + if(!errors.isEmpty()) { + throw new StorageQueryException(new BulkImportBatchInsertException("useridmapping errors", errors)); + } return mappingResults; } diff --git a/src/main/java/io/supertokens/userroles/UserRoles.java b/src/main/java/io/supertokens/userroles/UserRoles.java index 41811f1e5..478ca23c8 100644 --- a/src/main/java/io/supertokens/userroles/UserRoles.java +++ b/src/main/java/io/supertokens/userroles/UserRoles.java @@ -31,6 +31,8 @@ import org.jetbrains.annotations.TestOnly; import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; import java.util.Map; public class UserRoles { @@ -58,24 +60,28 @@ public static boolean addRoleToUser(Main main, TenantIdentifier tenantIdentifier } public static void addMultipleRolesToMultipleUsers(Main main, Storage storage, Map> rolesToUserByTenant) - throws StorageTransactionLogicException, UnknownRoleException, TenantOrAppNotFoundException { + throws StorageTransactionLogicException, TenantOrAppNotFoundException { // Roles are stored in public tenant storage and role to user mapping is stored in the tenant's storage // We do this because it's not straight forward to replicate roles to all storages of an app for(TenantIdentifier tenantIdentifier : rolesToUserByTenant.keySet()){ Storage appStorage = StorageLayer.getStorage( tenantIdentifier.toAppIdentifier().getAsPublicTenantIdentifier(), main); - // TODO!! -// if (!doesRoleExist(tenantIdentifier.toAppIdentifier(), appStorage, role)) { -// throw new UnknownRoleException(); -// } try { UserRolesSQLStorage userRolesStorage = StorageUtils.getUserRolesStorage(storage); userRolesStorage.startTransaction(con -> { - userRolesStorage.addRolesToUsers_Transaction(con, rolesToUserByTenant); - userRolesStorage.commitTransaction(con); - return null; + + List rolesFound = ((UserRolesSQLStorage) appStorage).doesMultipleRoleExist_Transaction( + tenantIdentifier.toAppIdentifier().getAsPublicTenantIdentifier().toAppIdentifier(), + con, new ArrayList<>(rolesToUserByTenant.get(tenantIdentifier).values())); + + if(rolesFound.contains(Boolean.FALSE)){ + throw new StorageTransactionLogicException(new UnknownRoleException()); + } + userRolesStorage.addRolesToUsers_Transaction(con, rolesToUserByTenant); + userRolesStorage.commitTransaction(con); + return null; }); } catch (StorageQueryException e) { diff --git a/src/main/java/io/supertokens/webserver/api/bulkimport/ImportUserAPI.java b/src/main/java/io/supertokens/webserver/api/bulkimport/ImportUserAPI.java index a6f9d3c8f..f21dd809c 100644 --- a/src/main/java/io/supertokens/webserver/api/bulkimport/ImportUserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/bulkimport/ImportUserAPI.java @@ -43,59 +43,59 @@ import java.io.IOException; public class ImportUserAPI extends WebserverAPI { - public ImportUserAPI(Main main) { - super(main, ""); - } + public ImportUserAPI(Main main) { + super(main, ""); + } - @Override - public String getPath() { - return "/bulk-import/import"; - } + @Override + public String getPath() { + return "/bulk-import/import"; + } - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - // API is app specific + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + // API is app specific - if (StorageLayer.isInMemDb(main)) { - throw new ServletException(new BadRequestException("This API is not supported in the in-memory database.")); - } + if (StorageLayer.isInMemDb(main)) { + throw new ServletException(new BadRequestException("This API is not supported in the in-memory database.")); + } - JsonObject jsonUser = InputParser.parseJsonObjectOrThrowError(req); + JsonObject jsonUser = InputParser.parseJsonObjectOrThrowError(req); - AppIdentifier appIdentifier = null; - Storage storage = null; - String[] allUserRoles = null; + AppIdentifier appIdentifier = null; + Storage storage = null; + String[] allUserRoles = null; - try { - appIdentifier = getAppIdentifier(req); - storage = enforcePublicTenantAndGetPublicTenantStorage(req); - allUserRoles = StorageUtils.getUserRolesStorage(storage).getRoles(appIdentifier); - } catch (TenantOrAppNotFoundException | BadPermissionException | StorageQueryException e) { - throw new ServletException(e); - } + try { + appIdentifier = getAppIdentifier(req); + storage = enforcePublicTenantAndGetPublicTenantStorage(req); + allUserRoles = StorageUtils.getUserRolesStorage(storage).getRoles(appIdentifier); + } catch (TenantOrAppNotFoundException | BadPermissionException | StorageQueryException e) { + throw new ServletException(e); + } - BulkImportUserUtils bulkImportUserUtils = new BulkImportUserUtils(allUserRoles); + BulkImportUserUtils bulkImportUserUtils = new BulkImportUserUtils(allUserRoles); - try { - BulkImportUser user = bulkImportUserUtils.createBulkImportUserFromJSON(main, appIdentifier, jsonUser, - Utils.getUUID()); + try { + BulkImportUser user = bulkImportUserUtils.createBulkImportUserFromJSON(main, appIdentifier, jsonUser, + Utils.getUUID()); - AuthRecipeUserInfo importedUser = BulkImport.importUser(main, appIdentifier, user); + AuthRecipeUserInfo importedUser = BulkImport.importUser(main, appIdentifier, user); - JsonObject result = new JsonObject(); - result.addProperty("status", "OK"); - result.add("user", importedUser.toJson()); - super.sendJsonResponse(200, result, resp); - } catch (io.supertokens.bulkimport.exceptions.InvalidBulkImportDataException e) { - JsonArray errors = e.errors.stream() - .map(JsonPrimitive::new) - .collect(JsonArray::new, JsonArray::add, JsonArray::addAll); + JsonObject result = new JsonObject(); + result.addProperty("status", "OK"); + result.add("user", importedUser.toJson()); + super.sendJsonResponse(200, result, resp); + } catch (io.supertokens.bulkimport.exceptions.InvalidBulkImportDataException e) { + JsonArray errors = e.errors.stream() + .map(JsonPrimitive::new) + .collect(JsonArray::new, JsonArray::add, JsonArray::addAll); - JsonObject errorResponseJson = new JsonObject(); - errorResponseJson.add("errors", errors); - throw new ServletException(new WebserverAPI.BadRequestException(errorResponseJson.toString())); - } catch (StorageQueryException | TenantOrAppNotFoundException | InvalidConfigException | DbInitException e) { - throw new ServletException(e); + JsonObject errorResponseJson = new JsonObject(); + errorResponseJson.add("errors", errors); + throw new ServletException(new WebserverAPI.BadRequestException(errorResponseJson.toString())); + } catch (StorageQueryException | TenantOrAppNotFoundException | InvalidConfigException | DbInitException e) { + throw new ServletException(e); + } } - } } diff --git a/src/test/java/io/supertokens/test/bulkimport/BulkImportFlowTest.java b/src/test/java/io/supertokens/test/bulkimport/BulkImportFlowTest.java index 233c7e1f8..509fa5e23 100644 --- a/src/test/java/io/supertokens/test/bulkimport/BulkImportFlowTest.java +++ b/src/test/java/io/supertokens/test/bulkimport/BulkImportFlowTest.java @@ -78,8 +78,8 @@ public void testWithOneMillionUsers() throws Exception { setFeatureFlags(main, new EE_FEATURES[] { EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY, EE_FEATURES.MFA }); - int NUMBER_OF_USERS_TO_UPLOAD = 1000000; // million - //int NUMBER_OF_USERS_TO_UPLOAD = 10000; + //int NUMBER_OF_USERS_TO_UPLOAD = 1000000; // million + int NUMBER_OF_USERS_TO_UPLOAD = 10000; int parallelism_set_to = Config.getConfig(main).getBulkMigrationParallelism(); System.out.println("Number of users to be imported with bulk import: " + NUMBER_OF_USERS_TO_UPLOAD); System.out.println("Worker threads: " + parallelism_set_to); @@ -108,7 +108,7 @@ public void testWithOneMillionUsers() throws Exception { long processingStartedTime = System.currentTimeMillis(); // Starting the processing cronjob here to be able to measure the runtime - startBulkImportCronjob(main, 6000); + startBulkImportCronjob(main, 8000); System.out.println("CronJob started"); // wait for the cron job to process them @@ -117,7 +117,7 @@ public void testWithOneMillionUsers() throws Exception { // Note2: the successfully processed users get deleted from the bulk_import_users table { long count = NUMBER_OF_USERS_TO_UPLOAD; - while(count != 0) { + while(true) { JsonObject response = loadBulkImportUsersCountWithStatus(main, null); assertEquals("OK", response.get("status").getAsString()); count = response.get("count").getAsLong(); @@ -134,6 +134,9 @@ public void testWithOneMillionUsers() throws Exception { long elapsedSeconds = (System.currentTimeMillis() - processingStartedTime) / 1000; System.out.println("Elapsed time: " + elapsedSeconds + " seconds, (" + elapsedSeconds / 3600 + " hours)"); + if(count == 0 ){ + break; + } Thread.sleep(60000); // one minute } } @@ -154,10 +157,91 @@ public void testWithOneMillionUsers() throws Exception { int failedImportedUsersNumber = loadBulkImportUsersCountWithStatus(main, BulkImportStorage.BULK_IMPORT_USER_STATUS.FAILED).get("count").getAsInt(); int usersInCore = loadUsersCount(main).get("count").getAsInt(); assertEquals(NUMBER_OF_USERS_TO_UPLOAD, usersInCore + failedImportedUsersNumber); + assertEquals(NUMBER_OF_USERS_TO_UPLOAD, usersInCore); } } + @Test + public void testBatchWithDuplicates() throws Exception { + String[] args = {"../"}; + + // set processing thread number + Utils.setValueInConfig("bulk_migration_parallelism", "12"); + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + Main main = process.getProcess(); + + setFeatureFlags(main, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY, EE_FEATURES.MFA}); + + int NUMBER_OF_USERS_TO_UPLOAD = 2; + + if (StorageLayer.getBaseStorage(main).getType() != STORAGE_TYPE.SQL || StorageLayer.isInMemDb(main)) { + return; + } + + // Create user roles before inserting bulk users + UserRoles.createNewRoleOrModifyItsPermissions(main, "role1", null); + UserRoles.createNewRoleOrModifyItsPermissions(main, "role2", null); + + // upload a bunch of users through the API + JsonObject usersJson = generateUsersJson(NUMBER_OF_USERS_TO_UPLOAD, 0); + + usersJson.get("users").getAsJsonArray().add(generateUsersJson(1, 0).get("users").getAsJsonArray().get(0).getAsJsonObject()); + usersJson.get("users").getAsJsonArray().add(generateUsersJson(1, 1).get("users").getAsJsonArray().get(0).getAsJsonObject()); + + JsonObject response = uploadBulkImportUsersJson(main, usersJson); + assertEquals("OK", response.get("status").getAsString()); + + // Starting the processing cronjob here to be able to measure the runtime + startBulkImportCronjob(main, 8000); + + // wait for the cron job to process them + // periodically check the remaining unprocessed users + // Note1: the cronjob starts the processing automatically + // Note2: the successfully processed users get deleted from the bulk_import_users table + + long count = NUMBER_OF_USERS_TO_UPLOAD; + int failedUsersNumber = 0; + while (true) { + response = loadBulkImportUsersCountWithStatus(main, null); + assertEquals("OK", response.get("status").getAsString()); + count = response.get("count").getAsLong(); + int newUsersNumber = loadBulkImportUsersCountWithStatus(main, + BulkImportStorage.BULK_IMPORT_USER_STATUS.NEW).get("count").getAsInt(); + failedUsersNumber = loadBulkImportUsersCountWithStatus(main, + BulkImportStorage.BULK_IMPORT_USER_STATUS.FAILED).get("count").getAsInt(); + int processingUsersNumber = loadBulkImportUsersCountWithStatus(main, + BulkImportStorage.BULK_IMPORT_USER_STATUS.PROCESSING).get("count").getAsInt(); + + count = newUsersNumber + processingUsersNumber; + if(count == 0) { + break; + } + Thread.sleep(5000); // 5 seconds + } + + //print failed users + JsonObject failedUsersLs = loadBulkImportUsersWithStatus(main, + BulkImportStorage.BULK_IMPORT_USER_STATUS.FAILED); + if (failedUsersLs.has("users")) { + System.out.println(failedUsersLs.get("users")); + } + System.out.println("Failed Users: " + failedUsersLs); + System.out.println("Failed Users Number: " + failedUsersNumber); + + // after processing finished, make sure every user got processed correctly + int failedImportedUsersNumber = loadBulkImportUsersCountWithStatus(main, + BulkImportStorage.BULK_IMPORT_USER_STATUS.FAILED).get("count").getAsInt(); + int usersInCore = loadUsersCount(main).get("count").getAsInt(); + System.out.println("Users in core: " + usersInCore); + assertEquals(NUMBER_OF_USERS_TO_UPLOAD + 2, usersInCore + failedImportedUsersNumber); + assertEquals(2, failedImportedUsersNumber); + + } + @Test public void testFirstLazyImportAfterBulkImport() throws Exception { String[] args = { "../" }; @@ -172,7 +256,7 @@ public void testFirstLazyImportAfterBulkImport() throws Exception { setFeatureFlags(main, new EE_FEATURES[] { EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY, EE_FEATURES.MFA }); - int NUMBER_OF_USERS_TO_UPLOAD = 100; + int NUMBER_OF_USERS_TO_UPLOAD = 1000; int parallelism_set_to = Config.getConfig(main).getBulkMigrationParallelism(); System.out.println("Number of users to be imported with bulk import: " + NUMBER_OF_USERS_TO_UPLOAD); System.out.println("Worker threads: " + parallelism_set_to); @@ -266,6 +350,133 @@ public void testFirstLazyImportAfterBulkImport() throws Exception { stopBulkImportCronjob(main); } + @Test + public void testLazyImport() throws Exception { + String[] args = { "../" }; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + Main main = process.getProcess(); + + setFeatureFlags(main, new EE_FEATURES[] { + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY, EE_FEATURES.MFA }); + + int NUMBER_OF_USERS_TO_UPLOAD = 100; + int parallelism_set_to = Config.getConfig(main).getBulkMigrationParallelism(); + System.out.println("Number of users to be imported with bulk import: " + NUMBER_OF_USERS_TO_UPLOAD); + System.out.println("Worker threads: " + parallelism_set_to); + + if (StorageLayer.getBaseStorage(main).getType() != STORAGE_TYPE.SQL || StorageLayer.isInMemDb(main)) { + return; + } + + // Create user roles before inserting bulk users + { + UserRoles.createNewRoleOrModifyItsPermissions(main, "role1", null); + UserRoles.createNewRoleOrModifyItsPermissions(main, "role2", null); + } + + // create users + JsonObject allUsersJson = generateUsersJson(NUMBER_OF_USERS_TO_UPLOAD, 0); + + // lazy import most of the users + int successfully_lazy_imported = 0; + for (int i = 0; i < allUsersJson.get("users").getAsJsonArray().size(); i++) { + JsonObject userToImportLazy = allUsersJson.get("users").getAsJsonArray().get(i).getAsJsonObject(); + JsonObject lazyImportResponse = lazyImportUser(main, userToImportLazy); + assertEquals("OK", lazyImportResponse.get("status").getAsString()); + assertNotNull(lazyImportResponse.get("user")); + successfully_lazy_imported++; + System.out.println(i + "th lazy imported"); + } + + // expect: lazy imported users are already there, duplicate.. errors + // expect: not lazy imported users are imported successfully + { + assertEquals(NUMBER_OF_USERS_TO_UPLOAD, successfully_lazy_imported ); + int usersInCore = loadUsersCount(main).get("count").getAsInt(); + assertEquals(NUMBER_OF_USERS_TO_UPLOAD, usersInCore); + } + } + + @Test + public void testLazyImportUnknownRecipeLoginMethod() throws Exception { + String[] args = { "../" }; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + Main main = process.getProcess(); + + setFeatureFlags(main, new EE_FEATURES[] { + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY, EE_FEATURES.MFA }); + + if (StorageLayer.getBaseStorage(main).getType() != STORAGE_TYPE.SQL || StorageLayer.isInMemDb(main)) { + return; + } + + // Create user roles before inserting bulk users + { + UserRoles.createNewRoleOrModifyItsPermissions(main, "role1", null); + UserRoles.createNewRoleOrModifyItsPermissions(main, "role2", null); + } + + // create users + JsonObject allUsersJson = generateUsersJson(1, 0); + allUsersJson.get("users").getAsJsonArray().get(0).getAsJsonObject().get("loginMethods") + .getAsJsonArray().get(0).getAsJsonObject().addProperty("recipeId", "not-existing-recipe"); + + JsonObject userToImportLazy = allUsersJson.get("users").getAsJsonArray().get(0).getAsJsonObject(); + try { + JsonObject lazyImportResponse = lazyImportUser(main, userToImportLazy); + } catch (HttpResponseException expected) { + assertEquals(400, expected.statusCode); + assertNotNull(expected.getMessage()); + assertEquals("Http error. Status Code: 400. Message: {\"errors\":[\"Invalid recipeId for loginMethod. Pass one of emailpassword, thirdparty or, passwordless!\"]}", + expected.getMessage()); + } + } + + @Test + public void testLazyImportDuplicatesFail() throws Exception { + String[] args = { "../" }; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + Main main = process.getProcess(); + + setFeatureFlags(main, new EE_FEATURES[] { + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY, EE_FEATURES.MFA }); + + if (StorageLayer.getBaseStorage(main).getType() != STORAGE_TYPE.SQL || StorageLayer.isInMemDb(main)) { + return; + } + + // Create user roles before inserting bulk users + { + UserRoles.createNewRoleOrModifyItsPermissions(main, "role1", null); + UserRoles.createNewRoleOrModifyItsPermissions(main, "role2", null); + } + + // create users + JsonObject allUsersJson = generateUsersJson(1, 0); + + JsonObject userToImportLazy = allUsersJson.get("users").getAsJsonArray().get(0).getAsJsonObject(); + JsonObject lazyImportResponse = lazyImportUser(main, userToImportLazy); + assertEquals("OK", lazyImportResponse.get("status").getAsString()); + assertNotNull(lazyImportResponse.get("user")); + + int usersInCore = loadUsersCount(main).get("count").getAsInt(); + assertEquals(1, usersInCore); + + JsonObject userToImportLazyAgain = allUsersJson.get("users").getAsJsonArray().get(0).getAsJsonObject(); + try { + JsonObject lazyImportResponseTwo = lazyImportUser(main, userToImportLazy); + } catch (HttpResponseException expected) { + assertEquals(400, expected.statusCode); + System.out.println(expected.getMessage()); + } + } + private static JsonObject lazyImportUser(Main main, JsonObject user) throws HttpResponseException, IOException { return HttpRequestForTesting.sendJsonPOSTRequest(main, "", @@ -323,9 +534,9 @@ private static JsonObject generateUsersJson(int numberOfUsers, int startIndex) { Random random = new Random(); JsonArray loginMethodsArray = new JsonArray(); - if(random.nextInt(2) == 0){ + //if(random.nextInt(2) == 0){ loginMethodsArray.add(createEmailLoginMethod(email, tenanatIds)); - } + //} if(random.nextInt(2) == 0){ loginMethodsArray.add(createThirdPartyLoginMethod(email, tenanatIds)); }