Skip to content
This repository was archived by the owner on Oct 30, 2023. It is now read-only.
Merged
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
@@ -0,0 +1,40 @@
package org.littleshoot.proxy;

import org.littleshoot.proxy.impl.ProxyUtils;

import java.util.Date;

import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;

public abstract class AbstractProxyAuthenticator implements ProxyAuthenticator {

@Override
public abstract boolean authenticate(HttpRequest request);

@Override
public abstract String getRealm();

@Override
public FullHttpResponse authenticationFailureResponse(HttpRequest request) {
String body = "<!DOCTYPE HTML \"-//IETF//DTD HTML 2.0//EN\">\n"
+ "<html><head>\n"
+ "<title>407 Proxy Authentication Required</title>\n"
+ "</head><body>\n"
+ "<h1>Proxy Authentication Required</h1>\n"
+ "<p>This server could not verify that you\n"
+ "are authorized to access the document\n"
+ "requested. Either you supplied the wrong\n"
+ "credentials (e.g., bad password), or your\n"
+ "browser doesn't understand how to supply\n"
+ "the credentials required.</p>\n" + "</body></html>\n";
FullHttpResponse response = ProxyUtils.createFullHttpResponse(HttpVersion.HTTP_1_1,
HttpResponseStatus.PROXY_AUTHENTICATION_REQUIRED, body);
HttpHeaders.setDate(response, new Date());
response.headers().set("Proxy-Authenticate", "Basic realm=\"" + (getRealm() == null ? "Restricted Files" : getRealm()) + "\"");
return response;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.littleshoot.proxy;

import com.google.common.io.BaseEncoding;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.charset.Charset;
import java.util.List;

import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpRequest;

/**
* Basic Auth user/password authenticator
*/
public abstract class BasicProxyAuthenticator extends AbstractProxyAuthenticator {

private static final Logger LOG = LoggerFactory.getLogger(BasicProxyAuthenticator.class);

@Override
public boolean authenticate(HttpRequest request) {
if (!request.headers().contains(HttpHeaders.Names.PROXY_AUTHORIZATION)) {
return false;
}

List<String> values = request.headers().getAll(HttpHeaders.Names.PROXY_AUTHORIZATION);
String fullValue = values.iterator().next();
String value = StringUtils.substringAfter(fullValue, "Basic ").trim();

if (StringUtils.isNotEmpty(value)) {
byte[] decodedValue = BaseEncoding.base64().decode(value);
String decodedString = new String(decodedValue, Charset.forName("UTF-8"));

String userName = StringUtils.substringBefore(decodedString, ":");
String password = StringUtils.substringAfter(decodedString, ":");

return authenticate(userName, password, request);
}

LOG.debug("Invalid authentication scheme. Expected 'Basic'");
return false;
}

public abstract boolean authenticate(String username, String password, HttpRequest request);
}
28 changes: 18 additions & 10 deletions src/main/java/org/littleshoot/proxy/ProxyAuthenticator.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
package org.littleshoot.proxy;

import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpRequest;

/**
* Interface for objects that can authenticate someone for using our Proxy on
* the basis of a username and password.
* the basis of authorization header.
*/
public interface ProxyAuthenticator {

/**
* Authenticates the user using the specified userName and password.
*
* @param userName
* The user name.
* @param password
* The password.
* @return <code>true</code> if the credentials are acceptable, otherwise
* Authenticates the user using the specified proxy authorization header.
*
* @param httpRequest
* http request
*
* @return <code>true</code> if the credential is acceptable, otherwise
* <code>false</code>.
*/
boolean authenticate(String userName, String password);
boolean authenticate(HttpRequest httpRequest);

/**
* The realm value to be used in the request for proxy authentication
* ("Proxy-Authenticate" header). Returning null will cause the string
Expand All @@ -25,4 +28,9 @@ public interface ProxyAuthenticator {
* @return
*/
String getRealm();

/**
* Response that is going to be returned on authentication failure
*/
FullHttpResponse authenticationFailureResponse(HttpRequest request);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.littleshoot.proxy.impl;

import com.google.common.io.BaseEncoding;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
Expand Down Expand Up @@ -39,7 +38,6 @@
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.util.Date;
import java.util.List;
import java.util.Locale;
Expand Down Expand Up @@ -996,27 +994,12 @@ private boolean authenticationRequired(HttpRequest request) {
final ProxyAuthenticator authenticator = proxyServer
.getProxyAuthenticator();

if (authenticator == null)
if (authenticator == null) {
return false;

if (!request.headers().contains(HttpHeaders.Names.PROXY_AUTHORIZATION)) {
writeAuthenticationRequired(authenticator.getRealm());
return true;
}

List<String> values = request.headers().getAll(
HttpHeaders.Names.PROXY_AUTHORIZATION);
String fullValue = values.iterator().next();
String value = StringUtils.substringAfter(fullValue, "Basic ").trim();

byte[] decodedValue = BaseEncoding.base64().decode(value);

String decodedString = new String(decodedValue, Charset.forName("UTF-8"));

String userName = StringUtils.substringBefore(decodedString, ":");
String password = StringUtils.substringAfter(decodedString, ":");
if (!authenticator.authenticate(userName, password)) {
writeAuthenticationRequired(authenticator.getRealm());
if (!authenticator.authenticate(request)) {
write(authenticator.authenticationFailureResponse(request));
return true;
}

Expand All @@ -1030,26 +1013,6 @@ private boolean authenticationRequired(HttpRequest request) {
return false;
}

private void writeAuthenticationRequired(String realm) {
String body = "<!DOCTYPE HTML \"-//IETF//DTD HTML 2.0//EN\">\n"
+ "<html><head>\n"
+ "<title>407 Proxy Authentication Required</title>\n"
+ "</head><body>\n"
+ "<h1>Proxy Authentication Required</h1>\n"
+ "<p>This server could not verify that you\n"
+ "are authorized to access the document\n"
+ "requested. Either you supplied the wrong\n"
+ "credentials (e.g., bad password), or your\n"
+ "browser doesn't understand how to supply\n"
+ "the credentials required.</p>\n" + "</body></html>\n";
FullHttpResponse response = ProxyUtils.createFullHttpResponse(HttpVersion.HTTP_1_1,
HttpResponseStatus.PROXY_AUTHENTICATION_REQUIRED, body);
HttpHeaders.setDate(response, new Date());
response.headers().set("Proxy-Authenticate",
"Basic realm=\"" + (realm == null ? "Restricted Files" : realm) + "\"");
write(response);
}

/***************************************************************************
* Request/Response Rewriting
**************************************************************************/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@
* uses MITM.
*/
public class MITMUsernamePasswordAuthenticatingProxyTest extends
UsernamePasswordAuthenticatingProxyTest
implements ProxyAuthenticator {
UsernamePasswordAuthenticatingProxyTest {
@Override
protected void setUp() {
this.proxyServer = bootstrapProxy()
.withPort(0)
.withProxyAuthenticator(this)
.withProxyAuthenticator(new TestBasicProxyAuthenticator())
.withManInTheMiddle(new SelfSignedMitmManagerFactory())
.start();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,40 +1,47 @@
package org.littleshoot.proxy;

import io.netty.handler.codec.http.HttpRequest;

/**
* Tests a single proxy that requires username/password authentication.
*/
public class UsernamePasswordAuthenticatingProxyTest extends BaseProxyTest
implements ProxyAuthenticator {
public class UsernamePasswordAuthenticatingProxyTest extends BaseProxyTest {
private static String USERNAME = "user1";
private static String PASSWORD = "password";

@Override
protected void setUp() {
this.proxyServer = bootstrapProxy()
.withPort(0)
.withProxyAuthenticator(this)
.withProxyAuthenticator(new TestBasicProxyAuthenticator())
.start();
}

@Override
protected String getUsername() {
return "user1";
return USERNAME;
}

@Override
protected String getPassword() {
return "user2";
}

@Override
public boolean authenticate(String userName, String password) {
return getUsername().equals(userName) && getPassword().equals(password);
return PASSWORD;
}

@Override
protected boolean isAuthenticating() {
return true;
}

@Override
public String getRealm() {
return null;
static class TestBasicProxyAuthenticator extends BasicProxyAuthenticator {

@Override
public String getRealm() {
return null;
}

@Override
public boolean authenticate(String username, String password, HttpRequest request) {
return USERNAME.equals(username) && PASSWORD.equals(password);
}
}
}