chore: Upgraded Spring Boot and Spring to intermediate ver (#18844)

* chore: Upgraded Spring Boot and Spring to intermediate ver

* Introducing encryption version

* Added exit path on start up

* Fixed a few failures with release data

* Modified from property to init migration

* Removed prop

* Update app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/InstanceConfig.java

Co-authored-by: Shrikant Sharat Kandula <shrikant@appsmith.com>

* Added comment on prop

* Minor stuff

* Test fixes

* Removed test dir

Co-authored-by: Shrikant Sharat Kandula <shrikant@appsmith.com>
This commit is contained in:
Nidhi 2022-12-22 15:59:20 +05:30 committed by GitHub
parent 85eeedf08f
commit 81b257673f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 333 additions and 54 deletions

View File

@ -5,6 +5,7 @@ import com.appsmith.git.configurations.GitServiceConfig;
import com.appsmith.git.service.GitExecutorImpl;
import org.apache.commons.io.FileUtils;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -33,6 +34,8 @@ public class FileUtilsImplTest {
@MockBean
private GitExecutorImpl gitExecutor;
private GitServiceConfig gitServiceConfig;
private static final String localTestDirectory = "localTestDirectory";
private static final Path localTestDirectoryPath = Path.of(localTestDirectory);
@BeforeEach
public void setUp() {
@ -41,8 +44,10 @@ public class FileUtilsImplTest {
fileUtils = new FileUtilsImpl(gitServiceConfig, gitExecutor);
}
private static final String localTestDirectory = "localTestDirectory";
private static final Path localTestDirectoryPath = Path.of(localTestDirectory);
@AfterEach
public void tearDown() {
this.deleteLocalTestDirectoryPath();
}
@Test
public void saveApplicationRef_removeActionAndActionCollectionDirectoryCreatedInV1FileFormat_success() throws GitAPIException, IOException {
@ -110,7 +115,6 @@ public class FileUtilsImplTest {
Assertions.fail("Error while scanning directory");
}
this.deleteLocalTestDirectoryPath();
}
@Test
@ -164,7 +168,6 @@ public class FileUtilsImplTest {
Assertions.fail("Error while scanning directory");
}
this.deleteLocalTestDirectoryPath();
}
/**

View File

@ -31,6 +31,7 @@ import java.util.Set;
public class CommonConfig {
private static final String ELASTIC_THREAD_POOL_NAME = "appsmith-elastic-pool";
public static final Integer LATEST_INSTANCE_SCHEMA_VERSION = 2;
@Value("${appsmith.instance.name:}")
private String instanceName;
@ -67,7 +68,6 @@ public class CommonConfig {
private List<String> allowedDomains;
@Bean
public Scheduler scheduler() {
return Schedulers.newElastic(ELASTIC_THREAD_POOL_NAME);

View File

@ -10,7 +10,10 @@ import com.appsmith.util.WebClientUtils;
import io.sentry.Sentry;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.stereotype.Component;
@ -18,6 +21,8 @@ import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.BodyInserters;
import reactor.core.publisher.Mono;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@ -34,20 +39,64 @@ public class InstanceConfig implements ApplicationListener<ApplicationReadyEvent
private boolean isRtsAccessible = false;
@Autowired
private ApplicationContext applicationContext;
@Override
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
configService.getByName(Appsmith.APPSMITH_REGISTERED)
Mono<Void> registrationAndRtsCheckMono = configService.getByName(Appsmith.APPSMITH_REGISTERED)
.filter(config -> Boolean.TRUE.equals(config.getConfig().get("value")))
.switchIfEmpty(registerInstance())
.doOnError(errorSignal -> log.debug("Instance registration failed with error: \n{}", errorSignal.getMessage()))
.then(performRtsHealthCheck())
.doFinally(ignored -> this.printReady())
.doFinally(ignored -> this.printReady());
Mono.when(checkInstanceSchemaVersion(), registrationAndRtsCheckMono)
.subscribe(null, e -> {
log.debug(e.getMessage());
log.debug("Application start up encountered an error: {}", e.getMessage());
Sentry.captureException(e);
});
}
private Mono<Void> checkInstanceSchemaVersion() {
return configService.getByName(Appsmith.INSTANCE_SCHEMA_VERSION)
.onErrorMap(AppsmithException.class, e -> new AppsmithException(AppsmithError.SCHEMA_VERSION_NOT_FOUND_ERROR))
.flatMap(config -> {
if (CommonConfig.LATEST_INSTANCE_SCHEMA_VERSION == config.getConfig().get("value")) {
return Mono.just(config);
}
return Mono.error(populateSchemaMismatchError((Integer) config.getConfig().get("value")));
})
.doOnError(errorSignal -> {
log.error("\n" +
"################################################\n" +
"Error while trying to start up Appsmith instance: \n" +
"{}\n" +
"################################################\n",
errorSignal.getMessage());
SpringApplication.exit(applicationContext, () -> 1);
System.exit(1);
})
.then();
}
private AppsmithException populateSchemaMismatchError(Integer currentInstanceSchemaVersion) {
List<String> versions = new LinkedList<>();
// Keep adding version numbers that brought in breaking instance schema migrations here
switch (currentInstanceSchemaVersion) {
// Example, we expect that in v1.8.14, all instances will have been migrated to instanceSchemaVer 2
case 1:
versions.add("v1.9");
default:
}
return new AppsmithException(AppsmithError.SCHEMA_MISMATCH_ERROR, versions);
}
private Mono<? extends Config> registerInstance() {
log.debug("Triggering registration of this instance...");

View File

@ -21,6 +21,7 @@ import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
import org.springframework.security.web.server.authentication.ServerAuthenticationEntryPointFailureHandler;
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
@ -45,7 +46,7 @@ import static com.appsmith.server.constants.Url.USER_URL;
import static java.time.temporal.ChronoUnit.DAYS;
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
@EnableReactiveMethodSecurity(useAuthorizationManager = true)
public class SecurityConfig {
@Autowired
@ -104,6 +105,8 @@ public class SecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
ServerAuthenticationEntryPointFailureHandler failureHandler = new ServerAuthenticationEntryPointFailureHandler(authenticationEntryPoint);
return http
// This picks up the configurationSource from the bean corsConfigurationSource()
.csrf().disable()
@ -139,25 +142,28 @@ public class SecurityConfig {
.pathMatchers("/public/**", "/oauth2/**").permitAll()
.anyExchange()
.authenticated()
.and().httpBasic()
.and().formLogin()
.loginPage(Url.LOGIN_URL)
.authenticationEntryPoint(authenticationEntryPoint)
.requiresAuthenticationMatcher(ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, Url.LOGIN_URL))
.authenticationSuccessHandler(authenticationSuccessHandler)
.authenticationFailureHandler(authenticationFailureHandler)
.and()
.httpBasic(httpBasicSpec -> httpBasicSpec.authenticationFailureHandler(failureHandler))
.formLogin(formLoginSpec -> formLoginSpec.authenticationFailureHandler(failureHandler)
.loginPage(Url.LOGIN_URL)
.authenticationEntryPoint(authenticationEntryPoint)
.requiresAuthenticationMatcher(ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, Url.LOGIN_URL))
.authenticationSuccessHandler(authenticationSuccessHandler)
.authenticationFailureHandler(authenticationFailureHandler))
// For Github SSO Login, check transformation class: CustomOAuth2UserServiceImpl
// For Google SSO Login, check transformation class: CustomOAuth2UserServiceImpl
.and().oauth2Login()
.authorizationRequestResolver(new CustomServerOAuth2AuthorizationRequestResolver(reactiveClientRegistrationRepository, commonConfig, redirectHelper))
.authenticationSuccessHandler(authenticationSuccessHandler)
.authenticationFailureHandler(authenticationFailureHandler)
.authorizedClientRepository(new ClientUserRepository(userService, commonConfig))
.and().logout()
.oauth2Login(oAuth2LoginSpec -> oAuth2LoginSpec.authenticationFailureHandler(failureHandler)
.authorizationRequestResolver(new CustomServerOAuth2AuthorizationRequestResolver(reactiveClientRegistrationRepository, commonConfig, redirectHelper))
.authenticationSuccessHandler(authenticationSuccessHandler)
.authenticationFailureHandler(authenticationFailureHandler)
.authorizedClientRepository(new ClientUserRepository(userService, commonConfig)))
.logout()
.logoutUrl(Url.LOGOUT_URL)
.logoutSuccessHandler(new LogoutSuccessHandler(objectMapper, analyticsService))
.and().build();
.and()
.build();
}
/**

View File

@ -2,6 +2,7 @@ package com.appsmith.server.constants;
public class Appsmith {
public final static String APPSMITH_REGISTERED = "appsmith_registered";
public final static String INSTANCE_SCHEMA_VERSION = "schemaVersion";
// We default the origin header to the production deployment of the client's URL
public static final String DEFAULT_ORIGIN_HEADER = "https://app.appsmith.com";
}

View File

@ -150,6 +150,8 @@ public enum AppsmithError {
ENV_FILE_NOT_FOUND(500, 5019, "Admin Settings is unavailable. Unable to read and write to Environment file.", AppsmithErrorAction.DEFAULT, null, ErrorType.CONFIGURATION_ERROR, null),
PUBLIC_APP_NO_PERMISSION_GROUP(500, 5020, "Invalid state. Public application does not have the required roles set for public access. Please reach out to Appsmith customer support to resolve this.", AppsmithErrorAction.LOG_EXTERNALLY, null, ErrorType.INTERNAL_ERROR, null),
RTS_SERVER_ERROR(500, 5021, "RTS server error while processing request: {0}", AppsmithErrorAction.LOG_EXTERNALLY, null, ErrorType.INTERNAL_ERROR, null),
SCHEMA_MISMATCH_ERROR(500, 5022, "Looks like you skipped some required update(s), please go back to the mandatory upgrade path {0}, or refer to ''https://docs.appsmith.com/'' for more info", AppsmithErrorAction.LOG_EXTERNALLY, null, ErrorType.INTERNAL_ERROR, null),
SCHEMA_VERSION_NOT_FOUND_ERROR(500, 5023, "Could not find mandatory instance schema version config. Please reach out to Appsmith customer support to resolve this.", AppsmithErrorAction.LOG_EXTERNALLY, null, ErrorType.INTERNAL_ERROR, null)
;
private final Integer httpErrorCode;

View File

@ -0,0 +1,52 @@
package com.appsmith.server.migrations;
import com.appsmith.server.configurations.CommonConfig;
import com.appsmith.server.constants.Appsmith;
import com.appsmith.server.domains.Config;
import com.appsmith.server.domains.QConfig;
import com.github.cloudyrock.mongock.ChangeLog;
import com.github.cloudyrock.mongock.ChangeSet;
import com.github.cloudyrock.mongock.driver.mongodb.springdata.v3.decorator.impl.MongockTemplate;
import lombok.extern.slf4j.Slf4j;
import net.minidev.json.JSONObject;
import java.util.Map;
import static com.appsmith.server.repositories.ce.BaseAppsmithRepositoryCEImpl.fieldName;
import static org.springframework.data.mongodb.core.query.Criteria.where;
import static org.springframework.data.mongodb.core.query.Query.query;
@Slf4j
@ChangeLog(order = "000")
public class DatabaseChangelog0 {
/**
* This migration initializes the correct version of instance schema migrations
* in the config collection for this Appsmith instance
* Future migrations that need to handle migrations for schema versions
* will check the current state and manage the migration accordingly
*/
@ChangeSet(order = "001", id = "initialize-schema-version", author = "")
public void initializeSchemaVersion(MongockTemplate mongockTemplate) {
Config instanceIdConfig = mongockTemplate.findOne(
query(where(fieldName(QConfig.config1.name)).is("instance-id")),
Config.class);
if (instanceIdConfig != null) {
// If instance id exists, this is an existing instance
// Instantiate with the first version so that we expect to go through all the migrations
mongockTemplate.insert(new Config(
new JSONObject(Map.of("value", 1)),
Appsmith.INSTANCE_SCHEMA_VERSION
));
} else {
// Is no instance id exists, this is a new instance
// Instantiate with latest schema version that this Appsmith release shipped with
mongockTemplate.insert(new Config(
new JSONObject(Map.of("value", CommonConfig.LATEST_INSTANCE_SCHEMA_VERSION)),
Appsmith.INSTANCE_SCHEMA_VERSION
));
}
}
}

View File

@ -139,7 +139,7 @@ import static org.springframework.data.mongodb.core.query.Update.update;
@Slf4j
@ChangeLog(order = "001")
public class DatabaseChangelog {
public class DatabaseChangelog1 {
public static ObjectMapper objectMapper = new ObjectMapper();
private static final String AGGREGATE_LIMIT = "aggregate.limit";
@ -2877,7 +2877,7 @@ public class DatabaseChangelog {
}
}
private Document getDocumentFromPath(Document document, String path) {
public static Document getDocumentFromPath(Document document, String path) {
String[] pathKeys = path.split("\\.");
Document documentPtr = document;

View File

@ -1,5 +1,6 @@
package com.appsmith.server.migrations;
import com.appsmith.external.helpers.Stopwatch;
import com.appsmith.external.models.ActionDTO;
import com.appsmith.external.models.BaseDomain;
import com.appsmith.external.models.Datasource;
@ -8,9 +9,12 @@ import com.appsmith.external.models.Policy;
import com.appsmith.external.models.Property;
import com.appsmith.external.models.QBaseDomain;
import com.appsmith.external.models.QDatasource;
import com.appsmith.external.services.EncryptionService;
import com.appsmith.server.acl.AclPermission;
import com.appsmith.server.acl.AppsmithRole;
import com.appsmith.server.acl.PolicyGenerator;
import com.appsmith.server.configurations.EncryptionConfig;
import com.appsmith.server.constants.Appsmith;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.domains.Action;
import com.appsmith.server.domains.ActionCollection;
@ -63,14 +67,23 @@ import com.github.cloudyrock.mongock.ChangeLog;
import com.github.cloudyrock.mongock.ChangeSet;
import com.github.cloudyrock.mongock.driver.mongodb.springdata.v3.decorator.impl.MongockTemplate;
import com.google.gson.Gson;
import com.mongodb.BasicDBObject;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.model.Filters;
import com.querydsl.core.types.Path;
import io.changock.migration.api.annotations.NonLockGuarded;
import lombok.extern.slf4j.Slf4j;
import net.minidev.json.JSONObject;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang.ArrayUtils;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.CollectionCallback;
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
import org.springframework.data.mongodb.core.aggregation.Fields;
import org.springframework.data.mongodb.core.index.Index;
@ -79,6 +92,8 @@ import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.redis.core.ReactiveRedisOperations;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StreamUtils;
@ -117,16 +132,17 @@ import static com.appsmith.server.constants.EnvVariables.APPSMITH_ADMIN_EMAILS;
import static com.appsmith.server.constants.FieldName.DEFAULT_PERMISSION_GROUP;
import static com.appsmith.server.constants.FieldName.PERMISSION_GROUP_ID;
import static com.appsmith.server.helpers.CollectionUtils.findSymmetricDiff;
import static com.appsmith.server.migrations.DatabaseChangelog.dropIndexIfExists;
import static com.appsmith.server.migrations.DatabaseChangelog.ensureIndexes;
import static com.appsmith.server.migrations.DatabaseChangelog.getUpdatedDynamicBindingPathList;
import static com.appsmith.server.migrations.DatabaseChangelog.installPluginToAllWorkspaces;
import static com.appsmith.server.migrations.DatabaseChangelog.makeIndex;
import static com.appsmith.server.migrations.DatabaseChangelog1.dropIndexIfExists;
import static com.appsmith.server.migrations.DatabaseChangelog1.ensureIndexes;
import static com.appsmith.server.migrations.DatabaseChangelog1.getUpdatedDynamicBindingPathList;
import static com.appsmith.server.migrations.DatabaseChangelog1.installPluginToAllWorkspaces;
import static com.appsmith.server.migrations.DatabaseChangelog1.makeIndex;
import static com.appsmith.server.migrations.MigrationHelperMethods.evictPermissionCacheForUsers;
import static com.appsmith.server.repositories.BaseAppsmithRepositoryImpl.fieldName;
import static java.lang.Boolean.TRUE;
import static org.springframework.data.mongodb.core.query.Criteria.where;
import static org.springframework.data.mongodb.core.query.Query.query;
import static org.springframework.data.mongodb.core.query.Update.update;
@Slf4j
@ -141,17 +157,17 @@ public class DatabaseChangelog2 {
public void fixPluginTitleCasing(MongockTemplate mongockTemplate) {
mongockTemplate.updateFirst(
query(where(fieldName(QPlugin.plugin.packageName)).is("mysql-plugin")),
Update.update(fieldName(QPlugin.plugin.name), "MySQL"),
update(fieldName(QPlugin.plugin.name), "MySQL"),
Plugin.class);
mongockTemplate.updateFirst(
query(where(fieldName(QPlugin.plugin.packageName)).is("mssql-plugin")),
Update.update(fieldName(QPlugin.plugin.name), "Microsoft SQL Server"),
update(fieldName(QPlugin.plugin.name), "Microsoft SQL Server"),
Plugin.class);
mongockTemplate.updateFirst(
query(where(fieldName(QPlugin.plugin.packageName)).is("elasticsearch-plugin")),
Update.update(fieldName(QPlugin.plugin.name), "Elasticsearch"),
update(fieldName(QPlugin.plugin.name), "Elasticsearch"),
Plugin.class);
}
@ -756,7 +772,7 @@ public class DatabaseChangelog2 {
public void setDefaultApplicationVersion(MongockTemplate mongockTemplate) {
mongockTemplate.updateMulti(
Query.query(where(fieldName(QApplication.application.deleted)).is(false)),
Update.update(fieldName(QApplication.application.applicationVersion),
update(fieldName(QApplication.application.applicationVersion),
ApplicationVersion.EARLIEST_VERSION),
Application.class);
}
@ -908,7 +924,7 @@ public class DatabaseChangelog2 {
if (!newName.equals(oldName)) {
//Using strings in the field names instead of QSequence becauce Sequence is not a AppsmithDomain
mongockTemplate.updateFirst(query(where("name").is(oldName)),
Update.update("name", newName),
update("name", newName),
Sequence.class
);
}
@ -1422,7 +1438,7 @@ public class DatabaseChangelog2 {
@ChangeSet(order = "021", id = "flush-spring-redis-keys-2a", author = "")
public void clearRedisCache2(ReactiveRedisOperations<String, String> reactiveRedisOperations) {
DatabaseChangelog.doClearRedisKeys(reactiveRedisOperations);
DatabaseChangelog1.doClearRedisKeys(reactiveRedisOperations);
}
private List<String> getCustomizedThemeIds(String fieldName, Function<Application, String> getThemeIdMethod, List<String> systemThemeIds, MongockTemplate mongockTemplate) {
@ -2779,12 +2795,158 @@ public class DatabaseChangelog2 {
dropIndexIfExists(mongockTemplate, Workspace.class, "tenantId_deleted");
ensureIndexes(mongockTemplate, Workspace.class, makeIndex("tenantId", "deleted").named("tenantId_deleted"));
}
@ChangeSet(order = "038", id = "add-unique-index-for-uidstring", author = "")
public void addUniqueIndexOnUidString(MongockTemplate mongoTemplate) {
Index uidStringUniqueness = makeIndex("uidString").unique()
.named("customjslibs_uidstring_index");
ensureIndexes(mongoTemplate, CustomJSLib.class, uidStringUniqueness);
}
// TODO We'll be deleting this migration after upgrade to Spring 6.0
@ChangeSet(order = "039", id = "deprecate-queryabletext-encryption", author = "")
public void deprecateQueryableTextEncryption(MongockTemplate mongockTemplate,
@NonLockGuarded EncryptionConfig encryptionConfig,
EncryptionService encryptionService) {
Stopwatch stopwatch = new Stopwatch("Instance Schema migration to v2");
Config encryptionVersion = mongockTemplate.findOne(
query(where(fieldName(QConfig.config1.name)).is(Appsmith.INSTANCE_SCHEMA_VERSION)),
Config.class);
if (encryptionVersion != null && (Integer) encryptionVersion.getConfig().get("value") < 2) {
String saltInHex = Hex.encodeHexString(encryptionConfig.getSalt().getBytes());
TextEncryptor textEncryptor = Encryptors.queryableText(encryptionConfig.getPassword(), saltInHex);
/**
* - List of attributes in datasources that need to be encoded.
* - Each path represents where the attribute exists in mongo db document.
*/
List<String> datasourcePathList = new ArrayList<>();
datasourcePathList.add("datasourceConfiguration.connection.ssl.keyFile.base64Content");
datasourcePathList.add("datasourceConfiguration.connection.ssl.certificateFile.base64Content");
datasourcePathList.add("datasourceConfiguration.connection.ssl.caCertificateFile.base64Content");
datasourcePathList.add("datasourceConfiguration.connection.ssl.pemCertificate.file.base64Content");
datasourcePathList.add("datasourceConfiguration.connection.ssl.pemCertificate.password");
datasourcePathList.add("datasourceConfiguration.sshProxy.privateKey.keyFile.base64Content");
datasourcePathList.add("datasourceConfiguration.sshProxy.privateKey.password");
datasourcePathList.add("datasourceConfiguration.authentication.value");
datasourcePathList.add("datasourceConfiguration.authentication.password");
datasourcePathList.add("datasourceConfiguration.authentication.bearerToken");
datasourcePathList.add("datasourceConfiguration.authentication.clientSecret");
datasourcePathList.add("datasourceConfiguration.authentication.authenticationResponse.token");
datasourcePathList.add("datasourceConfiguration.authentication.authenticationResponse.refreshToken");
datasourcePathList.add("datasourceConfiguration.authentication.authenticationResponse.tokenResponse");
List<Bson> datasourcePathListExists = datasourcePathList
.stream()
.map(Filters::exists)
.collect(Collectors.toList());
List<Bson> gitDeployKeysPathListExists = new ArrayList<>();
ArrayList<String> gitDeployKeysPathList = new ArrayList<>();
gitDeployKeysPathList.add("gitAuth.privateKey");
gitDeployKeysPathListExists.add(Filters.exists("gitAuth.privateKey"));
List<Bson> applicationPathListExists = new ArrayList<>();
ArrayList<String> applicationPathList = new ArrayList<>();
applicationPathList.add("gitApplicationMetadata.gitAuth.privateKey");
applicationPathListExists.add(Filters.exists("gitApplicationMetadata.gitAuth.privateKey"));
mongockTemplate.execute("datasource", getNewEncryptionCallback(textEncryptor, encryptionService, datasourcePathListExists, datasourcePathList, stopwatch));
mongockTemplate.execute("gitDeployKeys", getNewEncryptionCallback(textEncryptor, encryptionService, gitDeployKeysPathListExists, gitDeployKeysPathList, stopwatch));
mongockTemplate.execute("application", getNewEncryptionCallback(textEncryptor, encryptionService, applicationPathListExists, applicationPathList, stopwatch));
mongockTemplate.upsert(
query(where(fieldName(QConfig.config1.name)).is(Appsmith.INSTANCE_SCHEMA_VERSION)),
update("config.value", 2),
Config.class);
}
stopwatch.stopAndLogTimeInMillis();
}
private CollectionCallback<String> getNewEncryptionCallback(
TextEncryptor textEncryptor,
EncryptionService encryptionService,
Iterable<Bson> collectionFilterIterable,
List<String> pathList,
Stopwatch stopwatch) {
return new CollectionCallback<String>() {
@Override
public String doInCollection(MongoCollection<Document> collection) {
MongoCursor<Document> cursor = collection
.find(
Filters.and(
Filters.or(collectionFilterIterable),
Filters.not(Filters.exists("encryptionVersion"))))
.cursor();
log.debug("collection callback start: {}ms", stopwatch.getExecutionTime());
List<List<Bson>> documentPairList = new ArrayList<>();
while (cursor.hasNext()) {
Document old = cursor.next();
BasicDBObject query = new BasicDBObject();
query.put("_id", old.getObjectId("_id"));
// This document will have the encrypted values.
BasicDBObject updated = new BasicDBObject();
updated.put("$set", new BasicDBObject("encryptionVersion", 2));
updated.put("$unset", new BasicDBObject());
// Encrypt attributes
pathList.stream()
.forEach(path -> reapplyNewEncryptionToPathValueIfExists(old, updated, path, encryptionService, textEncryptor));
documentPairList.add(List.of(query, updated));
}
log.debug("collection callback processing end: {}ms", stopwatch.getExecutionTime());
log.debug("update will be run for {} documents", documentPairList.size());
/**
* - Replace old document with the updated document that has encrypted values.
* - Replacing here instead of the while loop above makes sure that we attempt replacement only if
* the encryption step succeeded without error for each selected document.
*/
documentPairList.stream().parallel()
.forEach(docPair -> collection.updateOne(docPair.get(0), docPair.get(1)));
log.debug("collection callback update end: {}ms", stopwatch.getExecutionTime());
return null;
}
};
}
private void reapplyNewEncryptionToPathValueIfExists(Document document, BasicDBObject update, String path,
EncryptionService encryptionService,
TextEncryptor textEncryptor) {
String[] pathKeys = path.split("\\.");
/**
* - For attribute path "datasourceConfiguration.connection.ssl.keyFile.base64Content", first get the parent
* document that contains the attribute 'base64Content' i.e. fetch the document corresponding to path
* "datasourceConfiguration.connection.ssl.keyFile"
*/
String parentDocumentPath = org.apache.commons.lang.StringUtils.join(ArrayUtils.subarray(pathKeys, 0, pathKeys.length - 1), ".");
Document parentDocument = DatabaseChangelog1.getDocumentFromPath(document, parentDocumentPath);
if (parentDocument != null) {
if (parentDocument.containsKey(pathKeys[pathKeys.length - 1])) {
String oldEncryptedValue = parentDocument.getString(pathKeys[pathKeys.length - 1]);
if (StringUtils.hasLength(String.valueOf(oldEncryptedValue))) {
String decryptedValue = null;
try {
decryptedValue = textEncryptor.decrypt(String.valueOf(oldEncryptedValue));
} catch (IllegalArgumentException e) {
// This happens on release DB for some creds that are malformed
if ("Hex-encoded string must have an even number of characters".equals(e.getMessage())) {
decryptedValue = String.valueOf(oldEncryptedValue);
}
}
String newEncryptedValue = encryptionService.encryptString(decryptedValue);
((BasicDBObject) update.get("$set")).put(path, newEncryptedValue);
if (path.startsWith("datasourceConfiguration.authentication")) {
((BasicDBObject) update.get("$unset")).put("datasourceConfiguration.authentication.isEncrypted", 1);
}
}
}
}
}
}

View File

@ -23,8 +23,8 @@ import com.appsmith.server.domains.User;
import com.appsmith.server.domains.Workspace;
import com.appsmith.server.dtos.ActionCollectionDTO;
import com.appsmith.server.dtos.ApplicationPagesDTO;
import com.appsmith.server.dtos.PageDTO;
import com.appsmith.server.dtos.CustomJSLibApplicationDTO;
import com.appsmith.server.dtos.PageDTO;
import com.appsmith.server.dtos.PageNameIdDTO;
import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException;
@ -63,8 +63,8 @@ import java.nio.file.Paths;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
@ -72,7 +72,6 @@ import java.util.stream.Collectors;
import static com.appsmith.external.helpers.AppsmithBeanUtils.copyNestedNonNullProperties;
import static org.apache.commons.lang.ObjectUtils.defaultIfNull;
import static com.appsmith.server.acl.AclPermission.MANAGE_APPLICATIONS;
@Slf4j
@ -424,7 +423,7 @@ public class ApplicationPageServiceCEImpl implements ApplicationPageServiceCE {
GitApplicationMetadata gitData = application.getGitApplicationMetadata();
if (gitData != null && !StringUtils.isEmpty(gitData.getDefaultApplicationId()) && !StringUtils.isEmpty(gitData.getRepoName())) {
String repoName = gitData.getRepoName();
Path repoPath = Paths.get(application.getOrganizationId(), gitData.getDefaultApplicationId(), repoName);
Path repoPath = Paths.get(application.getWorkspaceId(), gitData.getDefaultApplicationId(), repoName);
// Delete git repo from local
return gitFileUtils.deleteLocalRepo(repoPath)
.then(Mono.just(application));
@ -1076,14 +1075,15 @@ public class ApplicationPageServiceCEImpl implements ApplicationPageServiceCE {
Mono<List<NewAction>> publishedActionsFlux,
Mono<List<ActionCollection>> publishedActionsCollectionFlux,
Mono<Set<CustomJSLibApplicationDTO>> publishedJSLibDTOsMono,
String applicationId, boolean isPublishedManually) { return Mono.zip(
String applicationId, boolean isPublishedManually) {
return Mono.zip(
publishApplicationAndPages,
publishedActionsFlux,
publishedActionsCollectionFlux,
// not using existing applicationMono because we need the latest Application after published
applicationService.findById(applicationId, applicationPermission.getEditPermission()),
publishedJSLibDTOsMono
)
)
.flatMap(objects -> {
Application application = objects.getT4();
Map<String, Object> extraProperties = new HashMap<>();

View File

@ -18,8 +18,7 @@ public class EncryptionServiceCEImpl implements EncryptionServiceCE {
public EncryptionServiceCEImpl(EncryptionConfig encryptionConfig) {
this.encryptionConfig = encryptionConfig;
String saltInHex = Hex.encodeHexString(encryptionConfig.getSalt().getBytes());
this.textEncryptor = Encryptors.queryableText(encryptionConfig.getPassword(),
saltInHex);
this.textEncryptor = Encryptors.delux(encryptionConfig.getPassword(), saltInHex);
}
@Override

View File

@ -237,7 +237,8 @@ public class UserServiceCEImpl extends BaseService<UserRepository, User, String>
resetToken.setTokenHash(passwordEncoder.encode(token));
return resetToken;
});
}).flatMap(passwordResetTokenRepository::save)
})
.flatMap(passwordResetTokenRepository::save)
.flatMap(passwordResetToken -> {
log.debug("Password reset Token: {} for email: {}", token, passwordResetToken.getEmail());
@ -302,7 +303,7 @@ public class UserServiceCEImpl extends BaseService<UserRepository, User, String>
EmailTokenDTO emailTokenDTO;
try {
emailTokenDTO = parseValueFromEncryptedToken(encryptedToken);
} catch (IllegalStateException e) {
} catch (ArrayIndexOutOfBoundsException | IllegalStateException e) {
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.TOKEN));
}
@ -325,7 +326,7 @@ public class UserServiceCEImpl extends BaseService<UserRepository, User, String>
EmailTokenDTO emailTokenDTO;
try {
emailTokenDTO = parseValueFromEncryptedToken(encryptedToken);
} catch (IllegalStateException e) {
} catch (ArrayIndexOutOfBoundsException | IllegalStateException e) {
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.TOKEN));
}
@ -599,10 +600,9 @@ public class UserServiceCEImpl extends BaseService<UserRepository, User, String>
}
@Override
public Mono<? extends User> createNewUserAndSendInviteEmail(String email, String originHeader,
Workspace workspace, User inviter, String role) {
Workspace workspace, User inviter, String role) {
User newUser = new User();
newUser.setEmail(email.toLowerCase());

View File

@ -1,4 +1,8 @@
server.port=${PORT:8080}
# Allow the Spring context to close all active requests before shutting down the server
# Please ref: https://docs.spring.io/spring-boot/docs/2.3.0.RELEASE/reference/html/spring-boot-features.html#boot-features-graceful-shutdown
server.shutdown=graceful
spring.lifecycle.timeout-per-shutdown-phase=20s
# This property allows us to override beans during testing. This is useful when we want to set different configurations
# and different parameters during test as compared to production. If this property is disabled, some tests will fail.

View File

@ -166,7 +166,7 @@ public class DatasourceContextServiceTest {
DBAuth authentication = (DBAuth) savedDatasource.getDatasourceConfiguration().getAuthentication();
assertEquals(password, authentication.getPassword());
DBAuth encryptedAuthentication = (DBAuth) createdDatasource.getDatasourceConfiguration().getAuthentication();
assertEquals(encryptionService.encryptString(password), encryptedAuthentication.getPassword());
assertEquals(password, encryptionService.decryptString(encryptedAuthentication.getPassword()));
})
.verifyComplete();
}

View File

@ -862,7 +862,7 @@ public class DatasourceServiceTest {
.assertNext(savedDatasource -> {
DBAuth authentication = (DBAuth) savedDatasource.getDatasourceConfiguration().getAuthentication();
assertThat(authentication.getUsername()).isEqualTo(username);
assertThat(authentication.getPassword()).isEqualTo(encryptionService.encryptString(password));
assertThat(encryptionService.decryptString(authentication.getPassword())).isEqualTo(password);
})
.verifyComplete();
}
@ -961,7 +961,7 @@ public class DatasourceServiceTest {
DBAuth authentication = (DBAuth) updatedDatasource.getDatasourceConfiguration().getAuthentication();
assertThat(authentication.getUsername()).isEqualTo(username);
assertThat(encryptionService.encryptString(password)).isEqualTo(authentication.getPassword());
assertThat(password).isEqualTo(encryptionService.decryptString(authentication.getPassword()));
})
.verifyComplete();
}

View File

@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.5</version>
<version>2.7.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
@ -24,6 +24,7 @@
<project.version>1.0-SNAPSHOT</project.version>
<!-- By default skip the dockerization step. Only activate if necessary -->
<skipDockerBuild>true</skipDockerBuild>
<spring-security.version>5.8.0</spring-security.version>
<log4j2.version>2.17.1</log4j2.version>
<h2.version>2.1.210</h2.version>
<testcontainers.version>1.17.3</testcontainers.version>