Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: stats fix #816

Merged
merged 4 commits into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [unreleased]

## [6.0.13] - 2023-09-15

- Fixes paid stats reporting for multitenancy

## [6.0.12] - 2023-09-04

- Fixes randomly occurring `serialization error for concurrent update` in `verifySession` API
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ compileTestJava { options.encoding = "UTF-8" }
// }
//}

version = "6.0.12"
version = "6.0.13"


repositories {
Expand Down
43 changes: 35 additions & 8 deletions ee/src/main/java/io/supertokens/ee/EEFeatureFlag.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ public class EEFeatureFlag implements io.supertokens.featureflag.EEFeatureFlagIn
public static final String FEATURE_FLAG_KEY_IN_DB = "FEATURE_FLAG";
public static final String LICENSE_KEY_IN_DB = "LICENSE_KEY";

private static final List<JsonObject> licenseCheckRequests = new ArrayList<>();

private static final String[] ENTERPRISE_THIRD_PARTY_IDS = new String[] {
"google-workspaces",
"okta",
Expand Down Expand Up @@ -151,6 +153,12 @@ public void syncFeatureFlagWithLicenseKey()
licenseKey = this.getLicenseKeyFromDb();
this.isLicenseKeyPresent = true;
} catch (NoLicenseKeyFoundException ex) {
rishabhpoddar marked this conversation as resolved.
Show resolved Hide resolved
try {
licenseKey = this.getRootLicenseKeyFromDb();
verifyLicenseKey(licenseKey); // also sends paid user stats for the app
} catch (NoLicenseKeyFoundException ex2) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
} catch (NoLicenseKeyFoundException ex2) {
} catch (NoLicenseKeyFoundException | InvalidLicenseKeyException ex2) {

// follow through below
}
this.isLicenseKeyPresent = false;
this.setEnabledEEFeaturesInDb(new EE_FEATURES[]{});
return;
Expand Down Expand Up @@ -394,6 +402,9 @@ private EE_FEATURES[] doServerCall(String licenseKey)
json.addProperty("licenseKey", licenseKey);
json.addProperty("superTokensVersion", Version.getVersion(main).getCoreVersion());
json.add("paidFeatureUsageStats", this.getPaidFeatureStats());
if (Main.isTesting) {
licenseCheckRequests.add(json);
}
ProcessState.getInstance(main).addState(ProcessState.PROCESS_STATE.LICENSE_KEY_CHECK_NETWORK_CALL, null);
JsonObject licenseCheckResponse = HttpRequest.sendJsonPOSTRequest(this.main, REQUEST_ID,
"https://api.supertokens.io/0/st/license/check",
Expand Down Expand Up @@ -476,17 +487,33 @@ private void removeLicenseKeyFromDb() throws StorageQueryException, TenantOrAppN
new KeyValueInfo(LICENSE_KEY_IN_DB_NOT_PRESENT_VALUE));
}

@Override
public String getLicenseKeyFromDb()
throws NoLicenseKeyFoundException, StorageQueryException, TenantOrAppNotFoundException {
Logging.debug(main, appIdentifier.getAsPublicTenantIdentifier(), "Attempting to fetch license key from db");
KeyValueInfo info = StorageLayer.getStorage(this.appIdentifier.getAsPublicTenantIdentifier(), main)
.getKeyValue(this.appIdentifier.getAsPublicTenantIdentifier(), LICENSE_KEY_IN_DB);
private String getLicenseKeyInDb(TenantIdentifier tenantIdentifier)
throws TenantOrAppNotFoundException, StorageQueryException, NoLicenseKeyFoundException {
Logging.debug(main, tenantIdentifier, "Attempting to fetch license key from db");
KeyValueInfo info = StorageLayer.getStorage(tenantIdentifier, main)
.getKeyValue(tenantIdentifier, LICENSE_KEY_IN_DB);
if (info == null || info.value.equals(LICENSE_KEY_IN_DB_NOT_PRESENT_VALUE)) {
Logging.debug(main, appIdentifier.getAsPublicTenantIdentifier(), "No license key found in db");
Logging.debug(main, tenantIdentifier, "No license key found in db");
throw new NoLicenseKeyFoundException();
}
Logging.debug(main, appIdentifier.getAsPublicTenantIdentifier(), "Fetched license key from db: " + info.value);
Logging.debug(main, tenantIdentifier, "Fetched license key from db: " + info.value);
return info.value;
}

@Override
public String getLicenseKeyFromDb()
throws NoLicenseKeyFoundException, StorageQueryException, TenantOrAppNotFoundException {
return getLicenseKeyInDb(appIdentifier.getAsPublicTenantIdentifier());
}

private String getRootLicenseKeyFromDb()
throws TenantOrAppNotFoundException, StorageQueryException, NoLicenseKeyFoundException {
return getLicenseKeyInDb(TenantIdentifier.BASE_TENANT);
}

@TestOnly
public static List<JsonObject> getLicenseCheckRequests() {
assert (Main.isTesting);
return licenseCheckRequests;
}
}
129 changes: 129 additions & 0 deletions ee/src/test/java/io/supertokens/ee/test/TestMultitenancyStats.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package io.supertokens.ee.test;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import io.supertokens.ProcessState;
import io.supertokens.cronjobs.CronTaskTest;
import io.supertokens.ee.EEFeatureFlag;
import io.supertokens.ee.cronjobs.EELicenseCheck;
import io.supertokens.ee.test.httpRequest.HttpRequestForTesting;
import io.supertokens.featureflag.FeatureFlag;
import io.supertokens.multitenancy.Multitenancy;
import io.supertokens.pluginInterface.multitenancy.*;
import io.supertokens.storageLayer.StorageLayer;
import io.supertokens.webserver.WebserverAPI;
import org.junit.*;
import org.junit.rules.TestRule;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class TestMultitenancyStats {
@Rule
public TestRule watchman = Utils.getOnFailure();

@AfterClass
public static void afterTesting() {
Utils.afterTesting();
}

@Before
public void beforeEach() {
Utils.reset();
FeatureFlag.clearURLClassLoader();
}

private final String OPAQUE_KEY_WITH_MULTITENANCY_FEATURE = "ijaleljUd2kU9XXWLiqFYv5br8nutTxbyBqWypQdv2N-" +
"BocoNriPrnYQd0NXPm8rVkeEocN9ayq0B7c3Pv-BTBIhAZSclXMlgyfXtlwAOJk=9BfESEleW6LyTov47dXu";

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reset the static variable

@Test
public void testPaidStatsIsSentForAllAppsInMultitenancy() throws Exception {
String[] args = {"../../"};

TestingProcessManager.TestingProcess process = TestingProcessManager.start(args);
CronTaskTest.getInstance(process.main).setIntervalInSeconds(EELicenseCheck.RESOURCE_KEY, 1);
Assert.assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));

