Merge pull request #3930 from appsmithorg/task/apply-encryption

Applying AOP encryption
This commit is contained in:
Nidhi 2021-05-06 16:19:09 +05:30 committed by GitHub
commit ea9743123b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 187 additions and 430 deletions

View File

@ -53,144 +53,146 @@ public class EncryptionHandler {
// If it is not known, scan each field for annotation or Appsmith type
List<CandidateField> finalCandidateFields = new ArrayList<>();
ReflectionUtils.doWithFields(sourceClass, field -> {
if (field.getAnnotation(Encrypted.class) != null) {
CandidateField candidateField = new CandidateField(field, CandidateField.Type.ANNOTATED_FIELD);
finalCandidateFields.add(candidateField);
} else if (AppsmithDomain.class.isAssignableFrom(field.getType())) {
CandidateField candidateField = null;
field.setAccessible(true);
Object fieldValue = ReflectionUtils.getField(field, source);
if (fieldValue == null) {
if (this.encryptedFieldsMap.containsKey(field.getType())) {
// If this field is null, but the cache has a non-empty list of candidates already,
// then this is an appsmith field with known annotations
candidateField = new CandidateField(field, CandidateField.Type.APPSMITH_FIELD_KNOWN);
} else {
// If it is null and the cache is not aware of the field, this is still a prospect,
// but with an unknown type (could also be polymorphic)
candidateField = new CandidateField(field, CandidateField.Type.APPSMITH_FIELD_UNKNOWN);
}
} else {
// If an object exists, check if the object type is the same as the field type
CandidateField.Type appsmithFieldType;
if (field.getType().getCanonicalName().equals(fieldValue.getClass().getCanonicalName())) {
// If they match, then this is going to be an appsmith known field
appsmithFieldType = CandidateField.Type.APPSMITH_FIELD_KNOWN;
} else {
// If not, then this field is polymorphic,
// it will need to be checked for type every time
appsmithFieldType = CandidateField.Type.APPSMITH_FIELD_POLYMORPHIC;
}
// Now, go into field type and repeat
List<CandidateField> candidateFieldsForType = findCandidateFieldsForType(fieldValue);
if (appsmithFieldType.equals(CandidateField.Type.APPSMITH_FIELD_POLYMORPHIC)
|| !candidateFieldsForType.isEmpty()) {
// This type only qualifies as a candidate if it is polymorphic,
// or has a list of candidates
candidateField = new CandidateField(field, appsmithFieldType);
}
}
field.setAccessible(false);
if (candidateField != null) {
// This will only ever be null if the field value is populated,
// and is known to be a non-encryption related field
synchronized (sourceClass) {
ReflectionUtils.doWithFields(sourceClass, field -> {
if (field.getAnnotation(Encrypted.class) != null) {
CandidateField candidateField = new CandidateField(field, CandidateField.Type.ANNOTATED_FIELD);
finalCandidateFields.add(candidateField);
}
} else if (Collection.class.isAssignableFrom(field.getType()) &&
field.getGenericType() instanceof ParameterizedType) {
// If this is a collection, check if the Type parameter is an AppsmithDomain
Type[] typeArguments;
ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
typeArguments = parameterizedType.getActualTypeArguments();
Class<?> subFieldType = (Class<?>) typeArguments[0];
if (this.encryptedFieldsMap.containsKey(subFieldType)) {
// This is a known type, it should necessarily be of AppsmithDomain type
assert AppsmithDomain.class.isAssignableFrom(subFieldType);
final List<CandidateField> existingSubTypeCandidates = this.encryptedFieldsMap.get(subFieldType);
if (!existingSubTypeCandidates.isEmpty()) {
finalCandidateFields.add(new CandidateField(field, CandidateField.Type.APPSMITH_LIST_KNOWN));
}
} else if (AppsmithDomain.class.isAssignableFrom(subFieldType)) {
// If the type is not known, then this is either not parsed yet, or has polymorphic implementations
} else if (AppsmithDomain.class.isAssignableFrom(field.getType())) {
CandidateField candidateField = null;
field.setAccessible(true);
Object fieldValue = ReflectionUtils.getField(field, source);
List<?> list = (List<?>) fieldValue;
if (list == null || list.isEmpty()) {
finalCandidateFields.add(new CandidateField(field, CandidateField.Type.APPSMITH_LIST_UNKNOWN));
if (fieldValue == null) {
if (this.encryptedFieldsMap.containsKey(field.getType())) {
// If this field is null, but the cache has a non-empty list of candidates already,
// then this is an appsmith field with known annotations
candidateField = new CandidateField(field, CandidateField.Type.APPSMITH_FIELD_KNOWN);
} else {
// If it is null and the cache is not aware of the field, this is still a prospect,
// but with an unknown type (could also be polymorphic)
candidateField = new CandidateField(field, CandidateField.Type.APPSMITH_FIELD_UNKNOWN);
}
} else {
for (final Object o : list) {
if (o == null) {
continue;
}
if (o.getClass().getCanonicalName().equals(subFieldType.getTypeName())) {
final List<CandidateField> candidateFieldsForListMember = findCandidateFieldsForType(o);
if (candidateFieldsForListMember != null && !candidateFieldsForListMember.isEmpty()) {
finalCandidateFields.add(new CandidateField(field, CandidateField.Type.APPSMITH_LIST_KNOWN));
}
} else {
finalCandidateFields.add(new CandidateField(field, CandidateField.Type.APPSMITH_LIST_POLYMORPHIC));
}
break;
// If an object exists, check if the object type is the same as the field type
CandidateField.Type appsmithFieldType;
if (field.getType().getCanonicalName().equals(fieldValue.getClass().getCanonicalName())) {
// If they match, then this is going to be an appsmith known field
appsmithFieldType = CandidateField.Type.APPSMITH_FIELD_KNOWN;
} else {
// If not, then this field is polymorphic,
// it will need to be checked for type every time
appsmithFieldType = CandidateField.Type.APPSMITH_FIELD_POLYMORPHIC;
}
// Now, go into field type and repeat
List<CandidateField> candidateFieldsForType = findCandidateFieldsForType(fieldValue);
if (appsmithFieldType.equals(CandidateField.Type.APPSMITH_FIELD_POLYMORPHIC)
|| !candidateFieldsForType.isEmpty()) {
// This type only qualifies as a candidate if it is polymorphic,
// or has a list of candidates
candidateField = new CandidateField(field, appsmithFieldType);
}
}
field.setAccessible(false);
}
// TODO Add support for nested collections
} else if (Map.class.isAssignableFrom(field.getType()) &&
field.getGenericType() instanceof ParameterizedType) {
Type[] typeArguments;
ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
typeArguments = parameterizedType.getActualTypeArguments();
Class<?> subFieldType = (Class<?>) typeArguments[1];
if (this.encryptedFieldsMap.containsKey(subFieldType)) {
// This is a known type, it should necessarily be of AppsmithDomain type
assert AppsmithDomain.class.isAssignableFrom(subFieldType);
final List<CandidateField> existingSubTypeCandidates = this.encryptedFieldsMap.get(subFieldType);
if (!existingSubTypeCandidates.isEmpty()) {
finalCandidateFields.add(new CandidateField(field, CandidateField.Type.APPSMITH_MAP_KNOWN));
if (candidateField != null) {
// This will only ever be null if the field value is populated,
// and is known to be a non-encryption related field
finalCandidateFields.add(candidateField);
}
} else if (AppsmithDomain.class.isAssignableFrom(subFieldType)) {
// If the type is not known, then this is either not parsed yet, or has polymorphic implementations
} else if (Collection.class.isAssignableFrom(field.getType()) &&
field.getGenericType() instanceof ParameterizedType) {
// If this is a collection, check if the Type parameter is an AppsmithDomain
Type[] typeArguments;
ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
typeArguments = parameterizedType.getActualTypeArguments();
Class<?> subFieldType = (Class<?>) typeArguments[0];
field.setAccessible(true);
Object fieldValue = ReflectionUtils.getField(field, source);
Map<?, ?> map = (Map<?, ?>) fieldValue;
if (map == null || map.isEmpty()) {
finalCandidateFields.add(new CandidateField(field, CandidateField.Type.APPSMITH_MAP_UNKNOWN));
} else {
for (Map.Entry<?, ?> entry : map.entrySet()) {
final Object value = entry.getValue();
if (value == null) {
continue;
}
if (value.getClass().getCanonicalName().equals(subFieldType.getTypeName())) {
final List<CandidateField> candidateFieldsForListMember = findCandidateFieldsForType(value);
if (candidateFieldsForListMember != null && !candidateFieldsForListMember.isEmpty()) {
finalCandidateFields.add(new CandidateField(field, CandidateField.Type.APPSMITH_MAP_KNOWN));
}
} else {
finalCandidateFields.add(new CandidateField(field, CandidateField.Type.APPSMITH_MAP_POLYMORPHIC));
}
break;
if (this.encryptedFieldsMap.containsKey(subFieldType)) {
// This is a known type, it should necessarily be of AppsmithDomain type
assert AppsmithDomain.class.isAssignableFrom(subFieldType);
final List<CandidateField> existingSubTypeCandidates = this.encryptedFieldsMap.get(subFieldType);
if (!existingSubTypeCandidates.isEmpty()) {
finalCandidateFields.add(new CandidateField(field, CandidateField.Type.APPSMITH_LIST_KNOWN));
}
}
field.setAccessible(false);
}
}
} else if (AppsmithDomain.class.isAssignableFrom(subFieldType)) {
// If the type is not known, then this is either not parsed yet, or has polymorphic implementations
}, field -> field.getAnnotation(Encrypted.class) != null ||
AppsmithDomain.class.isAssignableFrom(field.getType()) ||
Collection.class.isAssignableFrom(field.getType()) ||
Map.class.isAssignableFrom(field.getType()));
field.setAccessible(true);
Object fieldValue = ReflectionUtils.getField(field, source);
List<?> list = (List<?>) fieldValue;
if (list == null || list.isEmpty()) {
finalCandidateFields.add(new CandidateField(field, CandidateField.Type.APPSMITH_LIST_UNKNOWN));
} else {
for (final Object o : list) {
if (o == null) {
continue;
}
if (o.getClass().getCanonicalName().equals(subFieldType.getTypeName())) {
final List<CandidateField> candidateFieldsForListMember = findCandidateFieldsForType(o);
if (candidateFieldsForListMember != null && !candidateFieldsForListMember.isEmpty()) {
finalCandidateFields.add(new CandidateField(field, CandidateField.Type.APPSMITH_LIST_KNOWN));
}
} else {
finalCandidateFields.add(new CandidateField(field, CandidateField.Type.APPSMITH_LIST_POLYMORPHIC));
}
break;
}
}
field.setAccessible(false);
}
// TODO Add support for nested collections
} else if (Map.class.isAssignableFrom(field.getType()) &&
field.getGenericType() instanceof ParameterizedType) {
Type[] typeArguments;
ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
typeArguments = parameterizedType.getActualTypeArguments();
Class<?> subFieldType = (Class<?>) typeArguments[1];
if (this.encryptedFieldsMap.containsKey(subFieldType)) {
// This is a known type, it should necessarily be of AppsmithDomain type
assert AppsmithDomain.class.isAssignableFrom(subFieldType);
final List<CandidateField> existingSubTypeCandidates = this.encryptedFieldsMap.get(subFieldType);
if (!existingSubTypeCandidates.isEmpty()) {
finalCandidateFields.add(new CandidateField(field, CandidateField.Type.APPSMITH_MAP_KNOWN));
}
} else if (AppsmithDomain.class.isAssignableFrom(subFieldType)) {
// If the type is not known, then this is either not parsed yet, or has polymorphic implementations
field.setAccessible(true);
Object fieldValue = ReflectionUtils.getField(field, source);
Map<?, ?> map = (Map<?, ?>) fieldValue;
if (map == null || map.isEmpty()) {
finalCandidateFields.add(new CandidateField(field, CandidateField.Type.APPSMITH_MAP_UNKNOWN));
} else {
for (Map.Entry<?, ?> entry : map.entrySet()) {
final Object value = entry.getValue();
if (value == null) {
continue;
}
if (value.getClass().getCanonicalName().equals(subFieldType.getTypeName())) {
final List<CandidateField> candidateFieldsForListMember = findCandidateFieldsForType(value);
if (candidateFieldsForListMember != null && !candidateFieldsForListMember.isEmpty()) {
finalCandidateFields.add(new CandidateField(field, CandidateField.Type.APPSMITH_MAP_KNOWN));
}
} else {
finalCandidateFields.add(new CandidateField(field, CandidateField.Type.APPSMITH_MAP_POLYMORPHIC));
}
break;
}
}
field.setAccessible(false);
}
}
}, field -> field.getAnnotation(Encrypted.class) != null ||
AppsmithDomain.class.isAssignableFrom(field.getType()) ||
Collection.class.isAssignableFrom(field.getType()) ||
Map.class.isAssignableFrom(field.getType()));
}
// Update cache for next use
encryptedFieldsMap.put(sourceClass, finalCandidateFields);
@ -198,7 +200,7 @@ public class EncryptionHandler {
}
boolean convertEncryption(Object source, Function<String, String> transformer) {
synchronized boolean convertEncryption(Object source, Function<String, String> transformer) {
if (source == null) {
return false;
}
@ -294,12 +296,11 @@ public class EncryptionHandler {
}
}
}
field.setAccessible(false);
}
field.setAccessible(false);
}
return hasEncryptedFields;
}
}

View File

@ -1,6 +1,5 @@
package com.appsmith.external.models;
import com.appsmith.external.annotations.encryption.Encrypted;
import com.appsmith.external.constants.Authentication;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonSubTypes;
@ -11,8 +10,6 @@ import lombok.Setter;
import org.springframework.data.annotation.Transient;
import reactor.core.publisher.Mono;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
@Getter
@ -53,25 +50,6 @@ public class AuthenticationDTO implements AppsmithDomain {
@JsonIgnore
AuthenticationResponse authenticationResponse;
@JsonIgnore
public Map<String, String> getEncryptionFields() {
return Collections.emptyMap();
}
public void setEncryptionFields(Map<String, String> encryptedFields) {
// This is supposed to be overridden by implementations.
}
@JsonIgnore
public Set<String> getEmptyEncryptionFields() {
return Collections.emptySet();
}
@JsonIgnore
public Boolean isEncrypted() {
return this.isEncrypted;
}
public Mono<Boolean> hasExpired() {
return Mono.just(Boolean.FALSE);
}

View File

@ -1,5 +1,6 @@
package com.appsmith.external.models;
import com.appsmith.external.annotations.encryption.Encrypted;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@ -14,13 +15,17 @@ import java.time.Instant;
@NoArgsConstructor
@AllArgsConstructor
public class AuthenticationResponse implements AppsmithDomain {
@Encrypted
String token;
@Encrypted
String refreshToken;
Instant issuedAt;
Instant expiresAt;
@Encrypted
Object tokenResponse;
}

View File

@ -1,8 +1,8 @@
package com.appsmith.external.models;
import com.appsmith.external.annotations.documenttype.DocumentType;
import com.appsmith.external.annotations.encryption.Encrypted;
import com.appsmith.external.constants.Authentication;
import com.appsmith.external.constants.FieldName;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Getter;
@ -10,9 +10,6 @@ import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import java.util.Map;
import java.util.Set;
@Getter
@Setter
@ToString
@ -29,31 +26,9 @@ public class DBAuth extends AuthenticationDTO {
String username;
@Encrypted
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
String password;
String databaseName;
@Override
public Map<String, String> getEncryptionFields() {
if (this.password != null && !this.password.isBlank()) {
return Map.of(FieldName.PASSWORD, this.password);
}
return Map.of();
}
@Override
public void setEncryptionFields(Map<String, String> encryptedFields) {
if (encryptedFields != null && encryptedFields.containsKey(FieldName.PASSWORD)) {
this.password = encryptedFields.get(FieldName.PASSWORD);
}
}
@Override
public Set<String> getEmptyEncryptionFields() {
if (this.password == null || this.password.isBlank()) {
return Set.of(FieldName.PASSWORD);
}
return Set.of();
}
}

View File

@ -1,10 +1,8 @@
package com.appsmith.external.models;
import com.appsmith.external.annotations.documenttype.DocumentType;
import com.appsmith.external.annotations.encryption.Encrypted;
import com.appsmith.external.constants.Authentication;
import com.appsmith.external.constants.FieldName;
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError;
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Getter;
@ -14,13 +12,8 @@ import lombok.ToString;
import org.apache.logging.log4j.util.Strings;
import org.springframework.data.annotation.Transient;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Mono;
import java.time.Instant;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@ -46,6 +39,7 @@ public class OAuth2 extends AuthenticationDTO {
String clientId;
@Encrypted
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
String clientSecret;
@ -79,73 +73,4 @@ public class OAuth2 extends AuthenticationDTO {
.collect(Collectors.toSet());
}
}
@Override
public Map<String, String> getEncryptionFields() {
Map<String, String> map = new HashMap<>();
if (this.clientSecret != null) {
map.put(FieldName.CLIENT_SECRET, this.clientSecret);
}
if (this.getAuthenticationResponse() != null) {
if (this.authenticationResponse.getToken() != null) {
map.put(FieldName.TOKEN, this.authenticationResponse.getToken());
}
if (this.authenticationResponse.getRefreshToken() != null) {
map.put(FieldName.REFRESH_TOKEN, this.authenticationResponse.getRefreshToken());
}
if (this.authenticationResponse.getTokenResponse() != null) {
map.put(FieldName.TOKEN_RESPONSE, String.valueOf(this.authenticationResponse.getTokenResponse()));
}
}
return map;
}
@Override
public void setEncryptionFields(Map<String, String> encryptedFields) {
if (encryptedFields != null) {
if (encryptedFields.containsKey(FieldName.CLIENT_SECRET)) {
this.clientSecret = encryptedFields.get(FieldName.CLIENT_SECRET);
}
if (encryptedFields.containsKey(FieldName.TOKEN)) {
this.authenticationResponse.setToken(encryptedFields.get(FieldName.TOKEN));
}
if (encryptedFields.containsKey(FieldName.REFRESH_TOKEN)) {
this.authenticationResponse.setRefreshToken(encryptedFields.get(FieldName.REFRESH_TOKEN));
}
if (encryptedFields.containsKey(FieldName.TOKEN_RESPONSE)) {
this.authenticationResponse.setTokenResponse(encryptedFields.get(FieldName.TOKEN_RESPONSE));
}
}
}
@Override
public Set<String> getEmptyEncryptionFields() {
Set<String> set = new HashSet<>();
if (this.clientSecret == null || this.clientSecret.isEmpty()) {
set.add(FieldName.CLIENT_SECRET);
}
if (this.getAuthenticationResponse() != null) {
if (this.authenticationResponse.getToken() == null || this.authenticationResponse.getToken().isEmpty()) {
set.add(FieldName.TOKEN);
}
if (this.authenticationResponse.getRefreshToken() == null || this.authenticationResponse.getRefreshToken().isEmpty()) {
set.add(FieldName.REFRESH_TOKEN);
}
if (this.authenticationResponse.getTokenResponse() == null || (String.valueOf(this.authenticationResponse.getTokenResponse())).isEmpty()) {
set.add(FieldName.TOKEN_RESPONSE);
}
}
return set;
}
@Override
public Mono<Boolean> hasExpired() {
if (this.authenticationResponse == null) {
return Mono.error(new AppsmithPluginException(
AppsmithPluginError.PLUGIN_ERROR,
"Expected datasource to have valid authentication tokens at this point"));
}
return Mono.just(authenticationResponse.expiresAt.isBefore(Instant.now().plusSeconds(60)));
}
}

View File

@ -79,9 +79,9 @@ public class MongoConfig {
return converter;
}
// @Bean
// public EncryptionMongoEventListener encryptionMongoEventListener(EncryptionService encryptionService) {
// return new EncryptionMongoEventListener(encryptionService);
// }
@Bean
public EncryptionMongoEventListener encryptionMongoEventListener(EncryptionService encryptionService) {
return new EncryptionMongoEventListener(encryptionService);
}
}

