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.
Files changed (122) hide show
  1. package/PROJECT_OVERVIEW.md +405 -0
  2. package/README.md +177 -0
  3. package/ark-control/pom.xml +142 -0
  4. package/ark-control/src/local-mock/java/com/epcc/commons/securityproxy/api/SecurityResult.java +14 -0
  5. package/ark-control/src/local-mock/java/com/epcc/commons/securityproxy/api/SymmetricalSecurityService.java +11 -0
  6. package/ark-control/src/local-mock/java/com/epcc/dubbo/result/Result.java +29 -0
  7. package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/annotation/EnDecryptField.java +16 -0
  8. package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/annotation/EnDecryptFieldLong.java +17 -0
  9. package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/annotation/EnDecryptFieldWithTag.java +16 -0
  10. package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/exception/BaseDataErrorCode.java +26 -0
  11. package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/exception/CheckException.java +33 -0
  12. package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/exception/ErrorCode.java +9 -0
  13. package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/redis/RedisCacheService.java +131 -0
  14. package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/util/Constant.java +24 -0
  15. package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/util/ResultUtil.java +9 -0
  16. package/ark-control/src/local-mock/java/com/union/control/local/sensitive/LocalMockSensitiveProxy.java +40 -0
  17. package/ark-control/src/local-mock/java/com/union/control/local/sensitive/LocalRedisCacheService.java +45 -0
  18. package/ark-control/src/local-mock/java/com/union/control/local/sensitive/LocalSensitiveHostConfiguration.java +27 -0
  19. package/ark-control/src/local-mock/java/com/union/control/local/sensitive/LocalSensitiveProxyConfiguration.java +16 -0
  20. package/ark-control/src/main/java/com/union/control/ArkControlApplication.java +17 -0
  21. package/ark-control/src/main/java/com/union/control/dubbo/ProviderAccessLogFilter.java +45 -0
  22. package/ark-control/src/main/java/com/union/control/mapper/AgentExecutionMapper.java +35 -0
  23. package/ark-control/src/main/java/com/union/control/mapper/ConversationMapper.java +44 -0
  24. package/ark-control/src/main/java/com/union/control/mapper/MemoryStoreMapper.java +41 -0
  25. package/ark-control/src/main/java/com/union/control/mapper/ScheduledTaskMapper.java +96 -0
  26. package/ark-control/src/main/java/com/union/control/mapper/SensitiveAddressBookDemo.java +31 -0
  27. package/ark-control/src/main/java/com/union/control/mapper/SensitiveDataDemoMapper.java +24 -0
  28. package/ark-control/src/main/java/com/union/control/mapper/interceptor/AESInterceptor.java +265 -0
  29. package/ark-control/src/main/java/com/union/control/mapper/interceptor/AddressBookHandler.java +105 -0
  30. package/ark-control/src/main/java/com/union/control/schedule/ScheduledExecutionToken.java +55 -0
  31. package/ark-control/src/main/java/com/union/control/schedule/ScheduledTaskScheduler.java +206 -0
  32. package/ark-control/src/main/java/com/union/control/service/impl/AgentExecutionServiceImpl.java +212 -0
  33. package/ark-control/src/main/java/com/union/control/service/impl/AgentProxyServiceImpl.java +143 -0
  34. package/ark-control/src/main/java/com/union/control/service/impl/ConversationServiceImpl.java +245 -0
  35. package/ark-control/src/main/java/com/union/control/service/impl/MemoryStoreServiceImpl.java +252 -0
  36. package/ark-control/src/main/java/com/union/control/service/impl/RunningAnalysisMockServiceImpl.java +301 -0
  37. package/ark-control/src/main/java/com/union/control/service/impl/ScheduledTaskServiceImpl.java +576 -0
  38. package/ark-control/src/main/java/com/union/control/service/impl/SensitiveDataDemoServiceImpl.java +144 -0
  39. package/ark-control/src/main/java/com/union/control/service/sensitive/RedisRevealTokenStore.java +90 -0
  40. package/ark-control/src/main/java/com/union/control/service/sensitive/SensitiveRevealProcessor.java +319 -0
  41. package/ark-control/src/main/java/com/union/control/service/sensitive/SensitiveRevealServiceImpl.java +72 -0
  42. package/ark-control/src/main/java/com/union/control/utils/AgentSupport.java +103 -0
  43. package/ark-control/src/main/java/com/union/control/utils/security/SymmetricalSecurityUtils.java +160 -0
  44. package/ark-control/src/main/resources/META-INF/dubbo/com.alibaba.dubbo.rpc.Filter +1 -0
  45. package/ark-control/src/main/resources/application.properties +4 -0
  46. package/ark-control/src/main/resources/application.yml +32 -0
  47. package/ark-control/src/main/resources/dubbo-provider.xml +20 -0
  48. package/ark-control/src/main/resources/mapper/AgentExecutionMapper.xml +75 -0
  49. package/ark-control/src/main/resources/mapper/ConversationMapper.xml +110 -0
  50. package/ark-control/src/main/resources/mapper/MemoryStoreMapper.xml +74 -0
  51. package/ark-control/src/main/resources/mapper/ScheduledTaskMapper.xml +286 -0
  52. package/ark-control/src/main/resources/mapper/SensitiveDataDemoMapper.xml +55 -0
  53. package/ark-control/src/main/resources/schema.sql +146 -0
  54. package/ark-control/src/test/java/com/union/control/AgentExecutionServiceTest.java +170 -0
  55. package/ark-control/src/test/java/com/union/control/ArkControlStructureContractTest.java +107 -0
  56. package/ark-control/src/test/java/com/union/control/ConversationServiceTest.java +89 -0
  57. package/ark-control/src/test/java/com/union/control/MemoryStoreServiceTest.java +26 -0
  58. package/ark-control/src/test/java/com/union/control/PydanticAiControlContractTest.java +52 -0
  59. package/ark-control/src/test/java/com/union/control/TestJson.java +45 -0
  60. package/ark-control/src/test/java/com/union/control/dubbo/ProviderAccessLogFilterTest.java +53 -0
  61. package/ark-control/src/test/java/com/union/control/local/sensitive/LocalSensitiveProxyConfigurationTest.java +23 -0
  62. package/ark-control/src/test/java/com/union/control/mapper/interceptor/AESInterceptorCompatibilityTest.java +505 -0
  63. package/ark-control/src/test/java/com/union/control/schedule/ScheduledExecutionTokenTest.java +27 -0
  64. package/ark-control/src/test/java/com/union/control/schedule/ScheduledTaskSchedulerTest.java +51 -0
  65. package/ark-control/src/test/java/com/union/control/service/AgentProxyServiceTest.java +111 -0
  66. package/ark-control/src/test/java/com/union/control/service/ScheduledTaskServiceTest.java +483 -0
  67. package/ark-control/src/test/java/com/union/control/service/impl/SensitiveDataDemoServiceImplTest.java +104 -0
  68. package/ark-control/src/test/java/com/union/control/service/sensitive/RedisRevealTokenStoreTest.java +65 -0
  69. package/ark-control/src/test/java/com/union/control/service/sensitive/SensitiveRevealBoundaryTest.java +99 -0
  70. package/ark-control/src/test/java/com/union/control/service/sensitive/SensitiveRevealProcessorCodecTest.java +111 -0
  71. package/ark-control/src/test/java/com/union/control/service/sensitive/SensitiveRevealProcessorTest.java +111 -0
  72. package/ark-control/src/test/java/com/union/control/utils/security/SymmetricalSecurityUtilsCompatibilityTest.java +87 -0
  73. package/ark-control-facade/pom.xml +16 -0
  74. package/ark-control-facade/src/main/java/com/union/control/service/AgentExecutionService.java +8 -0
  75. package/ark-control-facade/src/main/java/com/union/control/service/AgentProxyService.java +7 -0
  76. package/ark-control-facade/src/main/java/com/union/control/service/AgentResponse.java +22 -0
  77. package/ark-control-facade/src/main/java/com/union/control/service/ConversationService.java +12 -0
  78. package/ark-control-facade/src/main/java/com/union/control/service/MemoryStoreService.java +12 -0
  79. package/ark-control-facade/src/main/java/com/union/control/service/RunningAnalysisMockService.java +10 -0
  80. package/ark-control-facade/src/main/java/com/union/control/service/ScheduledTaskService.java +40 -0
  81. package/ark-control-facade/src/main/java/com/union/control/service/SensitiveDataDemoService.java +13 -0
  82. package/ark-control-facade/src/main/java/com/union/control/service/sensitive/SensitiveRevealService.java +18 -0
  83. package/ark-web/README.md +80 -0
  84. package/ark-web/pom.xml +105 -0
  85. package/ark-web/src/local-mock/java/com/epcc/arkweb/mock/ArkAuthServiceMock.java +105 -0
  86. package/ark-web/src/local-mock/java/com/epcc/arkweb/mock/LocalKaptchaConfiguration.java +26 -0
  87. package/ark-web/src/main/java/com/epcc/arkweb/Application.java +13 -0
  88. package/ark-web/src/main/java/com/epcc/arkweb/config/InterceptorConfig.java +20 -0
  89. package/ark-web/src/main/java/com/epcc/arkweb/config/Realm.java +64 -0
  90. package/ark-web/src/main/java/com/epcc/arkweb/config/ShiroConfig.java +129 -0
  91. package/ark-web/src/main/java/com/epcc/arkweb/filter/LoginFormFilter.java +43 -0
  92. package/ark-web/src/main/java/com/epcc/arkweb/helper/AuthContextHolder.java +39 -0
  93. package/ark-web/src/main/java/com/epcc/arkweb/helper/AuthenticatedRequest.java +91 -0
  94. package/ark-web/src/main/java/com/epcc/arkweb/model/ShiroUser.java +21 -0
  95. package/ark-web/src/main/java/com/epcc/arkweb/utils/ResultMsg.java +19 -0
  96. package/ark-web/src/main/java/com/epcc/arkweb/vo/llm/ScheduledTaskCommandVO.java +30 -0
  97. package/ark-web/src/main/java/com/epcc/arkweb/vo/llm/ScheduledTaskQueryVO.java +20 -0
  98. package/ark-web/src/main/java/com/epcc/arkweb/web/CommonController.java +25 -0
  99. package/ark-web/src/main/java/com/epcc/arkweb/web/llm/AgentAuthorizationInterceptor.java +229 -0
  100. package/ark-web/src/main/java/com/epcc/arkweb/web/llm/AgentController.java +165 -0
  101. package/ark-web/src/main/java/com/epcc/arkweb/web/llm/AgentPermission.java +13 -0
  102. package/ark-web/src/main/java/com/epcc/arkweb/web/llm/LlmController.java +209 -0
  103. package/ark-web/src/main/java/com/epcc/arkweb/web/llm/ScheduledTaskController.java +160 -0
  104. package/ark-web/src/main/java/com/epcc/arkweb/web/sensitive/reveal/SensitiveCaptchaService.java +146 -0
  105. package/ark-web/src/main/java/com/epcc/arkweb/web/sensitive/reveal/SensitiveRevealController.java +163 -0
  106. package/ark-web/src/main/resources/application.properties +1 -0
  107. package/ark-web/src/main/resources/application.yml +23 -0
  108. package/ark-web/src/main/resources/dubbo-consumer.xml +19 -0
  109. package/ark-web/src/production-overlay/java/com/epcc/arkweb/config/CasShiroConfig.java +229 -0
  110. package/ark-web/src/production-overlay/java/com/epcc/arkweb/config/InterceptorConfig.java +35 -0
  111. package/ark-web/src/production-overlay/java/com/epcc/arkweb/config/ShiroConfig.java +161 -0
  112. package/ark-web/src/test/java/com/epcc/arkweb/ArkWebStructureContractTest.java +290 -0
  113. package/ark-web/src/test/java/com/epcc/arkweb/ShiroTestSupport.java +32 -0
  114. package/ark-web/src/test/java/com/epcc/arkweb/config/LocalShiroIntegrationTest.java +121 -0
  115. package/ark-web/src/test/java/com/epcc/arkweb/helper/AuthenticatedRequestTest.java +131 -0
  116. package/ark-web/src/test/java/com/epcc/arkweb/local/LocalArkAuthServiceMockTest.java +17 -0
  117. package/ark-web/src/test/java/com/epcc/arkweb/web/llm/AgentAuthorizationInterceptorTest.java +134 -0
  118. package/ark-web/src/test/java/com/epcc/arkweb/web/llm/AgentControllerAuthorizationTest.java +97 -0
  119. package/ark-web/src/test/java/com/epcc/arkweb/web/llm/LlmControllerTest.java +113 -0
  120. package/ark-web/src/test/java/com/epcc/arkweb/web/sensitive/reveal/SensitiveRevealControllerTest.java +273 -0
  121. package/package.json +18 -0
  122. package/pom.xml +16 -0