if (StorageLayer.isInMemDb(process.main)) {
// cause we keep all features enabled in memdb anyway
return;
}

{
// Add the license
JsonObject requestBody = new JsonObject();

requestBody.addProperty("licenseKey", OPAQUE_KEY_WITH_MULTITENANCY_FEATURE);

HttpRequestForTesting.sendJsonPUTRequest(process.getProcess(), "",
"http://localhost:3567/ee/license",
requestBody, 10000, 10000, null, WebserverAPI.getLatestCDIVersion().get(), "");
}

{
// Create tenants and apps
JsonObject config = new JsonObject();
StorageLayer.getStorage(new TenantIdentifier(null, null, null), process.getProcess())
.modifyConfigToAddANewUserPoolForTesting(config, 1);

Multitenancy.addNewOrUpdateAppOrTenant(process.getProcess(), new TenantConfig(
new TenantIdentifier("127.0.0.1", null, null),
new EmailPasswordConfig(true),
new ThirdPartyConfig(true, null),
new PasswordlessConfig(true),
config
), false);

Multitenancy.addNewOrUpdateAppOrTenant(process.getProcess(), new TenantConfig(
new TenantIdentifier("127.0.0.1", "a1", null),
new EmailPasswordConfig(true),
new ThirdPartyConfig(true, null),
new PasswordlessConfig(true),
config
), false);

Multitenancy.addNewOrUpdateAppOrTenant(process.getProcess(), new TenantConfig(
new TenantIdentifier("127.0.0.1", "a1", "t1"),
new EmailPasswordConfig(true),
new ThirdPartyConfig(true, null),
new PasswordlessConfig(true),
config
), false);
}

Thread.sleep(2000); // Let all the cron tasks run

List<JsonObject> requests = EEFeatureFlag.getLicenseCheckRequests();
Set<TenantIdentifier> tenantIdentifiers = new HashSet<>();

for (JsonObject request : requests) {
if (request.has("paidFeatureUsageStats")) {
JsonObject paidStats = request.getAsJsonObject("paidFeatureUsageStats");
if (paidStats.has("multi_tenancy")) {
JsonObject mtStats = paidStats.getAsJsonObject("multi_tenancy");
String cud = mtStats.get("connectionUriDomain").getAsString();
String appId = mtStats.get("appId").getAsString();

JsonArray tenants = mtStats.get("tenants").getAsJsonArray();
for (JsonElement tenantElem : tenants) {
JsonObject tenant = tenantElem.getAsJsonObject();
String tenantId = tenant.get("tenantId").getAsString();

tenantIdentifiers.add(new TenantIdentifier(cud, appId, tenantId));
}
}
}
}

Assert.assertEquals(tenantIdentifiers.size(), 4);
Assert.assertTrue(tenantIdentifiers.contains(new TenantIdentifier(null, null, null)));
Assert.assertTrue(tenantIdentifiers.contains(new TenantIdentifier("127.0.0.1", null, null)));
Assert.assertTrue(tenantIdentifiers.contains(new TenantIdentifier("127.0.0.1", "a1", null)));
Assert.assertTrue(tenantIdentifiers.contains(new TenantIdentifier("127.0.0.1", "a1", "t1")));

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