This is an automated email from the ASF dual-hosted git repository.
lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git
The following commit(s) were added to refs/heads/rocketmq-studio by this push:
new f9f4ef15c fix(group): enforce ownership in offset reset previews
(#5418)
f9f4ef15c is described below
commit f9f4ef15c68b65928feb8f04f04cd2ae0e51fc4c
Author: coder999o <[email protected]>
AuthorDate: Fri Oct 9 11:25:52 2026 +0800
fix(group): enforce ownership in offset reset previews (#5418)
`previewResetOffset` normalized the instance id but never checked
ownership, so a cross-instance
preview reported "can reset" for a group or topic belonging to another
instance, and the AI tool's
plan step contradicted its own execute step. Route it through
`requireWriteInstance` for both the
group and the topic resource, exactly as `resetOffset` and
`skipAccumulated` already do.
---
.../studio/instance/topic/MetadataService.java | 5 ++--
.../studio/instance/topic/MetadataServiceTest.java | 30 ++++++++++++++++++++++
2 files changed, 33 insertions(+), 2 deletions(-)
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/MetadataService.java
b/server/src/main/java/org/apache/rocketmq/studio/instance/topic/MetadataService.java
index dbd5dace1..1cc1562f9 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/MetadataService.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/instance/topic/MetadataService.java
@@ -552,10 +552,11 @@ public class MetadataService {
public ResetConsumerOffsetPreviewVO previewResetOffset(String instanceId,
String name,
long timestamp,
String topic) {
- instanceId = normalizeInstanceId(instanceId);
String groupName = requireName(name, "consumer group name");
String topicName = requireName(topic, "topic name");
- return resolve(instanceId).previewResetOffset(instanceId, groupName,
timestamp, topicName);
+ String target = requireWriteInstance(instanceId, new
Resource(Kind.GROUP, groupName), true);
+ requireWriteInstance(target, ownershipGuard.topicResource(topicName),
true);
+ return resolve(target).previewResetOffset(target, groupName,
timestamp, topicName);
}
public void resetOffset(String instanceId, String name, long timestamp,
String topic) {
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/instance/topic/MetadataServiceTest.java
b/server/src/test/java/org/apache/rocketmq/studio/instance/topic/MetadataServiceTest.java
index e2535c047..b94c5db76 100644
---
a/server/src/test/java/org/apache/rocketmq/studio/instance/topic/MetadataServiceTest.java
+++
b/server/src/test/java/org/apache/rocketmq/studio/instance/topic/MetadataServiceTest.java
@@ -1001,6 +1001,36 @@ class MetadataServiceTest {
verifyNoInteractions(apacheProvider);
}
+ @Test
+ void previewResetOffsetShouldRejectForeignGroupBeforeProviderAccess() {
+ doThrow(new BusinessException(409, "Resource belongs to another
instance"))
+ .when(ownershipGuard).check(any(), eq(new
ResourceOwnershipGuard.Resource(
+ ResourceOwnershipGuard.Kind.GROUP, "cg-orders")),
eq(true));
+
+ assertThatThrownBy(() -> metadataService.previewResetOffset(
+ "instance-a", "cg-orders", 1784246400000L, "orders"))
+ .isInstanceOf(BusinessException.class)
+ .hasMessage("Resource belongs to another instance")
+ .satisfies(error -> assertThat(((BusinessException)
error).getCode()).isEqualTo(409));
+ verifyNoInteractions(apacheProvider);
+ }
+
+ @Test
+ void previewResetOffsetShouldRejectForeignTopicBeforeProviderAccess() {
+ when(ownershipGuard.check(any(), eq(new
ResourceOwnershipGuard.Resource(
+ ResourceOwnershipGuard.Kind.GROUP, "cg-orders")),
eq(true))).thenReturn(null);
+ doThrow(new BusinessException(409, "Resource belongs to another
instance"))
+ .when(ownershipGuard).check(any(), eq(new
ResourceOwnershipGuard.Resource(
+ ResourceOwnershipGuard.Kind.TOPIC, "orders")),
eq(true));
+
+ assertThatThrownBy(() -> metadataService.previewResetOffset(
+ "instance-a", "cg-orders", 1784246400000L, "orders"))
+ .isInstanceOf(BusinessException.class)
+ .hasMessage("Resource belongs to another instance")
+ .satisfies(error -> assertThat(((BusinessException)
error).getCode()).isEqualTo(409));
+ verifyNoInteractions(apacheProvider);
+ }
+
private ConsumerGroupVO consumerGroup(String name, String namespace, long
lag, SubscriptionMode mode) {
ConsumerGroupVO group = new ConsumerGroupVO();
group.setName(name);