Add GV2 accept by PNI invite.

fork-5.53.8
Cody Henthorne 2022-07-11 15:20:00 -04:00
rodzic b223ebe95e
commit c4bef8099f
71 zmienionych plików z 1468 dodań i 1016 usunięć

Wyświetl plik

@ -193,13 +193,13 @@ android {
buildConfigField "String[]", "SIGNAL_CONTENT_PROXY_IPS", content_proxy_ips buildConfigField "String[]", "SIGNAL_CONTENT_PROXY_IPS", content_proxy_ips
buildConfigField "String", "SIGNAL_AGENT", "\"OWA\"" buildConfigField "String", "SIGNAL_AGENT", "\"OWA\""
buildConfigField "String", "CDS_MRENCLAVE", "\"c98e00a4e3ff977a56afefe7362a27e4961e4f19e211febfbb19b897e6b80b15\"" buildConfigField "String", "CDS_MRENCLAVE", "\"c98e00a4e3ff977a56afefe7362a27e4961e4f19e211febfbb19b897e6b80b15\""
buildConfigField "String", "CDSI_MRENCLAVE", "\"42e36b74794abe612d698308b148ff8a7dc5fdc6ad28d99bc5024ed6ece18dfe\"" buildConfigField "String", "CDSI_MRENCLAVE", "\"e5eaa62da3514e8b37ccabddb87e52e7f319ccf5120a13f9e1b42b87ec9dd3dd\""
buildConfigField "org.thoughtcrime.securesms.KbsEnclave", "KBS_ENCLAVE", "new org.thoughtcrime.securesms.KbsEnclave(\"0cedba03535b41b67729ce9924185f831d7767928a1d1689acb689bc079c375f\", " + buildConfigField "org.thoughtcrime.securesms.KbsEnclave", "KBS_ENCLAVE", "new org.thoughtcrime.securesms.KbsEnclave(\"0cedba03535b41b67729ce9924185f831d7767928a1d1689acb689bc079c375f\", " +
"\"187d2739d22be65e74b65f0055e74d31310e4267e5fac2b1246cc8beba81af39\", " + "\"187d2739d22be65e74b65f0055e74d31310e4267e5fac2b1246cc8beba81af39\", " +
"\"ee19f1965b1eefa3dc4204eb70c04f397755f771b8c1909d080c04dad2a6a9ba\")" "\"ee19f1965b1eefa3dc4204eb70c04f397755f771b8c1909d080c04dad2a6a9ba\")"
buildConfigField "org.thoughtcrime.securesms.KbsEnclave[]", "KBS_FALLBACKS", "new org.thoughtcrime.securesms.KbsEnclave[0]" buildConfigField "org.thoughtcrime.securesms.KbsEnclave[]", "KBS_FALLBACKS", "new org.thoughtcrime.securesms.KbsEnclave[0]"
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF\"" buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF\""
buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X36nOoGPs54XsEGzPdEV+itQNGUFEjY6X9Uv+Acuks7NpyGvCoKxGwgKgE5XyJ+nNKlyHHOLb6N1NuHyBrZrgtY/JYJHRooo5CEqYKBqdFnmbTVGEkCvJKxLnjwKWf+fEPoWeQFj5ObDjcKMZf2Jm2Ae69x+ikU5gBXsRmoF94GXQ==\"" buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X36nOoGPs54XsEGzPdEV+itQNGUFEjY6X9Uv+Acuks7NpyGvCoKxGwgKgE5XyJ+nNKlyHHOLb6N1NuHyBrZrgtY/JYJHRooo5CEqYKBqdFnmbTVGEkCvJKxLnjwKWf+fEPoWeQFj5ObDjcKMZf2Jm2Ae69x+ikU5gBXsRmoF94GXTLfN0/vLt98KDPnxwAQL9j5V1jGOY8jQl6MLxEs56cwXN0dqCnImzVH3TZT1cJ8SW1BRX6qIVxEzjsSGx3yxF3suAilPMqGRp4ffyopjMD1JXiKR2RwLKzizUe5e8XyGOy9fplzhw3jVzTRyUZTRSZKkMLWcQ/gv0E4aONNqs4P\""
buildConfigField "String[]", "LANGUAGES", "new String[]{\"" + autoResConfig().collect { s -> s.replace('-r', '_') }.join('", "') + '"}' buildConfigField "String[]", "LANGUAGES", "new String[]{\"" + autoResConfig().collect { s -> s.replace('-r', '_') }.join('", "') + '"}'
buildConfigField "int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode" buildConfigField "int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode"
buildConfigField "String", "DEFAULT_CURRENCIES", "\"EUR,AUD,GBP,CAD,CNY\"" buildConfigField "String", "DEFAULT_CURRENCIES", "\"EUR,AUD,GBP,CAD,CNY\""
@ -337,7 +337,7 @@ android {
"\"ee19f1965b1eefa3dc4204eb70c04f397755f771b8c1909d080c04dad2a6a9ba\")" "\"ee19f1965b1eefa3dc4204eb70c04f397755f771b8c1909d080c04dad2a6a9ba\")"
buildConfigField "org.thoughtcrime.securesms.KbsEnclave[]", "KBS_FALLBACKS", "new org.thoughtcrime.securesms.KbsEnclave[0]" buildConfigField "org.thoughtcrime.securesms.KbsEnclave[]", "KBS_FALLBACKS", "new org.thoughtcrime.securesms.KbsEnclave[0]"
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx\"" buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx\""
buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdlukrpzzsCIvEwjwQlJYVPOQPj4V0F4UXXBdHSLK05uoPBCQG8G9rYIGedYsClJXnbrgGYG3eMTG5hnx4X4ntARBgELuMWWUEEfSK0mjXg+/2lPmWcTZWR9nkqgQQP0tbzuiPm74H2wMO4u1Wafe+UwyIlIT9L7KLS19Aw8r4sPrXQ==\"" buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdlukrpzzsCIvEwjwQlJYVPOQPj4V0F4UXXBdHSLK05uoPBCQG8G9rYIGedYsClJXnbrgGYG3eMTG5hnx4X4ntARBgELuMWWUEEfSK0mjXg+/2lPmWcTZWR9nkqgQQP0tbzuiPm74H2wMO4u1Wafe+UwyIlIT9L7KLS19Aw8r4sPrXZSSsOZ6s7M1+rTJN0bI5CKY2PX29y5Ok3jSWufIKcgKOnWoP67d5b2du2ZVJjpjfibNIHbT/cegy/sBLoFwtHogVYUewANUAXIaMPyCLRArsKhfJ5wBtTminG/PAvuBdJ70Z/bXVPf8TVsR292zQ65xwvWTejROW6AZX6aqucUj\""
buildConfigField "String", "MOBILE_COIN_ENVIRONMENT", "\"testnet\"" buildConfigField "String", "MOBILE_COIN_ENVIRONMENT", "\"testnet\""
buildConfigField "String", "SIGNAL_CAPTCHA_URL", "\"https://signalcaptchas.org/staging/registration/generate.html\"" buildConfigField "String", "SIGNAL_CAPTCHA_URL", "\"https://signalcaptchas.org/staging/registration/generate.html\""
buildConfigField "String", "RECAPTCHA_PROOF_URL", "\"https://signalcaptchas.org/staging/challenge/generate.html\"" buildConfigField "String", "RECAPTCHA_PROOF_URL", "\"https://signalcaptchas.org/staging/challenge/generate.html\""

Wyświetl plik

@ -77,6 +77,9 @@ object ContactDiscoveryRefreshV2 {
) )
stopwatch.split("recipient-db") stopwatch.split("recipient-db")
SignalDatabase.recipients.bulkUpdatedRegisteredStatus(registeredIds.associateWith { null }, emptyList())
stopwatch.split("update-registered")
stopwatch.stop(TAG) stopwatch.stop(TAG)
return ContactDiscovery.RefreshResult(registeredIds, emptyMap()) return ContactDiscovery.RefreshResult(registeredIds, emptyMap())
@ -127,6 +130,9 @@ object ContactDiscoveryRefreshV2 {
) )
stopwatch.split("recipient-db") stopwatch.split("recipient-db")
SignalDatabase.recipients.bulkUpdatedRegisteredStatus(registeredIds.associateWith { null }, emptyList())
stopwatch.split("update-registered")
stopwatch.stop(TAG) stopwatch.stop(TAG)
return ContactDiscovery.RefreshResult(registeredIds, emptyMap()) return ContactDiscovery.RefreshResult(registeredIds, emptyMap())

Wyświetl plik

