Skip to content

Commit

Permalink
feat: Changes for multitenancy dashboard (#146)
Browse files Browse the repository at this point in the history
* feat: Add a new method to fetch the config as json (#137)

* Add a new method to fetch the config as json

* Update changelog

* Revert version changes

* Update getConfigFieldInfo method implementation

* Add non null to not null fields

* Update getConfigFieldsInfo method signature in Storage interface

* Add @nullable annotation to ConfigFieldInfo class

* fix: core config list

* fix: core config

* fix: updated types

* fix: refactor config description

* fix: type fix

* fix: pr comments

* fix: pr comments

* fix: pr comments

* fix: to json 5.0

* fix: version and changelog

* fix: rename

* fix: nullable

* fix: condition

* fix: changelog

* fix: refactor

---------

Co-authored-by: Sattvik Chakravarthy <[email protected]>
  • Loading branch information
prateek3255 and sattvikc authored May 24, 2024
1 parent 8886445 commit ddab5f2
Show file tree
Hide file tree
Showing 6 changed files with 225 additions and 16 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [6.2.0] - 2024-05-24

- Adds new class `ConfigFieldInfo` that represents a core config field
- Adds new method `getPluginConfigFieldsInfo` to fetch the plugin config as json in `DashboardStorage`
- Updates `TenantConfig` to support `null` and empty array state for `firstFactors`
- Update `ThirdPartyConfig` to support `null` and empty array state for `providers`

## [6.1.0] - 2024-04-17

- Adds `addTenantIdentifier` to the storage interface.
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ plugins {
id 'java-library'
}

version = "6.1.0"
version = "6.2.0"

repositories {
mavenCentral()
Expand Down
64 changes: 64 additions & 0 deletions src/main/java/io/supertokens/pluginInterface/ConfigFieldInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* 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.pluginInterface;

import com.google.gson.JsonElement;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

public class ConfigFieldInfo {
@Nonnull
public String key;

@Nonnull
public String valueType;

Object value;

@Nonnull
public String description;

public boolean isDifferentAcrossTenants;

@Nullable
public String[] possibleValues;

public boolean isNullable;

public Object defaultValue;

public boolean isPluginProperty;

public boolean isPluginPropertyEditable;

public ConfigFieldInfo(@Nonnull String key, @Nonnull String valueType, JsonElement value, @Nonnull String description,
boolean isDifferentAcrossTenants,
@Nullable String[] possibleValues, boolean isNullable, Object defaultValue,
boolean isPluginProperty, boolean isPluginPropertyEditable) {
this.key = key;
this.valueType = valueType;
this.value = value;
this.description = description;
this.isDifferentAcrossTenants = isDifferentAcrossTenants;
this.possibleValues = possibleValues;
this.isNullable = isNullable;
this.defaultValue = defaultValue;
this.isPluginProperty = isPluginProperty;
this.isPluginPropertyEditable = isPluginPropertyEditable;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.supertokens.pluginInterface.dashboard;

import io.supertokens.pluginInterface.ConfigFieldInfo;
import io.supertokens.pluginInterface.Storage;
import io.supertokens.pluginInterface.dashboard.exceptions.DuplicateEmailException;
import io.supertokens.pluginInterface.dashboard.exceptions.DuplicateUserIdException;
Expand All @@ -8,6 +9,8 @@
import io.supertokens.pluginInterface.multitenancy.AppIdentifier;
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;

import java.util.List;

public interface DashboardStorage extends Storage {

void createNewDashboardUser(AppIdentifier appIdentifier, DashboardUser userInfo)
Expand Down Expand Up @@ -35,4 +38,6 @@ DashboardSessionInfo getSessionInfoWithSessionId(AppIdentifier appIdentifier, St

// this function removes based on expired time, so we can use this to globally remove from a particular db.
void revokeExpiredSessions() throws StorageQueryException;

List<ConfigFieldInfo> getPluginConfigFieldsInfo();
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@
package io.supertokens.pluginInterface.multitenancy;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.annotations.SerializedName;
import io.supertokens.pluginInterface.Storage;
import io.supertokens.pluginInterface.utils.Utils;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;

public class TenantConfig {

Expand Down Expand Up @@ -56,27 +58,34 @@ public class TenantConfig {
public TenantConfig(@Nonnull TenantIdentifier tenantIdentifier, @Nonnull EmailPasswordConfig emailPasswordConfig,
@Nonnull ThirdPartyConfig thirdPartyConfig,
@Nonnull PasswordlessConfig passwordlessConfig,
@Nullable String[] firstFactors, @Nullable String[] requiredSecondaryFactors,
@Nullable String[] firstFactors,
@Nullable String[] requiredSecondaryFactors,
@Nullable JsonObject coreConfig) {
this.tenantIdentifier = tenantIdentifier;
this.coreConfig = coreConfig == null ? new JsonObject() : coreConfig;
this.emailPasswordConfig = emailPasswordConfig;
this.passwordlessConfig = passwordlessConfig;
this.thirdPartyConfig = thirdPartyConfig;
this.firstFactors = firstFactors == null || firstFactors.length == 0 ? null : firstFactors;
this.requiredSecondaryFactors = requiredSecondaryFactors == null || requiredSecondaryFactors.length == 0 ? null : requiredSecondaryFactors;
this.firstFactors = firstFactors;
this.requiredSecondaryFactors =
requiredSecondaryFactors == null || requiredSecondaryFactors.length == 0 ? null :
requiredSecondaryFactors;
}

public TenantConfig(TenantConfig other) {
// copy constructor, that does a deep copy
Gson gson = new Gson();
this.tenantIdentifier = new TenantIdentifier(other.tenantIdentifier.getConnectionUriDomain(), other.tenantIdentifier.getAppId(), other.tenantIdentifier.getTenantId());
this.tenantIdentifier = new TenantIdentifier(other.tenantIdentifier.getConnectionUriDomain(),
other.tenantIdentifier.getAppId(), other.tenantIdentifier.getTenantId());
this.coreConfig = gson.fromJson(other.coreConfig.toString(), JsonObject.class);
this.emailPasswordConfig = new EmailPasswordConfig(other.emailPasswordConfig.enabled);
this.passwordlessConfig = new PasswordlessConfig(other.passwordlessConfig.enabled);
this.thirdPartyConfig = new ThirdPartyConfig(other.thirdPartyConfig.enabled, other.thirdPartyConfig.providers.clone());
this.thirdPartyConfig = new ThirdPartyConfig(
other.thirdPartyConfig.enabled,
other.thirdPartyConfig.providers != null ? other.thirdPartyConfig.providers.clone() : null);
this.firstFactors = other.firstFactors == null ? null : other.firstFactors.clone();
this.requiredSecondaryFactors = other.requiredSecondaryFactors == null ? null : other.requiredSecondaryFactors.clone();
this.requiredSecondaryFactors =
other.requiredSecondaryFactors == null ? null : other.requiredSecondaryFactors.clone();
}

public boolean deepEquals(TenantConfig other) {
Expand Down Expand Up @@ -106,20 +115,105 @@ public int hashCode() {
return tenantIdentifier.hashCode();
}

public JsonObject toJson(boolean shouldProtectDbConfig, Storage storage, String[] protectedCoreConfigs) {
public JsonObject toJson3_0(boolean shouldProtectDbConfig, Storage storage, String[] protectedCoreConfigs) {
JsonObject tenantConfigObject = toJson5_0(shouldProtectDbConfig, storage, protectedCoreConfigs);

// as per https://github.com/supertokens/supertokens-core/issues/979#issuecomment-2099971371
tenantConfigObject.get("emailPassword").getAsJsonObject().addProperty(
"enabled",
(this.firstFactors == null && this.emailPasswordConfig.enabled) ||
(this.firstFactors != null && List.of(this.firstFactors).contains("emailpassword"))
);
tenantConfigObject.get("thirdParty").getAsJsonObject().addProperty(
"enabled",
(this.firstFactors == null && this.thirdPartyConfig.enabled) ||
(this.firstFactors != null && List.of(this.firstFactors).contains("thirdparty"))
);
tenantConfigObject.get("passwordless").getAsJsonObject().addProperty(
"enabled",
(this.firstFactors == null && this.passwordlessConfig.enabled) ||
(this.firstFactors != null &&
(List.of(this.firstFactors).contains("otp-email") ||
List.of(this.firstFactors).contains("otp-phone") ||
List.of(this.firstFactors).contains("link-email") ||
List.of(this.firstFactors).contains("link-phone")))
);

tenantConfigObject.remove("firstFactors");
tenantConfigObject.remove("requiredSecondaryFactors");

return tenantConfigObject;
}

public JsonObject toJson5_0(boolean shouldProtectDbConfig, Storage storage, String[] protectedCoreConfigs) {

Gson gson = new Gson();
JsonObject tenantConfigObject = gson.toJsonTree(this).getAsJsonObject();

tenantConfigObject.add("thirdParty", this.thirdPartyConfig.toJson());
tenantConfigObject.addProperty("tenantId", this.tenantIdentifier.getTenantId());

if (shouldProtectDbConfig) {
String[] protectedConfigs = storage.getProtectedConfigsFromSuperTokensSaaSUsers();
for (String config : protectedConfigs) {
if (tenantConfigObject.get("coreConfig").getAsJsonObject().has(config)) {
tenantConfigObject.get("coreConfig").getAsJsonObject().remove(config);
}
}

for (String config : protectedCoreConfigs) {
if (tenantConfigObject.get("coreConfig").getAsJsonObject().has(config)) {
tenantConfigObject.get("coreConfig").getAsJsonObject().remove(config);
}
}
}

if (!tenantConfigObject.get("thirdParty").getAsJsonObject().has("providers")) {
tenantConfigObject.get("thirdParty").getAsJsonObject().add("providers", new JsonArray());
}

// as per https://github.com/supertokens/supertokens-core/issues/979#issuecomment-2099971371
tenantConfigObject.get("emailPassword").getAsJsonObject().addProperty(
"enabled",
(this.firstFactors == null && this.emailPasswordConfig.enabled) ||
(this.firstFactors != null && List.of(this.firstFactors).contains("emailpassword")) ||
(this.requiredSecondaryFactors != null && List.of(this.requiredSecondaryFactors).contains("emailpassword"))
);
tenantConfigObject.get("thirdParty").getAsJsonObject().addProperty(
"enabled",
(this.firstFactors == null && this.thirdPartyConfig.enabled) ||
(this.firstFactors != null && List.of(this.firstFactors).contains("thirdparty")) ||
(this.requiredSecondaryFactors != null && List.of(this.requiredSecondaryFactors).contains("thirdparty"))
);
tenantConfigObject.get("passwordless").getAsJsonObject().addProperty(
"enabled",
(this.firstFactors == null && this.passwordlessConfig.enabled) ||
(this.firstFactors != null &&
(List.of(this.firstFactors).contains("otp-email") ||
List.of(this.firstFactors).contains("otp-phone") ||
List.of(this.firstFactors).contains("link-email") ||
List.of(this.firstFactors).contains("link-phone"))) ||
(this.requiredSecondaryFactors != null &&
(List.of(this.requiredSecondaryFactors).contains("otp-email") ||
List.of(this.requiredSecondaryFactors).contains("otp-phone") ||
List.of(this.requiredSecondaryFactors).contains("link-email") ||
List.of(this.requiredSecondaryFactors).contains("link-phone")))
);

if (tenantConfigObject.has("firstFactors") && tenantConfigObject.get("firstFactors").getAsJsonArray().size() == 0) {
tenantConfigObject.remove("firstFactors");
}

if (tenantConfigObject.has("requiredSecondaryFactors") && tenantConfigObject.get("requiredSecondaryFactors").getAsJsonArray().size() == 0) {
tenantConfigObject.remove("requiredSecondaryFactors");
}
return tenantConfigObject;
}

public JsonObject toJson_v2(boolean shouldProtectDbConfig, Storage storage, String[] protectedCoreConfigs) {

Gson gson = new Gson();
JsonObject tenantConfigObject = gson.toJsonTree(this).getAsJsonObject();

tenantConfigObject.add("thirdParty", this.thirdPartyConfig.toJson());
tenantConfigObject.addProperty("tenantId", this.tenantIdentifier.getTenantId());

if (shouldProtectDbConfig) {
String[] protectedConfigs = storage.getProtectedConfigsFromSuperTokensSaaSUsers();
Expand All @@ -136,6 +230,42 @@ public JsonObject toJson(boolean shouldProtectDbConfig, Storage storage, String[
}
}

// as per https://github.com/supertokens/supertokens-core/issues/979#issuecomment-2099971371
tenantConfigObject.remove("emailPassword");
tenantConfigObject.remove("passwordless");
tenantConfigObject.get("thirdParty").getAsJsonObject().remove("enabled");

return tenantConfigObject;
}

public boolean isEmailPasswordEnabled() {
return this.emailPasswordConfig.enabled ||
this.firstFactors == null ||
(this.firstFactors != null && List.of(this.firstFactors).contains("emailpassword")) ||
(this.requiredSecondaryFactors != null && List.of(this.requiredSecondaryFactors).contains(
"emailpassword"));
}

public boolean isThirdPartyEnabled() {
return this.thirdPartyConfig.enabled ||
this.firstFactors == null ||
(this.firstFactors != null && List.of(this.firstFactors).contains("thirdparty")) ||
(this.requiredSecondaryFactors != null && List.of(this.requiredSecondaryFactors).contains(
"thirdparty"));
}

public boolean isPasswordlessEnabled() {
return this.passwordlessConfig.enabled ||
this.firstFactors == null ||
(this.firstFactors != null &&
(List.of(this.firstFactors).contains("otp-email") ||
List.of(this.firstFactors).contains("otp-phone") ||
List.of(this.firstFactors).contains("link-email") ||
List.of(this.firstFactors).contains("link-phone"))) ||
(this.requiredSecondaryFactors != null &&
(List.of(this.requiredSecondaryFactors).contains("otp-email") ||
List.of(this.requiredSecondaryFactors).contains("otp-phone") ||
List.of(this.requiredSecondaryFactors).contains("link-email") ||
List.of(this.requiredSecondaryFactors).contains("link-phone")));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,24 @@
public class ThirdPartyConfig {
public final boolean enabled;

@Nonnull
@Nullable
public final Provider[] providers;

public ThirdPartyConfig(boolean enabled, @Nullable Provider[] providers) {
this.enabled = enabled;
this.providers = providers == null ? new Provider[0] : providers;
this.providers = providers;
}

public JsonObject toJson() {
JsonObject result = new JsonObject();
result.addProperty("enabled", this.enabled);
result.add("providers", new JsonArray());

for (Provider provider : this.providers) {
result.getAsJsonArray("providers").add(provider.toJson());
if (this.providers != null) {
result.add("providers", new JsonArray());

for (Provider provider : this.providers) {
result.getAsJsonArray("providers").add(provider.toJson());
}
}

return result;
Expand Down

0 comments on commit ddab5f2

Please sign in to comment.