From 388050d4ea71728927a24447bda5d676521cff6f Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Tue, 17 Oct 2023 18:07:32 +0530 Subject: [PATCH] fix: mfa multitenancy updates --- .../multitenancy/TenantConfigSQLHelper.java | 3 ++- .../api/multitenancy/BaseCreateOrUpdate.java | 9 +++++---- .../api/multitenancy/CreateOrUpdateAppAPI.java | 18 +++++++++++++----- .../CreateOrUpdateConnectionUriDomainAPI.java | 18 +++++++++++++----- .../CreateOrUpdateTenantOrGetTenantAPI.java | 18 +++++++++++++----- .../test/multitenant/AppTenantUserTest.java | 6 ++---- .../api/TestTenantUserAssociation.java | 1 - .../generator/GenerateMfaConfig.java | 18 +++++++++++++++--- .../test/userIdMapping/UserIdMappingTest.java | 4 +--- 9 files changed, 64 insertions(+), 31 deletions(-) diff --git a/src/main/java/io/supertokens/inmemorydb/queries/multitenancy/TenantConfigSQLHelper.java b/src/main/java/io/supertokens/inmemorydb/queries/multitenancy/TenantConfigSQLHelper.java index 9dd93bab6..c026f9302 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/multitenancy/TenantConfigSQLHelper.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/multitenancy/TenantConfigSQLHelper.java @@ -21,6 +21,7 @@ import io.supertokens.inmemorydb.queries.utils.JsonUtils; import io.supertokens.pluginInterface.RowMapper; import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.mfa.MfaFirstFactors; import io.supertokens.pluginInterface.multitenancy.*; import java.sql.Connection; @@ -54,7 +55,7 @@ public TenantConfig map(ResultSet result) throws StorageQueryException { new ThirdPartyConfig(result.getBoolean("third_party_enabled"), this.providers), new PasswordlessConfig(result.getBoolean("passwordless_enabled")), new TotpConfig(false), // TODO - new MfaConfig(new String[0], new String[0]), // TODO + new MfaConfig(new MfaFirstFactors(null, null), new String[0]), // TODO JsonUtils.stringToJsonObject(result.getString("core_config")) ); } catch (Exception e) { diff --git a/src/main/java/io/supertokens/webserver/api/multitenancy/BaseCreateOrUpdate.java b/src/main/java/io/supertokens/webserver/api/multitenancy/BaseCreateOrUpdate.java index a97eda87d..06cfb4a3f 100644 --- a/src/main/java/io/supertokens/webserver/api/multitenancy/BaseCreateOrUpdate.java +++ b/src/main/java/io/supertokens/webserver/api/multitenancy/BaseCreateOrUpdate.java @@ -26,6 +26,7 @@ import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.exceptions.InvalidConfigException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.mfa.MfaFirstFactors; import io.supertokens.pluginInterface.multitenancy.*; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.thirdparty.InvalidProviderConfigException; @@ -46,7 +47,7 @@ public BaseCreateOrUpdate(Main main) { protected void handle(HttpServletRequest req, TenantIdentifier sourceTenantIdentifier, TenantIdentifier targetTenantIdentifier, Boolean emailPasswordEnabled, Boolean thirdPartyEnabled, Boolean passwordlessEnabled, Boolean totpEnabled, - String[] firstFactors, String[] defaultMFARequirements, + MfaFirstFactors firstFactors, String[] defaultRequiredFactors, JsonObject coreConfig, HttpServletResponse resp) throws ServletException, IOException { @@ -138,19 +139,19 @@ protected void handle(HttpServletRequest req, TenantIdentifier sourceTenantIdent tenantConfig.thirdPartyConfig, tenantConfig.passwordlessConfig, tenantConfig.totpConfig, - new MfaConfig(firstFactors, tenantConfig.mfaConfig.defaultMFARequirements), + new MfaConfig(firstFactors, tenantConfig.mfaConfig.defaultRequiredFactors), tenantConfig.coreConfig ); } - if (defaultMFARequirements != null) { + if (defaultRequiredFactors != null) { tenantConfig = new TenantConfig( tenantConfig.tenantIdentifier, tenantConfig.emailPasswordConfig, tenantConfig.thirdPartyConfig, tenantConfig.passwordlessConfig, tenantConfig.totpConfig, - new MfaConfig(tenantConfig.mfaConfig.firstFactors, defaultMFARequirements), + new MfaConfig(tenantConfig.mfaConfig.firstFactors, defaultRequiredFactors), tenantConfig.coreConfig ); } diff --git a/src/main/java/io/supertokens/webserver/api/multitenancy/CreateOrUpdateAppAPI.java b/src/main/java/io/supertokens/webserver/api/multitenancy/CreateOrUpdateAppAPI.java index 4af87a98e..00f78b9f7 100644 --- a/src/main/java/io/supertokens/webserver/api/multitenancy/CreateOrUpdateAppAPI.java +++ b/src/main/java/io/supertokens/webserver/api/multitenancy/CreateOrUpdateAppAPI.java @@ -19,6 +19,7 @@ import com.google.gson.JsonObject; import io.supertokens.Main; import io.supertokens.multitenancy.exception.BadPermissionException; +import io.supertokens.pluginInterface.mfa.MfaFirstFactors; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.utils.SemVer; @@ -58,13 +59,20 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO JsonObject coreConfig = InputParser.parseJsonObjectOrThrowError(input, "coreConfig", true); Boolean totpEnabled = null; - String[] firstFactors = new String[0]; - String[] defaultMFARequirements = new String[0]; + MfaFirstFactors firstFactors = new MfaFirstFactors(null, null); + String[] defaultRequiredFactors = null; if (getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_1)) { totpEnabled = InputParser.parseBooleanOrThrowError(input, "totpEnabled", true); - firstFactors = InputParser.parseStringArrayOrThrowError(input, "firstFactors", true); - defaultMFARequirements = InputParser.parseStringArrayOrThrowError(input, "defaultMFARequirements", true); + defaultRequiredFactors = InputParser.parseStringArrayOrThrowError(input, "defaultRequiredFactors", true); + + try { + if (input.has("firstFactors")) { + firstFactors = MfaFirstFactors.fromJson(input.get("firstFactors")); + } + } catch (IllegalArgumentException e) { + throw new ServletException(new BadRequestException(e.getMessage())); + } } TenantIdentifier sourceTenantIdentifier; @@ -78,7 +86,7 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO req, sourceTenantIdentifier, new TenantIdentifier(sourceTenantIdentifier.getConnectionUriDomain(), appId, null), emailPasswordEnabled, thirdPartyEnabled, passwordlessEnabled, - totpEnabled, firstFactors, defaultMFARequirements, + totpEnabled, firstFactors, defaultRequiredFactors, coreConfig, resp); } diff --git a/src/main/java/io/supertokens/webserver/api/multitenancy/CreateOrUpdateConnectionUriDomainAPI.java b/src/main/java/io/supertokens/webserver/api/multitenancy/CreateOrUpdateConnectionUriDomainAPI.java index fca93f4dd..fe564a228 100644 --- a/src/main/java/io/supertokens/webserver/api/multitenancy/CreateOrUpdateConnectionUriDomainAPI.java +++ b/src/main/java/io/supertokens/webserver/api/multitenancy/CreateOrUpdateConnectionUriDomainAPI.java @@ -18,6 +18,7 @@ import com.google.gson.JsonObject; import io.supertokens.Main; +import io.supertokens.pluginInterface.mfa.MfaFirstFactors; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.utils.SemVer; @@ -56,13 +57,20 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO JsonObject coreConfig = InputParser.parseJsonObjectOrThrowError(input, "coreConfig", true); Boolean totpEnabled = null; - String[] firstFactors = new String[0]; - String[] defaultMFARequirements = new String[0]; + MfaFirstFactors firstFactors = new MfaFirstFactors(null, null); + String[] defaultRequiredFactors = null; if (getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_1)) { totpEnabled = InputParser.parseBooleanOrThrowError(input, "totpEnabled", true); - firstFactors = InputParser.parseStringArrayOrThrowError(input, "firstFactors", true); - defaultMFARequirements = InputParser.parseStringArrayOrThrowError(input, "defaultMFARequirements", true); + defaultRequiredFactors = InputParser.parseStringArrayOrThrowError(input, "defaultRequiredFactors", true); + + try { + if (input.has("firstFactors")) { + firstFactors = MfaFirstFactors.fromJson(input.get("firstFactors")); + } + } catch (IllegalArgumentException e) { + throw new ServletException(new BadRequestException(e.getMessage())); + } } TenantIdentifier sourceTenantIdentifier; @@ -76,7 +84,7 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO req, sourceTenantIdentifier, new TenantIdentifier(connectionUriDomain, null, null), emailPasswordEnabled, thirdPartyEnabled, passwordlessEnabled, - totpEnabled, firstFactors, defaultMFARequirements, + totpEnabled, firstFactors, defaultRequiredFactors, coreConfig, resp); } diff --git a/src/main/java/io/supertokens/webserver/api/multitenancy/CreateOrUpdateTenantOrGetTenantAPI.java b/src/main/java/io/supertokens/webserver/api/multitenancy/CreateOrUpdateTenantOrGetTenantAPI.java index 44a64a338..882a60cc9 100644 --- a/src/main/java/io/supertokens/webserver/api/multitenancy/CreateOrUpdateTenantOrGetTenantAPI.java +++ b/src/main/java/io/supertokens/webserver/api/multitenancy/CreateOrUpdateTenantOrGetTenantAPI.java @@ -20,6 +20,7 @@ import io.supertokens.Main; import io.supertokens.config.CoreConfig; import io.supertokens.multitenancy.Multitenancy; +import io.supertokens.pluginInterface.mfa.MfaFirstFactors; import io.supertokens.pluginInterface.multitenancy.*; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.utils.SemVer; @@ -59,13 +60,20 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO JsonObject coreConfig = InputParser.parseJsonObjectOrThrowError(input, "coreConfig", true); Boolean totpEnabled = null; - String[] firstFactors = new String[0]; - String[] defaultMFARequirements = new String[0]; + MfaFirstFactors firstFactors = new MfaFirstFactors(null, null); + String[] defaultRequiredFactors = null; if (getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_1)) { totpEnabled = InputParser.parseBooleanOrThrowError(input, "totpEnabled", true); - firstFactors = InputParser.parseStringArrayOrThrowError(input, "firstFactors", true); - defaultMFARequirements = InputParser.parseStringArrayOrThrowError(input, "defaultMFARequirements", true); + defaultRequiredFactors = InputParser.parseStringArrayOrThrowError(input, "defaultRequiredFactors", true); + + try { + if (input.has("firstFactors")) { + firstFactors = MfaFirstFactors.fromJson(input.get("firstFactors")); + } + } catch (IllegalArgumentException e) { + throw new ServletException(new BadRequestException(e.getMessage())); + } } TenantIdentifier sourceTenantIdentifier; @@ -79,7 +87,7 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO req, sourceTenantIdentifier, new TenantIdentifier(sourceTenantIdentifier.getConnectionUriDomain(), sourceTenantIdentifier.getAppId(), tenantId), emailPasswordEnabled, thirdPartyEnabled, passwordlessEnabled, - totpEnabled, firstFactors, defaultMFARequirements, + totpEnabled, firstFactors, defaultRequiredFactors, coreConfig, resp); } diff --git a/src/test/java/io/supertokens/test/multitenant/AppTenantUserTest.java b/src/test/java/io/supertokens/test/multitenant/AppTenantUserTest.java index 62a9f8b6c..f781a9dff 100644 --- a/src/test/java/io/supertokens/test/multitenant/AppTenantUserTest.java +++ b/src/test/java/io/supertokens/test/multitenant/AppTenantUserTest.java @@ -26,7 +26,6 @@ import io.supertokens.pluginInterface.ActiveUsersStorage; import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; -import io.supertokens.pluginInterface.mfa.MfaStorage; import io.supertokens.pluginInterface.multitenancy.*; import io.supertokens.pluginInterface.nonAuthRecipe.NonAuthRecipeStorage; import io.supertokens.storageLayer.StorageLayer; @@ -77,8 +76,7 @@ public void testDeletingAppDeleteNonAuthRecipeData() throws Exception { // this list contains the package names for recipes which dont use UserIdMapping ArrayList classesToSkip = new ArrayList<>( - List.of("io.supertokens.pluginInterface.jwt.JWTRecipeStorage", ActiveUsersStorage.class.getName(), - MfaStorage.class.getName())); + List.of("io.supertokens.pluginInterface.jwt.JWTRecipeStorage", ActiveUsersStorage.class.getName())); Reflections reflections = new Reflections("io.supertokens.pluginInterface"); Set> classes = reflections.getSubTypesOf(NonAuthRecipeStorage.class); @@ -187,7 +185,7 @@ public void testDisassociationOfUserDeletesNonAuthRecipeData() throws Exception // this list contains the package names for recipes which dont use UserIdMapping ArrayList classesToSkip = new ArrayList<>( - List.of("io.supertokens.pluginInterface.jwt.JWTRecipeStorage", ActiveUsersStorage.class.getName(), MfaStorage.class.getName())); + List.of("io.supertokens.pluginInterface.jwt.JWTRecipeStorage", ActiveUsersStorage.class.getName())); Reflections reflections = new Reflections("io.supertokens.pluginInterface"); Set> classes = reflections.getSubTypesOf(NonAuthRecipeStorage.class); diff --git a/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java b/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java index 1f79b3e78..0b2dcc3a7 100644 --- a/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java +++ b/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java @@ -33,7 +33,6 @@ import io.supertokens.pluginInterface.exceptions.InvalidConfigException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.jwt.JWTRecipeStorage; -import io.supertokens.pluginInterface.mfa.MfaStorage; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; diff --git a/src/test/java/io/supertokens/test/multitenant/generator/GenerateMfaConfig.java b/src/test/java/io/supertokens/test/multitenant/generator/GenerateMfaConfig.java index fea260357..e12b2b444 100644 --- a/src/test/java/io/supertokens/test/multitenant/generator/GenerateMfaConfig.java +++ b/src/test/java/io/supertokens/test/multitenant/generator/GenerateMfaConfig.java @@ -16,13 +16,24 @@ package io.supertokens.test.multitenant.generator; +import io.supertokens.pluginInterface.mfa.MfaFirstFactors; + import java.util.HashSet; import java.util.Random; import java.util.Set; public class GenerateMfaConfig { private static final String[] FACTORS = new String[]{ - "password", "otp-email", "otp-phone", "link-email", "link-phone", "totp", "thirdparty" + "emailpassword", + "thirdparty", + "otp-email", + "otp-phone", + "link-email", + "link-phone" + }; + + private static final String[] OTHER_FACTORS = new String[]{ + "totp", "biometric", "custom" }; private static String[] selectRandomElements(String[] inputArray) { @@ -55,13 +66,14 @@ private static String[] selectRandomElements(String[] inputArray) { public static ConfigGenerator.GeneratedValueAndExpectation generate_firstFactors() { String[] factors = selectRandomElements(FACTORS); + String[] customFactors = selectRandomElements(OTHER_FACTORS); return new ConfigGenerator.GeneratedValueAndExpectation( - factors, + new MfaFirstFactors(factors, customFactors), new ConfigGenerator.Expectation("ok", factors)); } - public static ConfigGenerator.GeneratedValueAndExpectation generate_defaultMFARequirements() { + public static ConfigGenerator.GeneratedValueAndExpectation generate_defaultRequiredFactors() { String[] factors = selectRandomElements(FACTORS); return new ConfigGenerator.GeneratedValueAndExpectation( diff --git a/src/test/java/io/supertokens/test/userIdMapping/UserIdMappingTest.java b/src/test/java/io/supertokens/test/userIdMapping/UserIdMappingTest.java index 96d9fa5db..cfc9e6db2 100644 --- a/src/test/java/io/supertokens/test/userIdMapping/UserIdMappingTest.java +++ b/src/test/java/io/supertokens/test/userIdMapping/UserIdMappingTest.java @@ -25,7 +25,6 @@ import io.supertokens.pluginInterface.ActiveUsersStorage; import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; -import io.supertokens.pluginInterface.mfa.MfaStorage; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.nonAuthRecipe.NonAuthRecipeStorage; @@ -796,8 +795,7 @@ public void checkThatCreateUserIdMappingHasAllNonAuthRecipeChecks() throws Excep // this list contains the package names for recipes which dont use UserIdMapping ArrayList nonAuthRecipesWhichDontNeedUserIdMapping = new ArrayList<>( - List.of("io.supertokens.pluginInterface.jwt.JWTRecipeStorage", ActiveUsersStorage.class.getName(), - MfaStorage.class.getName())); + List.of("io.supertokens.pluginInterface.jwt.JWTRecipeStorage", ActiveUsersStorage.class.getName())); Reflections reflections = new Reflections("io.supertokens.pluginInterface"); Set> classes = reflections.getSubTypesOf(NonAuthRecipeStorage.class);