chore: Make core encrypt/decrypt methods statically available (#34117)

Two goals.

1. Move encryption/decryption implementation to `interfaces` module.
Today, only the interfaces are in `interfaces` module, not the
implementations. We need the implementation also to be in `interfaces`.
We need this for solving encryption/decryption in the `pg` branch.

2. Make the encryption and decryption functions be static, and not
depend on Spring's DI system. We need this to be available _before_
Spring's DI kicks in, during runtime annotation processing by Hibernate,
so we need it to be just static functions, and not depend on Spring
havin initialized.

This also means that we have to read the env variables directly. The
`application.properties` is loaded by Spring, and since we want this
information before Spring intializes, we'll need to read the env
variables directly.

⚠️ There's conflicts and additional changes needed on EE for build to
pass. **DO NOT MERGE** unless the author is known to be available. EE PR
also passed all at https://github.com/appsmithorg/appsmith-ee/pull/4282.

**/test all**

<!-- This is an auto-generated comment: Cypress test results  -->
> [!TIP]
> 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
> Workflow run:
<https://github.com/appsmithorg/appsmith/actions/runs/9467180088>
> Commit: 402785da8df5aa6b4eeec9955421ce1ff7af305f
> Cypress dashboard url: <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=9467180088&attempt=1"
target="_blank">Click here!</a>

<!-- end of auto-generated comment: Cypress test results  -->
This commit is contained in:
Shrikant Sharat Kandula 2024-06-12 12:38:25 +05:30 committed by GitHub
parent 3749688eb5
commit be76aeed60
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 63 additions and 122 deletions

View File

@ -86,6 +86,11 @@
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>

View File

@ -1,6 +1,6 @@
package com.appsmith.external.annotations.encryption;
import com.appsmith.external.services.EncryptionService;
import com.appsmith.external.helpers.EncryptionHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener;
import org.springframework.data.mongodb.core.mapping.event.AfterConvertEvent;
@ -9,13 +9,7 @@ import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent;
@Slf4j
public class EncryptionMongoEventListener<E> extends AbstractMongoEventListener<E> {
private final EncryptionService encryptionService;
EncryptionHandler encryptionHandler;
public EncryptionMongoEventListener(EncryptionService encryptionService) {
encryptionHandler = new EncryptionHandler();
this.encryptionService = encryptionService;
}
private final EncryptionHandler encryptionHandler = new EncryptionHandler();
// This lifecycle event is before we save a document into the DB,
// and even before the mapper has converted the object into a document type
@ -23,7 +17,7 @@ public class EncryptionMongoEventListener<E> extends AbstractMongoEventListener<
public void onBeforeConvert(BeforeConvertEvent<E> event) {
E source = event.getSource();
encryptionHandler.convertEncryption(source, encryptionService::encryptString);
encryptionHandler.convertEncryption(source, EncryptionHelper::encrypt);
}
// This lifecycle event is after we retrieve a document from the DB,
@ -32,6 +26,6 @@ public class EncryptionMongoEventListener<E> extends AbstractMongoEventListener<
public void onAfterConvert(AfterConvertEvent<E> event) {
E source = event.getSource();
encryptionHandler.convertEncryption(source, encryptionService::decryptString);
encryptionHandler.convertEncryption(source, EncryptionHelper::decrypt);
}
}

View File

@ -0,0 +1,41 @@
package com.appsmith.external.helpers;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import java.util.HexFormat;
public final class EncryptionHelper {
private static final String salt = requireEnv("APPSMITH_ENCRYPTION_SALT");
private static final String password = requireEnv("APPSMITH_ENCRYPTION_PASSWORD");
private static final HexFormat hexFormat = HexFormat.of();
private static final TextEncryptor textEncryptor = makeTextEncryptor();
private EncryptionHelper() {}
private static TextEncryptor makeTextEncryptor() {
final String saltInHex = hexFormat.formatHex(salt.getBytes());
return Encryptors.delux(password, saltInHex);
}
public static String encrypt(String plaintext) {
return textEncryptor.encrypt(plaintext);
}
public static String decrypt(String encryptedText) {
return textEncryptor.decrypt(encryptedText);
}
private static String requireEnv(String name) {
final String value = System.getenv(name);
if (StringUtils.isBlank(value)) {
throw new RuntimeException("Environment variable '%s' is required".formatted(name));
}
return value;
}
}

View File

@ -1,5 +0,0 @@
package com.appsmith.external.services;
import com.appsmith.external.services.ce.EncryptionServiceCE;
public interface EncryptionService extends EncryptionServiceCE {}

View File

@ -1,8 +0,0 @@
package com.appsmith.external.services.ce;
public interface EncryptionServiceCE {
String encryptString(String plaintext);
String decryptString(String encryptedText);
}

View File

@ -1,16 +0,0 @@
package com.appsmith.server.configurations;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@Configuration
@Getter
public class EncryptionConfig {
@Value("${encrypt.salt}")
private String salt;
@Value("${encrypt.password}")
private String password;
}

View File

@ -3,7 +3,6 @@ package com.appsmith.server.configurations;
import com.appsmith.external.annotations.documenttype.DocumentTypeMapper;
import com.appsmith.external.annotations.encryption.EncryptionMongoEventListener;
import com.appsmith.external.models.AuthenticationDTO;
import com.appsmith.external.services.EncryptionService;
import com.appsmith.server.configurations.mongo.SoftDeleteMongoRepositoryFactoryBean;
import com.appsmith.server.converters.StringToInstantConverter;
import com.appsmith.server.repositories.BaseRepositoryImpl;
@ -266,8 +265,8 @@ public class MongoConfig {
}
@Bean
public EncryptionMongoEventListener encryptionMongoEventListener(EncryptionService encryptionService) {
return new EncryptionMongoEventListener(encryptionService);
public EncryptionMongoEventListener encryptionMongoEventListener() {
return new EncryptionMongoEventListener();
}
@Bean

View File

@ -1,14 +0,0 @@
package com.appsmith.server.services;
import com.appsmith.external.services.EncryptionService;
import com.appsmith.server.configurations.EncryptionConfig;
import com.appsmith.server.services.ce.EncryptionServiceCEImpl;
import org.springframework.stereotype.Service;
@Service
public class EncryptionServiceImpl extends EncryptionServiceCEImpl implements EncryptionService {
public EncryptionServiceImpl(EncryptionConfig encryptionConfig) {
super(encryptionConfig);
}
}

View File

@ -1,6 +1,5 @@
package com.appsmith.server.services;
import com.appsmith.external.services.EncryptionService;
import com.appsmith.server.configurations.CommonConfig;
import com.appsmith.server.configurations.EmailConfig;
import com.appsmith.server.helpers.UserServiceHelper;
@ -35,7 +34,6 @@ public class UserServiceImpl extends UserServiceCECompatibleImpl implements User
PolicySolution policySolution,
CommonConfig commonConfig,
EmailConfig emailConfig,
EncryptionService encryptionService,
UserDataService userDataService,
TenantService tenantService,
PermissionGroupService permissionGroupService,
@ -54,7 +52,6 @@ public class UserServiceImpl extends UserServiceCECompatibleImpl implements User
passwordResetTokenRepository,
passwordEncoder,
commonConfig,
encryptionService,
userDataService,
tenantService,
userUtils,

View File

@ -1,32 +0,0 @@
package com.appsmith.server.services.ce;
import com.appsmith.external.services.ce.EncryptionServiceCE;
import com.appsmith.server.configurations.EncryptionConfig;
import org.apache.commons.codec.binary.Hex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.security.crypto.encrypt.TextEncryptor;
public class EncryptionServiceCEImpl implements EncryptionServiceCE {
private final EncryptionConfig encryptionConfig;
private TextEncryptor textEncryptor;
@Autowired
public EncryptionServiceCEImpl(EncryptionConfig encryptionConfig) {
this.encryptionConfig = encryptionConfig;
String saltInHex = Hex.encodeHexString(encryptionConfig.getSalt().getBytes());
this.textEncryptor = Encryptors.delux(encryptionConfig.getPassword(), saltInHex);
}
@Override
public String encryptString(String plaintext) {
return textEncryptor.encrypt(plaintext);
}
@Override
public String decryptString(String encryptedText) {
return textEncryptor.decrypt(encryptedText);
}
}

View File

@ -1,7 +1,7 @@
package com.appsmith.server.services.ce;
import com.appsmith.external.helpers.AppsmithBeanUtils;
import com.appsmith.external.services.EncryptionService;
import com.appsmith.external.helpers.EncryptionHelper;
import com.appsmith.server.acl.AclPermission;
import com.appsmith.server.configurations.CommonConfig;
import com.appsmith.server.constants.FieldName;
@ -96,7 +96,6 @@ public class UserServiceCEImpl extends BaseService<UserRepository, User, String>
private final PasswordEncoder passwordEncoder;
private final CommonConfig commonConfig;
private final EncryptionService encryptionService;
private final UserDataService userDataService;
private final TenantService tenantService;
private final UserUtils userUtils;
@ -128,7 +127,6 @@ public class UserServiceCEImpl extends BaseService<UserRepository, User, String>
PasswordResetTokenRepository passwordResetTokenRepository,
PasswordEncoder passwordEncoder,
CommonConfig commonConfig,
EncryptionService encryptionService,
UserDataService userDataService,
TenantService tenantService,
UserUtils userUtils,
@ -144,7 +142,6 @@ public class UserServiceCEImpl extends BaseService<UserRepository, User, String>
this.passwordResetTokenRepository = passwordResetTokenRepository;
this.passwordEncoder = passwordEncoder;
this.commonConfig = commonConfig;
this.encryptionService = encryptionService;
this.userDataService = userDataService;
this.tenantService = tenantService;
this.userUtils = userUtils;
@ -225,7 +222,7 @@ public class UserServiceCEImpl extends BaseService<UserRepository, User, String>
String resetUrl = String.format(
FORGOT_PASSWORD_CLIENT_URL_FORMAT,
resetUserPasswordDTO.getBaseUrl(),
encryptionService.encryptString(urlParams));
EncryptionHelper.encrypt(urlParams));
log.debug("Password reset url for email: {}: {}", passwordResetToken.getEmail(), resetUrl);
@ -695,7 +692,7 @@ public class UserServiceCEImpl extends BaseService<UserRepository, User, String>
}
private EmailTokenDTO parseValueFromEncryptedToken(String encryptedToken) {
String decryptString = encryptionService.decryptString(encryptedToken);
String decryptString = EncryptionHelper.decrypt(encryptedToken);
List<NameValuePair> nameValuePairs = WWWFormCodec.parse(decryptString, StandardCharsets.UTF_8);
Map<String, String> params = new HashMap<>();
@ -780,7 +777,7 @@ public class UserServiceCEImpl extends BaseService<UserRepository, User, String>
String verificationUrl = String.format(
EMAIL_VERIFICATION_CLIENT_URL_FORMAT,
resendEmailVerificationDTO.getBaseUrl(),
encryptionService.encryptString(urlParams),
EncryptionHelper.encrypt(urlParams),
URLEncoder.encode(emailVerificationToken.getEmail(), StandardCharsets.UTF_8),
redirectUrlCopy);

View File

@ -1,6 +1,5 @@
package com.appsmith.server.services.ce_compatible;
import com.appsmith.external.services.EncryptionService;
import com.appsmith.server.configurations.CommonConfig;
import com.appsmith.server.helpers.UserServiceHelper;
import com.appsmith.server.helpers.UserUtils;
@ -31,7 +30,6 @@ public class UserServiceCECompatibleImpl extends UserServiceCEImpl implements Us
PasswordResetTokenRepository passwordResetTokenRepository,
PasswordEncoder passwordEncoder,
CommonConfig commonConfig,
EncryptionService encryptionService,
UserDataService userDataService,
TenantService tenantService,
UserUtils userUtils,
@ -49,7 +47,6 @@ public class UserServiceCECompatibleImpl extends UserServiceCEImpl implements Us
passwordResetTokenRepository,
passwordEncoder,
commonConfig,
encryptionService,
userDataService,
tenantService,
userUtils,

View File

@ -87,11 +87,6 @@ appsmith.cloud_services.signature_base_url = ${APPSMITH_CLOUD_SERVICES_SIGNATURE
appsmith.cloud_services.template_upload_auth_header = ${APPSMITH_CLOUD_SERVICES_TEMPLATE_UPLOAD_AUTH:}
github_repo = ${APPSMITH_GITHUB_REPO:}
# MANDATORY!! No default properties are being provided for encryption password and salt for security.
# The server would not come up without these values provided through the environment variables.
encrypt.password=${APPSMITH_ENCRYPTION_PASSWORD:}
encrypt.salt=${APPSMITH_ENCRYPTION_SALT:}
# The following configurations are to help support prometheus scraping for monitoring
management.endpoints.web.exposure.include=prometheus,metrics
management.tracing.enabled=${APPSMITH_TRACING_ENABLED:false}

View File

@ -1,6 +1,7 @@
package com.appsmith.server.services;
import com.appsmith.external.constants.PluginConstants;
import com.appsmith.external.helpers.EncryptionHelper;
import com.appsmith.external.helpers.restApiUtils.connections.APIConnection;
import com.appsmith.external.helpers.restApiUtils.connections.APIConnectionFactory;
import com.appsmith.external.helpers.restApiUtils.connections.BearerTokenAuthentication;
@ -17,7 +18,6 @@ import com.appsmith.external.models.DatasourceStorageDTO;
import com.appsmith.external.models.OAuth2;
import com.appsmith.external.models.UpdatableConnection;
import com.appsmith.external.plugins.PluginExecutor;
import com.appsmith.external.services.EncryptionService;
import com.appsmith.server.applications.base.ApplicationService;
import com.appsmith.server.datasources.base.DatasourceService;
import com.appsmith.server.datasourcestorages.base.DatasourceStorageService;
@ -73,9 +73,6 @@ import static org.mockito.Mockito.spy;
@Slf4j
public class DatasourceContextServiceTest {
@Autowired
EncryptionService encryptionService;
@Autowired
WorkspaceRepository workspaceRepository;
@ -287,7 +284,7 @@ public class DatasourceContextServiceTest {
DBAuth encryptedAuthentication = (DBAuth) savedDatasourceStorageDTO
.getDatasourceConfiguration()
.getAuthentication();
assertEquals(password, encryptionService.decryptString(encryptedAuthentication.getPassword()));
assertEquals(password, EncryptionHelper.decrypt(encryptedAuthentication.getPassword()));
})
.verifyComplete();
}

View File

@ -1,5 +1,6 @@
package com.appsmith.server.services;
import com.appsmith.external.helpers.EncryptionHelper;
import com.appsmith.external.models.ActionConfiguration;
import com.appsmith.external.models.ActionDTO;
import com.appsmith.external.models.Connection;
@ -14,7 +15,6 @@ import com.appsmith.external.models.OAuth2;
import com.appsmith.external.models.Policy;
import com.appsmith.external.models.SSLDetails;
import com.appsmith.external.models.UploadedFile;
import com.appsmith.external.services.EncryptionService;
import com.appsmith.server.acl.AclPermission;
import com.appsmith.server.applications.base.ApplicationService;
import com.appsmith.server.constants.FieldName;
@ -103,9 +103,6 @@ public class DatasourceServiceTest {
@Autowired
ApplicationPageService applicationPageService;
@Autowired
EncryptionService encryptionService;
@Autowired
LayoutActionService layoutActionService;
@ -1095,7 +1092,7 @@ public class DatasourceServiceTest {
DBAuth authentication = (DBAuth)
datasourceStorageDTO.getDatasourceConfiguration().getAuthentication();
assertThat(authentication.getUsername()).isEqualTo(username);
assertThat(encryptionService.decryptString(authentication.getPassword()))
assertThat(EncryptionHelper.decrypt(authentication.getPassword()))
.isEqualTo(password);
})
.verifyComplete();
@ -1226,7 +1223,7 @@ public class DatasourceServiceTest {
datasourceStorageDTO.getDatasourceConfiguration().getAuthentication();
assertThat(authentication.getUsername()).isEqualTo(username);
assertThat(password).isEqualTo(encryptionService.decryptString(authentication.getPassword()));
assertThat(password).isEqualTo(EncryptionHelper.decrypt(authentication.getPassword()));
})
.verifyComplete();
}

View File

@ -1,7 +1,7 @@
package com.appsmith.server.services;
import com.appsmith.external.helpers.EncryptionHelper;
import com.appsmith.external.models.Policy;
import com.appsmith.external.services.EncryptionService;
import com.appsmith.server.applications.base.ApplicationService;
import com.appsmith.server.configurations.CommonConfig;
import com.appsmith.server.configurations.WithMockAppsmithUser;
@ -92,9 +92,6 @@ public class UserServiceTest {
@Autowired
PasswordEncoder passwordEncoder;
@Autowired
EncryptionService encryptionService;
@Autowired
UserDataService userDataService;
@ -552,7 +549,7 @@ public class UserServiceTest {
nameValuePairs.add(new BasicNameValuePair("email", emailAddress));
nameValuePairs.add(new BasicNameValuePair("token", token));
String urlParams = WWWFormCodec.format(nameValuePairs, StandardCharsets.UTF_8);
return encryptionService.encryptString(urlParams);
return EncryptionHelper.encrypt(urlParams);
}
@Test