-
Notifications
You must be signed in to change notification settings - Fork 5.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introducing ClientRequestFilter.java: A New Plugin for Applying Request Headers in the Authentication Filter Class #23380
base: master
Are you sure you want to change the base?
Conversation
…ng the plugin in authentication filter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR needs some test cases.
Nit rephrasing of release note entries following the Order of changes in the Release Notes Guidelines.
|
I have added a test case to verify setting values in the request header and committed it. |
@@ -499,6 +499,11 @@ | |||
<artifactId>ratis-common</artifactId> | |||
<optional>true</optional> | |||
</dependency> | |||
<dependency> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do not use Mockito in this project. Can you please refactor your code to avoid its use?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tdcmeehan
Instead of using Mockito to create mock objects and define their behavior, can I manually write simple classes (stubs) that simulate the behavior of the actual classes?
Do you have any suggestion here in order to verify the codebase if value correctly passed to request header?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that is how we go about this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tdcmeehan
I have refactored the code that was added for the test case.
assertEquals("CustomValue", wrappedRequest.getHeader("X-Custom-Header")); | ||
} | ||
|
||
abstract static class HttpServletRequestAdapter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's already a MockHttpServletRequest
, can you use that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tdcmeehan
I have refactored the code to use MockHttpServletRequest. Could you check it?
Thanks.
While we don't use Mockito, the stubs in the test are a little out of control. We need to reduce the size of this simple test. I have a couple of suggestions that you can research to make the testing simpler.
|
@tdcmeehan |
@@ -825,4 +830,25 @@ private static int driftServerPort(DriftServer server) | |||
{ | |||
return ((DriftNettyServerTransport) server.getServerTransport()).getPort(); | |||
} | |||
|
|||
public RequestModifierManager getRequestModifierManager() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please, just add a method to return the RequestModifierManager
, and let users inject whatever custom modifier they wish.
|
||
public class RequestModifierManager | ||
{ | ||
private final List<RequestModifier> requestModifiers; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be thread safe. I recommend using a CopyOnWriteArrayList
.
// authentication succeeded | ||
nextFilter.doFilter(withPrincipal(request, principal), response); | ||
CustomHttpServletRequestWrapper wrappedRequest = withPrincipal(request, principal); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of modifying the authentication filter, this should just go in its own filter that is applied after the authentication filter.
this.customHeaders = new HashMap<>(); | ||
} | ||
|
||
public void addHeader(String name, String value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused
public static class CustomHttpServletRequestWrapper | ||
extends HttpServletRequestWrapper | ||
{ | ||
private final Map<String, String> customHeaders; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use a concurrent map
import java.util.Map; | ||
import java.util.Optional; | ||
|
||
public interface RequestModifier |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's call this something more descriptive, such as ClientRequestFilter
.
- Improved code flow based on the review comments
@tdcmeehan |
|
||
public class ClientRequestFilterManager | ||
{ | ||
private final CopyOnWriteArrayList<ClientRequestFilter> clientRequestFilters; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private final CopyOnWriteArrayList<ClientRequestFilter> clientRequestFilters; | |
private final List<ClientRequestFilter> clientRequestFilters; |
private final CopyOnWriteArrayList<ClientRequestFilter> clientRequestFilters; | ||
public ClientRequestFilterManager() | ||
{ | ||
this.clientRequestFilters = new CopyOnWriteArrayList<>(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private final CopyOnWriteArrayList<ClientRequestFilter> clientRequestFilters; | |
public ClientRequestFilterManager() | |
{ | |
this.clientRequestFilters = new CopyOnWriteArrayList<>(); | |
} | |
private final List<ClientRequestFilter> clientRequestFilters = new CopyOnWriteArrayList<>(); |
|
||
public List<ClientRequestFilter> getClientRequestFilters() | ||
{ | ||
return Collections.unmodifiableList(clientRequestFilters); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return Collections.unmodifiableList(clientRequestFilters); | |
return ImmutableList.copyOf(clientRequestFilters); |
extraHeaderValueMap.ifPresent(map -> { | ||
for (Map.Entry<String, String> extraHeaderEntry : map.entrySet()) { | ||
if (request.getHeader(extraHeaderEntry.getKey()) == null) { | ||
extraHeadersMap.putIfAbsent(extraHeaderEntry.getKey(), extraHeaderEntry.getValue()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we should silently not put the header if there's an existing header. I think we should consider the following:
- The SPI contract mandates that any potential header is known upfront.
- The runtime will validate that the headers that are requested to be modified match with what is actually returned.
- There is a blocklist of headers that the plugin should not modify (such as query ID).
- The runtime will validate that all registered filters only return a disjoint set of headers being added. If there is a conflict, this is a fatal error that will require an administrator to fix (but un-registering one of these plugins).
- If there is a conflict here, then an exception is thrown.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tdcmeehan
I have gone through your comments and added a few validations in the current implementation. I can verify that with you. However, I have to know about the headers in the blocklist that should not be modified.
Could you help?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's start with the query ID, trace ID and transaction ID in PrestoHeaders.java.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have incorporated the changes mentioned in your comment. Could you please review them and share your suggestions?
private final Map<String, String> customHeaders; | ||
|
||
public CustomHttpServletRequestWrapper(HttpServletRequest request) | ||
{ | ||
super(request); | ||
this.customHeaders = new ConcurrentHashMap<>(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private final Map<String, String> customHeaders; | |
public CustomHttpServletRequestWrapper(HttpServletRequest request) | |
{ | |
super(request); | |
this.customHeaders = new ConcurrentHashMap<>(); | |
} | |
private final Map<String, String> customHeaders = new ConcurrentHashMap<>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some feedback is still unadressed.
for (Map.Entry<String, String> extraHeaderEntry : map.entrySet()) { | ||
String headerKey = extraHeaderEntry.getKey(); | ||
if (headersBlockList.contains(headerKey)) { | ||
throw new RuntimeException("Modification attempt detected: The header " + headerKey + " is present in the blocked headers list."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please throw a PrestoException and introduce a new error code for this. It should be a system error.
while (originalHeaderNames.hasMoreElements()) { | ||
headerNames.add(originalHeaderNames.nextElement()); | ||
} | ||
return Collections.enumeration(headerNames); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use Immutable collections.
@tdcmeehan Could you help me identify which feedback is still pending? I believe I have addressed feedback items 2, 3, 4, and 5 from the list above. |
Please scroll up in this PR and examine feedback on the code that is still remaining. |
Yeah. @tdcmeehan I have incorporated all feedback items now. Kindly review them and share your suggestions? Thanks. |
ClientRequestFilter customModifier = new ClientRequestFilter() | ||
{ | ||
@Override | ||
public List<String> getHeaderNames() | ||
{ | ||
return Collections.singletonList("X-Custom-Header"); | ||
} | ||
@Override | ||
public <T> Optional<Map<String, String>> getExtraHeaders(T additionalInfo) | ||
{ | ||
Map<String, String> headers = new HashMap<>(); | ||
headers.put("X-Custom-Header", "CustomValue_1"); | ||
return Optional.of(headers); | ||
} | ||
}; | ||
|
||
ClientRequestFilter customModifierConflict = new ClientRequestFilter() | ||
{ | ||
@Override | ||
public List<String> getHeaderNames() | ||
{ | ||
return Collections.singletonList("X-Custom-Header"); | ||
} | ||
@Override | ||
public <T> Optional<Map<String, String>> getExtraHeaders(T additionalInfo) | ||
{ | ||
Map<String, String> headers = new HashMap<>(); | ||
headers.put("X-Custom-Header", "CustomValue_2"); | ||
return Optional.of(headers); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please reduce duplicate code. Consider an inner helper class.
} | ||
|
||
static class ConcreteHttpServletRequest | ||
extends MockHttpServletRequest |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not modify MockHttpServletRequest
directly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In ConcreteHttpServletRequest, I am storing additional headers in the customHeaders map and overriding the getHeaders method to manage them. This functionality isn't natively provided by MockHttpServletRequest, so extending it makes sense.
@tdcmeehan |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please refactor tests to use the code under test.
for (Map.Entry<String, String> extraHeaderEntry : map.entrySet()) { | ||
String headerKey = extraHeaderEntry.getKey(); | ||
if (headersBlockList.contains(headerKey)) { | ||
throw new PrestoException(HEADER_MODIFICATION_ATTEMPT, "Modification attempt detected: The header " + headerKey + " is present in the blocked headers list."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of mentioning a "blocked headers list", just list out the headers that are not allowed to be modified.
throw new PrestoException(HEADER_MODIFICATION_ATTEMPT, "Modification attempt detected: The header " + headerKey + " is present in the blocked headers list."); | ||
} | ||
if (globallyAddedHeaders.contains(headerKey)) { | ||
throw new RuntimeException("Header conflict detected: " + headerKey + " already added by another filter."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not throw RuntimeException, throw PrestoException.
for (Map.Entry<String, String> extraHeaderEntry : map.entrySet()) { | ||
String headerKey = extraHeaderEntry.getKey(); | ||
if (headersBlockList.contains(headerKey)) { | ||
throw new RuntimeException("Modification attempt detected: The header " + headerKey + " is present in the blocked headers list."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we duplicating code here? I am very confused about how this test works.
@tdcmeehan |
Description
Motivation and Context
Impact
Test Plan
Contributor checklist
Release Notes
Please follow release notes guidelines and fill in the release notes below.