Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit29cac29

Browse files
authored
Merge pull request#1661 from lowcoder-org/nodeserver_encrypted_payload
Nodeserver encrypted payload
2 parents10066c1 +bda2f16 commit29cac29

File tree

10 files changed

+229
-24
lines changed

10 files changed

+229
-24
lines changed

‎server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/encryption/EncryptionService.java‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ public interface EncryptionService {
44

55
StringencryptString(Stringplaintext);
66

7+
StringencryptStringForNodeServer(Stringplaintext);
8+
79
StringdecryptString(StringencryptedText);
810

911
StringencryptPassword(Stringplaintext);

‎server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/encryption/EncryptionServiceImpl.java‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
importorg.lowcoder.sdk.config.CommonConfig;
66
importorg.lowcoder.sdk.config.CommonConfig.Encrypt;
77
importorg.springframework.beans.factory.annotation.Autowired;
8+
importorg.springframework.beans.factory.annotation.Value;
89
importorg.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
910
importorg.springframework.security.crypto.encrypt.Encryptors;
1011
importorg.springframework.security.crypto.encrypt.TextEncryptor;
@@ -14,13 +15,18 @@
1415
publicclassEncryptionServiceImplimplementsEncryptionService {
1516

1617
privatefinalTextEncryptortextEncryptor;
18+
privatefinalTextEncryptortextEncryptorForNodeServer;
1719
privatefinalBCryptPasswordEncoderbCryptPasswordEncoder =newBCryptPasswordEncoder();
1820

1921
@Autowired
20-
publicEncryptionServiceImpl(CommonConfigcommonConfig) {
22+
publicEncryptionServiceImpl(
23+
CommonConfigcommonConfig
24+
) {
2125
Encryptencrypt =commonConfig.getEncrypt();
2226
StringsaltInHex =Hex.encodeHexString(encrypt.getSalt().getBytes());
2327
this.textEncryptor =Encryptors.text(encrypt.getPassword(),saltInHex);
28+
StringsaltInHexForNodeServer =Hex.encodeHexString(commonConfig.getJsExecutor().getSalt().getBytes());
29+
this.textEncryptorForNodeServer =Encryptors.text(commonConfig.getJsExecutor().getPassword(),saltInHexForNodeServer);
2430
}
2531

2632
@Override
@@ -30,6 +36,13 @@ public String encryptString(String plaintext) {
3036
}
3137
returntextEncryptor.encrypt(plaintext);
3238
}
39+
@Override
40+
publicStringencryptStringForNodeServer(Stringplaintext) {
41+
if (StringUtils.isEmpty(plaintext)) {
42+
returnplaintext;
43+
}
44+
returntextEncryptorForNodeServer.encrypt(plaintext);
45+
}
3346

3447
@Override
3548
publicStringdecryptString(StringencryptedText) {

‎server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/plugin/client/DatasourcePluginClient.java‎

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
importorg.apache.commons.collections4.CollectionUtils;
66
importorg.apache.commons.collections4.MapUtils;
77
importorg.apache.commons.lang3.StringUtils;
8+
importorg.lowcoder.domain.encryption.EncryptionService;
89
importorg.lowcoder.domain.plugin.client.dto.DatasourcePluginDefinition;
910
importorg.lowcoder.domain.plugin.client.dto.GetPluginDynamicConfigRequestDTO;
1011
importorg.lowcoder.infra.js.NodeServerClient;
1112
importorg.lowcoder.infra.js.NodeServerHelper;
13+
importorg.lowcoder.sdk.config.CommonConfig;
1214
importorg.lowcoder.sdk.config.CommonConfigHelper;
1315
importorg.lowcoder.sdk.exception.ServerException;
1416
importorg.lowcoder.sdk.models.DatasourceTestResult;
@@ -30,6 +32,8 @@
3032

3133
importstaticorg.lowcoder.sdk.constants.GlobalContext.REQUEST;
3234

35+
importcom.fasterxml.jackson.databind.ObjectMapper;
36+
3337
@Slf4j
3438
@RequiredArgsConstructor
3539
@Component
@@ -45,13 +49,17 @@ public class DatasourcePluginClient implements NodeServerClient {
4549
.build();
4650

4751
privatefinalCommonConfigHelpercommonConfigHelper;
52+
privatefinalCommonConfigcommonConfig;
4853
privatefinalNodeServerHelpernodeServerHelper;
54+
privatefinalEncryptionServiceencryptionService;
4955

5056
privatestaticfinalStringPLUGINS_PATH ="plugins";
5157
privatestaticfinalStringRUN_PLUGIN_QUERY ="runPluginQuery";
5258
privatestaticfinalStringVALIDATE_PLUGIN_DATA_SOURCE_CONFIG ="validatePluginDataSourceConfig";
5359
privatestaticfinalStringGET_PLUGIN_DYNAMIC_CONFIG ="getPluginDynamicConfig";
5460

61+
privatestaticfinalObjectMapperOBJECT_MAPPER =newObjectMapper();
62+
5563
publicMono<List<Object>>getPluginDynamicConfigSafely(List<GetPluginDynamicConfigRequestDTO>getPluginDynamicConfigRequestDTOS) {
5664
returngetPluginDynamicConfig(getPluginDynamicConfigRequestDTOS)
5765
.onErrorResume(throwable -> {
@@ -119,21 +127,47 @@ public Flux<DatasourcePluginDefinition> getDatasourcePluginDefinitions() {
119127
@SuppressWarnings("unchecked")
120128
publicMono<QueryExecutionResult>executeQuery(StringpluginName,ObjectqueryDsl,List<Map<String,Object>>context,ObjectdatasourceConfig) {
121129
returngetAcceptLanguage()
122-
.flatMap(language ->WEB_CLIENT
123-
.post()
124-
.uri(nodeServerHelper.createUri(RUN_PLUGIN_QUERY))
125-
.header(HttpHeaders.ACCEPT_LANGUAGE,language)
126-
.bodyValue(Map.of("pluginName",pluginName,"dsl",queryDsl,"context",context,"dataSourceConfig",datasourceConfig))
127-
.exchangeToMono(response -> {
128-
if (response.statusCode().is2xxSuccessful()) {
129-
returnresponse.bodyToMono(Map.class)
130-
.map(map ->map.get("result"))
131-
.map(QueryExecutionResult::success);
132-
}
133-
returnresponse.bodyToMono(Map.class)
134-
.map(map ->MapUtils.getString(map,"message"))
135-
.map(QueryExecutionResult::errorWithMessage);
136-
}));
130+
.flatMap(language -> {
131+
try {
132+
Map<String,Object>body =Map.of(
133+
"pluginName",pluginName,
134+
"dsl",queryDsl,
135+
"context",context,
136+
"dataSourceConfig",datasourceConfig
137+
);
138+
Stringjson =OBJECT_MAPPER.writeValueAsString(body);
139+
140+
booleanencryptionEnabled = !(commonConfig.getJsExecutor().getPassword().isEmpty() ||commonConfig.getJsExecutor().getSalt().isEmpty());
141+
Stringpayload;
142+
WebClient.RequestBodySpecrequestSpec =WEB_CLIENT
143+
.post()
144+
.uri(nodeServerHelper.createUri(RUN_PLUGIN_QUERY))
145+
.header(HttpHeaders.ACCEPT_LANGUAGE,language);
146+
147+
if (encryptionEnabled) {
148+
payload =encryptionService.encryptStringForNodeServer(json);
149+
requestSpec =requestSpec.header("X-Encrypted","true");
150+
}else {
151+
payload =json;
152+
}
153+
154+
returnrequestSpec
155+
.bodyValue(payload)
156+
.exchangeToMono(response -> {
157+
if (response.statusCode().is2xxSuccessful()) {
158+
returnresponse.bodyToMono(Map.class)
159+
.map(map ->map.get("result"))
160+
.map(QueryExecutionResult::success);
161+
}
162+
returnresponse.bodyToMono(Map.class)
163+
.map(map ->MapUtils.getString(map,"message"))
164+
.map(QueryExecutionResult::errorWithMessage);
165+
});
166+
}catch (Exceptione) {
167+
log.error("Encryption error",e);
168+
returnMono.error(newServerException("Encryption error"));
169+
}
170+
});
137171
}
138172

139173
@SuppressWarnings("unchecked")
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
packageorg.lowcoder.domain.encryption;
2+
3+
importorg.junit.jupiter.api.BeforeEach;
4+
importorg.junit.jupiter.api.Test;
5+
importorg.lowcoder.sdk.config.CommonConfig;
6+
importorg.lowcoder.sdk.config.CommonConfig.Encrypt;
7+
importorg.lowcoder.sdk.config.CommonConfig.JsExecutor;
8+
importorg.springframework.security.crypto.encrypt.Encryptors;
9+
importorg.springframework.security.crypto.encrypt.TextEncryptor;
10+
11+
importstaticorg.junit.jupiter.api.Assertions.*;
12+
importstaticorg.mockito.Mockito.*;
13+
14+
classEncryptionServiceImplTest {
15+
16+
privateEncryptionServiceImplencryptionService;
17+
privateTextEncryptornodeServerEncryptor;
18+
privateStringnodePassword ="nodePassword";
19+
privateStringnodeSalt ="nodeSalt";
20+
21+
@BeforeEach
22+
voidsetUp() {
23+
// Mock CommonConfig and its nested classes
24+
Encryptencrypt =mock(Encrypt.class);
25+
when(encrypt.getPassword()).thenReturn("testPassword");
26+
when(encrypt.getSalt()).thenReturn("testSalt");
27+
28+
JsExecutorjsExecutor =mock(JsExecutor.class);
29+
when(jsExecutor.getPassword()).thenReturn(nodePassword);
30+
when(jsExecutor.getSalt()).thenReturn(nodeSalt);
31+
32+
CommonConfigcommonConfig =mock(CommonConfig.class);
33+
when(commonConfig.getEncrypt()).thenReturn(encrypt);
34+
when(commonConfig.getJsExecutor()).thenReturn(jsExecutor);
35+
36+
encryptionService =newEncryptionServiceImpl(commonConfig);
37+
38+
// For direct comparison in test
39+
StringsaltInHexForNodeServer =org.apache.commons.codec.binary.Hex.encodeHexString(nodeSalt.getBytes());
40+
nodeServerEncryptor =Encryptors.text(nodePassword,saltInHexForNodeServer);
41+
}
42+
43+
@Test
44+
voidtestEncryptStringForNodeServer_NullInput() {
45+
assertNull(encryptionService.encryptStringForNodeServer(null));
46+
}
47+
48+
@Test
49+
voidtestEncryptStringForNodeServer_EmptyInput() {
50+
assertEquals("",encryptionService.encryptStringForNodeServer(""));
51+
}
52+
53+
@Test
54+
voidtestEncryptStringForNodeServer_EncryptsAndDecryptsCorrectly() {
55+
Stringplain ="node secret";
56+
Stringencrypted =encryptionService.encryptStringForNodeServer(plain);
57+
assertNotNull(encrypted);
58+
assertNotEquals(plain,encrypted);
59+
60+
// Decrypt using the same encryptor to verify correctness
61+
Stringdecrypted =nodeServerEncryptor.decrypt(encrypted);
62+
assertEquals(plain,decrypted);
63+
}
64+
65+
@Test
66+
voidtestEncryptStringForNodeServer_DifferentInputsProduceDifferentOutputs() {
67+
Stringencrypted1 =encryptionService.encryptStringForNodeServer("abc");
68+
Stringencrypted2 =encryptionService.encryptStringForNodeServer("def");
69+
assertNotEquals(encrypted1,encrypted2);
70+
}
71+
72+
@Test
73+
voidtestEncryptStringForNodeServer_SameInputProducesDifferentOutputs() {
74+
Stringinput ="repeat";
75+
Stringencrypted1 =encryptionService.encryptStringForNodeServer(input);
76+
Stringencrypted2 =encryptionService.encryptStringForNodeServer(input);
77+
// Spring's Encryptors.text uses random IV, so outputs should differ
78+
assertNotEquals(encrypted1,encrypted2);
79+
}
80+
}

‎server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/config/CommonConfig.java‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,9 @@ public long getMaxAgeInSeconds() {
147147
@Data
148148
publicstaticclassJsExecutor {
149149
privateStringhost;
150+
privateStringpassword;
151+
privateStringsalt;
152+
privatebooleanisEncrypted;
150153
}
151154

152155
@Data

‎server/api-service/lowcoder-server/src/main/resources/application-debug.yaml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ common:
3737
cookie-name:LOWCODER_DEBUG_TOKEN
3838
js-executor:
3939
host:"http://127.0.0.1:6060"
40+
password:${LOWCODER_NODE_SERVICE_SECRET:}
41+
salt:${LOWCODER_NODE_SERVICE_SECRET_SALT:}
4042
workspace:
4143
mode:${LOWCODER_WORKSPACE_MODE:SAAS}
4244
plugin-dirs:

‎server/api-service/lowcoder-server/src/main/resources/application.yaml‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ common:
7474
corsAllowedDomainString:${LOWCODER_CORS_DOMAINS:*}
7575
js-executor:
7676
host:${LOWCODER_NODE_SERVICE_URL:http://127.0.0.1:6060}
77+
password:${LOWCODER_NODE_SERVICE_SECRET:}
78+
salt:${LOWCODER_NODE_SERVICE_SECRET_SALT:}
7779
max-query-request-size:${LOWCODER_MAX_REQUEST_SIZE:20m}
7880
max-query-response-size:${LOWCODER_MAX_REQUEST_SIZE:20m}
7981
max-upload-size:${LOWCODER_MAX_REQUEST_SIZE:20m}
@@ -129,4 +131,4 @@ management:
129131
redis:
130132
enabled:true
131133
diskspace:
132-
enabled:false
134+
enabled:false

‎server/node-service/src/controllers/plugins.ts‎

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,23 @@ import { Request, Response } from "express";
33
import_from"lodash";
44
import{Config}from"lowcoder-sdk/dataSource";
55
import*aspluginServicesfrom"../services/plugin";
6+
// Add import for decryption utility
7+
import{decryptString}from"../utils/encryption";// <-- implement this utility as needed
8+
9+
asyncfunctiongetDecryptedBody(req:Request):Promise<any>{
10+
if(req.headers["x-encrypted"]){
11+
// Assume body is a raw encrypted string, decrypt and parse as JSON
12+
constencrypted=typeofreq.body==="string" ?req.body :req.body?.toString?.();
13+
if(!encrypted)throwbadRequest("Missing encrypted body");
14+
constdecrypted=awaitdecryptString(encrypted);
15+
try{
16+
returnJSON.parse(decrypted);
17+
}catch(e){
18+
throwbadRequest("Failed to parse decrypted body as JSON");
19+
}
20+
}
21+
returnreq.body;
22+
}
623

724
exportasyncfunctionlistPlugins(req:Request,res:Response){
825
letids=req.query["id"]||[];
@@ -15,12 +32,10 @@ export async function listPlugins(req: Request, res: Response) {
1532
}
1633

1734
exportasyncfunctionrunPluginQuery(req:Request,res:Response){
18-
const{ pluginName, dsl, context, dataSourceConfig}=req.body;
35+
constbody=awaitgetDecryptedBody(req);
36+
const{ pluginName, dsl, context, dataSourceConfig}=body;
1937
constctx=pluginServices.getPluginContext(req);
2038

21-
22-
// console.log("pluginName: ", pluginName, "dsl: ", dsl, "context: ", context, "dataSourceConfig: ", dataSourceConfig, "ctx: ", ctx);
23-
2439
constresult=awaitpluginServices.runPluginQuery(
2540
pluginName,
2641
dsl,
@@ -32,7 +47,8 @@ export async function runPluginQuery(req: Request, res: Response) {
3247
}
3348

3449
exportasyncfunctionvalidatePluginDataSourceConfig(req:Request,res:Response){
35-
const{ pluginName, dataSourceConfig}=req.body;
50+
constbody=awaitgetDecryptedBody(req);
51+
const{ pluginName, dataSourceConfig}=body;
3652
constctx=pluginServices.getPluginContext(req);
3753
constresult=awaitpluginServices.validatePluginDataSourceConfig(
3854
pluginName,
@@ -50,10 +66,11 @@ type GetDynamicDefReqBody = {
5066

5167
exportasyncfunctiongetDynamicDef(req:Request,res:Response){
5268
constctx=pluginServices.getPluginContext(req);
53-
if(!Array.isArray(req.body)){
69+
constbody=awaitgetDecryptedBody(req);
70+
if(!Array.isArray(body)){
5471
throwbadRequest("request body is not a valid array");
5572
}
56-
constfields=req.bodyasGetDynamicDefReqBody;
73+
constfields=bodyasGetDynamicDefReqBody;
5774
constresult:Config[]=[];
5875
for(constitemoffields){
5976
constdef=awaitpluginServices.getDynamicConfigDef(

‎server/node-service/src/server.ts‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { collectDefaultMetrics } from "prom-client";
99
importapiRouterfrom"./routes/apiRouter";
1010
importsystemRouterfrom"./routes/systemRouter";
1111
importcors,{CorsOptions}from"cors";
12+
importbodyParserfrom"body-parser";
1213
collectDefaultMetrics();
1314

1415
constprefix="/node-service";
@@ -32,6 +33,15 @@ router.use(morgan("dev"));
3233
/** Parse the request */
3334
router.use(express.urlencoded({extended:false}));
3435

36+
/** Custom middleware: use raw body for encrypted requests */
37+
router.use((req,res,next)=>{
38+
if(req.headers["x-encrypted"]){
39+
bodyParser.text({type:"*/*"})(req,res,next);
40+
}else{
41+
bodyParser.json()(req,res,next);
42+
}
43+
});
44+
3545
/** Takes care of JSON data */
3646
router.use(
3747
express.json({

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp