union-ark-web 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PROJECT_OVERVIEW.md +405 -0
- package/README.md +177 -0
- package/ark-control/pom.xml +142 -0
- package/ark-control/src/local-mock/java/com/epcc/commons/securityproxy/api/SecurityResult.java +14 -0
- package/ark-control/src/local-mock/java/com/epcc/commons/securityproxy/api/SymmetricalSecurityService.java +11 -0
- package/ark-control/src/local-mock/java/com/epcc/dubbo/result/Result.java +29 -0
- package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/annotation/EnDecryptField.java +16 -0
- package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/annotation/EnDecryptFieldLong.java +17 -0
- package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/annotation/EnDecryptFieldWithTag.java +16 -0
- package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/exception/BaseDataErrorCode.java +26 -0
- package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/exception/CheckException.java +33 -0
- package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/exception/ErrorCode.java +9 -0
- package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/redis/RedisCacheService.java +131 -0
- package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/util/Constant.java +24 -0
- package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/util/ResultUtil.java +9 -0
- package/ark-control/src/local-mock/java/com/union/control/local/sensitive/LocalMockSensitiveProxy.java +40 -0
- package/ark-control/src/local-mock/java/com/union/control/local/sensitive/LocalRedisCacheService.java +45 -0
- package/ark-control/src/local-mock/java/com/union/control/local/sensitive/LocalSensitiveHostConfiguration.java +27 -0
- package/ark-control/src/local-mock/java/com/union/control/local/sensitive/LocalSensitiveProxyConfiguration.java +16 -0
- package/ark-control/src/main/java/com/union/control/ArkControlApplication.java +17 -0
- package/ark-control/src/main/java/com/union/control/dubbo/ProviderAccessLogFilter.java +45 -0
- package/ark-control/src/main/java/com/union/control/mapper/AgentExecutionMapper.java +35 -0
- package/ark-control/src/main/java/com/union/control/mapper/ConversationMapper.java +44 -0
- package/ark-control/src/main/java/com/union/control/mapper/MemoryStoreMapper.java +41 -0
- package/ark-control/src/main/java/com/union/control/mapper/ScheduledTaskMapper.java +96 -0
- package/ark-control/src/main/java/com/union/control/mapper/SensitiveAddressBookDemo.java +31 -0
- package/ark-control/src/main/java/com/union/control/mapper/SensitiveDataDemoMapper.java +24 -0
- package/ark-control/src/main/java/com/union/control/mapper/interceptor/AESInterceptor.java +265 -0
- package/ark-control/src/main/java/com/union/control/mapper/interceptor/AddressBookHandler.java +105 -0
- package/ark-control/src/main/java/com/union/control/schedule/ScheduledExecutionToken.java +55 -0
- package/ark-control/src/main/java/com/union/control/schedule/ScheduledTaskScheduler.java +206 -0
- package/ark-control/src/main/java/com/union/control/service/impl/AgentExecutionServiceImpl.java +212 -0
- package/ark-control/src/main/java/com/union/control/service/impl/AgentProxyServiceImpl.java +143 -0
- package/ark-control/src/main/java/com/union/control/service/impl/ConversationServiceImpl.java +245 -0
- package/ark-control/src/main/java/com/union/control/service/impl/MemoryStoreServiceImpl.java +252 -0
- package/ark-control/src/main/java/com/union/control/service/impl/RunningAnalysisMockServiceImpl.java +301 -0
- package/ark-control/src/main/java/com/union/control/service/impl/ScheduledTaskServiceImpl.java +576 -0
- package/ark-control/src/main/java/com/union/control/service/impl/SensitiveDataDemoServiceImpl.java +144 -0
- package/ark-control/src/main/java/com/union/control/service/sensitive/RedisRevealTokenStore.java +90 -0
- package/ark-control/src/main/java/com/union/control/service/sensitive/SensitiveRevealProcessor.java +319 -0
- package/ark-control/src/main/java/com/union/control/service/sensitive/SensitiveRevealServiceImpl.java +72 -0
- package/ark-control/src/main/java/com/union/control/utils/AgentSupport.java +103 -0
- package/ark-control/src/main/java/com/union/control/utils/security/SymmetricalSecurityUtils.java +160 -0
- package/ark-control/src/main/resources/META-INF/dubbo/com.alibaba.dubbo.rpc.Filter +1 -0
- package/ark-control/src/main/resources/application.properties +4 -0
- package/ark-control/src/main/resources/application.yml +32 -0
- package/ark-control/src/main/resources/dubbo-provider.xml +20 -0
- package/ark-control/src/main/resources/mapper/AgentExecutionMapper.xml +75 -0
- package/ark-control/src/main/resources/mapper/ConversationMapper.xml +110 -0
- package/ark-control/src/main/resources/mapper/MemoryStoreMapper.xml +74 -0
- package/ark-control/src/main/resources/mapper/ScheduledTaskMapper.xml +286 -0
- package/ark-control/src/main/resources/mapper/SensitiveDataDemoMapper.xml +55 -0
- package/ark-control/src/main/resources/schema.sql +146 -0
- package/ark-control/src/test/java/com/union/control/AgentExecutionServiceTest.java +170 -0
- package/ark-control/src/test/java/com/union/control/ArkControlStructureContractTest.java +107 -0
- package/ark-control/src/test/java/com/union/control/ConversationServiceTest.java +89 -0
- package/ark-control/src/test/java/com/union/control/MemoryStoreServiceTest.java +26 -0
- package/ark-control/src/test/java/com/union/control/PydanticAiControlContractTest.java +52 -0
- package/ark-control/src/test/java/com/union/control/TestJson.java +45 -0
- package/ark-control/src/test/java/com/union/control/dubbo/ProviderAccessLogFilterTest.java +53 -0
- package/ark-control/src/test/java/com/union/control/local/sensitive/LocalSensitiveProxyConfigurationTest.java +23 -0
- package/ark-control/src/test/java/com/union/control/mapper/interceptor/AESInterceptorCompatibilityTest.java +505 -0
- package/ark-control/src/test/java/com/union/control/schedule/ScheduledExecutionTokenTest.java +27 -0
- package/ark-control/src/test/java/com/union/control/schedule/ScheduledTaskSchedulerTest.java +51 -0
- package/ark-control/src/test/java/com/union/control/service/AgentProxyServiceTest.java +111 -0
- package/ark-control/src/test/java/com/union/control/service/ScheduledTaskServiceTest.java +483 -0
- package/ark-control/src/test/java/com/union/control/service/impl/SensitiveDataDemoServiceImplTest.java +104 -0
- package/ark-control/src/test/java/com/union/control/service/sensitive/RedisRevealTokenStoreTest.java +65 -0
- package/ark-control/src/test/java/com/union/control/service/sensitive/SensitiveRevealBoundaryTest.java +99 -0
- package/ark-control/src/test/java/com/union/control/service/sensitive/SensitiveRevealProcessorCodecTest.java +111 -0
- package/ark-control/src/test/java/com/union/control/service/sensitive/SensitiveRevealProcessorTest.java +111 -0
- package/ark-control/src/test/java/com/union/control/utils/security/SymmetricalSecurityUtilsCompatibilityTest.java +87 -0
- package/ark-control-facade/pom.xml +16 -0
- package/ark-control-facade/src/main/java/com/union/control/service/AgentExecutionService.java +8 -0
- package/ark-control-facade/src/main/java/com/union/control/service/AgentProxyService.java +7 -0
- package/ark-control-facade/src/main/java/com/union/control/service/AgentResponse.java +22 -0
- package/ark-control-facade/src/main/java/com/union/control/service/ConversationService.java +12 -0
- package/ark-control-facade/src/main/java/com/union/control/service/MemoryStoreService.java +12 -0
- package/ark-control-facade/src/main/java/com/union/control/service/RunningAnalysisMockService.java +10 -0
- package/ark-control-facade/src/main/java/com/union/control/service/ScheduledTaskService.java +40 -0
- package/ark-control-facade/src/main/java/com/union/control/service/SensitiveDataDemoService.java +13 -0
- package/ark-control-facade/src/main/java/com/union/control/service/sensitive/SensitiveRevealService.java +18 -0
- package/ark-web/README.md +80 -0
- package/ark-web/pom.xml +105 -0
- package/ark-web/src/local-mock/java/com/epcc/arkweb/mock/ArkAuthServiceMock.java +105 -0
- package/ark-web/src/local-mock/java/com/epcc/arkweb/mock/LocalKaptchaConfiguration.java +26 -0
- package/ark-web/src/main/java/com/epcc/arkweb/Application.java +13 -0
- package/ark-web/src/main/java/com/epcc/arkweb/config/InterceptorConfig.java +20 -0
- package/ark-web/src/main/java/com/epcc/arkweb/config/Realm.java +64 -0
- package/ark-web/src/main/java/com/epcc/arkweb/config/ShiroConfig.java +129 -0
- package/ark-web/src/main/java/com/epcc/arkweb/filter/LoginFormFilter.java +43 -0
- package/ark-web/src/main/java/com/epcc/arkweb/helper/AuthContextHolder.java +39 -0
- package/ark-web/src/main/java/com/epcc/arkweb/helper/AuthenticatedRequest.java +91 -0
- package/ark-web/src/main/java/com/epcc/arkweb/model/ShiroUser.java +21 -0
- package/ark-web/src/main/java/com/epcc/arkweb/utils/ResultMsg.java +19 -0
- package/ark-web/src/main/java/com/epcc/arkweb/vo/llm/ScheduledTaskCommandVO.java +30 -0
- package/ark-web/src/main/java/com/epcc/arkweb/vo/llm/ScheduledTaskQueryVO.java +20 -0
- package/ark-web/src/main/java/com/epcc/arkweb/web/CommonController.java +25 -0
- package/ark-web/src/main/java/com/epcc/arkweb/web/llm/AgentAuthorizationInterceptor.java +229 -0
- package/ark-web/src/main/java/com/epcc/arkweb/web/llm/AgentController.java +165 -0
- package/ark-web/src/main/java/com/epcc/arkweb/web/llm/AgentPermission.java +13 -0
- package/ark-web/src/main/java/com/epcc/arkweb/web/llm/LlmController.java +209 -0
- package/ark-web/src/main/java/com/epcc/arkweb/web/llm/ScheduledTaskController.java +160 -0
- package/ark-web/src/main/java/com/epcc/arkweb/web/sensitive/reveal/SensitiveCaptchaService.java +146 -0
- package/ark-web/src/main/java/com/epcc/arkweb/web/sensitive/reveal/SensitiveRevealController.java +163 -0
- package/ark-web/src/main/resources/application.properties +1 -0
- package/ark-web/src/main/resources/application.yml +23 -0
- package/ark-web/src/main/resources/dubbo-consumer.xml +19 -0
- package/ark-web/src/production-overlay/java/com/epcc/arkweb/config/CasShiroConfig.java +229 -0
- package/ark-web/src/production-overlay/java/com/epcc/arkweb/config/InterceptorConfig.java +35 -0
- package/ark-web/src/production-overlay/java/com/epcc/arkweb/config/ShiroConfig.java +161 -0
- package/ark-web/src/test/java/com/epcc/arkweb/ArkWebStructureContractTest.java +290 -0
- package/ark-web/src/test/java/com/epcc/arkweb/ShiroTestSupport.java +32 -0
- package/ark-web/src/test/java/com/epcc/arkweb/config/LocalShiroIntegrationTest.java +121 -0
- package/ark-web/src/test/java/com/epcc/arkweb/helper/AuthenticatedRequestTest.java +131 -0
- package/ark-web/src/test/java/com/epcc/arkweb/local/LocalArkAuthServiceMockTest.java +17 -0
- package/ark-web/src/test/java/com/epcc/arkweb/web/llm/AgentAuthorizationInterceptorTest.java +134 -0
- package/ark-web/src/test/java/com/epcc/arkweb/web/llm/AgentControllerAuthorizationTest.java +97 -0
- package/ark-web/src/test/java/com/epcc/arkweb/web/llm/LlmControllerTest.java +113 -0
- package/ark-web/src/test/java/com/epcc/arkweb/web/sensitive/reveal/SensitiveRevealControllerTest.java +273 -0
- package/package.json +18 -0
- package/pom.xml +16 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
package com.epcc.arkweb.web.llm;
|
|
2
|
+
|
|
3
|
+
import com.union.control.service.AgentProxyService;
|
|
4
|
+
import com.union.control.service.AgentResponse;
|
|
5
|
+
import com.union.control.service.AgentExecutionService;
|
|
6
|
+
import com.union.control.service.ConversationService;
|
|
7
|
+
import com.epcc.arkweb.helper.AuthenticatedRequest;
|
|
8
|
+
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
|
9
|
+
|
|
10
|
+
import org.springframework.beans.factory.annotation.Autowired;
|
|
11
|
+
import org.springframework.beans.factory.annotation.Value;
|
|
12
|
+
import org.springframework.http.ResponseEntity;
|
|
13
|
+
import org.springframework.http.HttpStatus;
|
|
14
|
+
import org.springframework.http.HttpMethod;
|
|
15
|
+
import org.springframework.http.MediaType;
|
|
16
|
+
import org.springframework.http.client.ClientHttpResponse;
|
|
17
|
+
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
|
18
|
+
import org.springframework.stereotype.Controller;
|
|
19
|
+
import org.springframework.web.client.ResponseErrorHandler;
|
|
20
|
+
import org.springframework.web.client.RestTemplate;
|
|
21
|
+
import org.springframework.web.bind.annotation.GetMapping;
|
|
22
|
+
import org.springframework.web.bind.annotation.PostMapping;
|
|
23
|
+
import org.springframework.web.bind.annotation.RequestBody;
|
|
24
|
+
import org.springframework.web.bind.annotation.RequestParam;
|
|
25
|
+
import org.springframework.web.bind.annotation.RequestHeader;
|
|
26
|
+
import org.springframework.web.bind.annotation.RequestMapping;
|
|
27
|
+
import org.springframework.http.HttpHeaders;
|
|
28
|
+
import org.springframework.web.bind.annotation.ResponseBody;
|
|
29
|
+
|
|
30
|
+
import javax.servlet.http.HttpServletResponse;
|
|
31
|
+
import java.io.IOException;
|
|
32
|
+
import java.io.InputStream;
|
|
33
|
+
import java.nio.charset.StandardCharsets;
|
|
34
|
+
import java.util.Map;
|
|
35
|
+
import java.util.concurrent.atomic.AtomicBoolean;
|
|
36
|
+
|
|
37
|
+
/** Browser-facing LLM API. Only Agent runs cross the Python boundary. */
|
|
38
|
+
@Controller
|
|
39
|
+
@RequestMapping(value = {"llm", "union-op/llm"})
|
|
40
|
+
public class LlmController {
|
|
41
|
+
private final ConversationService conversations;
|
|
42
|
+
private final AgentExecutionService executions;
|
|
43
|
+
private final AgentProxyService gateway;
|
|
44
|
+
private final AuthenticatedRequest request;
|
|
45
|
+
private final String pyAppBaseUrl;
|
|
46
|
+
private final RestTemplate http;
|
|
47
|
+
|
|
48
|
+
@Autowired
|
|
49
|
+
public LlmController(
|
|
50
|
+
ConversationService conversations,
|
|
51
|
+
AgentExecutionService executions,
|
|
52
|
+
AgentProxyService gateway,
|
|
53
|
+
AuthenticatedRequest request,
|
|
54
|
+
@Value("${agent.py-app-base-url}") String pyAppBaseUrl,
|
|
55
|
+
@Value("${AGENT_MAX_RUN_SECONDS:900}") double maxRunSeconds) {
|
|
56
|
+
this(conversations, executions, gateway, request, pyAppBaseUrl, new RestTemplate());
|
|
57
|
+
double millis = Math.ceil(maxRunSeconds * 1000);
|
|
58
|
+
if (!Double.isFinite(millis) || millis < 1 || millis > Integer.MAX_VALUE)
|
|
59
|
+
throw new IllegalArgumentException("AGENT_MAX_RUN_SECONDS is out of range");
|
|
60
|
+
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
|
|
61
|
+
factory.setConnectTimeout((int) millis);
|
|
62
|
+
factory.setReadTimeout((int) millis);
|
|
63
|
+
http.setRequestFactory(factory);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
LlmController(
|
|
67
|
+
ConversationService conversations,
|
|
68
|
+
AgentExecutionService executions,
|
|
69
|
+
AgentProxyService gateway,
|
|
70
|
+
AuthenticatedRequest request,
|
|
71
|
+
String pyAppBaseUrl,
|
|
72
|
+
RestTemplate http) {
|
|
73
|
+
this.conversations = conversations;
|
|
74
|
+
this.executions = executions;
|
|
75
|
+
this.gateway = gateway;
|
|
76
|
+
this.request = request;
|
|
77
|
+
this.pyAppBaseUrl = pyAppBaseUrl.replaceAll("/+$", "");
|
|
78
|
+
this.http = http;
|
|
79
|
+
this.http.setErrorHandler(new ResponseErrorHandler() {
|
|
80
|
+
@Override
|
|
81
|
+
public boolean hasError(ClientHttpResponse response) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@Override
|
|
86
|
+
public void handleError(ClientHttpResponse response) {}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@GetMapping("/conversationList")
|
|
91
|
+
@RequiresPermissions(value = "/assistantManager/page")
|
|
92
|
+
@ResponseBody
|
|
93
|
+
public Map<String, Object> conversationList(
|
|
94
|
+
@RequestParam(required = false, defaultValue = "100") int limit) {
|
|
95
|
+
return conversations.conversations(request.json("limit", limit));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@GetMapping("/conversationDetails")
|
|
99
|
+
@RequiresPermissions(value = "/assistantManager/page")
|
|
100
|
+
@ResponseBody
|
|
101
|
+
public Map<String, Object> conversationDetails(
|
|
102
|
+
@RequestParam String conversationId) {
|
|
103
|
+
return conversations.conversation(request.json("conversationId", conversationId));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@PostMapping("/conversationTitle")
|
|
107
|
+
@RequiresPermissions(value = "/assistantManager/page")
|
|
108
|
+
@ResponseBody
|
|
109
|
+
public Map<String, Object> conversationTitle(
|
|
110
|
+
@RequestBody Map<String, Object> payload) {
|
|
111
|
+
return conversations.rename(request.json(payload));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
@PostMapping("/conversationDelete")
|
|
115
|
+
@RequiresPermissions(value = "/assistantManager/page")
|
|
116
|
+
@ResponseBody
|
|
117
|
+
public Map<String, Object> conversationDelete(
|
|
118
|
+
@RequestBody Map<String, Object> payload) {
|
|
119
|
+
return conversations.deleteConversation(request.json(payload));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@PostMapping("/executionCancel")
|
|
123
|
+
@RequiresPermissions(value = "/assistantManager/page")
|
|
124
|
+
@ResponseBody
|
|
125
|
+
public ResponseEntity<String> executionCancel(
|
|
126
|
+
@RequestHeader(value = HttpHeaders.COOKIE, required = false) String cookie,
|
|
127
|
+
@RequestBody Map<String, Object> payload) {
|
|
128
|
+
Object conversationId = payload.get("conversationId");
|
|
129
|
+
Object runId = payload.get("runId");
|
|
130
|
+
if (!(conversationId instanceof String) || !(runId instanceof String))
|
|
131
|
+
throw new IllegalArgumentException("缺少 execution 标识");
|
|
132
|
+
return response(gateway.cancel(cookie, (String) conversationId, (String) runId));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
@PostMapping(value = "/chatMessage", produces = "text/event-stream")
|
|
136
|
+
@RequiresPermissions(value = "/assistantManager/page")
|
|
137
|
+
public void chatMessage(
|
|
138
|
+
@RequestHeader(value = HttpHeaders.COOKIE, required = false) String cookie,
|
|
139
|
+
@RequestBody byte[] payload, HttpServletResponse response) {
|
|
140
|
+
byte[] clientPayload = request.clientPayload(payload);
|
|
141
|
+
Map<String, Object> claimed = executions.claimAguiRun(request.json(clientPayload));
|
|
142
|
+
@SuppressWarnings("unchecked")
|
|
143
|
+
Map<String, Object> execution = (Map<String, Object>) claimed.get("data");
|
|
144
|
+
String conversationId = String.valueOf(execution.get("conversationId"));
|
|
145
|
+
String runId = String.valueOf(execution.get("runId"));
|
|
146
|
+
AtomicBoolean disconnected = new AtomicBoolean();
|
|
147
|
+
try {
|
|
148
|
+
http.execute(pyAppBaseUrl + "/agent/v1/runs", HttpMethod.POST,
|
|
149
|
+
upstreamRequest -> {
|
|
150
|
+
upstreamRequest.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
|
151
|
+
upstreamRequest.getHeaders().setAccept(
|
|
152
|
+
java.util.Collections.singletonList(MediaType.TEXT_EVENT_STREAM));
|
|
153
|
+
upstreamRequest.getHeaders().set(HttpHeaders.COOKIE, cookie);
|
|
154
|
+
upstreamRequest.getBody().write(clientPayload);
|
|
155
|
+
}, upstream -> {
|
|
156
|
+
response.setStatus(upstream.getRawStatusCode());
|
|
157
|
+
copyHeader(upstream, response, HttpHeaders.CONTENT_TYPE);
|
|
158
|
+
copyHeader(upstream, response, HttpHeaders.CACHE_CONTROL);
|
|
159
|
+
response.setHeader("X-Accel-Buffering", "no");
|
|
160
|
+
InputStream input = upstream.getBody();
|
|
161
|
+
byte[] buffer = new byte[4096];
|
|
162
|
+
int read;
|
|
163
|
+
while ((read = input.read(buffer)) != -1) {
|
|
164
|
+
try {
|
|
165
|
+
response.getOutputStream().write(buffer, 0, read);
|
|
166
|
+
response.getOutputStream().flush();
|
|
167
|
+
} catch (IOException error) {
|
|
168
|
+
disconnected.set(true);
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
response.flushBuffer();
|
|
174
|
+
} catch (IOException error) {
|
|
175
|
+
disconnected.set(true);
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
);
|
|
181
|
+
} catch (RuntimeException error) {
|
|
182
|
+
if (!disconnected.get()) throw error;
|
|
183
|
+
gateway.cancel(cookie, conversationId, runId);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
@PostMapping("/chatMessageSync")
|
|
188
|
+
@RequiresPermissions(value = "/assistantManager/page")
|
|
189
|
+
@ResponseBody
|
|
190
|
+
public ResponseEntity<String> chatMessageSync(
|
|
191
|
+
@RequestHeader(value = HttpHeaders.COOKIE, required = false) String cookie,
|
|
192
|
+
@RequestBody byte[] payload) {
|
|
193
|
+
return response(gateway.sync(cookie,
|
|
194
|
+
new String(request.clientPayload(payload), StandardCharsets.UTF_8)));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private static ResponseEntity<String> response(AgentResponse response) {
|
|
198
|
+
return ResponseEntity.status(HttpStatus.valueOf(response.getStatus()))
|
|
199
|
+
.contentType(MediaType.APPLICATION_JSON)
|
|
200
|
+
.body(response.getBody());
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private static void copyHeader(
|
|
204
|
+
ClientHttpResponse upstream, HttpServletResponse response, String name) {
|
|
205
|
+
String value = upstream.getHeaders().getFirst(name);
|
|
206
|
+
if (value != null) response.setHeader(name, value);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
package com.epcc.arkweb.web.llm;
|
|
2
|
+
|
|
3
|
+
import com.epcc.arkweb.helper.AuthenticatedRequest;
|
|
4
|
+
import com.union.control.service.ScheduledTaskService;
|
|
5
|
+
import com.epcc.arkweb.utils.ResultMsg;
|
|
6
|
+
import com.epcc.arkweb.vo.llm.ScheduledTaskCommandVO;
|
|
7
|
+
import com.epcc.arkweb.vo.llm.ScheduledTaskQueryVO;
|
|
8
|
+
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
|
9
|
+
import org.springframework.beans.factory.annotation.Autowired;
|
|
10
|
+
import org.springframework.stereotype.Controller;
|
|
11
|
+
import org.springframework.web.bind.annotation.RequestBody;
|
|
12
|
+
import org.springframework.web.bind.annotation.RequestMapping;
|
|
13
|
+
import org.springframework.web.bind.annotation.RequestMethod;
|
|
14
|
+
import org.springframework.web.bind.annotation.RequestParam;
|
|
15
|
+
import org.springframework.web.bind.annotation.ResponseBody;
|
|
16
|
+
|
|
17
|
+
import java.util.LinkedHashMap;
|
|
18
|
+
import java.util.Map;
|
|
19
|
+
|
|
20
|
+
@Controller
|
|
21
|
+
@RequestMapping(value = {"llm", "union-op/llm"})
|
|
22
|
+
public class ScheduledTaskController {
|
|
23
|
+
|
|
24
|
+
@Autowired
|
|
25
|
+
private ScheduledTaskService scheduledTaskService;
|
|
26
|
+
|
|
27
|
+
@Autowired
|
|
28
|
+
private AuthenticatedRequest request;
|
|
29
|
+
|
|
30
|
+
@RequestMapping(value = "/scheduledTaskCreate", method = RequestMethod.POST)
|
|
31
|
+
@RequiresPermissions(value = "/assistantManager/page")
|
|
32
|
+
@ResponseBody
|
|
33
|
+
public ResultMsg create(@RequestBody ScheduledTaskCommandVO command) {
|
|
34
|
+
return response(scheduledTaskService.create(request.json(command(command))));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@RequestMapping(value = "/scheduledTaskUpdate", method = RequestMethod.POST)
|
|
38
|
+
@RequiresPermissions(value = "/assistantManager/page")
|
|
39
|
+
@ResponseBody
|
|
40
|
+
public ResultMsg update(@RequestBody ScheduledTaskCommandVO command) {
|
|
41
|
+
return response(scheduledTaskService.update(request.json(command(command))));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@RequestMapping(value = "/scheduledTaskList", method = {RequestMethod.GET, RequestMethod.POST})
|
|
45
|
+
@RequiresPermissions(value = "/assistantManager/page")
|
|
46
|
+
@ResponseBody
|
|
47
|
+
public ResultMsg list(ScheduledTaskQueryVO query) {
|
|
48
|
+
return response(scheduledTaskService.list(request.json(
|
|
49
|
+
"keyword", query.getKeyword(), "status", query.getStatus(),
|
|
50
|
+
"page", page(query.getPage()), "pageSize", pageSize(query.getPageSize()))));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@RequestMapping(value = "/scheduledTaskDetail", method = RequestMethod.GET)
|
|
54
|
+
@RequiresPermissions(value = "/assistantManager/page")
|
|
55
|
+
@ResponseBody
|
|
56
|
+
public ResultMsg detail(@RequestParam Long taskId) {
|
|
57
|
+
return response(scheduledTaskService.detail(request.json(
|
|
58
|
+
"taskId", positive(taskId, "taskId"))));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@RequestMapping(value = "/scheduledTaskRunList", method = {RequestMethod.GET, RequestMethod.POST})
|
|
62
|
+
@RequiresPermissions(value = "/assistantManager/page")
|
|
63
|
+
@ResponseBody
|
|
64
|
+
public ResultMsg runList(ScheduledTaskQueryVO query) {
|
|
65
|
+
query.setTaskId(positive(query.getTaskId(), "taskId"));
|
|
66
|
+
return response(scheduledTaskService.runs(request.json(
|
|
67
|
+
"taskId", query.getTaskId(), "page", page(query.getPage()),
|
|
68
|
+
"pageSize", pageSize(query.getPageSize()))));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@RequestMapping(value = "/scheduledTaskRunDetail", method = RequestMethod.GET)
|
|
72
|
+
@RequiresPermissions(value = "/assistantManager/page")
|
|
73
|
+
@ResponseBody
|
|
74
|
+
public ResultMsg runDetail(@RequestParam Long runId) {
|
|
75
|
+
return response(scheduledTaskService.runDetail(request.json(
|
|
76
|
+
"runId", positive(runId, "runId"))));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@RequestMapping(value = "/scheduledTaskUnread", method = RequestMethod.GET)
|
|
80
|
+
@RequiresPermissions(value = "/assistantManager/page")
|
|
81
|
+
@ResponseBody
|
|
82
|
+
public ResultMsg unread() {
|
|
83
|
+
return response(scheduledTaskService.unread(request.json()));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@RequestMapping(value = "/scheduledTaskStart", method = RequestMethod.POST)
|
|
87
|
+
@RequiresPermissions(value = "/assistantManager/page")
|
|
88
|
+
@ResponseBody
|
|
89
|
+
public ResultMsg start(@RequestBody IdCommand command) {
|
|
90
|
+
return response(scheduledTaskService.start(request.json(
|
|
91
|
+
"taskId", positive(command.getTaskId(), "taskId"))));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@RequestMapping(value = "/scheduledTaskPause", method = RequestMethod.POST)
|
|
95
|
+
@RequiresPermissions(value = "/assistantManager/page")
|
|
96
|
+
@ResponseBody
|
|
97
|
+
public ResultMsg pause(@RequestBody IdCommand command) {
|
|
98
|
+
return response(scheduledTaskService.pause(request.json(
|
|
99
|
+
"taskId", positive(command.getTaskId(), "taskId"))));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@RequestMapping(value = "/scheduledTaskDiscard", method = RequestMethod.POST)
|
|
103
|
+
@RequiresPermissions(value = "/assistantManager/page")
|
|
104
|
+
@ResponseBody
|
|
105
|
+
public ResultMsg discard(@RequestBody IdCommand command) {
|
|
106
|
+
return response(scheduledTaskService.discard(request.json(
|
|
107
|
+
"taskId", positive(command.getTaskId(), "taskId"))));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@RequestMapping(value = "/scheduledTaskRunOpen", method = RequestMethod.POST)
|
|
111
|
+
@RequiresPermissions(value = "/assistantManager/page")
|
|
112
|
+
@ResponseBody
|
|
113
|
+
public ResultMsg open(@RequestBody IdCommand command) {
|
|
114
|
+
return response(scheduledTaskService.open(request.json(
|
|
115
|
+
"runId", positive(command.getRunId(), "runId"))));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private static Long positive(Long value, String name) {
|
|
119
|
+
if (value == null || value <= 0L) {
|
|
120
|
+
throw new IllegalArgumentException(name + " 非法");
|
|
121
|
+
}
|
|
122
|
+
return value;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private static int page(Integer value) { return value == null ? 1 : value; }
|
|
126
|
+
private static int pageSize(Integer value) { return value == null ? 20 : value; }
|
|
127
|
+
|
|
128
|
+
private static Map<String, Object> command(ScheduledTaskCommandVO value) {
|
|
129
|
+
if (value == null) throw new IllegalArgumentException("请求参数非法");
|
|
130
|
+
Map<String, Object> command = new LinkedHashMap<>();
|
|
131
|
+
command.put("taskId", value.getTaskId());
|
|
132
|
+
command.put("title", value.getTitle());
|
|
133
|
+
command.put("prompt", value.getPrompt());
|
|
134
|
+
command.put("scheduleType", value.getScheduleType());
|
|
135
|
+
command.put("runAt", value.getRunAt());
|
|
136
|
+
command.put("cronExpression", value.getCronExpression());
|
|
137
|
+
command.put("intervalSeconds", value.getIntervalSeconds());
|
|
138
|
+
command.put("timezone", value.getTimezone());
|
|
139
|
+
return command;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private static ResultMsg response(Map<String, Object> result) {
|
|
143
|
+
if (result == null || !Boolean.TRUE.equals(result.get("success"))) {
|
|
144
|
+
return ResultMsg.fail(result == null ? "定时任务服务不可用"
|
|
145
|
+
: String.valueOf(result.get("errorMsg")));
|
|
146
|
+
}
|
|
147
|
+
return ResultMsg.ok(result.get("data"));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
public static class IdCommand {
|
|
151
|
+
private Long taskId;
|
|
152
|
+
private Long runId;
|
|
153
|
+
|
|
154
|
+
public Long getTaskId() { return taskId; }
|
|
155
|
+
public void setTaskId(Long taskId) { this.taskId = taskId; }
|
|
156
|
+
public Long getRunId() { return runId; }
|
|
157
|
+
public void setRunId(Long runId) { this.runId = runId; }
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
}
|
package/ark-web/src/main/java/com/epcc/arkweb/web/sensitive/reveal/SensitiveCaptchaService.java
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
package com.epcc.arkweb.web.sensitive.reveal;
|
|
2
|
+
|
|
3
|
+
import com.google.code.kaptcha.Producer;
|
|
4
|
+
import org.apache.shiro.session.Session;
|
|
5
|
+
import org.slf4j.Logger;
|
|
6
|
+
import org.slf4j.LoggerFactory;
|
|
7
|
+
import org.springframework.beans.factory.annotation.Autowired;
|
|
8
|
+
import org.springframework.http.HttpStatus;
|
|
9
|
+
import org.springframework.stereotype.Service;
|
|
10
|
+
|
|
11
|
+
import javax.imageio.ImageIO;
|
|
12
|
+
import java.io.ByteArrayOutputStream;
|
|
13
|
+
import java.io.IOException;
|
|
14
|
+
import java.io.Serializable;
|
|
15
|
+
import java.nio.charset.StandardCharsets;
|
|
16
|
+
import java.security.MessageDigest;
|
|
17
|
+
import java.security.NoSuchAlgorithmException;
|
|
18
|
+
import java.time.Clock;
|
|
19
|
+
import java.util.Base64;
|
|
20
|
+
import java.util.UUID;
|
|
21
|
+
import java.util.regex.Pattern;
|
|
22
|
+
|
|
23
|
+
/** Web-only challenge; uses the existing login session, never the reveal token store. */
|
|
24
|
+
@Service
|
|
25
|
+
public class SensitiveCaptchaService {
|
|
26
|
+
private static final Logger LOG = LoggerFactory.getLogger(SensitiveCaptchaService.class);
|
|
27
|
+
private static final String SESSION_KEY = SensitiveCaptchaService.class.getName() + ".challenge";
|
|
28
|
+
private static final long TTL_MILLIS = 120000L;
|
|
29
|
+
private static final Pattern TOKEN = Pattern.compile("rt_[A-Za-z0-9_-]{43}");
|
|
30
|
+
private final Producer producer;
|
|
31
|
+
private final Clock clock;
|
|
32
|
+
// ponytail: short session updates share a JVM lock; multi-node one-use needs sticky sessions.
|
|
33
|
+
private final Object sessionLock = new Object();
|
|
34
|
+
|
|
35
|
+
@Autowired
|
|
36
|
+
public SensitiveCaptchaService(Producer producer) {
|
|
37
|
+
this(producer, Clock.systemUTC());
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
SensitiveCaptchaService(Producer producer, Clock clock) {
|
|
41
|
+
this.producer = producer;
|
|
42
|
+
this.clock = clock;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public byte[] generate(Session session, String userId, String token) {
|
|
46
|
+
long started = System.nanoTime();
|
|
47
|
+
String sessionRef = sessionRef(session);
|
|
48
|
+
if (token == null || !TOKEN.matcher(token).matches()) {
|
|
49
|
+
LOG.info("Sensitive captcha generation rejected session_ref={} reason=invalid_token", sessionRef);
|
|
50
|
+
throw new CaptchaException("INVALID_TOKEN", HttpStatus.BAD_REQUEST);
|
|
51
|
+
}
|
|
52
|
+
if (session == null) {
|
|
53
|
+
LOG.info("Sensitive captcha generation rejected session_ref=none reason=no_session");
|
|
54
|
+
throw new CaptchaException("CAPTCHA_REQUIRED", HttpStatus.FORBIDDEN);
|
|
55
|
+
}
|
|
56
|
+
synchronized (sessionLock) {
|
|
57
|
+
session.removeAttribute(SESSION_KEY);
|
|
58
|
+
}
|
|
59
|
+
String answer;
|
|
60
|
+
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
|
61
|
+
try {
|
|
62
|
+
answer = producer.createText();
|
|
63
|
+
if (!ImageIO.write(producer.createImage(answer), "png", output)) {
|
|
64
|
+
throw new IOException("PNG encoder unavailable");
|
|
65
|
+
}
|
|
66
|
+
} catch (IOException | RuntimeException error) {
|
|
67
|
+
LOG.info("Sensitive captcha generation failed session_ref={} error_type={}",
|
|
68
|
+
sessionRef, error.getClass().getSimpleName());
|
|
69
|
+
throw new CaptchaException("CAPTCHA_UNAVAILABLE", HttpStatus.SERVICE_UNAVAILABLE);
|
|
70
|
+
}
|
|
71
|
+
Challenge challenge = new Challenge(userId, token, answer, clock.millis() + TTL_MILLIS);
|
|
72
|
+
synchronized (sessionLock) {
|
|
73
|
+
session.setAttribute(SESSION_KEY, challenge);
|
|
74
|
+
}
|
|
75
|
+
LOG.info("Sensitive captcha generated session_ref={} challenge_ref={} ttl_seconds=120 elapsed_ms={}",
|
|
76
|
+
sessionRef, challenge.id, (System.nanoTime() - started) / 1000000);
|
|
77
|
+
return output.toByteArray();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public void verify(Session session, String userId, String token, String answer) {
|
|
81
|
+
String sessionRef = sessionRef(session);
|
|
82
|
+
Object stored;
|
|
83
|
+
synchronized (sessionLock) {
|
|
84
|
+
stored = session == null ? null : session.removeAttribute(SESSION_KEY);
|
|
85
|
+
}
|
|
86
|
+
if (!(stored instanceof Challenge)) {
|
|
87
|
+
LOG.info("Sensitive captcha verification rejected session_ref={} reason={}",
|
|
88
|
+
sessionRef, session == null ? "no_session" : "challenge_missing");
|
|
89
|
+
throw new CaptchaException("CAPTCHA_REQUIRED", HttpStatus.FORBIDDEN);
|
|
90
|
+
}
|
|
91
|
+
Challenge challenge = (Challenge) stored;
|
|
92
|
+
if (clock.millis() >= challenge.expiresAt) {
|
|
93
|
+
LOG.info("Sensitive captcha verification rejected session_ref={} challenge_ref={} reason=expired",
|
|
94
|
+
sessionRef, challenge.id);
|
|
95
|
+
throw new CaptchaException("CAPTCHA_EXPIRED", HttpStatus.FORBIDDEN);
|
|
96
|
+
}
|
|
97
|
+
String reason = !challenge.userId.equals(userId) ? "user_mismatch"
|
|
98
|
+
: !challenge.token.equals(token) ? "token_mismatch"
|
|
99
|
+
: answer == null || answer.trim().isEmpty() ? "answer_missing"
|
|
100
|
+
: answer.length() > 32 || !challenge.answer.equalsIgnoreCase(answer.trim()) ? "answer_incorrect" : null;
|
|
101
|
+
if (reason != null) {
|
|
102
|
+
LOG.info("Sensitive captcha verification rejected session_ref={} challenge_ref={} reason={}",
|
|
103
|
+
sessionRef, challenge.id, reason);
|
|
104
|
+
throw new CaptchaException("CAPTCHA_INVALID", HttpStatus.FORBIDDEN);
|
|
105
|
+
}
|
|
106
|
+
LOG.info("Sensitive captcha verification passed session_ref={} challenge_ref={}", sessionRef, challenge.id);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** A non-credential correlation value; never log the login session ID itself. */
|
|
110
|
+
static String sessionRef(Session session) {
|
|
111
|
+
if (session == null || session.getId() == null) return "none";
|
|
112
|
+
try {
|
|
113
|
+
byte[] digest = MessageDigest.getInstance("SHA-256")
|
|
114
|
+
.digest(session.getId().toString().getBytes(StandardCharsets.UTF_8));
|
|
115
|
+
return Base64.getUrlEncoder().withoutPadding().encodeToString(digest).substring(0, 16);
|
|
116
|
+
} catch (NoSuchAlgorithmException impossible) {
|
|
117
|
+
throw new IllegalStateException(impossible);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private static final class Challenge implements Serializable {
|
|
122
|
+
private static final long serialVersionUID = 1L;
|
|
123
|
+
final String id = UUID.randomUUID().toString();
|
|
124
|
+
final String userId;
|
|
125
|
+
final String token;
|
|
126
|
+
final String answer;
|
|
127
|
+
final long expiresAt;
|
|
128
|
+
|
|
129
|
+
Challenge(String userId, String token, String answer, long expiresAt) {
|
|
130
|
+
this.userId = userId;
|
|
131
|
+
this.token = token;
|
|
132
|
+
this.answer = answer;
|
|
133
|
+
this.expiresAt = expiresAt;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
public static final class CaptchaException extends RuntimeException {
|
|
138
|
+
private static final long serialVersionUID = 1L;
|
|
139
|
+
final HttpStatus status;
|
|
140
|
+
|
|
141
|
+
CaptchaException(String code, HttpStatus status) {
|
|
142
|
+
super(code);
|
|
143
|
+
this.status = status;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
package/ark-web/src/main/java/com/epcc/arkweb/web/sensitive/reveal/SensitiveRevealController.java
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
package com.epcc.arkweb.web.sensitive.reveal;
|
|
2
|
+
|
|
3
|
+
import com.epcc.arkweb.helper.AuthenticatedRequest;
|
|
4
|
+
import com.union.control.service.SensitiveDataDemoService;
|
|
5
|
+
import com.union.control.service.sensitive.SensitiveRevealService;
|
|
6
|
+
import org.apache.shiro.SecurityUtils;
|
|
7
|
+
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
|
8
|
+
import org.slf4j.Logger;
|
|
9
|
+
import org.slf4j.LoggerFactory;
|
|
10
|
+
import org.springframework.beans.factory.annotation.Value;
|
|
11
|
+
import org.springframework.http.CacheControl;
|
|
12
|
+
import org.springframework.http.HttpHeaders;
|
|
13
|
+
import org.springframework.http.HttpStatus;
|
|
14
|
+
import org.springframework.http.MediaType;
|
|
15
|
+
import org.springframework.http.ResponseEntity;
|
|
16
|
+
import org.springframework.web.bind.annotation.ExceptionHandler;
|
|
17
|
+
import org.springframework.web.bind.annotation.GetMapping;
|
|
18
|
+
import org.springframework.web.bind.annotation.PostMapping;
|
|
19
|
+
import org.springframework.web.bind.annotation.RequestBody;
|
|
20
|
+
import org.springframework.web.bind.annotation.RequestMapping;
|
|
21
|
+
import org.springframework.web.bind.annotation.RestController;
|
|
22
|
+
|
|
23
|
+
import java.util.Collections;
|
|
24
|
+
import java.util.List;
|
|
25
|
+
import java.util.Map;
|
|
26
|
+
import java.util.LinkedHashMap;
|
|
27
|
+
import java.util.UUID;
|
|
28
|
+
|
|
29
|
+
@RestController
|
|
30
|
+
@RequestMapping("/api/sensitive/reveal")
|
|
31
|
+
public class SensitiveRevealController {
|
|
32
|
+
private static final Logger LOG = LoggerFactory.getLogger(SensitiveRevealController.class);
|
|
33
|
+
private final SensitiveRevealService service;
|
|
34
|
+
private final SensitiveDataDemoService demoService;
|
|
35
|
+
private final AuthenticatedRequest request;
|
|
36
|
+
private final SensitiveCaptchaService captcha;
|
|
37
|
+
private final boolean strictMode;
|
|
38
|
+
|
|
39
|
+
public SensitiveRevealController(SensitiveRevealService service,
|
|
40
|
+
SensitiveDataDemoService demoService, AuthenticatedRequest request,
|
|
41
|
+
SensitiveCaptchaService captcha,
|
|
42
|
+
@Value("${sensitive.reveal.strict_mode:false}") boolean strictMode) {
|
|
43
|
+
this.service = service;
|
|
44
|
+
this.demoService = demoService;
|
|
45
|
+
this.request = request;
|
|
46
|
+
this.captcha = captcha;
|
|
47
|
+
this.strictMode = strictMode;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@GetMapping("/options")
|
|
51
|
+
@RequiresPermissions(value = "/assistantManager/page")
|
|
52
|
+
public ResponseEntity<Map<String, Boolean>> options() {
|
|
53
|
+
request.actor();
|
|
54
|
+
return ResponseEntity.ok().cacheControl(CacheControl.noStore())
|
|
55
|
+
.body(Collections.singletonMap("strictMode", strictMode));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@PostMapping("/captcha")
|
|
59
|
+
@RequiresPermissions(value = "/assistantManager/page")
|
|
60
|
+
public ResponseEntity<byte[]> captcha(@RequestBody Map<String, Object> payload) {
|
|
61
|
+
String userId = request.actor().getLoginName();
|
|
62
|
+
if (!strictMode) {
|
|
63
|
+
LOG.info("Sensitive captcha request rejected reason=strict_mode_disabled");
|
|
64
|
+
throw new SensitiveCaptchaService.CaptchaException("STRICT_MODE_DISABLED", HttpStatus.CONFLICT);
|
|
65
|
+
}
|
|
66
|
+
byte[] png = captcha.generate(SecurityUtils.getSubject().getSession(false),
|
|
67
|
+
userId, string(payload, "token"));
|
|
68
|
+
return ResponseEntity.ok().cacheControl(CacheControl.noStore())
|
|
69
|
+
.header(HttpHeaders.PRAGMA, "no-cache").contentType(MediaType.IMAGE_PNG).body(png);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@PostMapping
|
|
73
|
+
@RequiresPermissions(value = "/assistantManager/page")
|
|
74
|
+
public ResponseEntity<Map<String, String>> reveal(@RequestBody Map<String, Object> payload) {
|
|
75
|
+
String requestId = UUID.randomUUID().toString();
|
|
76
|
+
long started = System.nanoTime();
|
|
77
|
+
String stage = "captcha";
|
|
78
|
+
LOG.info("Sensitive reveal received request_id={} strict_mode={} session_ref={}", requestId, strictMode,
|
|
79
|
+
SensitiveCaptchaService.sessionRef(SecurityUtils.getSubject().getSession(false)));
|
|
80
|
+
try {
|
|
81
|
+
if (strictMode) {
|
|
82
|
+
captcha.verify(SecurityUtils.getSubject().getSession(false), request.actor().getLoginName(),
|
|
83
|
+
string(payload, "token"), string(payload, "captchaCode"));
|
|
84
|
+
}
|
|
85
|
+
stage = "control";
|
|
86
|
+
LOG.info("Sensitive reveal calling Control request_id={}", requestId);
|
|
87
|
+
// Only token and server-generated trace metadata cross Dubbo; the answer stays in Web.
|
|
88
|
+
Map<String, Object> input = new LinkedHashMap<>();
|
|
89
|
+
input.put("token", payload.get("token"));
|
|
90
|
+
input.put("revealRequestId", requestId);
|
|
91
|
+
String plaintext = service.reveal(request.json(input));
|
|
92
|
+
LOG.info("Sensitive reveal completed request_id={} elapsed_ms={}",
|
|
93
|
+
requestId, (System.nanoTime() - started) / 1000000);
|
|
94
|
+
return ResponseEntity.ok().cacheControl(CacheControl.noStore())
|
|
95
|
+
.header(HttpHeaders.PRAGMA, "no-cache")
|
|
96
|
+
.body(Collections.singletonMap("value", plaintext));
|
|
97
|
+
} catch (RuntimeException error) {
|
|
98
|
+
String reason = error instanceof SensitiveCaptchaService.CaptchaException
|
|
99
|
+
? error.getMessage() : error.getClass().getSimpleName();
|
|
100
|
+
LOG.info("Sensitive reveal rejected request_id={} stage={} reason={} elapsed_ms={}",
|
|
101
|
+
requestId, stage, reason, (System.nanoTime() - started) / 1000000);
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@ExceptionHandler(SensitiveCaptchaService.CaptchaException.class)
|
|
107
|
+
public ResponseEntity<Map<String, String>> captchaError(SensitiveCaptchaService.CaptchaException error) {
|
|
108
|
+
return ResponseEntity.status(error.status).cacheControl(CacheControl.noStore())
|
|
109
|
+
.header(HttpHeaders.PRAGMA, "no-cache").contentType(MediaType.APPLICATION_JSON)
|
|
110
|
+
.body(Collections.singletonMap("code", error.getMessage()));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private static String string(Map<String, Object> payload, String key) {
|
|
114
|
+
Object value = payload.get(key);
|
|
115
|
+
return value instanceof String ? (String) value : null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@PostMapping("/demo/save")
|
|
119
|
+
@RequiresPermissions(value = "/assistantManager/page")
|
|
120
|
+
public ResponseEntity<Map<String, Object>> saveDemo(@RequestBody Map<String, Object> payload) {
|
|
121
|
+
return ResponseEntity.ok().cacheControl(CacheControl.noStore())
|
|
122
|
+
.body(demoService.save(request.json(payload)));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@PostMapping("/demo/address-book/save")
|
|
126
|
+
@RequiresPermissions(value = "/assistantManager/page")
|
|
127
|
+
public ResponseEntity<Map<String, Object>> saveAddressBookDemo(@RequestBody Map<String, Object> payload) {
|
|
128
|
+
return ResponseEntity.ok().cacheControl(CacheControl.noStore())
|
|
129
|
+
.body(demoService.saveAddressBook(request.json(payload)));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
@ExceptionHandler(SensitiveRevealService.ExpiredTokenException.class)
|
|
133
|
+
public ResponseEntity<Map<String, String>> expiredToken(SensitiveRevealService.ExpiredTokenException error) {
|
|
134
|
+
return ResponseEntity.status(HttpStatus.GONE).cacheControl(CacheControl.noStore())
|
|
135
|
+
.body(Collections.singletonMap("code", "TOKEN_EXPIRED"));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@PostMapping("/demo/insert")
|
|
139
|
+
@RequiresPermissions(value = "/assistantManager/page")
|
|
140
|
+
public Map<String, Integer> insertDemo(@RequestBody Map<String, Object> payload) {
|
|
141
|
+
return Collections.singletonMap("affectedRows", demoService.insert(request.json(payload)));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
@GetMapping("/demo/query")
|
|
145
|
+
@RequiresPermissions(value = "/assistantManager/page")
|
|
146
|
+
public List<Map<String, Object>> queryDemo() {
|
|
147
|
+
return demoService.query(request.json());
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
@PostMapping("/demo/address-book/insert")
|
|
151
|
+
@RequiresPermissions(value = "/assistantManager/page")
|
|
152
|
+
public Map<String, Integer> insertAddressBookDemo(
|
|
153
|
+
@RequestBody Map<String, Object> payload) {
|
|
154
|
+
return Collections.singletonMap("affectedRows",
|
|
155
|
+
demoService.insertAddressBook(request.json(payload)));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
@GetMapping("/demo/address-book/query")
|
|
159
|
+
@RequiresPermissions(value = "/assistantManager/page")
|
|
160
|
+
public List<Map<String, Object>> queryAddressBookDemo() {
|
|
161
|
+
return demoService.queryAddressBook(request.json());
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
sensitive.reveal.strict_mode=${SENSITIVE_REVEAL_STRICT_MODE:true}
|