Handle negative directory case and unlisted contacts.

This commit is contained in:
Moxie Marlinspike 2013-10-01 16:26:04 -07:00
parent 75cca3add1
commit 2d083208cc
6 changed files with 101 additions and 32 deletions

View File

@ -15,8 +15,8 @@ import org.whispersystems.textsecure.util.Util;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -53,20 +53,7 @@ public class Directory {
this.databaseHelper = new DatabaseHelper(context, DATABASE_NAME, null, DATABASE_VERSION); this.databaseHelper = new DatabaseHelper(context, DATABASE_NAME, null, DATABASE_VERSION);
} }
public boolean containsNumbers(List<String> e164numbers) { public boolean isActiveNumber(String e164number) throws NotInDirectoryException {
if (e164numbers == null || e164numbers.isEmpty()) {
return false;
}
for (String e164number : e164numbers) {
if (!containsNumber(e164number))
return false;
}
return true;
}
public boolean containsNumber(String e164number) {
if (e164number == null || e164number.length() == 0) { if (e164number == null || e164number.length() == 0) {
return false; return false;
} }
@ -82,29 +69,33 @@ public class Directory {
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
return cursor.getInt(0) == 1; return cursor.getInt(0) == 1;
} else {
throw new NotInDirectoryException();
} }
return false;
} finally { } finally {
if (cursor != null) if (cursor != null)
cursor.close(); cursor.close();
} }
} }
public void setActiveTokens(List<String> tokens) { public void setToken(String token, boolean active) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(TOKEN, token);
values.put(REGISTERED, active ? 1 : 0);
values.put(TIMESTAMP, System.currentTimeMillis());
db.replace(TABLE_NAME, null, values);
}
public void setTokens(List<String> activeTokens, Collection<String> inactiveTokens) {
long timestamp = System.currentTimeMillis(); long timestamp = System.currentTimeMillis();
SQLiteDatabase db = databaseHelper.getWritableDatabase(); SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.beginTransaction(); db.beginTransaction();
try { try {
ContentValues clear = new ContentValues(); for (String token : activeTokens) {
clear.put(REGISTERED, 0); Log.w("Directory", "Adding active token: " + token);
clear.put(TIMESTAMP, timestamp);
db.update(TABLE_NAME, clear, null, null);
for (String token : tokens) {
Log.w("Directory", "Adding token: " + token);
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(TOKEN, token); values.put(TOKEN, token);
values.put(REGISTERED, 1); values.put(REGISTERED, 1);
@ -112,6 +103,14 @@ public class Directory {
db.replace(TABLE_NAME, null, values); db.replace(TABLE_NAME, null, values);
} }
for (String token : inactiveTokens) {
ContentValues values = new ContentValues();
values.put(TOKEN, token);
values.put(REGISTERED, 0);
values.put(TIMESTAMP, timestamp);
db.replace(TABLE_NAME, null, values);
}
db.setTransactionSuccessful(); db.setTransactionSuccessful();
} finally { } finally {
db.endTransaction(); db.endTransaction();
@ -135,6 +134,15 @@ public class Directory {
} }
} }
cursor.close();
cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, new String[] {TOKEN},
null, null, null, null, null);
while (cursor.moveToNext()) {
results.add(cursor.getString(0));
}
return results; return results;
} finally { } finally {
if (cursor != null) if (cursor != null)
@ -142,7 +150,7 @@ public class Directory {
} }
} }
private String getToken(String e164number) { public String getToken(String e164number) {
try { try {
MessageDigest digest = MessageDigest.getInstance("SHA1"); MessageDigest digest = MessageDigest.getInstance("SHA1");
byte[] token = Util.trim(digest.digest(e164number.getBytes()), 10); byte[] token = Util.trim(digest.digest(e164number.getBytes()), 10);

View File

@ -0,0 +1,4 @@
package org.whispersystems.textsecure.directory;
public class NotInDirectoryException extends Throwable {
}

View File

@ -0,0 +1,9 @@
package org.whispersystems.textsecure.push;
import java.io.IOException;
public class NotFoundException extends IOException {
public NotFoundException(String s) {
super(s);
}
}

View File

@ -42,7 +42,8 @@ public class PushServiceSocket {
private static final String REGISTER_GCM_PATH = "/v1/accounts/gcm/"; private static final String REGISTER_GCM_PATH = "/v1/accounts/gcm/";
private static final String PREKEY_PATH = "/v1/keys/%s"; private static final String PREKEY_PATH = "/v1/keys/%s";
private static final String DIRECTORY_PATH = "/v1/directory/"; private static final String DIRECTORY_TOKENS_PATH = "/v1/directory/tokens";
private static final String DIRECTORY_VERIFY_PATH = "/v1/directory/%s";
private static final String MESSAGE_PATH = "/v1/messages/"; private static final String MESSAGE_PATH = "/v1/messages/";
private static final String ATTACHMENT_PATH = "/v1/attachments/%s"; private static final String ATTACHMENT_PATH = "/v1/attachments/%s";
@ -172,7 +173,7 @@ public class PushServiceSocket {
public List<String> retrieveDirectory(Set<String> contactTokens) { public List<String> retrieveDirectory(Set<String> contactTokens) {
try { try {
ContactTokenList contactTokenList = new ContactTokenList(new LinkedList(contactTokens)); ContactTokenList contactTokenList = new ContactTokenList(new LinkedList(contactTokens));
String response = makeRequest(DIRECTORY_PATH, "PUT", new Gson().toJson(contactTokenList)); String response = makeRequest(DIRECTORY_TOKENS_PATH, "PUT", new Gson().toJson(contactTokenList));
ContactTokenList activeTokens = new Gson().fromJson(response, ContactTokenList.class); ContactTokenList activeTokens = new Gson().fromJson(response, ContactTokenList.class);
return activeTokens.getContacts(); return activeTokens.getContacts();
@ -182,6 +183,15 @@ public class PushServiceSocket {
} }
} }
public boolean isRegisteredUser(String contactToken) throws IOException {
try {
makeRequest(String.format(DIRECTORY_VERIFY_PATH, contactToken), "GET", null);
return true;
} catch (NotFoundException nfe) {
return false;
}
}
private void downloadExternalFile(String url, File localDestination) private void downloadExternalFile(String url, File localDestination)
throws IOException throws IOException
{ {
@ -284,6 +294,10 @@ public class PushServiceSocket {
throw new AuthorizationFailedException("Authorization failed!"); throw new AuthorizationFailedException("Authorization failed!");
} }
if (connection.getResponseCode() == 404) {
throw new NotFoundException("Not found");
}
if (connection.getResponseCode() != 200) { if (connection.getResponseCode() != 200) {
throw new IOException("Bad response: " + connection.getResponseCode() + " " + connection.getResponseMessage()); throw new IOException("Bad response: " + connection.getResponseCode() + " " + connection.getResponseMessage());
} }

View File

@ -286,8 +286,8 @@ public class RegistrationService extends Service {
List<String> activeTokens = socket.retrieveDirectory(contactTokens); List<String> activeTokens = socket.retrieveDirectory(contactTokens);
if (activeTokens != null) { if (activeTokens != null) {
Directory.getInstance(this).setActiveTokens(activeTokens); contactTokens.removeAll(activeTokens);
// NumberFilter.getInstance(this).update(directory.first, directory.second); Directory.getInstance(this).setTokens(activeTokens, contactTokens);
} }
} }

View File

@ -26,6 +26,8 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.directory.Directory; import org.whispersystems.textsecure.directory.Directory;
import org.whispersystems.textsecure.directory.NotInDirectoryException;
import org.whispersystems.textsecure.push.PushServiceSocket;
import java.io.IOException; import java.io.IOException;
import java.util.LinkedList; import java.util.LinkedList;
@ -57,7 +59,7 @@ public class UniversalTransport {
Recipient recipient = message.getIndividualRecipient(); Recipient recipient = message.getIndividualRecipient();
String number = Util.canonicalizeNumber(context, recipient.getNumber()); String number = Util.canonicalizeNumber(context, recipient.getNumber());
if (Directory.getInstance(context).containsNumber(number)) { if (isPushTransport(number)) {
try { try {
Log.w("UniversalTransport", "Delivering with GCM..."); Log.w("UniversalTransport", "Delivering with GCM...");
pushTransport.deliver(message); pushTransport.deliver(message);
@ -78,7 +80,7 @@ public class UniversalTransport {
List<String> destinations = getMediaDestinations(mediaMessage); List<String> destinations = getMediaDestinations(mediaMessage);
if (Directory.getInstance(context).containsNumbers(destinations)) { if (isPushTransport(destinations)) {
try { try {
Log.w("UniversalTransport", "Delivering media message with GCM..."); Log.w("UniversalTransport", "Delivering media message with GCM...");
pushTransport.deliver(mediaMessage, destinations); pushTransport.deliver(mediaMessage, destinations);
@ -117,4 +119,36 @@ public class UniversalTransport {
return destinations; return destinations;
} }
private boolean isPushTransport(String destination) {
Directory directory = Directory.getInstance(context);
try {
return directory.isActiveNumber(destination);
} catch (NotInDirectoryException e) {
try {
String localNumber = TextSecurePreferences.getLocalNumber(context);
String pushPassword = TextSecurePreferences.getPushServerPassword(context);
String contactToken = directory.getToken(destination);
PushServiceSocket socket = new PushServiceSocket(context, localNumber, pushPassword);
boolean registeredUser = socket.isRegisteredUser(contactToken);
directory.setToken(contactToken, registeredUser);
return registeredUser;
} catch (IOException e1) {
Log.w("UniversalTransport", e1);
return false;
}
}
}
private boolean isPushTransport(List<String> destinations) {
for (String destination : destinations) {
if (!isPushTransport(destination)) {
return false;
}
}
return true;
}
} }