View File

@ -1,6 +1,5 @@
package com.appsmith.server.services;
import com.appsmith.external.models.AuthenticationDTO;
import com.appsmith.server.domains.Datasource;
import com.appsmith.server.domains.DatasourceContext;
import reactor.core.publisher.Mono;
@ -22,6 +21,4 @@ public interface DatasourceContextService {
<T> Mono<T> retryOnce(Datasource datasource, Function<DatasourceContext, Mono<T>> task);
Mono<DatasourceContext> deleteDatasourceContext(String datasourceId);
AuthenticationDTO decryptSensitiveFields(AuthenticationDTO authenticationDTO);
}

View File

@ -18,7 +18,6 @@ import java.time.Instant;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;
import static com.appsmith.server.acl.AclPermission.EXECUTE_DATASOURCES;
@ -88,13 +87,6 @@ public class DatasourceContextServiceImpl implements DatasourceContextService {
.flatMap(objects -> {
Datasource datasource1 = objects.getT1();
// If authentication exists for the datasource, decrypt the fields
if (datasource1.getDatasourceConfiguration() != null &&
datasource1.getDatasourceConfiguration().getAuthentication() != null) {
AuthenticationDTO authentication = datasource1.getDatasourceConfiguration().getAuthentication();
datasource1.getDatasourceConfiguration().setAuthentication(decryptSensitiveFields(authentication));
}
PluginExecutor<Object> pluginExecutor = objects.getT2();
if (isStale) {
@ -185,18 +177,4 @@ public class DatasourceContextServiceImpl implements DatasourceContextService {
return datasourceContextMap.remove(datasourceId);
});
}
@Override
public AuthenticationDTO decryptSensitiveFields(AuthenticationDTO authentication) {
if (authentication != null && Boolean.TRUE.equals(authentication.isEncrypted())) {
Map<String, String> decryptedFields = authentication.getEncryptionFields().entrySet().stream()
.filter(e -> e.getValue() != null && !e.getValue().isBlank())
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> encryptionService.decryptString(e.getValue())));
authentication.setEncryptionFields(decryptedFields);
authentication.setIsEncrypted(false);
}
return authentication;
}
}