@@ -0,0 +1,39 @@
1
+ package com.epcc.arkweb.helper;
2
+
3
+ import com.epcc.arkweb.model.ShiroUser;
4
+ import org.apache.shiro.SecurityUtils;
5
+ import org.apache.shiro.session.InvalidSessionException;
6
+ import org.apache.shiro.subject.Subject;
7
+ import org.springframework.beans.BeanUtils;
8
+
9
+ /** Uses the same Shiro Subject access pattern as production ark-web. */
10
+ public final class AuthContextHolder {
11
+ private static final String SESSION_USER = "wl_session_user_key";
12
+
13
+ private AuthContextHolder() {}
14
+
15
+ public static ShiroUser getAuthUserDetails() {
16
+ Subject subject;
17
+ try {
18
+ subject = SecurityUtils.getSubject();
19
+ } catch (RuntimeException error) {
20
+ return null;
21
+ }
22
+ if (subject == null) return null;
23
+ try {
24
+ ShiroUser user = copy(subject.getSession(false) == null ? null
25
+ : subject.getSession(false).getAttribute(SESSION_USER));
26
+ return user == null ? copy(subject.getPrincipal()) : user;
27
+ } catch (InvalidSessionException error) {
28
+ return null;
29
+ }
30
+ }
31
+
32
+ private static ShiroUser copy(Object source) {
33
+ if (source == null) return null;
34
+ if (source instanceof ShiroUser) return (ShiroUser) source;
35
+ ShiroUser target = new ShiroUser();
36
+ BeanUtils.copyProperties(source, target);
37
+ return target;
38
+ }
39
+ }
@@ -0,0 +1,91 @@
1
+ package com.epcc.arkweb.helper;
2
+
3
+ import com.epcc.arkweb.model.ShiroUser;
4
+ import com.epcc.arkweb.web.llm.AgentAuthorizationInterceptor;
5
+ import com.fasterxml.jackson.core.type.TypeReference;
6
+ import com.fasterxml.jackson.databind.ObjectMapper;
7
+ import org.apache.shiro.authz.UnauthenticatedException;
8
+ import org.springframework.stereotype.Component;
9
+
10
+ import java.util.LinkedHashMap;
11
+ import java.util.Map;
12
+
13
+ /** Converts an authenticated Web request into the JSON-only service contract. */
14
+ @Component
15
+ public class AuthenticatedRequest {
16
+ private final ObjectMapper json;
17
+
18
+ public AuthenticatedRequest(ObjectMapper json) {
19
+ this.json = json;
20
+ }
21
+
22
+ public String json() {
23
+ return json(new LinkedHashMap<String, Object>());
24
+ }
25
+
26
+ public String json(Map<String, ?> payload) {
27
+ Map<String, Object> input = new LinkedHashMap<>();
28
+ if (payload != null) input.putAll(payload);
29
+ Map<String, Object> scheduled = AgentAuthorizationInterceptor.currentContext();
30
+ if (scheduled != null) {
31
+ input.put("userId", scheduled.get("userId"));
32
+ input.put("orgCode", scheduled.get("orgCode"));
33
+ input.put("roleId", scheduled.get("roleId"));
34
+ } else {
35
+ ShiroUser actor = actor();
36
+ input.put("userId", actor.getLoginName());
37
+ input.put("orgCode", actor.getOrgCode());
38
+ input.put("roleId", actor.getRoleId());
39
+ }
40
+ return write(input);
41
+ }
42
+
43
+ public String json(Object... entries) {
44
+ if (entries.length % 2 != 0) throw new IllegalArgumentException("请求参数非法");
45
+ Map<String, Object> input = new LinkedHashMap<>();
46
+ for (int index = 0; index < entries.length; index += 2)
47
+ input.put(String.valueOf(entries[index]), entries[index + 1]);
48
+ return json(input);
49
+ }
50
+
51
+ public String json(byte[] payload) {
52
+ try {
53
+ Map<String, Object> input = json.readValue(
54
+ payload, new TypeReference<Map<String, Object>>() {});
55
+ return json(input);
56
+ } catch (Exception error) {
57
+ throw new IllegalArgumentException("请求参数不是合法 JSON", error);
58
+ }
59
+ }
60
+
61
+ public byte[] clientPayload(byte[] payload) {
62
+ try {
63
+ Map<String, Object> input = json.readValue(
64
+ payload, new TypeReference<Map<String, Object>>() {});
65
+ input.remove("userId");
66
+ input.remove("orgCode");
67
+ input.remove("roleId");
68
+ return json.writeValueAsBytes(input);
69
+ } catch (Exception error) {
70
+ throw new IllegalArgumentException("请求参数不是合法 JSON", error);
71
+ }
72
+ }
73
+
74
+ public ShiroUser actor() {
75
+ ShiroUser actor = AuthContextHolder.getAuthUserDetails();
76
+ if (actor == null || blank(actor.getLoginName())) throw new UnauthenticatedException();
77
+ return actor;
78
+ }
79
+
80
+ private String write(Map<String, Object> input) {
81
+ try {
82
+ return json.writeValueAsString(input);
83
+ } catch (Exception error) {
84
+ throw new IllegalArgumentException("请求参数无法序列化", error);
85
+ }
86
+ }
87
+
88
+ private static boolean blank(String value) {
89
+ return value == null || value.trim().isEmpty() || value.length() > 64;
90
+ }
91
+ }
@@ -0,0 +1,21 @@
1
+ package com.epcc.arkweb.model;
2
+
3
+ import java.io.Serializable;
4
+
5
+ /**
6
+ * Local source-compatible subset of production ShiroUser.
7
+ * Production keeps its existing richer model.
8
+ */
9
+ public class ShiroUser implements Serializable {
10
+ private static final long serialVersionUID = 1L;
11
+ private String loginName;
12
+ private String orgCode;
13
+ private String roleId;
14
+
15
+ public String getLoginName() { return loginName; }
16
+ public void setLoginName(String loginName) { this.loginName = loginName; }
17
+ public String getOrgCode() { return orgCode; }
18
+ public void setOrgCode(String orgCode) { this.orgCode = orgCode; }
19
+ public String getRoleId() { return roleId; }
20
+ public void setRoleId(String roleId) { this.roleId = roleId; }
21
+ }
@@ -0,0 +1,19 @@
1
+ package com.epcc.arkweb.utils;
2
+
3
+ public final class ResultMsg {
4
+ private final boolean success;
5
+ private final Object data;
6
+ private final String message;
7
+
8
+ private ResultMsg(boolean success, Object data, String message) {
9
+ this.success = success;
10
+ this.data = data;
11
+ this.message = message;
12
+ }
13
+
14
+ public static ResultMsg ok(Object data) { return new ResultMsg(true, data, null); }
15
+ public static ResultMsg fail(String message) { return new ResultMsg(false, null, message); }
16
+ public boolean isSuccess() { return success; }
17
+ public Object getData() { return data; }
18
+ public String getMessage() { return message; }
19
+ }
@@ -0,0 +1,30 @@
1
+ package com.epcc.arkweb.vo.llm;
2
+
3
+ /** Browser-editable task fields. Identity fields intentionally do not exist. */
4
+ public class ScheduledTaskCommandVO {
5
+ private Long taskId;
6
+ private String title;
7
+ private String prompt;
8
+ private String scheduleType;
9
+ private String runAt;
10
+ private String cronExpression;
11
+ private Long intervalSeconds;
12
+ private String timezone;
13
+
14
+ public Long getTaskId() { return taskId; }
15
+ public void setTaskId(Long taskId) { this.taskId = taskId; }
16
+ public String getTitle() { return title; }
17
+ public void setTitle(String title) { this.title = title; }
18
+ public String getPrompt() { return prompt; }
19
+ public void setPrompt(String prompt) { this.prompt = prompt; }
20
+ public String getScheduleType() { return scheduleType; }
21
+ public void setScheduleType(String scheduleType) { this.scheduleType = scheduleType; }
22
+ public String getRunAt() { return runAt; }
23
+ public void setRunAt(String runAt) { this.runAt = runAt; }
24
+ public String getCronExpression() { return cronExpression; }
25
+ public void setCronExpression(String cronExpression) { this.cronExpression = cronExpression; }
26
+ public Long getIntervalSeconds() { return intervalSeconds; }
27
+ public void setIntervalSeconds(Long intervalSeconds) { this.intervalSeconds = intervalSeconds; }
28
+ public String getTimezone() { return timezone; }
29
+ public void setTimezone(String timezone) { this.timezone = timezone; }
30
+ }
@@ -0,0 +1,20 @@
1
+ package com.epcc.arkweb.vo.llm;
2
+
3
+ public class ScheduledTaskQueryVO {
4
+ private Long taskId;
5
+ private String keyword;
6
+ private String status;
7
+ private Integer page = 1;
8
+ private Integer pageSize = 20;
9
+
10
+ public Long getTaskId() { return taskId; }
11
+ public void setTaskId(Long taskId) { this.taskId = taskId; }
12
+ public String getKeyword() { return keyword; }
13
+ public void setKeyword(String keyword) { this.keyword = keyword; }
14
+ public String getStatus() { return status; }
15
+ public void setStatus(String status) { this.status = status; }
16
+ public Integer getPage() { return page; }
17
+ public void setPage(Integer page) { this.page = page; }
18
+ public Integer getPageSize() { return pageSize; }
19
+ public void setPageSize(Integer pageSize) { this.pageSize = pageSize; }
20
+ }
@@ -0,0 +1,25 @@
1
+ package com.epcc.arkweb.web;
2
+
3
+ import com.union.control.service.ConversationService;
4
+ import com.epcc.arkweb.helper.AuthenticatedRequest;
5
+ import org.springframework.web.bind.annotation.GetMapping;
6
+ import org.springframework.web.bind.annotation.RestController;
7
+
8
+ import java.util.Map;
9
+
10
+ /** Production-style authenticated user-info endpoint. */
11
+ @RestController
12
+ public class CommonController {
13
+ private final ConversationService service;
14
+ private final AuthenticatedRequest request;
15
+
16
+ public CommonController(ConversationService service, AuthenticatedRequest request) {
17
+ this.service = service;
18
+ this.request = request;
19
+ }
20
+
21
+ @GetMapping("/common/getUserInfo")
22
+ public Map<String, Object> getUserInfo() {
23
+ return service.userInfo(request.json());
24
+ }
25
+ }
@@ -0,0 +1,229 @@
1
+ package com.epcc.arkweb.web.llm;
2
+
3
+ import com.union.control.service.ScheduledTaskService;
4
+ import org.apache.shiro.SecurityUtils;
5
+ import org.apache.shiro.subject.Subject;
6
+ import org.slf4j.Logger;
7
+ import org.slf4j.LoggerFactory;
8
+ import org.springframework.beans.factory.annotation.Autowired;
9
+ import org.springframework.beans.factory.annotation.Qualifier;
10
+ import org.springframework.http.HttpHeaders;
11
+ import org.springframework.stereotype.Component;
12
+ import org.springframework.web.context.request.RequestAttributes;
13
+ import org.springframework.web.context.request.RequestContextHolder;
14
+ import org.springframework.web.method.HandlerMethod;
15
+ import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
16
+
17
+ import javax.servlet.http.HttpServletRequest;
18
+ import javax.servlet.http.HttpServletResponse;
19
+ import java.io.IOException;
20
+ import java.lang.reflect.InvocationTargetException;
21
+ import java.lang.reflect.Method;
22
+ import java.time.Instant;
23
+ import java.util.Collections;
24
+ import java.util.Enumeration;
25
+ import java.util.LinkedHashMap;
26
+ import java.util.List;
27
+ import java.util.Map;
28
+ import java.util.UUID;
29
+
30
+ /** Agent-only guard. Scheduled requests never create a Shiro Subject. */
31
+ @Component
32
+ public final class AgentAuthorizationInterceptor extends HandlerInterceptorAdapter {
33
+ public static final String TRUSTED_CONTEXT_HEADER = "X-Agent-Trusted-Context";
34
+ private static final String AUTHORIZATION_PREFIX = "Scheduled ";
35
+ public static final String CONTEXT_ATTRIBUTE =
36
+ AgentAuthorizationInterceptor.class.getName() + ".scheduledContext";
37
+ private static final Logger LOG = LoggerFactory.getLogger(AgentAuthorizationInterceptor.class);
38
+
39
+ private final ScheduledTaskService scheduledTasks;
40
+ private volatile Object arkAuthService;
41
+
42
+ @Autowired
43
+ public AgentAuthorizationInterceptor(ScheduledTaskService scheduledTasks) {
44
+ this.scheduledTasks = scheduledTasks;
45
+ }
46
+
47
+ /** Production supplies its existing bean; absence deliberately fails closed. */
48
+ @Autowired(required = false)
49
+ public void setArkAuthService(@Qualifier("arkAuthService") Object arkAuthService) {
50
+ this.arkAuthService = arkAuthService;
51
+ }
52
+
53
+ public Map<String, Object> authorize(String authorization, String permission) {
54
+ String token = token(authorization);
55
+ if (token == null) throw new IllegalArgumentException("scheduled token invalid");
56
+ Map<String, Object> context = context(scheduledTasks.resolveScheduledToken(token));
57
+ if (!hasPermission((String) context.get("roleId"),
58
+ (String) context.get("userId"), permission))
59
+ throw new SecurityException("scheduled permission denied");
60
+ return context;
61
+ }
62
+
63
+ @Override
64
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
65
+ Object handler) {
66
+ if (!(handler instanceof HandlerMethod)) return true;
67
+ AgentPermission permission = ((HandlerMethod) handler)
68
+ .getMethodAnnotation(AgentPermission.class);
69
+ String path = request.getRequestURI().substring(request.getContextPath().length());
70
+ if (permission == null) {
71
+ if ("/agent/scheduledTaskAuthorize".equals(path))
72
+ return validAuthorizationRequest(request, response);
73
+ return deny(response, 403, "agent_permission_denied");
74
+ }
75
+
76
+ List<String> authorization = headers(request, HttpHeaders.AUTHORIZATION);
77
+ List<String> trusted = headers(request, TRUSTED_CONTEXT_HEADER);
78
+ boolean cookie = present(request.getHeader(HttpHeaders.COOKIE));
79
+ if (authorization.size() > 1 || trusted.size() > 1
80
+ || (!authorization.isEmpty() && !trusted.isEmpty())
81
+ || (!trusted.isEmpty() && cookie))
82
+ return deny(response, 400, "mixed_authentication");
83
+
84
+ if (!trusted.isEmpty()) {
85
+ String rawToken = token(trusted.get(0));
86
+ if (rawToken == null) return deny(response, 401, "scheduled_auth_error");
87
+ try {
88
+ Map<String, Object> context = context(
89
+ scheduledTasks.resolveScheduledToken(rawToken));
90
+ if (!hasPermission((String) context.get("roleId"),
91
+ (String) context.get("userId"), permission.value()))
92
+ return deny(response, 403, "agent_permission_denied");
93
+ request.setAttribute(CONTEXT_ATTRIBUTE, context);
94
+ return true;
95
+ } catch (RuntimeException error) {
96
+ return deny(response, 401, "scheduled_auth_error");
97
+ }
98
+ }
99
+
100
+ // Raw scheduled credentials are accepted only by the exchange endpoint.
101
+ if (!authorization.isEmpty()) return deny(response, 401, "scheduled_auth_error");
102
+ try {
103
+ Subject subject = SecurityUtils.getSubject();
104
+ if (!subject.isAuthenticated() || !subject.isPermitted(permission.value()))
105
+ return deny(response, 403, "agent_permission_denied");
106
+ return true;
107
+ } catch (RuntimeException error) {
108
+ return deny(response, 401, "cas_auth_error");
109
+ }
110
+ }
111
+
112
+ @SuppressWarnings("unchecked")
113
+ public static Map<String, Object> currentContext() {
114
+ RequestAttributes request = RequestContextHolder.getRequestAttributes();
115
+ if (request == null) return null;
116
+ Object context = request.getAttribute(CONTEXT_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
117
+ return context instanceof Map ? (Map<String, Object>) context : null;
118
+ }
119
+
120
+ private boolean hasPermission(String roleId, String userName, String permission) {
121
+ if (!identity(roleId) || !identity(userName) || !identity(permission)) return false;
122
+ Object service = arkAuthService;
123
+ if (service == null) return false;
124
+ try {
125
+ Object result = service.getClass()
126
+ .getMethod("queryResource", String.class, String.class, String.class)
127
+ .invoke(service, roleId, userName, UUID.randomUUID().toString());
128
+ if (result == null || !Boolean.TRUE.equals(noArg(result, "isSuccess").invoke(result)))
129
+ return false;
130
+ Object values = noArg(result, "getResult").invoke(result);
131
+ if (!(values instanceof Iterable)) return false;
132
+ for (Object value : (Iterable<?>) values) {
133
+ if (value == null) continue;
134
+ Object url = noArg(value, "getResourceUrl").invoke(value);
135
+ if (permission.equals(url)) return true;
136
+ }
137
+ return false;
138
+ } catch (InvocationTargetException error) {
139
+ LOG.warn("scheduled role resource lookup failed error_type={}",
140
+ error.getCause() == null ? error.getClass().getSimpleName()
141
+ : error.getCause().getClass().getSimpleName());
142
+ return false;
143
+ } catch (Exception error) {
144
+ LOG.warn("scheduled role resource lookup failed error_type={}",
145
+ error.getClass().getSimpleName());
146
+ return false;
147
+ }
148
+ }
149
+
150
+ private static boolean validAuthorizationRequest(
151
+ HttpServletRequest request, HttpServletResponse response) {
152
+ List<String> authorization = headers(request, HttpHeaders.AUTHORIZATION);
153
+ if (authorization.size() != 1
154
+ || token(authorization.get(0)) == null
155
+ || present(request.getHeader(TRUSTED_CONTEXT_HEADER))
156
+ || present(request.getHeader(HttpHeaders.COOKIE)))
157
+ return deny(response, 401, "scheduled_auth_error");
158
+ return true;
159
+ }
160
+
161
+ private static List<String> headers(HttpServletRequest request, String name) {
162
+ Enumeration<String> values = request.getHeaders(name);
163
+ return values == null ? Collections.<String>emptyList() : Collections.list(values);
164
+ }
165
+
166
+ private static String token(String value) {
167
+ if (value == null || !value.startsWith(AUTHORIZATION_PREFIX)) return null;
168
+ String token = value.substring(AUTHORIZATION_PREFIX.length());
169
+ return token.length() == 43 && token.matches("[A-Za-z0-9_-]{43}") ? token : null;
170
+ }
171
+
172
+ private static Method noArg(Object target, String name) throws NoSuchMethodException {
173
+ return target.getClass().getMethod(name);
174
+ }
175
+
176
+ private static boolean identity(String value) {
177
+ return value != null && !value.trim().isEmpty() && value.length() <= 64;
178
+ }
179
+
180
+ private static boolean present(String value) {
181
+ return value != null && !value.trim().isEmpty();
182
+ }
183
+
184
+ private static Map<String, Object> context(Map<String, Object> row) {
185
+ if (row == null) throw new IllegalArgumentException("scheduled context missing");
186
+ Map<String, Object> context = new LinkedHashMap<>();
187
+ context.put("authenticationType", "SCHEDULED");
188
+ context.put("runId", positiveNumber(row, "runId"));
189
+ context.put("taskId", positiveNumber(row, "taskId"));
190
+ context.put("userId", text(row, "userId", 64));
191
+ context.put("orgCode", text(row, "orgCode", 64));
192
+ context.put("roleId", text(row, "roleId", 64));
193
+ context.put("prompt", text(row, "prompt", 4096));
194
+ context.put("scheduledAt", text(row, "scheduledAt", 64));
195
+ context.put("timezone", text(row, "timezone", 64));
196
+ String expiresAt = text(row, "expiresAt", 64);
197
+ if (!Instant.parse(expiresAt).isAfter(Instant.now()))
198
+ throw new IllegalArgumentException("scheduled context expired");
199
+ context.put("expiresAt", expiresAt);
200
+ return Collections.unmodifiableMap(context);
201
+ }
202
+
203
+ private static long positiveNumber(Map<String, Object> row, String key) {
204
+ Object value = row.get(key);
205
+ if (!(value instanceof Number) || ((Number) value).longValue() <= 0)
206
+ throw new IllegalArgumentException(key + " invalid");
207
+ return ((Number) value).longValue();
208
+ }
209
+
210
+ private static String text(Map<String, Object> row, String key, int max) {
211
+ Object raw = row.get(key);
212
+ if (!(raw instanceof String)) throw new IllegalArgumentException(key + " invalid");
213
+ String value = (String) raw;
214
+ if (value.trim().isEmpty() || value.length() > max)
215
+ throw new IllegalArgumentException(key + " invalid");
216
+ return value;
217
+ }
218
+
219
+ private static boolean deny(HttpServletResponse response, int status, String code) {
220
+ response.setStatus(status);
221
+ response.setContentType("application/json;charset=UTF-8");
222
+ response.setHeader(HttpHeaders.CACHE_CONTROL, "no-store");
223
+ try {
224
+ response.getWriter().write("{\"error\":\"" + code + "\"}");
225
+ } catch (IOException ignored) {}
226
+ return false;
227
+ }
228
+
229
+ }
@@ -0,0 +1,165 @@
1
+ package com.epcc.arkweb.web.llm;
2
+
3
+ import com.union.control.service.AgentExecutionService;
4
+ import com.union.control.service.ConversationService;
5
+ import com.union.control.service.MemoryStoreService;
6
+ import com.union.control.service.RunningAnalysisMockService;
7
+ import com.epcc.arkweb.helper.AuthenticatedRequest;
8
+ import org.springframework.beans.factory.annotation.Autowired;
9
+ import org.springframework.http.HttpHeaders;
10
+ import org.springframework.http.ResponseEntity;
11
+ import org.springframework.web.bind.annotation.PostMapping;
12
+ import org.springframework.web.bind.annotation.RequestBody;
13
+ import org.springframework.web.bind.annotation.RequestMapping;
14
+ import org.springframework.web.bind.annotation.RestController;
15
+
16
+ import java.util.Map;
17
+ import java.util.LinkedHashMap;
18
+
19
+ /** Internal Agent API used by py-app; CAS and scheduled requests use separate guards. */
20
+ @RestController
21
+ @RequestMapping("/agent")
22
+ public class AgentController {
23
+ private static final String EXECUTE_PERMISSION = "/assistantManager/page";
24
+ private final ConversationService conversations;
25
+ private final AgentExecutionService executions;
26
+ private final MemoryStoreService memoryStore;
27
+ private final RunningAnalysisMockService runningAnalysis;
28
+ private final AuthenticatedRequest request;
29
+ private final AgentAuthorizationInterceptor authorization;
30
+
31
+ @Autowired
32
+ public AgentController(
33
+ ConversationService conversations,
34
+ AgentExecutionService executions,
35
+ MemoryStoreService memoryStore,
36
+ RunningAnalysisMockService runningAnalysis,
37
+ AuthenticatedRequest request,
38
+ AgentAuthorizationInterceptor authorization) {
39
+ this.conversations = conversations;
40
+ this.executions = executions;
41
+ this.memoryStore = memoryStore;
42
+ this.runningAnalysis = runningAnalysis;
43
+ this.request = request;
44
+ this.authorization = authorization;
45
+ }
46
+
47
+ /** Validates the run-scoped token and performs the real-time role-resource check. */
48
+ @PostMapping("/scheduledTaskAuthorize")
49
+ public ResponseEntity<Map<String, Object>> scheduledTaskAuthorize(
50
+ @org.springframework.web.bind.annotation.RequestHeader(
51
+ value = HttpHeaders.AUTHORIZATION, required = false) String authorization,
52
+ @RequestBody(required = false) Object payload) {
53
+ if (payload != null && (!(payload instanceof Map) || !((Map<?, ?>) payload).isEmpty()))
54
+ return unauthorized();
55
+ try {
56
+ Map<String, Object> context = this.authorization.authorize(
57
+ authorization, EXECUTE_PERMISSION);
58
+ Map<String, Object> data = new LinkedHashMap<>(context);
59
+ // The value is an opaque, database-backed capability for subsequent tool calls.
60
+ data.put("trustedContext", authorization);
61
+ return ResponseEntity.ok().header(HttpHeaders.CACHE_CONTROL, "no-store")
62
+ .body(success(data));
63
+ } catch (RuntimeException error) {
64
+ return unauthorized();
65
+ }
66
+ }
67
+
68
+ private static ResponseEntity<Map<String, Object>> unauthorized() {
69
+ return ResponseEntity.status(401).header(HttpHeaders.CACHE_CONTROL, "no-store")
70
+ .body(java.util.Collections.<String, Object>singletonMap(
71
+ "error", "scheduled_auth_error"));
72
+ }
73
+
74
+ private static Map<String, Object> success(Map<String, Object> data) {
75
+ Map<String, Object> body = new LinkedHashMap<>();
76
+ body.put("success", true);
77
+ body.put("data", data);
78
+ return body;
79
+ }
80
+
81
+ @PostMapping("/getConversationMessages")
82
+ @AgentPermission("/assistantManager/page")
83
+ public Map<String, Object> messages(
84
+ @RequestBody Map<String, Object> payload) {
85
+ return conversations.conversationMessages(request.json(payload));
86
+ }
87
+
88
+ @PostMapping("/completeRun")
89
+ @AgentPermission("/assistantManager/page")
90
+ public Map<String, Object> completeRun(
91
+ @RequestBody Map<String, Object> payload) {
92
+ return executions.completeRun(request.json(payload));
93
+ }
94
+
95
+ @PostMapping("/memoryStore/read")
96
+ @AgentPermission("/assistantManager/page")
97
+ public Map<String, Object> memoryRead(
98
+ @RequestBody Map<String, Object> payload) {
99
+ return memoryStore.memoryRead(request.json(payload));
100
+ }
101
+
102
+ @PostMapping("/memoryStore/list")
103
+ @AgentPermission("/assistantManager/page")
104
+ public Map<String, Object> memoryList(
105
+ @RequestBody Map<String, Object> payload) {
106
+ return memoryStore.memoryList(request.json(payload));
107
+ }
108
+
109
+ @PostMapping("/memoryStore/operation")
110
+ @AgentPermission("/assistantManager/page")
111
+ public Map<String, Object> memoryOperation(
112
+ @RequestBody Map<String, Object> payload) {
113
+ return memoryStore.memoryOperation(request.json(payload));
114
+ }
115
+
116
+ @PostMapping("/memoryStore/write")
117
+ @AgentPermission("/assistantManager/page")
118
+ public Map<String, Object> memoryWrite(
119
+ @RequestBody Map<String, Object> payload) {
120
+ return memoryStore.memoryWrite(request.json(payload));
121
+ }
122
+
123
+ @PostMapping("/memoryStore/delete")
124
+ @AgentPermission("/assistantManager/page")
125
+ public Map<String, Object> memoryDelete(
126
+ @RequestBody Map<String, Object> payload) {
127
+ return memoryStore.memoryDelete(request.json(payload));
128
+ }
129
+
130
+ @PostMapping("/memoryStore/search")
131
+ @AgentPermission("/assistantManager/page")
132
+ public Map<String, Object> memorySearch(
133
+ @RequestBody Map<String, Object> payload) {
134
+ return memoryStore.memorySearch(request.json(payload));
135
+ }
136
+
137
+ @PostMapping("/getOrgInfo")
138
+ @AgentPermission("/assistantManager/page")
139
+ public Map<String, Object> getOrgInfo(
140
+ @RequestBody Map<String, Object> payload) {
141
+ return runningAnalysis.getOrgInfo(request.json(payload));
142
+ }
143
+
144
+ @PostMapping("/queryBigData")
145
+ @AgentPermission("/assistantManager/page")
146
+ public Map<String, Object> queryBigData(
147
+ @RequestBody Map<String, Object> payload) {
148
+ return runningAnalysis.queryBigData(request.json(payload));
149
+ }
150
+
151
+ @PostMapping("/announceList")
152
+ @AgentPermission("/assistantManager/page")
153
+ public Map<String, Object> announceList(
154
+ @RequestBody Map<String, Object> payload) {
155
+ return runningAnalysis.announceList(request.json(payload));
156
+ }
157
+
158
+ @PostMapping("/getJiraInfo")
159
+ @AgentPermission("/assistantManager/page")
160
+ public Map<String, Object> getJiraInfo(
161
+ @RequestBody Map<String, Object> payload) {
162
+ return runningAnalysis.getJiraInfo(request.json(payload));
163
+ }
164
+
165
+ }
@@ -0,0 +1,13 @@
1
+ package com.epcc.arkweb.web.llm;
2
+
3
+ import java.lang.annotation.ElementType;
4
+ import java.lang.annotation.Retention;
5
+ import java.lang.annotation.RetentionPolicy;
6
+ import java.lang.annotation.Target;
7
+
8
+ /** Permission required by one py-app tool endpoint. */
9
+ @Retention(RetentionPolicy.RUNTIME)
10
+ @Target(ElementType.METHOD)
11
+ public @interface AgentPermission {
12
+ String value();
13
+ }