Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 5 additions & 1 deletion app/client/src/ce/utils/adminSettingsHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,11 @@ export const getWrapperCategory = (
return categories[subCategory || category];
};

export const getFilteredGeneralCategories = (categories: Category[]) => {
export const getFilteredGeneralCategories = (
categories: Category[],
// eslint-disable-next-line @typescript-eslint/no-unused-vars
featureFlags?: Record<string, boolean>,
) => {
return categories
?.map((category: Category) => {
return category;
Expand Down
7 changes: 6 additions & 1 deletion app/client/src/pages/AdminSettings/LeftPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -202,12 +202,17 @@ export default function LeftPane() {
const isSuperUser = user?.isSuperUser;
const organizationPermissions = useSelector(getOrganizationPermissions);
const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
const isMultiOrgEnabled = useFeatureFlag(
FEATURE_FLAG.license_multi_org_enabled,
);
const isAuditLogsEnabled = getHasAuditLogsReadPermission(
isFeatureEnabled,
organizationPermissions,
);

const filteredGeneralCategories = getFilteredGeneralCategories(categories);
const filteredGeneralCategories = getFilteredGeneralCategories(categories, {
license_multi_org_enabled: isMultiOrgEnabled,
});

const filteredAclCategories = getFilteredAclCategories(
aclCategories,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.appsmith.server.helpers;

import com.appsmith.server.helpers.ce.BlacklistedEnvVariableHelperCE;

public interface BlacklistedEnvVariableHelper extends BlacklistedEnvVariableHelperCE {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.appsmith.server.helpers;

import com.appsmith.server.helpers.ce.BlacklistedEnvVariableHelperCEImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class BlacklistedEnvVariableHelperImpl extends BlacklistedEnvVariableHelperCEImpl
implements BlacklistedEnvVariableHelper {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.appsmith.server.helpers.ce;

import java.util.Set;

public interface BlacklistedEnvVariableHelperCE {
Set<String> getBlacklistedEnvVariableForAppsmithCloud(String organizationId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.appsmith.server.helpers.ce;

import java.util.Set;

public class BlacklistedEnvVariableHelperCEImpl implements BlacklistedEnvVariableHelperCE {
@Override
public Set<String> getBlacklistedEnvVariableForAppsmithCloud(String organizationId) {
return Set.of();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.appsmith.server.configurations.CommonConfig;
import com.appsmith.server.configurations.EmailConfig;
import com.appsmith.server.configurations.GoogleRecaptchaConfig;
import com.appsmith.server.helpers.BlacklistedEnvVariableHelper;
import com.appsmith.server.helpers.FileUtils;
import com.appsmith.server.helpers.UserUtils;
import com.appsmith.server.notifications.EmailSender;
Expand Down Expand Up @@ -40,7 +41,8 @@ public EnvManagerImpl(
UserUtils userUtils,
OrganizationService organizationService,
ObjectMapper objectMapper,
EmailService emailService) {
EmailService emailService,
BlacklistedEnvVariableHelper blacklistedEnvVariableHelper) {

super(
sessionUserService,
Expand All @@ -58,6 +60,7 @@ public EnvManagerImpl(
userUtils,
organizationService,
objectMapper,
emailService);
emailService,
blacklistedEnvVariableHelper);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,10 @@

public interface EnvManagerCE {

List<String> transformEnvContent(String envContent, Map<String, String> changes);
Mono<List<String>> transformEnvContent(String envContent, Map<String, String> changes);

Mono<Void> applyChanges(Map<String, String> changes, String originHeader);

Mono<Map<String, String>> applyChangesToEnvFileWithoutAclCheck(Map<String, String> changes);

Mono<Void> applyChangesFromMultipartFormData(MultiValueMap<String, Part> formData, String originHeader);

void setAnalyticsEventAction(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
import com.appsmith.server.dtos.TestEmailConfigRequestDTO;
import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.server.helpers.BlacklistedEnvVariableHelper;
import com.appsmith.server.helpers.CollectionUtils;
import com.appsmith.server.helpers.FeatureFlagMigrationHelper;
import com.appsmith.server.helpers.FileUtils;
import com.appsmith.server.helpers.TextUtils;
import com.appsmith.server.helpers.UserUtils;
Expand Down Expand Up @@ -46,6 +46,8 @@
import org.springframework.util.StringUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import reactor.util.context.Context;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;

Expand Down Expand Up @@ -83,6 +85,7 @@
import static com.appsmith.server.constants.EnvVariables.APPSMITH_RECAPTCHA_SITE_KEY;
import static com.appsmith.server.constants.EnvVariables.APPSMITH_REPLY_TO;
import static com.appsmith.server.constants.EnvVariables.APPSMITH_SIGNUP_ALLOWED_DOMAINS;
import static com.appsmith.server.constants.ce.FieldNameCE.ORGANIZATION_ID;
import static java.lang.Boolean.TRUE;

@Slf4j
Expand Down Expand Up @@ -113,6 +116,8 @@ public class EnvManagerCEImpl implements EnvManagerCE {

private final EmailService emailService;

private final BlacklistedEnvVariableHelper blacklistedEnvVariableHelper;

/**
* This regex pattern matches environment variable declarations like `VAR_NAME=value` or `VAR_NAME="value"` or just
* `VAR_NAME=`. It also defines two named capture groups, `name` and `value`, for the variable's name and value
Expand All @@ -139,7 +144,8 @@ public EnvManagerCEImpl(
UserUtils userUtils,
OrganizationService organizationService,
ObjectMapper objectMapper,
EmailService emailService) {
EmailService emailService,
BlacklistedEnvVariableHelper blacklistedEnvVariableHelper) {

this.sessionUserService = sessionUserService;
this.userService = userService;
Expand All @@ -157,6 +163,7 @@ public EnvManagerCEImpl(
this.organizationService = organizationService;
this.objectMapper = objectMapper;
this.emailService = emailService;
this.blacklistedEnvVariableHelper = blacklistedEnvVariableHelper;
}

/**
Expand All @@ -169,7 +176,7 @@ public EnvManagerCEImpl(
* @return List of string lines for updated env file content.
*/
@Override
public List<String> transformEnvContent(String envContent, Map<String, String> changes) {
public Mono<List<String>> transformEnvContent(String envContent, Map<String, String> changes) {
final Set<String> variablesNotInWhitelist = new HashSet<>(changes.keySet());
final Set<String> organizationConfigWhitelist = allowedOrganizationConfiguration();

Expand All @@ -178,44 +185,57 @@ public List<String> transformEnvContent(String envContent, Map<String, String> c
// class. This is because the configuration can be saved either in the .env file or the organization collection
variablesNotInWhitelist.removeAll(VARIABLE_WHITELIST);
variablesNotInWhitelist.removeAll(organizationConfigWhitelist);

if (!variablesNotInWhitelist.isEmpty()) {
throw new AppsmithException(AppsmithError.GENERIC_BAD_REQUEST);
return Mono.error(new AppsmithException(AppsmithError.GENERIC_BAD_REQUEST));
}

if (changes.containsKey(APPSMITH_MAIL_HOST.name())) {
changes.put(
APPSMITH_MAIL_ENABLED.name(),
Boolean.toString(StringUtils.hasText(changes.get(APPSMITH_MAIL_HOST.name()))));
}
return organizationService.getCurrentUserOrganizationId().map(organizationId -> {
// Create a copy of the changes map to avoid modifying the original map and avoiding unsupported ops
// exception if the map is unmodifiable
Map<String, String> updatedChanges = new HashMap<>(changes);
// Add a check to remove blacklisted envs for updates
Set<String> blacklistedEnvVariable =
blacklistedEnvVariableHelper.getBlacklistedEnvVariableForAppsmithCloud(organizationId);
for (String key : updatedChanges.keySet()) {
if (blacklistedEnvVariable.contains(key)) {
updatedChanges.remove(key);
}
}

if (changes.containsKey(APPSMITH_MAIL_USERNAME.name())) {
changes.put(
APPSMITH_MAIL_SMTP_AUTH.name(),
Boolean.toString(StringUtils.hasText(changes.get(APPSMITH_MAIL_USERNAME.name()))));
}
if (updatedChanges.containsKey(APPSMITH_MAIL_HOST.name())) {
updatedChanges.put(
APPSMITH_MAIL_ENABLED.name(),
Boolean.toString(StringUtils.hasText(updatedChanges.get(APPSMITH_MAIL_HOST.name()))));
}

final Set<String> remainingChangedNames = new HashSet<>(changes.keySet());
if (updatedChanges.containsKey(APPSMITH_MAIL_USERNAME.name())) {
updatedChanges.put(
APPSMITH_MAIL_SMTP_AUTH.name(),
Boolean.toString(StringUtils.hasText(updatedChanges.get(APPSMITH_MAIL_USERNAME.name()))));
}

final List<String> outLines = envContent
.lines()
.map(line -> {
final Matcher matcher = ENV_VARIABLE_PATTERN.matcher(line);
if (!matcher.matches()) {
return line;
}
final String name = matcher.group("name");
return remainingChangedNames.remove(name)
? String.format("%s=%s", name, escapeForShell(changes.get(name)))
: line;
})
.collect(Collectors.toList());
final Set<String> remainingChangedNames = new HashSet<>(updatedChanges.keySet());

for (final String name : remainingChangedNames) {
outLines.add(name + "=" + escapeForShell(changes.get(name)));
}
final List<String> outLines = envContent
.lines()
.map(line -> {
final Matcher matcher = ENV_VARIABLE_PATTERN.matcher(line);
if (!matcher.matches()) {
return line;
}
final String name = matcher.group("name");
return remainingChangedNames.remove(name)
? String.format("%s=%s", name, escapeForShell(updatedChanges.get(name)))
: line;
})
.collect(Collectors.toList());

for (final String name : remainingChangedNames) {
outLines.add(name + "=" + escapeForShell(updatedChanges.get(name)));
}

return outLines;
return outLines;
});
}

private String escapeForShell(String input) {
Expand Down Expand Up @@ -350,25 +370,31 @@ private Mono<Organization> updateOrganizationConfiguration(String organizationId
organizationService.updateOrganizationConfiguration(organizationId, organizationConfiguration));
}

// This flow is pertinent for any variables that need to change in the .env file or be saved in the organization
// configuration
@Override
public Mono<Void> applyChanges(Map<String, String> changes, String originHeader) {
// This flow is pertinent for any variables that need to change in the .env file or be saved in the organization
// configuration
// Create a copy of the changes map to avoid modifying the original map and avoiding unsupported ops exception
// if the map is unmodifiable
Map<String, String> envChanges = new HashMap<>(changes);
return verifyCurrentUserIsSuper()
.flatMap(user -> validateChanges(user, changes).thenReturn(user))
.flatMap(user -> applyChangesToEnvFileWithoutAclCheck(changes)
.flatMap(user -> validateChanges(user, envChanges).thenReturn(user))
.flatMap(user -> applyChangesToEnvFileWithoutAclCheck(envChanges)
// Add the organization id to the context to be able to extract the feature flags
.contextWrite(Context.of(ORGANIZATION_ID, user.getOrganizationId()))
// For configuration variables, save the variables to the config collection instead of .env file
// We ideally want to migrate all variables from .env file to the config collection for better
// scalability
// Write the changes to the organization collection in configuration field
.flatMap(originalVariables -> updateOrganizationConfiguration(user.getOrganizationId(), changes)
.then(sendAnalyticsEvent(user, originalVariables, changes))
.flatMap(originalVariables -> updateOrganizationConfiguration(
user.getOrganizationId(), envChanges)
.then(sendAnalyticsEvent(user, originalVariables, envChanges))
.thenReturn(originalVariables)))
.flatMap(originalValues -> {
Mono<Void> dependentTasks = Mono.empty();

// Try and update any at runtime, that can be.
final Map<String, String> changesCopy = new HashMap<>(changes);
final Map<String, String> changesCopy = new HashMap<>(envChanges);

if (changesCopy.containsKey(APPSMITH_SIGNUP_ALLOWED_DOMAINS.name())) {
commonConfig.setAllowedDomainsString(
Expand Down Expand Up @@ -434,14 +460,11 @@ public Mono<Void> applyChanges(Map<String, String> changes, String originHeader)
/**
* This method applies the changes to the env file and should be called internally within the server as the ACL
* checks are skipped. For client side calls please use {@link EnvManagerCEImpl#applyChanges(Map, String)}.
* Please refer {@link FeatureFlagMigrationHelper} for the use case where ACL checks
* should be skipped.
*
* @param changes Map of changes to be applied to the env file
* @return Map of original variables before the changes were applied
*/
@Override
public Mono<Map<String, String>> applyChangesToEnvFileWithoutAclCheck(Map<String, String> changes) {
private Mono<Map<String, String>> applyChangesToEnvFileWithoutAclCheck(Map<String, String> changes) {
final Path envFilePath = Path.of(commonConfig.getEnvFilePath());
String originalContent;
try {
Expand All @@ -459,15 +482,17 @@ public Mono<Map<String, String>> applyChangesToEnvFileWithoutAclCheck(Map<String
envFileChanges.remove(key);
}
}
final List<String> changedContent = transformEnvContent(originalContent, envFileChanges);

try {
Files.write(envFilePath, changedContent);
} catch (IOException e) {
log.error("Unable to write to env file " + envFilePath, e);
return Mono.error(e);
}
return Mono.just(originalVariables);
return transformEnvContent(originalContent, envFileChanges)
.publishOn(Schedulers.boundedElastic())
.map(changedContent -> {
try {
Files.write(envFilePath, changedContent);
} catch (IOException e) {
log.error("Unable to write to env file " + envFilePath, e);
throw new AppsmithException(AppsmithError.IO_ERROR, "Unable to write to env file");
}
return originalVariables;
});
}

@Override
Expand Down Expand Up @@ -700,15 +725,20 @@ public Mono<Map<String, String>> getAllWithoutAclCheck() {
*/
@Override
public Mono<Map<String, String>> getAllNonEmpty() {
return getAll().flatMap(map -> {
Map<String, String> nonEmptyValuesMap = new HashMap<>();
for (Map.Entry<String, String> entry : map.entrySet()) {
if (StringUtils.hasText(entry.getValue())) {
nonEmptyValuesMap.put(entry.getKey(), entry.getValue());
}
}
return Mono.just(nonEmptyValuesMap);
});
return getAll().zipWith(organizationService.getCurrentUserOrganizationId())
.flatMap(tuple2 -> {
Map<String, String> map = tuple2.getT1();
String organizationId = tuple2.getT2();
Map<String, String> nonEmptyValuesMap = new HashMap<>();
Set<String> blacklistedEnvVariable =
blacklistedEnvVariableHelper.getBlacklistedEnvVariableForAppsmithCloud(organizationId);
for (Map.Entry<String, String> entry : map.entrySet()) {
if (StringUtils.hasText(entry.getValue()) && !blacklistedEnvVariable.contains(entry.getKey())) {
nonEmptyValuesMap.put(entry.getKey(), entry.getValue());
}
}
return Mono.just(nonEmptyValuesMap);
});
}

@Override
Expand Down
Loading
Loading