Skip to content

Commit 3b1bf4d

Browse files
authored
Merge pull request #129 from yaauie/request-authentication-when-required-and-missing
negotiate for credentials when required and not supplied
2 parents b215b4b + 96c23dd commit 3b1bf4d

File tree

7 files changed

+50
-9
lines changed

7 files changed

+50
-9
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## 3.3.6
2+
- Fixes a regression introduced in 3.1.0's migration to the Netty back-end that broke some users'
3+
browser-based workflows. When an instance of this plugin that is configured to require Basic
4+
authentication receives a request that does not include authentication, it now appropriately
5+
includes an `WWW-Authenticate` header in its `401 Unauthorized` response, allowing the browser
6+
to collect credentials before retrying the request.
7+
18
## 3.3.5
29
- Updated jackson databind and Netty dependencies. Additionally, this release removes the dependency on `tcnative` +
310
`boringssl`, using JVM supplied ciphers instead. This may result in fewer ciphers being available if the JCE

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.3.5
1+
3.3.6

lib/logstash/inputs/http/message_handler.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ def validates_token(token)
2222
end
2323
end
2424

25+
def requires_token
26+
!!@auth_token
27+
end
28+
2529
def onNewMessage(remote_address, headers, body)
2630
@input.decode_body(headers, remote_address, body, @default_codec, @additional_codecs)
2731
end

spec/inputs/http_spec.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,11 @@
279279
it "should respond with 401" do
280280
expect(response.code).to eq(401)
281281
end
282+
it 'should include a WWW-Authenticate: Basic header' do
283+
expect(response['WWW-Authenticate']).to_not be_nil
284+
285+
expect(response['WWW-Authenticate']).to start_with('Basic realm=')
286+
end
282287
it "should not generate an event" do
283288
expect(logstash_queue).to be_empty
284289
end
@@ -295,6 +300,9 @@
295300
it "should respond with 401" do
296301
expect(response.code).to eq(401)
297302
end
303+
it 'should not include a WWW-Authenticate header' do
304+
expect(response['WWW-Authenticate']).to be_nil
305+
end
298306
it "should not generate an event" do
299307
expect(logstash_queue).to be_empty
300308
end

src/main/java/org/logstash/plugins/inputs/http/IMessageHandler.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ public interface IMessageHandler {
2323
*/
2424
boolean validatesToken(String token);
2525

26+
boolean requiresToken();
27+
2628
/**
2729
*
2830
* @return copy of the message handler

src/main/java/org/logstash/plugins/inputs/http/MessageHandler.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,6 @@ public Map<String, String> responseHeaders() {
4141
logger.debug("responseHeaders");
4242
return null;
4343
}
44+
45+
public boolean requiresToken() { return false; }
4446
}

src/main/java/org/logstash/plugins/inputs/http/MessageProcessor.java

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import io.netty.handler.codec.http.HttpHeaders;
1212
import io.netty.handler.codec.http.HttpResponse;
1313
import io.netty.handler.codec.http.HttpResponseStatus;
14+
import org.apache.logging.log4j.LogManager;
15+
import org.apache.logging.log4j.Logger;
1416
import org.logstash.plugins.inputs.http.util.RejectableRunnable;
1517

1618
import java.nio.charset.Charset;
@@ -23,7 +25,9 @@ public class MessageProcessor implements RejectableRunnable {
2325
private final String remoteAddress;
2426
private final IMessageHandler messageHandler;
2527
private final HttpResponseStatus responseStatus;
26-
private static final Charset charset = Charset.forName("UTF-8");
28+
29+
private static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
30+
private final static Logger LOGGER = LogManager.getLogger(MessageHandler.class);
2731

2832
MessageProcessor(ChannelHandlerContext ctx, FullHttpRequest req, String remoteAddress,
2933
IMessageHandler messageHandler, HttpResponseStatus responseStatus) {
@@ -47,12 +51,19 @@ public void onRejection() {
4751
public void run() {
4852
try {
4953
final HttpResponse response;
50-
final String token = req.headers().get(HttpHeaderNames.AUTHORIZATION);
51-
req.headers().remove(HttpHeaderNames.AUTHORIZATION);
52-
if (messageHandler.validatesToken(token)) {
53-
response = processMessage();
54+
if (messageHandler.requiresToken() && !req.headers().contains(HttpHeaderNames.AUTHORIZATION)) {
55+
LOGGER.debug("Required authorization not provided; requesting authentication.");
56+
response = generateAuthenticationRequestResponse();
5457
} else {
55-
response = generateFailedResponse(HttpResponseStatus.UNAUTHORIZED);
58+
final String token = req.headers().get(HttpHeaderNames.AUTHORIZATION);
59+
req.headers().remove(HttpHeaderNames.AUTHORIZATION);
60+
if (messageHandler.validatesToken(token)) {
61+
LOGGER.debug("Valid authorization; processing request.");
62+
response = processMessage();
63+
} else {
64+
LOGGER.debug("Invalid authorization; rejecting request.");
65+
response = generateFailedResponse(HttpResponseStatus.UNAUTHORIZED);
66+
}
5667
}
5768
ctx.writeAndFlush(response);
5869
} finally {
@@ -62,7 +73,7 @@ public void run() {
6273

6374
private FullHttpResponse processMessage() {
6475
final Map<String, String> formattedHeaders = formatHeaders(req.headers());
65-
final String body = req.content().toString(charset);
76+
final String body = req.content().toString(UTF8_CHARSET);
6677
if (messageHandler.onNewMessage(remoteAddress, formattedHeaders, body)) {
6778
return generateResponse(messageHandler.responseHeaders());
6879
} else {
@@ -76,6 +87,13 @@ private FullHttpResponse generateFailedResponse(HttpResponseStatus status) {
7687
return response;
7788
}
7889

90+
private FullHttpResponse generateAuthenticationRequestResponse() {
91+
final FullHttpResponse response = new DefaultFullHttpResponse(req.protocolVersion(), HttpResponseStatus.UNAUTHORIZED);
92+
response.headers().set(HttpHeaderNames.WWW_AUTHENTICATE, "Basic realm=\"Logstash HTTP Input\"");
93+
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, 0);
94+
return response;
95+
}
96+
7997
private FullHttpResponse generateResponse(Map<String, String> stringHeaders) {
8098

8199
final FullHttpResponse response = new DefaultFullHttpResponse(
@@ -88,7 +106,7 @@ private FullHttpResponse generateResponse(Map<String, String> stringHeaders) {
88106
response.headers().set(headers);
89107

90108
if (responseStatus != HttpResponseStatus.NO_CONTENT) {
91-
final ByteBuf payload = Unpooled.wrappedBuffer("ok".getBytes(charset));
109+
final ByteBuf payload = Unpooled.wrappedBuffer("ok".getBytes(UTF8_CHARSET));
92110
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, payload.readableBytes());
93111
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
94112
response.content().writeBytes(payload);

0 commit comments

Comments
 (0)