Skip to content

Commit

Permalink
fix: PR changes
Browse files Browse the repository at this point in the history
  • Loading branch information
anku255 committed Feb 20, 2024
1 parent 32625d4 commit d5f0c67
Show file tree
Hide file tree
Showing 6 changed files with 265 additions and 146 deletions.
4 changes: 2 additions & 2 deletions src/main/java/io/supertokens/bulkimport/BulkImport.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,11 @@ public static BulkImportUserPaginationContainer getUsers(AppIdentifierWithStorag

if (paginationToken == null) {
users = appIdentifierWithStorage.getBulkImportStorage()
.getBulkImportUsers(appIdentifierWithStorage, limit + 1, status, null);
.getBulkImportUsers(appIdentifierWithStorage, limit + 1, status, null, null);
} else {
BulkImportUserPaginationToken tokenInfo = BulkImportUserPaginationToken.extractTokenInfo(paginationToken);
users = appIdentifierWithStorage.getBulkImportStorage()
.getBulkImportUsers(appIdentifierWithStorage, limit + 1, status, tokenInfo.bulkImportUserId);
.getBulkImportUsers(appIdentifierWithStorage, limit + 1, status, tokenInfo.bulkImportUserId, tokenInfo.createdAt);
}

String nextPaginationToken = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ public class BulkImportUserPaginationToken {
public final String bulkImportUserId;
public final long createdAt;

public BulkImportUserPaginationToken(String bulkImportUserId, long timeJoined) {
public BulkImportUserPaginationToken(String bulkImportUserId, long createdAt) {
this.bulkImportUserId = bulkImportUserId;
this.createdAt = timeJoined;
this.createdAt = createdAt;
}

public static BulkImportUserPaginationToken extractTokenInfo(String token) throws InvalidTokenException {
Expand All @@ -35,8 +35,8 @@ public static BulkImportUserPaginationToken extractTokenInfo(String token) throw
throw new InvalidTokenException();
}
String bulkImportUserId = splitDecodedToken[0];
long timeJoined = Long.parseLong(splitDecodedToken[1]);
return new BulkImportUserPaginationToken(bulkImportUserId, timeJoined);
long createdAt = Long.parseLong(splitDecodedToken[1]);
return new BulkImportUserPaginationToken(bulkImportUserId, createdAt);
} catch (Exception e) {
throw new InvalidTokenException();
}
Expand Down
215 changes: 137 additions & 78 deletions src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,40 +17,56 @@
package io.supertokens.bulkimport;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;

import io.supertokens.Main;
import io.supertokens.bulkimport.exceptions.InvalidBulkImportDataException;
import io.supertokens.config.CoreConfig;
import io.supertokens.emailpassword.PasswordHashingUtils;
import io.supertokens.emailpassword.exceptions.UnsupportedPasswordHashingFormatException;
import io.supertokens.featureflag.EE_FEATURES;
import io.supertokens.featureflag.FeatureFlag;
import io.supertokens.multitenancy.Multitenancy;
import io.supertokens.pluginInterface.bulkimport.BulkImportUser;
import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod;
import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod.EmailPasswordLoginMethod;
import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod.ThirdPartyLoginMethod;
import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod.PasswordlessLoginMethod;
import io.supertokens.pluginInterface.bulkimport.BulkImportUser.TotpDevice;
import io.supertokens.pluginInterface.utils.JsonValidatorUtils.ValueType;
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
import io.supertokens.pluginInterface.multitenancy.AppIdentifier;
import io.supertokens.pluginInterface.multitenancy.TenantConfig;
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
import io.supertokens.utils.Utils;
import io.supertokens.utils.JsonValidatorUtils.ValueType;

import static io.supertokens.pluginInterface.utils.JsonValidatorUtils.parseAndValidateField;
import static io.supertokens.pluginInterface.utils.JsonValidatorUtils.validateJsonFieldType;
import static io.supertokens.utils.JsonValidatorUtils.parseAndValidateField;
import static io.supertokens.utils.JsonValidatorUtils.validateJsonFieldType;

public class BulkImportUserUtils {
public static BulkImportUser createBulkImportUserFromJSON(JsonObject userData, String id) throws InvalidBulkImportDataException {
public static BulkImportUser createBulkImportUserFromJSON(Main main, AppIdentifier appIdentifier, JsonObject userData, String id)
throws InvalidBulkImportDataException, StorageQueryException, TenantOrAppNotFoundException {
List<String> errors = new ArrayList<>();

String externalUserId = parseAndValidateField(userData, "externalUserId", ValueType.STRING, false, String.class, errors, ".");
JsonObject userMetadata = parseAndValidateField(userData, "userMetadata", ValueType.OBJECT, false, JsonObject.class, errors, ".");
List<String> userRoles = getParsedUserRoles(userData, errors);
String externalUserId = parseAndValidateField(userData, "externalUserId", ValueType.STRING, false, String.class,
errors, ".");
JsonObject userMetadata = parseAndValidateField(userData, "userMetadata", ValueType.OBJECT, false,
JsonObject.class, errors, ".");
List<String> userRoles = getParsedUserRoles(userData, errors);
List<TotpDevice> totpDevices = getParsedTotpDevices(userData, errors);
List<LoginMethod> loginMethods = getParsedLoginMethods(userData, errors);
List<LoginMethod> loginMethods = getParsedLoginMethods(main, appIdentifier, userData, errors);

if (!errors.isEmpty()) {
throw new InvalidBulkImportDataException(errors);
}
return new BulkImportUser(id, externalUserId, userMetadata, userRoles, totpDevices, loginMethods);
}
}

private static List<String> getParsedUserRoles(JsonObject userData, List<String> errors) {
JsonArray jsonUserRoles = parseAndValidateField(userData, "roles", ValueType.ARRAY_OF_STRING,
Expand Down Expand Up @@ -88,80 +104,123 @@ private static List<TotpDevice> getParsedTotpDevices(JsonObject userData, List<S
errors.add("skew should be >= 0 for a totp device.");
}

if(deviceName != null) {
if (deviceName != null) {
deviceName = deviceName.trim();
}
totpDevices.add(new TotpDevice(secretKey, period.intValue(), skew.intValue(), deviceName));
}
return totpDevices;
}

private static List<LoginMethod> getParsedLoginMethods(JsonObject userData, List<String> errors) {
JsonArray jsonLoginMethods = parseAndValidateField(userData, "loginMethods", ValueType.ARRAY_OF_OBJECT, true, JsonArray.class, errors, ".");

if (jsonLoginMethods == null) {
return new ArrayList<>();
}

if (jsonLoginMethods.size() == 0) {
errors.add("At least one loginMethod is required.");
return new ArrayList<>();
}

Boolean hasPrimaryLoginMethod = false;

List<LoginMethod> loginMethods = new ArrayList<>();
for (JsonElement jsonLoginMethod : jsonLoginMethods) {
JsonObject jsonLoginMethodObj = jsonLoginMethod.getAsJsonObject();

if (validateJsonFieldType(jsonLoginMethodObj, "isPrimary", ValueType.BOOLEAN)) {
if (jsonLoginMethodObj.get("isPrimary").getAsBoolean()) {
if (hasPrimaryLoginMethod) {
errors.add("No two loginMethods can have isPrimary as true.");
}
hasPrimaryLoginMethod = true;
}
}

String recipeId = parseAndValidateField(jsonLoginMethodObj, "recipeId", ValueType.STRING, true, String.class, errors, " for a loginMethod.");
String tenantId = parseAndValidateField(jsonLoginMethodObj, "tenantId", ValueType.STRING, false, String.class, errors, " for a loginMethod.");
Boolean isVerified = parseAndValidateField(jsonLoginMethodObj, "isVerified", ValueType.BOOLEAN, false, Boolean.class, errors, " for a loginMethod.");
Boolean isPrimary = parseAndValidateField(jsonLoginMethodObj, "isPrimary", ValueType.BOOLEAN, false, Boolean.class, errors, " for a loginMethod.");
Number timeJoined = parseAndValidateField(jsonLoginMethodObj, "timeJoinedInMSSinceEpoch", ValueType.NUMBER, false, Number.class, errors, " for a loginMethod");
Long timeJoinedInMSSinceEpoch = timeJoined != null ? timeJoined.longValue() : 0;

if ("emailpassword".equals(recipeId)) {
String email = parseAndValidateField(jsonLoginMethodObj, "email", ValueType.STRING, true, String.class, errors, " for an emailpassword recipe.");
String passwordHash = parseAndValidateField(jsonLoginMethodObj, "passwordHash", ValueType.STRING, true, String.class, errors, " for an emailpassword recipe.");
String hashingAlgorithm = parseAndValidateField(jsonLoginMethodObj, "hashingAlgorithm", ValueType.STRING, true, String.class, errors, " for an emailpassword recipe.");

email = email != null ? Utils.normaliseEmail(email) : null;
hashingAlgorithm = hashingAlgorithm != null ? hashingAlgorithm.trim().toUpperCase() : null;

EmailPasswordLoginMethod emailPasswordLoginMethod = new EmailPasswordLoginMethod(email, passwordHash, hashingAlgorithm);
loginMethods.add(new LoginMethod(tenantId, recipeId, isVerified, isPrimary, timeJoinedInMSSinceEpoch, emailPasswordLoginMethod, null, null));
} else if ("thirdparty".equals(recipeId)) {
String email = parseAndValidateField(jsonLoginMethodObj, "email", ValueType.STRING, true, String.class, errors, " for a thirdparty recipe.");
String thirdPartyId = parseAndValidateField(jsonLoginMethodObj, "thirdPartyId", ValueType.STRING, true, String.class, errors, " for a thirdparty recipe.");
String thirdPartyUserId = parseAndValidateField(jsonLoginMethodObj, "thirdPartyUserId", ValueType.STRING, true, String.class, errors, " for a thirdparty recipe.");

email = email != null ? Utils.normaliseEmail(email) : null;

ThirdPartyLoginMethod thirdPartyLoginMethod = new ThirdPartyLoginMethod(email, thirdPartyId, thirdPartyUserId);
loginMethods.add(new LoginMethod(tenantId, recipeId, isVerified, isPrimary, timeJoinedInMSSinceEpoch, null, thirdPartyLoginMethod, null));
} else if ("passwordless".equals(recipeId)) {
String email = parseAndValidateField(jsonLoginMethodObj, "email", ValueType.STRING, false, String.class, errors, " for a passwordless recipe.");
String phoneNumber = parseAndValidateField(jsonLoginMethodObj, "phoneNumber", ValueType.STRING, false, String.class, errors, " for a passwordless recipe.");

email = email != null ? Utils.normaliseEmail(email) : null;
phoneNumber = Utils.normalizeIfPhoneNumber(phoneNumber);

PasswordlessLoginMethod passwordlessLoginMethod = new PasswordlessLoginMethod(email, phoneNumber);
loginMethods.add(new LoginMethod(tenantId, recipeId, isVerified, isPrimary, timeJoinedInMSSinceEpoch, null, null, passwordlessLoginMethod));
} else if (recipeId != null) {
errors.add("Invalid recipeId for loginMethod. Pass one of emailpassword, thirdparty or, passwordless!");
}
}
return loginMethods;
}
private static List<LoginMethod> getParsedLoginMethods(Main main, AppIdentifier appIdentifier, JsonObject userData, List<String> errors)
throws StorageQueryException, TenantOrAppNotFoundException {
JsonArray jsonLoginMethods = parseAndValidateField(userData, "loginMethods", ValueType.ARRAY_OF_OBJECT, true, JsonArray.class, errors, ".");

if (jsonLoginMethods == null) {
return new ArrayList<>();
}

if (jsonLoginMethods.size() == 0) {
errors.add("At least one loginMethod is required.");
return new ArrayList<>();
}

boolean hasPrimaryLoginMethod = false;

List<LoginMethod> loginMethods = new ArrayList<>();
for (JsonElement jsonLoginMethod : jsonLoginMethods) {
JsonObject jsonLoginMethodObj = jsonLoginMethod.getAsJsonObject();

if (validateJsonFieldType(jsonLoginMethodObj, "isPrimary", ValueType.BOOLEAN)) {
if (jsonLoginMethodObj.get("isPrimary").getAsBoolean()) {
if (hasPrimaryLoginMethod) {
errors.add("No two loginMethods can have isPrimary as true.");
}
hasPrimaryLoginMethod = true;
}
}

String recipeId = parseAndValidateField(jsonLoginMethodObj, "recipeId", ValueType.STRING, true, String.class, errors, " for a loginMethod.");
String tenantId = parseAndValidateField(jsonLoginMethodObj, "tenantId", ValueType.STRING, false, String.class, errors, " for a loginMethod.");
Boolean isVerified = parseAndValidateField(jsonLoginMethodObj, "isVerified", ValueType.BOOLEAN, false, Boolean.class, errors, " for a loginMethod.");
Boolean isPrimary = parseAndValidateField(jsonLoginMethodObj, "isPrimary", ValueType.BOOLEAN, false, Boolean.class, errors, " for a loginMethod.");
Number timeJoined = parseAndValidateField(jsonLoginMethodObj, "timeJoinedInMSSinceEpoch", ValueType.NUMBER, false, Number.class, errors, " for a loginMethod");
long timeJoinedInMSSinceEpoch = timeJoined != null ? timeJoined.longValue() : 0;

validateTenantId(main, appIdentifier, tenantId, recipeId, errors);

if ("emailpassword".equals(recipeId)) {
String email = parseAndValidateField(jsonLoginMethodObj, "email", ValueType.STRING, true, String.class, errors, " for an emailpassword recipe.");
String passwordHash = parseAndValidateField(jsonLoginMethodObj, "passwordHash", ValueType.STRING, true, String.class, errors, " for an emailpassword recipe.");
String hashingAlgorithm = parseAndValidateField(jsonLoginMethodObj, "hashingAlgorithm", ValueType.STRING, true, String.class, errors, " for an emailpassword recipe.");

email = email != null ? Utils.normaliseEmail(email) : null;
hashingAlgorithm = hashingAlgorithm != null ? hashingAlgorithm.trim().toUpperCase() : null;

EmailPasswordLoginMethod emailPasswordLoginMethod = new EmailPasswordLoginMethod(email, passwordHash, hashingAlgorithm);

validatePasswordHashingAlgorithm(main, appIdentifier, emailPasswordLoginMethod, errors);

loginMethods.add(new LoginMethod(tenantId, recipeId, isVerified, isPrimary, timeJoinedInMSSinceEpoch, emailPasswordLoginMethod, null, null));
} else if ("thirdparty".equals(recipeId)) {
String email = parseAndValidateField(jsonLoginMethodObj, "email", ValueType.STRING, true, String.class, errors, " for a thirdparty recipe.");
String thirdPartyId = parseAndValidateField(jsonLoginMethodObj, "thirdPartyId", ValueType.STRING, true, String.class, errors, " for a thirdparty recipe.");
String thirdPartyUserId = parseAndValidateField(jsonLoginMethodObj, "thirdPartyUserId", ValueType.STRING, true, String.class, errors, " for a thirdparty recipe.");

email = email != null ? Utils.normaliseEmail(email) : null;

ThirdPartyLoginMethod thirdPartyLoginMethod = new ThirdPartyLoginMethod(email, thirdPartyId, thirdPartyUserId);
loginMethods.add(new LoginMethod(tenantId, recipeId, isVerified, isPrimary, timeJoinedInMSSinceEpoch, null, thirdPartyLoginMethod, null));
} else if ("passwordless".equals(recipeId)) {
String email = parseAndValidateField(jsonLoginMethodObj, "email", ValueType.STRING, false, String.class, errors, " for a passwordless recipe.");
String phoneNumber = parseAndValidateField(jsonLoginMethodObj, "phoneNumber", ValueType.STRING, false, String.class, errors, " for a passwordless recipe.");

email = email != null ? Utils.normaliseEmail(email) : null;
phoneNumber = Utils.normalizeIfPhoneNumber(phoneNumber);

PasswordlessLoginMethod passwordlessLoginMethod = new PasswordlessLoginMethod(email, phoneNumber);
loginMethods.add(new LoginMethod(tenantId, recipeId, isVerified, isPrimary, timeJoinedInMSSinceEpoch, null, null, passwordlessLoginMethod));
} else if (recipeId != null) {
errors.add("Invalid recipeId for loginMethod. Pass one of emailpassword, thirdparty or, passwordless!");
}
}
return loginMethods;
}

private static void validateTenantId(Main main, AppIdentifier appIdentifier, String tenantId, String recipeId, List<String> errors)
throws StorageQueryException, TenantOrAppNotFoundException {
if (tenantId == null || tenantId.equals(TenantIdentifier.DEFAULT_TENANT_ID)) {
return;
}

if (Arrays.stream(FeatureFlag.getInstance(main, appIdentifier).getEnabledFeatures())
.noneMatch(t -> t == EE_FEATURES.MULTI_TENANCY)) {
errors.add("Multitenancy must be enabled before importing users to a different tenant.");
return;
}

TenantConfig[] allTenantConfigs = Multitenancy.getAllTenantsForApp(appIdentifier, main);
ArrayList<String> validTenantIds = new ArrayList<>();
Arrays.stream(allTenantConfigs)
.forEach(tenantConfig -> validTenantIds.add(tenantConfig.tenantIdentifier.getTenantId()));

if (!validTenantIds.contains(tenantId)) {
errors.add("Invalid tenantId: " + tenantId + " for " + recipeId + " recipe.");
}
}

private static void validatePasswordHashingAlgorithm(Main main, AppIdentifier appIdentifier, EmailPasswordLoginMethod emailPasswordLoginMethod, List<String> errors) throws TenantOrAppNotFoundException {
if(emailPasswordLoginMethod.hashingAlgorithm == null || emailPasswordLoginMethod.passwordHash == null) {
return;
}

try {
CoreConfig.PASSWORD_HASHING_ALG passwordHashingAlgorithm = CoreConfig.PASSWORD_HASHING_ALG.valueOf(emailPasswordLoginMethod.hashingAlgorithm);
PasswordHashingUtils.assertSuperTokensSupportInputPasswordHashFormat(appIdentifier, main, emailPasswordLoginMethod.passwordHash, passwordHashingAlgorithm);
} catch (UnsupportedPasswordHashingFormatException e) {
errors.add(e.getMessage());
} catch (IllegalArgumentException e) {
errors.add("Invalid hashingAlgorithm for emailpassword recipe. Pass one of bcrypt, argon2 or, firebase_scrypt!");
}
}
}
Loading

0 comments on commit d5f0c67

Please sign in to comment.