Username reservation table
This commit is contained in:
parent
99c228dd6d
commit
523134f24b
|
@ -157,13 +157,14 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
|||
FaultTolerantDatabase messageDatabase = new FaultTolerantDatabase("message_database", messageJdbi, config.getMessageStoreConfiguration().getCircuitBreakerConfiguration());
|
||||
FaultTolerantDatabase abuseDatabase = new FaultTolerantDatabase("abuse_database", abuseJdbi, config.getAbuseDatabaseConfiguration().getCircuitBreakerConfiguration());
|
||||
|
||||
Accounts accounts = new Accounts(accountDatabase);
|
||||
PendingAccounts pendingAccounts = new PendingAccounts(accountDatabase);
|
||||
PendingDevices pendingDevices = new PendingDevices(accountDatabase);
|
||||
Usernames usernames = new Usernames(accountDatabase);
|
||||
Keys keys = new Keys(keysDatabase);
|
||||
Messages messages = new Messages(messageDatabase);
|
||||
AbusiveHostRules abusiveHostRules = new AbusiveHostRules(abuseDatabase);
|
||||
Accounts accounts = new Accounts(accountDatabase);
|
||||
PendingAccounts pendingAccounts = new PendingAccounts(accountDatabase);
|
||||
PendingDevices pendingDevices = new PendingDevices (accountDatabase);
|
||||
Usernames usernames = new Usernames(accountDatabase);
|
||||
ReservedUsernames reservedUsernames = new ReservedUsernames(accountDatabase);
|
||||
Keys keys = new Keys(keysDatabase);
|
||||
Messages messages = new Messages(messageDatabase);
|
||||
AbusiveHostRules abusiveHostRules = new AbusiveHostRules(abuseDatabase);
|
||||
|
||||
RedisClientFactory cacheClientFactory = new RedisClientFactory("main_cache", config.getCacheConfiguration().getUrl(), config.getCacheConfiguration().getReplicaUrls(), config.getCacheConfiguration().getCircuitBreakerConfiguration());
|
||||
RedisClientFactory directoryClientFactory = new RedisClientFactory("directory_cache", config.getDirectoryConfiguration().getRedisConfiguration().getUrl(), config.getDirectoryConfiguration().getRedisConfiguration().getReplicaUrls(), config.getDirectoryConfiguration().getRedisConfiguration().getCircuitBreakerConfiguration());
|
||||
|
@ -180,7 +181,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
|||
PendingAccountsManager pendingAccountsManager = new PendingAccountsManager(pendingAccounts, cacheClient);
|
||||
PendingDevicesManager pendingDevicesManager = new PendingDevicesManager (pendingDevices, cacheClient );
|
||||
AccountsManager accountsManager = new AccountsManager(accounts, directory, cacheClient);
|
||||
UsernamesManager usernamesManager = new UsernamesManager(usernames, cacheClient);
|
||||
UsernamesManager usernamesManager = new UsernamesManager(usernames, reservedUsernames, cacheClient);
|
||||
MessagesCache messagesCache = new MessagesCache(messagesClient, messages, accountsManager, config.getMessageCacheConfiguration().getPersistDelayMinutes());
|
||||
MessagesManager messagesManager = new MessagesManager(messages, messagesCache);
|
||||
DeadLetterHandler deadLetterHandler = new DeadLetterHandler(messagesManager);
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
package org.whispersystems.textsecuregcm.storage;
|
||||
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.codahale.metrics.SharedMetricRegistries;
|
||||
import com.codahale.metrics.Timer;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.whispersystems.textsecuregcm.util.Constants;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static com.codahale.metrics.MetricRegistry.name;
|
||||
|
||||
public class ReservedUsernames {
|
||||
|
||||
public static final String ID = "id";
|
||||
public static final String UID = "uuid";
|
||||
public static final String USERNAME = "username";
|
||||
|
||||
private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME);
|
||||
private final Timer queryTimer = metricRegistry.timer(name(ReservedUsernames.class, "query"));
|
||||
|
||||
private final FaultTolerantDatabase database;
|
||||
|
||||
public ReservedUsernames(FaultTolerantDatabase database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
public boolean isReserved(String username, UUID uuid) {
|
||||
return database.with(jdbi -> jdbi.withHandle(handle -> {
|
||||
try (Timer.Context ignored = queryTimer.time()) {
|
||||
Optional<Integer> reservations = handle.createQuery("SELECT COUNT(*) FROM reserved_usernames WHERE " + UID + " != :uuid AND :username ~* " + USERNAME)
|
||||
.bind("username", username)
|
||||
.bind("uuid", uuid)
|
||||
.mapTo(Integer.class)
|
||||
.findFirst();
|
||||
|
||||
return reservations.isPresent() && reservations.get() > 0;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setReserved(String username, UUID reservedFor) {
|
||||
database.use(jdbi -> jdbi.useHandle(handle -> {
|
||||
handle.createUpdate("INSERT INTO reserved_usernames (" + USERNAME + ", " + UID + ") VALUES(:username, :uuid)")
|
||||
.bind("username", username)
|
||||
.bind("uuid", reservedFor)
|
||||
.execute();
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
|
@ -30,15 +30,21 @@ public class UsernamesManager {
|
|||
private final Logger logger = LoggerFactory.getLogger(AccountsManager.class);
|
||||
|
||||
private final Usernames usernames;
|
||||
private final ReservedUsernames reservedUsernames;
|
||||
private final ReplicatedJedisPool cacheClient;
|
||||
|
||||
public UsernamesManager(Usernames usernames, ReplicatedJedisPool cacheClient) {
|
||||
this.usernames = usernames;
|
||||
this.cacheClient = cacheClient;
|
||||
public UsernamesManager(Usernames usernames, ReservedUsernames reservedUsernames, ReplicatedJedisPool cacheClient) {
|
||||
this.usernames = usernames;
|
||||
this.reservedUsernames = reservedUsernames;
|
||||
this.cacheClient = cacheClient;
|
||||
}
|
||||
|
||||
public boolean put(UUID uuid, String username) {
|
||||
try (Timer.Context ignored = createTimer.time()) {
|
||||
if (reservedUsernames.isReserved(username, uuid)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (databasePut(uuid, username)) {
|
||||
redisSet(uuid, username);
|
||||
|
||||
|
|
|
@ -223,4 +223,20 @@
|
|||
</createTable>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="10" author="moxie">
|
||||
<createTable tableName="reserved_usernames">
|
||||
<column name="id" type="bigint" autoIncrement="true">
|
||||
<constraints nullable="false" primaryKey="true"/>
|
||||
</column>
|
||||
|
||||
<column name="username" type="text">
|
||||
<constraints nullable="false" unique="true"/>
|
||||
</column>
|
||||
|
||||
<column name="uuid" type="uuid">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
package org.whispersystems.textsecuregcm.tests.storage;
|
||||
|
||||
import com.opentable.db.postgres.embedded.LiquibasePreparer;
|
||||
import com.opentable.db.postgres.junit.EmbeddedPostgresRules;
|
||||
import com.opentable.db.postgres.junit.PreparedDbRule;
|
||||
import org.jdbi.v3.core.Jdbi;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration;
|
||||
import org.whispersystems.textsecuregcm.storage.FaultTolerantDatabase;
|
||||
import org.whispersystems.textsecuregcm.storage.ReservedUsernames;
|
||||
import org.whispersystems.textsecuregcm.storage.Usernames;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
public class ReservedUsernamesTest {
|
||||
|
||||
@Rule
|
||||
public PreparedDbRule db = EmbeddedPostgresRules.preparedDatabase(LiquibasePreparer.forClasspathLocation("accountsdb.xml"));
|
||||
|
||||
private ReservedUsernames reserved;
|
||||
|
||||
@Before
|
||||
public void setupAccountsDao() {
|
||||
FaultTolerantDatabase faultTolerantDatabase = new FaultTolerantDatabase("reservedUsernamesTest",
|
||||
Jdbi.create(db.getTestDatabase()),
|
||||
new CircuitBreakerConfiguration());
|
||||
|
||||
this.reserved = new ReservedUsernames(faultTolerantDatabase);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReservedRegexp() {
|
||||
UUID reservedFor = UUID.randomUUID();
|
||||
String username = ".*myusername.*";
|
||||
|
||||
reserved.setReserved(username, reservedFor);
|
||||
|
||||
|
||||
assertTrue(reserved.isReserved("myusername", UUID.randomUUID()));
|
||||
assertFalse(reserved.isReserved("myusername", reservedFor));
|
||||
assertFalse(reserved.isReserved("thyusername", UUID.randomUUID()));
|
||||
assertTrue(reserved.isReserved("somemyusername", UUID.randomUUID()));
|
||||
assertTrue(reserved.isReserved("myusernamesome", UUID.randomUUID()));
|
||||
assertTrue(reserved.isReserved("somemyusernamesome", UUID.randomUUID()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReservedLiteral() {
|
||||
UUID reservedFor = UUID.randomUUID();
|
||||
String username = "^foobar$";
|
||||
|
||||
reserved.setReserved(username, reservedFor);
|
||||
|
||||
assertTrue(reserved.isReserved("foobar", UUID.randomUUID()));
|
||||
assertFalse(reserved.isReserved("foobar", reservedFor));
|
||||
assertFalse(reserved.isReserved("somefoobar", UUID.randomUUID()));
|
||||
assertFalse(reserved.isReserved("foobarsome", UUID.randomUUID()));
|
||||
assertFalse(reserved.isReserved("somefoobarsome", UUID.randomUUID()));
|
||||
}
|
||||
}
|
|
@ -2,21 +2,16 @@ package org.whispersystems.textsecuregcm.tests.storage;
|
|||
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.redis.ReplicatedJedisPool;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.Accounts;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.DirectoryManager;
|
||||
import org.whispersystems.textsecuregcm.storage.ReservedUsernames;
|
||||
import org.whispersystems.textsecuregcm.storage.Usernames;
|
||||
import org.whispersystems.textsecuregcm.storage.UsernamesManager;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static junit.framework.TestCase.assertSame;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.*;
|
||||
import redis.clients.jedis.Jedis;
|
||||
|
@ -29,13 +24,14 @@ public class UsernamesManagerTest {
|
|||
ReplicatedJedisPool cacheClient = mock(ReplicatedJedisPool.class);
|
||||
Jedis jedis = mock(Jedis.class );
|
||||
Usernames usernames = mock(Usernames.class );
|
||||
ReservedUsernames reserved = mock(ReservedUsernames.class);
|
||||
|
||||
UUID uuid = UUID.randomUUID();
|
||||
|
||||
when(cacheClient.getReadResource()).thenReturn(jedis);
|
||||
when(jedis.get(eq("UsernameByUsername::n00bkiller"))).thenReturn(uuid.toString());
|
||||
|
||||
UsernamesManager usernamesManager = new UsernamesManager(usernames, cacheClient);
|
||||
UsernamesManager usernamesManager = new UsernamesManager(usernames, reserved, cacheClient);
|
||||
Optional<UUID> retrieved = usernamesManager.get("n00bkiller");
|
||||
|
||||
assertTrue(retrieved.isPresent());
|
||||
|
@ -52,13 +48,14 @@ public class UsernamesManagerTest {
|
|||
ReplicatedJedisPool cacheClient = mock(ReplicatedJedisPool.class);
|
||||
Jedis jedis = mock(Jedis.class );
|
||||
Usernames usernames = mock(Usernames.class );
|
||||
ReservedUsernames reserved = mock(ReservedUsernames.class);
|
||||
|
||||
UUID uuid = UUID.randomUUID();
|
||||
|
||||
when(cacheClient.getReadResource()).thenReturn(jedis);
|
||||
when(jedis.get(eq("UsernameByUuid::" + uuid.toString()))).thenReturn("n00bkiller");
|
||||
|
||||
UsernamesManager usernamesManager = new UsernamesManager(usernames, cacheClient);
|
||||
UsernamesManager usernamesManager = new UsernamesManager(usernames, reserved, cacheClient);
|
||||
Optional<String> retrieved = usernamesManager.get(uuid);
|
||||
|
||||
assertTrue(retrieved.isPresent());
|
||||
|
@ -76,6 +73,7 @@ public class UsernamesManagerTest {
|
|||
ReplicatedJedisPool cacheClient = mock(ReplicatedJedisPool.class);
|
||||
Jedis jedis = mock(Jedis.class );
|
||||
Usernames usernames = mock(Usernames.class );
|
||||
ReservedUsernames reserved = mock(ReservedUsernames.class);
|
||||
|
||||
UUID uuid = UUID.randomUUID();
|
||||
|
||||
|
@ -85,7 +83,7 @@ public class UsernamesManagerTest {
|
|||
when(jedis.get(eq("UsernameByUsername::n00bkiller"))).thenReturn(null);
|
||||
when(usernames.get(eq("n00bkiller"))).thenReturn(Optional.of(uuid));
|
||||
|
||||
UsernamesManager usernamesManager = new UsernamesManager(usernames, cacheClient);
|
||||
UsernamesManager usernamesManager = new UsernamesManager(usernames, reserved, cacheClient);
|
||||
Optional<UUID> retrieved = usernamesManager.get("n00bkiller");
|
||||
|
||||
assertTrue(retrieved.isPresent());
|
||||
|
@ -106,6 +104,7 @@ public class UsernamesManagerTest {
|
|||
ReplicatedJedisPool cacheClient = mock(ReplicatedJedisPool.class);
|
||||
Jedis jedis = mock(Jedis.class );
|
||||
Usernames usernames = mock(Usernames.class );
|
||||
ReservedUsernames reserved = mock(ReservedUsernames.class);
|
||||
|
||||
UUID uuid = UUID.randomUUID();
|
||||
|
||||
|
@ -114,7 +113,7 @@ public class UsernamesManagerTest {
|
|||
when(jedis.get(eq("UsernameByUuid::" + uuid.toString()))).thenReturn(null);
|
||||
when(usernames.get(eq(uuid))).thenReturn(Optional.of("n00bkiller"));
|
||||
|
||||
UsernamesManager usernamesManager = new UsernamesManager(usernames, cacheClient);
|
||||
UsernamesManager usernamesManager = new UsernamesManager(usernames, reserved, cacheClient);
|
||||
Optional<String> retrieved = usernamesManager.get(uuid);
|
||||
|
||||
assertTrue(retrieved.isPresent());
|
||||
|
@ -135,6 +134,7 @@ public class UsernamesManagerTest {
|
|||
ReplicatedJedisPool cacheClient = mock(ReplicatedJedisPool.class);
|
||||
Jedis jedis = mock(Jedis.class );
|
||||
Usernames usernames = mock(Usernames.class );
|
||||
ReservedUsernames reserved = mock(ReservedUsernames.class);
|
||||
|
||||
UUID uuid = UUID.randomUUID();
|
||||
|
||||
|
@ -143,7 +143,7 @@ public class UsernamesManagerTest {
|
|||
when(jedis.get(eq("UsernameByUsername::n00bkiller"))).thenThrow(new JedisException("Connection lost!"));
|
||||
when(usernames.get(eq("n00bkiller"))).thenReturn(Optional.of(uuid));
|
||||
|
||||
UsernamesManager usernamesManager = new UsernamesManager(usernames, cacheClient);
|
||||
UsernamesManager usernamesManager = new UsernamesManager(usernames, reserved, cacheClient);
|
||||
Optional<UUID> retrieved = usernamesManager.get("n00bkiller");
|
||||
|
||||
assertTrue(retrieved.isPresent());
|
||||
|
@ -164,6 +164,7 @@ public class UsernamesManagerTest {
|
|||
ReplicatedJedisPool cacheClient = mock(ReplicatedJedisPool.class);
|
||||
Jedis jedis = mock(Jedis.class );
|
||||
Usernames usernames = mock(Usernames.class );
|
||||
ReservedUsernames reserved = mock(ReservedUsernames.class);
|
||||
|
||||
UUID uuid = UUID.randomUUID();
|
||||
|
||||
|
@ -172,7 +173,7 @@ public class UsernamesManagerTest {
|
|||
when(jedis.get(eq("UsernameByUuid::" + uuid))).thenThrow(new JedisException("Connection lost!"));
|
||||
when(usernames.get(eq(uuid))).thenReturn(Optional.of("n00bkiller"));
|
||||
|
||||
UsernamesManager usernamesManager = new UsernamesManager(usernames, cacheClient);
|
||||
UsernamesManager usernamesManager = new UsernamesManager(usernames, reserved, cacheClient);
|
||||
Optional<String> retrieved = usernamesManager.get(uuid);
|
||||
|
||||
assertTrue(retrieved.isPresent());
|
||||
|
|
Loading…
Reference in New Issue