Skip to content

Commit

Permalink
fix: multitenancy
Browse files Browse the repository at this point in the history
  • Loading branch information
sattvikc committed Sep 12, 2023
1 parent bb938de commit 6d2b9fd
Show file tree
Hide file tree
Showing 2 changed files with 200 additions and 18 deletions.
9 changes: 9 additions & 0 deletions src/main/java/io/supertokens/multitenancy/Multitenancy.java
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,9 @@ public static boolean addUserIdToTenant(Main main, TenantIdentifierWithStorage t
for (String email : emails) {
AuthRecipeUserInfo[] usersWithSameEmail = storage.listPrimaryUsersByEmail_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, email);
for (AuthRecipeUserInfo userWithSameEmail : usersWithSameEmail) {
if (userWithSameEmail.getSupertokensUserId().equals(userToAssociate.getSupertokensUserId())) {
continue; // it's the same user, no need to check anything
}
if (userWithSameEmail.isPrimaryUser && userWithSameEmail.tenantIds.contains(tenantId) && !userWithSameEmail.getSupertokensUserId().equals(userId)) {
for (LoginMethod lm1 : userWithSameEmail.loginMethods) {
if (lm1.tenantIds.contains(tenantId)) {
Expand All @@ -444,6 +447,9 @@ public static boolean addUserIdToTenant(Main main, TenantIdentifierWithStorage t
for (String phoneNumber : phoneNumbers) {
AuthRecipeUserInfo[] usersWithSamePhoneNumber = storage.listPrimaryUsersByPhoneNumber_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, phoneNumber);
for (AuthRecipeUserInfo userWithSamePhoneNumber : usersWithSamePhoneNumber) {
if (userWithSamePhoneNumber.getSupertokensUserId().equals(userToAssociate.getSupertokensUserId())) {
continue; // it's the same user, no need to check anything
}
if (userWithSamePhoneNumber.tenantIds.contains(tenantId) && !userWithSamePhoneNumber.getSupertokensUserId().equals(userId)) {
for (LoginMethod lm1 : userWithSamePhoneNumber.loginMethods) {
if (lm1.tenantIds.contains(tenantId)) {
Expand All @@ -462,6 +468,9 @@ public static boolean addUserIdToTenant(Main main, TenantIdentifierWithStorage t
for (LoginMethod.ThirdParty tp : thirdParties) {
AuthRecipeUserInfo[] usersWithSameThirdPartyInfo = storage.listPrimaryUsersByThirdPartyInfo_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, tp.id, tp.userId);
for (AuthRecipeUserInfo userWithSameThirdPartyInfo : usersWithSameThirdPartyInfo) {
if (userWithSameThirdPartyInfo.getSupertokensUserId().equals(userToAssociate.getSupertokensUserId())) {
continue; // it's the same user, no need to check anything
}
if (userWithSameThirdPartyInfo.tenantIds.contains(tenantId) && !userWithSameThirdPartyInfo.getSupertokensUserId().equals(userId)) {
for (LoginMethod lm1 : userWithSameThirdPartyInfo.loginMethods) {
if (lm1.tenantIds.contains(tenantId)) {
Expand Down
209 changes: 191 additions & 18 deletions src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import io.supertokens.ProcessState;
import io.supertokens.authRecipe.AuthRecipe;
import io.supertokens.authRecipe.exception.AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException;
import io.supertokens.authRecipe.exception.RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException;
import io.supertokens.emailpassword.EmailPassword;
import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException;
import io.supertokens.emailpassword.exceptions.WrongCredentialsException;
Expand All @@ -32,6 +33,7 @@
import io.supertokens.passwordless.Passwordless;
import io.supertokens.passwordless.exceptions.PhoneNumberChangeNotAllowedException;
import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo;
import io.supertokens.pluginInterface.authRecipe.LoginMethod;
import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException;
import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException;
import io.supertokens.pluginInterface.exceptions.InvalidConfigException;
Expand All @@ -45,6 +47,7 @@
import io.supertokens.test.Utils;
import io.supertokens.thirdparty.InvalidProviderConfigException;
import io.supertokens.thirdparty.ThirdParty;
import io.supertokens.userroles.UserRoles;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Rule;
Expand Down Expand Up @@ -237,6 +240,97 @@ public void testUserAreNotAutomaticallySharedBetweenTenantsOfLinkedAccountsForTP
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
}

@Test
public void testTenantDeletionWithAccountLinking() throws Exception {
String[] args = {"../"};
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false);
FeatureFlagTestContent.getInstance(process.getProcess())
.setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{
EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY});
process.startProcess();
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));

createTenants(process.getProcess());

t1 = new TenantIdentifier(null, "a1", null);
t2 = new TenantIdentifier(null, "a1", "t1");

TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess()));
TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess()));

AuthRecipeUserInfo user1 = EmailPassword.signUp(t2WithStorage, process.getProcess(), "[email protected]", "password");
AuthRecipeUserInfo user2 = ThirdParty.signInUp(t2WithStorage, process.getProcess(), "google", "googleid1", "[email protected]").user;

AuthRecipe.createPrimaryUser(process.getProcess(), t2WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId());
AuthRecipe.linkAccounts(process.getProcess(), t2WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId(), user1.getSupertokensUserId());

Multitenancy.deleteTenant(t2, process.getProcess());

AuthRecipeUserInfo getUser1 = AuthRecipe.getUserById(t1WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId());
for (LoginMethod lm : getUser1.loginMethods) {
assertEquals(0, lm.tenantIds.size());
}

AuthRecipeUserInfo getUser2 = AuthRecipe.getUserById(t1WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId());
for (LoginMethod lm : getUser2.loginMethods) {
assertEquals(0, lm.tenantIds.size());
}

process.kill();
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
}

@Test
public void testTenantDeletionWithAccountLinkingWithUserRoles() throws Exception {
String[] args = {"../"};
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false);
FeatureFlagTestContent.getInstance(process.getProcess())
.setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{
EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY});
process.startProcess();
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));

createTenants(process.getProcess());

t1 = new TenantIdentifier(null, "a1", null);
t2 = new TenantIdentifier(null, "a1", "t1");

TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess()));
TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess()));