View File

@ -1,6 +1,5 @@
package com.appsmith.server.services;
import com.appsmith.external.models.AuthenticationDTO;
import com.appsmith.external.models.DatasourceTestResult;
import com.appsmith.server.acl.AclPermission;
import com.appsmith.server.domains.Datasource;
@ -30,7 +29,5 @@ public interface DatasourceService extends CrudService<Datasource, String> {
Flux<Datasource> saveAll(List<Datasource> datasourceList);
AuthenticationDTO encryptAuthenticationFields(AuthenticationDTO authentication);
public Mono<Datasource> populateHintMessages(Datasource datasource);
Mono<Datasource> populateHintMessages(Datasource datasource);
}

View File

@ -1,7 +1,7 @@
package com.appsmith.server.services;
import com.appsmith.external.helpers.BeanCopyUtils;
import com.appsmith.external.helpers.MustacheHelper;
import com.appsmith.external.models.AuthenticationDTO;
import com.appsmith.external.models.DatasourceConfiguration;
import com.appsmith.external.models.DatasourceTestResult;
import com.appsmith.external.models.Endpoint;
@ -36,7 +36,6 @@ import javax.validation.Validator;
import javax.validation.constraints.NotNull;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@ -84,17 +83,6 @@ public class DatasourceServiceImpl extends BaseService<DatasourceRepository, Dat
this.encryptionService = encryptionService;
}
@Override
public Mono<Datasource> getById(String s) {
return super.getById(s).flatMap(datasource -> {
if (datasource.getDatasourceConfiguration().getAuthentication() != null &&
Boolean.TRUE.equals(datasource.getDatasourceConfiguration().getAuthentication().isEncrypted())) {
datasource.getDatasourceConfiguration().setAuthentication(decryptSensitiveFields(datasource.getDatasourceConfiguration().getAuthentication()));
}
return Mono.just(datasource);
});
}
@Override
public Mono<Datasource> create(@NotNull Datasource datasource) {
String orgId = datasource.getOrganizationId();
@ -105,11 +93,6 @@ public class DatasourceServiceImpl extends BaseService<DatasourceRepository, Dat
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.ID));
}
// If Authentication Details are present in the datasource, encrypt the details before saving
if (datasource.getDatasourceConfiguration() != null) {
datasource.getDatasourceConfiguration().setAuthentication(encryptAuthenticationFields(datasource.getDatasourceConfiguration().getAuthentication()));
}
Mono<Datasource> datasourceMono = Mono.just(datasource);
if (StringUtils.isEmpty(datasource.getName())) {
datasourceMono = sequenceService
@ -190,11 +173,6 @@ public class DatasourceServiceImpl extends BaseService<DatasourceRepository, Dat
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.ID));
}
// If Authentication Details are present in the datasource, encrypt the details before saving
if (datasource.getDatasourceConfiguration() != null) {
datasource.getDatasourceConfiguration().setAuthentication(encryptAuthenticationFields(datasource.getDatasourceConfiguration().getAuthentication()));
}
// Since policies are a server only concept, first set the empty set (set by constructor) to null
datasource.setPolicies(null);
@ -215,23 +193,6 @@ public class DatasourceServiceImpl extends BaseService<DatasourceRepository, Dat
.flatMap(this::populateHintMessages);
}
@Override
public AuthenticationDTO encryptAuthenticationFields(AuthenticationDTO authentication) {
if (authentication != null
&& !Boolean.TRUE.equals(authentication.isEncrypted())) {
Map<String, String> encryptedFields = authentication.getEncryptionFields().entrySet().stream()
.filter(e -> e.getValue() != null)
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> encryptionService.encryptString(e.getValue())));
if (!encryptedFields.isEmpty()) {
authentication.setEncryptionFields(encryptedFields);
authentication.setIsEncrypted(true);
}
}
return authentication;
}
@Override
public Mono<Datasource> validateDatasource(Datasource datasource) {
Set<String> invalids = new HashSet<>();
@ -318,22 +279,18 @@ public class DatasourceServiceImpl extends BaseService<DatasourceRepository, Dat
*/
@Override
public Mono<DatasourceTestResult> testDatasource(Datasource datasource) {
Mono<Datasource> datasourceMono = null;
Mono<Datasource> datasourceMono = Mono.just(datasource);
// Fetch any fields that maybe encrypted from the db if the datasource being tested does not have those fields set.
// This scenario would happen whenever an existing datasource is being tested and no changes are present in the
// encrypted field (because encrypted fields are not sent over the network after encryption back to the client
if (datasource.getId() != null && datasource.getDatasourceConfiguration() != null &&
datasource.getDatasourceConfiguration().getAuthentication() != null) {
Set<String> emptyFields = datasource.getDatasourceConfiguration().getAuthentication().getEmptyEncryptionFields();
if (emptyFields != null && !emptyFields.isEmpty()) {
datasourceMono = getById(datasource.getId())
.switchIfEmpty(Mono.just(datasource));
}
}
if (datasourceMono == null) {
datasourceMono = Mono.just(datasource);
datasourceMono = getById(datasource.getId())
.map(datasource1 -> {
BeanCopyUtils.copyNestedNonNullProperties(datasource, datasource1);
return datasource1;
})
.switchIfEmpty(Mono.just(datasource));
}
return datasourceMono
@ -426,19 +383,4 @@ public class DatasourceServiceImpl extends BaseService<DatasourceRepository, Dat
.flatMap(toDelete -> repository.archive(toDelete).thenReturn(toDelete))
.flatMap(analyticsService::sendDeleteEvent);
}
private AuthenticationDTO decryptSensitiveFields(AuthenticationDTO authentication) {
if (authentication != null && Boolean.TRUE.equals(authentication.isEncrypted())) {
Map<String, String> decryptedFields = authentication.getEncryptionFields().entrySet().stream()
.filter(e -> e.getValue() != null)
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> encryptionService.decryptString(e.getValue())));
authentication.setEncryptionFields(decryptedFields);
authentication.setIsEncrypted(false);
}
return authentication;
}
}

