Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3524,44 +3524,62 @@ void handleResponse(AbstractResponse abstractResponse) {
for (final Node node : allNodes) {
final long nowList = time.milliseconds();
runnable.call(new Call("listGroups", deadline, new ConstantNodeIdProvider(node.id())) {

// If only regular consumer group types are required, we can try an earlier request version if
// UnsupportedVersionException is thrown
final boolean canTryEarlierRequestVersion = options.regularConsumerGroupTypes();
boolean tryUsingEarlierRequestVersion = false;
Comment on lines +3527 to +3531
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feature Flag Pattern

Boolean flag controls version fallback behavior instead of using a strategy pattern. This creates implicit coupling between flag state and method behavior, making future version handling changes more complex.

Standards
  • Design-Pattern-Strategy
  • Clean-Code-Flags


@Override
ListGroupsRequest.Builder createRequest(int timeoutMs) {
List<String> groupTypes = options.types()
.stream()
.map(GroupType::toString)
.collect(Collectors.toList());
List<String> groupStates = options.groupStates()
.stream()
.map(GroupState::toString)
.collect(Collectors.toList());
return new ListGroupsRequest.Builder(new ListGroupsRequestData()
.setTypesFilter(groupTypes)
.setStatesFilter(groupStates)
);
if (tryUsingEarlierRequestVersion) {
List<String> groupStates = options.groupStates()
.stream()
.map(GroupState::toString)
.collect(Collectors.toList());
return new ListGroupsRequest.Builder(new ListGroupsRequestData()
.setStatesFilter(groupStates)
);
} else {
List<String> groupTypes = options.types()
.stream()
.map(GroupType::toString)
.collect(Collectors.toList());
List<String> groupStates = options.groupStates()
.stream()
.map(GroupState::toString)
.collect(Collectors.toList());
return new ListGroupsRequest.Builder(new ListGroupsRequestData()
.setTypesFilter(groupTypes)
.setStatesFilter(groupStates)
);
Comment on lines +3544 to +3555
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary Collection Conversion

Converting Set to List unnecessarily creates intermediate collections. This conversion happens on every request in a hot path. For large numbers of group types/states, this creates avoidable object allocation and garbage collection pressure.

Suggested change
List<String> groupTypes = options.types()
.stream()
.map(GroupType::toString)
.collect(Collectors.toList());
List<String> groupStates = options.groupStates()
.stream()
.map(GroupState::toString)
.collect(Collectors.toList());
return new ListGroupsRequest.Builder(new ListGroupsRequestData()
.setTypesFilter(groupTypes)
.setStatesFilter(groupStates)
);
List<String> groupTypes = new ArrayList<>(options.types().size());
for (GroupType type : options.types()) {
groupTypes.add(type.toString());
}
List<String> groupStates = new ArrayList<>(options.groupStates().size());
for (GroupState state : options.groupStates()) {
groupStates.add(state.toString());
}
return new ListGroupsRequest.Builder(new ListGroupsRequestData()
.setTypesFilter(groupTypes)
.setStatesFilter(groupStates)
);
Standards
  • ISO-IEC-25010-Performance-Resource-Utilization
  • Algorithm-Opt-Collection-Conversion

}
Comment on lines +3528 to +3556
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate Protocol Logic

Duplicate code for groupStates conversion appears in both branches. This violates DRY principle and increases maintenance burden when modifying the conversion logic.

Standards
  • Clean-Code-DRY
  • SOLID-SRP

Comment on lines +3535 to +3556
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Repeated Request Logic

Duplicated code for creating groupStates list in both branches increases maintenance burden. The stream transformation of groupStates is repeated, violating DRY principle. Extract common code to reduce duplication and improve maintainability.

Standards
  • Clean-Code-DRY
  • Refactoring-Extract-Method
  • Maintainability-Quality-Duplication

}

private void maybeAddGroup(ListGroupsResponseData.ListedGroup group) {
final String groupId = group.groupId();
final Optional<GroupType> type;
if (group.groupType() == null || group.groupType().isEmpty()) {
type = Optional.empty();
} else {
type = Optional.of(GroupType.parse(group.groupType()));
}
final String protocolType = group.protocolType();
final Optional<GroupState> groupState;
if (group.groupState() == null || group.groupState().isEmpty()) {
groupState = Optional.empty();
} else {
groupState = Optional.of(GroupState.parse(group.groupState()));
String protocolType = group.protocolType();
if (options.protocolTypes().isEmpty() || options.protocolTypes().contains(protocolType)) {
Comment on lines +3560 to +3561
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Protocol Type Filtering Lacks Null Safety

The protocol type filtering doesn't handle null protocol types safely. If group.protocolType() returns null, the contains() check will throw NullPointerException. This would cause the listing operation to fail unexpectedly.

Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • ISO-IEC-25010-Functional-Correctness-Appropriateness
  • SRE-Error-Handling

Comment on lines +3560 to +3561
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential NullPointerException

No null check on protocolType before using it in contains() method. If broker returns null protocolType, this will cause NullPointerException, crashing the client application. This breaks reliability when interacting with non-standard broker responses.

Suggested change
String protocolType = group.protocolType();
if (options.protocolTypes().isEmpty() || options.protocolTypes().contains(protocolType)) {
String protocolType = group.protocolType();
if (options.protocolTypes().isEmpty() || (protocolType != null && options.protocolTypes().contains(protocolType))) {
Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • ISO-IEC-25010-Functional-Correctness-Robustness
  • DbC-Defensive-Programming

Comment on lines +3560 to +3561
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Protocol Type Filtering

Protocol type filtering happens after receiving response from broker, not in request. This creates unnecessary network traffic and processing overhead when filtering could be done server-side. May cause performance degradation under high load.

Suggested change
String protocolType = group.protocolType();
if (options.protocolTypes().isEmpty() || options.protocolTypes().contains(protocolType)) {
if (tryUsingEarlierRequestVersion) {
List<String> groupStates = options.groupStates()
.stream()
.map(GroupState::toString)
.collect(Collectors.toList());
return new ListGroupsRequest.Builder(new ListGroupsRequestData()
.setStatesFilter(groupStates)
);
} else {
List<String> groupTypes = options.types()
.stream()
.map(GroupType::toString)
.collect(Collectors.toList());
List<String> groupStates = options.groupStates()
.stream()
.map(GroupState::toString)
.collect(Collectors.toList());
List<String> protocolTypes = options.protocolTypes()
.stream()
.collect(Collectors.toList());
return new ListGroupsRequest.Builder(new ListGroupsRequestData()
.setTypesFilter(groupTypes)
.setStatesFilter(groupStates)
.setProtocolTypesFilter(protocolTypes)
);
}
Standards
  • ISO-IEC-25010-Reliability-Resource-Utilization
  • ISO-IEC-25010-Functional-Correctness-Appropriateness
  • SRE-Efficiency

Comment on lines +3560 to +3561
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Protocol Type Filtering

The protocol type filtering doesn't handle null protocol types safely. If group.protocolType() returns null, the contains() check will throw NullPointerException, causing request failure. This breaks reliability when processing groups with null protocol types.

Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • ISO-IEC-25010-Functional-Correctness-Appropriateness
  • SRE-Error-Handling

Comment on lines +3560 to +3561
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential NPE Risk

The protocolType variable may be null, causing NullPointerException when calling contains(). While the protocol type is unlikely to be null in practice, defensive null checking would prevent potential runtime failures.

Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • ISO-IEC-25010-Functional-Correctness-Appropriateness
  • DbC-Precondition-Validation
  • SRE-Error-Prevention

Comment on lines +3560 to +3561
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Protocol Type Filter

The protocol type filter check doesn't handle null protocol types correctly. If group.protocolType() returns null, the contains() check will throw NullPointerException when options.protocolTypes() contains non-null values.

Standards
  • Algorithm-Correctness-Null-Safety
  • Business-Rule-Input-Validation
  • Logic-Verification-Defensive-Programming

Comment on lines +3560 to +3561
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Protocol Type Filtering

The protocol type filtering implementation checks if the protocol type is empty before applying the filter. This could allow groups with empty protocol types to bypass filtering restrictions, potentially exposing groups that should be restricted. An attacker could exploit this to gain visibility into groups they shouldn't have access to.

Standards
  • CWE-284
  • OWASP-A01
  • NIST-SSDF-PW.1

Comment on lines +3560 to +3561
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate Protocol Filtering

Protocol type filtering is duplicated between client and server. The server already filters by protocol type in ListGroupsRequest, making client-side filtering redundant. This creates maintenance burden when protocol handling changes.

Standards
  • Clean-Code-DRY
  • Maintainability-Quality-Duplication
  • Design-Pattern-Consistency

final String groupId = group.groupId();
Comment on lines +3560 to +3562
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing Protocol Type Validation

Protocol type validation allows null values without explicit checks. An attacker could exploit null protocol types to bypass filtering. Implementing null-safety checks prevents unexpected behavior and potential security bypasses.

Suggested change
String protocolType = group.protocolType();
if (options.protocolTypes().isEmpty() || options.protocolTypes().contains(protocolType)) {
final String groupId = group.groupId();
String protocolType = group.protocolType() != null ? group.protocolType() : "";
if (options.protocolTypes().isEmpty() || options.protocolTypes().contains(protocolType)) {
final String groupId = group.groupId();
Standards
  • CWE-697
  • OWASP-A04

Comment on lines +3561 to +3562
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing Protocol Type Validation

The code doesn't handle null protocol types before checking containment. If protocolType is null, the contains() check will throw NullPointerException, causing request processing to fail. This creates a reliability issue when processing responses with null protocol types.

Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • ISO-IEC-25010-Functional-Correctness-Appropriateness
  • DbC-Precondition-Validation

final Optional<GroupType> type;
if (group.groupType() == null || group.groupType().isEmpty()) {
type = Optional.empty();
} else {
type = Optional.of(GroupType.parse(group.groupType()));
}
final Optional<GroupState> groupState;
Comment on lines 3559 to +3569
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Client-Side Protocol Type Filtering Bypass

The protocol type filtering is performed client-side after receiving the response from the server. This means that even if a user is only authorized to see certain protocol types, they could modify the client code to bypass this filtering and see all group types returned by the server. The server should be responsible for enforcing access controls based on protocol types, not the client. This could lead to information disclosure where users can access group data they shouldn't have permission to view.

Suggested change
private void maybeAddGroup(ListGroupsResponseData.ListedGroup group) {
final String groupId = group.groupId();
final Optional<GroupType> type;
if (group.groupType() == null || group.groupType().isEmpty()) {
type = Optional.empty();
} else {
type = Optional.of(GroupType.parse(group.groupType()));
}
final String protocolType = group.protocolType();
final Optional<GroupState> groupState;
if (group.groupState() == null || group.groupState().isEmpty()) {
groupState = Optional.empty();
} else {
groupState = Optional.of(GroupState.parse(group.groupState()));
String protocolType = group.protocolType();
if (options.protocolTypes().isEmpty() || options.protocolTypes().contains(protocolType)) {
final String groupId = group.groupId();
final Optional<GroupType> type;
if (group.groupType() == null || group.groupType().isEmpty()) {
type = Optional.empty();
} else {
type = Optional.of(GroupType.parse(group.groupType()));
}
final Optional<GroupState> groupState;
// In createRequest method, add protocol types to the request
List<String> protocolTypes = new ArrayList<>(options.protocolTypes());
return new ListGroupsRequest.Builder(new ListGroupsRequestData()
.setTypesFilter(groupTypes)
.setStatesFilter(groupStates)
.setProtocolTypesFilter(protocolTypes));

if (group.groupState() == null || group.groupState().isEmpty()) {
groupState = Optional.empty();
} else {
groupState = Optional.of(GroupState.parse(group.groupState()));
}
final GroupListing groupListing = new GroupListing(
groupId,
type,
protocolType,
groupState
);
results.addListing(groupListing);
}
Comment on lines +3560 to 3582
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Protocol Type Filtering Logic Inconsistency

Protocol type filtering happens client-side after receiving results, while type filtering happens server-side. This inconsistency causes unnecessary network traffic and processing when many groups exist, especially when using earlier request versions.

Suggested change
String protocolType = group.protocolType();
if (options.protocolTypes().isEmpty() || options.protocolTypes().contains(protocolType)) {
final String groupId = group.groupId();
final Optional<GroupType> type;
if (group.groupType() == null || group.groupType().isEmpty()) {
type = Optional.empty();
} else {
type = Optional.of(GroupType.parse(group.groupType()));
}
final Optional<GroupState> groupState;
if (group.groupState() == null || group.groupState().isEmpty()) {
groupState = Optional.empty();
} else {
groupState = Optional.of(GroupState.parse(group.groupState()));
}
final GroupListing groupListing = new GroupListing(
groupId,
type,
protocolType,
groupState
);
results.addListing(groupListing);
}
String protocolType = group.protocolType();
// When using earlier request version, we need to filter by protocol type client-side
// Otherwise, server-side filtering was already applied
if (tryUsingEarlierRequestVersion && !options.protocolTypes().isEmpty() && !options.protocolTypes().contains(protocolType)) {
return; // Skip this group as it doesn't match protocol type filter
}
final String groupId = group.groupId();
final Optional<GroupType> type;
if (group.groupType() == null || group.groupType().isEmpty()) {
type = Optional.empty();
} else {
type = Optional.of(GroupType.parse(group.groupType()));
}
final Optional<GroupState> groupState;
if (group.groupState() == null || group.groupState().isEmpty()) {
groupState = Optional.empty();
} else {
groupState = Optional.of(GroupState.parse(group.groupState()));
}
final GroupListing groupListing = new GroupListing(
groupId,
type,
protocolType,
groupState
);
results.addListing(groupListing);
Standards
  • ISO-IEC-25010-Functional-Correctness-Appropriateness
  • Algorithm-Correctness-Data-Flow
  • Business-Rule-Protocol-Filtering

Comment on lines +3560 to 3582
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Protocol Filter Inconsistency

Protocol type filtering is applied client-side while group type filtering is applied server-side. This inconsistency could lead to incorrect filtering behavior when both filters are specified, as protocol filtering happens after receiving results.

Standards
  • Logic-Verification-Consistency
  • Business-Rule-Validation
  • Algorithm-Correctness-Filter-Chain

Comment on lines +3560 to 3582
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Protocol Type Filtering

Protocol type filtering happens client-side after fetching results from server. This can cause unnecessary network traffic and processing overhead when many groups exist but few match the filter.

Standards
  • ISO-IEC-25010-Reliability-Maturity
  • ISO-IEC-25010-Functional-Correctness-Appropriateness
  • SRE-Efficiency

Comment on lines +3560 to 3582
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Protocol Filter Duplication

Client-side filtering duplicates server-side filtering logic. This creates maintenance overhead as filtering logic must be synchronized between client and server implementations.

Standards
  • Clean-Code-DRY
  • Design-Pattern-Responsibility

final GroupListing groupListing = new GroupListing(
groupId,
type,
protocolType,
groupState
);
results.addListing(groupListing);
}

@Override
Expand All @@ -3582,6 +3600,23 @@ void handleResponse(AbstractResponse abstractResponse) {
}
}

@Override
boolean handleUnsupportedVersionException(final UnsupportedVersionException exception) {
// If we cannot try the earlier request version, give up
if (!canTryEarlierRequestVersion) {
return false;
}

// If have already tried the earlier request version, give up
if (tryUsingEarlierRequestVersion) {
return false;
}
Comment on lines +3605 to +3613
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incomplete Error Handling

The handleUnsupportedVersionException method returns false when it cannot handle the exception, but doesn't provide any error information about why the version is unsupported. This can lead to silent failures and difficult debugging when version compatibility issues occur.

Suggested change
// If we cannot try the earlier request version, give up
if (!canTryEarlierRequestVersion) {
return false;
}
// If have already tried the earlier request version, give up
if (tryUsingEarlierRequestVersion) {
return false;
}
// If we cannot try the earlier request version, give up
if (!canTryEarlierRequestVersion) {
log.debug("Cannot handle UnsupportedVersionException for listGroups request: only regular consumer group types can use earlier version");
return false;
}
// If have already tried the earlier request version, give up
if (tryUsingEarlierRequestVersion) {
log.debug("Already tried earlier request version for listGroups, cannot handle UnsupportedVersionException");
return false;
}
Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • ISO-IEC-25010-Functional-Correctness-Appropriateness
  • DbC-Error-Reporting
  • SRE-Observability


// Have a try using the earlier request version
tryUsingEarlierRequestVersion = true;
return true;
Comment on lines +3616 to +3617
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential SSRF Risk

Automatic fallback to earlier protocol versions could bypass security controls. Attackers could exploit older, less secure protocol versions with fewer filtering capabilities.

Standards
  • CWE-942
  • OWASP-A10

Comment on lines +3605 to +3617
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incomplete Version Fallback

The version fallback mechanism only attempts one earlier version. If multiple version downgrades are needed, the code will fail after a single retry. This creates a reliability gap when interacting with brokers supporting much older versions.

Standards
  • ISO-IEC-25010-Reliability-Compatibility
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • SRE-Graceful-Degradation

}
Comment on lines +3604 to +3618
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incomplete Error Handling

The error handling logic doesn't reset filters when falling back to earlier request version. When using earlier version without type filtering capability, the client should adjust its expectations accordingly.

Standards
  • Error-Handling-Completeness
  • Logic-Verification-State-Transitions
  • Algorithm-Correctness-Fallback-Logic


@Override
void handleFailure(Throwable throwable) {
synchronized (results) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,38 @@ public class ListGroupsOptions extends AbstractOptions<ListGroupsOptions> {
private Set<GroupType> types = Set.of();
private Set<String> protocolTypes = Set.of();

// Types filter is supported by brokers with version 4.0.0 or later. Older brokers only support
// classic groups, so listing consumer groups on an older broker does not need to use a types filter.
private boolean regularConsumerGroupTypes = false;

/**
* Only consumer groups will be returned by listGroups().
* This operation sets filters on group type and protocol type which select consumer groups.
*/
public static ListGroupsOptions forConsumerGroups() {
return new ListGroupsOptions()
.withTypes(Set.of(GroupType.CLASSIC, GroupType.CONSUMER))
.withTypes(Set.of(GroupType.CLASSIC, GroupType.CONSUMER), true)
.withProtocolTypes(Set.of("", ConsumerProtocol.PROTOCOL_TYPE));
}

/**
* Only share groups will be returned by listGroups().
* This operation sets a filter on group type which select share groups.
*/
public static ListGroupsOptions forShareGroups() {
return new ListGroupsOptions()
.withTypes(Set.of(GroupType.SHARE));
}

/**
* Only streams groups will be returned by listGroups().
* This operation sets a filter on group type which select streams groups.
*/
public static ListGroupsOptions forStreamsGroups() {
return new ListGroupsOptions()
.withTypes(Set.of(GroupType.STREAMS));
}

/**
* If groupStates is set, only groups in these states will be returned by listGroups().
* Otherwise, all groups are returned.
Expand All @@ -56,6 +78,10 @@ public ListGroupsOptions inGroupStates(Set<GroupState> groupStates) {
return this;
}

/**
* If protocol types is set, only groups of these protocol types will be returned by listGroups().
* Otherwise, all groups are returned.
*/
public ListGroupsOptions withProtocolTypes(Set<String> protocolTypes) {
this.protocolTypes = (protocolTypes == null || protocolTypes.isEmpty()) ? Set.of() : Set.copyOf(protocolTypes);
return this;
Comment on lines 85 to 87
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Insecure Default in Protocol Type Filtering

The withProtocolTypes method treats null or empty sets as 'no filtering' rather than 'no access'. This follows the principle of being permissive by default, which is not a secure design pattern. If a developer forgets to specify protocol types, the system will allow access to all protocol types rather than denying access by default, potentially leading to unintended information disclosure.

Suggested change
public ListGroupsOptions withProtocolTypes(Set<String> protocolTypes) {
this.protocolTypes = (protocolTypes == null || protocolTypes.isEmpty()) ? Set.of() : Set.copyOf(protocolTypes);
return this;
public ListGroupsOptions withProtocolTypes(Set<String> protocolTypes) {
// Default to an empty set (no access) if null is provided
this.protocolTypes = (protocolTypes == null) ? Set.of() : Set.copyOf(protocolTypes);
return this;
}

Expand All @@ -66,7 +92,12 @@ public ListGroupsOptions withProtocolTypes(Set<String> protocolTypes) {
* Otherwise, all groups are returned.
*/
public ListGroupsOptions withTypes(Set<GroupType> types) {
return this.withTypes(types, false);
}

ListGroupsOptions withTypes(Set<GroupType> types, boolean regularConsumerGroupTypes) {
this.types = (types == null || types.isEmpty()) ? Set.of() : Set.copyOf(types);
this.regularConsumerGroupTypes = regularConsumerGroupTypes;
return this;
Comment on lines +98 to 101
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Boolean Flag Parameter

Boolean parameter creates unclear API and makes method calls harder to understand. The regularConsumerGroupTypes flag hides intention and creates maintenance burden. Consider using descriptive method names or builder pattern instead.

Standards
  • Clean-Code-Functions
  • Maintainability-Quality-API-Design
  • Clean-Code-Boolean-Parameters

}

Expand All @@ -90,4 +121,8 @@ public Set<String> protocolTypes() {
public Set<GroupType> types() {
return types;
}

boolean regularConsumerGroupTypes() {
return regularConsumerGroupTypes;
}
}
Loading