From 880f22f14fc38ad347251e6e3441880485aca20e Mon Sep 17 00:00:00 2001 From: tamassoltesz Date: Sat, 19 Oct 2024 01:31:28 +0200 Subject: [PATCH] fix: session creation - checking tenant for user --- CHANGELOG.md | 6 + build.gradle | 2 +- coreDriverInterfaceSupported.json | 3 +- .../java/io/supertokens/session/Session.java | 28 +- .../java/io/supertokens/utils/SemVer.java | 1 + .../supertokens/webserver/WebserverAPI.java | 6 +- .../webserver/api/session/SessionAPI.java | 7 +- .../test/accountlinking/SessionTests.java | 2 +- .../session/TenantCheckForKnownUsersTest.java | 273 +++++++++++++++++ .../test/session/api/SessionAPITest5_2.java | 283 ++++++++++++++++++ 10 files changed, 598 insertions(+), 13 deletions(-) create mode 100644 src/test/java/io/supertokens/test/session/TenantCheckForKnownUsersTest.java create mode 100644 src/test/java/io/supertokens/test/session/api/SessionAPITest5_2.java diff --git a/CHANGELOG.md b/CHANGELOG.md index df9077ecd..a344b44ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [9.2.4] + +- Adds support for CDI 5.2 +- In CDI 5.2, when creating a new session for a known user, checks if the user is a member of that tenant. + If not, returns UNAUTHORISED. + ## [9.2.3] - 2024-10-09 - Adds support for `--with-temp-dir` in CLI and `tempDirLocation=` in Core diff --git a/build.gradle b/build.gradle index 7ede11ac5..28e1025fb 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ compileTestJava { options.encoding = "UTF-8" } // } //} -version = "9.2.3" +version = "9.2.4" repositories { diff --git a/coreDriverInterfaceSupported.json b/coreDriverInterfaceSupported.json index aa87aab03..130c4b2c1 100644 --- a/coreDriverInterfaceSupported.json +++ b/coreDriverInterfaceSupported.json @@ -20,6 +20,7 @@ "3.1", "4.0", "5.0", - "5.1" + "5.1", + "5.2" ] } diff --git a/src/main/java/io/supertokens/session/Session.java b/src/main/java/io/supertokens/session/Session.java index 380068a5e..4e4439c10 100644 --- a/src/main/java/io/supertokens/session/Session.java +++ b/src/main/java/io/supertokens/session/Session.java @@ -19,6 +19,7 @@ import com.google.gson.JsonObject; import io.supertokens.Main; import io.supertokens.ProcessState; +import io.supertokens.authRecipe.AuthRecipe; import io.supertokens.config.Config; import io.supertokens.config.CoreConfig; import io.supertokens.exceptions.AccessTokenPayloadError; @@ -53,6 +54,7 @@ import io.supertokens.storageLayer.StorageLayer; import io.supertokens.useridmapping.UserIdMapping; import io.supertokens.useridmapping.UserIdType; +import io.supertokens.utils.SemVer; import io.supertokens.utils.Utils; import org.jetbrains.annotations.TestOnly; @@ -82,7 +84,7 @@ public static SessionInformationHolder createNewSession(TenantIdentifier tenantI JWT.JWTException, UnsupportedJWTSigningAlgorithmException, AccessTokenPayloadError { try { return createNewSession(tenantIdentifier, storage, main, recipeUserId, userDataInJWT, userDataInDatabase, - false, AccessToken.getLatestVersion(), false); + false, AccessToken.getLatestVersion(), false, null); } catch (TenantOrAppNotFoundException e) { throw new IllegalStateException(e); } @@ -101,8 +103,9 @@ public static SessionInformationHolder createNewSession(Main main, try { return createNewSession( new TenantIdentifier(null, null, null), storage, main, - recipeUserId, userDataInJWT, userDataInDatabase, false, AccessToken.getLatestVersion(), false); - } catch (TenantOrAppNotFoundException e) { + recipeUserId, userDataInJWT, userDataInDatabase, false, + AccessToken.getLatestVersion(), false, null); + } catch (TenantOrAppNotFoundException | UnauthorisedException e) { throw new IllegalStateException(e); } } @@ -121,8 +124,8 @@ public static SessionInformationHolder createNewSession(Main main, @Nonnull Stri try { return createNewSession( new TenantIdentifier(null, null, null), storage, main, - recipeUserId, userDataInJWT, userDataInDatabase, enableAntiCsrf, version, useStaticKey); - } catch (TenantOrAppNotFoundException e) { + recipeUserId, userDataInJWT, userDataInDatabase, enableAntiCsrf, version, useStaticKey, null); + } catch (TenantOrAppNotFoundException | UnauthorisedException e) { throw new IllegalStateException(e); } } @@ -132,11 +135,11 @@ public static SessionInformationHolder createNewSession(TenantIdentifier tenantI @Nonnull JsonObject userDataInJWT, @Nonnull JsonObject userDataInDatabase, boolean enableAntiCsrf, AccessToken.VERSION version, - boolean useStaticKey) + boolean useStaticKey, SemVer semVer) throws NoSuchAlgorithmException, StorageQueryException, InvalidKeyException, InvalidKeySpecException, StorageTransactionLogicException, SignatureException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, NoSuchPaddingException, AccessTokenPayloadError, - UnsupportedJWTSigningAlgorithmException, TenantOrAppNotFoundException { + UnsupportedJWTSigningAlgorithmException, TenantOrAppNotFoundException, UnauthorisedException { String sessionHandle = UUID.randomUUID().toString(); if (!tenantIdentifier.getTenantId().equals(TenantIdentifier.DEFAULT_TENANT_ID)) { sessionHandle += "_" + tenantIdentifier.getTenantId(); @@ -151,6 +154,7 @@ public static SessionInformationHolder createNewSession(TenantIdentifier tenantI recipeUserId = userIdMapping.superTokensUserId; } + primaryUserId = StorageUtils.getAuthRecipeStorage(storage) .getPrimaryUserIdStrForUserId(tenantIdentifier.toAppIdentifier(), recipeUserId); if (primaryUserId == null) { @@ -166,6 +170,16 @@ public static SessionInformationHolder createNewSession(TenantIdentifier tenantI if (userIdMappings.containsKey(recipeUserId)) { recipeUserId = userIdMappings.get(recipeUserId); } + + if(semVer!= null && semVer.greaterThanOrEqualTo(SemVer.v5_2)) { + AuthRecipeUserInfo authRecipeUserInfo = AuthRecipe.getUserById(tenantIdentifier.toAppIdentifier(), + storage, recipeUserId); + if (authRecipeUserInfo != null) { + if (!authRecipeUserInfo.tenantIds.contains(tenantIdentifier.getTenantId())) { + throw new UnauthorisedException("User is not part of requested tenant!"); + } + } + } } String antiCsrfToken = enableAntiCsrf ? UUID.randomUUID().toString() : null; diff --git a/src/main/java/io/supertokens/utils/SemVer.java b/src/main/java/io/supertokens/utils/SemVer.java index 6c94518c3..ef43014af 100644 --- a/src/main/java/io/supertokens/utils/SemVer.java +++ b/src/main/java/io/supertokens/utils/SemVer.java @@ -37,6 +37,7 @@ public class SemVer implements Comparable { public static final SemVer v4_0 = new SemVer("4.0"); public static final SemVer v5_0 = new SemVer("5.0"); public static final SemVer v5_1 = new SemVer("5.1"); + public static final SemVer v5_2 = new SemVer("5.2"); final private String version; diff --git a/src/main/java/io/supertokens/webserver/WebserverAPI.java b/src/main/java/io/supertokens/webserver/WebserverAPI.java index 3cf62be23..33cb703a7 100644 --- a/src/main/java/io/supertokens/webserver/WebserverAPI.java +++ b/src/main/java/io/supertokens/webserver/WebserverAPI.java @@ -29,7 +29,8 @@ import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; -import io.supertokens.pluginInterface.multitenancy.*; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.useridmapping.UserIdType; @@ -76,10 +77,11 @@ public abstract class WebserverAPI extends HttpServlet { supportedVersions.add(SemVer.v4_0); supportedVersions.add(SemVer.v5_0); supportedVersions.add(SemVer.v5_1); + supportedVersions.add(SemVer.v5_2); } public static SemVer getLatestCDIVersion() { - return SemVer.v5_1; + return SemVer.v5_2; } public SemVer getLatestCDIVersionForRequest(HttpServletRequest req) diff --git a/src/main/java/io/supertokens/webserver/api/session/SessionAPI.java b/src/main/java/io/supertokens/webserver/api/session/SessionAPI.java index 7af0fa841..127db70f5 100644 --- a/src/main/java/io/supertokens/webserver/api/session/SessionAPI.java +++ b/src/main/java/io/supertokens/webserver/api/session/SessionAPI.java @@ -103,7 +103,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I SessionInformationHolder sessionInfo = Session.createNewSession( tenantIdentifier, storage, main, userId, userDataInJWT, userDataInDatabase, enableAntiCsrf, accessTokenVersion, - useStaticSigningKey); + useStaticSigningKey, version); if (storage.getType() == STORAGE_TYPE.SQL) { try { @@ -143,6 +143,11 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I super.sendJsonResponse(200, result, resp); } catch (AccessTokenPayloadError e) { throw new ServletException(new BadRequestException(e.getMessage())); + } catch (UnauthorisedException e) { + JsonObject reply = new JsonObject(); + reply.addProperty("status", "UNAUTHORISED"); + reply.addProperty("message", e.getMessage()); + super.sendJsonResponse(200, reply, resp); } catch (NoSuchAlgorithmException | StorageQueryException | InvalidKeyException | InvalidKeySpecException | StorageTransactionLogicException | SignatureException | IllegalBlockSizeException | BadPaddingException | InvalidAlgorithmParameterException | NoSuchPaddingException | diff --git a/src/test/java/io/supertokens/test/accountlinking/SessionTests.java b/src/test/java/io/supertokens/test/accountlinking/SessionTests.java index ac12a8c9a..cbb9de093 100644 --- a/src/test/java/io/supertokens/test/accountlinking/SessionTests.java +++ b/src/test/java/io/supertokens/test/accountlinking/SessionTests.java @@ -386,7 +386,7 @@ public void testSessionBehaviourWhenUserBelongsTo2TenantsAndThenLinkedToSomeOthe AuthRecipe.createPrimaryUser(process.getProcess(), t1.toAppIdentifier(), t1Storage, user2.getSupertokensUserId()); - AuthRecipe.linkAccounts(process.getProcess(), t1.toAppIdentifier(), t1Storage, user1.getSupertokensUserId(), + AuthRecipe.linkAccounts(process.getProcess(), t2.toAppIdentifier(), t2Storage, user1.getSupertokensUserId(), user2.getSupertokensUserId()); SessionInformationHolder session1 = Session.createNewSession(t2, t2Storage, process.getProcess(), diff --git a/src/test/java/io/supertokens/test/session/TenantCheckForKnownUsersTest.java b/src/test/java/io/supertokens/test/session/TenantCheckForKnownUsersTest.java new file mode 100644 index 000000000..c8ea41cc0 --- /dev/null +++ b/src/test/java/io/supertokens/test/session/TenantCheckForKnownUsersTest.java @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test.session; + +import com.google.gson.JsonObject; +import io.supertokens.ProcessState; +import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.exceptions.UnauthorisedException; +import io.supertokens.multitenancy.Multitenancy; +import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.multitenancy.*; +import io.supertokens.session.Session; +import io.supertokens.session.accessToken.AccessToken; +import io.supertokens.session.info.SessionInformationHolder; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import io.supertokens.utils.SemVer; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import static junit.framework.TestCase.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +public class TenantCheckForKnownUsersTest { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + @Test + public void verifyUnknownUsersSessionCreationWorks() throws Exception { + + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + String userId = "userId-not-existing"; + JsonObject userDataInJWT = new JsonObject(); + userDataInJWT.addProperty("key", "value"); + JsonObject userDataInDatabase = new JsonObject(); + userDataInDatabase.addProperty("key", "value"); + + SessionInformationHolder sessionInfo = Session.createNewSession(new TenantIdentifier(null, null, null), StorageLayer.getBaseStorage(process.getProcess()), + process.getProcess(), userId, userDataInJWT, userDataInDatabase, true, AccessToken.getLatestVersion(), false, + SemVer.v5_2); + + JsonObject sessionData = Session.getSession(process.getProcess(), + sessionInfo.session.handle).userDataInDatabase; + assertEquals(userDataInDatabase.toString(), sessionData.toString()); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void verifyKnownUsersWithRightTenantSessionCreationWorks() throws Exception { + + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + TenantIdentifier app = new TenantIdentifier(null, "a1", null); + TenantIdentifier tenant = new TenantIdentifier(null, "a1", "t1"); + + // Create tenants + Multitenancy.addNewOrUpdateAppOrTenant(process.getProcess(), new TenantConfig( + app, + new EmailPasswordConfig(true), + new ThirdPartyConfig(true, null), + new PasswordlessConfig(true), + null, null, new JsonObject() + ), false); + + Multitenancy.addNewOrUpdateAppOrTenant(process.getProcess(), new TenantConfig( + tenant, + new EmailPasswordConfig(true), + new ThirdPartyConfig(true, null), + new PasswordlessConfig(true), + null, null, new JsonObject() + ), false); + + Storage appStorage = ( + StorageLayer.getStorage(app, process.getProcess())); + Storage tenantStorage = ( + StorageLayer.getStorage(tenant, process.getProcess())); + + + AuthRecipeUserInfo user = EmailPassword.signUp(app, appStorage, process.getProcess(), "test@example.com", + "password"); + String userId = user.getSupertokensUserId(); + + Multitenancy.addUserIdToTenant(process.getProcess(), tenant, tenantStorage, userId); + + JsonObject userDataInJWT = new JsonObject(); + userDataInJWT.addProperty("key", "value"); + JsonObject userDataInDatabase = new JsonObject(); + userDataInDatabase.addProperty("key", "value"); + + SessionInformationHolder sessionInfo = Session.createNewSession(new TenantIdentifier(null, "a1", "t1"), StorageLayer.getBaseStorage(process.getProcess()), + process.getProcess(), userId, userDataInJWT, userDataInDatabase, true, AccessToken.getLatestVersion(), false, + SemVer.v5_2); + + JsonObject sessionData = Session.getSession(new TenantIdentifier(null, "a1", "t1"), + StorageLayer.getBaseStorage(process.getProcess()), sessionInfo.session.handle).userDataInDatabase; + assertEquals(userDataInDatabase.toString(), sessionData.toString()); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void verifyKnownUsersSessionCreationWithWrongTenantThrows() throws Exception { + + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + TenantIdentifier app = new TenantIdentifier(null, "a1", null); + TenantIdentifier tenant = new TenantIdentifier(null, "a1", "t1"); + TenantIdentifier tenant2 = new TenantIdentifier(null, "a1", "t2"); + + // Create tenants + Multitenancy.addNewOrUpdateAppOrTenant(process.getProcess(), new TenantConfig( + app, + new EmailPasswordConfig(true), + new ThirdPartyConfig(true, null), + new PasswordlessConfig(true), + null, null, new JsonObject() + ), false); + + Multitenancy.addNewOrUpdateAppOrTenant(process.getProcess(), new TenantConfig( + tenant, + new EmailPasswordConfig(true), + new ThirdPartyConfig(true, null), + new PasswordlessConfig(true), + null, null, new JsonObject() + ), false); + + Multitenancy.addNewOrUpdateAppOrTenant(process.getProcess(), new TenantConfig( + tenant2, + new EmailPasswordConfig(true), + new ThirdPartyConfig(true, null), + new PasswordlessConfig(true), + null, null, new JsonObject() + ), false); + + Storage appStorage = ( + StorageLayer.getStorage(app, process.getProcess())); + Storage tenantStorage = ( + StorageLayer.getStorage(tenant, process.getProcess())); + + + AuthRecipeUserInfo user = EmailPassword.signUp(app, appStorage, process.getProcess(), "test@example.com", + "password"); + String userId = user.getSupertokensUserId(); + + Multitenancy.addUserIdToTenant(process.getProcess(), tenant, tenantStorage, userId); //user only added to tenant! + + + JsonObject userDataInJWT = new JsonObject(); + userDataInJWT.addProperty("key", "value"); + JsonObject userDataInDatabase = new JsonObject(); + userDataInDatabase.addProperty("key", "value"); + + + try { + SessionInformationHolder sessionInfo = Session.createNewSession(tenant2, StorageLayer.getBaseStorage(process.getProcess()), + process.getProcess(), userId, userDataInJWT, userDataInDatabase, true, AccessToken.getLatestVersion(), false, + SemVer.v5_2); + + fail(); + } catch (UnauthorisedException e) { + //pass + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void verifyKnownUsersSessionCreationWithWrongTenantDoesntThrowWithLesserCDI() throws Exception { + + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + TenantIdentifier app = new TenantIdentifier(null, "a1", null); + TenantIdentifier tenant = new TenantIdentifier(null, "a1", "t1"); + TenantIdentifier tenant2 = new TenantIdentifier(null, "a1", "t2"); + + // Create tenants + Multitenancy.addNewOrUpdateAppOrTenant(process.getProcess(), new TenantConfig( + app, + new EmailPasswordConfig(true), + new ThirdPartyConfig(true, null), + new PasswordlessConfig(true), + null, null, new JsonObject() + ), false); + + Multitenancy.addNewOrUpdateAppOrTenant(process.getProcess(), new TenantConfig( + tenant, + new EmailPasswordConfig(true), + new ThirdPartyConfig(true, null), + new PasswordlessConfig(true), + null, null, new JsonObject() + ), false); + + Multitenancy.addNewOrUpdateAppOrTenant(process.getProcess(), new TenantConfig( + tenant2, + new EmailPasswordConfig(true), + new ThirdPartyConfig(true, null), + new PasswordlessConfig(true), + null, null, new JsonObject() + ), false); + + Storage appStorage = ( + StorageLayer.getStorage(app, process.getProcess())); + Storage tenantStorage = ( + StorageLayer.getStorage(tenant, process.getProcess())); + + + AuthRecipeUserInfo user = EmailPassword.signUp(app, appStorage, process.getProcess(), "test@example.com", + "password"); + String userId = user.getSupertokensUserId(); + + Multitenancy.addUserIdToTenant(process.getProcess(), tenant, tenantStorage, userId); //user only added to tenant! + + + JsonObject userDataInJWT = new JsonObject(); + userDataInJWT.addProperty("key", "value"); + JsonObject userDataInDatabase = new JsonObject(); + userDataInDatabase.addProperty("key", "value"); + + SessionInformationHolder sessionInfo = Session.createNewSession(tenant2, StorageLayer.getBaseStorage(process.getProcess()), + process.getProcess(), userId, userDataInJWT, userDataInDatabase, true, AccessToken.getLatestVersion(), false, + SemVer.v5_1); + + JsonObject sessionData = Session.getSession(tenant2, StorageLayer.getBaseStorage(process.getProcess()), sessionInfo.session.handle).userDataInDatabase; + assertEquals(userDataInDatabase.toString(), sessionData.toString()); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + +} diff --git a/src/test/java/io/supertokens/test/session/api/SessionAPITest5_2.java b/src/test/java/io/supertokens/test/session/api/SessionAPITest5_2.java new file mode 100644 index 000000000..605b60d4b --- /dev/null +++ b/src/test/java/io/supertokens/test/session/api/SessionAPITest5_2.java @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test.session.api; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import io.supertokens.ProcessState; +import io.supertokens.session.accessToken.AccessToken; +import io.supertokens.session.jwt.JWT; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import io.supertokens.test.httpRequest.HttpRequestForTesting; +import io.supertokens.utils.SemVer; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Map; +import java.util.UUID; + +import static junit.framework.TestCase.*; +import static org.junit.Assert.assertNotNull; + +public class SessionAPITest5_2 { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + @Test + public void successOutputCheck() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + String userId = "userId"; + JsonObject userDataInJWT = new JsonObject(); + userDataInJWT.addProperty("key", "value"); + JsonObject userDataInDatabase = new JsonObject(); + userDataInDatabase.addProperty("key", "value"); + + JsonObject request = new JsonObject(); + request.addProperty("userId", userId); + request.add("userDataInJWT", userDataInJWT); + request.add("userDataInDatabase", userDataInDatabase); + request.addProperty("useStaticKey", false); + request.addProperty("enableAntiCsrf", false); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/session", request, 1000, 1000, null, SemVer.v5_2.get(), + "session"); + checkSessionResponse(response, process, userId, userDataInJWT, false); + assertFalse(response.has("antiCsrfToken")); + + String iat = "" + JWT.getPayloadWithoutVerifying( + response.get("accessToken").getAsJsonObject().get("token").getAsString()).payload.get("iat").getAsInt(); + assertEquals(10, iat.length()); + //noinspection ResultOfMethodCallIgnored + Long.parseLong(iat); // We are checking that this doesn't throw, it would if it was in exponential form + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + + } + + @Test + public void badInputTest() throws Exception { + + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + String userId = "userId"; + JsonObject userDataInDatabase = new JsonObject(); + userDataInDatabase.addProperty("key", "value"); + + try { + JsonObject userDataInJWT = new JsonObject(); + userDataInJWT.addProperty("sub", "value"); + + JsonObject request = new JsonObject(); + request.addProperty("userId", userId); + request.add("userDataInJWT", userDataInJWT); + request.add("userDataInDatabase", userDataInDatabase); + request.addProperty("enableAntiCsrf", false); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/recipe/session", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), "session"); + fail(); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + assertEquals(e.statusCode, 400); + assertEquals(e.getMessage(), + "Http error. Status Code: 400. Message: The user payload contains protected field"); + } + + try { + JsonObject userDataInJWT = new JsonObject(); + userDataInJWT.addProperty("rsub", "value"); + + JsonObject request = new JsonObject(); + request.addProperty("userId", userId); + request.add("userDataInJWT", userDataInJWT); + request.add("userDataInDatabase", userDataInDatabase); + request.addProperty("enableAntiCsrf", false); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/recipe/session", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), "session"); + fail(); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + assertEquals(e.statusCode, 400); + assertEquals(e.getMessage(), + "Http error. Status Code: 400. Message: The user payload contains protected field"); + } + + try { + JsonObject userDataInJWT = new JsonObject(); + userDataInJWT.addProperty("tId", "t1"); + + JsonObject request = new JsonObject(); + request.addProperty("userId", userId); + request.add("userDataInJWT", userDataInJWT); + request.add("userDataInDatabase", userDataInDatabase); + request.addProperty("enableAntiCsrf", false); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/recipe/session", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), "session"); + fail(); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + assertEquals(e.statusCode, 400); + assertEquals(e.getMessage(), + "Http error. Status Code: 400. Message: The user payload contains protected field"); + } + + try { + JsonObject userDataInJWT = new JsonObject(); + userDataInJWT.addProperty("exp", "value"); + + JsonObject request = new JsonObject(); + request.addProperty("userId", userId); + request.add("userDataInJWT", userDataInJWT); + request.add("userDataInDatabase", userDataInDatabase); + request.addProperty("enableAntiCsrf", false); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/recipe/session", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), "session"); + fail(); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + assertEquals(e.statusCode, 400); + assertEquals(e.getMessage(), + "Http error. Status Code: 400. Message: The user payload contains protected field"); + } + + try { + JsonObject userDataInJWT = new JsonObject(); + userDataInJWT.addProperty("sessionHandle", "value"); + + JsonObject request = new JsonObject(); + request.addProperty("userId", userId); + request.add("userDataInJWT", userDataInJWT); + request.add("userDataInDatabase", userDataInDatabase); + request.addProperty("enableAntiCsrf", false); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/recipe/session", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), "session"); + fail(); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + assertEquals(e.statusCode, 400); + assertEquals(e.getMessage(), + "Http error. Status Code: 400. Message: The user payload contains protected field"); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void successOutputCheckWithStatic() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + String userId = "userId"; + JsonObject userDataInJWT = new JsonObject(); + userDataInJWT.addProperty("key", "value"); + JsonObject userDataInDatabase = new JsonObject(); + userDataInDatabase.addProperty("key", "value"); + + JsonObject request = new JsonObject(); + request.addProperty("userId", userId); + request.add("userDataInJWT", userDataInJWT); + request.add("userDataInDatabase", userDataInDatabase); + request.addProperty("useDynamicSigningKey", false); + request.addProperty("enableAntiCsrf", false); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/session", request, 1000, 1000, null, SemVer.v5_2.get(), + "session"); + checkSessionResponse(response, process, userId, userDataInJWT, true); + assertFalse(response.has("antiCsrfToken")); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + public static void checkSessionResponse(JsonObject response, TestingProcessManager.TestingProcess process, + String userId, JsonObject userDataInJWT, boolean isStatic) + throws JWT.JWTException { + assertNotNull(response.get("session").getAsJsonObject().get("handle").getAsString()); + assertEquals(response.get("session").getAsJsonObject().get("userId").getAsString(), userId); + assertEquals(response.get("session").getAsJsonObject().get("userDataInJWT").getAsJsonObject().toString(), + userDataInJWT.toString()); + assertEquals(response.get("session").getAsJsonObject().get("tenantId").getAsString(), "public"); + assertEquals(response.get("session").getAsJsonObject().get("recipeUserId").getAsString(), userId); + assertEquals(response.get("session").getAsJsonObject().entrySet().size(), 5); + + assertTrue(response.get("accessToken").getAsJsonObject().has("token")); + assertTrue(response.get("accessToken").getAsJsonObject().has("expiry")); + assertTrue(response.get("accessToken").getAsJsonObject().has("createdTime")); + assertEquals(response.get("accessToken").getAsJsonObject().entrySet().size(), 3); + + assertTrue(response.get("refreshToken").getAsJsonObject().has("token")); + assertTrue(response.get("refreshToken").getAsJsonObject().has("expiry")); + assertTrue(response.get("refreshToken").getAsJsonObject().has("createdTime")); + assertEquals(response.get("refreshToken").getAsJsonObject().entrySet().size(), 3); + + JWT.JWTPreParseInfo preParseInfo = JWT.preParseJWTInfo( + response.get("accessToken").getAsJsonObject().get("token").getAsString()); + assertEquals(preParseInfo.version, AccessToken.VERSION.V5); + assertNotNull(preParseInfo.kid); + + IllegalArgumentException caught = null; + try { + // We are just checking the format basically; + UUID.fromString(preParseInfo.kid.substring(2)); + } catch (IllegalArgumentException ex) { + caught = ex; + } + + if (isStatic) { + assertNull(caught); + assertEquals("s-", preParseInfo.kid.substring(0, 2)); + } else { + assertNotNull(caught); + assertEquals("d-", preParseInfo.kid.substring(0, 2)); + } + + Base64.getUrlDecoder().decode(preParseInfo.header); + Base64.getUrlDecoder().decode(preParseInfo.signature); + + + JsonObject payload = new JsonParser().parse( + new String(Base64.getUrlDecoder().decode(preParseInfo.payload), StandardCharsets.UTF_8)) + .getAsJsonObject(); + assertFalse(payload.has("userData")); + + for (Map.Entry entry : userDataInJWT.entrySet()) { + assertTrue(payload.has(entry.getKey())); + assertEquals(payload.get(entry.getKey()).toString(), userDataInJWT.get(entry.getKey()).toString()); + } + } +}