ConversationScopeTest.java
2.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package com.xly.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xly.agent.AgentIdentity;
import com.xly.config.RedisChatMemoryStore;
import org.junit.jupiter.api.Test;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* 会话归属的**安全不变量**(审计 Critical #5):会话 id 绑定服务端身份,
* 伪造他人的 conversationId 只会落到自己命名空间,且 owns() 拒绝越权访问。
*/
class ConversationScopeTest {
private ConversationService service(StringRedisTemplate redis) {
return new ConversationService(redis, mock(RedisChatMemoryStore.class), new ObjectMapper(),
mock(LedgerService.class), mock(StateService.class),
new EventProjectionService(new ObjectMapper()));
}
private static AgentIdentity user(String id) {
return new AgentIdentity("tok", id, "brand", null);
}
@Test
void foreignConversationIdIsRemappedIntoOwnNamespace() {
ConversationService s = service(mock(StringRedisTemplate.class));
String remapped = s.scopedId(user("u1"), "u2:c-123");
assertTrue(remapped.startsWith("u1:"), "别人的会话 id 必须被重挂到自己名下");
assertFalse(remapped.equals("u2:c-123"), "绝不能直接命中对方的会话键");
assertEquals("u1:c-123", s.scopedId(user("u1"), "u1:c-123"), "自己的 id 原样保留");
assertEquals("u1:default", s.scopedId(user("u1"), null));
}
@Test
void ownsRejectsOtherUsersConversation() {
StringRedisTemplate redis = mock(StringRedisTemplate.class);
@SuppressWarnings("unchecked")
HashOperations<String, Object, Object> hash = mock(HashOperations.class);
when(redis.opsForHash()).thenReturn(hash);
when(hash.hasKey(anyString(), anyString())).thenReturn(true);
ConversationService s = service(redis);
assertTrue(s.owns("u1", "u1:c-1"));
assertFalse(s.owns("u1", "u2:c-1"), "前缀不属于本人 → 拒绝");
assertFalse(s.owns("u1", null));
assertFalse(s.owns(null, "u1:c-1"));
}
}