View File

@ -8,7 +8,6 @@ import com.appsmith.external.helpers.MustacheHelper;
import com.appsmith.external.models.ActionConfiguration;
import com.appsmith.external.models.ActionExecutionRequest;
import com.appsmith.external.models.ActionExecutionResult;
import com.appsmith.external.models.AuthenticationDTO;
import com.appsmith.external.models.Param;
import com.appsmith.external.models.Policy;
import com.appsmith.external.models.Provider;
@ -243,17 +242,6 @@ public class NewActionServiceImpl extends BaseService<NewActionRepository, NewAc
Mono<Datasource> datasourceMono;
if (action.getDatasource().getId() == null) {
if (action.getDatasource().getDatasourceConfiguration() != null &&
action.getDatasource().getDatasourceConfiguration().getAuthentication() != null) {
action.getDatasource()
.getDatasourceConfiguration()
.setAuthentication(datasourceService.encryptAuthenticationFields(action
.getDatasource()
.getDatasourceConfiguration()
.getAuthentication()
));
}
datasourceMono = Mono.just(action.getDatasource())
.flatMap(datasourceService::validateDatasource);
} else {
@ -540,13 +528,6 @@ public class NewActionServiceImpl extends BaseService<NewActionRepository, NewAc
// Set the action name
actionName.set(action.getName());
// If authentication exists for the datasource, decrypt the fields
if (datasource.getDatasourceConfiguration() != null &&
datasource.getDatasourceConfiguration().getAuthentication() != null) {
AuthenticationDTO authentication = datasource.getDatasourceConfiguration().getAuthentication();
datasource.getDatasourceConfiguration().setAuthentication(datasourceContextService.decryptSensitiveFields(authentication));
}
ActionConfiguration actionConfiguration = action.getActionConfiguration();
Integer timeoutDuration = actionConfiguration.getTimeoutInMillisecond();
@ -562,12 +543,6 @@ public class NewActionServiceImpl extends BaseService<NewActionRepository, NewAc
.flatMap(datasourceContextService::getDatasourceContext)
// Now that we have the context (connection details), execute the action.
.flatMap(resourceContext -> validatedDatasourceMono.flatMap(datasource1 -> {
// Check encryption again
if (datasource1.getDatasourceConfiguration() != null &&
datasource1.getDatasourceConfiguration().getAuthentication() != null) {
AuthenticationDTO authentication = datasource1.getDatasourceConfiguration().getAuthentication();
datasource1.getDatasourceConfiguration().setAuthentication(datasourceContextService.decryptSensitiveFields(authentication));
}
return (Mono<ActionExecutionResult>) pluginExecutor.executeParameterized(
resourceContext.getConnection(),
executeActionDTO,

View File

@ -388,7 +388,6 @@ public class AuthenticationService {
datasource.getDatasourceConfiguration() != null &&
datasource.getDatasourceConfiguration().getAuthentication() instanceof OAuth2);
OAuth2 oAuth2 = (OAuth2) datasource.getDatasourceConfiguration().getAuthentication();
assert (!oAuth2.isEncrypted());
return pluginService.findById(datasource.getPluginId())
.filter(plugin -> PluginType.SAAS.equals(plugin.getType()))
.zipWith(configService.getInstanceId())

View File

@ -3,9 +3,9 @@ package com.appsmith.server.solutions;
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError;
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException;
import com.appsmith.external.exceptions.pluginExceptions.StaleConnectionException;
import com.appsmith.external.models.AuthenticationDTO;
import com.appsmith.external.models.DatasourceStructure;
import com.appsmith.external.plugins.PluginExecutor;
import com.appsmith.external.services.EncryptionService;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.domains.Datasource;
import com.appsmith.server.exceptions.AppsmithError;
@ -14,7 +14,6 @@ import com.appsmith.server.helpers.PluginExecutorHelper;
import com.appsmith.server.repositories.CustomDatasourceRepository;
import com.appsmith.server.services.DatasourceContextService;
import com.appsmith.server.services.DatasourceService;
import com.appsmith.external.services.EncryptionService;
import com.appsmith.server.services.PluginService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -23,9 +22,7 @@ import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Mono;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
@Component
@RequiredArgsConstructor
@ -78,8 +75,6 @@ public class DatasourceStructureSolution {
return Mono.just(datasource.getStructure());
}
decryptEncryptedFieldsInDatasource(datasource);
// This mono, when computed, will load the structure of the datasource by calling the plugin method.
return pluginExecutorHelper
.getPluginExecutor(pluginService.findById(datasource.getPluginId()))
@ -130,23 +125,4 @@ public class DatasourceStructureSolution {
: datasourceRepository.saveStructure(datasource.getId(), structure).thenReturn(structure)
);
}
private Datasource decryptEncryptedFieldsInDatasource(Datasource datasource) {
// If datasource has encrypted fields, decrypt and set it in the datasource.
if (datasource.getDatasourceConfiguration() != null) {
AuthenticationDTO authentication = datasource.getDatasourceConfiguration().getAuthentication();
if (authentication != null && authentication.isEncrypted()) {
Map<String, String> decryptedFields = authentication.getEncryptionFields().entrySet().stream()
.filter(e -> e.getValue() != null)
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> encryptionService.decryptString(e.getValue())));
authentication.setEncryptionFields(decryptedFields);
authentication.setIsEncrypted(false);
}
}
return datasource;
}
}

View File

@ -404,9 +404,6 @@ public class ExamplesOrganizationCloner {
makePristine(templateDatasource);
templateDatasource.setOrganizationId(toOrganizationId);
if (authentication != null) {
datasourceContextService.decryptSensitiveFields(authentication);
}
return createSuffixedDatasource(templateDatasource);
}));

