Delete registration recovery passwords by both phone number and PNI
This commit is contained in:
parent
8c9cc4cce5
commit
13a8c6256d
|
@ -12,6 +12,7 @@ import java.util.concurrent.CompletableFuture;
|
||||||
import org.signal.integration.config.Config;
|
import org.signal.integration.config.Config;
|
||||||
import org.whispersystems.textsecuregcm.metrics.NoopAwsSdkMetricPublisher;
|
import org.whispersystems.textsecuregcm.metrics.NoopAwsSdkMetricPublisher;
|
||||||
import org.whispersystems.textsecuregcm.registration.VerificationSession;
|
import org.whispersystems.textsecuregcm.registration.VerificationSession;
|
||||||
|
import org.whispersystems.textsecuregcm.storage.PhoneNumberIdentifiers;
|
||||||
import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswords;
|
import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswords;
|
||||||
import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager;
|
import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager;
|
||||||
import org.whispersystems.textsecuregcm.storage.VerificationSessionManager;
|
import org.whispersystems.textsecuregcm.storage.VerificationSessionManager;
|
||||||
|
@ -37,6 +38,9 @@ public class IntegrationTools {
|
||||||
final DynamoDbClient dynamoDbClient =
|
final DynamoDbClient dynamoDbClient =
|
||||||
config.dynamoDbClient().buildSyncClient(credentialsProvider, new NoopAwsSdkMetricPublisher());
|
config.dynamoDbClient().buildSyncClient(credentialsProvider, new NoopAwsSdkMetricPublisher());
|
||||||
|
|
||||||
|
final PhoneNumberIdentifiers phoneNumberIdentifiers =
|
||||||
|
new PhoneNumberIdentifiers(dynamoDbAsyncClient, config.dynamoDbTables().phoneNumberIdentifiers());
|
||||||
|
|
||||||
final RegistrationRecoveryPasswords registrationRecoveryPasswords = new RegistrationRecoveryPasswords(
|
final RegistrationRecoveryPasswords registrationRecoveryPasswords = new RegistrationRecoveryPasswords(
|
||||||
config.dynamoDbTables().registrationRecovery(), Duration.ofDays(1), dynamoDbClient, dynamoDbAsyncClient);
|
config.dynamoDbTables().registrationRecovery(), Duration.ofDays(1), dynamoDbClient, dynamoDbAsyncClient);
|
||||||
|
|
||||||
|
@ -44,7 +48,7 @@ public class IntegrationTools {
|
||||||
dynamoDbAsyncClient, config.dynamoDbTables().verificationSessions(), Clock.systemUTC());
|
dynamoDbAsyncClient, config.dynamoDbTables().verificationSessions(), Clock.systemUTC());
|
||||||
|
|
||||||
return new IntegrationTools(
|
return new IntegrationTools(
|
||||||
new RegistrationRecoveryPasswordsManager(registrationRecoveryPasswords),
|
new RegistrationRecoveryPasswordsManager(registrationRecoveryPasswords, phoneNumberIdentifiers),
|
||||||
new VerificationSessionManager(verificationSessions)
|
new VerificationSessionManager(verificationSessions)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,5 +8,6 @@ package org.signal.integration.config;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
public record DynamoDbTables(@NotBlank String registrationRecovery,
|
public record DynamoDbTables(@NotBlank String registrationRecovery,
|
||||||
@NotBlank String verificationSessions) {
|
@NotBlank String verificationSessions,
|
||||||
|
@NotBlank String phoneNumberIdentifiers) {
|
||||||
}
|
}
|
||||||
|
|
|
@ -583,8 +583,8 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||||
|
|
||||||
ExperimentEnrollmentManager experimentEnrollmentManager = new ExperimentEnrollmentManager(
|
ExperimentEnrollmentManager experimentEnrollmentManager = new ExperimentEnrollmentManager(
|
||||||
dynamicConfigurationManager);
|
dynamicConfigurationManager);
|
||||||
RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager = new RegistrationRecoveryPasswordsManager(
|
RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager =
|
||||||
registrationRecoveryPasswords);
|
new RegistrationRecoveryPasswordsManager(registrationRecoveryPasswords, phoneNumberIdentifiers);
|
||||||
UsernameHashZkProofVerifier usernameHashZkProofVerifier = new UsernameHashZkProofVerifier();
|
UsernameHashZkProofVerifier usernameHashZkProofVerifier = new UsernameHashZkProofVerifier();
|
||||||
|
|
||||||
RegistrationServiceClient registrationServiceClient = config.getRegistrationServiceConfiguration()
|
RegistrationServiceClient registrationServiceClient = config.getRegistrationServiceConfiguration()
|
||||||
|
|
|
@ -27,7 +27,6 @@ import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||||
import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil;
|
import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil;
|
||||||
import org.whispersystems.textsecuregcm.push.NotPushRegisteredException;
|
import org.whispersystems.textsecuregcm.push.NotPushRegisteredException;
|
||||||
import org.whispersystems.textsecuregcm.push.PushNotificationManager;
|
import org.whispersystems.textsecuregcm.push.PushNotificationManager;
|
||||||
import org.whispersystems.textsecuregcm.push.WebSocketConnectionEventManager;
|
|
||||||
import org.whispersystems.textsecuregcm.storage.Account;
|
import org.whispersystems.textsecuregcm.storage.Account;
|
||||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||||
import org.whispersystems.textsecuregcm.storage.Device;
|
import org.whispersystems.textsecuregcm.storage.Device;
|
||||||
|
|
|
@ -11,6 +11,7 @@ import java.time.Clock;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import org.whispersystems.textsecuregcm.auth.SaltedTokenHash;
|
import org.whispersystems.textsecuregcm.auth.SaltedTokenHash;
|
||||||
import org.whispersystems.textsecuregcm.util.AttributeValues;
|
import org.whispersystems.textsecuregcm.util.AttributeValues;
|
||||||
|
@ -18,12 +19,16 @@ import org.whispersystems.textsecuregcm.util.Util;
|
||||||
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
|
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
|
||||||
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
|
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
|
||||||
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
|
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
|
||||||
import software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest;
|
import software.amazon.awssdk.services.dynamodb.model.Delete;
|
||||||
import software.amazon.awssdk.services.dynamodb.model.GetItemRequest;
|
import software.amazon.awssdk.services.dynamodb.model.GetItemRequest;
|
||||||
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest;
|
import software.amazon.awssdk.services.dynamodb.model.Put;
|
||||||
|
import software.amazon.awssdk.services.dynamodb.model.TransactWriteItem;
|
||||||
|
import software.amazon.awssdk.services.dynamodb.model.TransactWriteItemsRequest;
|
||||||
|
|
||||||
public class RegistrationRecoveryPasswords extends AbstractDynamoDbStore {
|
public class RegistrationRecoveryPasswords extends AbstractDynamoDbStore {
|
||||||
|
|
||||||
|
// As a temporary transitional measure, this can be either a string representation of an E164-formatted phone number
|
||||||
|
// or a UUID (PNI) string
|
||||||
static final String KEY_E164 = "P";
|
static final String KEY_E164 = "P";
|
||||||
static final String ATTR_EXP = "E";
|
static final String ATTR_EXP = "E";
|
||||||
static final String ATTR_SALT = "S";
|
static final String ATTR_SALT = "S";
|
||||||
|
@ -75,26 +80,57 @@ public class RegistrationRecoveryPasswords extends AbstractDynamoDbStore {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<Void> addOrReplace(final String number, final SaltedTokenHash data) {
|
public CompletableFuture<Optional<SaltedTokenHash>> lookup(final UUID phoneNumberIdentifier) {
|
||||||
return asyncClient.putItem(PutItemRequest.builder()
|
return lookup(phoneNumberIdentifier.toString());
|
||||||
.tableName(tableName)
|
}
|
||||||
.item(Map.of(
|
|
||||||
KEY_E164, AttributeValues.fromString(number),
|
public CompletableFuture<Void> addOrReplace(final String number, final UUID phoneNumberIdentifier, final SaltedTokenHash data) {
|
||||||
ATTR_EXP, AttributeValues.fromLong(expirationSeconds()),
|
final long expirationSeconds = expirationSeconds();
|
||||||
ATTR_SALT, AttributeValues.fromString(data.salt()),
|
|
||||||
ATTR_HASH, AttributeValues.fromString(data.hash())))
|
return asyncClient.transactWriteItems(TransactWriteItemsRequest.builder()
|
||||||
.build())
|
.transactItems(
|
||||||
|
buildPutRecoveryPasswordWriteItem(number, expirationSeconds, data.salt(), data.hash())
|
||||||
|
// buildPutRecoveryPasswordWriteItem(phoneNumberIdentifier.toString(), expirationSeconds, data.salt(), data.hash())
|
||||||
|
)
|
||||||
|
.build())
|
||||||
.thenRun(Util.NOOP);
|
.thenRun(Util.NOOP);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<Void> removeEntry(final String number) {
|
private TransactWriteItem buildPutRecoveryPasswordWriteItem(final String key,
|
||||||
return asyncClient.deleteItem(DeleteItemRequest.builder()
|
final long expirationSeconds,
|
||||||
|
final String salt,
|
||||||
|
final String hash) {
|
||||||
|
|
||||||
|
return TransactWriteItem.builder()
|
||||||
|
.put(Put.builder()
|
||||||
.tableName(tableName)
|
.tableName(tableName)
|
||||||
.key(Map.of(KEY_E164, AttributeValues.fromString(number)))
|
.item(Map.of(
|
||||||
|
KEY_E164, AttributeValues.fromString(key),
|
||||||
|
ATTR_EXP, AttributeValues.fromLong(expirationSeconds),
|
||||||
|
ATTR_SALT, AttributeValues.fromString(salt),
|
||||||
|
ATTR_HASH, AttributeValues.fromString(hash)))
|
||||||
.build())
|
.build())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletableFuture<Void> removeEntry(final String number, final UUID phoneNumberIdentifier) {
|
||||||
|
return asyncClient.transactWriteItems(TransactWriteItemsRequest.builder()
|
||||||
|
.transactItems(
|
||||||
|
buildDeleteRecoveryPasswordWriteItem(number),
|
||||||
|
buildDeleteRecoveryPasswordWriteItem(phoneNumberIdentifier.toString()))
|
||||||
|
.build())
|
||||||
.thenRun(Util.NOOP);
|
.thenRun(Util.NOOP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private TransactWriteItem buildDeleteRecoveryPasswordWriteItem(final String key) {
|
||||||
|
return TransactWriteItem.builder()
|
||||||
|
.delete(Delete.builder()
|
||||||
|
.tableName(tableName)
|
||||||
|
.key(Map.of(KEY_E164, AttributeValues.fromString(key)))
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
private long expirationSeconds() {
|
private long expirationSeconds() {
|
||||||
return clock.instant().plus(expiration).getEpochSecond();
|
return clock.instant().plus(expiration).getEpochSecond();
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,10 +21,13 @@ public class RegistrationRecoveryPasswordsManager {
|
||||||
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||||
|
|
||||||
private final RegistrationRecoveryPasswords registrationRecoveryPasswords;
|
private final RegistrationRecoveryPasswords registrationRecoveryPasswords;
|
||||||
|
private final PhoneNumberIdentifiers phoneNumberIdentifiers;
|
||||||
|
|
||||||
|
public RegistrationRecoveryPasswordsManager(final RegistrationRecoveryPasswords registrationRecoveryPasswords,
|
||||||
|
final PhoneNumberIdentifiers phoneNumberIdentifiers) {
|
||||||
|
|
||||||
public RegistrationRecoveryPasswordsManager(final RegistrationRecoveryPasswords registrationRecoveryPasswords) {
|
|
||||||
this.registrationRecoveryPasswords = requireNonNull(registrationRecoveryPasswords);
|
this.registrationRecoveryPasswords = requireNonNull(registrationRecoveryPasswords);
|
||||||
|
this.phoneNumberIdentifiers = phoneNumberIdentifiers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<Boolean> verify(final String number, final byte[] password) {
|
public CompletableFuture<Boolean> verify(final String number, final byte[] password) {
|
||||||
|
@ -41,26 +44,27 @@ public class RegistrationRecoveryPasswordsManager {
|
||||||
public CompletableFuture<Void> storeForCurrentNumber(final String number, final byte[] password) {
|
public CompletableFuture<Void> storeForCurrentNumber(final String number, final byte[] password) {
|
||||||
final String token = bytesToString(password);
|
final String token = bytesToString(password);
|
||||||
final SaltedTokenHash tokenHash = SaltedTokenHash.generateFor(token);
|
final SaltedTokenHash tokenHash = SaltedTokenHash.generateFor(token);
|
||||||
return registrationRecoveryPasswords.addOrReplace(number, tokenHash)
|
|
||||||
.whenComplete((result, error) -> {
|
return phoneNumberIdentifiers.getPhoneNumberIdentifier(number)
|
||||||
if (error != null) {
|
.thenCompose(phoneNumberIdentifier -> registrationRecoveryPasswords.addOrReplace(number, phoneNumberIdentifier, tokenHash)
|
||||||
logger.warn("Failed to store Registration Recovery Password", error);
|
.whenComplete((result, error) -> {
|
||||||
}
|
if (error != null) {
|
||||||
});
|
logger.warn("Failed to store Registration Recovery Password", error);
|
||||||
|
}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<Void> removeForNumber(final String number) {
|
public CompletableFuture<Void> removeForNumber(final String number) {
|
||||||
// remove is a "fire-and-forget" operation,
|
return phoneNumberIdentifiers.getPhoneNumberIdentifier(number)
|
||||||
// there is no action to be taken on its completion
|
.thenCompose(phoneNumberIdentifier -> registrationRecoveryPasswords.removeEntry(number, phoneNumberIdentifier)
|
||||||
return registrationRecoveryPasswords.removeEntry(number)
|
.whenComplete((ignored, error) -> {
|
||||||
.whenComplete((ignored, error) -> {
|
if (error instanceof ResourceNotFoundException) {
|
||||||
if (error instanceof ResourceNotFoundException) {
|
// These will naturally happen if a recovery password is already deleted. Since we can remove
|
||||||
// These will naturally happen if a recovery password is already deleted. Since we can remove
|
// the recovery password through many flows, we avoid creating log messages for these exceptions
|
||||||
// the recovery password through many flows, we avoid creating log messages for these exceptions
|
} else if (error != null) {
|
||||||
} else if (error != null) {
|
logger.warn("Failed to remove Registration Recovery Password", error);
|
||||||
logger.warn("Failed to remove Registration Recovery Password", error);
|
}
|
||||||
}
|
}));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String bytesToString(final byte[] bytes) {
|
private static String bytesToString(final byte[] bytes) {
|
||||||
|
|
|
@ -170,9 +170,6 @@ record CommandDependencies(
|
||||||
dynamoDbAsyncClient
|
dynamoDbAsyncClient
|
||||||
);
|
);
|
||||||
|
|
||||||
RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager = new RegistrationRecoveryPasswordsManager(
|
|
||||||
registrationRecoveryPasswords);
|
|
||||||
|
|
||||||
ClientPublicKeys clientPublicKeys =
|
ClientPublicKeys clientPublicKeys =
|
||||||
new ClientPublicKeys(dynamoDbAsyncClient, configuration.getDynamoDbTables().getClientPublicKeys().getTableName());
|
new ClientPublicKeys(dynamoDbAsyncClient, configuration.getDynamoDbTables().getClientPublicKeys().getTableName());
|
||||||
|
|
||||||
|
@ -225,6 +222,8 @@ record CommandDependencies(
|
||||||
configuration.getDynamoDbTables().getDeletedAccountsLock().getTableName());
|
configuration.getDynamoDbTables().getDeletedAccountsLock().getTableName());
|
||||||
ClientPublicKeysManager clientPublicKeysManager =
|
ClientPublicKeysManager clientPublicKeysManager =
|
||||||
new ClientPublicKeysManager(clientPublicKeys, accountLockManager, accountLockExecutor);
|
new ClientPublicKeysManager(clientPublicKeys, accountLockManager, accountLockExecutor);
|
||||||
|
RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager =
|
||||||
|
new RegistrationRecoveryPasswordsManager(registrationRecoveryPasswords, phoneNumberIdentifiers);
|
||||||
AccountsManager accountsManager = new AccountsManager(accounts, phoneNumberIdentifiers, cacheCluster,
|
AccountsManager accountsManager = new AccountsManager(accounts, phoneNumberIdentifiers, cacheCluster,
|
||||||
pubsubClient, accountLockManager, keys, messagesManager, profilesManager,
|
pubsubClient, accountLockManager, keys, messagesManager, profilesManager,
|
||||||
secureStorageClient, secureValueRecovery2Client, disconnectionRequestManager,
|
secureStorageClient, secureValueRecovery2Client, disconnectionRequestManager,
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
package org.whispersystems.textsecuregcm.storage;
|
package org.whispersystems.textsecuregcm.storage;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
@ -14,6 +15,7 @@ import java.time.Clock;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
@ -34,6 +36,7 @@ public class RegistrationRecoveryTest {
|
||||||
private static final Duration EXPIRATION = Duration.ofSeconds(1000);
|
private static final Duration EXPIRATION = Duration.ofSeconds(1000);
|
||||||
|
|
||||||
private static final String NUMBER = "+18005555555";
|
private static final String NUMBER = "+18005555555";
|
||||||
|
private static final UUID PNI = UUID.randomUUID();
|
||||||
|
|
||||||
private static final SaltedTokenHash ORIGINAL_HASH = SaltedTokenHash.generateFor("pass1");
|
private static final SaltedTokenHash ORIGINAL_HASH = SaltedTokenHash.generateFor("pass1");
|
||||||
|
|
||||||
|
@ -41,72 +44,106 @@ public class RegistrationRecoveryTest {
|
||||||
|
|
||||||
@RegisterExtension
|
@RegisterExtension
|
||||||
private static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(
|
private static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(
|
||||||
|
Tables.PNI,
|
||||||
Tables.REGISTRATION_RECOVERY_PASSWORDS);
|
Tables.REGISTRATION_RECOVERY_PASSWORDS);
|
||||||
|
|
||||||
private RegistrationRecoveryPasswords store;
|
private RegistrationRecoveryPasswords registrationRecoveryPasswords;
|
||||||
|
|
||||||
private RegistrationRecoveryPasswordsManager manager;
|
private RegistrationRecoveryPasswordsManager manager;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void before() throws Exception {
|
public void before() throws Exception {
|
||||||
CLOCK.setTimeMillis(Clock.systemUTC().millis());
|
CLOCK.setTimeMillis(Clock.systemUTC().millis());
|
||||||
store = new RegistrationRecoveryPasswords(
|
registrationRecoveryPasswords = new RegistrationRecoveryPasswords(
|
||||||
Tables.REGISTRATION_RECOVERY_PASSWORDS.tableName(),
|
Tables.REGISTRATION_RECOVERY_PASSWORDS.tableName(),
|
||||||
EXPIRATION,
|
EXPIRATION,
|
||||||
DYNAMO_DB_EXTENSION.getDynamoDbClient(),
|
DYNAMO_DB_EXTENSION.getDynamoDbClient(),
|
||||||
DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(),
|
DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(),
|
||||||
CLOCK
|
CLOCK
|
||||||
);
|
);
|
||||||
manager = new RegistrationRecoveryPasswordsManager(store);
|
|
||||||
|
final PhoneNumberIdentifiers phoneNumberIdentifiers =
|
||||||
|
new PhoneNumberIdentifiers(DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(), Tables.PNI.tableName());
|
||||||
|
|
||||||
|
manager = new RegistrationRecoveryPasswordsManager(registrationRecoveryPasswords, phoneNumberIdentifiers);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLookupAfterWrite() throws Exception {
|
public void testLookupAfterWrite() throws Exception {
|
||||||
store.addOrReplace(NUMBER, ORIGINAL_HASH).get();
|
registrationRecoveryPasswords.addOrReplace(NUMBER, PNI, ORIGINAL_HASH).get();
|
||||||
final long initialExp = fetchTimestamp(NUMBER);
|
final long initialExp = fetchTimestamp(NUMBER);
|
||||||
final long expectedExpiration = CLOCK.instant().getEpochSecond() + EXPIRATION.getSeconds();
|
final long expectedExpiration = CLOCK.instant().getEpochSecond() + EXPIRATION.getSeconds();
|
||||||
assertEquals(expectedExpiration, initialExp);
|
assertEquals(expectedExpiration, initialExp);
|
||||||
|
|
||||||
final Optional<SaltedTokenHash> saltedTokenHash = store.lookup(NUMBER).get();
|
{
|
||||||
assertTrue(saltedTokenHash.isPresent());
|
final Optional<SaltedTokenHash> saltedTokenHashByNumber = registrationRecoveryPasswords.lookup(NUMBER).get();
|
||||||
assertEquals(ORIGINAL_HASH.salt(), saltedTokenHash.get().salt());
|
assertTrue(saltedTokenHashByNumber.isPresent());
|
||||||
assertEquals(ORIGINAL_HASH.hash(), saltedTokenHash.get().hash());
|
assertEquals(ORIGINAL_HASH.salt(), saltedTokenHashByNumber.get().salt());
|
||||||
|
assertEquals(ORIGINAL_HASH.hash(), saltedTokenHashByNumber.get().hash());
|
||||||
|
}
|
||||||
|
|
||||||
|
/* {
|
||||||
|
final Optional<SaltedTokenHash> saltedTokenHashByPni = registrationRecoveryPasswords.lookup(PNI).get();
|
||||||
|
assertTrue(saltedTokenHashByPni.isPresent());
|
||||||
|
assertEquals(ORIGINAL_HASH.salt(), saltedTokenHashByPni.get().salt());
|
||||||
|
assertEquals(ORIGINAL_HASH.hash(), saltedTokenHashByPni.get().hash());
|
||||||
|
} */
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLookupAfterRefresh() throws Exception {
|
public void testLookupAfterRefresh() throws Exception {
|
||||||
store.addOrReplace(NUMBER, ORIGINAL_HASH).get();
|
registrationRecoveryPasswords.addOrReplace(NUMBER, PNI, ORIGINAL_HASH).get();
|
||||||
|
|
||||||
CLOCK.increment(50, TimeUnit.SECONDS);
|
CLOCK.increment(50, TimeUnit.SECONDS);
|
||||||
store.addOrReplace(NUMBER, ORIGINAL_HASH).get();
|
registrationRecoveryPasswords.addOrReplace(NUMBER, PNI, ORIGINAL_HASH).get();
|
||||||
final long updatedExp = fetchTimestamp(NUMBER);
|
final long updatedExp = fetchTimestamp(NUMBER);
|
||||||
final long expectedExp = CLOCK.instant().getEpochSecond() + EXPIRATION.getSeconds();
|
final long expectedExp = CLOCK.instant().getEpochSecond() + EXPIRATION.getSeconds();
|
||||||
assertEquals(expectedExp, updatedExp);
|
assertEquals(expectedExp, updatedExp);
|
||||||
|
|
||||||
final Optional<SaltedTokenHash> saltedTokenHash = store.lookup(NUMBER).get();
|
{
|
||||||
assertTrue(saltedTokenHash.isPresent());
|
final Optional<SaltedTokenHash> saltedTokenHashByNumber = registrationRecoveryPasswords.lookup(NUMBER).get();
|
||||||
assertEquals(ORIGINAL_HASH.salt(), saltedTokenHash.get().salt());
|
assertTrue(saltedTokenHashByNumber.isPresent());
|
||||||
assertEquals(ORIGINAL_HASH.hash(), saltedTokenHash.get().hash());
|
assertEquals(ORIGINAL_HASH.salt(), saltedTokenHashByNumber.get().salt());
|
||||||
|
assertEquals(ORIGINAL_HASH.hash(), saltedTokenHashByNumber.get().hash());
|
||||||
|
}
|
||||||
|
|
||||||
|
/* {
|
||||||
|
final Optional<SaltedTokenHash> saltedTokenHashByPni = registrationRecoveryPasswords.lookup(PNI).get();
|
||||||
|
assertTrue(saltedTokenHashByPni.isPresent());
|
||||||
|
assertEquals(ORIGINAL_HASH.salt(), saltedTokenHashByPni.get().salt());
|
||||||
|
assertEquals(ORIGINAL_HASH.hash(), saltedTokenHashByPni.get().hash());
|
||||||
|
} */
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReplace() throws Exception {
|
public void testReplace() throws Exception {
|
||||||
store.addOrReplace(NUMBER, ORIGINAL_HASH).get();
|
registrationRecoveryPasswords.addOrReplace(NUMBER, PNI, ORIGINAL_HASH).get();
|
||||||
store.addOrReplace(NUMBER, ANOTHER_HASH).get();
|
registrationRecoveryPasswords.addOrReplace(NUMBER, PNI, ANOTHER_HASH).get();
|
||||||
|
|
||||||
final Optional<SaltedTokenHash> saltedTokenHash = store.lookup(NUMBER).get();
|
{
|
||||||
assertTrue(saltedTokenHash.isPresent());
|
final Optional<SaltedTokenHash> saltedTokenHashByNumber = registrationRecoveryPasswords.lookup(NUMBER).get();
|
||||||
assertEquals(ANOTHER_HASH.salt(), saltedTokenHash.get().salt());
|
assertTrue(saltedTokenHashByNumber.isPresent());
|
||||||
assertEquals(ANOTHER_HASH.hash(), saltedTokenHash.get().hash());
|
assertEquals(ANOTHER_HASH.salt(), saltedTokenHashByNumber.get().salt());
|
||||||
|
assertEquals(ANOTHER_HASH.hash(), saltedTokenHashByNumber.get().hash());
|
||||||
|
}
|
||||||
|
|
||||||
|
/* {
|
||||||
|
final Optional<SaltedTokenHash> saltedTokenHashByPni = registrationRecoveryPasswords.lookup(PNI).get();
|
||||||
|
assertTrue(saltedTokenHashByPni.isPresent());
|
||||||
|
assertEquals(ANOTHER_HASH.salt(), saltedTokenHashByPni.get().salt());
|
||||||
|
assertEquals(ANOTHER_HASH.hash(), saltedTokenHashByPni.get().hash());
|
||||||
|
} */
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRemove() throws Exception {
|
public void testRemove() throws Exception {
|
||||||
store.addOrReplace(NUMBER, ORIGINAL_HASH).get();
|
assertDoesNotThrow(() -> registrationRecoveryPasswords.removeEntry(NUMBER, PNI).join());
|
||||||
assertTrue(store.lookup(NUMBER).get().isPresent());
|
|
||||||
|
|
||||||
store.removeEntry(NUMBER).get();
|
registrationRecoveryPasswords.addOrReplace(NUMBER, PNI, ORIGINAL_HASH).get();
|
||||||
assertTrue(store.lookup(NUMBER).get().isEmpty());
|
assertTrue(registrationRecoveryPasswords.lookup(NUMBER).get().isPresent());
|
||||||
|
|
||||||
|
registrationRecoveryPasswords.removeEntry(NUMBER, PNI).get();
|
||||||
|
assertTrue(registrationRecoveryPasswords.lookup(NUMBER).get().isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
Loading…
Reference in New Issue