PR #11883 - changes from review
Signed-off-by: Lachlan Roberts <lachlan.p.roberts@gmail.com>
This commit is contained in:
parent
32b7043932
commit
2f668353ac
|
@ -44,7 +44,6 @@ import org.eclipse.jetty.security.authentication.LoginAuthenticator;
|
||||||
import org.eclipse.jetty.security.authentication.SessionAuthentication;
|
import org.eclipse.jetty.security.authentication.SessionAuthentication;
|
||||||
import org.eclipse.jetty.security.siwe.internal.AnyUserLoginService;
|
import org.eclipse.jetty.security.siwe.internal.AnyUserLoginService;
|
||||||
import org.eclipse.jetty.security.siwe.internal.EthereumUtil;
|
import org.eclipse.jetty.security.siwe.internal.EthereumUtil;
|
||||||
import org.eclipse.jetty.security.siwe.internal.SignInWithEthereumParser;
|
|
||||||
import org.eclipse.jetty.security.siwe.internal.SignInWithEthereumToken;
|
import org.eclipse.jetty.security.siwe.internal.SignInWithEthereumToken;
|
||||||
import org.eclipse.jetty.server.FormFields;
|
import org.eclipse.jetty.server.FormFields;
|
||||||
import org.eclipse.jetty.server.Request;
|
import org.eclipse.jetty.server.Request;
|
||||||
|
@ -573,7 +572,7 @@ public class EthereumAuthenticator extends LoginAuthenticator implements Dumpabl
|
||||||
SignedMessage signedMessage = parseMessage(request, response, callback);
|
SignedMessage signedMessage = parseMessage(request, response, callback);
|
||||||
if (signedMessage == null)
|
if (signedMessage == null)
|
||||||
return AuthenticationState.SEND_FAILURE;
|
return AuthenticationState.SEND_FAILURE;
|
||||||
SignInWithEthereumToken siwe = SignInWithEthereumParser.parse(signedMessage.message());
|
SignInWithEthereumToken siwe = SignInWithEthereumToken.from(signedMessage.message());
|
||||||
if (siwe == null || !validateSignInWithEthereumToken(siwe, signedMessage, request, response, callback))
|
if (siwe == null || !validateSignInWithEthereumToken(siwe, signedMessage, request, response, callback))
|
||||||
return AuthenticationState.SEND_FAILURE;
|
return AuthenticationState.SEND_FAILURE;
|
||||||
|
|
||||||
|
|
|
@ -1,73 +0,0 @@
|
||||||
//
|
|
||||||
// ========================================================================
|
|
||||||
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
|
|
||||||
//
|
|
||||||
// This program and the accompanying materials are made available under the
|
|
||||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
||||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
|
||||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
|
||||||
// ========================================================================
|
|
||||||
//
|
|
||||||
|
|
||||||
package org.eclipse.jetty.security.siwe.internal;
|
|
||||||
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a SIWE Message, based off the ABNF Message Format from <a href="https://eips.ethereum.org/EIPS/eip-4361">EIP-4361</a>.
|
|
||||||
*/
|
|
||||||
public class SignInWithEthereumParser
|
|
||||||
{
|
|
||||||
private static final String SCHEME_PATTERN = "[a-zA-Z][a-zA-Z0-9+\\-.]*";
|
|
||||||
private static final String DOMAIN_PATTERN = "(?:[a-zA-Z0-9\\-._~%]+@)?[a-zA-Z0-9\\-._~%]+(?:\\:[0-9]+)?";
|
|
||||||
private static final String ADDRESS_PATTERN = "0x[0-9a-fA-F]{40}";
|
|
||||||
private static final String STATEMENT_PATTERN = "[^\\n]*";
|
|
||||||
private static final String URI_PATTERN = "[^\\n]+";
|
|
||||||
private static final String VERSION_PATTERN = "[0-9]+";
|
|
||||||
private static final String CHAIN_ID_PATTERN = "[0-9]+";
|
|
||||||
private static final String NONCE_PATTERN = "[a-zA-Z0-9]{8}";
|
|
||||||
private static final String DATE_TIME_PATTERN = "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?(?:Z|[+-]\\d{2}:\\d{2})?";
|
|
||||||
private static final String REQUEST_ID_PATTERN = "[^\\n]*";
|
|
||||||
private static final String RESOURCE_PATTERN = "- " + URI_PATTERN;
|
|
||||||
private static final String RESOURCES_PATTERN = "(?:\n" + RESOURCE_PATTERN + ")*";
|
|
||||||
private static final Pattern SIGN_IN_WITH_ETHEREUM_PATTERN = Pattern.compile(
|
|
||||||
"^(?:(?<scheme>" + SCHEME_PATTERN + ")://)?(?<domain>" + DOMAIN_PATTERN + ") wants you to sign in with your Ethereum account:\n" +
|
|
||||||
"(?<address>" + ADDRESS_PATTERN + ")\n\n" +
|
|
||||||
"(?<statement>" + STATEMENT_PATTERN + ")?\n\n" +
|
|
||||||
"URI: (?<uri>" + URI_PATTERN + ")\n" +
|
|
||||||
"Version: (?<version>" + VERSION_PATTERN + ")\n" +
|
|
||||||
"Chain ID: (?<chainId>" + CHAIN_ID_PATTERN + ")\n" +
|
|
||||||
"Nonce: (?<nonce>" + NONCE_PATTERN + ")\n" +
|
|
||||||
"Issued At: (?<issuedAt>" + DATE_TIME_PATTERN + ")" +
|
|
||||||
"(?:\nExpiration Time: (?<expirationTime>" + DATE_TIME_PATTERN + "))?" +
|
|
||||||
"(?:\nNot Before: (?<notBefore>" + DATE_TIME_PATTERN + "))?" +
|
|
||||||
"(?:\nRequest ID: (?<requestId>" + REQUEST_ID_PATTERN + "))?" +
|
|
||||||
"(?:\nResources:(?<resources>" + RESOURCES_PATTERN + "))?$",
|
|
||||||
Pattern.DOTALL
|
|
||||||
);
|
|
||||||
|
|
||||||
private SignInWithEthereumParser()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse a {@link SignInWithEthereumToken} from a {@link String}.
|
|
||||||
* @param message the SIWE message to parse.
|
|
||||||
* @return the {@link SignInWithEthereumToken} or null if it was not a valid SIWE message.
|
|
||||||
*/
|
|
||||||
public static SignInWithEthereumToken parse(String message)
|
|
||||||
{
|
|
||||||
Matcher matcher = SIGN_IN_WITH_ETHEREUM_PATTERN.matcher(message);
|
|
||||||
if (!matcher.matches())
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return new SignInWithEthereumToken(matcher.group("scheme"), matcher.group("domain"),
|
|
||||||
matcher.group("address"), matcher.group("statement"), matcher.group("uri"),
|
|
||||||
matcher.group("version"), matcher.group("chainId"), matcher.group("nonce"),
|
|
||||||
matcher.group("issuedAt"), matcher.group("expirationTime"), matcher.group("notBefore"),
|
|
||||||
matcher.group("requestId"), matcher.group("resources"));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -16,6 +16,8 @@ package org.eclipse.jetty.security.siwe.internal;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.eclipse.jetty.security.ServerAuthException;
|
import org.eclipse.jetty.security.ServerAuthException;
|
||||||
import org.eclipse.jetty.security.siwe.EthereumAuthenticator;
|
import org.eclipse.jetty.security.siwe.EthereumAuthenticator;
|
||||||
|
@ -52,6 +54,52 @@ public record SignInWithEthereumToken(String scheme,
|
||||||
String requestId,
|
String requestId,
|
||||||
String resources)
|
String resources)
|
||||||
{
|
{
|
||||||
|
private static final String SCHEME_PATTERN = "[a-zA-Z][a-zA-Z0-9+\\-.]*";
|
||||||
|
private static final String DOMAIN_PATTERN = "(?:[a-zA-Z0-9\\-._~%]+@)?[a-zA-Z0-9\\-._~%]+(?:\\:[0-9]+)?";
|
||||||
|
private static final String ADDRESS_PATTERN = "0x[0-9a-fA-F]{40}";
|
||||||
|
private static final String STATEMENT_PATTERN = "[^\\n]*";
|
||||||
|
private static final String URI_PATTERN = "[^\\n]+";
|
||||||
|
private static final String VERSION_PATTERN = "[0-9]+";
|
||||||
|
private static final String CHAIN_ID_PATTERN = "[0-9]+";
|
||||||
|
private static final String NONCE_PATTERN = "[a-zA-Z0-9]{8}";
|
||||||
|
private static final String DATE_TIME_PATTERN = "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?(?:Z|[+-]\\d{2}:\\d{2})?";
|
||||||
|
private static final String REQUEST_ID_PATTERN = "[^\\n]*";
|
||||||
|
private static final String RESOURCE_PATTERN = "- " + URI_PATTERN;
|
||||||
|
private static final String RESOURCES_PATTERN = "(?:\n" + RESOURCE_PATTERN + ")*";
|
||||||
|
private static final Pattern SIGN_IN_WITH_ETHEREUM_PATTERN = Pattern.compile(
|
||||||
|
"^(?:(?<scheme>" + SCHEME_PATTERN + ")://)?(?<domain>" + DOMAIN_PATTERN + ") wants you to sign in with your Ethereum account:\n" +
|
||||||
|
"(?<address>" + ADDRESS_PATTERN + ")\n\n" +
|
||||||
|
"(?<statement>" + STATEMENT_PATTERN + ")?\n\n" +
|
||||||
|
"URI: (?<uri>" + URI_PATTERN + ")\n" +
|
||||||
|
"Version: (?<version>" + VERSION_PATTERN + ")\n" +
|
||||||
|
"Chain ID: (?<chainId>" + CHAIN_ID_PATTERN + ")\n" +
|
||||||
|
"Nonce: (?<nonce>" + NONCE_PATTERN + ")\n" +
|
||||||
|
"Issued At: (?<issuedAt>" + DATE_TIME_PATTERN + ")" +
|
||||||
|
"(?:\nExpiration Time: (?<expirationTime>" + DATE_TIME_PATTERN + "))?" +
|
||||||
|
"(?:\nNot Before: (?<notBefore>" + DATE_TIME_PATTERN + "))?" +
|
||||||
|
"(?:\nRequest ID: (?<requestId>" + REQUEST_ID_PATTERN + "))?" +
|
||||||
|
"(?:\nResources:(?<resources>" + RESOURCES_PATTERN + "))?$",
|
||||||
|
Pattern.DOTALL
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a SIWE Message into a {@link SignInWithEthereumToken},
|
||||||
|
* based off the ABNF Message Format from <a href="https://eips.ethereum.org/EIPS/eip-4361">EIP-4361</a>.
|
||||||
|
* @param message the SIWE message to parse.
|
||||||
|
* @return the {@link SignInWithEthereumToken} or null if it was not a valid SIWE message.
|
||||||
|
*/
|
||||||
|
public static SignInWithEthereumToken from(String message)
|
||||||
|
{
|
||||||
|
Matcher matcher = SIGN_IN_WITH_ETHEREUM_PATTERN.matcher(message);
|
||||||
|
if (!matcher.matches())
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new SignInWithEthereumToken(matcher.group("scheme"), matcher.group("domain"),
|
||||||
|
matcher.group("address"), matcher.group("statement"), matcher.group("uri"),
|
||||||
|
matcher.group("version"), matcher.group("chainId"), matcher.group("nonce"),
|
||||||
|
matcher.group("issuedAt"), matcher.group("expirationTime"), matcher.group("notBefore"),
|
||||||
|
matcher.group("requestId"), matcher.group("resources"));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param signedMessage the {@link EthereumAuthenticator.SignedMessage}.
|
* @param signedMessage the {@link EthereumAuthenticator.SignedMessage}.
|
||||||
|
|
|
@ -20,7 +20,6 @@ import java.util.List;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.eclipse.jetty.security.siwe.internal.EthereumUtil;
|
import org.eclipse.jetty.security.siwe.internal.EthereumUtil;
|
||||||
import org.eclipse.jetty.security.siwe.internal.SignInWithEthereumParser;
|
|
||||||
import org.eclipse.jetty.security.siwe.internal.SignInWithEthereumToken;
|
import org.eclipse.jetty.security.siwe.internal.SignInWithEthereumToken;
|
||||||
import org.eclipse.jetty.security.siwe.util.SignInWithEthereumGenerator;
|
import org.eclipse.jetty.security.siwe.util.SignInWithEthereumGenerator;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
@ -97,7 +96,7 @@ public class SignInWithEthereumParserTest
|
||||||
@MethodSource("specExamples")
|
@MethodSource("specExamples")
|
||||||
public void testSpecExamples(String message, String scheme, String domain)
|
public void testSpecExamples(String message, String scheme, String domain)
|
||||||
{
|
{
|
||||||
SignInWithEthereumToken siwe = SignInWithEthereumParser.parse(message);
|
SignInWithEthereumToken siwe = SignInWithEthereumToken.from(message);
|
||||||
assertNotNull(siwe);
|
assertNotNull(siwe);
|
||||||
assertThat(siwe.address(), equalTo("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"));
|
assertThat(siwe.address(), equalTo("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"));
|
||||||
assertThat(siwe.issuedAt(), equalTo("2021-09-30T16:25:24Z"));
|
assertThat(siwe.issuedAt(), equalTo("2021-09-30T16:25:24Z"));
|
||||||
|
@ -139,7 +138,7 @@ public class SignInWithEthereumParserTest
|
||||||
String message = SignInWithEthereumGenerator.generateMessage(scheme, domain, address, statement, uri, version, chainId, nonce, issuedAt,
|
String message = SignInWithEthereumGenerator.generateMessage(scheme, domain, address, statement, uri, version, chainId, nonce, issuedAt,
|
||||||
expirationTime, notBefore, requestId, resources);
|
expirationTime, notBefore, requestId, resources);
|
||||||
|
|
||||||
SignInWithEthereumToken siwe = SignInWithEthereumParser.parse(message);
|
SignInWithEthereumToken siwe = SignInWithEthereumToken.from(message);
|
||||||
assertNotNull(siwe);
|
assertNotNull(siwe);
|
||||||
assertThat(siwe.scheme(), equalTo(scheme));
|
assertThat(siwe.scheme(), equalTo(scheme));
|
||||||
assertThat(siwe.domain(), equalTo(domain));
|
assertThat(siwe.domain(), equalTo(domain));
|
||||||
|
|
|
@ -17,7 +17,6 @@ import java.time.LocalDateTime;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import org.eclipse.jetty.security.siwe.internal.EthereumUtil;
|
import org.eclipse.jetty.security.siwe.internal.EthereumUtil;
|
||||||
import org.eclipse.jetty.security.siwe.internal.SignInWithEthereumParser;
|
|
||||||
import org.eclipse.jetty.security.siwe.internal.SignInWithEthereumToken;
|
import org.eclipse.jetty.security.siwe.internal.SignInWithEthereumToken;
|
||||||
import org.eclipse.jetty.security.siwe.util.EthereumCredentials;
|
import org.eclipse.jetty.security.siwe.util.EthereumCredentials;
|
||||||
import org.eclipse.jetty.security.siwe.util.SignInWithEthereumGenerator;
|
import org.eclipse.jetty.security.siwe.util.SignInWithEthereumGenerator;
|
||||||
|
@ -51,7 +50,7 @@ public class SignInWithEthereumTokenTest
|
||||||
);
|
);
|
||||||
|
|
||||||
EthereumAuthenticator.SignedMessage signedMessage = credentials.signMessage(message);
|
EthereumAuthenticator.SignedMessage signedMessage = credentials.signMessage(message);
|
||||||
SignInWithEthereumToken siwe = SignInWithEthereumParser.parse(message);
|
SignInWithEthereumToken siwe = SignInWithEthereumToken.from(message);
|
||||||
assertNotNull(siwe);
|
assertNotNull(siwe);
|
||||||
|
|
||||||
Throwable error = assertThrows(Throwable.class, () ->
|
Throwable error = assertThrows(Throwable.class, () ->
|
||||||
|
@ -80,7 +79,7 @@ public class SignInWithEthereumTokenTest
|
||||||
);
|
);
|
||||||
|
|
||||||
EthereumAuthenticator.SignedMessage signedMessage = credentials.signMessage(message);
|
EthereumAuthenticator.SignedMessage signedMessage = credentials.signMessage(message);
|
||||||
SignInWithEthereumToken siwe = SignInWithEthereumParser.parse(message);
|
SignInWithEthereumToken siwe = SignInWithEthereumToken.from(message);
|
||||||
assertNotNull(siwe);
|
assertNotNull(siwe);
|
||||||
|
|
||||||
Throwable error = assertThrows(Throwable.class, () ->
|
Throwable error = assertThrows(Throwable.class, () ->
|
||||||
|
@ -110,7 +109,7 @@ public class SignInWithEthereumTokenTest
|
||||||
);
|
);
|
||||||
|
|
||||||
EthereumAuthenticator.SignedMessage signedMessage = credentials.signMessage(message);
|
EthereumAuthenticator.SignedMessage signedMessage = credentials.signMessage(message);
|
||||||
SignInWithEthereumToken siwe = SignInWithEthereumParser.parse(message);
|
SignInWithEthereumToken siwe = SignInWithEthereumToken.from(message);
|
||||||
assertNotNull(siwe);
|
assertNotNull(siwe);
|
||||||
|
|
||||||
Throwable error = assertThrows(Throwable.class, () ->
|
Throwable error = assertThrows(Throwable.class, () ->
|
||||||
|
@ -137,7 +136,7 @@ public class SignInWithEthereumTokenTest
|
||||||
);
|
);
|
||||||
|
|
||||||
EthereumAuthenticator.SignedMessage signedMessage = credentials.signMessage(message);
|
EthereumAuthenticator.SignedMessage signedMessage = credentials.signMessage(message);
|
||||||
SignInWithEthereumToken siwe = SignInWithEthereumParser.parse(message);
|
SignInWithEthereumToken siwe = SignInWithEthereumToken.from(message);
|
||||||
assertNotNull(siwe);
|
assertNotNull(siwe);
|
||||||
|
|
||||||
IncludeExcludeSet<String, String> domains = new IncludeExcludeSet<>();
|
IncludeExcludeSet<String, String> domains = new IncludeExcludeSet<>();
|
||||||
|
@ -167,7 +166,7 @@ public class SignInWithEthereumTokenTest
|
||||||
);
|
);
|
||||||
|
|
||||||
EthereumAuthenticator.SignedMessage signedMessage = credentials.signMessage(message);
|
EthereumAuthenticator.SignedMessage signedMessage = credentials.signMessage(message);
|
||||||
SignInWithEthereumToken siwe = SignInWithEthereumParser.parse(message);
|
SignInWithEthereumToken siwe = SignInWithEthereumToken.from(message);
|
||||||
assertNotNull(siwe);
|
assertNotNull(siwe);
|
||||||
|
|
||||||
IncludeExcludeSet<String, String> chainIds = new IncludeExcludeSet<>();
|
IncludeExcludeSet<String, String> chainIds = new IncludeExcludeSet<>();
|
||||||
|
@ -197,7 +196,7 @@ public class SignInWithEthereumTokenTest
|
||||||
);
|
);
|
||||||
|
|
||||||
EthereumAuthenticator.SignedMessage signedMessage = credentials.signMessage(message);
|
EthereumAuthenticator.SignedMessage signedMessage = credentials.signMessage(message);
|
||||||
SignInWithEthereumToken siwe = SignInWithEthereumParser.parse(message);
|
SignInWithEthereumToken siwe = SignInWithEthereumToken.from(message);
|
||||||
assertNotNull(siwe);
|
assertNotNull(siwe);
|
||||||
|
|
||||||
Predicate<String> nonceValidation = nonce -> false;
|
Predicate<String> nonceValidation = nonce -> false;
|
||||||
|
@ -225,7 +224,7 @@ public class SignInWithEthereumTokenTest
|
||||||
);
|
);
|
||||||
|
|
||||||
EthereumAuthenticator.SignedMessage signedMessage = credentials.signMessage(message);
|
EthereumAuthenticator.SignedMessage signedMessage = credentials.signMessage(message);
|
||||||
SignInWithEthereumToken siwe = SignInWithEthereumParser.parse(message);
|
SignInWithEthereumToken siwe = SignInWithEthereumToken.from(message);
|
||||||
assertNotNull(siwe);
|
assertNotNull(siwe);
|
||||||
|
|
||||||
Predicate<String> nonceValidation = nonce -> true;
|
Predicate<String> nonceValidation = nonce -> true;
|
||||||
|
|
Loading…
Reference in New Issue