Merge pull request #2752 from lachlan-roberts/jetty-9.4.x-2739-AuthenticationProtocolHandler

Issue #2739 - AuthenticationProtocolHandler Multiple Challenge Pattern
This commit is contained in:
Greg Wilkins 2018-08-07 10:45:43 +10:00 committed by GitHub
commit 9dd2369a53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 57 additions and 73 deletions

View File

@ -38,7 +38,6 @@ import org.eclipse.jetty.client.util.BufferingResponseListener;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.QuotedCSV;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -46,16 +45,12 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler
{
public static final int DEFAULT_MAX_CONTENT_LENGTH = 16*1024;
public static final Logger LOG = Log.getLogger(AuthenticationProtocolHandler.class);
private static final Pattern PARAM_PATTERN = Pattern.compile("([^=]+)=(.*)");
private static final Pattern TYPE_PATTERN = Pattern.compile("([^\\s]+)(\\s+(.*))?");
private static final Pattern MULTIPLE_CHALLENGE_PATTERN = Pattern.compile("(.*?)\\s*,\\s*([^=\\s,]+(\\s+[^=\\s].*)?)");
private static final Pattern BASE64_PATTERN = Pattern.compile("[\\+\\-\\.\\/\\dA-Z_a-z~]+=*");
private final HttpClient client;
private final int maxContentLength;
private final ResponseNotifier notifier;
private static final Pattern CHALLENGE_PATTERN = Pattern.compile("(?<schemeOnly>[!#$%&'*+\\-.^_`|~0-9A-Za-z]+)|(?:(?<scheme>[!#$%&'*+\\-.^_`|~0-9A-Za-z]+)\\s+)?(?:(?<token68>[a-zA-Z0-9\\-._~+\\/]+=*)|(?<paramName>[!#$%&'*+\\-.^_`|~0-9A-Za-z]+)\\s*=\\s*(?:(?<paramValue>.*)))");
protected AuthenticationProtocolHandler(HttpClient client, int maxContentLength)
{
this.client = client;
@ -82,80 +77,51 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler
// Return new instances every time to keep track of the response content
return new AuthenticationListener();
}
protected List<HeaderInfo> getHeaderInfo(String value) throws IllegalArgumentException
protected List<HeaderInfo> getHeaderInfo(String header) throws IllegalArgumentException
{
String header = value;
List<HeaderInfo> headerInfos = new ArrayList<>();
while(true)
Matcher m;
for(String value : new QuotedCSV(true, header))
{
Matcher m = MULTIPLE_CHALLENGE_PATTERN.matcher(header);
m = CHALLENGE_PATTERN.matcher(value);
if (m.matches())
{
headerInfos.add(newHeaderInfo(m.group(1)));
header = m.group(2);
}
else
{
headerInfos.add(newHeaderInfo(header));
break;
if(m.group("schemeOnly") != null)
{
headerInfos.add(new HeaderInfo(getAuthorizationHeader(), m.group(1), new HashMap<>()));
continue;
}
if (m.group("scheme") != null)
{
headerInfos.add(new HeaderInfo(getAuthorizationHeader(), m.group("scheme"), new HashMap<>()));
}
if (headerInfos.isEmpty())
throw new IllegalArgumentException("Parameters without auth-scheme");
Map<String, String> authParams = headerInfos.get(headerInfos.size() - 1).getParameters();
if (m.group("paramName") != null)
{
String paramVal = QuotedCSV.unquote(m.group("paramValue"));
authParams.put(m.group("paramName"), paramVal);
}
else if (m.group("token68") != null)
{
if (!authParams.isEmpty())
throw new IllegalArgumentException("token68 after auth-params");
authParams.put("base64", m.group("token68"));
}
}
}
return headerInfos;
}
protected HeaderInfo newHeaderInfo(String value) throws IllegalArgumentException
{
String type;
Map<String,String> params = new HashMap<>();
Matcher m = TYPE_PATTERN.matcher(value);
if (m.matches())
{
type = m.group(1);
if (m.group(2) != null)
params = parseParameters(m.group(3));
}
else
{
throw new IllegalArgumentException("Invalid Authentication Format");
}
return new HeaderInfo(getAuthorizationHeader(), type, params);
}
protected Map<String, String> parseParameters(String wwwAuthenticate) throws IllegalArgumentException
{
Map<String, String> result = new HashMap<>();
Matcher b64 = BASE64_PATTERN.matcher(wwwAuthenticate);
if (b64.matches())
{
result.put("base64", wwwAuthenticate);
return result;
}
QuotedCSV parts = new QuotedCSV(false, wwwAuthenticate);
for (String part : parts)
{
Matcher params = PARAM_PATTERN.matcher(part);
if (params.matches())
{
String name = StringUtil.asciiToLowerCase(params.group(1));
String value = (params.group(2)==null) ? "" : params.group(2);
result.put(name, value);
}
else
{
throw new IllegalArgumentException("Invalid Authentication Format");
}
}
return result;
}
private class AuthenticationListener extends BufferingResponseListener
{

View File

@ -31,12 +31,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.IntFunction;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.api.Authentication;
import org.eclipse.jetty.client.api.Authentication.HeaderInfo;
import org.eclipse.jetty.client.api.AuthenticationStore;
import org.eclipse.jetty.client.api.ContentProvider;
import org.eclipse.jetty.client.api.ContentResponse;
@ -44,7 +44,6 @@ import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Response.Listener;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.api.Authentication.HeaderInfo;
import org.eclipse.jetty.client.util.BasicAuthentication;
import org.eclipse.jetty.client.util.DeferredContentProvider;
import org.eclipse.jetty.client.util.DigestAuthentication;
@ -705,7 +704,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
Assert.assertTrue(headerInfo.getParameter("name").equals("value"));
Assert.assertTrue(headerInfo.getParameter("other").equals("value2"));
headerInfos = aph.getHeaderInfo("Scheme name=value, Scheme2 name=value2");
headerInfos = aph.getHeaderInfo(", , , , ,,,Scheme name=value, ,,Scheme2 name=value2,, ,,");
Assert.assertEquals(headerInfos.size(), 2);
Assert.assertTrue(headerInfos.get(0).getType().equalsIgnoreCase("Scheme"));
Assert.assertTrue(headerInfos.get(0).getParameter("nAmE").equals("value"));
@ -768,4 +767,23 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
Assert.assertTrue(headerInfoList.get(2).getParameter("realm").equals("thermostat3="));
Assert.assertTrue(headerInfoList.get(2).getParameter("nonce").equals("9523570528="));
}
@Test
public void testSingleChallangeLooksLikeMultipleChallenge()
{
AuthenticationProtocolHandler aph = new WWWAuthenticationProtocolHandler(client);
List<HeaderInfo> headerInfoList = aph.getHeaderInfo("Digest param=\",f \"");
Assert.assertEquals(1, headerInfoList.size());
headerInfoList = aph.getHeaderInfo("Digest realm=\"thermostat\", qop=\",Digest realm=hello\", nonce=\"1523430383=\"");
Assert.assertEquals(1, headerInfoList.size());
HeaderInfo headerInfo = headerInfoList.get(0);
Assert.assertTrue(headerInfo.getType().equalsIgnoreCase("Digest"));
Assert.assertTrue(headerInfo.getParameter("qop").equals(",Digest realm=hello"));
Assert.assertTrue(headerInfo.getParameter("realm").equals("thermostat"));
Assert.assertThat(headerInfo.getParameter("nonce"), Matchers.is("1523430383="));
}
}