View File

@ -2,6 +2,7 @@ package com.appsmith.server.services;
import com.appsmith.external.models.DBAuth;
import com.appsmith.external.models.DatasourceConfiguration;
import com.appsmith.external.services.EncryptionService;
import com.appsmith.server.acl.AclPermission;
import com.appsmith.server.domains.Datasource;
import com.appsmith.server.domains.Organization;
@ -10,6 +11,7 @@ import com.appsmith.server.helpers.MockPluginExecutor;
import com.appsmith.server.helpers.PluginExecutorHelper;
import com.appsmith.server.repositories.OrganizationRepository;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -22,15 +24,13 @@ import org.springframework.test.context.junit4.SpringRunner;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class DatasourceContextServiceTest {
@Autowired
DatasourceContextService datasourceContextService;
EncryptionService encryptionService;
@Autowired
OrganizationRepository organizationRepository;
@ -44,7 +44,7 @@ public class DatasourceContextServiceTest {
@MockBean
PluginExecutorHelper pluginExecutorHelper;
String orgId = "";
String orgId = "";
@Before
@WithUserDetails(value = "api_user")
@ -53,7 +53,6 @@ public class DatasourceContextServiceTest {
orgId = testOrg.getId();
}
@Test
@WithUserDetails(value = "api_user")
public void checkDecryptionOfAuthenticationDTOTest() {
@ -73,21 +72,29 @@ public class DatasourceContextServiceTest {
datasource.setDatasourceConfiguration(datasourceConfiguration);
datasource.setOrganizationId(orgId);
Mono<Datasource> datasourceMono = pluginMono.map(plugin -> {
datasource.setPluginId(plugin.getId());
return datasource;
}).flatMap(datasourceService::create);
final Datasource createdDatasource = pluginMono
.map(plugin -> {
datasource.setPluginId(plugin.getId());
return datasource;
})
.flatMap(datasourceService::create)
.block();
assert createdDatasource != null;
Mono<Datasource> datasourceMono = datasourceService.findById(createdDatasource.getId());
StepVerifier
.create(datasourceMono)
.assertNext(savedDatasource -> {
DBAuth authentication = (DBAuth) savedDatasource.getDatasourceConfiguration().getAuthentication();
DBAuth decryptedAuthentication = (DBAuth) datasourceContextService.decryptSensitiveFields(authentication);
assertThat(decryptedAuthentication.getPassword()).isEqualTo(password);
Assert.assertEquals(password, authentication.getPassword());
DBAuth encryptedAuthentication = (DBAuth) createdDatasource.getDatasourceConfiguration().getAuthentication();
Assert.assertEquals(encryptionService.encryptString(password), encryptedAuthentication.getPassword());
})
.verifyComplete();
}
@Test
@WithUserDetails(value = "api_user")
public void checkDecryptionOfAuthenticationDTONullPassword() {
@ -103,17 +110,24 @@ public class DatasourceContextServiceTest {
datasource.setDatasourceConfiguration(datasourceConfiguration);
datasource.setOrganizationId(orgId);
Mono<Datasource> datasourceMono = pluginMono.map(plugin -> {
datasource.setPluginId(plugin.getId());
return datasource;
}).flatMap(datasourceService::create);
final Datasource createdDatasource = pluginMono
.map(plugin -> {
datasource.setPluginId(plugin.getId());
return datasource;
})
.flatMap(datasourceService::create)
.block();
assert createdDatasource != null;
Mono<Datasource> datasourceMono = datasourceService.findById(createdDatasource.getId());
StepVerifier
.create(datasourceMono)
.assertNext(savedDatasource -> {
DBAuth authentication = (DBAuth) savedDatasource.getDatasourceConfiguration().getAuthentication();
DBAuth decryptedAuthentication = (DBAuth) datasourceContextService.decryptSensitiveFields(authentication);
assertThat(decryptedAuthentication.getPassword()).isNull();
Assert.assertNull(authentication.getPassword());
DBAuth encryptedAuthentication = (DBAuth) createdDatasource.getDatasourceConfiguration().getAuthentication();
Assert.assertNull(encryptedAuthentication.getPassword());
})
.verifyComplete();
}

View File

@ -611,7 +611,6 @@ public class DatasourceServiceTest {
DBAuth authentication = (DBAuth) savedDatasource.getDatasourceConfiguration().getAuthentication();
assertThat(authentication.getUsername()).isEqualTo(username);
assertThat(authentication.getPassword()).isEqualTo(encryptionService.encryptString(password));
assertThat(authentication.isEncrypted()).isTrue();
})
.verifyComplete();
}
@ -645,7 +644,6 @@ public class DatasourceServiceTest {
DBAuth authentication = (DBAuth) savedDatasource.getDatasourceConfiguration().getAuthentication();
assertThat(authentication.getUsername()).isNull();
assertThat(authentication.getPassword()).isNull();
assertThat(authentication.isEncrypted()).isNull();
})
.verifyComplete();
}
@ -695,7 +693,6 @@ public class DatasourceServiceTest {
assertThat(authentication.getUsername()).isEqualTo(username);
assertThat(encryptionService.encryptString(password)).isEqualTo(authentication.getPassword());
assertThat(authentication.isEncrypted()).isTrue();
})
.verifyComplete();
}

View File

@ -929,7 +929,8 @@ public class ExamplesOrganizationClonerTests {
.thenReturn(targetOrg1);
});
})
.flatMap(this::loadOrganizationData);
.flatMap(this::loadOrganizationData)
.doOnError(error -> log.error("Error in test", error));
StepVerifier.create(resultMono)
.assertNext(data -> {