AuthRecipeUserInfo user1 = EmailPassword.signUp(t2WithStorage, process.getProcess(), "[email protected]", "password");
AuthRecipeUserInfo user2 = ThirdParty.signInUp(t2WithStorage, process.getProcess(), "google", "googleid1", "[email protected]").user;

AuthRecipe.createPrimaryUser(process.getProcess(), t2WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId());
AuthRecipe.linkAccounts(process.getProcess(), t2WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId(), user1.getSupertokensUserId());

UserRoles.createNewRoleOrModifyItsPermissions(t2WithStorage.toAppIdentifierWithStorage(), "admin", new String[]{"p1"});
UserRoles.addRoleToUser(t2WithStorage, user1.getSupertokensUserId(), "admin");

Multitenancy.deleteTenant(t2, process.getProcess());

createTenants(process.getProcess()); // create the tenant again

Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, user1.getSupertokensUserId()); // add the user to the tenant again
Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, user2.getSupertokensUserId()); // add the user to the tenant again

AuthRecipeUserInfo getUser1 = AuthRecipe.getUserById(t1WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId());
for (LoginMethod lm : getUser1.loginMethods) {
assertEquals(1, lm.tenantIds.size());
}

AuthRecipeUserInfo getUser2 = AuthRecipe.getUserById(t1WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId());
for (LoginMethod lm : getUser2.loginMethods) {
assertEquals(1, lm.tenantIds.size());
}

String[] roles = UserRoles.getRolesForUser(t2WithStorage, user1.getSupertokensUserId());
assertEquals(0, roles.length); // must be deleted with tenant

process.kill();
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
}

@Test
public void testVariousCases() throws Exception {
t1 = new TenantIdentifier(null, "a1", null);
Expand Down Expand Up @@ -545,12 +639,12 @@ public void testVariousCases() throws Exception {
new CreateThirdPartyUser(t1, "google", "googleid3", "[email protected]").expect(new EmailChangeNotAllowedException()),
}),
new TestCase(new TestCaseStep[]{
new CreateEmailPasswordUser(t1, "[email protected]"),
new CreateEmailPasswordUser(t1, "[email protected]"),
new MakePrimaryUser(t1, 0),
new LinkAccounts(t1, 0, 1),
new UnlinkAccount(t1, 0),
new AssociateUserToTenant(t2, 0).expect(new UnknownUserIdException()),
new CreateEmailPasswordUser(t1, "[email protected]"),
new CreateEmailPasswordUser(t1, "[email protected]"),
new MakePrimaryUser(t1, 0),
new LinkAccounts(t1, 0, 1),
new UnlinkAccount(t1, 0),
new AssociateUserToTenant(t2, 0).expect(new UnknownUserIdException()),
}),