@ -311,6 +311,7 @@ import org.thoughtcrime.securesms.verify.VerifyIdentityActivity;
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper; import org.thoughtcrime.securesms.wallpaper.ChatWallpaper;
import org.thoughtcrime.securesms.wallpaper.ChatWallpaperDimLevelUtil; import org.thoughtcrime.securesms.wallpaper.ChatWallpaperDimLevelUtil;
import org.whispersystems.signalservice.api.SignalSessionLock; import org.whispersystems.signalservice.api.SignalSessionLock;
import org.whispersystems.signalservice.api.util.ExpiringProfileCredentialUtil;
import java.io.IOException; import java.io.IOException;
import java.security.SecureRandom; import java.security.SecureRandom;
@ -1267,7 +1268,7 @@ public class ConversationParentFragment extends Fragment
AttachmentManager.selectLocation(this, PICK_LOCATION, getSendButtonColor(sendButton.getSelectedSendType())); AttachmentManager.selectLocation(this, PICK_LOCATION, getSendButtonColor(sendButton.getSelectedSendType()));
break; break;
case PAYMENT: case PAYMENT:
if (recipient.get().hasProfileKeyCredential()) { if (ExpiringProfileCredentialUtil.isValid(recipient.get().getExpiringProfileKeyCredential())) {
AttachmentManager.selectPayment(this, recipient.getId()); AttachmentManager.selectPayment(this, recipient.getId());
} else { } else {
CanNotSendPaymentDialog.show(requireActivity()); CanNotSendPaymentDialog.show(requireActivity());

Wyświetl plik

@ -6,7 +6,6 @@ import androidx.annotation.Nullable;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredential;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
@ -63,18 +62,6 @@ public final class ProfileKeyUtil {
} }
} }
public static @Nullable ProfileKeyCredential profileKeyCredentialOrNull(@Nullable byte[] profileKeyCredential) {
if (profileKeyCredential != null) {
try {
return new ProfileKeyCredential(profileKeyCredential);
} catch (InvalidInputException e) {
Log.w(TAG, String.format(Locale.US, "Seen non-null profile key credential of wrong length %d", profileKeyCredential.length), e);
}
}
return null;
}
public static @NonNull ProfileKey profileKeyOrThrow(@NonNull byte[] profileKey) { public static @NonNull ProfileKey profileKeyOrThrow(@NonNull byte[] profileKey) {
try { try {
return new ProfileKey(profileKey); return new ProfileKey(profileKey);

Wyświetl plik

@ -83,6 +83,9 @@ public class GroupDatabase extends Database {
private static final String UNMIGRATED_V1_MEMBERS = "former_v1_members"; private static final String UNMIGRATED_V1_MEMBERS = "former_v1_members";
private static final String DISTRIBUTION_ID = "distribution_id"; private static final String DISTRIBUTION_ID = "distribution_id";
private static final String DISPLAY_AS_STORY = "display_as_story"; private static final String DISPLAY_AS_STORY = "display_as_story";
/** Was temporarily used for PNP accept by pni but is no longer needed/updated */
@Deprecated
private static final String AUTH_SERVICE_ID = "auth_service_id"; private static final String AUTH_SERVICE_ID = "auth_service_id";
@ -125,7 +128,7 @@ public class GroupDatabase extends Database {
private static final String[] GROUP_PROJECTION = { private static final String[] GROUP_PROJECTION = {
GROUP_ID, RECIPIENT_ID, TITLE, MEMBERS, UNMIGRATED_V1_MEMBERS, AVATAR_ID, AVATAR_KEY, AVATAR_CONTENT_TYPE, AVATAR_RELAY, AVATAR_DIGEST, GROUP_ID, RECIPIENT_ID, TITLE, MEMBERS, UNMIGRATED_V1_MEMBERS, AVATAR_ID, AVATAR_KEY, AVATAR_CONTENT_TYPE, AVATAR_RELAY, AVATAR_DIGEST,
TIMESTAMP, ACTIVE, MMS, V2_MASTER_KEY, V2_REVISION, V2_DECRYPTED_GROUP, AUTH_SERVICE_ID TIMESTAMP, ACTIVE, MMS, V2_MASTER_KEY, V2_REVISION, V2_DECRYPTED_GROUP
}; };
static final List<String> TYPED_GROUP_PROJECTION = Stream.of(GROUP_PROJECTION).map(columnName -> TABLE_NAME + "." + columnName).toList(); static final List<String> TYPED_GROUP_PROJECTION = Stream.of(GROUP_PROJECTION).map(columnName -> TABLE_NAME + "." + columnName).toList();
@ -477,25 +480,23 @@ public class GroupDatabase extends Database {
if (groupExists(groupId.deriveV2MigrationGroupId())) { if (groupExists(groupId.deriveV2MigrationGroupId())) {
throw new LegacyGroupInsertException(groupId); throw new LegacyGroupInsertException(groupId);
} }
create(null, groupId, title, members, avatar, relay, null, null); create(groupId, title, members, avatar, relay, null, null);
} }
public void create(@NonNull GroupId.Mms groupId, public void create(@NonNull GroupId.Mms groupId,
@Nullable String title, @Nullable String title,
@NonNull Collection<RecipientId> members) @NonNull Collection<RecipientId> members)
{ {
create(null, groupId, Util.isEmpty(title) ? null : title, members, null, null, null, null); create(groupId, Util.isEmpty(title) ? null : title, members, null, null, null, null);
} }
public GroupId.V2 create(@Nullable ServiceId authServiceId, public GroupId.V2 create(@NonNull GroupMasterKey groupMasterKey,
@NonNull GroupMasterKey groupMasterKey,
@NonNull DecryptedGroup groupState) @NonNull DecryptedGroup groupState)
{ {
return create(authServiceId, groupMasterKey, groupState, false); return create(groupMasterKey, groupState, false);
} }
public GroupId.V2 create(@Nullable ServiceId authServiceId, public GroupId.V2 create(@NonNull GroupMasterKey groupMasterKey,
@NonNull GroupMasterKey groupMasterKey,
@NonNull DecryptedGroup groupState, @NonNull DecryptedGroup groupState,
boolean force) boolean force)
{ {
@ -507,7 +508,7 @@ public class GroupDatabase extends Database {
Log.w(TAG, "Forcing the creation of a group even though we already have a V1 ID!"); Log.w(TAG, "Forcing the creation of a group even though we already have a V1 ID!");
} }
create(authServiceId, groupId, groupState.getTitle(), Collections.emptyList(), null, null, groupMasterKey, groupState); create(groupId, groupState.getTitle(), Collections.emptyList(), null, null, groupMasterKey, groupState);
return groupId; return groupId;
} }
@ -537,8 +538,8 @@ public class GroupDatabase extends Database {
if (updated < 1) { if (updated < 1) {
Log.w(TAG, "No group entry. Creating restore placeholder for " + groupId); Log.w(TAG, "No group entry. Creating restore placeholder for " + groupId);
create(authServiceId, create(
groupMasterKey, groupMasterKey,
DecryptedGroup.newBuilder() DecryptedGroup.newBuilder()
.setRevision(GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION) .setRevision(GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION)
.build(), .build(),
@ -559,8 +560,7 @@ public class GroupDatabase extends Database {
/** /**
* @param groupMasterKey null for V1, must be non-null for V2 (presence dictates group version). * @param groupMasterKey null for V1, must be non-null for V2 (presence dictates group version).
*/ */
private void create(@Nullable ServiceId authServiceId, private void create(@NonNull GroupId groupId,
@NonNull GroupId groupId,
@Nullable String title, @Nullable String title,
@NonNull Collection<RecipientId> memberCollection, @NonNull Collection<RecipientId> memberCollection,
@Nullable SignalServiceAttachmentPointer avatar, @Nullable SignalServiceAttachmentPointer avatar,
@ -575,7 +575,6 @@ public class GroupDatabase extends Database {
Collections.sort(members); Collections.sort(members);
ContentValues contentValues = new ContentValues(); ContentValues contentValues = new ContentValues();
contentValues.put(AUTH_SERVICE_ID, authServiceId != null ? authServiceId.toString() : null);
contentValues.put(RECIPIENT_ID, groupRecipientId.serialize()); contentValues.put(RECIPIENT_ID, groupRecipientId.serialize());
contentValues.put(GROUP_ID, groupId.toString()); contentValues.put(GROUP_ID, groupId.toString());
contentValues.put(TITLE, title); contentValues.put(TITLE, title);
@ -987,13 +986,6 @@ public class GroupDatabase extends Database {
return result; return result;
} }
public void setAuthServiceId(@Nullable ServiceId authServiceId, @NonNull GroupId groupId) {
ContentValues values = new ContentValues(1);
values.put(AUTH_SERVICE_ID, authServiceId == null ? null : authServiceId.toString());
getWritableDatabase().update(TABLE_NAME, values, GROUP_ID + " = ?", SqlUtil.buildArgs(groupId));
}
public static class Reader implements Closeable { public static class Reader implements Closeable {
public final Cursor cursor; public final Cursor cursor;
@ -1038,8 +1030,7 @@ public class GroupDatabase extends Database {
CursorUtil.requireBlob(cursor, V2_MASTER_KEY), CursorUtil.requireBlob(cursor, V2_MASTER_KEY),
CursorUtil.requireInt(cursor, V2_REVISION), CursorUtil.requireInt(cursor, V2_REVISION),
CursorUtil.requireBlob(cursor, V2_DECRYPTED_GROUP), CursorUtil.requireBlob(cursor, V2_DECRYPTED_GROUP),
CursorUtil.getString(cursor, DISTRIBUTION_ID).map(DistributionId::from).orElse(null), CursorUtil.getString(cursor, DISTRIBUTION_ID).map(DistributionId::from).orElse(null));
CursorUtil.requireString(cursor, AUTH_SERVICE_ID));
} }
@Override @Override
@ -1065,7 +1056,6 @@ public class GroupDatabase extends Database {
private final boolean mms; private final boolean mms;
@Nullable private final V2GroupProperties v2GroupProperties; @Nullable private final V2GroupProperties v2GroupProperties;
private final DistributionId distributionId; private final DistributionId distributionId;
@Nullable private final String authServiceId;
public GroupRecord(@NonNull GroupId id, public GroupRecord(@NonNull GroupId id,
@NonNull RecipientId recipientId, @NonNull RecipientId recipientId,
@ -1082,8 +1072,7 @@ public class GroupDatabase extends Database {
@Nullable byte[] groupMasterKeyBytes, @Nullable byte[] groupMasterKeyBytes,
int groupRevision, int groupRevision,
@Nullable byte[] decryptedGroupBytes, @Nullable byte[] decryptedGroupBytes,
@Nullable DistributionId distributionId, @Nullable DistributionId distributionId)
@Nullable String authServiceId)
{ {
this.id = id; this.id = id;
this.recipientId = recipientId; this.recipientId = recipientId;
@ -1096,7 +1085,6 @@ public class GroupDatabase extends Database {
this.active = active; this.active = active;
this.mms = mms; this.mms = mms;
this.distributionId = distributionId; this.distributionId = distributionId;
this.authServiceId = authServiceId;
V2GroupProperties v2GroupProperties = null; V2GroupProperties v2GroupProperties = null;
if (groupMasterKeyBytes != null && decryptedGroupBytes != null) { if (groupMasterKeyBytes != null && decryptedGroupBytes != null) {
@ -1285,10 +1273,6 @@ public class GroupDatabase extends Database {
} }
return false; return false;
} }
public @Nullable ServiceId getAuthServiceId() {
return ServiceId.parseOrNull(authServiceId);
}
} }
public static class V2GroupProperties { public static class V2GroupProperties {

Wyświetl plik

@ -33,8 +33,8 @@ import org.signal.core.util.withinTransaction
import org.signal.libsignal.protocol.IdentityKey import org.signal.libsignal.protocol.IdentityKey
import org.signal.libsignal.protocol.InvalidKeyException import org.signal.libsignal.protocol.InvalidKeyException
import org.signal.libsignal.zkgroup.InvalidInputException import org.signal.libsignal.zkgroup.InvalidInputException
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential
import org.signal.libsignal.zkgroup.profiles.ProfileKey import org.signal.libsignal.zkgroup.profiles.ProfileKey
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredential
import org.signal.storageservice.protos.groups.local.DecryptedGroup import org.signal.storageservice.protos.groups.local.DecryptedGroup
import org.thoughtcrime.securesms.badges.Badges import org.thoughtcrime.securesms.badges.Badges
import org.thoughtcrime.securesms.badges.Badges.toDatabaseBadge import org.thoughtcrime.securesms.badges.Badges.toDatabaseBadge
@ -66,7 +66,7 @@ import org.thoughtcrime.securesms.database.model.ThreadRecord
import org.thoughtcrime.securesms.database.model.databaseprotos.BadgeList import org.thoughtcrime.securesms.database.model.databaseprotos.BadgeList
import org.thoughtcrime.securesms.database.model.databaseprotos.ChatColor import org.thoughtcrime.securesms.database.model.databaseprotos.ChatColor
import org.thoughtcrime.securesms.database.model.databaseprotos.DeviceLastResetTime import org.thoughtcrime.securesms.database.model.databaseprotos.DeviceLastResetTime
import org.thoughtcrime.securesms.database.model.databaseprotos.ProfileKeyCredentialColumnData import org.thoughtcrime.securesms.database.model.databaseprotos.ExpiringProfileKeyCredentialColumnData
import org.thoughtcrime.securesms.database.model.databaseprotos.RecipientExtras import org.thoughtcrime.securesms.database.model.databaseprotos.RecipientExtras
import org.thoughtcrime.securesms.database.model.databaseprotos.Wallpaper import org.thoughtcrime.securesms.database.model.databaseprotos.Wallpaper
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
@ -154,7 +154,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
private const val SYSTEM_CONTACT_URI = "system_contact_uri" private const val SYSTEM_CONTACT_URI = "system_contact_uri"
private const val SYSTEM_INFO_PENDING = "system_info_pending" private const val SYSTEM_INFO_PENDING = "system_info_pending"
private const val PROFILE_KEY = "profile_key" private const val PROFILE_KEY = "profile_key"
private const val PROFILE_KEY_CREDENTIAL = "profile_key_credential" const val EXPIRING_PROFILE_KEY_CREDENTIAL = "profile_key_credential"
private const val SIGNAL_PROFILE_AVATAR = "signal_profile_avatar" private const val SIGNAL_PROFILE_AVATAR = "signal_profile_avatar"
const val PROFILE_SHARING = "profile_sharing" const val PROFILE_SHARING = "profile_sharing"
private const val LAST_PROFILE_FETCH = "last_profile_fetch" private const val LAST_PROFILE_FETCH = "last_profile_fetch"
@ -214,7 +214,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
$SYSTEM_CONTACT_URI TEXT DEFAULT NULL, $SYSTEM_CONTACT_URI TEXT DEFAULT NULL,
$SYSTEM_INFO_PENDING INTEGER DEFAULT 0, $SYSTEM_INFO_PENDING INTEGER DEFAULT 0,
$PROFILE_KEY TEXT DEFAULT NULL, $PROFILE_KEY TEXT DEFAULT NULL,
$PROFILE_KEY_CREDENTIAL TEXT DEFAULT NULL, $EXPIRING_PROFILE_KEY_CREDENTIAL TEXT DEFAULT NULL,
$PROFILE_GIVEN_NAME TEXT DEFAULT NULL, $PROFILE_GIVEN_NAME TEXT DEFAULT NULL,
$PROFILE_FAMILY_NAME TEXT DEFAULT NULL, $PROFILE_FAMILY_NAME TEXT DEFAULT NULL,
$PROFILE_JOINED_NAME TEXT DEFAULT NULL, $PROFILE_JOINED_NAME TEXT DEFAULT NULL,
@ -269,7 +269,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
MESSAGE_EXPIRATION_TIME, MESSAGE_EXPIRATION_TIME,
REGISTERED, REGISTERED,
PROFILE_KEY, PROFILE_KEY,
PROFILE_KEY_CREDENTIAL, EXPIRING_PROFILE_KEY_CREDENTIAL,
SYSTEM_JOINED_NAME, SYSTEM_JOINED_NAME,
SYSTEM_GIVEN_NAME, SYSTEM_GIVEN_NAME,
SYSTEM_FAMILY_NAME, SYSTEM_FAMILY_NAME,
@ -925,7 +925,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
val recipientId = getByStorageKeyOrThrow(update.new.id.raw) val recipientId = getByStorageKeyOrThrow(update.new.id.raw)
if (StorageSyncHelper.profileKeyChanged(update)) { if (StorageSyncHelper.profileKeyChanged(update)) {
val clearValues = ContentValues(1).apply { val clearValues = ContentValues(1).apply {
putNull(PROFILE_KEY_CREDENTIAL) putNull(EXPIRING_PROFILE_KEY_CREDENTIAL)
} }
db.update(TABLE_NAME, clearValues, ID_WHERE, SqlUtil.buildArgs(recipientId)) db.update(TABLE_NAME, clearValues, ID_WHERE, SqlUtil.buildArgs(recipientId))
} }
@ -986,7 +986,6 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
Log.i(TAG, "Creating restore placeholder for $groupId") Log.i(TAG, "Creating restore placeholder for $groupId")
groups.create( groups.create(
null,
masterKey, masterKey,
DecryptedGroup.newBuilder() DecryptedGroup.newBuilder()
.setRevision(GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION) .setRevision(GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION)
@ -1560,7 +1559,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
} }
val valuesToSet = ContentValues(3).apply { val valuesToSet = ContentValues(3).apply {
put(PROFILE_KEY, encodedProfileKey) put(PROFILE_KEY, encodedProfileKey)
putNull(PROFILE_KEY_CREDENTIAL) putNull(EXPIRING_PROFILE_KEY_CREDENTIAL)
put(UNIDENTIFIED_ACCESS_MODE, UnidentifiedAccessMode.UNKNOWN.mode) put(UNIDENTIFIED_ACCESS_MODE, UnidentifiedAccessMode.UNKNOWN.mode)
} }
@ -1592,7 +1591,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
val args = arrayOf(id.serialize()) val args = arrayOf(id.serialize())
val valuesToSet = ContentValues(3).apply { val valuesToSet = ContentValues(3).apply {
put(PROFILE_KEY, Base64.encodeBytes(profileKey.serialize())) put(PROFILE_KEY, Base64.encodeBytes(profileKey.serialize()))
putNull(PROFILE_KEY_CREDENTIAL) putNull(EXPIRING_PROFILE_KEY_CREDENTIAL)
put(UNIDENTIFIED_ACCESS_MODE, UnidentifiedAccessMode.UNKNOWN.mode) put(UNIDENTIFIED_ACCESS_MODE, UnidentifiedAccessMode.UNKNOWN.mode)
} }
@ -1611,16 +1610,16 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
fun setProfileKeyCredential( fun setProfileKeyCredential(
id: RecipientId, id: RecipientId,
profileKey: ProfileKey, profileKey: ProfileKey,
profileKeyCredential: ProfileKeyCredential expiringProfileKeyCredential: ExpiringProfileKeyCredential
): Boolean { ): Boolean {
val selection = "$ID = ? AND $PROFILE_KEY = ?" val selection = "$ID = ? AND $PROFILE_KEY = ?"
val args = arrayOf(id.serialize(), Base64.encodeBytes(profileKey.serialize())) val args = arrayOf(id.serialize(), Base64.encodeBytes(profileKey.serialize()))
val columnData = ProfileKeyCredentialColumnData.newBuilder() val columnData = ExpiringProfileKeyCredentialColumnData.newBuilder()
.setProfileKey(ByteString.copyFrom(profileKey.serialize())) .setProfileKey(ByteString.copyFrom(profileKey.serialize()))
.setProfileKeyCredential(ByteString.copyFrom(profileKeyCredential.serialize())) .setExpiringProfileKeyCredential(ByteString.copyFrom(expiringProfileKeyCredential.serialize()))
.build() .build()
val values = ContentValues(1).apply { val values = ContentValues(1).apply {
put(PROFILE_KEY_CREDENTIAL, Base64.encodeBytes(columnData.toByteArray())) put(EXPIRING_PROFILE_KEY_CREDENTIAL, Base64.encodeBytes(columnData.toByteArray()))
} }
val updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, values) val updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, values)
@ -1634,11 +1633,8 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
fun clearProfileKeyCredential(id: RecipientId) { fun clearProfileKeyCredential(id: RecipientId) {
val values = ContentValues(1) val values = ContentValues(1)
values.putNull(PROFILE_KEY_CREDENTIAL) values.putNull(EXPIRING_PROFILE_KEY_CREDENTIAL)
values.putNull(PROFILE_KEY)
values.put(PROFILE_SHARING, 0)
if (update(id, values)) { if (update(id, values)) {
rotateStorageId(id)
ApplicationDependencies.getDatabaseObserver().notifyRecipientChanged(id) ApplicationDependencies.getDatabaseObserver().notifyRecipientChanged(id)
} }
} }
@ -2101,11 +2097,11 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
db.beginTransaction() db.beginTransaction()
try { try {
for ((recipientId, aci) in registered) { for ((recipientId, serviceId) in registered) {
val values = ContentValues(2).apply { val values = ContentValues(2).apply {
put(REGISTERED, RegisteredState.REGISTERED.id) put(REGISTERED, RegisteredState.REGISTERED.id)
if (aci != null) { if (serviceId != null) {
put(SERVICE_ID, aci.toString().lowercase()) put(SERVICE_ID, serviceId.toString().lowercase())
} }
} }
@ -2117,7 +2113,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
} catch (e: SQLiteConstraintException) { } catch (e: SQLiteConstraintException) {
Log.w(TAG, "[bulkUpdateRegisteredStatus] Hit a conflict when trying to update $recipientId. Possibly merging.") Log.w(TAG, "[bulkUpdateRegisteredStatus] Hit a conflict when trying to update $recipientId. Possibly merging.")
val e164 = getRecord(recipientId).e164 val e164 = getRecord(recipientId).e164
val newId = getAndPossiblyMerge(aci, e164) val newId = getAndPossiblyMerge(serviceId, e164)
Log.w(TAG, "[bulkUpdateRegisteredStatus] Merged into $newId") Log.w(TAG, "[bulkUpdateRegisteredStatus] Merged into $newId")
} }
} }
@ -2173,7 +2169,8 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
} }
/** /**
* Processes CDSv2 results, merging recipients as necessary. * Processes CDSv2 results, merging recipients as necessary. Does not mark users as
* registered.
* *
* Important: This is under active development and is not suitable for actual use. * Important: This is under active development and is not suitable for actual use.
* *
@ -3489,6 +3486,9 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
} }
} }
.run() .run()
ApplicationDependencies.getRecipientCache().clear()
RecipientId.clearCache()
} }
/** /**
@ -3499,7 +3499,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
.update(TABLE_NAME) .update(TABLE_NAME)
.values( .values(
PROFILE_KEY to null, PROFILE_KEY to null,
PROFILE_KEY_CREDENTIAL to null, EXPIRING_PROFILE_KEY_CREDENTIAL to null,
PROFILE_GIVEN_NAME to null, PROFILE_GIVEN_NAME to null,
PROFILE_FAMILY_NAME to null, PROFILE_FAMILY_NAME to null,
PROFILE_JOINED_NAME to null, PROFILE_JOINED_NAME to null,
@ -3514,6 +3514,9 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
} }
} }
.run() .run()
ApplicationDependencies.getRecipientCache().clear()
RecipientId.clearCache()
} }
fun getRecord(context: Context, cursor: Cursor): RecipientRecord { fun getRecord(context: Context, cursor: Cursor): RecipientRecord {
@ -3522,9 +3525,9 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
fun getRecord(context: Context, cursor: Cursor, idColumnName: String): RecipientRecord { fun getRecord(context: Context, cursor: Cursor, idColumnName: String): RecipientRecord {
val profileKeyString = cursor.requireString(PROFILE_KEY) val profileKeyString = cursor.requireString(PROFILE_KEY)
val profileKeyCredentialString = cursor.requireString(PROFILE_KEY_CREDENTIAL) val expiringProfileKeyCredentialString = cursor.requireString(EXPIRING_PROFILE_KEY_CREDENTIAL)
var profileKey: ByteArray? = null var profileKey: ByteArray? = null
var profileKeyCredential: ProfileKeyCredential? = null var expiringProfileKeyCredential: ExpiringProfileKeyCredential? = null
if (profileKeyString != null) { if (profileKeyString != null) {
try { try {
@ -3533,12 +3536,12 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
Log.w(TAG, e) Log.w(TAG, e)
} }
if (profileKeyCredentialString != null) { if (expiringProfileKeyCredentialString != null) {
try { try {
val columnDataBytes = Base64.decode(profileKeyCredentialString) val columnDataBytes = Base64.decode(expiringProfileKeyCredentialString)
val columnData = ProfileKeyCredentialColumnData.parseFrom(columnDataBytes) val columnData = ExpiringProfileKeyCredentialColumnData.parseFrom(columnDataBytes)
if (Arrays.equals(columnData.profileKey.toByteArray(), profileKey)) { if (Arrays.equals(columnData.profileKey.toByteArray(), profileKey)) {
profileKeyCredential = ProfileKeyCredential(columnData.profileKeyCredential.toByteArray()) expiringProfileKeyCredential = ExpiringProfileKeyCredential(columnData.expiringProfileKeyCredential.toByteArray())
} else { } else {
Log.i(TAG, "Out of date profile key credential data ignored on read") Log.i(TAG, "Out of date profile key credential data ignored on read")
} }
@ -3598,7 +3601,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
expireMessages = cursor.requireInt(MESSAGE_EXPIRATION_TIME), expireMessages = cursor.requireInt(MESSAGE_EXPIRATION_TIME),
registered = RegisteredState.fromId(cursor.requireInt(REGISTERED)), registered = RegisteredState.fromId(cursor.requireInt(REGISTERED)),
profileKey = profileKey, profileKey = profileKey,
profileKeyCredential = profileKeyCredential, expiringProfileKeyCredential = expiringProfileKeyCredential,
systemProfileName = ProfileName.fromParts(cursor.requireString(SYSTEM_GIVEN_NAME), cursor.requireString(SYSTEM_FAMILY_NAME)), systemProfileName = ProfileName.fromParts(cursor.requireString(SYSTEM_GIVEN_NAME), cursor.requireString(SYSTEM_FAMILY_NAME)),
systemDisplayName = cursor.requireString(SYSTEM_JOINED_NAME), systemDisplayName = cursor.requireString(SYSTEM_JOINED_NAME),
systemContactPhotoUri = cursor.requireString(SYSTEM_PHOTO_URI), systemContactPhotoUri = cursor.requireString(SYSTEM_PHOTO_URI),
@ -3688,7 +3691,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
private fun updateProfileValuesForMerge(values: ContentValues, record: RecipientRecord) { private fun updateProfileValuesForMerge(values: ContentValues, record: RecipientRecord) {
values.apply { values.apply {
put(PROFILE_KEY, if (record.profileKey != null) Base64.encodeBytes(record.profileKey) else null) put(PROFILE_KEY, if (record.profileKey != null) Base64.encodeBytes(record.profileKey) else null)
putNull(PROFILE_KEY_CREDENTIAL) putNull(EXPIRING_PROFILE_KEY_CREDENTIAL)
put(SIGNAL_PROFILE_AVATAR, record.signalProfileAvatar) put(SIGNAL_PROFILE_AVATAR, record.signalProfileAvatar)
put(PROFILE_GIVEN_NAME, record.signalProfileName.givenName) put(PROFILE_GIVEN_NAME, record.signalProfileName.givenName)
put(PROFILE_FAMILY_NAME, record.signalProfileName.familyName) put(PROFILE_FAMILY_NAME, record.signalProfileName.familyName)

Wyświetl plik

@ -202,8 +202,9 @@ object SignalDatabaseMigrations {
private const val REMOTE_MEGAPHONE = 146 private const val REMOTE_MEGAPHONE = 146
private const val QUOTE_INDEX = 147 private const val QUOTE_INDEX = 147
private const val MY_STORY_PRIVACY_MODE = 148 private const val MY_STORY_PRIVACY_MODE = 148
private const val EXPIRING_PROFILE_CREDENTIALS = 149
const val DATABASE_VERSION = 148 const val DATABASE_VERSION = 149
@JvmStatic @JvmStatic
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
@ -2658,6 +2659,10 @@ object SignalDatabaseMigrations {
db.execSQL("CREATE UNIQUE INDEX distribution_list_member_list_id_recipient_id_privacy_mode_index ON distribution_list_member (list_id, recipient_id, privacy_mode)") db.execSQL("CREATE UNIQUE INDEX distribution_list_member_list_id_recipient_id_privacy_mode_index ON distribution_list_member (list_id, recipient_id, privacy_mode)")
} }
if (oldVersion < EXPIRING_PROFILE_CREDENTIALS) {
db.execSQL("UPDATE recipient SET profile_key_credential = NULL")
}
} }
@JvmStatic @JvmStatic

Wyświetl plik

@ -28,13 +28,13 @@ import org.signal.storageservice.protos.groups.local.DecryptedRequestingMember;
import org.signal.storageservice.protos.groups.local.EnabledState; import org.signal.storageservice.protos.groups.local.EnabledState;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.groups.GV2AccessLevelUtil; import org.thoughtcrime.securesms.groups.GV2AccessLevelUtil;
import org.thoughtcrime.securesms.keyvalue.ServiceIds;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.ExpirationUtil; import org.thoughtcrime.securesms.util.ExpirationUtil;
import org.thoughtcrime.securesms.util.SpanUtil; import org.thoughtcrime.securesms.util.SpanUtil;
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil; import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.ServiceIds;
import org.whispersystems.signalservice.api.util.UuidUtil; import org.whispersystems.signalservice.api.util.UuidUtil;
import java.util.Arrays; import java.util.Arrays;
@ -91,8 +91,8 @@ final class GroupsV2UpdateMessageProducer {
} }
if (DecryptedGroupUtil.findMemberByUuid(group.getMembersList(), selfIds.getAci().uuid()).isPresent() || if (DecryptedGroupUtil.findMemberByUuid(group.getMembersList(), selfIds.getAci().uuid()).isPresent() ||
(selfIds.getPni() != null && DecryptedGroupUtil.findMemberByUuid(group.getMembersList(), selfIds.getPni().uuid()).isPresent()) (selfIds.getPni() != null && DecryptedGroupUtil.findMemberByUuid(group.getMembersList(), selfIds.getPni().uuid()).isPresent()))
) { {
return updateDescription(context.getString(R.string.MessageRecord_you_joined_the_group), R.drawable.ic_update_group_add_16); return updateDescription(context.getString(R.string.MessageRecord_you_joined_the_group), R.drawable.ic_update_group_add_16);
} else { } else {
return updateDescription(context.getString(R.string.MessageRecord_group_updated), R.drawable.ic_update_group_16); return updateDescription(context.getString(R.string.MessageRecord_group_updated), R.drawable.ic_update_group_16);
@ -124,6 +124,7 @@ final class GroupsV2UpdateMessageProducer {
describeUnknownEditorRequestingMembersApprovals(change, updates); describeUnknownEditorRequestingMembersApprovals(change, updates);
describeUnknownEditorRequestingMembersDeletes(change, updates); describeUnknownEditorRequestingMembersDeletes(change, updates);
describeUnknownEditorAnnouncementGroupChange(change, updates); describeUnknownEditorAnnouncementGroupChange(change, updates);
describeUnknownEditorPromotePendingPniAci(change, updates);
describeUnknownEditorMemberRemovals(change, updates); describeUnknownEditorMemberRemovals(change, updates);
@ -149,6 +150,7 @@ final class GroupsV2UpdateMessageProducer {
describeRequestingMembersApprovals(change, updates); describeRequestingMembersApprovals(change, updates);
describeRequestingMembersDeletes(change, updates); describeRequestingMembersDeletes(change, updates);
describeAnnouncementGroupChange(change, updates); describeAnnouncementGroupChange(change, updates);
describePromotePendingPniAci(change, updates);
describeMemberRemovals(change, updates); describeMemberRemovals(change, updates);
@ -304,8 +306,8 @@ final class GroupsV2UpdateMessageProducer {
} }
private void describeInvitations(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) { private void describeInvitations(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
boolean editorIsYou = selfIds.matches(change.getEditor()); boolean editorIsYou = selfIds.matches(change.getEditor());
int notYouInviteCount = 0; int notYouInviteCount = 0;
for (DecryptedPendingMember invitee : change.getNewPendingMembersList()) { for (DecryptedPendingMember invitee : change.getNewPendingMembersList()) {
boolean newMemberIsYou = selfIds.matches(invitee.getUuid()); boolean newMemberIsYou = selfIds.matches(invitee.getUuid());
@ -351,8 +353,8 @@ final class GroupsV2UpdateMessageProducer {
} }
private void describeRevokedInvitations(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) { private void describeRevokedInvitations(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
boolean editorIsYou = selfIds.matches(change.getEditor()); boolean editorIsYou = selfIds.matches(change.getEditor());
int notDeclineCount = 0; int notDeclineCount = 0;
for (DecryptedPendingMemberRemoval invitee : change.getDeletePendingMembersList()) { for (DecryptedPendingMemberRemoval invitee : change.getDeletePendingMembersList()) {
boolean decline = invitee.getUuid().equals(change.getEditor()); boolean decline = invitee.getUuid().equals(change.getEditor());
@ -400,8 +402,8 @@ final class GroupsV2UpdateMessageProducer {
boolean editorIsYou = selfIds.matches(change.getEditor()); boolean editorIsYou = selfIds.matches(change.getEditor());
for (DecryptedMember newMember : change.getPromotePendingMembersList()) { for (DecryptedMember newMember : change.getPromotePendingMembersList()) {
ByteString uuid = newMember.getUuid(); ByteString uuid = newMember.getUuid();
boolean newMemberIsYou = selfIds.matches(uuid); boolean newMemberIsYou = selfIds.matches(uuid);
if (editorIsYou) { if (editorIsYou) {
if (newMemberIsYou) { if (newMemberIsYou) {
@ -425,8 +427,8 @@ final class GroupsV2UpdateMessageProducer {
private void describeUnknownEditorPromotePending(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) { private void describeUnknownEditorPromotePending(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
for (DecryptedMember newMember : change.getPromotePendingMembersList()) { for (DecryptedMember newMember : change.getPromotePendingMembersList()) {
ByteString uuid = newMember.getUuid(); ByteString uuid = newMember.getUuid();
boolean newMemberIsYou = selfIds.matches(uuid); boolean newMemberIsYou = selfIds.matches(uuid);
if (newMemberIsYou) { if (newMemberIsYou) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_joined_the_group), R.drawable.ic_update_group_add_16)); updates.add(updateDescription(context.getString(R.string.MessageRecord_you_joined_the_group), R.drawable.ic_update_group_add_16));
@ -679,7 +681,7 @@ final class GroupsV2UpdateMessageProducer {
if (requestingMemberIsYou) { if (requestingMemberIsYou) {
updates.add(updateDescription(R.string.MessageRecord_s_approved_your_request_to_join_the_group, change.getEditor(), R.drawable.ic_update_group_accept_16)); updates.add(updateDescription(R.string.MessageRecord_s_approved_your_request_to_join_the_group, change.getEditor(), R.drawable.ic_update_group_accept_16));
} else { } else {
boolean editorIsYou = selfIds.matches(change.getEditor()); boolean editorIsYou = selfIds.matches(change.getEditor());
if (editorIsYou) { if (editorIsYou) {
updates.add(updateDescription(R.string.MessageRecord_you_approved_a_request_to_join_the_group_from_s, requestingMember.getUuid(), R.drawable.ic_update_group_accept_16)); updates.add(updateDescription(R.string.MessageRecord_you_approved_a_request_to_join_the_group_from_s, requestingMember.getUuid(), R.drawable.ic_update_group_accept_16));
@ -770,6 +772,46 @@ final class GroupsV2UpdateMessageProducer {
} }
} }
private void describePromotePendingPniAci(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
boolean editorIsYou = selfIds.matches(change.getEditor());
for (DecryptedMember newMember : change.getPromotePendingPniAciMembersList()) {
ByteString uuid = newMember.getUuid();
boolean newMemberIsYou = selfIds.matches(uuid);
if (editorIsYou) {
if (newMemberIsYou) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_accepted_invite), R.drawable.ic_update_group_accept_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_you_added_invited_member_s, uuid, R.drawable.ic_update_group_add_16));
}
} else {
if (newMemberIsYou) {
updates.add(updateDescription(R.string.MessageRecord_s_added_you, change.getEditor(), R.drawable.ic_update_group_add_16));
} else {
if (uuid.equals(change.getEditor())) {
updates.add(updateDescription(R.string.MessageRecord_s_accepted_invite, uuid, R.drawable.ic_update_group_accept_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_s_added_invited_member_s, change.getEditor(), uuid, R.drawable.ic_update_group_add_16));
}
}
}
}
}
private void describeUnknownEditorPromotePendingPniAci(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
for (DecryptedMember newMember : change.getPromotePendingPniAciMembersList()) {
ByteString uuid = newMember.getUuid();
boolean newMemberIsYou = selfIds.matches(uuid);
if (newMemberIsYou) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_joined_the_group), R.drawable.ic_update_group_add_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_s_joined_the_group, uuid, R.drawable.ic_update_group_add_16));
}
}
}
private static UpdateDescription updateDescription(@NonNull String string, @DrawableRes int iconResource) { private static UpdateDescription updateDescription(@NonNull String string, @DrawableRes int iconResource) {
return UpdateDescription.staticDescription(string, iconResource); return UpdateDescription.staticDescription(string, iconResource);
} }
@ -868,8 +910,8 @@ final class GroupsV2UpdateMessageProducer {
@VisibleForTesting @VisibleForTesting
static @NonNull Spannable makeRecipientsClickable(@NonNull Context context, @NonNull String template, @NonNull List<RecipientId> recipientIds, @Nullable Consumer<RecipientId> clickHandler) { static @NonNull Spannable makeRecipientsClickable(@NonNull Context context, @NonNull String template, @NonNull List<RecipientId> recipientIds, @Nullable Consumer<RecipientId> clickHandler) {
SpannableStringBuilder builder = new SpannableStringBuilder(); SpannableStringBuilder builder = new SpannableStringBuilder();
int startIndex = 0; int startIndex = 0;
Map<String, RecipientId> idByPlaceholder = new HashMap<>(); Map<String, RecipientId> idByPlaceholder = new HashMap<>();
for (RecipientId id : recipientIds) { for (RecipientId id : recipientIds) {

Wyświetl plik

@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.database.model
import android.net.Uri import android.net.Uri
import org.signal.libsignal.zkgroup.groups.GroupMasterKey import org.signal.libsignal.zkgroup.groups.GroupMasterKey
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredential import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential
import org.thoughtcrime.securesms.badges.models.Badge import org.thoughtcrime.securesms.badges.models.Badge
import org.thoughtcrime.securesms.conversation.colors.AvatarColor import org.thoughtcrime.securesms.conversation.colors.AvatarColor
import org.thoughtcrime.securesms.conversation.colors.ChatColors import org.thoughtcrime.securesms.conversation.colors.ChatColors
@ -45,7 +45,7 @@ data class RecipientRecord(
val expireMessages: Int, val expireMessages: Int,
val registered: RegisteredState, val registered: RegisteredState,
val profileKey: ByteArray?, val profileKey: ByteArray?,
val profileKeyCredential: ProfileKeyCredential?, val expiringProfileKeyCredential: ExpiringProfileKeyCredential?,
val systemProfileName: ProfileName, val systemProfileName: ProfileName,
val systemDisplayName: String?, val systemDisplayName: String?,
val systemContactPhotoUri: String?, val systemContactPhotoUri: String?,

Wyświetl plik

@ -169,10 +169,9 @@ public class ApplicationDependencies {
if (groupsV2Authorization == null) { if (groupsV2Authorization == null) {
synchronized (LOCK) { synchronized (LOCK) {
if (groupsV2Authorization == null) { if (groupsV2Authorization == null) {
GroupsV2Authorization.ValueCache aciAuthCache = new GroupsV2AuthorizationMemoryValueCache(SignalStore.groupsV2AciAuthorizationCache()); GroupsV2Authorization.ValueCache authCache = new GroupsV2AuthorizationMemoryValueCache(SignalStore.groupsV2AciAuthorizationCache());
GroupsV2Authorization.ValueCache pniAuthCache = new GroupsV2AuthorizationMemoryValueCache(SignalStore.groupsV2PniAuthorizationCache());
groupsV2Authorization = new GroupsV2Authorization(getSignalServiceAccountManager().getGroupsV2Api(), aciAuthCache, pniAuthCache); groupsV2Authorization = new GroupsV2Authorization(getSignalServiceAccountManager().getGroupsV2Api(), authCache);
} }
} }
} }

Wyświetl plik

@ -177,7 +177,6 @@ public final class GroupManager {
*/ */
@WorkerThread @WorkerThread
public static void updateGroupFromServer(@NonNull Context context, public static void updateGroupFromServer(@NonNull Context context,
@NonNull ServiceId authServiceId,
@NonNull GroupMasterKey groupMasterKey, @NonNull GroupMasterKey groupMasterKey,
int revision, int revision,
long timestamp, long timestamp,
@ -185,18 +184,18 @@ public final class GroupManager {
throws GroupChangeBusyException, IOException, GroupNotAMemberException throws GroupChangeBusyException, IOException, GroupNotAMemberException
{ {
try (GroupManagerV2.GroupUpdater updater = new GroupManagerV2(context).updater(groupMasterKey)) { try (GroupManagerV2.GroupUpdater updater = new GroupManagerV2(context).updater(groupMasterKey)) {
updater.updateLocalToServerRevision(authServiceId, revision, timestamp, signedGroupChange); updater.updateLocalToServerRevision(revision, timestamp, signedGroupChange);
} }
} }
@WorkerThread @WorkerThread
public static V2GroupServerStatus v2GroupStatus(@NonNull Context context, public static V2GroupServerStatus v2GroupStatus(@NonNull Context context,
@NonNull ServiceId authServiceserviceId, @NonNull ServiceId authServiceId,
@NonNull GroupMasterKey groupMasterKey) @NonNull GroupMasterKey groupMasterKey)
throws IOException throws IOException
{ {
try { try {
new GroupManagerV2(context).groupServerQuery(authServiceserviceId, groupMasterKey); new GroupManagerV2(context).groupServerQuery(authServiceId, groupMasterKey);
return V2GroupServerStatus.FULL_OR_PENDING_MEMBER; return V2GroupServerStatus.FULL_OR_PENDING_MEMBER;
} catch (GroupNotAMemberException e) { } catch (GroupNotAMemberException e) {
return V2GroupServerStatus.NOT_A_MEMBER; return V2GroupServerStatus.NOT_A_MEMBER;

Wyświetl plik

@ -19,8 +19,8 @@ import org.signal.libsignal.zkgroup.groups.ClientZkGroupCipher;
import org.signal.libsignal.zkgroup.groups.GroupMasterKey; import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
import org.signal.libsignal.zkgroup.groups.GroupSecretParams; import org.signal.libsignal.zkgroup.groups.GroupSecretParams;
import org.signal.libsignal.zkgroup.groups.UuidCiphertext; import org.signal.libsignal.zkgroup.groups.UuidCiphertext;
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredential;
import org.signal.storageservice.protos.groups.AccessControl; import org.signal.storageservice.protos.groups.AccessControl;
import org.signal.storageservice.protos.groups.GroupChange; import org.signal.storageservice.protos.groups.GroupChange;
import org.signal.storageservice.protos.groups.GroupExternalCredential; import org.signal.storageservice.protos.groups.GroupExternalCredential;
@ -51,6 +51,8 @@ import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.ProfileUtil;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil; import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
import org.whispersystems.signalservice.api.groupsv2.GroupCandidate; import org.whispersystems.signalservice.api.groupsv2.GroupCandidate;
@ -64,6 +66,7 @@ import org.whispersystems.signalservice.api.groupsv2.NotAbleToApplyGroupV2Change
import org.whispersystems.signalservice.api.push.ACI; import org.whispersystems.signalservice.api.push.ACI;
import org.whispersystems.signalservice.api.push.PNI; import org.whispersystems.signalservice.api.push.PNI;
import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.ServiceIds;
import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException; import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
import org.whispersystems.signalservice.api.push.exceptions.ConflictException; import org.whispersystems.signalservice.api.push.exceptions.ConflictException;
import org.whispersystems.signalservice.api.util.UuidUtil; import org.whispersystems.signalservice.api.util.UuidUtil;
@ -97,6 +100,7 @@ final class GroupManagerV2 {
private final GroupsV2Operations groupsV2Operations; private final GroupsV2Operations groupsV2Operations;
private final GroupsV2Authorization authorization; private final GroupsV2Authorization authorization;
private final GroupsV2StateProcessor groupsV2StateProcessor; private final GroupsV2StateProcessor groupsV2StateProcessor;
private final ServiceIds serviceIds;
private final ACI selfAci; private final ACI selfAci;
private final PNI selfPni; private final PNI selfPni;
private final GroupCandidateHelper groupCandidateHelper; private final GroupCandidateHelper groupCandidateHelper;
@ -109,9 +113,8 @@ final class GroupManagerV2 {
ApplicationDependencies.getGroupsV2Operations(), ApplicationDependencies.getGroupsV2Operations(),
ApplicationDependencies.getGroupsV2Authorization(), ApplicationDependencies.getGroupsV2Authorization(),
ApplicationDependencies.getGroupsV2StateProcessor(), ApplicationDependencies.getGroupsV2StateProcessor(),
SignalStore.account().requireAci(), SignalStore.account().getServiceIds(),
SignalStore.account().requirePni(), new GroupCandidateHelper(),
new GroupCandidateHelper(context),
new SendGroupUpdateHelper(context)); new SendGroupUpdateHelper(context));
} }
@ -121,8 +124,7 @@ final class GroupManagerV2 {
GroupsV2Operations groupsV2Operations, GroupsV2Operations groupsV2Operations,
GroupsV2Authorization authorization, GroupsV2Authorization authorization,
GroupsV2StateProcessor groupsV2StateProcessor, GroupsV2StateProcessor groupsV2StateProcessor,
ACI selfAci, ServiceIds serviceIds,
PNI selfPni,
GroupCandidateHelper groupCandidateHelper, GroupCandidateHelper groupCandidateHelper,
SendGroupUpdateHelper sendGroupUpdateHelper) SendGroupUpdateHelper sendGroupUpdateHelper)
{ {
@ -132,8 +134,9 @@ final class GroupManagerV2 {
this.groupsV2Operations = groupsV2Operations; this.groupsV2Operations = groupsV2Operations;
this.authorization = authorization; this.authorization = authorization;
this.groupsV2StateProcessor = groupsV2StateProcessor; this.groupsV2StateProcessor = groupsV2StateProcessor;
this.selfAci = selfAci; this.serviceIds = serviceIds;
this.selfPni = selfPni; this.selfAci = serviceIds.getAci();
this.selfPni = serviceIds.requirePni();
this.groupCandidateHelper = groupCandidateHelper; this.groupCandidateHelper = groupCandidateHelper;
this.sendGroupUpdateHelper = sendGroupUpdateHelper; this.sendGroupUpdateHelper = sendGroupUpdateHelper;
} }
@ -145,7 +148,7 @@ final class GroupManagerV2 {
return groupsV2Api.getGroupJoinInfo(groupSecretParams, return groupsV2Api.getGroupJoinInfo(groupSecretParams,
Optional.ofNullable(password).map(GroupLinkPassword::serialize), Optional.ofNullable(password).map(GroupLinkPassword::serialize),
authorization.getAuthorizationForToday(selfAci, groupSecretParams)); authorization.getAuthorizationForToday(serviceIds, groupSecretParams));
} }
@WorkerThread @WorkerThread
@ -159,7 +162,7 @@ final class GroupManagerV2 {
GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey); GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey);
return groupsV2Api.getGroupExternalCredential(authorization.getAuthorizationForToday(selfAci, groupSecretParams)); return groupsV2Api.getGroupExternalCredential(authorization.getAuthorizationForToday(serviceIds, groupSecretParams));
} }
@WorkerThread @WorkerThread
@ -212,7 +215,7 @@ final class GroupManagerV2 {
void groupServerQuery(@NonNull ServiceId authServiceId, @NonNull GroupMasterKey groupMasterKey) void groupServerQuery(@NonNull ServiceId authServiceId, @NonNull GroupMasterKey groupMasterKey)
throws GroupNotAMemberException, IOException, GroupDoesNotExistException throws GroupNotAMemberException, IOException, GroupDoesNotExistException
{ {
new GroupsV2StateProcessor(context).forGroup(authServiceId, groupMasterKey) new GroupsV2StateProcessor(context).forGroup(serviceIds, groupMasterKey)
.getCurrentGroupStateFromServer(); .getCurrentGroupStateFromServer();
} }
@ -220,7 +223,7 @@ final class GroupManagerV2 {
@NonNull DecryptedGroup addedGroupVersion(@NonNull ServiceId authServiceId, @NonNull GroupMasterKey groupMasterKey) @NonNull DecryptedGroup addedGroupVersion(@NonNull ServiceId authServiceId, @NonNull GroupMasterKey groupMasterKey)
throws GroupNotAMemberException, IOException, GroupDoesNotExistException throws GroupNotAMemberException, IOException, GroupDoesNotExistException
{ {
GroupsV2StateProcessor.StateProcessorForGroup stateProcessorForGroup = new GroupsV2StateProcessor(context).forGroup(authServiceId, groupMasterKey); GroupsV2StateProcessor.StateProcessorForGroup stateProcessorForGroup = new GroupsV2StateProcessor(context).forGroup(serviceIds, groupMasterKey);
DecryptedGroup latest = stateProcessorForGroup.getCurrentGroupStateFromServer(); DecryptedGroup latest = stateProcessorForGroup.getCurrentGroupStateFromServer();
if (latest.getRevision() == 0) { if (latest.getRevision() == 0) {
@ -291,7 +294,7 @@ final class GroupManagerV2 {
} }
GroupMasterKey masterKey = groupSecretParams.getMasterKey(); GroupMasterKey masterKey = groupSecretParams.getMasterKey();
GroupId.V2 groupId = groupDatabase.create(authServiceId, masterKey, decryptedGroup); GroupId.V2 groupId = groupDatabase.create(masterKey, decryptedGroup);
RecipientId groupRecipientId = SignalDatabase.recipients().getOrInsertFromGroupId(groupId); RecipientId groupRecipientId = SignalDatabase.recipients().getOrInsertFromGroupId(groupId);
Recipient groupRecipient = Recipient.resolved(groupRecipientId); Recipient groupRecipient = Recipient.resolved(groupRecipientId);
@ -344,7 +347,7 @@ final class GroupManagerV2 {
Set<GroupCandidate> groupCandidates = groupCandidateHelper.recipientIdsToCandidates(new HashSet<>(newMembers)); Set<GroupCandidate> groupCandidates = groupCandidateHelper.recipientIdsToCandidates(new HashSet<>(newMembers));
if (SignalStore.internalValues().gv2ForceInvites()) { if (SignalStore.internalValues().gv2ForceInvites()) {
groupCandidates = GroupCandidate.withoutProfileKeyCredentials(groupCandidates); groupCandidates = GroupCandidate.withoutExpiringProfileKeyCredentials(groupCandidates);
} }
return commitChangeWithConflictResolution(selfAci, groupOperations.createModifyGroupMembershipChange(groupCandidates, bannedMembers, selfAci.uuid())); return commitChangeWithConflictResolution(selfAci, groupOperations.createModifyGroupMembershipChange(groupCandidates, bannedMembers, selfAci.uuid()));
@ -391,7 +394,7 @@ final class GroupManagerV2 {
} }
if (avatarChanged) { if (avatarChanged) {
String cdnKey = avatarBytes != null ? groupsV2Api.uploadAvatar(avatarBytes, groupSecretParams, authorization.getAuthorizationForToday(selfAci, groupSecretParams)) String cdnKey = avatarBytes != null ? groupsV2Api.uploadAvatar(avatarBytes, groupSecretParams, authorization.getAuthorizationForToday(serviceIds, groupSecretParams))
: ""; : "";
change.setModifyAvatar(GroupChange.Actions.ModifyAvatarAction.newBuilder() change.setModifyAvatar(GroupChange.Actions.ModifyAvatarAction.newBuilder()
.setAvatar(cdnKey)); .setAvatar(cdnKey));
@ -524,13 +527,13 @@ final class GroupManagerV2 {
GroupCandidate groupCandidate = groupCandidateHelper.recipientIdToCandidate(Recipient.self().getId()); GroupCandidate groupCandidate = groupCandidateHelper.recipientIdToCandidate(Recipient.self().getId());
if (!groupCandidate.hasProfileKeyCredential()) { if (!groupCandidate.hasValidProfileKeyCredential()) {
Log.w(TAG, "No credential available, repairing"); Log.w(TAG, "No credential available, repairing");
ApplicationDependencies.getJobManager().add(new ProfileUploadJob()); ApplicationDependencies.getJobManager().add(new ProfileUploadJob());
return null; return null;
} }
return commitChangeWithConflictResolution(selfAci, groupOperations.createUpdateProfileKeyCredentialChange(groupCandidate.requireProfileKeyCredential())); return commitChangeWithConflictResolution(selfAci, groupOperations.createUpdateProfileKeyCredentialChange(groupCandidate.requireExpiringProfileKeyCredential()));
} }
@WorkerThread @WorkerThread
@ -545,14 +548,23 @@ final class GroupManagerV2 {
return null; return null;
} }
Optional<DecryptedPendingMember> aciInPending = DecryptedGroupUtil.findPendingByUuid(group.getPendingMembersList(), selfAci.uuid());
Optional<DecryptedPendingMember> pniInPending = DecryptedGroupUtil.findPendingByUuid(group.getPendingMembersList(), selfPni.uuid());
GroupCandidate groupCandidate = groupCandidateHelper.recipientIdToCandidate(Recipient.self().getId()); GroupCandidate groupCandidate = groupCandidateHelper.recipientIdToCandidate(Recipient.self().getId());
if (!groupCandidate.hasProfileKeyCredential()) { if (!groupCandidate.hasValidProfileKeyCredential()) {
Log.w(TAG, "No credential available"); Log.w(TAG, "No credential available");
return null; return null;
} }
return commitChangeWithConflictResolution(selfAci, groupOperations.createAcceptInviteChange(groupCandidate.requireProfileKeyCredential())); if (aciInPending.isPresent()) {
return commitChangeWithConflictResolution(selfAci, groupOperations.createAcceptInviteChange(groupCandidate.requireExpiringProfileKeyCredential()));
} else if (pniInPending.isPresent() && FeatureFlags.phoneNumberPrivacy()) {
return commitChangeWithConflictResolution(selfPni, groupOperations.createAcceptPniInviteChange(groupCandidate.requireExpiringProfileKeyCredential()));
}
throw new GroupChangeFailedException("Unable to accept invite when not in pending list");
} }
public GroupManager.GroupActionResult ban(UUID uuid) public GroupManager.GroupActionResult ban(UUID uuid)
@ -629,13 +641,19 @@ final class GroupManagerV2 {
private @NonNull GroupManager.GroupActionResult commitChangeWithConflictResolution(@NonNull ServiceId authServiceId, @NonNull GroupChange.Actions.Builder change, boolean allowWhenBlocked, boolean sendToMembers) private @NonNull GroupManager.GroupActionResult commitChangeWithConflictResolution(@NonNull ServiceId authServiceId, @NonNull GroupChange.Actions.Builder change, boolean allowWhenBlocked, boolean sendToMembers)
throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException
{ {
boolean refetchedAddMemberCredentials = false;
change.setSourceUuid(UuidUtil.toByteString(authServiceId.uuid())); change.setSourceUuid(UuidUtil.toByteString(authServiceId.uuid()));
for (int attempt = 0; attempt < 5; attempt++) { for (int attempt = 0; attempt < 5; attempt++) {
try { try {
return commitChange(authServiceId, change, allowWhenBlocked, sendToMembers); return commitChange(authServiceId, change, allowWhenBlocked, sendToMembers);
} catch (GroupPatchNotAcceptedException e) { } catch (GroupPatchNotAcceptedException e) {
throw new GroupChangeFailedException(e); if (change.getAddMembersCount() > 0 && !refetchedAddMemberCredentials) {
refetchedAddMemberCredentials = true;
change = refetchAddMemberCredentials(change);
} else {
throw new GroupChangeFailedException(e);
}
} catch (ConflictException e) { } catch (ConflictException e) {
Log.w(TAG, "Invalid group patch or conflict", e); Log.w(TAG, "Invalid group patch or conflict", e);
@ -657,7 +675,7 @@ final class GroupManagerV2 {
private GroupChange.Actions.Builder resolveConflict(@NonNull ServiceId authServiceId, @NonNull GroupChange.Actions.Builder change) private GroupChange.Actions.Builder resolveConflict(@NonNull ServiceId authServiceId, @NonNull GroupChange.Actions.Builder change)
throws IOException, GroupNotAMemberException, GroupChangeFailedException throws IOException, GroupNotAMemberException, GroupChangeFailedException
{ {
GroupsV2StateProcessor.GroupUpdateResult groupUpdateResult = groupsV2StateProcessor.forGroup(authServiceId, groupMasterKey) GroupsV2StateProcessor.GroupUpdateResult groupUpdateResult = groupsV2StateProcessor.forGroup(serviceIds, groupMasterKey)
.updateLocalGroupToRevision(GroupsV2StateProcessor.LATEST, System.currentTimeMillis(), null); .updateLocalGroupToRevision(GroupsV2StateProcessor.LATEST, System.currentTimeMillis(), null);
if (groupUpdateResult.getLatestServer() == null) { if (groupUpdateResult.getLatestServer() == null) {
@ -685,6 +703,27 @@ final class GroupManagerV2 {
} }
} }
private GroupChange.Actions.Builder refetchAddMemberCredentials(@NonNull GroupChange.Actions.Builder change) {
try {
List<RecipientId> ids = groupOperations.decryptAddMembers(change.getAddMembersList())
.stream()
.map(RecipientId::from)
.collect(java.util.stream.Collectors.toList());
for (RecipientId id : ids) {
ProfileUtil.updateExpiringProfileKeyCredential(Recipient.resolved(id));
}
List<GroupCandidate> groupCandidates = groupCandidateHelper.recipientIdsToCandidatesList(ids);
return groupOperations.replaceAddMembers(change, groupCandidates);
} catch (InvalidInputException | VerificationFailedException | IOException e) {
Log.w(TAG, "Unable to refetch credentials for added members, failing change", e);
}
return change;
}
private GroupManager.GroupActionResult commitChange(@NonNull ServiceId authServiceId, @NonNull GroupChange.Actions.Builder change, boolean allowWhenBlocked, boolean sendToMembers) private GroupManager.GroupActionResult commitChange(@NonNull ServiceId authServiceId, @NonNull GroupChange.Actions.Builder change, boolean allowWhenBlocked, boolean sendToMembers)
throws GroupNotAMemberException, GroupChangeFailedException, IOException, GroupInsufficientRightsException throws GroupNotAMemberException, GroupChangeFailedException, IOException, GroupInsufficientRightsException
{ {
@ -726,7 +765,7 @@ final class GroupManagerV2 {
throws GroupNotAMemberException, GroupChangeFailedException, IOException, GroupInsufficientRightsException throws GroupNotAMemberException, GroupChangeFailedException, IOException, GroupInsufficientRightsException
{ {
try { try {
return groupsV2Api.patchGroup(change, authorization.getAuthorizationForToday(authServiceId, groupSecretParams), Optional.empty()); return groupsV2Api.patchGroup(change, authorization.getAuthorizationForToday(serviceIds, groupSecretParams), Optional.empty());
} catch (NotInGroupException e) { } catch (NotInGroupException e) {
Log.w(TAG, e); Log.w(TAG, e);
throw new GroupNotAMemberException(e); throw new GroupNotAMemberException(e);
@ -751,10 +790,10 @@ final class GroupManagerV2 {
} }
@WorkerThread @WorkerThread
void updateLocalToServerRevision(@NonNull ServiceId authServiceId, int revision, long timestamp, @Nullable byte[] signedGroupChange) void updateLocalToServerRevision(int revision, long timestamp, @Nullable byte[] signedGroupChange)
throws IOException, GroupNotAMemberException throws IOException, GroupNotAMemberException
{ {
new GroupsV2StateProcessor(context).forGroup(authServiceId, groupMasterKey) new GroupsV2StateProcessor(context).forGroup(serviceIds, groupMasterKey)
.updateLocalGroupToRevision(revision, timestamp, getDecryptedGroupChange(signedGroupChange)); .updateLocalGroupToRevision(revision, timestamp, getDecryptedGroupChange(signedGroupChange));
} }
@ -792,10 +831,10 @@ final class GroupManagerV2 {
if (SignalStore.internalValues().gv2ForceInvites()) { if (SignalStore.internalValues().gv2ForceInvites()) {
Log.w(TAG, "Forcing GV2 invites due to internal setting"); Log.w(TAG, "Forcing GV2 invites due to internal setting");
candidates = GroupCandidate.withoutProfileKeyCredentials(candidates); candidates = GroupCandidate.withoutExpiringProfileKeyCredentials(candidates);
} }
if (!self.hasProfileKeyCredential()) { if (!self.hasValidProfileKeyCredential()) {
Log.w(TAG, "Cannot create a V2 group as self does not have a versioned profile"); Log.w(TAG, "Cannot create a V2 group as self does not have a versioned profile");
throw new MembershipNotSuitableForV2Exception("Cannot create a V2 group as self does not have a versioned profile"); throw new MembershipNotSuitableForV2Exception("Cannot create a V2 group as self does not have a versioned profile");
} }
@ -809,9 +848,9 @@ final class GroupManagerV2 {
disappearingMessageTimerSeconds); disappearingMessageTimerSeconds);
try { try {
groupsV2Api.putNewGroup(newGroup, authorization.getAuthorizationForToday(selfAci, groupSecretParams)); groupsV2Api.putNewGroup(newGroup, authorization.getAuthorizationForToday(serviceIds, groupSecretParams));
DecryptedGroup decryptedGroup = groupsV2Api.getGroup(groupSecretParams, ApplicationDependencies.getGroupsV2Authorization().getAuthorizationForToday(selfAci, groupSecretParams)); DecryptedGroup decryptedGroup = groupsV2Api.getGroup(groupSecretParams, ApplicationDependencies.getGroupsV2Authorization().getAuthorizationForToday(serviceIds, groupSecretParams));
if (decryptedGroup == null) { if (decryptedGroup == null) {
throw new GroupChangeFailedException(); throw new GroupChangeFailedException();
} }
@ -907,7 +946,7 @@ final class GroupManagerV2 {
groupDatabase.update(groupId, updatedGroup); groupDatabase.update(groupId, updatedGroup);
} else { } else {
groupDatabase.create(selfAci, groupMasterKey, decryptedGroup); groupDatabase.create(groupMasterKey, decryptedGroup);
Log.i(TAG, "Created local group with placeholder"); Log.i(TAG, "Created local group with placeholder");
} }
@ -951,7 +990,7 @@ final class GroupManagerV2 {
throws GroupChangeFailedException, IOException throws GroupChangeFailedException, IOException
{ {
try { try {
new GroupsV2StateProcessor(context).forGroup(selfAci, groupMasterKey) new GroupsV2StateProcessor(context).forGroup(serviceIds, groupMasterKey)
.updateLocalGroupToRevision(decryptedChange.getRevision(), .updateLocalGroupToRevision(decryptedChange.getRevision(),
System.currentTimeMillis(), System.currentTimeMillis(),
decryptedChange); decryptedChange);
@ -1030,14 +1069,14 @@ final class GroupManagerV2 {
GroupCandidate self = groupCandidateHelper.recipientIdToCandidate(Recipient.self().getId()); GroupCandidate self = groupCandidateHelper.recipientIdToCandidate(Recipient.self().getId());
if (!self.hasProfileKeyCredential()) { if (!self.hasValidProfileKeyCredential()) {
throw new MembershipNotSuitableForV2Exception("No profile key credential for self"); throw new MembershipNotSuitableForV2Exception("No profile key credential for self");
} }
ProfileKeyCredential profileKeyCredential = self.requireProfileKeyCredential(); ExpiringProfileKeyCredential expiringProfileKeyCredential = self.requireExpiringProfileKeyCredential();
GroupChange.Actions.Builder change = requestToJoin ? groupOperations.createGroupJoinRequest(profileKeyCredential) GroupChange.Actions.Builder change = requestToJoin ? groupOperations.createGroupJoinRequest(expiringProfileKeyCredential)
: groupOperations.createGroupJoinDirect(profileKeyCredential); : groupOperations.createGroupJoinDirect(expiringProfileKeyCredential);
change.setSourceUuid(selfAci.toByteString()); change.setSourceUuid(selfAci.toByteString());
@ -1083,7 +1122,7 @@ final class GroupManagerV2 {
throws GroupChangeFailedException, IOException, GroupLinkNotActiveException throws GroupChangeFailedException, IOException, GroupLinkNotActiveException
{ {
try { try {
return groupsV2Api.patchGroup(change, authorization.getAuthorizationForToday(selfAci, groupSecretParams), Optional.ofNullable(password).map(GroupLinkPassword::serialize)); return groupsV2Api.patchGroup(change, authorization.getAuthorizationForToday(serviceIds, groupSecretParams), Optional.ofNullable(password).map(GroupLinkPassword::serialize));
} catch (NotInGroupException | VerificationFailedException e) { } catch (NotInGroupException | VerificationFailedException e) {
Log.w(TAG, e); Log.w(TAG, e);
throw new GroupChangeFailedException(e); throw new GroupChangeFailedException(e);
@ -1127,7 +1166,7 @@ final class GroupManagerV2 {
throws IOException, VerificationFailedException, InvalidGroupStateException throws IOException, VerificationFailedException, InvalidGroupStateException
{ {
try { try {
groupsV2Api.getGroup(groupSecretParams, authorization.getAuthorizationForToday(selfAci, groupSecretParams)); groupsV2Api.getGroup(groupSecretParams, authorization.getAuthorizationForToday(serviceIds, groupSecretParams));
return true; return true;
} catch (NotInGroupException ex) { } catch (NotInGroupException ex) {
return false; return false;

Wyświetl plik

@ -72,7 +72,7 @@ public final class GroupsV1MigrationUtil {
throw new InvalidMigrationStateException(); throw new InvalidMigrationStateException();
} }
switch (GroupManager.v2GroupStatus(context, SignalStore.account().getAci(), gv2MasterKey)) { switch (GroupManager.v2GroupStatus(context, SignalStore.account().requireAci(), gv2MasterKey)) {
case DOES_NOT_EXIST: case DOES_NOT_EXIST:
Log.i(TAG, "Group does not exist on the service."); Log.i(TAG, "Group does not exist on the service.");
@ -186,7 +186,7 @@ public final class GroupsV1MigrationUtil {
Log.i(TAG, "[Local] Applying all changes since V" + decryptedGroup.getRevision()); Log.i(TAG, "[Local] Applying all changes since V" + decryptedGroup.getRevision());
try { try {
GroupManager.updateGroupFromServer(context, SignalStore.account().requireAci(), gv1Id.deriveV2MigrationMasterKey(), LATEST, System.currentTimeMillis(), null); GroupManager.updateGroupFromServer(context, gv1Id.deriveV2MigrationMasterKey(), LATEST, System.currentTimeMillis(), null);
} catch (GroupChangeBusyException | GroupNotAMemberException e) { } catch (GroupChangeBusyException | GroupNotAMemberException e) {
Log.w(TAG, e); Log.w(TAG, e);
} }

Wyświetl plik

@ -4,69 +4,53 @@ import androidx.annotation.NonNull;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.signal.libsignal.zkgroup.VerificationFailedException; import org.signal.libsignal.zkgroup.VerificationFailedException;
import org.signal.libsignal.zkgroup.auth.AuthCredentialResponse; import org.signal.libsignal.zkgroup.auth.AuthCredentialWithPniResponse;
import org.signal.libsignal.zkgroup.groups.GroupSecretParams; import org.signal.libsignal.zkgroup.groups.GroupSecretParams;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api; import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api;
import org.whispersystems.signalservice.api.groupsv2.GroupsV2AuthorizationString; import org.whispersystems.signalservice.api.groupsv2.GroupsV2AuthorizationString;
import org.whispersystems.signalservice.api.groupsv2.NoCredentialForRedemptionTimeException; import org.whispersystems.signalservice.api.groupsv2.NoCredentialForRedemptionTimeException;
import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.ServiceIds;
import java.io.IOException; import java.io.IOException;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class GroupsV2Authorization { public class GroupsV2Authorization {
private static final String TAG = Log.tag(GroupsV2Authorization.class); private static final String TAG = Log.tag(GroupsV2Authorization.class);
private final ValueCache aciCache; private final ValueCache authCache;
private final ValueCache pniCache;
private final GroupsV2Api groupsV2Api; private final GroupsV2Api groupsV2Api;
public GroupsV2Authorization(@NonNull GroupsV2Api groupsV2Api, @NonNull ValueCache aciCache, @NonNull ValueCache pniCache) { public GroupsV2Authorization(@NonNull GroupsV2Api groupsV2Api, @NonNull ValueCache authCache) {
this.groupsV2Api = groupsV2Api; this.groupsV2Api = groupsV2Api;
this.aciCache = aciCache; this.authCache = authCache;
this.pniCache = pniCache;
} }
public GroupsV2AuthorizationString getAuthorizationForToday(@NonNull ServiceId authServiceId, public GroupsV2AuthorizationString getAuthorizationForToday(@NonNull ServiceIds serviceIds,
@NonNull GroupSecretParams groupSecretParams) @NonNull GroupSecretParams groupSecretParams)
throws IOException, VerificationFailedException throws IOException, VerificationFailedException
{ {
boolean isPni = Objects.equals(authServiceId, SignalStore.account().getPni()); final long today = currentDaySeconds();
ValueCache cache = isPni ? pniCache : aciCache;
return getAuthorizationForToday(authServiceId, cache, groupSecretParams, !isPni); Map<Long, AuthCredentialWithPniResponse> credentials = authCache.read();
}
private GroupsV2AuthorizationString getAuthorizationForToday(@NonNull ServiceId authServiceId,
@NonNull ValueCache cache,
@NonNull GroupSecretParams groupSecretParams,
boolean isAci)
throws IOException, VerificationFailedException
{
final int today = currentTimeDays();
Map<Integer, AuthCredentialResponse> credentials = cache.read();
try { try {
return getAuthorization(authServiceId, groupSecretParams, credentials, today); return getAuthorization(serviceIds, groupSecretParams, credentials, today);
} catch (NoCredentialForRedemptionTimeException e) { } catch (NoCredentialForRedemptionTimeException e) {
Log.i(TAG, "Auth out of date, will update auth and try again"); Log.i(TAG, "Auth out of date, will update auth and try again");
cache.clear(); authCache.clear();
} catch (VerificationFailedException e) { } catch (VerificationFailedException e) {
Log.w(TAG, "Verification failed, will update auth and try again", e); Log.w(TAG, "Verification failed, will update auth and try again", e);
cache.clear(); authCache.clear();
} }
Log.i(TAG, "Getting new auth credential responses"); Log.i(TAG, "Getting new auth credential responses");
credentials = groupsV2Api.getCredentials(today, isAci); credentials = groupsV2Api.getCredentials(today);
cache.write(credentials); authCache.write(credentials);
try { try {
return getAuthorization(authServiceId, groupSecretParams, credentials, today); return getAuthorization(serviceIds, groupSecretParams, credentials, today);
} catch (NoCredentialForRedemptionTimeException e) { } catch (NoCredentialForRedemptionTimeException e) {
Log.w(TAG, "The credentials returned did not include the day requested"); Log.w(TAG, "The credentials returned did not include the day requested");
throw new IOException("Failed to get credentials"); throw new IOException("Failed to get credentials");
@ -74,35 +58,34 @@ public class GroupsV2Authorization {
} }
public void clear() { public void clear() {
aciCache.clear(); authCache.clear();
pniCache.clear();
} }
private static int currentTimeDays() { private static long currentDaySeconds() {
return (int) TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis()); return TimeUnit.DAYS.toSeconds(TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis()));
} }
private GroupsV2AuthorizationString getAuthorization(ServiceId authServiceId, private GroupsV2AuthorizationString getAuthorization(ServiceIds serviceIds,
GroupSecretParams groupSecretParams, GroupSecretParams groupSecretParams,
Map<Integer, AuthCredentialResponse> credentials, Map<Long, AuthCredentialWithPniResponse> credentials,
int today) long todaySeconds)
throws NoCredentialForRedemptionTimeException, VerificationFailedException throws NoCredentialForRedemptionTimeException, VerificationFailedException
{ {
AuthCredentialResponse authCredentialResponse = credentials.get(today); AuthCredentialWithPniResponse authCredentialWithPniResponse = credentials.get(todaySeconds);
if (authCredentialResponse == null) { if (authCredentialWithPniResponse == null) {
throw new NoCredentialForRedemptionTimeException(); throw new NoCredentialForRedemptionTimeException();
} }
return groupsV2Api.getGroupsV2AuthorizationString(authServiceId, today, groupSecretParams, authCredentialResponse); return groupsV2Api.getGroupsV2AuthorizationString(serviceIds.getAci(), serviceIds.requirePni(), todaySeconds, groupSecretParams, authCredentialWithPniResponse);
} }
public interface ValueCache { public interface ValueCache {
void clear(); void clear();
@NonNull Map<Integer, AuthCredentialResponse> read(); @NonNull Map<Long, AuthCredentialWithPniResponse> read();
void write(@NonNull Map<Integer, AuthCredentialResponse> values); void write(@NonNull Map<Long, AuthCredentialWithPniResponse> values);
} }
} }

Wyświetl plik

@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.groups;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import org.signal.libsignal.zkgroup.auth.AuthCredentialResponse; import org.signal.libsignal.zkgroup.auth.AuthCredentialWithPniResponse;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -10,8 +10,8 @@ import java.util.Map;
public final class GroupsV2AuthorizationMemoryValueCache implements GroupsV2Authorization.ValueCache { public final class GroupsV2AuthorizationMemoryValueCache implements GroupsV2Authorization.ValueCache {
private final GroupsV2Authorization.ValueCache inner; private final GroupsV2Authorization.ValueCache inner;
private Map<Integer, AuthCredentialResponse> values; private Map<Long, AuthCredentialWithPniResponse> values;
public GroupsV2AuthorizationMemoryValueCache(@NonNull GroupsV2Authorization.ValueCache inner) { public GroupsV2AuthorizationMemoryValueCache(@NonNull GroupsV2Authorization.ValueCache inner) {
this.inner = inner; this.inner = inner;
@ -24,11 +24,11 @@ public final class GroupsV2AuthorizationMemoryValueCache implements GroupsV2Auth
} }
@Override @Override
public @NonNull synchronized Map<Integer, AuthCredentialResponse> read() { public @NonNull synchronized Map<Long, AuthCredentialWithPniResponse> read() {
Map<Integer, AuthCredentialResponse> map = values; Map<Long, AuthCredentialWithPniResponse> map = values;
if (map == null) { if (map == null) {
map = inner.read(); map = inner.read();
values = map; values = map;
} }
@ -36,7 +36,7 @@ public final class GroupsV2AuthorizationMemoryValueCache implements GroupsV2Auth
} }
@Override @Override
public synchronized void write(@NonNull Map<Integer, AuthCredentialResponse> values) { public synchronized void write(@NonNull Map<Long, AuthCredentialWithPniResponse> values) {
inner.write(values); inner.write(values);
this.values = Collections.unmodifiableMap(new HashMap<>(values)); this.values = Collections.unmodifiableMap(new HashMap<>(values));
} }

Wyświetl plik

@ -1,27 +1,25 @@
package org.thoughtcrime.securesms.groups.v2; package org.thoughtcrime.securesms.groups.v2;
import android.content.Context;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread; import androidx.annotation.WorkerThread;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredential;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.ProfileUtil;
import org.whispersystems.signalservice.api.SignalServiceAccountManager; import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.groupsv2.GroupCandidate; import org.whispersystems.signalservice.api.groupsv2.GroupCandidate;
import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.ServiceId;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.Locale; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
@ -29,7 +27,7 @@ public class GroupCandidateHelper {
private final SignalServiceAccountManager signalServiceAccountManager; private final SignalServiceAccountManager signalServiceAccountManager;
private final RecipientDatabase recipientDatabase; private final RecipientDatabase recipientDatabase;
public GroupCandidateHelper(@NonNull Context context) { public GroupCandidateHelper() {
signalServiceAccountManager = ApplicationDependencies.getSignalServiceAccountManager(); signalServiceAccountManager = ApplicationDependencies.getSignalServiceAccountManager();
recipientDatabase = SignalDatabase.recipients(); recipientDatabase = SignalDatabase.recipients();
} }
@ -52,27 +50,17 @@ public class GroupCandidateHelper {
throw new AssertionError("Non UUID members should have need detected by now"); throw new AssertionError("Non UUID members should have need detected by now");
} }
Optional<ProfileKeyCredential> profileKeyCredential = Optional.ofNullable(recipient.getProfileKeyCredential()); Optional<ExpiringProfileKeyCredential> expiringProfileKeyCredential = Optional.ofNullable(recipient.getExpiringProfileKeyCredential());
GroupCandidate candidate = new GroupCandidate(serviceId.uuid(), profileKeyCredential); GroupCandidate candidate = new GroupCandidate(serviceId.uuid(), expiringProfileKeyCredential);
if (!candidate.hasProfileKeyCredential()) { if (!candidate.hasValidProfileKeyCredential()) {
ProfileKey profileKey = ProfileKeyUtil.profileKeyOrNull(recipient.getProfileKey()); recipientDatabase.clearProfileKeyCredential(recipient.getId());
if (profileKey != null) { Optional<ExpiringProfileKeyCredential> credential = ProfileUtil.updateExpiringProfileKeyCredential(recipient);
Log.i(TAG, String.format("No profile key credential on recipient %s, fetching", recipient.getId())); if (credential.isPresent()) {
candidate = candidate.withExpiringProfileKeyCredential(credential.get());
Optional<ProfileKeyCredential> profileKeyCredentialOptional = signalServiceAccountManager.resolveProfileKeyCredential(serviceId, profileKey, Locale.getDefault()); } else {
candidate = candidate.withoutExpiringProfileKeyCredential();
if (profileKeyCredentialOptional.isPresent()) {
boolean updatedProfileKey = recipientDatabase.setProfileKeyCredential(recipient.getId(), profileKey, profileKeyCredentialOptional.get());
if (!updatedProfileKey) {
Log.w(TAG, String.format("Failed to update the profile key credential on recipient %s", recipient.getId()));
} else {
Log.i(TAG, String.format("Got new profile key credential for recipient %s", recipient.getId()));
candidate = candidate.withProfileKeyCredential(profileKeyCredentialOptional.get());
}
}
} }
} }
@ -91,4 +79,17 @@ public class GroupCandidateHelper {
return result; return result;
} }
@WorkerThread
public @NonNull List<GroupCandidate> recipientIdsToCandidatesList(@NonNull Collection<RecipientId> recipientIds)
throws IOException
{
List<GroupCandidate> result = new ArrayList<>(recipientIds.size());
for (RecipientId recipientId : recipientIds) {
result.add(recipientIdToCandidate(recipientId));
}
return result;
}
} }

Wyświetl plik

@ -54,6 +54,7 @@ import org.whispersystems.signalservice.api.groupsv2.InvalidGroupStateException;
import org.whispersystems.signalservice.api.groupsv2.NotAbleToApplyGroupV2ChangeException; import org.whispersystems.signalservice.api.groupsv2.NotAbleToApplyGroupV2ChangeException;
import org.whispersystems.signalservice.api.groupsv2.PartialDecryptedGroup; import org.whispersystems.signalservice.api.groupsv2.PartialDecryptedGroup;
import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.ServiceIds;
import org.whispersystems.signalservice.api.util.UuidUtil; import org.whispersystems.signalservice.api.util.UuidUtil;
import org.whispersystems.signalservice.internal.push.exceptions.GroupNotFoundException; import org.whispersystems.signalservice.internal.push.exceptions.GroupNotFoundException;
import org.whispersystems.signalservice.internal.push.exceptions.NotInGroupException; import org.whispersystems.signalservice.internal.push.exceptions.NotInGroupException;
@ -103,10 +104,10 @@ public class GroupsV2StateProcessor {
this.groupDatabase = SignalDatabase.groups(); this.groupDatabase = SignalDatabase.groups();
} }
public StateProcessorForGroup forGroup(@NonNull ServiceId serviceId, @NonNull GroupMasterKey groupMasterKey) { public StateProcessorForGroup forGroup(@NonNull ServiceIds serviceIds, @NonNull GroupMasterKey groupMasterKey) {
ProfileAndMessageHelper profileAndMessageHelper = new ProfileAndMessageHelper(context, serviceId, groupMasterKey, GroupId.v2(groupMasterKey), recipientDatabase); ProfileAndMessageHelper profileAndMessageHelper = new ProfileAndMessageHelper(context, serviceIds.getAci(), groupMasterKey, GroupId.v2(groupMasterKey), recipientDatabase);
return new StateProcessorForGroup(serviceId, context, groupDatabase, groupsV2Api, groupsV2Authorization, groupMasterKey, profileAndMessageHelper); return new StateProcessorForGroup(serviceIds, context, groupDatabase, groupsV2Api, groupsV2Authorization, groupMasterKey, profileAndMessageHelper);
} }
public enum GroupState { public enum GroupState {
@ -140,7 +141,7 @@ public class GroupsV2StateProcessor {
} }
public static final class StateProcessorForGroup { public static final class StateProcessorForGroup {
private final ServiceId serviceId; private final ServiceIds serviceIds;
private final Context context; private final Context context;
private final GroupDatabase groupDatabase; private final GroupDatabase groupDatabase;
private final GroupsV2Api groupsV2Api; private final GroupsV2Api groupsV2Api;
@ -150,7 +151,7 @@ public class GroupsV2StateProcessor {
private final GroupSecretParams groupSecretParams; private final GroupSecretParams groupSecretParams;
private final ProfileAndMessageHelper profileAndMessageHelper; private final ProfileAndMessageHelper profileAndMessageHelper;
@VisibleForTesting StateProcessorForGroup(@NonNull ServiceId serviceId, @VisibleForTesting StateProcessorForGroup(@NonNull ServiceIds serviceIds,
@NonNull Context context, @NonNull Context context,
@NonNull GroupDatabase groupDatabase, @NonNull GroupDatabase groupDatabase,
@NonNull GroupsV2Api groupsV2Api, @NonNull GroupsV2Api groupsV2Api,
@ -158,7 +159,7 @@ public class GroupsV2StateProcessor {
@NonNull GroupMasterKey groupMasterKey, @NonNull GroupMasterKey groupMasterKey,
@NonNull ProfileAndMessageHelper profileAndMessageHelper) @NonNull ProfileAndMessageHelper profileAndMessageHelper)
{ {
this.serviceId = serviceId; this.serviceIds = serviceIds;
this.context = context; this.context = context;
this.groupDatabase = groupDatabase; this.groupDatabase = groupDatabase;
this.groupsV2Api = groupsV2Api; this.groupsV2Api = groupsV2Api;
@ -196,17 +197,17 @@ public class GroupsV2StateProcessor {
{ {
if (notInGroupAndNotBeingAdded(localRecord, signedGroupChange) && notHavingInviteRevoked(signedGroupChange)) { if (notInGroupAndNotBeingAdded(localRecord, signedGroupChange) && notHavingInviteRevoked(signedGroupChange)) {
Log.w(TAG, "Ignoring P2P group change because we're not currently in the group and this change doesn't add us in. Falling back to a server fetch."); warn("Ignoring P2P group change because we're not currently in the group and this change doesn't add us in. Falling back to a server fetch.");
} else if (SignalStore.internalValues().gv2IgnoreP2PChanges()) { } else if (SignalStore.internalValues().gv2IgnoreP2PChanges()) {
Log.w(TAG, "Ignoring P2P group change by setting"); warn( "Ignoring P2P group change by setting");
} else { } else {
try { try {
Log.i(TAG, "Applying P2P group change"); info("Applying P2P group change");
DecryptedGroup newState = DecryptedGroupUtil.apply(localState, signedGroupChange); DecryptedGroup newState = DecryptedGroupUtil.apply(localState, signedGroupChange);
inputGroupState = new GlobalGroupState(localState, Collections.singletonList(new ServerGroupLogEntry(newState, signedGroupChange))); inputGroupState = new GlobalGroupState(localState, Collections.singletonList(new ServerGroupLogEntry(newState, signedGroupChange)));
} catch (NotAbleToApplyGroupV2ChangeException e) { } catch (NotAbleToApplyGroupV2ChangeException e) {
Log.w(TAG, "Unable to apply P2P group change", e); warn( "Unable to apply P2P group change", e);
} }
} }
} }
@ -218,24 +219,24 @@ public class GroupsV2StateProcessor {
if (localState != null && signedGroupChange != null) { if (localState != null && signedGroupChange != null) {
try { try {
if (notInGroupAndNotBeingAdded(localRecord, signedGroupChange)) { if (notInGroupAndNotBeingAdded(localRecord, signedGroupChange)) {
Log.w(TAG, "Server says we're not a member. Ignoring P2P group change because we're not currently in the group and this change doesn't add us in."); warn( "Server says we're not a member. Ignoring P2P group change because we're not currently in the group and this change doesn't add us in.");
} else { } else {
Log.i(TAG, "Server says we're not a member. Applying P2P group change."); info("Server says we're not a member. Applying P2P group change.");
DecryptedGroup newState = DecryptedGroupUtil.applyWithoutRevisionCheck(localState, signedGroupChange); DecryptedGroup newState = DecryptedGroupUtil.applyWithoutRevisionCheck(localState, signedGroupChange);
inputGroupState = new GlobalGroupState(localState, Collections.singletonList(new ServerGroupLogEntry(newState, signedGroupChange))); inputGroupState = new GlobalGroupState(localState, Collections.singletonList(new ServerGroupLogEntry(newState, signedGroupChange)));
} }
} catch (NotAbleToApplyGroupV2ChangeException failed) { } catch (NotAbleToApplyGroupV2ChangeException failed) {
Log.w(TAG, "Unable to apply P2P group change when not a member", failed); warn( "Unable to apply P2P group change when not a member", failed);
} }
} }
if (inputGroupState == null) { if (inputGroupState == null) {
if (localState != null && DecryptedGroupUtil.isPendingOrRequesting(localState, serviceId.uuid())) { if (localState != null && DecryptedGroupUtil.isPendingOrRequesting(localState, serviceIds)) {
Log.w(TAG, "Unable to query server for group " + groupId + " server says we're not in group, but we think we are a pending or requesting member"); warn( "Unable to query server for group " + groupId + " server says we're not in group, but we think we are a pending or requesting member");
throw new GroupNotAMemberException(e, true); throw new GroupNotAMemberException(e, true);
} else { } else {
Log.w(TAG, "Unable to query server for group " + groupId + " server says we're not in group, inserting leave message"); warn( "Unable to query server for group " + groupId + " server says we're not in group, inserting leave message");
insertGroupLeave(); insertGroupLeave();
} }
throw e; throw e;
@ -252,7 +253,7 @@ public class GroupsV2StateProcessor {
updateLocalDatabaseGroupState(inputGroupState, newLocalState); updateLocalDatabaseGroupState(inputGroupState, newLocalState);
if (localState != null && localState.getRevision() == GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION) { if (localState != null && localState.getRevision() == GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION) {
Log.i(TAG, "Inserting single update message for restore placeholder"); info("Inserting single update message for restore placeholder");
profileAndMessageHelper.insertUpdateMessages(timestamp, null, Collections.singleton(new LocalGroupLogEntry(newLocalState, null))); profileAndMessageHelper.insertUpdateMessages(timestamp, null, Collections.singleton(new LocalGroupLogEntry(newLocalState, null)));
} else { } else {
profileAndMessageHelper.insertUpdateMessages(timestamp, localState, advanceGroupStateResult.getProcessedLogEntries()); profileAndMessageHelper.insertUpdateMessages(timestamp, localState, advanceGroupStateResult.getProcessedLogEntries());
@ -261,7 +262,7 @@ public class GroupsV2StateProcessor {
GlobalGroupState remainingWork = advanceGroupStateResult.getNewGlobalGroupState(); GlobalGroupState remainingWork = advanceGroupStateResult.getNewGlobalGroupState();
if (remainingWork.getServerHistory().size() > 0) { if (remainingWork.getServerHistory().size() > 0) {
Log.i(TAG, String.format(Locale.US, "There are more revisions on the server for this group, scheduling for later, V[%d..%d]", newLocalState.getRevision() + 1, remainingWork.getLatestRevisionNumber())); info(String.format(Locale.US, "There are more revisions on the server for this group, scheduling for later, V[%d..%d]", newLocalState.getRevision() + 1, remainingWork.getLatestRevisionNumber()));
ApplicationDependencies.getJobManager().add(new RequestGroupV2InfoJob(groupId, remainingWork.getLatestRevisionNumber())); ApplicationDependencies.getJobManager().add(new RequestGroupV2InfoJob(groupId, remainingWork.getLatestRevisionNumber()));
} }
@ -276,14 +277,14 @@ public class GroupsV2StateProcessor {
.map(DecryptedMember::getUuid) .map(DecryptedMember::getUuid)
.map(UuidUtil::fromByteStringOrNull) .map(UuidUtil::fromByteStringOrNull)
.filter(Objects::nonNull) .filter(Objects::nonNull)
.anyMatch(u -> u.equals(serviceId.uuid())); .anyMatch(serviceIds::matches);
boolean addedAsPendingMember = signedGroupChange.getNewPendingMembersList() boolean addedAsPendingMember = signedGroupChange.getNewPendingMembersList()
.stream() .stream()
.map(DecryptedPendingMember::getUuid) .map(DecryptedPendingMember::getUuid)
.map(UuidUtil::fromByteStringOrNull) .map(UuidUtil::fromByteStringOrNull)
.filter(Objects::nonNull) .filter(Objects::nonNull)
.anyMatch(u -> u.equals(serviceId.uuid())); .anyMatch(serviceIds::matches);
return !currentlyInGroup && !addedAsMember && !addedAsPendingMember; return !currentlyInGroup && !addedAsMember && !addedAsPendingMember;
} }
@ -294,7 +295,7 @@ public class GroupsV2StateProcessor {
.map(DecryptedPendingMemberRemoval::getUuid) .map(DecryptedPendingMemberRemoval::getUuid)
.map(UuidUtil::fromByteStringOrNull) .map(UuidUtil::fromByteStringOrNull)
.filter(Objects::nonNull) .filter(Objects::nonNull)
.anyMatch(u -> u.equals(serviceId.uuid())); .anyMatch(serviceIds::matches);
return !havingInviteRevoked; return !havingInviteRevoked;
} }
@ -305,44 +306,43 @@ public class GroupsV2StateProcessor {
private GroupUpdateResult updateLocalGroupFromServerPaged(int revision, DecryptedGroup localState, long timestamp, boolean forceIncludeFirst) throws IOException, GroupNotAMemberException { private GroupUpdateResult updateLocalGroupFromServerPaged(int revision, DecryptedGroup localState, long timestamp, boolean forceIncludeFirst) throws IOException, GroupNotAMemberException {
boolean latestRevisionOnly = revision == LATEST && (localState == null || localState.getRevision() == GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION); boolean latestRevisionOnly = revision == LATEST && (localState == null || localState.getRevision() == GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION);
Log.i(TAG, "Paging from server revision: " + (revision == LATEST ? "latest" : revision) + ", latestOnly: " + latestRevisionOnly); info("Paging from server revision: " + (revision == LATEST ? "latest" : revision) + ", latestOnly: " + latestRevisionOnly);
PartialDecryptedGroup latestServerGroup; PartialDecryptedGroup latestServerGroup;
GlobalGroupState inputGroupState; GlobalGroupState inputGroupState;
try { try {
latestServerGroup = groupsV2Api.getPartialDecryptedGroup(groupSecretParams, groupsV2Authorization.getAuthorizationForToday(serviceId, groupSecretParams)); latestServerGroup = groupsV2Api.getPartialDecryptedGroup(groupSecretParams, groupsV2Authorization.getAuthorizationForToday(serviceIds, groupSecretParams));
} catch (NotInGroupException | GroupNotFoundException e) { } catch (NotInGroupException | GroupNotFoundException e) {
throw new GroupNotAMemberException(e); throw new GroupNotAMemberException(e);
} catch (VerificationFailedException | InvalidGroupStateException e) { } catch (VerificationFailedException | InvalidGroupStateException e) {
throw new IOException(e); throw new IOException(e);
} }
if (localState != null && localState.getRevision() >= latestServerGroup.getRevision() && GroupProtoUtil.isMember(serviceId.uuid(), localState.getMembersList())) { if (localState != null && localState.getRevision() >= latestServerGroup.getRevision() && GroupProtoUtil.isMember(serviceIds.getAci().uuid(), localState.getMembersList())) {
Log.i(TAG, "Local state is at or later than server"); info("Local state is at or later than server");
return new GroupUpdateResult(GroupState.GROUP_CONSISTENT_OR_AHEAD, null); return new GroupUpdateResult(GroupState.GROUP_CONSISTENT_OR_AHEAD, null);
} }
if (latestRevisionOnly || !GroupProtoUtil.isMember(serviceId.uuid(), latestServerGroup.getMembersList())) { if (latestRevisionOnly || !GroupProtoUtil.isMember(serviceIds.getAci().uuid(), latestServerGroup.getMembersList())) {
Log.i(TAG, "Latest revision or not a member, use latest only"); info("Latest revision or not a member, use latest only");
inputGroupState = new GlobalGroupState(localState, Collections.singletonList(new ServerGroupLogEntry(latestServerGroup.getFullyDecryptedGroup(), null))); inputGroupState = new GlobalGroupState(localState, Collections.singletonList(new ServerGroupLogEntry(latestServerGroup.getFullyDecryptedGroup(), null)));
} else { } else {
int revisionWeWereAdded = GroupProtoUtil.findRevisionWeWereAdded(latestServerGroup, serviceId.uuid()); int revisionWeWereAdded = GroupProtoUtil.findRevisionWeWereAdded(latestServerGroup, serviceIds.getAci().uuid());
int logsNeededFrom = localState != null ? Math.max(localState.getRevision(), revisionWeWereAdded) : revisionWeWereAdded; int logsNeededFrom = localState != null ? Math.max(localState.getRevision(), revisionWeWereAdded) : revisionWeWereAdded;
boolean includeFirstState = forceIncludeFirst || boolean includeFirstState = forceIncludeFirst ||
localState == null || localState == null ||
localState.getRevision() < 0 || localState.getRevision() < 0 ||
localState.getRevision() == revisionWeWereAdded || localState.getRevision() == revisionWeWereAdded ||
!GroupProtoUtil.isMember(serviceId.uuid(), localState.getMembersList()) || !GroupProtoUtil.isMember(serviceIds.getAci().uuid(), localState.getMembersList()) ||
(revision == LATEST && localState.getRevision() + 1 < latestServerGroup.getRevision()); (revision == LATEST && localState.getRevision() + 1 < latestServerGroup.getRevision());
Log.i(TAG, info("Requesting from server currentRevision: " + (localState != null ? localState.getRevision() : "null") +
"Requesting from server currentRevision: " + (localState != null ? localState.getRevision() : "null") + " logsNeededFrom: " + logsNeededFrom +
" logsNeededFrom: " + logsNeededFrom + " includeFirstState: " + includeFirstState +
" includeFirstState: " + includeFirstState + " forceIncludeFirst: " + forceIncludeFirst);
" forceIncludeFirst: " + forceIncludeFirst); inputGroupState = getFullMemberHistoryPage(localState, logsNeededFrom, includeFirstState);
inputGroupState = getFullMemberHistoryPage(localState, serviceId, logsNeededFrom, includeFirstState);
} }
ProfileKeySet profileKeys = new ProfileKeySet(); ProfileKeySet profileKeys = new ProfileKeySet();
@ -354,13 +354,13 @@ public class GroupsV2StateProcessor {
while (hasMore) { while (hasMore) {
AdvanceGroupStateResult advanceGroupStateResult = GroupStateMapper.partiallyAdvanceGroupState(inputGroupState, revision); AdvanceGroupStateResult advanceGroupStateResult = GroupStateMapper.partiallyAdvanceGroupState(inputGroupState, revision);
DecryptedGroup newLocalState = advanceGroupStateResult.getNewGlobalGroupState().getLocalState(); DecryptedGroup newLocalState = advanceGroupStateResult.getNewGlobalGroupState().getLocalState();
Log.i(TAG, "Advanced group to revision: " + (newLocalState != null ? newLocalState.getRevision() : "null")); info("Advanced group to revision: " + (newLocalState != null ? newLocalState.getRevision() : "null"));
if (newLocalState != null && !inputGroupState.hasMore() && !forceIncludeFirst) { if (newLocalState != null && !inputGroupState.hasMore() && !forceIncludeFirst) {
int newLocalRevision = newLocalState.getRevision(); int newLocalRevision = newLocalState.getRevision();
int requestRevision = (revision == LATEST) ? latestServerGroup.getRevision() : revision; int requestRevision = (revision == LATEST) ? latestServerGroup.getRevision() : revision;
if (newLocalRevision < requestRevision) { if (newLocalRevision < requestRevision) {
Log.w(TAG, "Paging again with force first snapshot enabled due to error processing changes. New local revision [" + newLocalRevision + "] hasn't reached our desired level [" + requestRevision + "]"); warn( "Paging again with force first snapshot enabled due to error processing changes. New local revision [" + newLocalRevision + "] hasn't reached our desired level [" + requestRevision + "]");
return updateLocalGroupFromServerPaged(revision, localState, timestamp, true); return updateLocalGroupFromServerPaged(revision, localState, timestamp, true);
} }
} }
@ -389,20 +389,20 @@ public class GroupsV2StateProcessor {
hasMore = inputGroupState.hasMore(); hasMore = inputGroupState.hasMore();
if (hasMore) { if (hasMore) {
Log.i(TAG, "Request next page from server revision: " + finalState.getRevision() + " nextPageRevision: " + inputGroupState.getNextPageRevision()); info("Request next page from server revision: " + finalState.getRevision() + " nextPageRevision: " + inputGroupState.getNextPageRevision());
inputGroupState = getFullMemberHistoryPage(finalState, serviceId, inputGroupState.getNextPageRevision(), false); inputGroupState = getFullMemberHistoryPage(finalState, inputGroupState.getNextPageRevision(), false);
} }
} }
if (localState != null && localState.getRevision() == GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION) { if (localState != null && localState.getRevision() == GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION) {
Log.i(TAG, "Inserting single update message for restore placeholder"); info("Inserting single update message for restore placeholder");
profileAndMessageHelper.insertUpdateMessages(timestamp, null, Collections.singleton(new LocalGroupLogEntry(finalState, null))); profileAndMessageHelper.insertUpdateMessages(timestamp, null, Collections.singleton(new LocalGroupLogEntry(finalState, null)));
} }
profileAndMessageHelper.persistLearnedProfileKeys(profileKeys); profileAndMessageHelper.persistLearnedProfileKeys(profileKeys);
if (finalGlobalGroupState.getServerHistory().size() > 0) { if (finalGlobalGroupState.getServerHistory().size() > 0) {
Log.i(TAG, String.format(Locale.US, "There are more revisions on the server for this group, scheduling for later, V[%d..%d]", finalState.getRevision() + 1, finalGlobalGroupState.getLatestRevisionNumber())); info(String.format(Locale.US, "There are more revisions on the server for this group, scheduling for later, V[%d..%d]", finalState.getRevision() + 1, finalGlobalGroupState.getLatestRevisionNumber()));
ApplicationDependencies.getJobManager().add(new RequestGroupV2InfoJob(groupId, finalGlobalGroupState.getLatestRevisionNumber())); ApplicationDependencies.getJobManager().add(new RequestGroupV2InfoJob(groupId, finalGlobalGroupState.getLatestRevisionNumber()));
} }
@ -414,7 +414,7 @@ public class GroupsV2StateProcessor {
throws IOException, GroupNotAMemberException, GroupDoesNotExistException throws IOException, GroupNotAMemberException, GroupDoesNotExistException
{ {
try { try {
return groupsV2Api.getGroup(groupSecretParams, groupsV2Authorization.getAuthorizationForToday(serviceId, groupSecretParams)); return groupsV2Api.getGroup(groupSecretParams, groupsV2Authorization.getAuthorizationForToday(serviceIds, groupSecretParams));
} catch (GroupNotFoundException e) { } catch (GroupNotFoundException e) {
throw new GroupDoesNotExistException(e); throw new GroupDoesNotExistException(e);
} catch (NotInGroupException e) { } catch (NotInGroupException e) {
@ -429,7 +429,7 @@ public class GroupsV2StateProcessor {
throws IOException, GroupNotAMemberException, GroupDoesNotExistException throws IOException, GroupNotAMemberException, GroupDoesNotExistException
{ {
try { try {
return groupsV2Api.getGroupHistoryPage(groupSecretParams, revision, groupsV2Authorization.getAuthorizationForToday(serviceId, groupSecretParams), true) return groupsV2Api.getGroupHistoryPage(groupSecretParams, revision, groupsV2Authorization.getAuthorizationForToday(serviceIds, groupSecretParams), true)
.getResults() .getResults()
.get(0) .get(0)
.getGroup() .getGroup()
@ -445,12 +445,12 @@ public class GroupsV2StateProcessor {
private void insertGroupLeave() { private void insertGroupLeave() {
if (!groupDatabase.isActive(groupId)) { if (!groupDatabase.isActive(groupId)) {
Log.w(TAG, "Group has already been left."); warn("Group has already been left.");
return; return;
} }
Recipient groupRecipient = Recipient.externalGroupExact(groupId); Recipient groupRecipient = Recipient.externalGroupExact(groupId);
UUID selfUuid = serviceId.uuid(); UUID selfUuid = serviceIds.getAci().uuid();
DecryptedGroup decryptedGroup = groupDatabase.requireGroup(groupId) DecryptedGroup decryptedGroup = groupDatabase.requireGroup(groupId)
.requireV2GroupProperties() .requireV2GroupProperties()
@ -485,7 +485,7 @@ public class GroupsV2StateProcessor {
mmsDatabase.markAsSent(id, true); mmsDatabase.markAsSent(id, true);
threadDatabase.update(threadId, false, false); threadDatabase.update(threadId, false, false);
} catch (MmsException e) { } catch (MmsException e) {
Log.w(TAG, "Failed to insert leave message.", e); warn( "Failed to insert leave message.", e);
} }
groupDatabase.setActive(groupId, false); groupDatabase.setActive(groupId, false);
@ -509,7 +509,7 @@ public class GroupsV2StateProcessor {
boolean needsAvatarFetch; boolean needsAvatarFetch;
if (inputGroupState.getLocalState() == null) { if (inputGroupState.getLocalState() == null) {
groupDatabase.create(serviceId, masterKey, newLocalState); groupDatabase.create(masterKey, newLocalState);
needsAvatarFetch = !TextUtils.isEmpty(newLocalState.getAvatar()); needsAvatarFetch = !TextUtils.isEmpty(newLocalState.getAvatar());
} else { } else {
groupDatabase.update(masterKey, newLocalState); groupDatabase.update(masterKey, newLocalState);
@ -523,14 +523,14 @@ public class GroupsV2StateProcessor {
profileAndMessageHelper.determineProfileSharing(inputGroupState, newLocalState); profileAndMessageHelper.determineProfileSharing(inputGroupState, newLocalState);
} }
private GlobalGroupState getFullMemberHistoryPage(DecryptedGroup localState, @NonNull ServiceId serviceId, int logsNeededFromRevision, boolean includeFirstState) throws IOException { private GlobalGroupState getFullMemberHistoryPage(DecryptedGroup localState, int logsNeededFromRevision, boolean includeFirstState) throws IOException {
try { try {
GroupHistoryPage groupHistoryPage = groupsV2Api.getGroupHistoryPage(groupSecretParams, logsNeededFromRevision, groupsV2Authorization.getAuthorizationForToday(serviceId, groupSecretParams), includeFirstState); GroupHistoryPage groupHistoryPage = groupsV2Api.getGroupHistoryPage(groupSecretParams, logsNeededFromRevision, groupsV2Authorization.getAuthorizationForToday(serviceIds, groupSecretParams), includeFirstState);
ArrayList<ServerGroupLogEntry> history = new ArrayList<>(groupHistoryPage.getResults().size()); ArrayList<ServerGroupLogEntry> history = new ArrayList<>(groupHistoryPage.getResults().size());
boolean ignoreServerChanges = SignalStore.internalValues().gv2IgnoreServerChanges(); boolean ignoreServerChanges = SignalStore.internalValues().gv2IgnoreServerChanges();
if (ignoreServerChanges) { if (ignoreServerChanges) {
Log.w(TAG, "Server change logs are ignored by setting"); warn( "Server change logs are ignored by setting");
} }
for (DecryptedGroupHistoryEntry entry : groupHistoryPage.getResults()) { for (DecryptedGroupHistoryEntry entry : groupHistoryPage.getResults()) {
@ -547,6 +547,22 @@ public class GroupsV2StateProcessor {
throw new IOException(e); throw new IOException(e);
} }
} }
private void info(String message) {
info(message, null);
}
private void info(String message, Throwable t) {
Log.i(TAG, "[" + groupId.toString() + "] " + message, t);
}
private void warn(String message) {
warn(message, null);
}
private void warn(String message, Throwable e) {
Log.w(TAG, "[" + groupId.toString() + "] " + message, e);
}
} }
@VisibleForTesting @VisibleForTesting
@ -615,7 +631,7 @@ public class GroupsV2StateProcessor {
.map(uuid -> Recipient.externalPush(ServiceId.from(uuid)))); .map(uuid -> Recipient.externalPush(ServiceId.from(uuid))));
if (addedBy.isPresent() && addedBy.get().isBlocked()) { if (addedBy.isPresent() && addedBy.get().isBlocked()) {
Log.i(TAG, String.format( "Added to group %s by a blocked user %s. Leaving group.", groupId, addedBy.get().getId())); Log.i(TAG, String.format("Added to group %s by a blocked user %s. Leaving group.", groupId, addedBy.get().getId()));
ApplicationDependencies.getJobManager().add(new LeaveGroupV2Job(groupId)); ApplicationDependencies.getJobManager().add(new LeaveGroupV2Job(groupId));
//noinspection UnnecessaryReturnStatement //noinspection UnnecessaryReturnStatement
return; return;

Wyświetl plik

@ -6,8 +6,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredential;
import org.thoughtcrime.securesms.badges.BadgeRepository; import org.thoughtcrime.securesms.badges.BadgeRepository;
import org.thoughtcrime.securesms.badges.Badges; import org.thoughtcrime.securesms.badges.Badges;
import org.thoughtcrime.securesms.badges.models.Badge; import org.thoughtcrime.securesms.badges.models.Badge;
@ -32,13 +32,13 @@ import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile; import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription; import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription;
import org.whispersystems.signalservice.api.util.ExpiringProfileCredentialUtil;
import org.whispersystems.signalservice.internal.ServiceResponse; import org.whispersystems.signalservice.internal.ServiceResponse;
import java.io.IOException; import java.io.IOException;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -130,26 +130,23 @@ public class RefreshOwnProfileJob extends BaseJob {
setProfileBadges(profile.getBadges()); setProfileBadges(profile.getBadges());
ensureUnidentifiedAccessCorrect(profile.getUnidentifiedAccess(), profile.isUnrestrictedUnidentifiedAccess()); ensureUnidentifiedAccessCorrect(profile.getUnidentifiedAccess(), profile.isUnrestrictedUnidentifiedAccess());
Optional<ProfileKeyCredential> profileKeyCredential = profileAndCredential.getProfileKeyCredential(); profileAndCredential.getExpiringProfileKeyCredential()
if (profileKeyCredential.isPresent()) { .ifPresent(expiringProfileKeyCredential -> setExpiringProfileKeyCredential(self, ProfileKeyUtil.getSelfProfileKey(), expiringProfileKeyCredential));
setProfileKeyCredential(self, ProfileKeyUtil.getSelfProfileKey(), profileKeyCredential.get());
}
StoryOnboardingDownloadJob.Companion.enqueueIfNeeded(); StoryOnboardingDownloadJob.Companion.enqueueIfNeeded();
} }
private void setProfileKeyCredential(@NonNull Recipient recipient, private void setExpiringProfileKeyCredential(@NonNull Recipient recipient,
@NonNull ProfileKey recipientProfileKey, @NonNull ProfileKey recipientProfileKey,
@NonNull ProfileKeyCredential credential) @NonNull ExpiringProfileKeyCredential credential)
{ {
RecipientDatabase recipientDatabase = SignalDatabase.recipients(); RecipientDatabase recipientDatabase = SignalDatabase.recipients();
recipientDatabase.setProfileKeyCredential(recipient.getId(), recipientProfileKey, credential); recipientDatabase.setProfileKeyCredential(recipient.getId(), recipientProfileKey, credential);
} }
private static SignalServiceProfile.RequestType getRequestType(@NonNull Recipient recipient) { private static SignalServiceProfile.RequestType getRequestType(@NonNull Recipient recipient) {
return !recipient.hasProfileKeyCredential() return ExpiringProfileCredentialUtil.isValid(recipient.getExpiringProfileKeyCredential()) ? SignalServiceProfile.RequestType.PROFILE
? SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL : SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL;
: SignalServiceProfile.RequestType.PROFILE;
} }
@Override @Override

Wyświetl plik

@ -4,7 +4,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread; import androidx.annotation.WorkerThread;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.signal.libsignal.zkgroup.VerificationFailedException;
import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.groups.GroupChangeBusyException; import org.thoughtcrime.securesms.groups.GroupChangeBusyException;
@ -15,10 +14,8 @@ import org.thoughtcrime.securesms.groups.v2.processing.GroupsV2StateProcessor;
import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.whispersystems.signalservice.api.groupsv2.NoCredentialForRedemptionTimeException; import org.whispersystems.signalservice.api.groupsv2.NoCredentialForRedemptionTimeException;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
import java.io.IOException; import java.io.IOException;
@ -91,24 +88,7 @@ final class RequestGroupV2InfoWorkerJob extends BaseJob {
return; return;
} }
ServiceId authServiceId = group.get().getAuthServiceId() != null ? group.get().getAuthServiceId() : SignalStore.account().requireAci(); GroupManager.updateGroupFromServer(context, group.get().requireV2GroupProperties().getGroupMasterKey(), toRevision, System.currentTimeMillis(), null);
try {
GroupManager.updateGroupFromServer(context, authServiceId, group.get().requireV2GroupProperties().getGroupMasterKey(), toRevision, System.currentTimeMillis(), null);
} catch (GroupNotAMemberException | IOException e) {
ServiceId otherServiceId = authServiceId.equals(SignalStore.account().getPni()) ? SignalStore.account().getAci() : SignalStore.account().getPni();
boolean isNotAMemberOrPending = e instanceof GroupNotAMemberException && !((GroupNotAMemberException) e).isLikelyPendingMember();
boolean verificationFailed = e.getCause() instanceof VerificationFailedException;
if (otherServiceId != null && (isNotAMemberOrPending || verificationFailed)) {
Log.i(TAG, "Request failed, attempting with other id");
GroupManager.updateGroupFromServer(context, otherServiceId, group.get().requireV2GroupProperties().getGroupMasterKey(), toRevision, System.currentTimeMillis(), null);
Log.i(TAG, "Request succeeded with other credential. Associating " + otherServiceId + " with group " + groupId);
SignalDatabase.groups().setAuthServiceId(otherServiceId, groupId);
} else {
throw e;
}
}
} }
@Override @Override

Wyświetl plik

@ -12,13 +12,14 @@ import com.annimon.stream.Collectors;
import com.annimon.stream.Stream; import com.annimon.stream.Stream;
import org.signal.core.util.ListUtil; import org.signal.core.util.ListUtil;
import org.signal.core.util.SetUtil;
import org.signal.core.util.concurrent.SignalExecutors; import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.IdentityKey; import org.signal.libsignal.protocol.IdentityKey;
import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.InvalidKeyException;
import org.signal.libsignal.protocol.util.Pair; import org.signal.libsignal.protocol.util.Pair;
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredential;
import org.thoughtcrime.securesms.badges.Badges; import org.thoughtcrime.securesms.badges.Badges;
import org.thoughtcrime.securesms.badges.models.Badge; import org.thoughtcrime.securesms.badges.models.Badge;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
@ -41,7 +42,6 @@ import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.IdentityUtil; import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.ProfileUtil; import org.thoughtcrime.securesms.util.ProfileUtil;
import org.signal.core.util.SetUtil;
import org.thoughtcrime.securesms.util.Stopwatch; import org.thoughtcrime.securesms.util.Stopwatch;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException; import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
@ -50,6 +50,7 @@ import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile; import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.services.ProfileService; import org.whispersystems.signalservice.api.services.ProfileService;
import org.whispersystems.signalservice.api.util.ExpiringProfileCredentialUtil;
import org.whispersystems.signalservice.internal.ServiceResponse; import org.whispersystems.signalservice.internal.ServiceResponse;
import java.io.IOException; import java.io.IOException;
@ -59,7 +60,6 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -274,7 +274,7 @@ public class RetrieveProfileJob extends BaseJob {
} else if (processor.genericIoError()) { } else if (processor.genericIoError()) {
state.retries.add(recipient.getId()); state.retries.add(recipient.getId());
} else { } else {
Log.w(TAG, "Failed to retrieve profile for " + recipient.getId()); Log.w(TAG, "Failed to retrieve profile for " + recipient.getId(), processor.getError());
} }
return state; return state;
}) })
@ -350,10 +350,8 @@ public class RetrieveProfileJob extends BaseJob {
setUnidentifiedAccessMode(recipient, profile.getUnidentifiedAccess(), profile.isUnrestrictedUnidentifiedAccess()); setUnidentifiedAccessMode(recipient, profile.getUnidentifiedAccess(), profile.isUnrestrictedUnidentifiedAccess());
if (recipientProfileKey != null) { if (recipientProfileKey != null) {
Optional<ProfileKeyCredential> profileKeyCredential = profileAndCredential.getProfileKeyCredential(); profileAndCredential.getExpiringProfileKeyCredential()
if (profileKeyCredential.isPresent()) { .ifPresent(profileKeyCredential -> setExpiringProfileKeyCredential(recipient, recipientProfileKey, profileKeyCredential));
setProfileKeyCredential(recipient, recipientProfileKey, profileKeyCredential.get());
}
} }
} }
@ -371,18 +369,17 @@ public class RetrieveProfileJob extends BaseJob {
SignalDatabase.recipients().setBadges(recipient.getId(), badges); SignalDatabase.recipients().setBadges(recipient.getId(), badges);
} }
private void setProfileKeyCredential(@NonNull Recipient recipient, private void setExpiringProfileKeyCredential(@NonNull Recipient recipient,
@NonNull ProfileKey recipientProfileKey, @NonNull ProfileKey recipientProfileKey,
@NonNull ProfileKeyCredential credential) @NonNull ExpiringProfileKeyCredential credential)
{ {
RecipientDatabase recipientDatabase = SignalDatabase.recipients(); RecipientDatabase recipientDatabase = SignalDatabase.recipients();
recipientDatabase.setProfileKeyCredential(recipient.getId(), recipientProfileKey, credential); recipientDatabase.setProfileKeyCredential(recipient.getId(), recipientProfileKey, credential);
} }
private static SignalServiceProfile.RequestType getRequestType(@NonNull Recipient recipient) { private static SignalServiceProfile.RequestType getRequestType(@NonNull Recipient recipient) {
return !recipient.hasProfileKeyCredential() return ExpiringProfileCredentialUtil.isValid(recipient.getExpiringProfileKeyCredential()) ? SignalServiceProfile.RequestType.PROFILE
? SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL : SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL;
: SignalServiceProfile.RequestType.PROFILE;
} }
private void setIdentityKey(Recipient recipient, String identityKeyValue) { private void setIdentityKey(Recipient recipient, String identityKeyValue) {

Wyświetl plik

@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.Util import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.api.push.ACI import org.whispersystems.signalservice.api.push.ACI
import org.whispersystems.signalservice.api.push.PNI import org.whispersystems.signalservice.api.push.PNI
import org.whispersystems.signalservice.api.push.ServiceIds
import org.whispersystems.signalservice.api.push.SignalServiceAddress import org.whispersystems.signalservice.api.push.SignalServiceAddress
import java.security.SecureRandom import java.security.SecureRandom

Wyświetl plik

@ -7,7 +7,7 @@ import com.google.protobuf.InvalidProtocolBufferException;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.auth.AuthCredentialResponse; import org.signal.libsignal.zkgroup.auth.AuthCredentialWithPniResponse;
import org.thoughtcrime.securesms.database.model.databaseprotos.TemporalAuthCredentialResponse; import org.thoughtcrime.securesms.database.model.databaseprotos.TemporalAuthCredentialResponse;
import org.thoughtcrime.securesms.database.model.databaseprotos.TemporalAuthCredentialResponses; import org.thoughtcrime.securesms.database.model.databaseprotos.TemporalAuthCredentialResponses;
import org.thoughtcrime.securesms.groups.GroupsV2Authorization; import org.thoughtcrime.securesms.groups.GroupsV2Authorization;
@ -21,27 +21,20 @@ public final class GroupsV2AuthorizationSignalStoreCache implements GroupsV2Auth
private static final String TAG = Log.tag(GroupsV2AuthorizationSignalStoreCache.class); private static final String TAG = Log.tag(GroupsV2AuthorizationSignalStoreCache.class);
private static final String ACI_PREFIX = "gv2:auth_token_cache"; private static final String ACI_PNI_PREFIX = "gv2:auth_token_cache";
private static final int ACI_VERSION = 2; private static final int ACI_PNI_VERSION = 3;
private static final String PNI_PREFIX = "gv2:auth_token_cache:pni";
private static final int PNI_VERSION = 1;
private final String key; private final String key;
private final KeyValueStore store; private final KeyValueStore store;
public static GroupsV2AuthorizationSignalStoreCache createAciCache(@NonNull KeyValueStore store) { public static GroupsV2AuthorizationSignalStoreCache createAciCache(@NonNull KeyValueStore store) {
if (store.containsKey(ACI_PREFIX)) { if (store.containsKey(ACI_PNI_PREFIX)) {
store.beginWrite() store.beginWrite()
.remove(ACI_PREFIX) .remove(ACI_PNI_PREFIX)
.commit(); .commit();
} }
return new GroupsV2AuthorizationSignalStoreCache(store, ACI_PREFIX + ":" + ACI_VERSION); return new GroupsV2AuthorizationSignalStoreCache(store, ACI_PNI_PREFIX + ":" + ACI_PNI_VERSION);
}
public static GroupsV2AuthorizationSignalStoreCache createPniCache(@NonNull KeyValueStore store) {
return new GroupsV2AuthorizationSignalStoreCache(store, PNI_PREFIX + ":" + PNI_VERSION);
} }
private GroupsV2AuthorizationSignalStoreCache(@NonNull KeyValueStore store, @NonNull String key) { private GroupsV2AuthorizationSignalStoreCache(@NonNull KeyValueStore store, @NonNull String key) {
@ -55,27 +48,27 @@ public final class GroupsV2AuthorizationSignalStoreCache implements GroupsV2Auth
.remove(key) .remove(key)
.commit(); .commit();
info("Cleared local response cache"); Log.i(TAG, "Cleared local response cache");
} }
@Override @Override
public @NonNull Map<Integer, AuthCredentialResponse> read() { public @NonNull Map<Long, AuthCredentialWithPniResponse> read() {
byte[] credentialBlob = store.getBlob(key, null); byte[] credentialBlob = store.getBlob(key, null);
if (credentialBlob == null) { if (credentialBlob == null) {
info("No credentials responses are cached locally"); Log.i(TAG, "No credentials responses are cached locally");
return Collections.emptyMap(); return Collections.emptyMap();
} }
try { try {
TemporalAuthCredentialResponses temporalCredentials = TemporalAuthCredentialResponses.parseFrom(credentialBlob); TemporalAuthCredentialResponses temporalCredentials = TemporalAuthCredentialResponses.parseFrom(credentialBlob);
HashMap<Integer, AuthCredentialResponse> result = new HashMap<>(temporalCredentials.getCredentialResponseCount()); HashMap<Long, AuthCredentialWithPniResponse> result = new HashMap<>(temporalCredentials.getCredentialResponseCount());
for (TemporalAuthCredentialResponse credential : temporalCredentials.getCredentialResponseList()) { for (TemporalAuthCredentialResponse credential : temporalCredentials.getCredentialResponseList()) {
result.put(credential.getDate(), new AuthCredentialResponse(credential.getAuthCredentialResponse().toByteArray())); result.put(credential.getDate(), new AuthCredentialWithPniResponse(credential.getAuthCredentialResponse().toByteArray()));
} }
info(String.format(Locale.US, "Loaded %d credentials from local storage", result.size())); Log.i(TAG, String.format(Locale.US, "Loaded %d credentials from local storage", result.size()));
return result; return result;
} catch (InvalidProtocolBufferException | InvalidInputException e) { } catch (InvalidProtocolBufferException | InvalidInputException e) {
@ -84,10 +77,10 @@ public final class GroupsV2AuthorizationSignalStoreCache implements GroupsV2Auth
} }
@Override @Override
public void write(@NonNull Map<Integer, AuthCredentialResponse> values) { public void write(@NonNull Map<Long, AuthCredentialWithPniResponse> values) {
TemporalAuthCredentialResponses.Builder builder = TemporalAuthCredentialResponses.newBuilder(); TemporalAuthCredentialResponses.Builder builder = TemporalAuthCredentialResponses.newBuilder();
for (Map.Entry<Integer, AuthCredentialResponse> entry : values.entrySet()) { for (Map.Entry<Long, AuthCredentialWithPniResponse> entry : values.entrySet()) {
builder.addCredentialResponse(TemporalAuthCredentialResponse.newBuilder() builder.addCredentialResponse(TemporalAuthCredentialResponse.newBuilder()
.setDate(entry.getKey()) .setDate(entry.getKey())
.setAuthCredentialResponse(ByteString.copyFrom(entry.getValue().serialize()))); .setAuthCredentialResponse(ByteString.copyFrom(entry.getValue().serialize())));
@ -97,10 +90,6 @@ public final class GroupsV2AuthorizationSignalStoreCache implements GroupsV2Auth
.putBlob(key, builder.build().toByteArray()) .putBlob(key, builder.build().toByteArray())
.commit(); .commit();
info(String.format(Locale.US, "Written %d credentials to local storage", values.size())); Log.i(TAG, String.format(Locale.US, "Written %d credentials to local storage", values.size()));
}
private void info(String message) {
Log.i(TAG, (key.startsWith(PNI_PREFIX) ? "[PNI]" : "[ACI]") + " " + message);
} }
} }

Wyświetl plik

@ -1,25 +0,0 @@
package org.thoughtcrime.securesms.keyvalue
import com.google.protobuf.ByteString
import org.whispersystems.signalservice.api.push.ACI
import org.whispersystems.signalservice.api.push.PNI
import org.whispersystems.signalservice.api.util.UuidUtil
import java.util.UUID
/**
* Helper for dealing with [ServiceId] matching when you only care that either of your
* service ids match but don't care which one.
*/
data class ServiceIds(val aci: ACI, val pni: PNI?) {
private val aciByteString: ByteString by lazy { UuidUtil.toByteString(aci.uuid()) }
private val pniByteString: ByteString? by lazy { pni?.let { UuidUtil.toByteString(it.uuid()) } }
fun matches(uuid: UUID): Boolean {
return uuid == aci.uuid() || uuid == pni?.uuid()
}
fun matches(uuid: ByteString): Boolean {
return uuid == aciByteString || uuid == pniByteString
}
}

Wyświetl plik

@ -265,10 +265,6 @@ public final class SignalStore {
return GroupsV2AuthorizationSignalStoreCache.createAciCache(getStore()); return GroupsV2AuthorizationSignalStoreCache.createAciCache(getStore());
} }
public static @NonNull GroupsV2AuthorizationSignalStoreCache groupsV2PniAuthorizationCache() {
return GroupsV2AuthorizationSignalStoreCache.createPniCache(getStore());
}
public static @NonNull PreferenceDataStore getPreferenceDataStore() { public static @NonNull PreferenceDataStore getPreferenceDataStore() {
return new SignalPreferenceDataStore(getStore()); return new SignalPreferenceDataStore(getStore());
} }

Wyświetl plik

@ -544,13 +544,8 @@ public final class MessageContentProcessor {
throws IOException, GroupChangeBusyException throws IOException, GroupChangeBusyException
{ {
try { try {
ServiceId authServiceId = ServiceId.parseOrNull(content.getDestinationUuid()); long timestamp = groupV2.getSignedGroupChange() != null ? content.getTimestamp() : content.getTimestamp() - 1;
if (authServiceId == null) { GroupManager.updateGroupFromServer(context, groupV2.getMasterKey(), groupV2.getRevision(), timestamp, groupV2.getSignedGroupChange());
warn(content.getTimestamp(), "Group message missing destination uuid, defaulting to ACI");
authServiceId = SignalStore.account().requireAci();
}
long timestamp = groupV2.getSignedGroupChange() != null ? content.getTimestamp() : content.getTimestamp() - 1;
GroupManager.updateGroupFromServer(context, authServiceId, groupV2.getMasterKey(), groupV2.getRevision(), timestamp, groupV2.getSignedGroupChange());
return true; return true;
} catch (GroupNotAMemberException e) { } catch (GroupNotAMemberException e) {
warn(String.valueOf(content.getTimestamp()), "Ignoring message for a group we're not in"); warn(String.valueOf(content.getTimestamp()), "Ignoring message for a group we're not in");

Wyświetl plik

@ -103,9 +103,10 @@ public class ApplicationMigrations {
static final int STORY_DISTRIBUTION_LIST_SYNC = 59; static final int STORY_DISTRIBUTION_LIST_SYNC = 59;
static final int EMOJI_VERSION_7 = 60; static final int EMOJI_VERSION_7 = 60;
static final int MY_STORY_PRIVACY_MODE = 61; static final int MY_STORY_PRIVACY_MODE = 61;
static final int REFRESH_EXPIRING_CREDENTIAL = 62;
} }
public static final int CURRENT_VERSION = 61; public static final int CURRENT_VERSION = 62;
/** /**
* This *must* be called after the {@link JobManager} has been instantiated, but *before* the call * This *must* be called after the {@link JobManager} has been instantiated, but *before* the call
@ -451,6 +452,10 @@ public class ApplicationMigrations {
jobs.put(Version.MY_STORY_PRIVACY_MODE, new SyncDistributionListsMigrationJob()); jobs.put(Version.MY_STORY_PRIVACY_MODE, new SyncDistributionListsMigrationJob());
} }
if (lastSeenVersion < Version.REFRESH_EXPIRING_CREDENTIAL) {
jobs.put(Version.REFRESH_EXPIRING_CREDENTIAL, new AttributesMigrationJob());
}
return jobs; return jobs;
} }

Wyświetl plik

@ -10,6 +10,7 @@ import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction; import androidx.fragment.app.FragmentTransaction;
import androidx.navigation.Navigation; import androidx.navigation.Navigation;
import org.signal.core.util.concurrent.SimpleTask;
import org.thoughtcrime.securesms.ContactSelectionListFragment; import org.thoughtcrime.securesms.ContactSelectionListFragment;
import org.thoughtcrime.securesms.LoggingFragment; import org.thoughtcrime.securesms.LoggingFragment;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
@ -22,8 +23,8 @@ import org.thoughtcrime.securesms.payments.preferences.model.PayeeParcelable;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.ViewUtil;
import org.signal.core.util.concurrent.SimpleTask;
import org.thoughtcrime.securesms.util.navigation.SafeNavigation; import org.thoughtcrime.securesms.util.navigation.SafeNavigation;
import org.whispersystems.signalservice.api.util.ExpiringProfileCredentialUtil;
import java.util.Optional; import java.util.Optional;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -81,7 +82,7 @@ public class PaymentRecipientSelectionFragment extends LoggingFragment implement
} }
@Override @Override
public void onContactDeselected(@NonNull Optional<RecipientId> recipientId, @Nullable String number) { } public void onContactDeselected(@NonNull Optional<RecipientId> recipientId, @Nullable String number) {}
@Override @Override
public void onSelectionChanged() { public void onSelectionChanged() {
@ -98,9 +99,9 @@ public class PaymentRecipientSelectionFragment extends LoggingFragment implement
} }
private void createPaymentOrShowWarningDialog(@NonNull Recipient recipient) { private void createPaymentOrShowWarningDialog(@NonNull Recipient recipient) {
if (recipient.hasProfileKeyCredential()) { if (ExpiringProfileCredentialUtil.isValid(recipient.getExpiringProfileKeyCredential())) {
createPayment(recipient.getId()); createPayment(recipient.getId());
} else { } else {
showWarningDialog(recipient.getId()); showWarningDialog(recipient.getId());
} }
} }

Wyświetl plik

@ -14,7 +14,7 @@ import com.annimon.stream.Stream;
import org.signal.core.util.StringUtil; import org.signal.core.util.StringUtil;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredential; import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.badges.models.Badge; import org.thoughtcrime.securesms.badges.models.Badge;
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto; import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
@ -81,61 +81,61 @@ public class Recipient {
private static final int MAX_MEMBER_NAMES = 10; private static final int MAX_MEMBER_NAMES = 10;
private final RecipientId id; private final RecipientId id;
private final boolean resolving; private final boolean resolving;
private final ServiceId serviceId; private final ServiceId serviceId;
private final PNI pni; private final PNI pni;
private final String username; private final String username;
private final String e164; private final String e164;
private final String email; private final String email;
private final GroupId groupId; private final GroupId groupId;
private final DistributionListId distributionListId; private final DistributionListId distributionListId;
private final List<Recipient> participants; private final List<Recipient> participants;
private final Optional<Long> groupAvatarId; private final Optional<Long> groupAvatarId;
private final boolean isSelf; private final boolean isSelf;
private final boolean blocked; private final boolean blocked;
private final long muteUntil; private final long muteUntil;
private final VibrateState messageVibrate; private final VibrateState messageVibrate;
private final VibrateState callVibrate; private final VibrateState callVibrate;
private final Uri messageRingtone; private final Uri messageRingtone;
private final Uri callRingtone; private final Uri callRingtone;
private final Optional<Integer> defaultSubscriptionId; private final Optional<Integer> defaultSubscriptionId;
private final int expireMessages; private final int expireMessages;
private final RegisteredState registered; private final RegisteredState registered;
private final byte[] profileKey; private final byte[] profileKey;
private final ProfileKeyCredential profileKeyCredential; private final ExpiringProfileKeyCredential expiringProfileKeyCredential;
private final String groupName; private final String groupName;
private final Uri systemContactPhoto; private final Uri systemContactPhoto;
private final String customLabel; private final String customLabel;
private final Uri contactUri; private final Uri contactUri;
private final ProfileName signalProfileName; private final ProfileName signalProfileName;
private final String profileAvatar; private final String profileAvatar;
private final boolean hasProfileImage; private final boolean hasProfileImage;
private final boolean profileSharing; private final boolean profileSharing;
private final long lastProfileFetch; private final long lastProfileFetch;
private final String notificationChannel; private final String notificationChannel;
private final UnidentifiedAccessMode unidentifiedAccessMode; private final UnidentifiedAccessMode unidentifiedAccessMode;
private final boolean forceSmsSelection; private final boolean forceSmsSelection;
private final Capability groupsV1MigrationCapability; private final Capability groupsV1MigrationCapability;
private final Capability senderKeyCapability; private final Capability senderKeyCapability;
private final Capability announcementGroupCapability; private final Capability announcementGroupCapability;
private final Capability changeNumberCapability; private final Capability changeNumberCapability;
private final Capability storiesCapability; private final Capability storiesCapability;
private final Capability giftBadgesCapability; private final Capability giftBadgesCapability;
private final InsightsBannerTier insightsBannerTier; private final InsightsBannerTier insightsBannerTier;
private final byte[] storageId; private final byte[] storageId;
private final MentionSetting mentionSetting; private final MentionSetting mentionSetting;
private final ChatWallpaper wallpaper; private final ChatWallpaper wallpaper;
private final ChatColors chatColors; private final ChatColors chatColors;
private final AvatarColor avatarColor; private final AvatarColor avatarColor;
private final String about; private final String about;
private final String aboutEmoji; private final String aboutEmoji;
private final ProfileName systemProfileName; private final ProfileName systemProfileName;
private final String systemContactName; private final String systemContactName;
private final Optional<Extras> extras; private final Optional<Extras> extras;
private final boolean hasGroupsInCommon; private final boolean hasGroupsInCommon;
private final List<Badge> badges; private final List<Badge> badges;
private final boolean isReleaseNotesRecipient; private final boolean isReleaseNotesRecipient;
/** /**
* Returns a {@link LiveRecipient}, which contains a {@link Recipient} that may or may not be * Returns a {@link LiveRecipient}, which contains a {@link Recipient} that may or may not be
@ -358,119 +358,119 @@ public class Recipient {
} }
Recipient(@NonNull RecipientId id) { Recipient(@NonNull RecipientId id) {
this.id = id; this.id = id;
this.resolving = true; this.resolving = true;
this.serviceId = null; this.serviceId = null;
this.pni = null; this.pni = null;
this.username = null; this.username = null;
this.e164 = null; this.e164 = null;
this.email = null; this.email = null;
this.groupId = null; this.groupId = null;
this.distributionListId = null; this.distributionListId = null;
this.participants = Collections.emptyList(); this.participants = Collections.emptyList();
this.groupAvatarId = Optional.empty(); this.groupAvatarId = Optional.empty();
this.isSelf = false; this.isSelf = false;
this.blocked = false; this.blocked = false;
this.muteUntil = 0; this.muteUntil = 0;
this.messageVibrate = VibrateState.DEFAULT; this.messageVibrate = VibrateState.DEFAULT;
this.callVibrate = VibrateState.DEFAULT; this.callVibrate = VibrateState.DEFAULT;
this.messageRingtone = null; this.messageRingtone = null;
this.callRingtone = null; this.callRingtone = null;
this.insightsBannerTier = InsightsBannerTier.TIER_TWO; this.insightsBannerTier = InsightsBannerTier.TIER_TWO;
this.defaultSubscriptionId = Optional.empty(); this.defaultSubscriptionId = Optional.empty();
this.expireMessages = 0; this.expireMessages = 0;
this.registered = RegisteredState.UNKNOWN; this.registered = RegisteredState.UNKNOWN;
this.profileKey = null; this.profileKey = null;
this.profileKeyCredential = null; this.expiringProfileKeyCredential = null;
this.groupName = null; this.groupName = null;
this.systemContactPhoto = null; this.systemContactPhoto = null;
this.customLabel = null; this.customLabel = null;
this.contactUri = null; this.contactUri = null;
this.signalProfileName = ProfileName.EMPTY; this.signalProfileName = ProfileName.EMPTY;
this.profileAvatar = null; this.profileAvatar = null;
this.hasProfileImage = false; this.hasProfileImage = false;
this.profileSharing = false; this.profileSharing = false;
this.lastProfileFetch = 0; this.lastProfileFetch = 0;
this.notificationChannel = null; this.notificationChannel = null;
this.unidentifiedAccessMode = UnidentifiedAccessMode.DISABLED; this.unidentifiedAccessMode = UnidentifiedAccessMode.DISABLED;
this.forceSmsSelection = false; this.forceSmsSelection = false;
this.groupsV1MigrationCapability = Capability.UNKNOWN; this.groupsV1MigrationCapability = Capability.UNKNOWN;
this.senderKeyCapability = Capability.UNKNOWN; this.senderKeyCapability = Capability.UNKNOWN;
this.announcementGroupCapability = Capability.UNKNOWN; this.announcementGroupCapability = Capability.UNKNOWN;
this.changeNumberCapability = Capability.UNKNOWN; this.changeNumberCapability = Capability.UNKNOWN;
this.storiesCapability = Capability.UNKNOWN; this.storiesCapability = Capability.UNKNOWN;
this.giftBadgesCapability = Capability.UNKNOWN; this.giftBadgesCapability = Capability.UNKNOWN;
this.storageId = null; this.storageId = null;
this.mentionSetting = MentionSetting.ALWAYS_NOTIFY; this.mentionSetting = MentionSetting.ALWAYS_NOTIFY;
this.wallpaper = null; this.wallpaper = null;
this.chatColors = null; this.chatColors = null;
this.avatarColor = AvatarColor.UNKNOWN; this.avatarColor = AvatarColor.UNKNOWN;
this.about = null; this.about = null;
this.aboutEmoji = null; this.aboutEmoji = null;
this.systemProfileName = ProfileName.EMPTY; this.systemProfileName = ProfileName.EMPTY;
this.systemContactName = null; this.systemContactName = null;
this.extras = Optional.empty(); this.extras = Optional.empty();
this.hasGroupsInCommon = false; this.hasGroupsInCommon = false;
this.badges = Collections.emptyList(); this.badges = Collections.emptyList();
this.isReleaseNotesRecipient = false; this.isReleaseNotesRecipient = false;
} }
public Recipient(@NonNull RecipientId id, @NonNull RecipientDetails details, boolean resolved) { public Recipient(@NonNull RecipientId id, @NonNull RecipientDetails details, boolean resolved) {
this.id = id; this.id = id;
this.resolving = !resolved; this.resolving = !resolved;
this.serviceId = details.serviceId; this.serviceId = details.serviceId;
this.pni = details.pni; this.pni = details.pni;
this.username = details.username; this.username = details.username;
this.e164 = details.e164; this.e164 = details.e164;
this.email = details.email; this.email = details.email;
this.groupId = details.groupId; this.groupId = details.groupId;
this.distributionListId = details.distributionListId; this.distributionListId = details.distributionListId;
this.participants = details.participants; this.participants = details.participants;
this.groupAvatarId = details.groupAvatarId; this.groupAvatarId = details.groupAvatarId;
this.isSelf = details.isSelf; this.isSelf = details.isSelf;
this.blocked = details.blocked; this.blocked = details.blocked;
this.muteUntil = details.mutedUntil; this.muteUntil = details.mutedUntil;
this.messageVibrate = details.messageVibrateState; this.messageVibrate = details.messageVibrateState;
this.callVibrate = details.callVibrateState; this.callVibrate = details.callVibrateState;
this.messageRingtone = details.messageRingtone; this.messageRingtone = details.messageRingtone;
this.callRingtone = details.callRingtone; this.callRingtone = details.callRingtone;
this.insightsBannerTier = details.insightsBannerTier; this.insightsBannerTier = details.insightsBannerTier;
this.defaultSubscriptionId = details.defaultSubscriptionId; this.defaultSubscriptionId = details.defaultSubscriptionId;
this.expireMessages = details.expireMessages; this.expireMessages = details.expireMessages;
this.registered = details.registered; this.registered = details.registered;
this.profileKey = details.profileKey; this.profileKey = details.profileKey;
this.profileKeyCredential = details.profileKeyCredential; this.expiringProfileKeyCredential = details.expiringProfileKeyCredential;
this.groupName = details.groupName; this.groupName = details.groupName;
this.systemContactPhoto = details.systemContactPhoto; this.systemContactPhoto = details.systemContactPhoto;
this.customLabel = details.customLabel; this.customLabel = details.customLabel;
this.contactUri = details.contactUri; this.contactUri = details.contactUri;
this.signalProfileName = details.profileName; this.signalProfileName = details.profileName;
this.profileAvatar = details.profileAvatar; this.profileAvatar = details.profileAvatar;
this.hasProfileImage = details.hasProfileImage; this.hasProfileImage = details.hasProfileImage;
this.profileSharing = details.profileSharing; this.profileSharing = details.profileSharing;
this.lastProfileFetch = details.lastProfileFetch; this.lastProfileFetch = details.lastProfileFetch;
this.notificationChannel = details.notificationChannel; this.notificationChannel = details.notificationChannel;
this.unidentifiedAccessMode = details.unidentifiedAccessMode; this.unidentifiedAccessMode = details.unidentifiedAccessMode;
this.forceSmsSelection = details.forceSmsSelection; this.forceSmsSelection = details.forceSmsSelection;
this.groupsV1MigrationCapability = details.groupsV1MigrationCapability; this.groupsV1MigrationCapability = details.groupsV1MigrationCapability;
this.senderKeyCapability = details.senderKeyCapability; this.senderKeyCapability = details.senderKeyCapability;
this.announcementGroupCapability = details.announcementGroupCapability; this.announcementGroupCapability = details.announcementGroupCapability;
this.changeNumberCapability = details.changeNumberCapability; this.changeNumberCapability = details.changeNumberCapability;
this.storiesCapability = details.storiesCapability; this.storiesCapability = details.storiesCapability;
this.giftBadgesCapability = details.giftBadgesCapability; this.giftBadgesCapability = details.giftBadgesCapability;
this.storageId = details.storageId; this.storageId = details.storageId;
this.mentionSetting = details.mentionSetting; this.mentionSetting = details.mentionSetting;
this.wallpaper = details.wallpaper; this.wallpaper = details.wallpaper;
this.chatColors = details.chatColors; this.chatColors = details.chatColors;
this.avatarColor = details.avatarColor; this.avatarColor = details.avatarColor;
this.about = details.about; this.about = details.about;
this.aboutEmoji = details.aboutEmoji; this.aboutEmoji = details.aboutEmoji;
this.systemProfileName = details.systemProfileName; this.systemProfileName = details.systemProfileName;
this.systemContactName = details.systemContactName; this.systemContactName = details.systemContactName;
this.extras = details.extras; this.extras = details.extras;
this.hasGroupsInCommon = details.hasGroupsInCommon; this.hasGroupsInCommon = details.hasGroupsInCommon;
this.badges = details.badges; this.badges = details.badges;
this.isReleaseNotesRecipient = details.isReleaseChannel; this.isReleaseNotesRecipient = details.isReleaseChannel;
} }
public @NonNull RecipientId getId() { public @NonNull RecipientId getId() {
@ -1022,12 +1022,8 @@ public class Recipient {
return profileKey; return profileKey;
} }
public @Nullable ProfileKeyCredential getProfileKeyCredential() { public @Nullable ExpiringProfileKeyCredential getExpiringProfileKeyCredential() {
return profileKeyCredential; return expiringProfileKeyCredential;
}
public boolean hasProfileKeyCredential() {
return profileKeyCredential != null;
} }
public @Nullable byte[] getStorageServiceId() { public @Nullable byte[] getStorageServiceId() {
@ -1285,7 +1281,7 @@ public class Recipient {
Objects.equals(defaultSubscriptionId, other.defaultSubscriptionId) && Objects.equals(defaultSubscriptionId, other.defaultSubscriptionId) &&
registered == other.registered && registered == other.registered &&
Arrays.equals(profileKey, other.profileKey) && Arrays.equals(profileKey, other.profileKey) &&
Objects.equals(profileKeyCredential, other.profileKeyCredential) && Objects.equals(expiringProfileKeyCredential, other.expiringProfileKeyCredential) &&
Objects.equals(groupName, other.groupName) && Objects.equals(groupName, other.groupName) &&
Objects.equals(systemContactPhoto, other.systemContactPhoto) && Objects.equals(systemContactPhoto, other.systemContactPhoto) &&
Objects.equals(customLabel, other.customLabel) && Objects.equals(customLabel, other.customLabel) &&

Wyświetl plik

@ -6,7 +6,7 @@ import android.net.Uri;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredential; import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
import org.thoughtcrime.securesms.badges.models.Badge; import org.thoughtcrime.securesms.badges.models.Badge;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor; import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
import org.thoughtcrime.securesms.conversation.colors.ChatColors; import org.thoughtcrime.securesms.conversation.colors.ChatColors;
@ -33,60 +33,60 @@ import java.util.Optional;
public class RecipientDetails { public class RecipientDetails {
final ServiceId serviceId; final ServiceId serviceId;
final PNI pni; final PNI pni;
final String username; final String username;
final String e164; final String e164;
final String email; final String email;
final GroupId groupId; final GroupId groupId;
final DistributionListId distributionListId; final DistributionListId distributionListId;
final String groupName; final String groupName;
final String systemContactName; final String systemContactName;
final String customLabel; final String customLabel;
final Uri systemContactPhoto; final Uri systemContactPhoto;
final Uri contactUri; final Uri contactUri;
final Optional<Long> groupAvatarId; final Optional<Long> groupAvatarId;
final Uri messageRingtone; final Uri messageRingtone;
final Uri callRingtone; final Uri callRingtone;
final long mutedUntil; final long mutedUntil;
final VibrateState messageVibrateState; final VibrateState messageVibrateState;
final VibrateState callVibrateState; final VibrateState callVibrateState;
final boolean blocked; final boolean blocked;
final int expireMessages; final int expireMessages;
final List<Recipient> participants; final List<Recipient> participants;
final ProfileName profileName; final ProfileName profileName;
final Optional<Integer> defaultSubscriptionId; final Optional<Integer> defaultSubscriptionId;
final RegisteredState registered; final RegisteredState registered;
final byte[] profileKey; final byte[] profileKey;
final ProfileKeyCredential profileKeyCredential; final ExpiringProfileKeyCredential expiringProfileKeyCredential;
final String profileAvatar; final String profileAvatar;
final boolean hasProfileImage; final boolean hasProfileImage;
final boolean profileSharing; final boolean profileSharing;
final long lastProfileFetch; final long lastProfileFetch;
final boolean systemContact; final boolean systemContact;
final boolean isSelf; final boolean isSelf;
final String notificationChannel; final String notificationChannel;
final UnidentifiedAccessMode unidentifiedAccessMode; final UnidentifiedAccessMode unidentifiedAccessMode;
final boolean forceSmsSelection; final boolean forceSmsSelection;
final Recipient.Capability groupsV1MigrationCapability; final Recipient.Capability groupsV1MigrationCapability;
final Recipient.Capability senderKeyCapability; final Recipient.Capability senderKeyCapability;
final Recipient.Capability announcementGroupCapability; final Recipient.Capability announcementGroupCapability;
final Recipient.Capability changeNumberCapability; final Recipient.Capability changeNumberCapability;
final Recipient.Capability storiesCapability; final Recipient.Capability storiesCapability;
final Recipient.Capability giftBadgesCapability; final Recipient.Capability giftBadgesCapability;
final InsightsBannerTier insightsBannerTier; final InsightsBannerTier insightsBannerTier;
final byte[] storageId; final byte[] storageId;
final MentionSetting mentionSetting; final MentionSetting mentionSetting;
final ChatWallpaper wallpaper; final ChatWallpaper wallpaper;
final ChatColors chatColors; final ChatColors chatColors;
final AvatarColor avatarColor; final AvatarColor avatarColor;
final String about; final String about;
final String aboutEmoji; final String aboutEmoji;
final ProfileName systemProfileName; final ProfileName systemProfileName;
final Optional<Recipient.Extras> extras; final Optional<Recipient.Extras> extras;
final boolean hasGroupsInCommon; final boolean hasGroupsInCommon;
final List<Badge> badges; final List<Badge> badges;
final boolean isReleaseChannel; final boolean isReleaseChannel;
public RecipientDetails(@Nullable String groupName, public RecipientDetails(@Nullable String groupName,
@Nullable String systemContactName, @Nullable String systemContactName,
@ -98,117 +98,117 @@ public class RecipientDetails {
@Nullable List<Recipient> participants, @Nullable List<Recipient> participants,
boolean isReleaseChannel) boolean isReleaseChannel)
{ {
this.groupAvatarId = groupAvatarId; this.groupAvatarId = groupAvatarId;
this.systemContactPhoto = Util.uri(record.getSystemContactPhotoUri()); this.systemContactPhoto = Util.uri(record.getSystemContactPhotoUri());
this.customLabel = record.getSystemPhoneLabel(); this.customLabel = record.getSystemPhoneLabel();
this.contactUri = Util.uri(record.getSystemContactUri()); this.contactUri = Util.uri(record.getSystemContactUri());
this.serviceId = record.getServiceId(); this.serviceId = record.getServiceId();
this.pni = record.getPni(); this.pni = record.getPni();
this.username = record.getUsername(); this.username = record.getUsername();
this.e164 = record.getE164(); this.e164 = record.getE164();
this.email = record.getEmail(); this.email = record.getEmail();
this.groupId = record.getGroupId(); this.groupId = record.getGroupId();
this.distributionListId = record.getDistributionListId(); this.distributionListId = record.getDistributionListId();
this.messageRingtone = record.getMessageRingtone(); this.messageRingtone = record.getMessageRingtone();
this.callRingtone = record.getCallRingtone(); this.callRingtone = record.getCallRingtone();
this.mutedUntil = record.getMuteUntil(); this.mutedUntil = record.getMuteUntil();
this.messageVibrateState = record.getMessageVibrateState(); this.messageVibrateState = record.getMessageVibrateState();
this.callVibrateState = record.getCallVibrateState(); this.callVibrateState = record.getCallVibrateState();
this.blocked = record.isBlocked(); this.blocked = record.isBlocked();
this.expireMessages = record.getExpireMessages(); this.expireMessages = record.getExpireMessages();
this.participants = participants == null ? new LinkedList<>() : participants; this.participants = participants == null ? new LinkedList<>() : participants;
this.profileName = record.getProfileName(); this.profileName = record.getProfileName();
this.defaultSubscriptionId = record.getDefaultSubscriptionId(); this.defaultSubscriptionId = record.getDefaultSubscriptionId();
this.registered = registeredState; this.registered = registeredState;
this.profileKey = record.getProfileKey(); this.profileKey = record.getProfileKey();
this.profileKeyCredential = record.getProfileKeyCredential(); this.expiringProfileKeyCredential = record.getExpiringProfileKeyCredential();
this.profileAvatar = record.getProfileAvatar(); this.profileAvatar = record.getProfileAvatar();
this.hasProfileImage = record.hasProfileImage(); this.hasProfileImage = record.hasProfileImage();
this.profileSharing = record.isProfileSharing(); this.profileSharing = record.isProfileSharing();
this.lastProfileFetch = record.getLastProfileFetch(); this.lastProfileFetch = record.getLastProfileFetch();
this.systemContact = systemContact; this.systemContact = systemContact;
this.isSelf = isSelf; this.isSelf = isSelf;
this.notificationChannel = record.getNotificationChannel(); this.notificationChannel = record.getNotificationChannel();
this.unidentifiedAccessMode = record.getUnidentifiedAccessMode(); this.unidentifiedAccessMode = record.getUnidentifiedAccessMode();
this.forceSmsSelection = record.isForceSmsSelection(); this.forceSmsSelection = record.isForceSmsSelection();
this.groupsV1MigrationCapability = record.getGroupsV1MigrationCapability(); this.groupsV1MigrationCapability = record.getGroupsV1MigrationCapability();
this.senderKeyCapability = record.getSenderKeyCapability(); this.senderKeyCapability = record.getSenderKeyCapability();
this.announcementGroupCapability = record.getAnnouncementGroupCapability(); this.announcementGroupCapability = record.getAnnouncementGroupCapability();
this.changeNumberCapability = record.getChangeNumberCapability(); this.changeNumberCapability = record.getChangeNumberCapability();
this.storiesCapability = record.getStoriesCapability(); this.storiesCapability = record.getStoriesCapability();
this.giftBadgesCapability = record.getGiftBadgesCapability(); this.giftBadgesCapability = record.getGiftBadgesCapability();
this.insightsBannerTier = record.getInsightsBannerTier(); this.insightsBannerTier = record.getInsightsBannerTier();
this.storageId = record.getStorageId(); this.storageId = record.getStorageId();
this.mentionSetting = record.getMentionSetting(); this.mentionSetting = record.getMentionSetting();
this.wallpaper = record.getWallpaper(); this.wallpaper = record.getWallpaper();
this.chatColors = record.getChatColors(); this.chatColors = record.getChatColors();
this.avatarColor = record.getAvatarColor(); this.avatarColor = record.getAvatarColor();
this.about = record.getAbout(); this.about = record.getAbout();
this.aboutEmoji = record.getAboutEmoji(); this.aboutEmoji = record.getAboutEmoji();
this.systemProfileName = record.getSystemProfileName(); this.systemProfileName = record.getSystemProfileName();
this.groupName = groupName; this.groupName = groupName;
this.systemContactName = systemContactName; this.systemContactName = systemContactName;
this.extras = Optional.ofNullable(record.getExtras()); this.extras = Optional.ofNullable(record.getExtras());
this.hasGroupsInCommon = record.hasGroupsInCommon(); this.hasGroupsInCommon = record.hasGroupsInCommon();
this.badges = record.getBadges(); this.badges = record.getBadges();
this.isReleaseChannel = isReleaseChannel; this.isReleaseChannel = isReleaseChannel;
} }
private RecipientDetails() { private RecipientDetails() {
this.groupAvatarId = null; this.groupAvatarId = null;
this.systemContactPhoto = null; this.systemContactPhoto = null;
this.customLabel = null; this.customLabel = null;
this.contactUri = null; this.contactUri = null;
this.serviceId = null; this.serviceId = null;
this.pni = null; this.pni = null;
this.username = null; this.username = null;
this.e164 = null; this.e164 = null;
this.email = null; this.email = null;
this.groupId = null; this.groupId = null;
this.distributionListId = null; this.distributionListId = null;
this.messageRingtone = null; this.messageRingtone = null;
this.callRingtone = null; this.callRingtone = null;
this.mutedUntil = 0; this.mutedUntil = 0;
this.messageVibrateState = VibrateState.DEFAULT; this.messageVibrateState = VibrateState.DEFAULT;
this.callVibrateState = VibrateState.DEFAULT; this.callVibrateState = VibrateState.DEFAULT;
this.blocked = false; this.blocked = false;
this.expireMessages = 0; this.expireMessages = 0;
this.participants = new LinkedList<>(); this.participants = new LinkedList<>();
this.profileName = ProfileName.EMPTY; this.profileName = ProfileName.EMPTY;
this.insightsBannerTier = InsightsBannerTier.TIER_TWO; this.insightsBannerTier = InsightsBannerTier.TIER_TWO;
this.defaultSubscriptionId = Optional.empty(); this.defaultSubscriptionId = Optional.empty();
this.registered = RegisteredState.UNKNOWN; this.registered = RegisteredState.UNKNOWN;
this.profileKey = null; this.profileKey = null;
this.profileKeyCredential = null; this.expiringProfileKeyCredential = null;
this.profileAvatar = null; this.profileAvatar = null;
this.hasProfileImage = false; this.hasProfileImage = false;
this.profileSharing = false; this.profileSharing = false;
this.lastProfileFetch = 0; this.lastProfileFetch = 0;
this.systemContact = true; this.systemContact = true;
this.isSelf = false; this.isSelf = false;
this.notificationChannel = null; this.notificationChannel = null;
this.unidentifiedAccessMode = UnidentifiedAccessMode.UNKNOWN; this.unidentifiedAccessMode = UnidentifiedAccessMode.UNKNOWN;
this.forceSmsSelection = false; this.forceSmsSelection = false;
this.groupName = null; this.groupName = null;
this.groupsV1MigrationCapability = Recipient.Capability.UNKNOWN; this.groupsV1MigrationCapability = Recipient.Capability.UNKNOWN;
this.senderKeyCapability = Recipient.Capability.UNKNOWN; this.senderKeyCapability = Recipient.Capability.UNKNOWN;
this.announcementGroupCapability = Recipient.Capability.UNKNOWN; this.announcementGroupCapability = Recipient.Capability.UNKNOWN;
this.changeNumberCapability = Recipient.Capability.UNKNOWN; this.changeNumberCapability = Recipient.Capability.UNKNOWN;
this.storiesCapability = Recipient.Capability.UNKNOWN; this.storiesCapability = Recipient.Capability.UNKNOWN;
this.giftBadgesCapability = Recipient.Capability.UNKNOWN; this.giftBadgesCapability = Recipient.Capability.UNKNOWN;
this.storageId = null; this.storageId = null;
this.mentionSetting = MentionSetting.ALWAYS_NOTIFY; this.mentionSetting = MentionSetting.ALWAYS_NOTIFY;
this.wallpaper = null; this.wallpaper = null;
this.chatColors = null; this.chatColors = null;
this.avatarColor = AvatarColor.UNKNOWN; this.avatarColor = AvatarColor.UNKNOWN;
this.about = null; this.about = null;
this.aboutEmoji = null; this.aboutEmoji = null;
this.systemProfileName = ProfileName.EMPTY; this.systemProfileName = ProfileName.EMPTY;
this.systemContactName = null; this.systemContactName = null;
this.extras = Optional.empty(); this.extras = Optional.empty();
this.hasGroupsInCommon = false; this.hasGroupsInCommon = false;
this.badges = Collections.emptyList(); this.badges = Collections.emptyList();
this.isReleaseChannel = false; this.isReleaseChannel = false;
} }
public static @NonNull RecipientDetails forIndividual(@NonNull Context context, @NonNull RecipientRecord settings) { public static @NonNull RecipientDetails forIndividual(@NonNull Context context, @NonNull RecipientRecord settings) {

Wyświetl plik

@ -85,7 +85,6 @@ public final class FeatureFlags {
private static final String GROUP_CALL_RINGING = "android.calling.groupCallRinging"; private static final String GROUP_CALL_RINGING = "android.calling.groupCallRinging";
private static final String DONOR_BADGES = "android.donorBadges.6"; private static final String DONOR_BADGES = "android.donorBadges.6";
private static final String DONOR_BADGES_DISPLAY = "android.donorBadges.display.4"; private static final String DONOR_BADGES_DISPLAY = "android.donorBadges.display.4";
private static final String CDSH = "android.cdsh";
private static final String STORIES = "android.stories.2"; private static final String STORIES = "android.stories.2";
private static final String STORIES_TEXT_FUNCTIONS = "android.stories.text.functions"; private static final String STORIES_TEXT_FUNCTIONS = "android.stories.text.functions";
private static final String HARDWARE_AEC_BLOCKLIST_MODELS = "android.calling.hardwareAecBlockList"; private static final String HARDWARE_AEC_BLOCKLIST_MODELS = "android.calling.hardwareAecBlockList";
@ -134,7 +133,6 @@ public final class FeatureFlags {
SUGGEST_SMS_BLACKLIST, SUGGEST_SMS_BLACKLIST,
MAX_GROUP_CALL_RING_SIZE, MAX_GROUP_CALL_RING_SIZE,
GROUP_CALL_RINGING, GROUP_CALL_RINGING,
CDSH,
SENDER_KEY_MAX_AGE, SENDER_KEY_MAX_AGE,
DONOR_BADGES, DONOR_BADGES,
DONOR_BADGES_DISPLAY, DONOR_BADGES_DISPLAY,
@ -199,7 +197,6 @@ public final class FeatureFlags {
SENDER_KEY, SENDER_KEY,
MAX_GROUP_CALL_RING_SIZE, MAX_GROUP_CALL_RING_SIZE,
GROUP_CALL_RINGING, GROUP_CALL_RINGING,
CDSH,
SENDER_KEY_MAX_AGE, SENDER_KEY_MAX_AGE,
DONOR_BADGES_DISPLAY, DONOR_BADGES_DISPLAY,
DONATE_MEGAPHONE, DONATE_MEGAPHONE,
@ -470,10 +467,6 @@ public final class FeatureFlags {
return getBoolean(DONOR_BADGES_DISPLAY, true); return getBoolean(DONOR_BADGES_DISPLAY, true);
} }
public static boolean cdsh() {
return Environment.IS_STAGING && getBoolean(CDSH, false);
}
/** A comma-separated list of models that should *not* use hardware AEC for calling. */ /** A comma-separated list of models that should *not* use hardware AEC for calling. */
public static @NonNull String hardwareAecBlocklistModels() { public static @NonNull String hardwareAecBlocklistModels() {
return getString(HARDWARE_AEC_BLOCKLIST_MODELS, ""); return getString(HARDWARE_AEC_BLOCKLIST_MODELS, "");

Wyświetl plik

@ -44,6 +44,13 @@ fun Long.toLocalDateTime(zoneId: ZoneId = ZoneId.systemDefault()): LocalDateTime
return LocalDateTime.ofInstant(Instant.ofEpochMilli(this), zoneId) return LocalDateTime.ofInstant(Instant.ofEpochMilli(this), zoneId)
} }
/**
* Convert milliseconds to local date time with provided [zoneId].
*/
fun Instant.toLocalDateTime(zoneId: ZoneId = ZoneId.systemDefault()): LocalDateTime {
return LocalDateTime.ofInstant(this, zoneId)
}
/** /**
* Converts milliseconds to local time with provided [zoneId]. * Converts milliseconds to local time with provided [zoneId].
*/ */

Wyświetl plik

@ -12,6 +12,7 @@ import org.signal.libsignal.protocol.IdentityKeyPair;
import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.InvalidKeyException;
import org.signal.libsignal.protocol.util.Pair; import org.signal.libsignal.protocol.util.Pair;
import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.thoughtcrime.securesms.badges.models.Badge; import org.thoughtcrime.securesms.badges.models.Badge;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
@ -282,6 +283,35 @@ public final class ProfileUtil {
Recipient.self().getBadges()); Recipient.self().getBadges());
} }
/**
* Attempts to update just the expiring profile key credential with a new one. If unable, an empty optional is returned.
*
* Note: It will try to find missing profile key credentials from the server and persist locally.
*/
public static Optional<ExpiringProfileKeyCredential> updateExpiringProfileKeyCredential(@NonNull Recipient recipient) throws IOException {
ProfileKey profileKey = ProfileKeyUtil.profileKeyOrNull(recipient.getProfileKey());
if (profileKey != null) {
Log.i(TAG, String.format("Updating profile key credential on recipient %s, fetching", recipient.getId()));
Optional<ExpiringProfileKeyCredential> profileKeyCredentialOptional = ApplicationDependencies.getSignalServiceAccountManager()
.resolveProfileKeyCredential(recipient.requireServiceId(), profileKey, Locale.getDefault());
if (profileKeyCredentialOptional.isPresent()) {
boolean updatedProfileKey = SignalDatabase.recipients().setProfileKeyCredential(recipient.getId(), profileKey, profileKeyCredentialOptional.get());
if (!updatedProfileKey) {
Log.w(TAG, String.format("Failed to update the profile key credential on recipient %s", recipient.getId()));
} else {
Log.i(TAG, String.format("Got new profile key credential for recipient %s", recipient.getId()));
return profileKeyCredentialOptional;
}
}
}
return Optional.empty();
}
private static void uploadProfile(@NonNull ProfileName profileName, private static void uploadProfile(@NonNull ProfileName profileName,
@Nullable String about, @Nullable String about,
@Nullable String aboutEmoji, @Nullable String aboutEmoji,

Wyświetl plik

@ -52,7 +52,7 @@ message DecryptedGroupV2Context {
} }
message TemporalAuthCredentialResponse { message TemporalAuthCredentialResponse {
int32 date = 1; int64 date = 1;
bytes authCredentialResponse = 2; bytes authCredentialResponse = 2;
} }
@ -118,9 +118,9 @@ message GroupCallUpdateDetails {
bool isCallFull = 5; bool isCallFull = 5;
} }
message ProfileKeyCredentialColumnData { message ExpiringProfileKeyCredentialColumnData {
bytes profileKey = 1; bytes profileKey = 1;
bytes profileKeyCredential = 2; bytes expiringProfileKeyCredential = 2;
} }
message DeviceLastResetTime { message DeviceLastResetTime {

Wyświetl plik

@ -17,6 +17,7 @@ import org.thoughtcrime.securesms.database.LocalMetricsDatabase
import org.thoughtcrime.securesms.database.LogDatabase import org.thoughtcrime.securesms.database.LogDatabase
import org.thoughtcrime.securesms.database.MegaphoneDatabase import org.thoughtcrime.securesms.database.MegaphoneDatabase
import org.thoughtcrime.securesms.database.MessageBitmaskColumnTransformer import org.thoughtcrime.securesms.database.MessageBitmaskColumnTransformer
import org.thoughtcrime.securesms.database.ProfileKeyCredentialTransformer
import org.thoughtcrime.securesms.database.QueryMonitor import org.thoughtcrime.securesms.database.QueryMonitor
import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.TimestampTransformer import org.thoughtcrime.securesms.database.TimestampTransformer
@ -62,7 +63,7 @@ class SpinnerApplicationContext : ApplicationContext() {
linkedMapOf( linkedMapOf(
"signal" to DatabaseConfig( "signal" to DatabaseConfig(
db = SignalDatabase.rawDatabase, db = SignalDatabase.rawDatabase,
columnTransformers = listOf(MessageBitmaskColumnTransformer, GV2Transformer, GV2UpdateTransformer, IsStoryTransformer, TimestampTransformer) columnTransformers = listOf(MessageBitmaskColumnTransformer, GV2Transformer, GV2UpdateTransformer, IsStoryTransformer, TimestampTransformer, ProfileKeyCredentialTransformer)
), ),
"jobmanager" to DatabaseConfig(db = JobDatabase.getInstance(this).sqlCipherDatabase), "jobmanager" to DatabaseConfig(db = JobDatabase.getInstance(this).sqlCipherDatabase),
"keyvalue" to DatabaseConfig(db = KeyValueDatabase.getInstance(this).sqlCipherDatabase), "keyvalue" to DatabaseConfig(db = KeyValueDatabase.getInstance(this).sqlCipherDatabase),

Wyświetl plik

@ -7,7 +7,9 @@ import org.signal.spinner.ColumnTransformer
import org.signal.spinner.DefaultColumnTransformer import org.signal.spinner.DefaultColumnTransformer
import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.UpdateDescription import org.thoughtcrime.securesms.database.model.UpdateDescription
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.util.Base64
object GV2UpdateTransformer : ColumnTransformer { object GV2UpdateTransformer : ColumnTransformer {
override fun matches(tableName: String?, columnName: String): Boolean { override fun matches(tableName: String?, columnName: String): Boolean {
@ -24,8 +26,11 @@ object GV2UpdateTransformer : ColumnTransformer {
val body: String? = CursorUtil.requireString(cursor, MmsSmsColumns.BODY) val body: String? = CursorUtil.requireString(cursor, MmsSmsColumns.BODY)
return if (MmsSmsColumns.Types.isGroupV2(type) && MmsSmsColumns.Types.isGroupUpdate(type) && body != null) { return if (MmsSmsColumns.Types.isGroupV2(type) && MmsSmsColumns.Types.isGroupUpdate(type) && body != null) {
val decoded = Base64.decode(body)
val decryptedGroupV2Context = DecryptedGroupV2Context.parseFrom(decoded)
val gv2ChangeDescription: UpdateDescription = MessageRecord.getGv2ChangeDescription(ApplicationDependencies.getApplication(), body, null) val gv2ChangeDescription: UpdateDescription = MessageRecord.getGv2ChangeDescription(ApplicationDependencies.getApplication(), body, null)
gv2ChangeDescription.spannable.toString()
"${gv2ChangeDescription.spannable}<br><br>${decryptedGroupV2Context.change}"
} else { } else {
body ?: "" body ?: ""
} }

Wyświetl plik

@ -0,0 +1,33 @@
package org.thoughtcrime.securesms.database
import android.database.Cursor
import org.signal.core.util.Hex
import org.signal.core.util.requireString
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential
import org.signal.spinner.ColumnTransformer
import org.signal.spinner.DefaultColumnTransformer
import org.thoughtcrime.securesms.database.model.databaseprotos.ExpiringProfileKeyCredentialColumnData
import org.thoughtcrime.securesms.util.Base64
import org.thoughtcrime.securesms.util.toLocalDateTime
import java.security.MessageDigest
object ProfileKeyCredentialTransformer : ColumnTransformer {
override fun matches(tableName: String?, columnName: String): Boolean {
return columnName == RecipientDatabase.EXPIRING_PROFILE_KEY_CREDENTIAL && (tableName == null || tableName == RecipientDatabase.TABLE_NAME)
}
override fun transform(tableName: String?, columnName: String, cursor: Cursor): String {
val columnDataString = cursor.requireString(RecipientDatabase.EXPIRING_PROFILE_KEY_CREDENTIAL) ?: return DefaultColumnTransformer.transform(tableName, columnName, cursor)
val columnDataBytes = Base64.decode(columnDataString)
val columnData = ExpiringProfileKeyCredentialColumnData.parseFrom(columnDataBytes)
val credential = ExpiringProfileKeyCredential(columnData.expiringProfileKeyCredential.toByteArray())
return """
Credential: ${Hex.toStringCondensed(MessageDigest.getInstance("SHA-256").digest(credential.serialize()))}
Expires: ${credential.expirationTime.toLocalDateTime()}
Matching Profile Key:
${Base64.encodeBytes(columnData.profileKey.toByteArray())}
""".trimIndent().replace("\n", "<br>")
}
}

Wyświetl plik

@ -21,7 +21,6 @@ import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations
import org.whispersystems.signalservice.api.push.DistributionId import org.whispersystems.signalservice.api.push.DistributionId
import org.whispersystems.signalservice.api.push.ServiceId import org.whispersystems.signalservice.api.push.ServiceId
import java.util.Optional import java.util.Optional
import java.util.UUID
fun DecryptedGroupChange.Builder.setNewDescription(description: String) { fun DecryptedGroupChange.Builder.setNewDescription(description: String) {
newDescription = DecryptedString.newBuilder().setValue(description).build() newDescription = DecryptedString.newBuilder().setValue(description).build()
@ -118,11 +117,10 @@ class GroupStateTestData(private val masterKey: GroupMasterKey, private val grou
pendingMembers: List<DecryptedPendingMember> = emptyList(), pendingMembers: List<DecryptedPendingMember> = emptyList(),
requestingMembers: List<DecryptedRequestingMember> = emptyList(), requestingMembers: List<DecryptedRequestingMember> = emptyList(),
inviteLinkPassword: ByteArray = ByteArray(0), inviteLinkPassword: ByteArray = ByteArray(0),
disappearingMessageTimer: DecryptedTimer = DecryptedTimer.getDefaultInstance(), disappearingMessageTimer: DecryptedTimer = DecryptedTimer.getDefaultInstance()
serviceId: String = ServiceId.from(UUID.randomUUID()).toString()
) { ) {
localState = decryptedGroup(revision, title, avatar, description, accessControl, members, pendingMembers, requestingMembers, inviteLinkPassword, disappearingMessageTimer) localState = decryptedGroup(revision, title, avatar, description, accessControl, members, pendingMembers, requestingMembers, inviteLinkPassword, disappearingMessageTimer)
groupRecord = groupRecord(masterKey, localState!!, active = active, serviceId = serviceId) groupRecord = groupRecord(masterKey, localState!!, active = active)
} }
fun serverState( fun serverState(
@ -173,8 +171,7 @@ fun groupRecord(
active: Boolean = true, active: Boolean = true,
avatarDigest: ByteArray = ByteArray(0), avatarDigest: ByteArray = ByteArray(0),
mms: Boolean = false, mms: Boolean = false,
distributionId: DistributionId? = null, distributionId: DistributionId? = null
serviceId: String = ServiceId.from(UUID.randomUUID()).toString()
): Optional<GroupDatabase.GroupRecord> { ): Optional<GroupDatabase.GroupRecord> {
return Optional.of( return Optional.of(
GroupDatabase.GroupRecord( GroupDatabase.GroupRecord(
@ -193,8 +190,7 @@ fun groupRecord(
masterKey.serialize(), masterKey.serialize(),
decryptedGroup.revision, decryptedGroup.revision,
decryptedGroup.toByteArray(), decryptedGroup.toByteArray(),
distributionId, distributionId
serviceId
) )
) )
} }

Wyświetl plik

@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.database
import android.net.Uri import android.net.Uri
import org.signal.core.util.Bitmask import org.signal.core.util.Bitmask
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredential import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential
import org.thoughtcrime.securesms.badges.models.Badge import org.thoughtcrime.securesms.badges.models.Badge
import org.thoughtcrime.securesms.conversation.colors.AvatarColor import org.thoughtcrime.securesms.conversation.colors.AvatarColor
import org.thoughtcrime.securesms.conversation.colors.ChatColors import org.thoughtcrime.securesms.conversation.colors.ChatColors
@ -47,7 +47,7 @@ object RecipientDatabaseTestUtils {
expireMessages: Int = 0, expireMessages: Int = 0,
registered: RecipientDatabase.RegisteredState = RecipientDatabase.RegisteredState.REGISTERED, registered: RecipientDatabase.RegisteredState = RecipientDatabase.RegisteredState.REGISTERED,
profileKey: ByteArray = Random.nextBytes(32), profileKey: ByteArray = Random.nextBytes(32),
profileKeyCredential: ProfileKeyCredential? = null, expiringProfileKeyCredential: ExpiringProfileKeyCredential? = null,
systemProfileName: ProfileName = ProfileName.EMPTY, systemProfileName: ProfileName = ProfileName.EMPTY,
systemDisplayName: String? = null, systemDisplayName: String? = null,
systemContactPhoto: String? = null, systemContactPhoto: String? = null,
@ -111,7 +111,7 @@ object RecipientDatabaseTestUtils {
expireMessages, expireMessages,
registered, registered,
profileKey, profileKey,
profileKeyCredential, expiringProfileKeyCredential,
systemProfileName, systemProfileName,
systemDisplayName, systemDisplayName,
systemContactPhoto, systemContactPhoto,

Wyświetl plik

@ -24,12 +24,12 @@ import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange; import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
import org.signal.storageservice.protos.groups.local.DecryptedMember; import org.signal.storageservice.protos.groups.local.DecryptedMember;
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember; import org.signal.storageservice.protos.groups.local.DecryptedPendingMember;
import org.thoughtcrime.securesms.keyvalue.ServiceIds;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientId;
import org.whispersystems.signalservice.api.push.ACI; import org.whispersystems.signalservice.api.push.ACI;
import org.whispersystems.signalservice.api.push.PNI; import org.whispersystems.signalservice.api.push.PNI;
import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.ServiceIds;
import org.whispersystems.signalservice.api.util.UuidUtil; import org.whispersystems.signalservice.api.util.UuidUtil;
import java.util.Arrays; import java.util.Arrays;

Wyświetl plik

@ -41,6 +41,7 @@ import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations
import org.whispersystems.signalservice.api.push.ACI import org.whispersystems.signalservice.api.push.ACI
import org.whispersystems.signalservice.api.push.PNI import org.whispersystems.signalservice.api.push.PNI
import org.whispersystems.signalservice.api.push.ServiceId import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.ServiceIds
import java.util.UUID import java.util.UUID
@RunWith(RobolectricTestRunner::class) @RunWith(RobolectricTestRunner::class)
@ -55,6 +56,7 @@ class GroupManagerV2Test_edit {
val selfAci: ACI = ACI.from(UUID.randomUUID()) val selfAci: ACI = ACI.from(UUID.randomUUID())
val selfPni: PNI = PNI.from(UUID.randomUUID()) val selfPni: PNI = PNI.from(UUID.randomUUID())
val serviceIds: ServiceIds = ServiceIds(selfAci, selfPni)
val otherSid: ServiceId = ServiceId.from(UUID.randomUUID()) val otherSid: ServiceId = ServiceId.from(UUID.randomUUID())
val selfAndOthers: List<DecryptedMember> = listOf(member(selfAci), member(otherSid)) val selfAndOthers: List<DecryptedMember> = listOf(member(selfAci), member(otherSid))
val others: List<DecryptedMember> = listOf(member(otherSid)) val others: List<DecryptedMember> = listOf(member(otherSid))
@ -103,8 +105,7 @@ class GroupManagerV2Test_edit {
groupsV2Operations, groupsV2Operations,
groupsV2Authorization, groupsV2Authorization,
groupsV2StateProcessor, groupsV2StateProcessor,
selfAci, serviceIds,
selfPni,
groupCandidateHelper, groupCandidateHelper,
sendGroupUpdateHelper sendGroupUpdateHelper
) )
@ -139,8 +140,7 @@ class GroupManagerV2Test_edit {
members = listOf( members = listOf(
member(selfAci, role = Member.Role.ADMINISTRATOR), member(selfAci, role = Member.Role.ADMINISTRATOR),
member(otherSid) member(otherSid)
), )
serviceId = selfAci.toString()
) )
groupChange(6) { groupChange(6) {
source(selfAci) source(selfAci)

Wyświetl plik

@ -43,7 +43,9 @@ import org.thoughtcrime.securesms.testutil.SystemOutLogger
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api
import org.whispersystems.signalservice.api.groupsv2.PartialDecryptedGroup import org.whispersystems.signalservice.api.groupsv2.PartialDecryptedGroup
import org.whispersystems.signalservice.api.push.ACI import org.whispersystems.signalservice.api.push.ACI
import org.whispersystems.signalservice.api.push.PNI
import org.whispersystems.signalservice.api.push.ServiceId import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.ServiceIds
import java.util.UUID import java.util.UUID
@RunWith(RobolectricTestRunner::class) @RunWith(RobolectricTestRunner::class)
@ -53,6 +55,7 @@ class GroupsV2StateProcessorTest {
companion object { companion object {
private val masterKey = GroupMasterKey(fromStringCondensed("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) private val masterKey = GroupMasterKey(fromStringCondensed("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))
private val selfAci: ACI = ACI.from(UUID.randomUUID()) private val selfAci: ACI = ACI.from(UUID.randomUUID())
private val serviceIds: ServiceIds = ServiceIds(selfAci, PNI.from(UUID.randomUUID()))
private val otherSid: ServiceId = ServiceId.from(UUID.randomUUID()) private val otherSid: ServiceId = ServiceId.from(UUID.randomUUID())
private val selfAndOthers: List<DecryptedMember> = listOf(member(selfAci), member(otherSid)) private val selfAndOthers: List<DecryptedMember> = listOf(member(selfAci), member(otherSid))
private val others: List<DecryptedMember> = listOf(member(otherSid)) private val others: List<DecryptedMember> = listOf(member(otherSid))
@ -80,7 +83,7 @@ class GroupsV2StateProcessorTest {
groupsV2Authorization = mock(GroupsV2Authorization::class.java) groupsV2Authorization = mock(GroupsV2Authorization::class.java)
profileAndMessageHelper = mock(GroupsV2StateProcessor.ProfileAndMessageHelper::class.java) profileAndMessageHelper = mock(GroupsV2StateProcessor.ProfileAndMessageHelper::class.java)
processor = GroupsV2StateProcessor.StateProcessorForGroup(selfAci, ApplicationProvider.getApplicationContext(), groupDatabase, groupsV2API, groupsV2Authorization, masterKey, profileAndMessageHelper) processor = GroupsV2StateProcessor.StateProcessorForGroup(serviceIds, ApplicationProvider.getApplicationContext(), groupDatabase, groupsV2API, groupsV2Authorization, masterKey, profileAndMessageHelper)
} }
@After @After

Wyświetl plik

@ -35,8 +35,6 @@ ext {
MINIMUM_SDK = 19 MINIMUM_SDK = 19
JAVA_VERSION = JavaVersion.VERSION_1_8 JAVA_VERSION = JavaVersion.VERSION_1_8
LIBSIGNAL_CLIENT_VERSION = '0.9.4'
} }
wrapper { wrapper {

Wyświetl plik

@ -4,7 +4,7 @@
dependencyResolutionManagement { dependencyResolutionManagement {
versionCatalogs { versionCatalogs {
libs { libs {
version('libsignal-client', '0.17.0') version('libsignal-client', '0.18.1')
version('exoplayer', '2.15.0') version('exoplayer', '2.15.0')
version('androidx-camera', '1.0.0-beta11') version('androidx-camera', '1.0.0-beta11')
version('androidx-lifecycle', '2.3.1') version('androidx-lifecycle', '2.3.1')

Wyświetl plik

@ -5508,20 +5508,20 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="f7ae20ce655cefd1db052ad2597169a9442ad05939dce51ade3d419eed5d0f19" origin="Generated by Gradle"/> <sha256 value="f7ae20ce655cefd1db052ad2597169a9442ad05939dce51ade3d419eed5d0f19" origin="Generated by Gradle"/>
</artifact> </artifact>
</component> </component>
<component group="org.signal" name="libsignal-android" version="0.17.0"> <component group="org.signal" name="libsignal-android" version="0.18.1">
<artifact name="libsignal-android-0.17.0.aar"> <artifact name="libsignal-android-0.18.1.aar">
<sha256 value="3b18d6c2e1452fb1ce40a17e3f77abb3337daa09f9dedcb7f2a64ad32ca05c8a" origin="Generated by Gradle"/> <sha256 value="ed6d9d49ac5d3d169116ed73cbf54bb26d9fd86046ed43bccabe9b17a20012de" origin="Generated by Gradle"/>
</artifact> </artifact>
<artifact name="libsignal-android-0.17.0.module"> <artifact name="libsignal-android-0.18.1.module">
<sha256 value="95510c70f1bf0fd65ddc1f39fbf7a42d75d8a064dc3a2cbe52496fa2f2c535f0" origin="Generated by Gradle"/> <sha256 value="1cbb2658d652cd3eccad807923645d9f35fcaeece13cbf2df75db7b2b18d3ee4" origin="Generated by Gradle"/>
</artifact> </artifact>
</component> </component>
<component group="org.signal" name="libsignal-client" version="0.17.0"> <component group="org.signal" name="libsignal-client" version="0.18.1">
<artifact name="libsignal-client-0.17.0.jar"> <artifact name="libsignal-client-0.18.1.jar">
<sha256 value="3611dc9425d7ef85daedbafa49fa67f49e921bf89e36f601d0b79b1268315619" origin="Generated by Gradle"/> <sha256 value="d912835a741aeac8484be8f49d6d56204182a777c3203493c06bbcdce5946565" origin="Generated by Gradle"/>
</artifact> </artifact>
<artifact name="libsignal-client-0.17.0.module"> <artifact name="libsignal-client-0.18.1.module">
<sha256 value="cc6125c6e30421eb39a115576371a41f0adde7ea501e99d2c74192264e6fb1ab" origin="Generated by Gradle"/> <sha256 value="a5fb2a1f1f8d94680eae2fa4f4d665126b1b7d2d685c15d2e074887deeb9f3f1" origin="Generated by Gradle"/>
</artifact> </artifact>
</component> </component>
<component group="org.signal" name="ringrtc-android" version="2.20.11.1"> <component group="org.signal" name="ringrtc-android" version="2.20.11.1">

Wyświetl plik

@ -16,8 +16,8 @@ import org.signal.libsignal.protocol.ecc.ECPublicKey;
import org.signal.libsignal.protocol.logging.Log; import org.signal.libsignal.protocol.logging.Log;
import org.signal.libsignal.protocol.state.PreKeyRecord; import org.signal.libsignal.protocol.state.PreKeyRecord;
import org.signal.libsignal.protocol.state.SignedPreKeyRecord; import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredential;
import org.whispersystems.signalservice.api.account.AccountAttributes; import org.whispersystems.signalservice.api.account.AccountAttributes;
import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException; import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
import org.whispersystems.signalservice.api.crypto.ProfileCipher; import org.whispersystems.signalservice.api.crypto.ProfileCipher;
@ -824,12 +824,12 @@ public class SignalServiceAccountManager {
profileAvatarData); profileAvatarData);
} }
public Optional<ProfileKeyCredential> resolveProfileKeyCredential(ServiceId serviceId, ProfileKey profileKey, Locale locale) public Optional<ExpiringProfileKeyCredential> resolveProfileKeyCredential(ServiceId serviceId, ProfileKey profileKey, Locale locale)
throws NonSuccessfulResponseCodeException, PushNetworkException throws NonSuccessfulResponseCodeException, PushNetworkException
{ {
try { try {
ProfileAndCredential credential = this.pushServiceSocket.retrieveVersionedProfileAndCredential(serviceId.uuid(), profileKey, Optional.empty(), locale).get(10, TimeUnit.SECONDS); ProfileAndCredential credential = this.pushServiceSocket.retrieveVersionedProfileAndCredential(serviceId.uuid(), profileKey, Optional.empty(), locale).get(10, TimeUnit.SECONDS);
return credential.getProfileKeyCredential(); return credential.getExpiringProfileKeyCredential();
} catch (InterruptedException | TimeoutException e) { } catch (InterruptedException | TimeoutException e) {
throw new PushNetworkException(e); throw new PushNetworkException(e);
} catch (ExecutionException e) { } catch (ExecutionException e) {

Wyświetl plik

@ -44,4 +44,6 @@ public interface ChangeSetModifier {
void removeAddBannedMembers(int i); void removeAddBannedMembers(int i);
void removeDeleteBannedMembers(int i); void removeDeleteBannedMembers(int i);
void removePromotePendingPniAciMembers(int i);
} }

Wyświetl plik

@ -143,6 +143,11 @@ final class DecryptedGroupChangeActionsBuilderChangeSetModifier implements Chang
result.removeDeleteBannedMembers(i); result.removeDeleteBannedMembers(i);
} }
@Override
public void removePromotePendingPniAciMembers(int i) {
result.removePromotePendingPniAciMembers(i);
}
private static List<ByteString> removeIndexFromByteStringList(List<ByteString> byteStrings, int i) { private static List<ByteString> removeIndexFromByteStringList(List<ByteString> byteStrings, int i) {
List<ByteString> modifiedList = new ArrayList<>(byteStrings); List<ByteString> modifiedList = new ArrayList<>(byteStrings);

Wyświetl plik

@ -15,6 +15,7 @@ import org.signal.storageservice.protos.groups.local.DecryptedPendingMember;
import org.signal.storageservice.protos.groups.local.DecryptedPendingMemberRemoval; import org.signal.storageservice.protos.groups.local.DecryptedPendingMemberRemoval;
import org.signal.storageservice.protos.groups.local.DecryptedRequestingMember; import org.signal.storageservice.protos.groups.local.DecryptedRequestingMember;
import org.signal.storageservice.protos.groups.local.EnabledState; import org.signal.storageservice.protos.groups.local.EnabledState;
import org.whispersystems.signalservice.api.push.ServiceIds;
import org.whispersystems.signalservice.api.util.UuidUtil; import org.whispersystems.signalservice.api.util.UuidUtil;
import java.util.ArrayList; import java.util.ArrayList;
@ -193,6 +194,16 @@ public final class DecryptedGroupUtil {
return Optional.empty(); return Optional.empty();
} }
public static Optional<DecryptedPendingMember> findPendingByServiceIds(Collection<DecryptedPendingMember> members, ServiceIds serviceIds) {
for (DecryptedPendingMember member : members) {
if (serviceIds.matches(member.getUuid())) {
return Optional.of(member);
}
}
return Optional.empty();
}
private static int findPendingIndexByUuidCipherText(List<DecryptedPendingMember> members, ByteString cipherText) { private static int findPendingIndexByUuidCipherText(List<DecryptedPendingMember> members, ByteString cipherText) {
for (int i = 0; i < members.size(); i++) { for (int i = 0; i < members.size(); i++) {
DecryptedPendingMember member = members.get(i); DecryptedPendingMember member = members.get(i);
@ -227,9 +238,19 @@ public final class DecryptedGroupUtil {
return Optional.empty(); return Optional.empty();
} }
public static boolean isPendingOrRequesting(DecryptedGroup group, UUID uuid) { public static Optional<DecryptedRequestingMember> findRequestingByServiceIds(Collection<DecryptedRequestingMember> members, ServiceIds serviceIds) {
return findPendingByUuid(group.getPendingMembersList(), uuid).isPresent() || for (DecryptedRequestingMember member : members) {
findRequestingByUuid(group.getRequestingMembersList(), uuid).isPresent(); if (serviceIds.matches(member.getUuid())) {
return Optional.of(member);
}
}
return Optional.empty();
}
public static boolean isPendingOrRequesting(DecryptedGroup group, ServiceIds serviceIds) {
return findPendingByServiceIds(group.getPendingMembersList(), serviceIds).isPresent() ||
findRequestingByServiceIds(group.getRequestingMembersList(), serviceIds).isPresent();
} }
public static boolean isRequesting(DecryptedGroup group, UUID uuid) { public static boolean isRequesting(DecryptedGroup group, UUID uuid) {
@ -324,6 +345,8 @@ public final class DecryptedGroupUtil {
applyDeleteBannedMembersActions(builder, change.getDeleteBannedMembersList()); applyDeleteBannedMembersActions(builder, change.getDeleteBannedMembersList());
applyPromotePendingPniAciMemberActions(builder, change.getPromotePendingPniAciMembersList());
return builder.build(); return builder.build();
} }
@ -557,6 +580,19 @@ public final class DecryptedGroupUtil {
} }
} }
protected static void applyPromotePendingPniAciMemberActions(DecryptedGroup.Builder builder, List<DecryptedMember> promotePendingPniAciMembersList) throws NotAbleToApplyGroupV2ChangeException {
for (DecryptedMember newMember : promotePendingPniAciMembersList) {
int index = findPendingIndexByUuid(builder.getPendingMembersList(), newMember.getPni());
if (index == -1) {
throw new NotAbleToApplyGroupV2ChangeException();
}
builder.removePendingMembers(index);
builder.addMembers(newMember);
}
}
private static DecryptedMember withNewProfileKey(DecryptedMember member, ByteString profileKey) { private static DecryptedMember withNewProfileKey(DecryptedMember member, ByteString profileKey) {
return DecryptedMember.newBuilder(member) return DecryptedMember.newBuilder(member)
.setProfileKey(profileKey) .setProfileKey(profileKey)
@ -653,48 +689,50 @@ public final class DecryptedGroupUtil {
* When updating this, update {@link #changeIsEmptyExceptForBanChangesAndOptionalProfileKeyChanges(DecryptedGroupChange)} * When updating this, update {@link #changeIsEmptyExceptForBanChangesAndOptionalProfileKeyChanges(DecryptedGroupChange)}
*/ */
public static boolean changeIsEmptyExceptForProfileKeyChanges(DecryptedGroupChange change) { public static boolean changeIsEmptyExceptForProfileKeyChanges(DecryptedGroupChange change) {
return change.getNewMembersCount() == 0 && // field 3 return change.getNewMembersCount() == 0 && // field 3
change.getDeleteMembersCount() == 0 && // field 4 change.getDeleteMembersCount() == 0 && // field 4
change.getModifyMemberRolesCount() == 0 && // field 5 change.getModifyMemberRolesCount() == 0 && // field 5
change.getNewPendingMembersCount() == 0 && // field 7 change.getNewPendingMembersCount() == 0 && // field 7
change.getDeletePendingMembersCount() == 0 && // field 8 change.getDeletePendingMembersCount() == 0 && // field 8
change.getPromotePendingMembersCount() == 0 && // field 9 change.getPromotePendingMembersCount() == 0 && // field 9
!change.hasNewTitle() && // field 10 !change.hasNewTitle() && // field 10
!change.hasNewAvatar() && // field 11 !change.hasNewAvatar() && // field 11
!change.hasNewTimer() && // field 12 !change.hasNewTimer() && // field 12
isEmpty(change.getNewAttributeAccess()) && // field 13 isEmpty(change.getNewAttributeAccess()) && // field 13
isEmpty(change.getNewMemberAccess()) && // field 14 isEmpty(change.getNewMemberAccess()) && // field 14
isEmpty(change.getNewInviteLinkAccess()) && // field 15 isEmpty(change.getNewInviteLinkAccess()) && // field 15
change.getNewRequestingMembersCount() == 0 && // field 16 change.getNewRequestingMembersCount() == 0 && // field 16
change.getDeleteRequestingMembersCount() == 0 && // field 17 change.getDeleteRequestingMembersCount() == 0 && // field 17
change.getPromoteRequestingMembersCount() == 0 && // field 18 change.getPromoteRequestingMembersCount() == 0 && // field 18
change.getNewInviteLinkPassword().size() == 0 && // field 19 change.getNewInviteLinkPassword().size() == 0 && // field 19
!change.hasNewDescription() && // field 20 !change.hasNewDescription() && // field 20
isEmpty(change.getNewIsAnnouncementGroup()) && // field 21 isEmpty(change.getNewIsAnnouncementGroup()) && // field 21
change.getNewBannedMembersCount() == 0 && // field 22 change.getNewBannedMembersCount() == 0 && // field 22
change.getDeleteBannedMembersCount() == 0; // field 23 change.getDeleteBannedMembersCount() == 0 && // field 23
change.getPromotePendingPniAciMembersCount() == 0; // field 24
} }
public static boolean changeIsEmptyExceptForBanChangesAndOptionalProfileKeyChanges(DecryptedGroupChange change) { public static boolean changeIsEmptyExceptForBanChangesAndOptionalProfileKeyChanges(DecryptedGroupChange change) {
return (change.getNewBannedMembersCount() != 0 || change.getDeleteBannedMembersCount() != 0) && return (change.getNewBannedMembersCount() != 0 || change.getDeleteBannedMembersCount() != 0) &&
change.getNewMembersCount() == 0 && // field 3 change.getNewMembersCount() == 0 && // field 3
change.getDeleteMembersCount() == 0 && // field 4 change.getDeleteMembersCount() == 0 && // field 4
change.getModifyMemberRolesCount() == 0 && // field 5 change.getModifyMemberRolesCount() == 0 && // field 5
change.getNewPendingMembersCount() == 0 && // field 7 change.getNewPendingMembersCount() == 0 && // field 7
change.getDeletePendingMembersCount() == 0 && // field 8 change.getDeletePendingMembersCount() == 0 && // field 8
change.getPromotePendingMembersCount() == 0 && // field 9 change.getPromotePendingMembersCount() == 0 && // field 9
!change.hasNewTitle() && // field 10 !change.hasNewTitle() && // field 10
!change.hasNewAvatar() && // field 11 !change.hasNewAvatar() && // field 11
!change.hasNewTimer() && // field 12 !change.hasNewTimer() && // field 12
isEmpty(change.getNewAttributeAccess()) && // field 13 isEmpty(change.getNewAttributeAccess()) && // field 13
isEmpty(change.getNewMemberAccess()) && // field 14 isEmpty(change.getNewMemberAccess()) && // field 14
isEmpty(change.getNewInviteLinkAccess()) && // field 15 isEmpty(change.getNewInviteLinkAccess()) && // field 15
change.getNewRequestingMembersCount() == 0 && // field 16 change.getNewRequestingMembersCount() == 0 && // field 16
change.getDeleteRequestingMembersCount() == 0 && // field 17 change.getDeleteRequestingMembersCount() == 0 && // field 17
change.getPromoteRequestingMembersCount() == 0 && // field 18 change.getPromoteRequestingMembersCount() == 0 && // field 18
change.getNewInviteLinkPassword().size() == 0 && // field 19 change.getNewInviteLinkPassword().size() == 0 && // field 19
!change.hasNewDescription() && // field 20 !change.hasNewDescription() && // field 20
isEmpty(change.getNewIsAnnouncementGroup()); // field 21 isEmpty(change.getNewIsAnnouncementGroup()) && // field 21
change.getPromotePendingPniAciMembersCount() == 0; // field 24
} }
static boolean isEmpty(AccessControl.AccessRequired newAttributeAccess) { static boolean isEmpty(AccessControl.AccessRequired newAttributeAccess) {

Wyświetl plik

@ -1,12 +1,9 @@
package org.whispersystems.signalservice.api.groupsv2; package org.whispersystems.signalservice.api.groupsv2;
import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredential; import org.whispersystems.signalservice.api.util.ExpiringProfileCredentialUtil;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@ -14,7 +11,7 @@ import java.util.UUID;
/** /**
* Represents a potential new member of a group. * Represents a potential new member of a group.
* <p> * <p>
* The entry may or may not have a {@link ProfileKeyCredential}. * The entry may or may not have a {@link ExpiringProfileKeyCredential}.
* <p> * <p>
* If it does not, then this user can only be invited. * If it does not, then this user can only be invited.
* <p> * <p>
@ -22,60 +19,50 @@ import java.util.UUID;
*/ */
public final class GroupCandidate { public final class GroupCandidate {
private final UUID uuid; private final UUID uuid;
private final Optional<ProfileKeyCredential> profileKeyCredential; private final Optional<ExpiringProfileKeyCredential> expiringProfileKeyCredential;
public GroupCandidate(UUID uuid, Optional<ProfileKeyCredential> profileKeyCredential) { public GroupCandidate(UUID uuid, Optional<ExpiringProfileKeyCredential> expiringProfileKeyCredential) {
this.uuid = uuid; this.uuid = uuid;
this.profileKeyCredential = profileKeyCredential; this.expiringProfileKeyCredential = expiringProfileKeyCredential;
} }
public UUID getUuid() { public UUID getUuid() {
return uuid; return uuid;
} }
public Optional<ProfileKeyCredential> getProfileKeyCredential() { public Optional<ExpiringProfileKeyCredential> getExpiringProfileKeyCredential() {
return profileKeyCredential; return expiringProfileKeyCredential;
} }
public ProfileKeyCredential requireProfileKeyCredential() { public ExpiringProfileKeyCredential requireExpiringProfileKeyCredential() {
if (profileKeyCredential.isPresent()) { if (expiringProfileKeyCredential.isPresent()) {
return profileKeyCredential.get(); return expiringProfileKeyCredential.get();
} }
throw new IllegalStateException("no profile key credential"); throw new IllegalStateException("no profile key credential");
} }
public boolean hasProfileKeyCredential() { public boolean hasValidProfileKeyCredential() {
return profileKeyCredential.isPresent(); return expiringProfileKeyCredential.map(ExpiringProfileCredentialUtil::isValid).orElse(false);
} }
public static Set<GroupCandidate> withoutProfileKeyCredentials(Set<GroupCandidate> groupCandidates) { public static Set<GroupCandidate> withoutExpiringProfileKeyCredentials(Set<GroupCandidate> groupCandidates) {
HashSet<GroupCandidate> result = new HashSet<>(groupCandidates.size()); HashSet<GroupCandidate> result = new HashSet<>(groupCandidates.size());
for (GroupCandidate candidate: groupCandidates) { for (GroupCandidate candidate : groupCandidates) {
result.add(candidate.withoutProfileKeyCredential()); result.add(candidate.withoutExpiringProfileKeyCredential());
} }
return result; return result;
} }
public GroupCandidate withoutProfileKeyCredential() { public GroupCandidate withoutExpiringProfileKeyCredential() {
return hasProfileKeyCredential() ? new GroupCandidate(uuid, Optional.empty()) return expiringProfileKeyCredential.isPresent() ? new GroupCandidate(uuid, Optional.empty())
: this; : this;
} }
public GroupCandidate withProfileKeyCredential(ProfileKeyCredential profileKeyCredential) { public GroupCandidate withExpiringProfileKeyCredential(ExpiringProfileKeyCredential expiringProfileKeyCredential) {
return new GroupCandidate(uuid, Optional.of(profileKeyCredential)); return new GroupCandidate(uuid, Optional.of(expiringProfileKeyCredential));
}
public static List<UUID> toUuidList(Collection<GroupCandidate> candidates) {
final List<UUID> uuidList = new ArrayList<>(candidates.size());
for (GroupCandidate candidate : candidates) {
uuidList.add(candidate.getUuid());
}
return uuidList;
} }
@Override @Override

Wyświetl plik

@ -122,4 +122,9 @@ final class GroupChangeActionsBuilderChangeSetModifier implements ChangeSetModif
public void removeDeleteBannedMembers(int i) { public void removeDeleteBannedMembers(int i) {
result.removeDeleteBannedMembers(i); result.removeDeleteBannedMembers(i);
} }
@Override
public void removePromotePendingPniAciMembers(int i) {
result.removePromotePendingPniAciMembers(i);
}
} }

Wyświetl plik

@ -25,27 +25,28 @@ public final class GroupChangeUtil {
* True iff there are no change actions. * True iff there are no change actions.
*/ */
public static boolean changeIsEmpty(GroupChange.Actions change) { public static boolean changeIsEmpty(GroupChange.Actions change) {
return change.getAddMembersCount() == 0 && // field 3 return change.getAddMembersCount() == 0 && // field 3
change.getDeleteMembersCount() == 0 && // field 4 change.getDeleteMembersCount() == 0 && // field 4
change.getModifyMemberRolesCount() == 0 && // field 5 change.getModifyMemberRolesCount() == 0 && // field 5
change.getModifyMemberProfileKeysCount() == 0 && // field 6 change.getModifyMemberProfileKeysCount() == 0 && // field 6
change.getAddPendingMembersCount() == 0 && // field 7 change.getAddPendingMembersCount() == 0 && // field 7
change.getDeletePendingMembersCount() == 0 && // field 8 change.getDeletePendingMembersCount() == 0 && // field 8
change.getPromotePendingMembersCount() == 0 && // field 9 change.getPromotePendingMembersCount() == 0 && // field 9
!change.hasModifyTitle() && // field 10 !change.hasModifyTitle() && // field 10
!change.hasModifyAvatar() && // field 11 !change.hasModifyAvatar() && // field 11
!change.hasModifyDisappearingMessagesTimer() && // field 12 !change.hasModifyDisappearingMessagesTimer() && // field 12
!change.hasModifyAttributesAccess() && // field 13 !change.hasModifyAttributesAccess() && // field 13
!change.hasModifyMemberAccess() && // field 14 !change.hasModifyMemberAccess() && // field 14
!change.hasModifyAddFromInviteLinkAccess() && // field 15 !change.hasModifyAddFromInviteLinkAccess() && // field 15
change.getAddRequestingMembersCount() == 0 && // field 16 change.getAddRequestingMembersCount() == 0 && // field 16
change.getDeleteRequestingMembersCount() == 0 && // field 17 change.getDeleteRequestingMembersCount() == 0 && // field 17
change.getPromoteRequestingMembersCount() == 0 && // field 18 change.getPromoteRequestingMembersCount() == 0 && // field 18
!change.hasModifyInviteLinkPassword() && // field 19 !change.hasModifyInviteLinkPassword() && // field 19
!change.hasModifyDescription() && // field 20 !change.hasModifyDescription() && // field 20
!change.hasModifyAnnouncementsOnly() && // field 21 !change.hasModifyAnnouncementsOnly() && // field 21
change.getAddBannedMembersCount() == 0 && // field 22 change.getAddBannedMembersCount() == 0 && // field 22
change.getDeleteBannedMembersCount() == 0; // field 23 change.getDeleteBannedMembersCount() == 0 && // field 23
change.getPromotePendingPniAciMembersCount() == 0; // field 24
} }
/** /**
@ -147,6 +148,7 @@ public final class GroupChangeUtil {
resolveField21ModifyAnnouncementsOnly (groupState, conflictingChange, changeSetModifier); resolveField21ModifyAnnouncementsOnly (groupState, conflictingChange, changeSetModifier);
resolveField22AddBannedMembers (conflictingChange, changeSetModifier, bannedMembersByUuid); resolveField22AddBannedMembers (conflictingChange, changeSetModifier, bannedMembersByUuid);
resolveField23DeleteBannedMembers (conflictingChange, changeSetModifier, bannedMembersByUuid); resolveField23DeleteBannedMembers (conflictingChange, changeSetModifier, bannedMembersByUuid);
resolveField24PromotePendingPniAciMembers (conflictingChange, changeSetModifier, fullMembersByUuid);
} }
private static void resolveField3AddMembers(DecryptedGroupChange conflictingChange, ChangeSetModifier result, HashMap<ByteString, DecryptedMember> fullMembersByUuid, HashMap<ByteString, DecryptedPendingMember> pendingMembersByUuid) { private static void resolveField3AddMembers(DecryptedGroupChange conflictingChange, ChangeSetModifier result, HashMap<ByteString, DecryptedMember> fullMembersByUuid, HashMap<ByteString, DecryptedPendingMember> pendingMembersByUuid) {
@ -352,4 +354,16 @@ public final class GroupChangeUtil {
} }
} }
} }
private static void resolveField24PromotePendingPniAciMembers(DecryptedGroupChange conflictingChange, ChangeSetModifier result, HashMap<ByteString, DecryptedMember> fullMembersByUuid) {
List<DecryptedMember> promotePendingPniAciMembersList = conflictingChange.getPromotePendingPniAciMembersList();
for (int i = promotePendingPniAciMembersList.size() - 1; i >= 0; i--) {
DecryptedMember member = promotePendingPniAciMembersList.get(i);
if (fullMembersByUuid.containsKey(member.getUuid())) {
result.removePromotePendingPniAciMembers(i);
}
}
}
} }

Wyświetl plik

@ -7,6 +7,8 @@ import org.signal.libsignal.zkgroup.VerificationFailedException;
import org.signal.libsignal.zkgroup.auth.AuthCredential; import org.signal.libsignal.zkgroup.auth.AuthCredential;
import org.signal.libsignal.zkgroup.auth.AuthCredentialPresentation; import org.signal.libsignal.zkgroup.auth.AuthCredentialPresentation;
import org.signal.libsignal.zkgroup.auth.AuthCredentialResponse; import org.signal.libsignal.zkgroup.auth.AuthCredentialResponse;
import org.signal.libsignal.zkgroup.auth.AuthCredentialWithPni;
import org.signal.libsignal.zkgroup.auth.AuthCredentialWithPniResponse;
import org.signal.libsignal.zkgroup.auth.ClientZkAuthOperations; import org.signal.libsignal.zkgroup.auth.ClientZkAuthOperations;
import org.signal.libsignal.zkgroup.groups.ClientZkGroupCipher; import org.signal.libsignal.zkgroup.groups.ClientZkGroupCipher;
import org.signal.libsignal.zkgroup.groups.GroupSecretParams; import org.signal.libsignal.zkgroup.groups.GroupSecretParams;
@ -44,24 +46,25 @@ public class GroupsV2Api {
/** /**
* Provides 7 days of credentials, which you should cache. * Provides 7 days of credentials, which you should cache.
*/ */
public HashMap<Integer, AuthCredentialResponse> getCredentials(int today, boolean isAci) public HashMap<Long, AuthCredentialWithPniResponse> getCredentials(long todaySeconds)
throws IOException throws IOException
{ {
return parseCredentialResponse(socket.retrieveGroupsV2Credentials(today, isAci)); return parseCredentialResponse(socket.retrieveGroupsV2Credentials(todaySeconds));
} }
/** /**
* Create an auth token from a credential response. * Create an auth token from a credential response.
*/ */
public GroupsV2AuthorizationString getGroupsV2AuthorizationString(ServiceId self, public GroupsV2AuthorizationString getGroupsV2AuthorizationString(ServiceId aci,
int today, ServiceId pni,
long redemptionTimeSeconds,
GroupSecretParams groupSecretParams, GroupSecretParams groupSecretParams,
AuthCredentialResponse authCredentialResponse) AuthCredentialWithPniResponse authCredentialWithPniResponse)
throws VerificationFailedException throws VerificationFailedException
{ {
ClientZkAuthOperations authOperations = groupsOperations.getAuthOperations(); ClientZkAuthOperations authOperations = groupsOperations.getAuthOperations();
AuthCredential authCredential = authOperations.receiveAuthCredential(self.uuid(), today, authCredentialResponse); AuthCredentialWithPni authCredentialWithPni = authOperations.receiveAuthCredentialWithPni(aci.uuid(), pni.uuid(), redemptionTimeSeconds, authCredentialWithPniResponse);
AuthCredentialPresentation authCredentialPresentation = authOperations.createAuthCredentialPresentation(new SecureRandom(), groupSecretParams, authCredential); AuthCredentialPresentation authCredentialPresentation = authOperations.createAuthCredentialPresentation(new SecureRandom(), groupSecretParams, authCredentialWithPni);
return new GroupsV2AuthorizationString(groupSecretParams, authCredentialPresentation); return new GroupsV2AuthorizationString(groupSecretParams, authCredentialPresentation);
} }
@ -171,20 +174,20 @@ public class GroupsV2Api {
return socket.getGroupExternalCredential(authorization); return socket.getGroupExternalCredential(authorization);
} }
private static HashMap<Integer, AuthCredentialResponse> parseCredentialResponse(CredentialResponse credentialResponse) private static HashMap<Long, AuthCredentialWithPniResponse> parseCredentialResponse(CredentialResponse credentialResponse)
throws IOException throws IOException
{ {
HashMap<Integer, AuthCredentialResponse> credentials = new HashMap<>(); HashMap<Long, AuthCredentialWithPniResponse> credentials = new HashMap<>();
for (TemporalCredential credential : credentialResponse.getCredentials()) { for (TemporalCredential credential : credentialResponse.getCredentials()) {
AuthCredentialResponse authCredentialResponse; AuthCredentialWithPniResponse authCredentialWithPniResponse;
try { try {
authCredentialResponse = new AuthCredentialResponse(credential.getCredential()); authCredentialWithPniResponse = new AuthCredentialWithPniResponse(credential.getCredential());
} catch (InvalidInputException e) { } catch (InvalidInputException e) {
throw new IOException(e); throw new IOException(e);
} }
credentials.put(credential.getRedemptionTime(), authCredentialResponse); credentials.put(credential.getRedemptionTime(), authCredentialWithPniResponse);
} }
return credentials; return credentials;

Wyświetl plik

@ -14,8 +14,8 @@ import org.signal.libsignal.zkgroup.groups.GroupSecretParams;
import org.signal.libsignal.zkgroup.groups.ProfileKeyCiphertext; import org.signal.libsignal.zkgroup.groups.ProfileKeyCiphertext;
import org.signal.libsignal.zkgroup.groups.UuidCiphertext; import org.signal.libsignal.zkgroup.groups.UuidCiphertext;
import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations; import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations;
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredential;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialPresentation; import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialPresentation;
import org.signal.storageservice.protos.groups.AccessControl; import org.signal.storageservice.protos.groups.AccessControl;
import org.signal.storageservice.protos.groups.BannedMember; import org.signal.storageservice.protos.groups.BannedMember;
@ -39,6 +39,7 @@ import org.signal.storageservice.protos.groups.local.DecryptedRequestingMember;
import org.signal.storageservice.protos.groups.local.DecryptedString; import org.signal.storageservice.protos.groups.local.DecryptedString;
import org.signal.storageservice.protos.groups.local.DecryptedTimer; import org.signal.storageservice.protos.groups.local.DecryptedTimer;
import org.signal.storageservice.protos.groups.local.EnabledState; import org.signal.storageservice.protos.groups.local.EnabledState;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.util.UuidUtil; import org.whispersystems.signalservice.api.util.UuidUtil;
import java.security.SecureRandom; import java.security.SecureRandom;
@ -109,13 +110,13 @@ public final class GroupsV2Operations {
.setAttributes(AccessControl.AccessRequired.MEMBER) .setAttributes(AccessControl.AccessRequired.MEMBER)
.setMembers(AccessControl.AccessRequired.MEMBER)); .setMembers(AccessControl.AccessRequired.MEMBER));
group.addMembers(groupOperations.member(self.getProfileKeyCredential().get(), Member.Role.ADMINISTRATOR)); group.addMembers(groupOperations.member(self.requireExpiringProfileKeyCredential(), Member.Role.ADMINISTRATOR));
for (GroupCandidate credential : members) { for (GroupCandidate credential : members) {
ProfileKeyCredential profileKeyCredential = credential.getProfileKeyCredential().orElse(null); ExpiringProfileKeyCredential expiringProfileKeyCredential = credential.getExpiringProfileKeyCredential().orElse(null);
if (profileKeyCredential != null) { if (expiringProfileKeyCredential != null) {
group.addMembers(groupOperations.member(profileKeyCredential, memberRole)); group.addMembers(groupOperations.member(expiringProfileKeyCredential, memberRole));
} else { } else {
group.addPendingMembers(groupOperations.invitee(credential.getUuid(), memberRole)); group.addPendingMembers(groupOperations.invitee(credential.getUuid(), memberRole));
} }
@ -172,13 +173,13 @@ public final class GroupsV2Operations {
: createUnbanUuidsChange(membersToUnban); : createUnbanUuidsChange(membersToUnban);
for (GroupCandidate credential : membersToAdd) { for (GroupCandidate credential : membersToAdd) {
Member.Role newMemberRole = Member.Role.DEFAULT; Member.Role newMemberRole = Member.Role.DEFAULT;
ProfileKeyCredential profileKeyCredential = credential.getProfileKeyCredential().orElse(null); ExpiringProfileKeyCredential expiringProfileKeyCredential = credential.getExpiringProfileKeyCredential().orElse(null);
if (profileKeyCredential != null) { if (expiringProfileKeyCredential != null) {
actions.addAddMembers(GroupChange.Actions.AddMemberAction actions.addAddMembers(GroupChange.Actions.AddMemberAction
.newBuilder() .newBuilder()
.setAdded(groupOperations.member(profileKeyCredential, newMemberRole))); .setAdded(groupOperations.member(expiringProfileKeyCredential, newMemberRole)));
} else { } else {
actions.addAddPendingMembers(GroupChange.Actions.AddPendingMemberAction actions.addAddPendingMembers(GroupChange.Actions.AddPendingMemberAction
.newBuilder() .newBuilder()
@ -190,24 +191,24 @@ public final class GroupsV2Operations {
return actions; return actions;
} }
public GroupChange.Actions.Builder createGroupJoinRequest(ProfileKeyCredential profileKeyCredential) { public GroupChange.Actions.Builder createGroupJoinRequest(ExpiringProfileKeyCredential expiringProfileKeyCredential) {
GroupOperations groupOperations = forGroup(groupSecretParams); GroupOperations groupOperations = forGroup(groupSecretParams);
GroupChange.Actions.Builder actions = GroupChange.Actions.newBuilder(); GroupChange.Actions.Builder actions = GroupChange.Actions.newBuilder();
actions.addAddRequestingMembers(GroupChange.Actions.AddRequestingMemberAction actions.addAddRequestingMembers(GroupChange.Actions.AddRequestingMemberAction
.newBuilder() .newBuilder()
.setAdded(groupOperations.requestingMember(profileKeyCredential))); .setAdded(groupOperations.requestingMember(expiringProfileKeyCredential)));
return actions; return actions;
} }
public GroupChange.Actions.Builder createGroupJoinDirect(ProfileKeyCredential profileKeyCredential) { public GroupChange.Actions.Builder createGroupJoinDirect(ExpiringProfileKeyCredential expiringProfileKeyCredential) {
GroupOperations groupOperations = forGroup(groupSecretParams); GroupOperations groupOperations = forGroup(groupSecretParams);
GroupChange.Actions.Builder actions = GroupChange.Actions.newBuilder(); GroupChange.Actions.Builder actions = GroupChange.Actions.newBuilder();
actions.addAddMembers(GroupChange.Actions.AddMemberAction actions.addAddMembers(GroupChange.Actions.AddMemberAction
.newBuilder() .newBuilder()
.setAdded(groupOperations.member(profileKeyCredential, Member.Role.DEFAULT))); .setAdded(groupOperations.member(expiringProfileKeyCredential, Member.Role.DEFAULT)));
return actions; return actions;
} }
@ -272,8 +273,8 @@ public final class GroupsV2Operations {
.setTimer(encryptTimer(timerDurationSeconds))); .setTimer(encryptTimer(timerDurationSeconds)));
} }
public GroupChange.Actions.Builder createUpdateProfileKeyCredentialChange(ProfileKeyCredential profileKeyCredential) { public GroupChange.Actions.Builder createUpdateProfileKeyCredentialChange(ExpiringProfileKeyCredential expiringProfileKeyCredential) {
ProfileKeyCredentialPresentation presentation = clientZkProfileOperations.createProfileKeyCredentialPresentation(random, groupSecretParams, profileKeyCredential); ProfileKeyCredentialPresentation presentation = clientZkProfileOperations.createProfileKeyCredentialPresentation(random, groupSecretParams, expiringProfileKeyCredential);
return GroupChange.Actions return GroupChange.Actions
.newBuilder() .newBuilder()
@ -282,14 +283,20 @@ public final class GroupsV2Operations {
.setPresentation(ByteString.copyFrom(presentation.serialize()))); .setPresentation(ByteString.copyFrom(presentation.serialize())));
} }
public GroupChange.Actions.Builder createAcceptInviteChange(ProfileKeyCredential profileKeyCredential) { public GroupChange.Actions.Builder createAcceptInviteChange(ExpiringProfileKeyCredential credential) {
ProfileKeyCredentialPresentation presentation = clientZkProfileOperations.createProfileKeyCredentialPresentation(random, groupSecretParams, profileKeyCredential); ProfileKeyCredentialPresentation presentation = clientZkProfileOperations.createProfileKeyCredentialPresentation(random, groupSecretParams, credential);
return GroupChange.Actions return GroupChange.Actions.newBuilder()
.newBuilder() .addPromotePendingMembers(GroupChange.Actions.PromotePendingMemberAction.newBuilder()
.addPromotePendingMembers(GroupChange.Actions.PromotePendingMemberAction .setPresentation(ByteString.copyFrom(presentation.serialize())));
.newBuilder() }
.setPresentation(ByteString.copyFrom(presentation.serialize())));
public GroupChange.Actions.Builder createAcceptPniInviteChange(ExpiringProfileKeyCredential credential) {
ByteString presentation = ByteString.copyFrom(clientZkProfileOperations.createProfileKeyCredentialPresentation(random, groupSecretParams, credential).serialize());
return GroupChange.Actions.newBuilder()
.addPromotePendingPniAciMembers(GroupChange.Actions.PromotePendingPniAciMemberProfileKeyAction.newBuilder()
.setPresentation(presentation));
} }
public GroupChange.Actions.Builder createRemoveInvitationChange(final Set<UuidCiphertext> uuidCipherTextsFromInvitesToRemove) { public GroupChange.Actions.Builder createRemoveInvitationChange(final Set<UuidCiphertext> uuidCipherTextsFromInvitesToRemove) {
@ -387,7 +394,30 @@ public final class GroupsV2Operations {
return builder; return builder;
} }
private Member.Builder member(ProfileKeyCredential credential, Member.Role role) { public GroupChange.Actions.Builder replaceAddMembers(GroupChange.Actions.Builder change, List<GroupCandidate> candidates) throws InvalidInputException {
if (change.getAddMembersCount() != candidates.size()) {
throw new InvalidInputException("Replacement candidates not same size as original add");
}
for (int i = 0; i < change.getAddMembersCount(); i++) {
GroupChange.Actions.AddMemberAction original = change.getAddMembers(i);
GroupCandidate candidate = candidates.get(i);
ExpiringProfileKeyCredential expiringProfileKeyCredential = candidate.getExpiringProfileKeyCredential().orElse(null);
if (expiringProfileKeyCredential == null) {
throw new InvalidInputException("Replacement candidate missing credential");
}
change.setAddMembers(i,
GroupChange.Actions.AddMemberAction.newBuilder()
.setAdded(member(expiringProfileKeyCredential, original.getAdded().getRole())));
}
return change;
}
private Member.Builder member(ExpiringProfileKeyCredential credential, Member.Role role) {
ProfileKeyCredentialPresentation presentation = clientZkProfileOperations.createProfileKeyCredentialPresentation(new SecureRandom(), groupSecretParams, credential); ProfileKeyCredentialPresentation presentation = clientZkProfileOperations.createProfileKeyCredentialPresentation(new SecureRandom(), groupSecretParams, credential);
return Member.newBuilder() return Member.newBuilder()
@ -395,7 +425,7 @@ public final class GroupsV2Operations {
.setPresentation(ByteString.copyFrom(presentation.serialize())); .setPresentation(ByteString.copyFrom(presentation.serialize()));
} }
private RequestingMember.Builder requestingMember(ProfileKeyCredential credential) { private RequestingMember.Builder requestingMember(ExpiringProfileKeyCredential credential) {
ProfileKeyCredentialPresentation presentation = clientZkProfileOperations.createProfileKeyCredentialPresentation(new SecureRandom(), groupSecretParams, credential); ProfileKeyCredentialPresentation presentation = clientZkProfileOperations.createProfileKeyCredentialPresentation(new SecureRandom(), groupSecretParams, credential);
return RequestingMember.newBuilder() return RequestingMember.newBuilder()
@ -559,15 +589,23 @@ public final class GroupsV2Operations {
// Field 6 // Field 6
for (GroupChange.Actions.ModifyMemberProfileKeyAction modifyMemberProfileKeyAction : actions.getModifyMemberProfileKeysList()) { for (GroupChange.Actions.ModifyMemberProfileKeyAction modifyMemberProfileKeyAction : actions.getModifyMemberProfileKeysList()) {
try { try {
ProfileKeyCredentialPresentation presentation = new ProfileKeyCredentialPresentation(modifyMemberProfileKeyAction.getPresentation().toByteArray()); UUID uuid;
presentation.getProfileKeyCiphertext(); ProfileKey profileKey;
if (modifyMemberProfileKeyAction.getUserId().isEmpty() || modifyMemberProfileKeyAction.getProfileKey().isEmpty()) {
ProfileKeyCredentialPresentation presentation = new ProfileKeyCredentialPresentation(modifyMemberProfileKeyAction.getPresentation().toByteArray());
uuid = decryptUuid(ByteString.copyFrom(presentation.getUuidCiphertext().serialize()));
profileKey = decryptProfileKey(ByteString.copyFrom(presentation.getProfileKeyCiphertext().serialize()), uuid);
} else {
uuid = decryptUuid(modifyMemberProfileKeyAction.getUserId());
profileKey = decryptProfileKey(modifyMemberProfileKeyAction.getProfileKey(), uuid);
}
UUID uuid = decryptUuid(ByteString.copyFrom(presentation.getUuidCiphertext().serialize()));
builder.addModifiedProfileKeys(DecryptedMember.newBuilder() builder.addModifiedProfileKeys(DecryptedMember.newBuilder()
.setRole(Member.Role.UNKNOWN) .setRole(Member.Role.UNKNOWN)
.setJoinedAtRevision(-1) .setJoinedAtRevision(-1)
.setUuid(UuidUtil.toByteString(uuid)) .setUuid(UuidUtil.toByteString(uuid))
.setProfileKey(ByteString.copyFrom(decryptProfileKey(ByteString.copyFrom(presentation.getProfileKeyCiphertext().serialize()), uuid).serialize()))); .setProfileKey(ByteString.copyFrom(profileKey.serialize())));
} catch (InvalidInputException e) { } catch (InvalidInputException e) {
throw new InvalidGroupStateException(e); throw new InvalidGroupStateException(e);
} }
@ -600,19 +638,27 @@ public final class GroupsV2Operations {
// Field 9 // Field 9
for (GroupChange.Actions.PromotePendingMemberAction promotePendingMemberAction : actions.getPromotePendingMembersList()) { for (GroupChange.Actions.PromotePendingMemberAction promotePendingMemberAction : actions.getPromotePendingMembersList()) {
ProfileKeyCredentialPresentation profileKeyCredentialPresentation;
try { try {
profileKeyCredentialPresentation = new ProfileKeyCredentialPresentation(promotePendingMemberAction.getPresentation().toByteArray()); UUID uuid;
ProfileKey profileKey;
if (promotePendingMemberAction.getUserId().isEmpty() || promotePendingMemberAction.getProfileKey().isEmpty()) {
ProfileKeyCredentialPresentation presentation = new ProfileKeyCredentialPresentation(promotePendingMemberAction.getPresentation().toByteArray());
uuid = decryptUuid(ByteString.copyFrom(presentation.getUuidCiphertext().serialize()));
profileKey = decryptProfileKey(ByteString.copyFrom(presentation.getProfileKeyCiphertext().serialize()), uuid);
} else {
uuid = decryptUuid(promotePendingMemberAction.getUserId());
profileKey = decryptProfileKey(promotePendingMemberAction.getProfileKey(), uuid);
}
builder.addPromotePendingMembers(DecryptedMember.newBuilder()
.setJoinedAtRevision(-1)
.setRole(Member.Role.DEFAULT)
.setUuid(UuidUtil.toByteString(uuid))
.setProfileKey(ByteString.copyFrom(profileKey.serialize())));
} catch (InvalidInputException e) { } catch (InvalidInputException e) {
throw new InvalidGroupStateException(e); throw new InvalidGroupStateException(e);
} }
UUID uuid = clientZkGroupCipher.decryptUuid(profileKeyCredentialPresentation.getUuidCiphertext());
ProfileKey profileKey = clientZkGroupCipher.decryptProfileKey(profileKeyCredentialPresentation.getProfileKeyCiphertext(), uuid);
builder.addPromotePendingMembers(DecryptedMember.newBuilder()
.setJoinedAtRevision(-1)
.setRole(Member.Role.DEFAULT)
.setUuid(UuidUtil.toByteString(uuid))
.setProfileKey(ByteString.copyFrom(profileKey.serialize())));
} }
// Field 10 // Field 10
@ -686,6 +732,21 @@ public final class GroupsV2Operations {
builder.addDeleteBannedMembers(DecryptedBannedMember.newBuilder().setUuid(decryptUuidToByteString(action.getDeletedUserId())).build()); builder.addDeleteBannedMembers(DecryptedBannedMember.newBuilder().setUuid(decryptUuidToByteString(action.getDeletedUserId())).build());
} }
// Field 24
for (GroupChange.Actions.PromotePendingPniAciMemberProfileKeyAction promotePendingPniAciMemberAction : actions.getPromotePendingPniAciMembersList()) {
UUID uuid = decryptUuid(promotePendingPniAciMemberAction.getUserId());
UUID pni = decryptUuid(promotePendingPniAciMemberAction.getPni());
ProfileKey profileKey = decryptProfileKey(promotePendingPniAciMemberAction.getProfileKey(), uuid);
builder.setEditor(UuidUtil.toByteString(uuid))
.addPromotePendingPniAciMembers(DecryptedMember.newBuilder()
.setUuid(UuidUtil.toByteString(uuid))
.setRole(Member.Role.DEFAULT)
.setProfileKey(ByteString.copyFrom(profileKey.serialize()))
.setJoinedAtRevision(actions.getRevision())
.setPni(UuidUtil.toByteString(pni)));
}
return builder.build(); return builder.build();
} }
@ -920,6 +981,18 @@ public final class GroupsV2Operations {
.setUserId(encryptUuid(uuid)) .setUserId(encryptUuid(uuid))
.setRole(role)); .setRole(role));
} }
public List<ServiceId> decryptAddMembers(List<GroupChange.Actions.AddMemberAction> addMembers) throws InvalidInputException, VerificationFailedException {
List<ServiceId> ids = new ArrayList<>(addMembers.size());
for (int i = 0; i < addMembers.size(); i++) {
GroupChange.Actions.AddMemberAction addMember = addMembers.get(i);
ProfileKeyCredentialPresentation profileKeyCredentialPresentation = new ProfileKeyCredentialPresentation(addMember.getAdded().getPresentation().toByteArray());
ids.add(ServiceId.from(clientZkGroupCipher.decryptUuid(profileKeyCredentialPresentation.getUuidCiphertext())));
}
return ids;
}
} }
public static class NewGroup { public static class NewGroup {

Wyświetl plik

@ -8,13 +8,13 @@ public class TemporalCredential {
private byte[] credential; private byte[] credential;
@JsonProperty @JsonProperty
private int redemptionTime; private long redemptionTime;
public byte[] getCredential() { public byte[] getCredential() {
return credential; return credential;
} }
public int getRedemptionTime() { public long getRedemptionTime() {
return redemptionTime; return redemptionTime;
} }
} }

Wyświetl plik

@ -1,23 +1,23 @@
package org.whispersystems.signalservice.api.profiles; package org.whispersystems.signalservice.api.profiles;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredential; import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
import java.util.Optional; import java.util.Optional;
public final class ProfileAndCredential { public final class ProfileAndCredential {
private final SignalServiceProfile profile; private final SignalServiceProfile profile;
private final SignalServiceProfile.RequestType requestType; private final SignalServiceProfile.RequestType requestType;
private final Optional<ProfileKeyCredential> profileKeyCredential; private final Optional<ExpiringProfileKeyCredential> expiringProfileKeyCredential;
public ProfileAndCredential(SignalServiceProfile profile, public ProfileAndCredential(SignalServiceProfile profile,
SignalServiceProfile.RequestType requestType, SignalServiceProfile.RequestType requestType,
Optional<ProfileKeyCredential> profileKeyCredential) Optional<ExpiringProfileKeyCredential> expiringProfileKeyCredential)
{ {
this.profile = profile; this.profile = profile;
this.requestType = requestType; this.requestType = requestType;
this.profileKeyCredential = profileKeyCredential; this.expiringProfileKeyCredential = expiringProfileKeyCredential;
} }
public SignalServiceProfile getProfile() { public SignalServiceProfile getProfile() {
@ -28,7 +28,7 @@ public final class ProfileAndCredential {
return requestType; return requestType;
} }
public Optional<ProfileKeyCredential> getProfileKeyCredential() { public Optional<ExpiringProfileKeyCredential> getExpiringProfileKeyCredential() {
return profileKeyCredential; return expiringProfileKeyCredential;
} }
} }

Wyświetl plik

@ -9,7 +9,7 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.signal.libsignal.protocol.logging.Log; import org.signal.libsignal.protocol.logging.Log;
import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialResponse; import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredentialResponse;
import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.internal.util.JsonUtil; import org.whispersystems.signalservice.internal.util.JsonUtil;
@ -245,11 +245,11 @@ public class SignalServiceProfile {
} }
} }
public ProfileKeyCredentialResponse getProfileKeyCredentialResponse() { public ExpiringProfileKeyCredentialResponse getExpiringProfileKeyCredentialResponse() {
if (credential == null) return null; if (credential == null) return null;
try { try {
return new ProfileKeyCredentialResponse(credential); return new ExpiringProfileKeyCredentialResponse(credential);
} catch (InvalidInputException e) { } catch (InvalidInputException e) {
Log.w(TAG, e); Log.w(TAG, e);
return null; return null;

Wyświetl plik

@ -0,0 +1,54 @@
package org.whispersystems.signalservice.api.push;
import com.google.protobuf.ByteString;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.util.Objects;
import java.util.UUID;
/**
* Helper for dealing with [ServiceId] matching when you only care that either of your
* service ids match but don't care which one.
*/
public final class ServiceIds {
private final ACI aci;
private final PNI pni;
private ByteString aciByteString;
private ByteString pniByteString;
public ServiceIds(ACI aci, PNI pni) {
this.aci = aci;
this.pni = pni;
}
public ACI getAci() {
return aci;
}
public PNI getPni() {
return pni;
}
public PNI requirePni() {
return Objects.requireNonNull(pni);
}
public boolean matches(UUID uuid) {
return uuid.equals(aci.uuid()) || (pni != null && uuid.equals(pni.uuid()));
}
public boolean matches(ByteString uuid) {
if (aciByteString == null) {
aciByteString = aci.toByteString();
}
if (pniByteString == null && pni != null) {
pniByteString = pni.toByteString();
}
return uuid.equals(aciByteString) || uuid.equals(pniByteString);
}
}

Wyświetl plik

@ -4,8 +4,8 @@ import org.signal.libsignal.protocol.IdentityKey;
import org.signal.libsignal.protocol.util.Pair; import org.signal.libsignal.protocol.util.Pair;
import org.signal.libsignal.zkgroup.VerificationFailedException; import org.signal.libsignal.zkgroup.VerificationFailedException;
import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations; import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations;
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredential;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialRequest; import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialRequest;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialRequestContext; import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialRequestContext;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyVersion; import org.signal.libsignal.zkgroup.profiles.ProfileKeyVersion;
@ -90,7 +90,7 @@ public final class ProfileService {
ProfileKeyCredentialRequest request = requestContext.getRequest(); ProfileKeyCredentialRequest request = requestContext.getRequest();
String credentialRequest = Hex.toStringCondensed(request.serialize()); String credentialRequest = Hex.toStringCondensed(request.serialize());
builder.setPath(String.format("/v1/profile/%s/%s/%s", serviceId, version, credentialRequest)); builder.setPath(String.format("/v1/profile/%s/%s/%s?credentialType=expiringProfileKey", serviceId, version, credentialRequest));
} else { } else {
builder.setPath(String.format("/v1/profile/%s/%s", serviceId, version)); builder.setPath(String.format("/v1/profile/%s/%s", serviceId, version));
} }
@ -170,13 +170,13 @@ public final class ProfileService {
throws MalformedResponseException throws MalformedResponseException
{ {
try { try {
SignalServiceProfile signalServiceProfile = JsonUtil.fromJsonResponse(body, SignalServiceProfile.class); SignalServiceProfile signalServiceProfile = JsonUtil.fromJsonResponse(body, SignalServiceProfile.class);
ProfileKeyCredential profileKeyCredential = null; ExpiringProfileKeyCredential expiringProfileKeyCredential = null;
if (requestContext != null && signalServiceProfile.getProfileKeyCredentialResponse() != null) { if (requestContext != null && signalServiceProfile.getExpiringProfileKeyCredentialResponse() != null) {
profileKeyCredential = clientZkProfileOperations.receiveProfileKeyCredential(requestContext, signalServiceProfile.getProfileKeyCredentialResponse()); expiringProfileKeyCredential = clientZkProfileOperations.receiveExpiringProfileKeyCredential(requestContext, signalServiceProfile.getExpiringProfileKeyCredentialResponse());
} }
return ServiceResponse.forResult(new ProfileAndCredential(signalServiceProfile, requestType, Optional.ofNullable(profileKeyCredential)), status, body); return ServiceResponse.forResult(new ProfileAndCredential(signalServiceProfile, requestType, Optional.ofNullable(expiringProfileKeyCredential)), status, body);
} catch (VerificationFailedException e) { } catch (VerificationFailedException e) {
return ServiceResponse.forApplicationError(e, status, body); return ServiceResponse.forApplicationError(e, status, body);
} }
@ -204,5 +204,10 @@ public final class ProfileService {
public boolean genericIoError() { public boolean genericIoError() {
return super.genericIoError(); return super.genericIoError();
} }
@Override
public Throwable getError() {
return super.getError();
}
} }
} }

Wyświetl plik

@ -0,0 +1,21 @@
package org.whispersystems.signalservice.api.util;
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
import java.time.temporal.ChronoField;
import java.util.concurrent.TimeUnit;
public final class ExpiringProfileCredentialUtil {
private ExpiringProfileCredentialUtil() {}
public static boolean isValid(ExpiringProfileKeyCredential credential) {
if (credential == null) {
return false;
}
long now = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis());
long expires = credential.getExpirationTime().getLong(ChronoField.INSTANT_SECONDS);
return now < expires;
}
}

Wyświetl plik

@ -22,6 +22,7 @@ import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
import org.signal.libsignal.protocol.util.Pair; import org.signal.libsignal.protocol.util.Pair;
import org.signal.libsignal.zkgroup.VerificationFailedException; import org.signal.libsignal.zkgroup.VerificationFailedException;
import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations; import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations;
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredential; import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredential;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialRequest; import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialRequest;
@ -236,7 +237,7 @@ public class PushServiceSocket {
private static final String STICKER_MANIFEST_PATH = "stickers/%s/manifest.proto"; private static final String STICKER_MANIFEST_PATH = "stickers/%s/manifest.proto";
private static final String STICKER_PATH = "stickers/%s/full/%d"; private static final String STICKER_PATH = "stickers/%s/full/%d";
private static final String GROUPSV2_CREDENTIAL = "/v1/certificate/group/%d/%d?identity=%s"; private static final String GROUPSV2_CREDENTIAL = "/v1/certificate/auth/group?redemptionStartSeconds=%d&redemptionEndSeconds=%d";
private static final String GROUPSV2_GROUP = "/v1/groups/"; private static final String GROUPSV2_GROUP = "/v1/groups/";
private static final String GROUPSV2_GROUP_PASSWORD = "/v1/groups/?inviteLinkPassword=%s"; private static final String GROUPSV2_GROUP_PASSWORD = "/v1/groups/?inviteLinkPassword=%s";
private static final String GROUPSV2_GROUP_CHANGES = "/v1/groups/logs/%s?maxSupportedChangeEpoch=%d&includeFirstState=%s&includeLastState=false"; private static final String GROUPSV2_GROUP_CHANGES = "/v1/groups/logs/%s?maxSupportedChangeEpoch=%d&includeFirstState=%s&includeLastState=false";
@ -774,7 +775,7 @@ public class PushServiceSocket {
String version = profileKeyIdentifier.serialize(); String version = profileKeyIdentifier.serialize();
String credentialRequest = Hex.toStringCondensed(request.serialize()); String credentialRequest = Hex.toStringCondensed(request.serialize());
String subPath = String.format("%s/%s/%s", target, version, credentialRequest); String subPath = String.format("%s/%s/%s?credentialType=expiringProfileKey", target, version, credentialRequest);
ListenableFuture<String> response = submitServiceRequest(String.format(PROFILE_PATH, subPath), "GET", null, AcceptLanguagesUtil.getHeadersWithAcceptLanguage(locale), unidentifiedAccess); ListenableFuture<String> response = submitServiceRequest(String.format(PROFILE_PATH, subPath), "GET", null, AcceptLanguagesUtil.getHeadersWithAcceptLanguage(locale), unidentifiedAccess);
@ -789,10 +790,10 @@ public class PushServiceSocket {
SignalServiceProfile signalServiceProfile = JsonUtil.fromJson(body, SignalServiceProfile.class); SignalServiceProfile signalServiceProfile = JsonUtil.fromJson(body, SignalServiceProfile.class);
try { try {
ProfileKeyCredential profileKeyCredential = signalServiceProfile.getProfileKeyCredentialResponse() != null ExpiringProfileKeyCredential expiringProfileKeyCredential = signalServiceProfile.getExpiringProfileKeyCredentialResponse() != null
? clientZkProfileOperations.receiveProfileKeyCredential(requestContext, signalServiceProfile.getProfileKeyCredentialResponse()) ? clientZkProfileOperations.receiveExpiringProfileKeyCredential(requestContext, signalServiceProfile.getExpiringProfileKeyCredentialResponse())
: null; : null;
return new ProfileAndCredential(signalServiceProfile, SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL, Optional.ofNullable(profileKeyCredential)); return new ProfileAndCredential(signalServiceProfile, SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL, Optional.ofNullable(expiringProfileKeyCredential));
} catch (VerificationFailedException e) { } catch (VerificationFailedException e) {
Log.w(TAG, "Failed to verify credential.", e); Log.w(TAG, "Failed to verify credential.", e);
return new ProfileAndCredential(signalServiceProfile, SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL, Optional.empty()); return new ProfileAndCredential(signalServiceProfile, SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL, Optional.empty());
@ -2339,15 +2340,15 @@ public class PushServiceSocket {
public enum ClientSet { ContactDiscovery, KeyBackup } public enum ClientSet { ContactDiscovery, KeyBackup }
public CredentialResponse retrieveGroupsV2Credentials(int today, boolean isAci) public CredentialResponse retrieveGroupsV2Credentials(long todaySeconds)
throws IOException throws IOException
{ {
int todayPlus7 = today + 7; long todayPlus7 = todaySeconds + TimeUnit.DAYS.toSeconds(7);
String response = makeServiceRequest(String.format(Locale.US, GROUPSV2_CREDENTIAL, today, todayPlus7, isAci ? "aci" : "pni"), String response = makeServiceRequest(String.format(Locale.US, GROUPSV2_CREDENTIAL, todaySeconds, todayPlus7),
"GET", "GET",
null, null,
NO_HEADERS, NO_HEADERS,
Optional.empty()); Optional.empty());
return JsonUtil.fromJson(response, CredentialResponse.class); return JsonUtil.fromJson(response, CredentialResponse.class);
} }

Wyświetl plik

@ -17,6 +17,7 @@ message DecryptedMember {
Member.Role role = 2; Member.Role role = 2;
bytes profileKey = 3; bytes profileKey = 3;
uint32 joinedAtRevision = 5; uint32 joinedAtRevision = 5;
bytes pni = 6;
} }
message DecryptedPendingMember { message DecryptedPendingMember {
@ -73,29 +74,30 @@ message DecryptedGroup {
// Decrypted version of message GroupChange.Actions // Decrypted version of message GroupChange.Actions
// Keep field numbers in step // Keep field numbers in step
message DecryptedGroupChange { message DecryptedGroupChange {
bytes editor = 1; bytes editor = 1;
uint32 revision = 2; uint32 revision = 2;
repeated DecryptedMember newMembers = 3; repeated DecryptedMember newMembers = 3;
repeated bytes deleteMembers = 4; repeated bytes deleteMembers = 4;
repeated DecryptedModifyMemberRole modifyMemberRoles = 5; repeated DecryptedModifyMemberRole modifyMemberRoles = 5;
repeated DecryptedMember modifiedProfileKeys = 6; repeated DecryptedMember modifiedProfileKeys = 6;
repeated DecryptedPendingMember newPendingMembers = 7; repeated DecryptedPendingMember newPendingMembers = 7;
repeated DecryptedPendingMemberRemoval deletePendingMembers = 8; repeated DecryptedPendingMemberRemoval deletePendingMembers = 8;
repeated DecryptedMember promotePendingMembers = 9; repeated DecryptedMember promotePendingMembers = 9;
DecryptedString newTitle = 10; DecryptedString newTitle = 10;
DecryptedString newAvatar = 11; DecryptedString newAvatar = 11;
DecryptedTimer newTimer = 12; DecryptedTimer newTimer = 12;
AccessControl.AccessRequired newAttributeAccess = 13; AccessControl.AccessRequired newAttributeAccess = 13;
AccessControl.AccessRequired newMemberAccess = 14; AccessControl.AccessRequired newMemberAccess = 14;
AccessControl.AccessRequired newInviteLinkAccess = 15; AccessControl.AccessRequired newInviteLinkAccess = 15;
repeated DecryptedRequestingMember newRequestingMembers = 16; repeated DecryptedRequestingMember newRequestingMembers = 16;
repeated bytes deleteRequestingMembers = 17; repeated bytes deleteRequestingMembers = 17;
repeated DecryptedApproveMember promoteRequestingMembers = 18; repeated DecryptedApproveMember promoteRequestingMembers = 18;
bytes newInviteLinkPassword = 19; bytes newInviteLinkPassword = 19;
DecryptedString newDescription = 20; DecryptedString newDescription = 20;
EnabledState newIsAnnouncementGroup = 21; EnabledState newIsAnnouncementGroup = 21;
repeated DecryptedBannedMember newBannedMembers = 22; repeated DecryptedBannedMember newBannedMembers = 22;
repeated DecryptedBannedMember deleteBannedMembers = 23; repeated DecryptedBannedMember deleteBannedMembers = 23;
repeated DecryptedMember promotePendingPniAciMembers = 24;
} }
message DecryptedString { message DecryptedString {

Wyświetl plik

@ -99,7 +99,9 @@ message GroupChange {
} }
message ModifyMemberProfileKeyAction { message ModifyMemberProfileKeyAction {
bytes presentation = 1; bytes presentation = 1; // Only set when sending to server
bytes user_id = 2; // Only set when receiving from server
bytes profile_key = 3; // Only set when receiving from server
} }
message AddPendingMemberAction { message AddPendingMemberAction {
@ -111,7 +113,16 @@ message GroupChange {
} }
message PromotePendingMemberAction { message PromotePendingMemberAction {
bytes presentation = 1; bytes presentation = 1; // Only set when sending to server
bytes user_id = 2; // Only set when receiving from server
bytes profile_key = 3; // Only set when receiving from server
}
message PromotePendingPniAciMemberProfileKeyAction {
bytes presentation = 1; // Only set when sending to server
bytes userId = 2; // Only set when receiving from server
bytes pni = 3; // Only set when receiving from server
bytes profileKey = 4; // Only set when receiving from server
} }
message AddRequestingMemberAction { message AddRequestingMemberAction {
@ -194,6 +205,7 @@ message GroupChange {
ModifyAnnouncementsOnlyAction modifyAnnouncementsOnly = 21; ModifyAnnouncementsOnlyAction modifyAnnouncementsOnly = 21;
repeated AddBannedMemberAction addBannedMembers = 22; repeated AddBannedMemberAction addBannedMembers = 22;
repeated DeleteBannedMemberAction deleteBannedMembers = 23; repeated DeleteBannedMemberAction deleteBannedMembers = 23;
repeated PromotePendingPniAciMemberProfileKeyAction promotePendingPniAciMembers = 24;
} }
bytes actions = 1; bytes actions = 1;

Wyświetl plik

@ -31,6 +31,7 @@ import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.banne
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.member; import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.member;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.newProfileKey; import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.newProfileKey;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.pendingMember; import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.pendingMember;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.pendingPniAciMember;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.randomProfileKey; import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.randomProfileKey;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.requestingMember; import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.requestingMember;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.withProfileKey; import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.withProfileKey;
@ -48,7 +49,7 @@ public final class DecryptedGroupUtil_apply_Test {
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroupChange.class); int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroupChange.class);
assertEquals("DecryptedGroupUtil and its tests need updating to account for new fields on " + DecryptedGroupChange.class.getName(), assertEquals("DecryptedGroupUtil and its tests need updating to account for new fields on " + DecryptedGroupChange.class.getName(),
23, maxFieldFound); 24, maxFieldFound);
} }
@Test @Test
@ -958,4 +959,31 @@ public final class DecryptedGroupUtil_apply_Test {
.build(), .build(),
newGroup); newGroup);
} }
@Test
public void promote_pending_member_pni_aci() throws NotAbleToApplyGroupV2ChangeException {
ProfileKey profileKey2 = randomProfileKey();
DecryptedMember member1 = member(UUID.randomUUID());
UUID pending2Aci = UUID.randomUUID();
UUID pending2Pni = UUID.randomUUID();
DecryptedPendingMember pending2 = pendingMember(pending2Pni);
DecryptedMember member2 = pendingPniAciMember(pending2Aci, pending2Pni, profileKey2);
DecryptedGroup newGroup = DecryptedGroupUtil.apply(DecryptedGroup.newBuilder()
.setRevision(10)
.addMembers(member1)
.addPendingMembers(pending2)
.build(),
DecryptedGroupChange.newBuilder()
.setRevision(11)
.addPromotePendingPniAciMembers(member2)
.build());
assertEquals(DecryptedGroup.newBuilder()
.setRevision(11)
.addMembers(member1)
.addMembers(member2)
.build(),
newGroup);
}
} }

Wyświetl plik

@ -20,6 +20,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.member; import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.member;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.pendingMember; import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.pendingMember;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.pendingPniAciMember;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.pendingMemberRemoval; import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.pendingMemberRemoval;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.promoteAdmin; import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.promoteAdmin;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.randomProfileKey; import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.randomProfileKey;
@ -37,7 +38,7 @@ public final class DecryptedGroupUtil_empty_Test {
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroupChange.class); int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroupChange.class);
assertEquals("DecryptedGroupUtil and its tests need updating to account for new fields on " + DecryptedGroupChange.class.getName(), assertEquals("DecryptedGroupUtil and its tests need updating to account for new fields on " + DecryptedGroupChange.class.getName(),
23, maxFieldFound); 24, maxFieldFound);
} }
@Test @Test
@ -254,4 +255,14 @@ public final class DecryptedGroupUtil_empty_Test {
assertFalse(DecryptedGroupUtil.changeIsEmpty(change)); assertFalse(DecryptedGroupUtil.changeIsEmpty(change));
assertFalse(DecryptedGroupUtil.changeIsEmptyExceptForProfileKeyChanges(change)); assertFalse(DecryptedGroupUtil.changeIsEmptyExceptForProfileKeyChanges(change));
} }
@Test
public void not_empty_with_promote_pending_pni_aci_members_field_24() {
DecryptedGroupChange change = DecryptedGroupChange.newBuilder()
.addPromotePendingPniAciMembers(pendingPniAciMember(UUID.randomUUID(), UUID.randomUUID(), randomProfileKey()))
.build();
assertFalse(DecryptedGroupUtil.changeIsEmpty(change));
assertFalse(DecryptedGroupUtil.changeIsEmptyExceptForProfileKeyChanges(change));
}
} }

Wyświetl plik

@ -20,7 +20,7 @@ public final class GroupChangeUtil_changeIsEmpty_Test {
int maxFieldFound = getMaxDeclaredFieldNumber(GroupChange.Actions.class); int maxFieldFound = getMaxDeclaredFieldNumber(GroupChange.Actions.class);
assertEquals("GroupChangeUtil and its tests need updating to account for new fields on " + GroupChange.Actions.class.getName(), assertEquals("GroupChangeUtil and its tests need updating to account for new fields on " + GroupChange.Actions.class.getName(),
23, maxFieldFound); 24, maxFieldFound);
} }
@Test @Test
@ -216,4 +216,13 @@ public final class GroupChangeUtil_changeIsEmpty_Test {
assertFalse(GroupChangeUtil.changeIsEmpty(actions)); assertFalse(GroupChangeUtil.changeIsEmpty(actions));
} }
@Test
public void not_empty_with_promote_pending_pni_aci_members_field_24() {
GroupChange.Actions actions = GroupChange.Actions.newBuilder()
.addPromotePendingPniAciMembers(GroupChange.Actions.PromotePendingPniAciMemberProfileKeyAction.getDefaultInstance())
.build();
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
}
} }

Wyświetl plik

@ -11,6 +11,7 @@ import org.signal.storageservice.protos.groups.Member;
import org.signal.storageservice.protos.groups.PendingMember; import org.signal.storageservice.protos.groups.PendingMember;
import org.signal.storageservice.protos.groups.local.DecryptedGroup; import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange; import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
import org.signal.storageservice.protos.groups.local.DecryptedMember;
import org.signal.storageservice.protos.groups.local.DecryptedString; import org.signal.storageservice.protos.groups.local.DecryptedString;
import org.signal.storageservice.protos.groups.local.DecryptedTimer; import org.signal.storageservice.protos.groups.local.DecryptedTimer;
import org.signal.storageservice.protos.groups.local.EnabledState; import org.signal.storageservice.protos.groups.local.EnabledState;
@ -31,6 +32,7 @@ import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.encry
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.member; import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.member;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.pendingMember; import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.pendingMember;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.pendingMemberRemoval; import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.pendingMemberRemoval;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.pendingPniAciMember;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.presentation; import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.presentation;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.promoteAdmin; import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.promoteAdmin;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.randomProfileKey; import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.randomProfileKey;
@ -49,7 +51,7 @@ public final class GroupChangeUtil_resolveConflict_Test {
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroupChange.class); int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroupChange.class);
assertEquals("GroupChangeUtil#resolveConflict and its tests need updating to account for new fields on " + DecryptedGroupChange.class.getName(), assertEquals("GroupChangeUtil#resolveConflict and its tests need updating to account for new fields on " + DecryptedGroupChange.class.getName(),
23, maxFieldFound); 24, maxFieldFound);
} }
/** /**
@ -62,7 +64,7 @@ public final class GroupChangeUtil_resolveConflict_Test {
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroupChange.class); int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroupChange.class);
assertEquals("GroupChangeUtil#resolveConflict and its tests need updating to account for new fields on " + GroupChange.class.getName(), assertEquals("GroupChangeUtil#resolveConflict and its tests need updating to account for new fields on " + GroupChange.class.getName(),
23, maxFieldFound); 24, maxFieldFound);
} }
/** /**
@ -802,4 +804,31 @@ public final class GroupChangeUtil_resolveConflict_Test {
.build(); .build();
assertEquals(expected, resolvedActions); assertEquals(expected, resolvedActions);
} }
@Test
public void field_24__promote_pending_members() {
DecryptedMember member1 = pendingPniAciMember(UUID.randomUUID(), UUID.randomUUID(), randomProfileKey());
DecryptedMember member2 = pendingPniAciMember(UUID.randomUUID(), UUID.randomUUID(), randomProfileKey());
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.addMembers(member(UuidUtil.fromByteString(member1.getUuid())))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.addPromotePendingPniAciMembers(pendingPniAciMember(member1.getUuid(), member1.getPni(), member1.getProfileKey()))
.addPromotePendingPniAciMembers(pendingPniAciMember(member2.getUuid(), member2.getPni(), member2.getProfileKey()))
.build();
GroupChange.Actions change = GroupChange.Actions.newBuilder()
.addPromotePendingPniAciMembers(GroupChange.Actions.PromotePendingPniAciMemberProfileKeyAction.newBuilder().setPresentation(presentation(member1.getPni(), member1.getProfileKey())))
.addPromotePendingPniAciMembers(GroupChange.Actions.PromotePendingPniAciMemberProfileKeyAction.newBuilder().setPresentation(presentation(member2.getPni(), member2.getProfileKey())))
.build();
GroupChange.Actions resolvedActions = GroupChangeUtil.resolveConflict(groupState, decryptedChange, change).build();
GroupChange.Actions expected = GroupChange.Actions.newBuilder()
.addPromotePendingPniAciMembers(GroupChange.Actions.PromotePendingPniAciMemberProfileKeyAction.newBuilder().setPresentation(presentation(member2.getPni(), member2.getProfileKey())))
.build();
assertEquals(expected, resolvedActions);
}
} }

Wyświetl plik

@ -5,8 +5,10 @@ import com.google.protobuf.ByteString;
import org.junit.Test; import org.junit.Test;
import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.signal.storageservice.protos.groups.AccessControl; import org.signal.storageservice.protos.groups.AccessControl;
import org.signal.storageservice.protos.groups.GroupChange;
import org.signal.storageservice.protos.groups.local.DecryptedGroup; import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange; import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
import org.signal.storageservice.protos.groups.local.DecryptedMember;
import org.signal.storageservice.protos.groups.local.DecryptedString; import org.signal.storageservice.protos.groups.local.DecryptedString;
import org.signal.storageservice.protos.groups.local.DecryptedTimer; import org.signal.storageservice.protos.groups.local.DecryptedTimer;
import org.signal.storageservice.protos.groups.local.EnabledState; import org.signal.storageservice.protos.groups.local.EnabledState;
@ -24,6 +26,8 @@ import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.demot
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.member; import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.member;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.pendingMember; import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.pendingMember;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.pendingMemberRemoval; import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.pendingMemberRemoval;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.pendingPniAciMember;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.presentation;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.promoteAdmin; import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.promoteAdmin;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.randomProfileKey; import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.randomProfileKey;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.requestingMember; import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.requestingMember;
@ -41,7 +45,7 @@ public final class GroupChangeUtil_resolveConflict_decryptedOnly_Test {
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroupChange.class); int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroupChange.class);
assertEquals("GroupChangeUtil#resolveConflict and its tests need updating to account for new fields on " + DecryptedGroupChange.class.getName(), assertEquals("GroupChangeUtil#resolveConflict and its tests need updating to account for new fields on " + DecryptedGroupChange.class.getName(),
23, maxFieldFound); 24, maxFieldFound);
} }
/** /**
@ -650,4 +654,27 @@ public final class GroupChangeUtil_resolveConflict_decryptedOnly_Test {
assertEquals(expected, resolvedChanges); assertEquals(expected, resolvedChanges);
} }
@Test
public void field_24__promote_pending_members() {
DecryptedMember member1 = pendingPniAciMember(UUID.randomUUID(), UUID.randomUUID(), randomProfileKey());
DecryptedMember member2 = pendingPniAciMember(UUID.randomUUID(), UUID.randomUUID(), randomProfileKey());
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.addMembers(member(UuidUtil.fromByteString(member1.getUuid())))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.addPromotePendingPniAciMembers(pendingPniAciMember(member1.getUuid(), member1.getPni(), member1.getProfileKey()))
.addPromotePendingPniAciMembers(pendingPniAciMember(member2.getUuid(), member2.getPni(), member2.getProfileKey()))
.build();
DecryptedGroupChange resolvedChanges = GroupChangeUtil.resolveConflict(groupState, decryptedChange).build();
DecryptedGroupChange expected = DecryptedGroupChange.newBuilder()
.addPromotePendingPniAciMembers(pendingPniAciMember(member2.getUuid(), member2.getPni(), member2.getProfileKey()))
.build();
assertEquals(expected, resolvedChanges);
}
} }

Wyświetl plik

@ -7,17 +7,18 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.VerificationFailedException; import org.signal.libsignal.zkgroup.VerificationFailedException;
import org.signal.libsignal.zkgroup.groups.ClientZkGroupCipher;
import org.signal.libsignal.zkgroup.groups.GroupMasterKey; import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
import org.signal.libsignal.zkgroup.groups.GroupSecretParams; import org.signal.libsignal.zkgroup.groups.GroupSecretParams;
import org.signal.libsignal.zkgroup.groups.UuidCiphertext; import org.signal.libsignal.zkgroup.groups.UuidCiphertext;
import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations; import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations;
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredentialResponse;
import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCommitment; import org.signal.libsignal.zkgroup.profiles.ProfileKeyCommitment;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredential;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialPresentation; import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialPresentation;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialRequest; import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialRequest;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialRequestContext; import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialRequestContext;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialResponse;
import org.signal.storageservice.protos.groups.AccessControl; import org.signal.storageservice.protos.groups.AccessControl;
import org.signal.storageservice.protos.groups.GroupChange; import org.signal.storageservice.protos.groups.GroupChange;
import org.signal.storageservice.protos.groups.Member; import org.signal.storageservice.protos.groups.Member;
@ -36,6 +37,8 @@ import org.whispersystems.signalservice.api.util.UuidUtil;
import org.whispersystems.signalservice.internal.util.Util; import org.whispersystems.signalservice.internal.util.Util;
import org.whispersystems.signalservice.testutil.LibSignalLibraryUtil; import org.whispersystems.signalservice.testutil.LibSignalLibraryUtil;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Collections; import java.util.Collections;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
@ -66,7 +69,8 @@ public final class GroupsV2Operations_decrypt_change_Test {
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroupChange.class); int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroupChange.class);
assertEquals("GroupV2Operations#decryptChange and its tests need updating to account for new fields on " + DecryptedGroupChange.class.getName(), assertEquals("GroupV2Operations#decryptChange and its tests need updating to account for new fields on " + DecryptedGroupChange.class.getName(),
23, maxFieldFound); 24,
maxFieldFound);
} }
@Test @Test
@ -112,7 +116,7 @@ public final class GroupsV2Operations_decrypt_change_Test {
ProfileKey profileKey = newProfileKey(); ProfileKey profileKey = newProfileKey();
GroupCandidate groupCandidate = groupCandidate(newMember, profileKey); GroupCandidate groupCandidate = groupCandidate(newMember, profileKey);
assertDecryption(groupOperations.createGroupJoinDirect(groupCandidate.getProfileKeyCredential().get()) assertDecryption(groupOperations.createGroupJoinDirect(groupCandidate.getExpiringProfileKeyCredential().get())
.setRevision(10), .setRevision(10),
DecryptedGroupChange.newBuilder() DecryptedGroupChange.newBuilder()
.setRevision(10) .setRevision(10)
@ -148,7 +152,7 @@ public final class GroupsV2Operations_decrypt_change_Test {
actions.addAddMembers(GroupChange.Actions.AddMemberAction.newBuilder() actions.addAddMembers(GroupChange.Actions.AddMemberAction.newBuilder()
.setAdded(Member.newBuilder().setRole(Member.Role.DEFAULT) .setAdded(Member.newBuilder().setRole(Member.Role.DEFAULT)
.setPresentation(ByteString.copyFrom(randomPresentation)))); .setPresentation(ByteString.copyFrom(randomPresentation))));
groupOperations.decryptChange(GroupChange.newBuilder().setActions(actions.build().toByteString()).build(), false); groupOperations.decryptChange(GroupChange.newBuilder().setActions(actions.build().toByteString()).build(), false);
} }
@ -203,7 +207,7 @@ public final class GroupsV2Operations_decrypt_change_Test {
ProfileKey profileKey = newProfileKey(); ProfileKey profileKey = newProfileKey();
GroupCandidate groupCandidate = groupCandidate(self, profileKey); GroupCandidate groupCandidate = groupCandidate(self, profileKey);
assertDecryption(groupOperations.createUpdateProfileKeyCredentialChange(groupCandidate.getProfileKeyCredential().get()) assertDecryption(groupOperations.createUpdateProfileKeyCredentialChange(groupCandidate.getExpiringProfileKeyCredential().get())
.setRevision(10), .setRevision(10),
DecryptedGroupChange.newBuilder() DecryptedGroupChange.newBuilder()
.setRevision(10) .setRevision(10)
@ -230,7 +234,7 @@ public final class GroupsV2Operations_decrypt_change_Test {
.setRole(Member.Role.DEFAULT) .setRole(Member.Role.DEFAULT)
.setUuid(UuidUtil.toByteString(newMember)))); .setUuid(UuidUtil.toByteString(newMember))));
} }
@Test @Test
public void can_decrypt_pending_member_removals_field8() throws InvalidInputException { public void can_decrypt_pending_member_removals_field8() throws InvalidInputException {
UUID oldMember = UUID.randomUUID(); UUID oldMember = UUID.randomUUID();
@ -248,9 +252,9 @@ public final class GroupsV2Operations_decrypt_change_Test {
byte[] uuidCiphertext = Util.getSecretBytes(60); byte[] uuidCiphertext = Util.getSecretBytes(60);
assertDecryption(GroupChange.Actions assertDecryption(GroupChange.Actions
.newBuilder() .newBuilder()
.addDeletePendingMembers(GroupChange.Actions.DeletePendingMemberAction.newBuilder() .addDeletePendingMembers(GroupChange.Actions.DeletePendingMemberAction.newBuilder()
.setDeletedUserId(ByteString.copyFrom(uuidCiphertext))), .setDeletedUserId(ByteString.copyFrom(uuidCiphertext))),
DecryptedGroupChange.newBuilder() DecryptedGroupChange.newBuilder()
.addDeletePendingMembers(DecryptedPendingMemberRemoval.newBuilder() .addDeletePendingMembers(DecryptedPendingMemberRemoval.newBuilder()
.setUuid(UuidUtil.toByteString(UuidUtil.UNKNOWN_UUID)) .setUuid(UuidUtil.toByteString(UuidUtil.UNKNOWN_UUID))
@ -263,7 +267,7 @@ public final class GroupsV2Operations_decrypt_change_Test {
ProfileKey profileKey = newProfileKey(); ProfileKey profileKey = newProfileKey();
GroupCandidate groupCandidate = groupCandidate(newMember, profileKey); GroupCandidate groupCandidate = groupCandidate(newMember, profileKey);
assertDecryption(groupOperations.createAcceptInviteChange(groupCandidate.getProfileKeyCredential().get()), assertDecryption(groupOperations.createAcceptInviteChange(groupCandidate.getExpiringProfileKeyCredential().get()),
DecryptedGroupChange.newBuilder() DecryptedGroupChange.newBuilder()
.addPromotePendingMembers(DecryptedMember.newBuilder() .addPromotePendingMembers(DecryptedMember.newBuilder()
.setUuid(UuidUtil.toByteString(newMember)) .setUuid(UuidUtil.toByteString(newMember))
@ -328,7 +332,7 @@ public final class GroupsV2Operations_decrypt_change_Test {
ProfileKey profileKey = newProfileKey(); ProfileKey profileKey = newProfileKey();
GroupCandidate groupCandidate = groupCandidate(newRequestingMember, profileKey); GroupCandidate groupCandidate = groupCandidate(newRequestingMember, profileKey);
assertDecryption(groupOperations.createGroupJoinRequest(groupCandidate.getProfileKeyCredential().get()) assertDecryption(groupOperations.createGroupJoinRequest(groupCandidate.getExpiringProfileKeyCredential().get())
.setRevision(10), .setRevision(10),
DecryptedGroupChange.newBuilder() DecryptedGroupChange.newBuilder()
.setRevision(10) .setRevision(10)
@ -368,9 +372,9 @@ public final class GroupsV2Operations_decrypt_change_Test {
assertDecryption(GroupChange.Actions.newBuilder() assertDecryption(GroupChange.Actions.newBuilder()
.setModifyInviteLinkPassword(GroupChange.Actions.ModifyInviteLinkPasswordAction.newBuilder() .setModifyInviteLinkPassword(GroupChange.Actions.ModifyInviteLinkPasswordAction.newBuilder()
.setInviteLinkPassword(ByteString.copyFrom(newPassword))), .setInviteLinkPassword(ByteString.copyFrom(newPassword))),
DecryptedGroupChange.newBuilder() DecryptedGroupChange.newBuilder()
.setNewInviteLinkPassword(ByteString.copyFrom(newPassword))); .setNewInviteLinkPassword(ByteString.copyFrom(newPassword)));
} }
@Test @Test
@ -413,6 +417,32 @@ public final class GroupsV2Operations_decrypt_change_Test {
.setUuid(UuidUtil.toByteString(ban)))); .setUuid(UuidUtil.toByteString(ban))));
} }
@Test
public void can_decrypt_promote_pending_pni_aci_member_field24() {
UUID memberUuid = UUID.randomUUID();
UUID memberPni = UUID.randomUUID();
ProfileKey profileKey = newProfileKey();
GroupChange.Actions.Builder builder = GroupChange.Actions.newBuilder()
.setSourceUuid(groupOperations.encryptUuid(memberPni))
.setRevision(5)
.addPromotePendingPniAciMembers(GroupChange.Actions.PromotePendingPniAciMemberProfileKeyAction.newBuilder()
.setUserId(groupOperations.encryptUuid(memberUuid))
.setPni(groupOperations.encryptUuid(memberPni))
.setProfileKey(encryptProfileKey(memberUuid, profileKey)));
assertDecryptionWithEditorSet(builder,
DecryptedGroupChange.newBuilder()
.setEditor(UuidUtil.toByteString(memberUuid))
.setRevision(5)
.addPromotePendingPniAciMembers(DecryptedMember.newBuilder()
.setUuid(UuidUtil.toByteString(memberUuid))
.setPni(UuidUtil.toByteString(memberPni))
.setRole(Member.Role.DEFAULT)
.setProfileKey(ByteString.copyFrom(profileKey.serialize()))
.setJoinedAtRevision(5)));
}
private static ProfileKey newProfileKey() { private static ProfileKey newProfileKey() {
try { try {
return new ProfileKey(Util.getSecretBytes(32)); return new ProfileKey(Util.getSecretBytes(32));
@ -421,22 +451,26 @@ public final class GroupsV2Operations_decrypt_change_Test {
} }
} }
private ByteString encryptProfileKey(UUID uuid, ProfileKey profileKey) {
return ByteString.copyFrom(new ClientZkGroupCipher(groupSecretParams).encryptProfileKey(profileKey, uuid).serialize());
}
static GroupCandidate groupCandidate(UUID uuid) { static GroupCandidate groupCandidate(UUID uuid) {
return new GroupCandidate(uuid, Optional.empty()); return new GroupCandidate(uuid, Optional.empty());
} }
GroupCandidate groupCandidate(UUID uuid, ProfileKey profileKey) { GroupCandidate groupCandidate(UUID uuid, ProfileKey profileKey) {
try { try {
ClientZkProfileOperations profileOperations = clientZkOperations.getProfileOperations(); ClientZkProfileOperations profileOperations = clientZkOperations.getProfileOperations();
ProfileKeyCommitment commitment = profileKey.getCommitment(uuid); ProfileKeyCommitment commitment = profileKey.getCommitment(uuid);
ProfileKeyCredentialRequestContext requestContext = profileOperations.createProfileKeyCredentialRequestContext(uuid, profileKey); ProfileKeyCredentialRequestContext requestContext = profileOperations.createProfileKeyCredentialRequestContext(uuid, profileKey);
ProfileKeyCredentialRequest request = requestContext.getRequest(); ProfileKeyCredentialRequest request = requestContext.getRequest();
ProfileKeyCredentialResponse profileKeyCredentialResponse = server.getProfileKeyCredentialResponse(request, uuid, commitment); ExpiringProfileKeyCredentialResponse expiringProfileKeyCredentialResponse = server.getExpiringProfileKeyCredentialResponse(request, uuid, commitment, Instant.now().plus(7, ChronoUnit.DAYS).truncatedTo(ChronoUnit.DAYS));
ProfileKeyCredential profileKeyCredential = profileOperations.receiveProfileKeyCredential(requestContext, profileKeyCredentialResponse); ExpiringProfileKeyCredential profileKeyCredential = profileOperations.receiveExpiringProfileKeyCredential(requestContext, expiringProfileKeyCredentialResponse);
GroupCandidate groupCandidate = new GroupCandidate(uuid, Optional.of(profileKeyCredential)); GroupCandidate groupCandidate = new GroupCandidate(uuid, Optional.of(profileKeyCredential));
ProfileKeyCredentialPresentation presentation = profileOperations.createProfileKeyCredentialPresentation(groupSecretParams, profileKeyCredential); ProfileKeyCredentialPresentation presentation = profileOperations.createProfileKeyCredentialPresentation(groupSecretParams, profileKeyCredential);
server.assertProfileKeyCredentialPresentation(groupSecretParams.getPublicParams(), presentation); server.assertProfileKeyCredentialPresentation(groupSecretParams.getPublicParams(), presentation, Instant.now());
return groupCandidate; return groupCandidate;
} catch (VerificationFailedException e) { } catch (VerificationFailedException e) {
@ -447,9 +481,14 @@ public final class GroupsV2Operations_decrypt_change_Test {
void assertDecryption(GroupChange.Actions.Builder inputChange, void assertDecryption(GroupChange.Actions.Builder inputChange,
DecryptedGroupChange.Builder expectedDecrypted) DecryptedGroupChange.Builder expectedDecrypted)
{ {
UUID editor = UUID.randomUUID(); UUID editor = UUID.randomUUID();
GroupChange.Actions actions = inputChange.setSourceUuid(groupOperations.encryptUuid(editor)) assertDecryptionWithEditorSet(inputChange.setSourceUuid(groupOperations.encryptUuid(editor)), expectedDecrypted.setEditor(UuidUtil.toByteString(editor)));
.build(); }
void assertDecryptionWithEditorSet(GroupChange.Actions.Builder inputChange,
DecryptedGroupChange.Builder expectedDecrypted)
{
GroupChange.Actions actions = inputChange.build();
GroupChange change = GroupChange.newBuilder() GroupChange change = GroupChange.newBuilder()
.setActions(actions.toByteString()) .setActions(actions.toByteString())
@ -457,8 +496,7 @@ public final class GroupsV2Operations_decrypt_change_Test {
DecryptedGroupChange decryptedGroupChange = decrypt(change); DecryptedGroupChange decryptedGroupChange = decrypt(change);
assertEquals(expectedDecrypted.setEditor(UuidUtil.toByteString(editor)) assertEquals(expectedDecrypted.build(),
.build(),
decryptedGroupChange); decryptedGroupChange);
} }
@ -466,7 +504,7 @@ public final class GroupsV2Operations_decrypt_change_Test {
try { try {
return groupOperations.decryptChange(build, false).get(); return groupOperations.decryptChange(build, false).get();
} catch (InvalidProtocolBufferException | VerificationFailedException | InvalidGroupStateException e) { } catch (InvalidProtocolBufferException | VerificationFailedException | InvalidGroupStateException e) {
throw new AssertionError(e); throw new AssertionError(e);
} }
} }

Wyświetl plik

@ -55,6 +55,14 @@ final class ProtoTestUtils {
return ByteString.copyFrom(concat); return ByteString.copyFrom(concat);
} }
/**
* Emulates a presentation by concatenating the uuid and profile key which makes it suitable for
* equality assertions in these tests.
*/
static ByteString presentation(ByteString uuid, ByteString profileKey) {
return uuid.concat(profileKey);
}
static DecryptedModifyMemberRole promoteAdmin(UUID member) { static DecryptedModifyMemberRole promoteAdmin(UUID member) {
return DecryptedModifyMemberRole.newBuilder() return DecryptedModifyMemberRole.newBuilder()
.setUuid(UuidUtil.toByteString(member)) .setUuid(UuidUtil.toByteString(member))
@ -148,6 +156,22 @@ final class ProtoTestUtils {
return withProfileKey(member(uuid), profileKey); return withProfileKey(member(uuid), profileKey);
} }
static DecryptedMember pendingPniAciMember(UUID uuid, UUID pni, ProfileKey profileKey) {
return DecryptedMember.newBuilder()
.setUuid(UuidUtil.toByteString(uuid))
.setPni(UuidUtil.toByteString(pni))
.setProfileKey(ByteString.copyFrom(profileKey.serialize()))
.build();
}
static DecryptedMember pendingPniAciMember(ByteString uuid, ByteString pni, ByteString profileKey) {
return DecryptedMember.newBuilder()
.setUuid(uuid)
.setPni(pni)
.setProfileKey(profileKey)
.build();
}
static DecryptedMember admin(UUID uuid, ProfileKey profileKey) { static DecryptedMember admin(UUID uuid, ProfileKey profileKey) {
return withProfileKey(admin(uuid), profileKey); return withProfileKey(admin(uuid), profileKey);
} }

Wyświetl plik

@ -4,13 +4,14 @@ import org.signal.libsignal.zkgroup.ServerPublicParams;
import org.signal.libsignal.zkgroup.ServerSecretParams; import org.signal.libsignal.zkgroup.ServerSecretParams;
import org.signal.libsignal.zkgroup.VerificationFailedException; import org.signal.libsignal.zkgroup.VerificationFailedException;
import org.signal.libsignal.zkgroup.groups.GroupPublicParams; import org.signal.libsignal.zkgroup.groups.GroupPublicParams;
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredentialResponse;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCommitment; import org.signal.libsignal.zkgroup.profiles.ProfileKeyCommitment;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialPresentation; import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialPresentation;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialRequest; import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialRequest;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialResponse;
import org.signal.libsignal.zkgroup.profiles.ServerZkProfileOperations; import org.signal.libsignal.zkgroup.profiles.ServerZkProfileOperations;
import org.whispersystems.signalservice.testutil.LibSignalLibraryUtil; import org.whispersystems.signalservice.testutil.LibSignalLibraryUtil;
import java.time.Instant;
import java.util.UUID; import java.util.UUID;
/** /**
@ -34,13 +35,13 @@ final class TestZkGroupServer {
return serverPublicParams; return serverPublicParams;
} }
public ProfileKeyCredentialResponse getProfileKeyCredentialResponse(ProfileKeyCredentialRequest request, UUID uuid, ProfileKeyCommitment commitment) throws VerificationFailedException { public ExpiringProfileKeyCredentialResponse getExpiringProfileKeyCredentialResponse(ProfileKeyCredentialRequest request, UUID uuid, ProfileKeyCommitment commitment, Instant expiration) throws VerificationFailedException {
return serverZkProfileOperations.issueProfileKeyCredential(request, uuid, commitment); return serverZkProfileOperations.issueExpiringProfileKeyCredential(request, uuid, commitment, expiration);
} }
public void assertProfileKeyCredentialPresentation(GroupPublicParams publicParams, ProfileKeyCredentialPresentation profileKeyCredentialPresentation) { public void assertProfileKeyCredentialPresentation(GroupPublicParams publicParams, ProfileKeyCredentialPresentation profileKeyCredentialPresentation, Instant now) {
try { try {
serverZkProfileOperations.verifyProfileKeyCredentialPresentation(publicParams, profileKeyCredentialPresentation); serverZkProfileOperations.verifyProfileKeyCredentialPresentation(publicParams, profileKeyCredentialPresentation, now);
} catch (VerificationFailedException e) { } catch (VerificationFailedException e) {
throw new AssertionError(e); throw new AssertionError(e);
} }

Wyświetl plik

@ -102,10 +102,10 @@ internal class SpinnerServer(
database = dbName, database = dbName,
databases = databases.keys.toList(), databases = databases.keys.toList(),
plugins = plugins.values.toList(), plugins = plugins.values.toList(),
tables = db.getTables().toTableInfo(), tables = db.getTables().use { it.toTableInfo() },
indices = db.getIndexes().toIndexInfo(), indices = db.getIndexes().use { it.toIndexInfo() },
triggers = db.getTriggers().toTriggerInfo(), triggers = db.getTriggers().use { it.toTriggerInfo() },
queryResult = db.getTables().toQueryResult() queryResult = db.getTables().use { it.toQueryResult() }
) )
) )
} }
@ -141,7 +141,7 @@ internal class SpinnerServer(
} }
val query = "select * from $table limit $pageSize offset ${pageSize * pageIndex}" val query = "select * from $table limit $pageSize offset ${pageSize * pageIndex}"
val queryResult = dbConfig.db.query(query).toQueryResult(columnTransformers = dbConfig.columnTransformers) val queryResult = dbConfig.db.query(query).use { it.toQueryResult(columnTransformers = dbConfig.columnTransformers) }
return renderTemplate( return renderTemplate(
"browse", "browse",
@ -217,7 +217,7 @@ internal class SpinnerServer(
databases = databases.keys.toList(), databases = databases.keys.toList(),
plugins = plugins.values.toList(), plugins = plugins.values.toList(),
query = rawQuery, query = rawQuery,
queryResult = dbConfig.db.query(query).toQueryResult(queryStartTime = startTime, columnTransformers = dbConfig.columnTransformers) queryResult = dbConfig.db.query(query).use { it.toQueryResult(queryStartTime = startTime, columnTransformers = dbConfig.columnTransformers) }
) )
) )
} }