Skip to content

Commit

Permalink
fix: enforce public tenant in session APIs (#964)
Browse files Browse the repository at this point in the history
  • Loading branch information
sattvikc authored Mar 18, 2024
1 parent 08355c7 commit 6b7e443
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -113,19 +113,14 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I
String[] sessionHandlesRevoked;

if (revokeAcrossAllTenants) {
// when revokeAcrossAllTenants is true, and given that the backend SDK might pass tenant id
// we do not want to enforce public tenant here but behave as if this is an app specific API
// So instead of calling enforcePublicTenantAndGetAllStoragesForApp, we simply do all the logic
// here to fetch the storages and find the storage where `userId` exists. If user id does not
// exist, we use the storage for the tenantId passed in the request.
AppIdentifier appIdentifier = getAppIdentifier(req);
Storage[] storages = StorageLayer.getStoragesForApp(main, appIdentifier);
try {
StorageAndUserIdMapping storageAndUserIdMapping = StorageLayer.findStorageAndUserIdMappingForUser(
appIdentifier, storages, userId, UserIdType.ANY);
StorageAndUserIdMapping storageAndUserIdMapping =
enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi(
req, userId, UserIdType.ANY, false);
storage = storageAndUserIdMapping.storage;
} catch (UnknownUserIdException e) {
storage = getTenantStorage(req);
throw new IllegalStateException("should never happen");
}
sessionHandlesRevoked = Session.revokeAllSessionsForUser(
main, appIdentifier, storage, userId, revokeSessionsForLinkedAccounts);
Expand Down Expand Up @@ -159,7 +154,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I
}
result.add("sessionHandlesRevoked", sessionHandlesRevokedJSON);
super.sendJsonResponse(200, result, resp);
} catch (StorageQueryException | TenantOrAppNotFoundException e) {
} catch (StorageQueryException | TenantOrAppNotFoundException | BadPermissionException e) {
throw new ServletException(e);
}
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,21 +77,15 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO
String[] sessionHandles;