new TestCase(new TestCaseStep[]{
Expand All @@ -570,6 +664,70 @@ public void testVariousCases() throws Exception {
new UnlinkAccount(t1, 0),
new AssociateUserToTenant(t2, 0).expect(new UnknownUserIdException()),
}),
new TestCase(new TestCaseStep[]{
new CreateEmailPasswordUser(t1, "[email protected]"),
new CreateEmailPasswordUser(t1, "[email protected]"),
new MakePrimaryUser(t1, 0),
new LinkAccounts(t1, 0, 1),
new DisassociateUserFromTenant(t1, 0),
new AssociateUserToTenant(t2, 0),
new TestCaseStep() {
@Override
public void execute(Main main) throws Exception {
TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, main));
AuthRecipeUserInfo user = AuthRecipe.getUserById(t1WithStorage.toAppIdentifierWithStorage(), TestCase.users.get(0).getSupertokensUserId());
assertEquals(2, user.loginMethods.length);
assertTrue(user.loginMethods[0].tenantIds.contains(t2.getTenantId()));
assertTrue(user.loginMethods[1].tenantIds.contains(t1.getTenantId()));
}
}
}),

new TestCase(new TestCaseStep[]{
new CreateEmailPasswordUser(t1, "[email protected]"),
new DisassociateUserFromTenant(t1, 0),
new CreateEmailPasswordUser(t1, "[email protected]"),
new DisassociateUserFromTenant(t1, 1),
new MakePrimaryUser(t1, 0),
new MakePrimaryUser(t1, 1),
new AssociateUserToTenant(t1, 0),
new AssociateUserToTenant(t1, 1).expect(new DuplicateEmailException()),
new LinkAccounts(t1, 0, 1).expect(new RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException(null, "")),
}),

new TestCase(new TestCaseStep[]{
new CreateEmailPasswordUser(t1, "[email protected]"),
new DisassociateUserFromTenant(t1, 0),
new CreateEmailPasswordUser(t1, "[email protected]"),
new DisassociateUserFromTenant(t1, 1),
new MakePrimaryUser(t1, 0),
new AssociateUserToTenant(t1, 0),
new LinkAccounts(t1, 0, 1),
new AssociateUserToTenant(t1, 1).expect(new DuplicateEmailException()),
new AssociateUserToTenant(t2, 1),
}),
new TestCase(new TestCaseStep[]{
new CreateEmailPasswordUser(t1, "[email protected]"),
new CreateEmailPasswordUser(t1, "[email protected]"),
new MakePrimaryUser(t1, 0),
new LinkAccounts(t1, 0, 1),
new UnlinkAccount(t1, 0),
new TestCaseStep() {
@Override
public void execute(Main main) throws Exception {
TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, main));
AuthRecipe.deleteUser(t1WithStorage.toAppIdentifierWithStorage(), TestCase.users.get(1).getSupertokensUserId());
}
},
new TestCaseStep() {
@Override
public void execute(Main main) throws Exception {
TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, main));
AuthRecipeUserInfo user = AuthRecipe.getUserById(t1WithStorage.toAppIdentifierWithStorage(), TestCase.users.get(0).getSupertokensUserId());
assertNull(user);
}
}
}),
};

int i = 0;
Expand Down Expand Up @@ -618,7 +776,7 @@ public void doTest(Main main) throws Exception {
}
}

private static class TestCaseStep {
private static abstract class TestCaseStep {
Exception e;

public TestCaseStep expect(Exception e) {
Expand All @@ -639,8 +797,7 @@ public void doStep(Main main) throws Exception {
}
}

public void execute(Main main) throws Exception {
}
abstract public void execute(Main main) throws Exception;
}

private static class CreateEmailPasswordUser extends TestCaseStep {
Expand Down Expand Up @@ -843,15 +1000,31 @@ private static class SignInEmailPasswordUser extends TestCaseStep {
TenantIdentifier tenantIdentifier;
int userIndex;

public SignInEmailPasswordUser(TenantIdentifier tenantIdentifier, int userIndex) {
this.tenantIdentifier = tenantIdentifier;
this.userIndex = userIndex;
}
public SignInEmailPasswordUser(TenantIdentifier tenantIdentifier, int userIndex) {
this.tenantIdentifier = tenantIdentifier;
this.userIndex = userIndex;
}

@Override
public void execute(Main main) throws Exception {
TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main));
EmailPassword.signIn(tenantIdentifierWithStorage, main, TestCase.users.get(userIndex).loginMethods[0].email, "password");
}
@Override
public void execute(Main main) throws Exception {
TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main));
EmailPassword.signIn(tenantIdentifierWithStorage, main, TestCase.users.get(userIndex).loginMethods[0].email, "password");
}
}

private static class DisassociateUserFromTenant extends TestCaseStep {
TenantIdentifier tenantIdentifier;
int userIndex;

public DisassociateUserFromTenant(TenantIdentifier tenantIdentifier, int userIndex) {
this.tenantIdentifier = tenantIdentifier;
this.userIndex = userIndex;
}

@Override
public void execute(Main main) throws Exception {
TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main));
Multitenancy.removeUserIdFromTenant(main, tenantIdentifierWithStorage, TestCase.users.get(userIndex).getSupertokensUserId(), null);
}
}
}

0 comments on commit 6d2b9fd

Please sign in to comment.