Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9f17f72cc3 | |||
| d8d94a773d | |||
| 957579c8b7 | |||
| d9a1a36dc8 | |||
| 5d4b010710 | |||
| 2486869b6b | |||
| 327173c8f6 |
+26
@@ -4,8 +4,10 @@ import com.fasterxml.jackson.databind.JsonNode;
|
|||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import io.github.hectorvent.floci.config.EmulatorConfig;
|
import io.github.hectorvent.floci.config.EmulatorConfig;
|
||||||
import io.github.hectorvent.floci.core.common.SsrfProtection;
|
import io.github.hectorvent.floci.core.common.SsrfProtection;
|
||||||
|
import io.github.hectorvent.floci.services.cognito.CognitoService;
|
||||||
import jakarta.annotation.PreDestroy;
|
import jakarta.annotation.PreDestroy;
|
||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import jakarta.enterprise.inject.Instance;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import org.apache.hc.client5.http.DnsResolver;
|
import org.apache.hc.client5.http.DnsResolver;
|
||||||
import org.apache.hc.client5.http.SystemDefaultDnsResolver;
|
import org.apache.hc.client5.http.SystemDefaultDnsResolver;
|
||||||
@@ -37,6 +39,7 @@ import java.util.Base64;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies the RS256 signature of a token presented to an HTTP API v2 JWT authorizer, the same
|
* Verifies the RS256 signature of a token presented to an HTTP API v2 JWT authorizer, the same
|
||||||
@@ -66,8 +69,27 @@ public class JwtSignatureVerifier implements AutoCloseable {
|
|||||||
private final CloseableHttpClient httpClient;
|
private final CloseableHttpClient httpClient;
|
||||||
private final boolean allowPrivateNetworkTargets;
|
private final boolean allowPrivateNetworkTargets;
|
||||||
private final Map<String, CachedJwks> jwksCache = new ConcurrentHashMap<>();
|
private final Map<String, CachedJwks> jwksCache = new ConcurrentHashMap<>();
|
||||||
|
private Function<String, Optional<Map<String, RSAPublicKey>>> localIssuerKeys = issuer -> Optional.empty();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An issuer that is one of this emulator's own Cognito pools is served at Floci's base URL, which
|
||||||
|
* is plain HTTP on a hostname such as {@code localhost} or a Compose service name, so the HTTPS
|
||||||
|
* and address rules below would refuse to fetch it. Its keys are read from the pool instead:
|
||||||
|
* they are the same keys the pool publishes at its JWKS URL.
|
||||||
|
*/
|
||||||
@Inject
|
@Inject
|
||||||
|
public JwtSignatureVerifier(ObjectMapper objectMapper, EmulatorConfig config, Instance<CognitoService> cognito) {
|
||||||
|
this(objectMapper, config);
|
||||||
|
withLocalIssuerKeys(issuer -> cognito.isResolvable()
|
||||||
|
? cognito.get().localIssuerKeys(issuer) : Optional.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Where the keys of in-process issuers come from; package-private so tests can stand one in. */
|
||||||
|
JwtSignatureVerifier withLocalIssuerKeys(Function<String, Optional<Map<String, RSAPublicKey>>> localIssuerKeys) {
|
||||||
|
this.localIssuerKeys = localIssuerKeys;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public JwtSignatureVerifier(ObjectMapper objectMapper, EmulatorConfig config) {
|
public JwtSignatureVerifier(ObjectMapper objectMapper, EmulatorConfig config) {
|
||||||
this(objectMapper, SystemDefaultDnsResolver.INSTANCE,
|
this(objectMapper, SystemDefaultDnsResolver.INSTANCE,
|
||||||
config.security().allowPrivateJwtTargets());
|
config.security().allowPrivateJwtTargets());
|
||||||
@@ -162,6 +184,10 @@ public class JwtSignatureVerifier implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private RSAPublicKey resolveKey(String issuer, String kid) throws JwtVerificationException {
|
private RSAPublicKey resolveKey(String issuer, String kid) throws JwtVerificationException {
|
||||||
|
Optional<Map<String, RSAPublicKey>> local = localIssuerKeys.apply(issuer);
|
||||||
|
if (local.isPresent()) {
|
||||||
|
return local.get().get(kid);
|
||||||
|
}
|
||||||
CachedJwks cached = jwksCache.get(issuer);
|
CachedJwks cached = jwksCache.get(issuer);
|
||||||
if (cached != null && !cached.isExpired()) {
|
if (cached != null && !cached.isExpired()) {
|
||||||
RSAPublicKey key = cached.keysByKid().get(kid);
|
RSAPublicKey key = cached.keysByKid().get(kid);
|
||||||
|
|||||||
+4
-1
@@ -291,7 +291,10 @@ final class CognitoAuthFlowHandler {
|
|||||||
if (!"UserNotFoundException".equals(ae.getErrorCode())) throw ae;
|
if (!"UserNotFoundException".equals(ae.getErrorCode())) throw ae;
|
||||||
user = tryUserMigration(pool, client, username, password, null, clientMetadata,
|
user = tryUserMigration(pool, client, username, password, null, clientMetadata,
|
||||||
"UserMigration_Authentication");
|
"UserMigration_Authentication");
|
||||||
if (user == null) throw ae;
|
if (user == null) {
|
||||||
|
if (!CognitoService.hidesUserExistence(client)) throw ae;
|
||||||
|
throw new AwsException("NotAuthorizedException", "Incorrect username or password", 400);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
firePreAuthentication(pool, client, user, null, clientMetadata, false);
|
firePreAuthentication(pool, client, user, null, clientMetadata, false);
|
||||||
|
|||||||
@@ -2592,7 +2592,8 @@ public class CognitoService implements ResourceProvider {
|
|||||||
String confirmationCode) {
|
String confirmationCode) {
|
||||||
UserPool pool = poolStore.get(userPoolId)
|
UserPool pool = poolStore.get(userPoolId)
|
||||||
.orElseThrow(() -> userPoolNotFound(userPoolId));
|
.orElseThrow(() -> userPoolNotFound(userPoolId));
|
||||||
CognitoUser user = adminGetUser(client.getUserPoolId(), username);
|
CognitoUser user = userForClientOrNull(client, username);
|
||||||
|
if (user == null) throw codeMismatch();
|
||||||
if (verificationCodeService != null && isSignUpConfirmationEnabled(pool)) {
|
if (verificationCodeService != null && isSignUpConfirmationEnabled(pool)) {
|
||||||
try {
|
try {
|
||||||
verificationCodeService.consume(client.getUserPoolId(), user.getUsername(),
|
verificationCodeService.consume(client.getUserPoolId(), user.getUsername(),
|
||||||
@@ -2636,7 +2637,8 @@ public class CognitoService implements ResourceProvider {
|
|||||||
.orElseThrow(() -> new AwsException("ResourceNotFoundException", "Client not found",
|
.orElseThrow(() -> new AwsException("ResourceNotFoundException", "Client not found",
|
||||||
400));
|
400));
|
||||||
UserPool pool = describeUserPool(client.getUserPoolId());
|
UserPool pool = describeUserPool(client.getUserPoolId());
|
||||||
CognitoUser user = adminGetUser(client.getUserPoolId(), username);
|
CognitoUser user = userForClientOrNull(client, username);
|
||||||
|
if (user == null) return simulatedCodeDelivery(username);
|
||||||
if (!"UNCONFIRMED".equals(user.getUserStatus())) {
|
if (!"UNCONFIRMED".equals(user.getUserStatus())) {
|
||||||
throw new AwsException("NotAuthorizedException",
|
throw new AwsException("NotAuthorizedException",
|
||||||
"User cannot be confirmed. Current status is " + user.getUserStatus(), 400);
|
"User cannot be confirmed. Current status is " + user.getUserStatus(), 400);
|
||||||
@@ -2774,7 +2776,8 @@ public class CognitoService implements ResourceProvider {
|
|||||||
public Map<String, Object> forgotPassword(String clientId, String username) {
|
public Map<String, Object> forgotPassword(String clientId, String username) {
|
||||||
UserPoolClient client = clientStore.get(clientId)
|
UserPoolClient client = clientStore.get(clientId)
|
||||||
.orElseThrow(() -> new AwsException("ResourceNotFoundException", "Client not found", 400));
|
.orElseThrow(() -> new AwsException("ResourceNotFoundException", "Client not found", 400));
|
||||||
CognitoUser user = adminGetUser(client.getUserPoolId(), username);
|
CognitoUser user = userForClientOrNull(client, username);
|
||||||
|
if (user == null) return new LinkedHashMap<>(simulatedCodeDelivery(username));
|
||||||
UserPool pool = describeUserPool(client.getUserPoolId());
|
UserPool pool = describeUserPool(client.getUserPoolId());
|
||||||
ensureVerificationWiring();
|
ensureVerificationWiring();
|
||||||
DeliveryTarget deliveryTarget = resolveForgotPasswordDeliveryTarget(pool, user);
|
DeliveryTarget deliveryTarget = resolveForgotPasswordDeliveryTarget(pool, user);
|
||||||
@@ -2800,7 +2803,8 @@ public class CognitoService implements ResourceProvider {
|
|||||||
public void confirmForgotPassword(String clientId, String username, String confirmationCode, String newPassword) {
|
public void confirmForgotPassword(String clientId, String username, String confirmationCode, String newPassword) {
|
||||||
UserPoolClient client = clientStore.get(clientId)
|
UserPoolClient client = clientStore.get(clientId)
|
||||||
.orElseThrow(() -> new AwsException("ResourceNotFoundException", "Client not found", 400));
|
.orElseThrow(() -> new AwsException("ResourceNotFoundException", "Client not found", 400));
|
||||||
CognitoUser user = adminGetUser(client.getUserPoolId(), username);
|
CognitoUser user = userForClientOrNull(client, username);
|
||||||
|
if (user == null) throw codeMismatch();
|
||||||
ensureVerificationWiring();
|
ensureVerificationWiring();
|
||||||
try {
|
try {
|
||||||
verificationCodeService.consume(client.getUserPoolId(), user.getUsername(),
|
verificationCodeService.consume(client.getUserPoolId(), user.getUsername(),
|
||||||
@@ -3075,6 +3079,18 @@ public class CognitoService implements ResourceProvider {
|
|||||||
return overrideId;
|
return overrideId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The signing keys, by kid, of this emulator's own pool that {@code issuer} names exactly, so a
|
||||||
|
* JWT authorizer can verify the pool's tokens in-process; empty for every other issuer.
|
||||||
|
*/
|
||||||
|
public Optional<Map<String, RSAPublicKey>> localIssuerKeys(String issuer) {
|
||||||
|
if (issuer == null) return Optional.empty();
|
||||||
|
String trimmed = issuer.replaceAll("/+$", "");
|
||||||
|
String poolId = trimmed.substring(trimmed.lastIndexOf('/') + 1);
|
||||||
|
if (poolId.isEmpty() || !getIssuer(poolId).equals(trimmed)) return Optional.empty();
|
||||||
|
return poolStore.get(poolId).map(pool -> Map.of(getSigningKeyId(pool), getSigningPublicKey(pool)));
|
||||||
|
}
|
||||||
|
|
||||||
public String getJwksUri(String poolId) {
|
public String getJwksUri(String poolId) {
|
||||||
return getIssuer(poolId) + "/.well-known/jwks.json";
|
return getIssuer(poolId) + "/.well-known/jwks.json";
|
||||||
}
|
}
|
||||||
@@ -4446,10 +4462,38 @@ public class CognitoService implements ResourceProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the app client hides which usernames exist (PreventUserExistenceErrors=ENABLED). Real Cognito then
|
||||||
|
* answers for an unknown user exactly as for a real one: a failed sign-in is "Incorrect username or password",
|
||||||
|
* a code request reports a simulated delivery and a code check reports a wrong code. Unset means LEGACY.
|
||||||
|
*/
|
||||||
|
static boolean hidesUserExistence(UserPoolClient client) {
|
||||||
|
return "ENABLED".equals(client.getPreventUserExistenceErrors());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The user, or null when it does not exist and the client hides that; otherwise UserNotFoundException. */
|
||||||
|
private CognitoUser userForClientOrNull(UserPoolClient client, String username) {
|
||||||
|
try {
|
||||||
|
return adminGetUser(client.getUserPoolId(), username);
|
||||||
|
} catch (AwsException e) {
|
||||||
|
if ("UserNotFoundException".equals(e.getErrorCode()) && hidesUserExistence(client)) return null;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, String> simulatedCodeDelivery(String username) {
|
||||||
|
return username != null && username.contains("@")
|
||||||
|
? Map.of("AttributeName", "email", "DeliveryMedium", "EMAIL", "Destination", maskEmail(username))
|
||||||
|
: Map.of("AttributeName", "phone_number", "DeliveryMedium", "SMS", "Destination", "+*******0000");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AwsException codeMismatch() {
|
||||||
|
return new AwsException("CodeMismatchException", "Invalid verification code provided, please try again.", 400);
|
||||||
|
}
|
||||||
|
|
||||||
private AwsException mapVerificationCodeException(VerificationCodeException e) {
|
private AwsException mapVerificationCodeException(VerificationCodeException e) {
|
||||||
return switch (e.getKind()) {
|
return switch (e.getKind()) {
|
||||||
case MISMATCH, NOT_FOUND -> new AwsException("CodeMismatchException",
|
case MISMATCH, NOT_FOUND -> codeMismatch();
|
||||||
"Invalid verification code provided, please try again.", 400);
|
|
||||||
case EXPIRED -> new AwsException("ExpiredCodeException",
|
case EXPIRED -> new AwsException("ExpiredCodeException",
|
||||||
"Invalid code provided, please request a code again.", 400);
|
"Invalid code provided, please request a code again.", 400);
|
||||||
case RATE_LIMIT -> new AwsException("LimitExceededException",
|
case RATE_LIMIT -> new AwsException("LimitExceededException",
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ public class SmtpRelay {
|
|||||||
|
|
||||||
private static final Logger LOG = Logger.getLogger(SmtpRelay.class);
|
private static final Logger LOG = Logger.getLogger(SmtpRelay.class);
|
||||||
private static final long RELAY_TIMEOUT_SECONDS = 30;
|
private static final long RELAY_TIMEOUT_SECONDS = 30;
|
||||||
|
/** Every SASL method the mail client supports except XOAUTH2, which expects a token. */
|
||||||
|
static final String PASSWORD_AUTH_METHODS = "CRAM-SHA256 CRAM-SHA1 CRAM-MD5 DIGEST-MD5 LOGIN PLAIN";
|
||||||
|
|
||||||
/** SES control header naming the configuration set to apply to a raw send. */
|
/** SES control header naming the configuration set to apply to a raw send. */
|
||||||
public static final String HEADER_CONFIGURATION_SET = "X-SES-CONFIGURATION-SET";
|
public static final String HEADER_CONFIGURATION_SET = "X-SES-CONFIGURATION-SET";
|
||||||
@@ -118,6 +120,9 @@ public class SmtpRelay {
|
|||||||
.filter(u -> !u.isBlank())
|
.filter(u -> !u.isBlank())
|
||||||
.ifPresent(u -> {
|
.ifPresent(u -> {
|
||||||
mailConfig.setUsername(u);
|
mailConfig.setUsername(u);
|
||||||
|
// smtp-pass is a password, never an OAuth token: without this the client prefers
|
||||||
|
// XOAUTH2 whenever the server offers it (Gmail does) and the login is refused.
|
||||||
|
mailConfig.setAuthMethods(PASSWORD_AUTH_METHODS);
|
||||||
config.services().ses().smtpPass()
|
config.services().ses().smtpPass()
|
||||||
.filter(p -> !p.isBlank())
|
.filter(p -> !p.isBlank())
|
||||||
.ifPresent(mailConfig::setPassword);
|
.ifPresent(mailConfig::setPassword);
|
||||||
|
|||||||
+20
@@ -98,6 +98,26 @@ class JwtSignatureVerifierTest {
|
|||||||
verifier.verify(token, issuer); // does not throw
|
verifier.verify(token, issuer); // does not throw
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void verifiesAnInProcessIssuerWithoutFetchingIt() throws Exception {
|
||||||
|
// Floci's own Cognito issuer: plain HTTP on a hostname, which the strict fetch rules refuse.
|
||||||
|
String localIssuer = "http://floci:4566/us-east-1_local";
|
||||||
|
strictVerifier.withLocalIssuerKeys(candidate -> candidate.equals(localIssuer)
|
||||||
|
? java.util.Optional.of(Map.of("test-key-1", publicKey)) : java.util.Optional.empty());
|
||||||
|
|
||||||
|
assertDoesNotThrow(() -> strictVerifier.verify(signToken("test-key-1", privateKey), localIssuer));
|
||||||
|
assertThrows(JwtSignatureVerifier.JwtVerificationException.class,
|
||||||
|
() -> strictVerifier.verify(signToken("other-key", privateKey), localIssuer));
|
||||||
|
KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA");
|
||||||
|
gen.initialize(2048);
|
||||||
|
String forged = signToken("test-key-1", (RSAPrivateKey) gen.generateKeyPair().getPrivate());
|
||||||
|
assertThrows(JwtSignatureVerifier.JwtVerificationException.class,
|
||||||
|
() -> strictVerifier.verify(forged, localIssuer));
|
||||||
|
// Any other plain-HTTP issuer still goes through the strict fetch, and is refused.
|
||||||
|
assertThrows(JwtSignatureVerifier.JwtVerificationException.class,
|
||||||
|
() -> strictVerifier.verify(signToken("test-key-1", privateKey), "http://floci:4566/us-east-1_other"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void rejectsATokenSignedWithADifferentKey() throws Exception {
|
void rejectsATokenSignedWithADifferentKey() throws Exception {
|
||||||
KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA");
|
KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA");
|
||||||
|
|||||||
@@ -373,6 +373,63 @@ class CognitoServiceTest {
|
|||||||
null, null, List.of(), null, null);
|
null, null, List.of(), null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class PreventUserExistenceErrors {
|
||||||
|
|
||||||
|
private UserPoolClient client(UserPool pool, String preventUserExistenceErrors) {
|
||||||
|
return service.createUserPoolClient(pool.getId(), "existence-client", false, false, List.of(), List.of(),
|
||||||
|
null, List.of(), null, List.of("ALLOW_USER_PASSWORD_AUTH"), null, null, List.of(),
|
||||||
|
preventUserExistenceErrors, List.of(), null, null, null, List.of(), null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void enabledAnswersAnUnknownUserLikeAWrongPassword() {
|
||||||
|
UserPool pool = createPoolAndUser();
|
||||||
|
UserPoolClient client = client(pool, "ENABLED");
|
||||||
|
|
||||||
|
AwsException unknown = assertThrows(AwsException.class, () -> service.initiateAuth(client.getClientId(),
|
||||||
|
"USER_PASSWORD_AUTH", Map.of("USERNAME", "nobody", "PASSWORD", "Perm1234!")));
|
||||||
|
AwsException wrongPassword = assertThrows(AwsException.class, () -> service.initiateAuth(client.getClientId(),
|
||||||
|
"USER_PASSWORD_AUTH", Map.of("USERNAME", "alice", "PASSWORD", "Wrong1234!")));
|
||||||
|
|
||||||
|
assertEquals("NotAuthorizedException", unknown.getErrorCode());
|
||||||
|
assertEquals(wrongPassword.getErrorCode(), unknown.getErrorCode());
|
||||||
|
assertEquals(wrongPassword.getMessage(), unknown.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void enabledSimulatesCodeDeliveryAndRejectsCodesForAnUnknownUser() {
|
||||||
|
UserPool pool = createPoolAndUser();
|
||||||
|
UserPoolClient client = client(pool, "ENABLED");
|
||||||
|
|
||||||
|
assertEquals(Map.of("AttributeName", "email", "DeliveryMedium", "EMAIL", "Destination", "n***@e***"),
|
||||||
|
service.forgotPassword(client.getClientId(), "nobody@example.com"));
|
||||||
|
assertEquals("EMAIL", service.resendConfirmationCode(client.getClientId(), "nobody@example.com")
|
||||||
|
.get("DeliveryMedium"));
|
||||||
|
assertEquals("CodeMismatchException", assertThrows(AwsException.class, () ->
|
||||||
|
service.confirmForgotPassword(client.getClientId(), "nobody@example.com", "123456", "Perm1234!"))
|
||||||
|
.getErrorCode());
|
||||||
|
assertEquals("CodeMismatchException", assertThrows(AwsException.class, () ->
|
||||||
|
service.confirmSignUp(client.getClientId(), "nobody@example.com", "123456")).getErrorCode());
|
||||||
|
assertThrows(AwsException.class, () -> service.adminGetUser(pool.getId(), "nobody@example.com"),
|
||||||
|
"no user is created along the way");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void legacyAndUnsetStillReportUserNotFound() {
|
||||||
|
UserPool pool = createPoolAndUser();
|
||||||
|
for (String setting : new String[] {"LEGACY", null}) {
|
||||||
|
UserPoolClient client = client(pool, setting);
|
||||||
|
|
||||||
|
assertEquals("UserNotFoundException", assertThrows(AwsException.class, () -> service.initiateAuth(
|
||||||
|
client.getClientId(), "USER_PASSWORD_AUTH", Map.of("USERNAME", "nobody", "PASSWORD", "x")))
|
||||||
|
.getErrorCode());
|
||||||
|
assertEquals("UserNotFoundException", assertThrows(AwsException.class, () ->
|
||||||
|
service.forgotPassword(client.getClientId(), "nobody")).getErrorCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@CsvSource({
|
@CsvSource({
|
||||||
"Short1!a",
|
"Short1!a",
|
||||||
@@ -1312,6 +1369,22 @@ class CognitoServiceTest {
|
|||||||
assertEquals("http://localhost:4566/custompool", service.getIssuer(pool.getId()));
|
assertEquals("http://localhost:4566/custompool", service.getIssuer(pool.getId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void localIssuerKeysServeOnlyThisEmulatorsOwnPoolIssuers() throws Exception {
|
||||||
|
UserPool pool = createPoolAndUser();
|
||||||
|
UserPoolClient client = clientWithFlows(pool, "ALLOW_USER_PASSWORD_AUTH");
|
||||||
|
String token = (String) ((Map<?, ?>) service.initiateAuth(client.getClientId(), "USER_PASSWORD_AUTH",
|
||||||
|
Map.of("USERNAME", "alice", "PASSWORD", "Perm1234!")).get("AuthenticationResult")).get("AccessToken");
|
||||||
|
String kid = MAPPER.readTree(Base64.getUrlDecoder().decode(token.split("\\.")[0])).path("kid").asText();
|
||||||
|
|
||||||
|
Map<String, java.security.interfaces.RSAPublicKey> keys = service.localIssuerKeys(service.getIssuer(pool.getId())).orElseThrow();
|
||||||
|
assertTrue(keys.containsKey(kid), "the key that signed the pool's tokens");
|
||||||
|
assertTrue(service.localIssuerKeys(service.getIssuer(pool.getId()) + "/").isPresent());
|
||||||
|
assertTrue(service.localIssuerKeys("https://cognito-idp.us-east-1.amazonaws.com/" + pool.getId()).isEmpty());
|
||||||
|
assertTrue(service.localIssuerKeys(service.getIssuer("us-east-1_missing")).isEmpty());
|
||||||
|
assertTrue(service.localIssuerKeys(null).isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// Groups
|
// Groups
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|||||||
@@ -287,7 +287,8 @@ class SmtpRelayServerTest {
|
|||||||
String command = line.toUpperCase(Locale.ROOT);
|
String command = line.toUpperCase(Locale.ROOT);
|
||||||
if (command.startsWith("EHLO")) {
|
if (command.startsWith("EHLO")) {
|
||||||
reply(out, "250-floci-stub");
|
reply(out, "250-floci-stub");
|
||||||
reply(out, "250 AUTH PLAIN");
|
// Gmail also offers OAuth sign-in, which a password must never be sent as.
|
||||||
|
reply(out, "250 AUTH PLAIN XOAUTH2 OAUTHBEARER");
|
||||||
} else if (command.startsWith("HELO")) {
|
} else if (command.startsWith("HELO")) {
|
||||||
reply(out, "250 floci-stub");
|
reply(out, "250 floci-stub");
|
||||||
} else if (command.startsWith("AUTH")) {
|
} else if (command.startsWith("AUTH")) {
|
||||||
|
|||||||
Reference in New Issue
Block a user