if (fetchAcrossAllTenants) {
// when fetchAcrossAllTenants is true, and given that the backend SDK might pass tenant id
// we do not want to enforce public tenant here but behave as if this is an app specific API
// So instead of calling enforcePublicTenantAndGetAllStoragesForApp, we simply do all the logic
// here to fetch the storages and find the storage where `userId` exists. If user id does not
// exist, we use the storage for the tenantId passed in the request.
AppIdentifier appIdentifier = getAppIdentifier(req);
Storage[] storages = StorageLayer.getStoragesForApp(main, appIdentifier);
Storage storage;
try {
StorageAndUserIdMapping storageAndUserIdMapping =
StorageLayer.findStorageAndUserIdMappingForUser(
appIdentifier, storages, userId, UserIdType.ANY);
enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi(
req, userId, UserIdType.ANY, false);
storage = storageAndUserIdMapping.storage;
} catch (UnknownUserIdException e) {
storage = getTenantStorage(req);
throw new IllegalStateException("should never happen");
}
sessionHandles = Session.getAllNonExpiredSessionHandlesForUser(
main, appIdentifier, storage, userId,
Expand All @@ -112,7 +106,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO
result.add("sessionHandles", arr);
super.sendJsonResponse(200, result, resp);

} catch (StorageQueryException | TenantOrAppNotFoundException e) {
} catch (StorageQueryException | TenantOrAppNotFoundException | BadPermissionException e) {
throw new ServletException(e);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,24 @@
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
import io.supertokens.pluginInterface.usermetadata.sqlStorage.UserMetadataSQLStorage;
import io.supertokens.session.Session;
import io.supertokens.session.info.SessionInformationHolder;
import io.supertokens.storageLayer.StorageLayer;
import io.supertokens.test.TestingProcessManager;
import io.supertokens.test.Utils;
import io.supertokens.test.httpRequest.HttpRequestForTesting;
import io.supertokens.test.httpRequest.HttpResponseException;
import io.supertokens.thirdparty.InvalidProviderConfigException;
import io.supertokens.useridmapping.UserIdMapping;
import io.supertokens.utils.SemVer;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import static org.junit.Assert.*;

Expand Down Expand Up @@ -375,4 +381,91 @@ public void testEmailVerificationWithUsersOnDifferentTenantStorages() throws Exc
assertFalse(t1EvStorage.isEmailVerified(t0.toAppIdentifier(), user2.getSupertokensUserId(), "[email protected]")); // ensure t1 storage does not have user2's ev
assertFalse(t0EvStorage.isEmailVerified(t0.toAppIdentifier(), user1.getSupertokensUserId(), "[email protected]")); // ensure t0 storage does not have user1's ev
}

@Test
public void testSessionCannotGetAcrossAllStorageOrRevokedAcrossAllTenantsFromNonPublicTenant() throws Exception {
if (StorageLayer.getBaseStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) {
return;
}

if (StorageLayer.isInMemDb(process.getProcess())) {
return;
}

TenantIdentifier t0 = new TenantIdentifier(null, null, null);
Storage t0Storage = (StorageLayer.getStorage(t0, process.getProcess()));

TenantIdentifier t1 = new TenantIdentifier(null, null, "t1");
Storage t1Storage = (StorageLayer.getStorage(t1, process.getProcess()));

// Create users
AuthRecipeUserInfo user1 = EmailPassword.signUp(t0, t0Storage, process.getProcess(), "[email protected]", "password123");
AuthRecipeUserInfo user2 = EmailPassword.signUp(t1, t1Storage, process.getProcess(), "[email protected]", "password123");

UserIdMapping.populateExternalUserIdForUsers(t0.toAppIdentifier(), t0Storage, new AuthRecipeUserInfo[]{user1});
UserIdMapping.populateExternalUserIdForUsers(t1.toAppIdentifier(), t1Storage, new AuthRecipeUserInfo[]{user2});

SessionInformationHolder sess1 = Session.createNewSession(t0, t0Storage,
process.getProcess(), user1.getSupertokensUserId(), new JsonObject(), new JsonObject());
SessionInformationHolder sess2 = Session.createNewSession(t1, t1Storage,
process.getProcess(), user2.getSupertokensUserId(), new JsonObject(), new JsonObject());

{
Map<String, String> params = new HashMap<>();
params.put("fetchAcrossAllTenants", "true");
params.put("userId", user1.getSupertokensUserId());

JsonObject sessionResponse = HttpRequestForTesting.sendGETRequest(process.getProcess(), "",
HttpRequestForTesting.getMultitenantUrl(t1, "/recipe/session/user"),
params, 1000, 1000, null, SemVer.v4_0.get(),
"session");
assertEquals("OK", sessionResponse.get("status").getAsString());
assertEquals(1, sessionResponse.get("sessionHandles").getAsJsonArray().size());
assertEquals(sess1.session.handle, sessionResponse.get("sessionHandles").getAsJsonArray().get(0).getAsString());
}

{
try {
Map<String, String> params = new HashMap<>();
params.put("fetchAcrossAllTenants", "true");
params.put("userId", user1.getSupertokensUserId());

JsonObject sessionResponse = HttpRequestForTesting.sendGETRequest(process.getProcess(), "",
HttpRequestForTesting.getMultitenantUrl(t1, "/recipe/session/user"),
params, 1000, 1000, null, SemVer.v5_0.get(),
"session");
fail();
} catch (HttpResponseException e) {
assertEquals(403, e.statusCode);
}
}

{
try {
JsonObject requestBody = new JsonObject();
requestBody.addProperty("userId", user1.getSupertokensUserId());
requestBody.addProperty("revokeAcrossAllTenants", true);

JsonObject sessionResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "",
HttpRequestForTesting.getMultitenantUrl(t1, "/recipe/session/remove"), requestBody,
1000, 1000, null, SemVer.v5_0.get(),
"session");
fail();
} catch (HttpResponseException e) {
assertEquals(403, e.statusCode);
}
}

{
JsonObject requestBody = new JsonObject();
requestBody.addProperty("userId", user1.getSupertokensUserId());
requestBody.addProperty("revokeAcrossAllTenants", true);

JsonObject sessionResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "",
HttpRequestForTesting.getMultitenantUrl(t1, "/recipe/session/remove"), requestBody,
1000, 1000, null, SemVer.v4_0.get(),
"session");
assertEquals("OK", sessionResponse.get("status").getAsString());
}
}
}

0 comments on commit 6b7e443

Please sign in to comment.