Allow using data URIs for updateGroup/updateProfile avatars

Fixes #1082
This commit is contained in:
AsamK 2022-11-14 19:31:40 +01:00
parent dcaf1cc189
commit 5771bb858f
11 changed files with 60 additions and 73 deletions

View file

@ -102,7 +102,7 @@ public interface Manager extends Closeable {
void deleteGroup(GroupId groupId) throws IOException;
Pair<GroupId, SendGroupMessageResults> createGroup(
String name, Set<RecipientIdentifier.Single> members, File avatarFile
String name, Set<RecipientIdentifier.Single> members, String avatarFile
) throws IOException, AttachmentInvalidException, UnregisteredRecipientException;
SendGroupMessageResults updateGroup(

View file

@ -394,7 +394,7 @@ class ManagerImpl implements Manager {
@Override
public Pair<GroupId, SendGroupMessageResults> createGroup(
String name, Set<RecipientIdentifier.Single> members, File avatarFile
String name, Set<RecipientIdentifier.Single> members, String avatarFile
) throws IOException, AttachmentInvalidException, UnregisteredRecipientException {
return context.getGroupHelper()
.createGroup(name,

View file

@ -3,7 +3,6 @@ package org.asamk.signal.manager.api;
import org.asamk.signal.manager.groups.GroupLinkState;
import org.asamk.signal.manager.groups.GroupPermission;
import java.io.File;
import java.util.Set;
public class UpdateGroup {
@ -20,7 +19,7 @@ public class UpdateGroup {
private final GroupLinkState groupLinkState;
private final GroupPermission addMemberPermission;
private final GroupPermission editDetailsPermission;
private final File avatarFile;
private final String avatarFile;
private final Integer expirationTimer;
private final Boolean isAnnouncementGroup;
@ -77,7 +76,7 @@ public class UpdateGroup {
final GroupLinkState groupLinkState,
final GroupPermission addMemberPermission,
final GroupPermission editDetailsPermission,
final File avatarFile,
final String avatarFile,
final Integer expirationTimer,
final Boolean isAnnouncementGroup
) {
@ -146,7 +145,7 @@ public class UpdateGroup {
return editDetailsPermission;
}
public File getAvatarFile() {
public String getAvatarFile() {
return avatarFile;
}
@ -172,7 +171,7 @@ public class UpdateGroup {
private GroupLinkState groupLinkState;
private GroupPermission addMemberPermission;
private GroupPermission editDetailsPermission;
private File avatarFile;
private String avatarFile;
private Integer expirationTimer;
private Boolean isAnnouncementGroup;
@ -192,7 +191,7 @@ public class UpdateGroup {
final GroupLinkState groupLinkState,
final GroupPermission addMemberPermission,
final GroupPermission editDetailsPermission,
final File avatarFile,
final String avatarFile,
final Integer expirationTimer,
final Boolean isAnnouncementGroup
) {
@ -273,7 +272,7 @@ public class UpdateGroup {
return this;
}
public Builder withAvatarFile(final File val) {
public Builder withAvatarFile(final String val) {
avatarFile = val;
return this;
}

View file

@ -1,14 +1,12 @@
package org.asamk.signal.manager.api;
import java.io.File;
public class UpdateProfile {
private final String givenName;
private final String familyName;
private final String about;
private final String aboutEmoji;
private final File avatar;
private final String avatar;
private final boolean deleteAvatar;
private final byte[] mobileCoinAddress;
@ -54,7 +52,7 @@ public class UpdateProfile {
return aboutEmoji;
}
public File getAvatar() {
public String getAvatar() {
return avatar;
}
@ -72,7 +70,7 @@ public class UpdateProfile {
private String familyName;
private String about;
private String aboutEmoji;
private File avatar;
private String avatar;
private boolean deleteAvatar;
private byte[] mobileCoinAddress;
@ -99,7 +97,7 @@ public class UpdateProfile {
return this;
}
public Builder withAvatar(final File val) {
public Builder withAvatar(final String val) {
avatar = val;
return this;
}

View file

@ -26,6 +26,7 @@ import org.asamk.signal.manager.storage.groups.GroupInfoV2;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.asamk.signal.manager.util.AttachmentUtils;
import org.asamk.signal.manager.util.IOUtils;
import org.asamk.signal.manager.util.Utils;
import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
import org.signal.libsignal.zkgroup.groups.GroupSecretParams;
@ -47,7 +48,6 @@ import org.whispersystems.signalservice.api.push.DistributionId;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.exceptions.ConflictException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@ -164,7 +164,7 @@ public class GroupHelper {
}
public Pair<GroupId, SendGroupMessageResults> createGroup(
String name, Set<RecipientId> members, File avatarFile
String name, Set<RecipientId> members, String avatarFile
) throws IOException, AttachmentInvalidException {
final var selfRecipientId = account.getSelfRecipientId();
if (members != null && members.contains(selfRecipientId)) {
@ -172,14 +172,15 @@ public class GroupHelper {
members.remove(selfRecipientId);
}
final var avatarBytes = readAvatarBytes(avatarFile);
var gv2Pair = context.getGroupV2Helper()
.createGroup(name == null ? "" : name, members == null ? Set.of() : members, avatarFile);
.createGroup(name == null ? "" : name, members == null ? Set.of() : members, avatarBytes);
if (gv2Pair == null) {
// Failed to create v2 group, creating v1 group instead
var gv1 = new GroupInfoV1(GroupIdV1.createRandom());
gv1.addMembers(List.of(selfRecipientId));
final var result = updateGroupV1(gv1, name, members, avatarFile);
final var result = updateGroupV1(gv1, name, members, avatarBytes);
return new Pair<>(gv1.getGroupId(), result);
}
@ -187,10 +188,9 @@ public class GroupHelper {
final var decryptedGroup = gv2Pair.second();
gv2.setGroup(decryptedGroup);
if (avatarFile != null) {
if (avatarBytes != null) {
context.getAvatarStore()
.storeGroupAvatar(gv2.getGroupId(),
outputStream -> IOUtils.copyFileToStream(avatarFile, outputStream));
.storeGroupAvatar(gv2.getGroupId(), outputStream -> outputStream.write(avatarBytes));
}
account.getGroupStore().updateGroup(gv2);
@ -217,11 +217,12 @@ public class GroupHelper {
final GroupLinkState groupLinkState,
final GroupPermission addMemberPermission,
final GroupPermission editDetailsPermission,
final File avatarFile,
final String avatarFile,
final Integer expirationTimer,
final Boolean isAnnouncementGroup
) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException, GroupSendingNotAllowedException {
var group = getGroupForUpdating(groupId);
final var avatarBytes = readAvatarBytes(avatarFile);
if (group instanceof GroupInfoV2) {
try {
@ -238,7 +239,7 @@ public class GroupHelper {
groupLinkState,
addMemberPermission,
editDetailsPermission,
avatarFile,
avatarBytes,
expirationTimer,
isAnnouncementGroup);
} catch (ConflictException e) {
@ -257,14 +258,14 @@ public class GroupHelper {
groupLinkState,
addMemberPermission,
editDetailsPermission,
avatarFile,
avatarBytes,
expirationTimer,
isAnnouncementGroup);
}
}
final var gv1 = (GroupInfoV1) group;
final var result = updateGroupV1(gv1, name, members, avatarFile);
final var result = updateGroupV1(gv1, name, members, avatarBytes);
if (expirationTimer != null) {
setExpirationTimer(gv1, expirationTimer);
}
@ -521,7 +522,7 @@ public class GroupHelper {
}
private SendGroupMessageResults updateGroupV1(
final GroupInfoV1 gv1, final String name, final Set<RecipientId> members, final File avatarFile
final GroupInfoV1 gv1, final String name, final Set<RecipientId> members, final byte[] avatarFile
) throws IOException, AttachmentInvalidException {
updateGroupV1Details(gv1, name, members, avatarFile);
@ -534,7 +535,7 @@ public class GroupHelper {
}
private void updateGroupV1Details(
final GroupInfoV1 g, final String name, final Collection<RecipientId> members, final File avatarFile
final GroupInfoV1 g, final String name, final Collection<RecipientId> members, final byte[] avatarFile
) throws IOException {
if (name != null) {
g.name = name;
@ -545,9 +546,7 @@ public class GroupHelper {
}
if (avatarFile != null) {
context.getAvatarStore()
.storeGroupAvatar(g.getGroupId(),
outputStream -> IOUtils.copyFileToStream(avatarFile, outputStream));
context.getAvatarStore().storeGroupAvatar(g.getGroupId(), outputStream -> outputStream.write(avatarFile));
}
}
@ -581,7 +580,7 @@ public class GroupHelper {
final GroupLinkState groupLinkState,
final GroupPermission addMemberPermission,
final GroupPermission editDetailsPermission,
final File avatarFile,
final byte[] avatarFile,
final Integer expirationTimer,
final Boolean isAnnouncementGroup
) throws IOException {
@ -716,8 +715,7 @@ public class GroupHelper {
var groupGroupChangePair = groupV2Helper.updateGroup(group, name, description, avatarFile);
if (avatarFile != null) {
context.getAvatarStore()
.storeGroupAvatar(group.getGroupId(),
outputStream -> IOUtils.copyFileToStream(avatarFile, outputStream));
.storeGroupAvatar(group.getGroupId(), outputStream -> outputStream.write(avatarFile));
}
result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
}
@ -819,4 +817,13 @@ public class GroupHelper {
account.getRecipientAddressResolver()))
.toList());
}
private byte[] readAvatarBytes(final String avatarFile) throws IOException {
if (avatarFile == null) {
return null;
}
try (final var avatar = Utils.createStreamDetails(avatarFile).first().getStream()) {
return IOUtils.readFully(avatar);
}
}
}

View file

@ -12,7 +12,6 @@ import org.asamk.signal.manager.groups.GroupUtils;
import org.asamk.signal.manager.groups.NotAGroupMemberException;
import org.asamk.signal.manager.storage.groups.GroupInfoV2;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.asamk.signal.manager.util.IOUtils;
import org.asamk.signal.manager.util.Utils;
import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.VerificationFailedException;
@ -47,10 +46,7 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@ -138,10 +134,9 @@ class GroupV2Helper {
}
Pair<GroupInfoV2, DecryptedGroup> createGroup(
String name, Set<RecipientId> members, File avatarFile
) throws IOException {
final var avatarBytes = readAvatarBytes(avatarFile);
final var newGroup = buildNewGroup(name, members, avatarBytes);
String name, Set<RecipientId> members, byte[] avatarFile
) {
final var newGroup = buildNewGroup(name, members, avatarFile);
if (newGroup == null) {
return null;
}
@ -170,14 +165,6 @@ class GroupV2Helper {
return new Pair<>(g, decryptedGroup);
}
private byte[] readAvatarBytes(final File avatarFile) throws IOException {
final byte[] avatarBytes;
try (InputStream avatar = avatarFile == null ? null : new FileInputStream(avatarFile)) {
avatarBytes = avatar == null ? null : IOUtils.readFully(avatar);
}
return avatarBytes;
}
private GroupsV2Operations.NewGroup buildNewGroup(
String name, Set<RecipientId> members, byte[] avatar
) {
@ -210,7 +197,7 @@ class GroupV2Helper {
}
Pair<DecryptedGroup, GroupChange> updateGroup(
GroupInfoV2 groupInfoV2, String name, String description, File avatarFile
GroupInfoV2 groupInfoV2, String name, String description, byte[] avatarFile
) throws IOException {
final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey());
var groupOperations = dependencies.getGroupsV2Operations().forGroup(groupSecretParams);
@ -222,9 +209,8 @@ class GroupV2Helper {
}
if (avatarFile != null) {
final var avatarBytes = readAvatarBytes(avatarFile);
var avatarCdnKey = dependencies.getGroupsV2Api()
.uploadAvatar(avatarBytes, groupSecretParams, getGroupAuthForToday(groupSecretParams));
.uploadAvatar(avatarFile, groupSecretParams, getGroupAuthForToday(groupSecretParams));
change.setModifyAvatar(GroupChange.Actions.ModifyAvatarAction.newBuilder().setAvatar(avatarCdnKey));
}

View file

@ -30,7 +30,6 @@ import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException
import org.whispersystems.signalservice.api.services.ProfileService;
import org.whispersystems.signalservice.api.util.ExpiringProfileCredentialUtil;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
@ -146,7 +145,7 @@ public final class ProfileHelper {
final String familyName,
String about,
String aboutEmoji,
Optional<File> avatar,
Optional<String> avatar,
byte[] mobileCoinAddress
) throws IOException {
setProfile(true, false, givenName, familyName, about, aboutEmoji, avatar, mobileCoinAddress);
@ -159,7 +158,7 @@ public final class ProfileHelper {
final String familyName,
String about,
String aboutEmoji,
Optional<File> avatar,
Optional<String> avatar,
byte[] mobileCoinAddress
) throws IOException {
var profile = getSelfProfile();
@ -183,7 +182,8 @@ public final class ProfileHelper {
if (uploadProfile) {
final var streamDetails = avatar != null && avatar.isPresent()
? Utils.createStreamDetailsFromFile(avatar.get())
? Utils.createStreamDetails(avatar.get())
.first()
: forceUploadAvatar && avatar == null ? context.getAvatarStore()
.retrieveProfileAvatar(account.getSelfRecipientAddress()) : null;
try (streamDetails) {
@ -212,9 +212,10 @@ public final class ProfileHelper {
if (avatar != null) {
if (avatar.isPresent()) {
final var streamDetails = Utils.createStreamDetails(avatar.get()).first();
context.getAvatarStore()
.storeProfileAvatar(account.getSelfRecipientAddress(),
outputStream -> IOUtils.copyFileToStream(avatar.get(), outputStream));
outputStream -> IOUtils.copyStream(streamDetails.getStream(), outputStream));
} else {
context.getAvatarStore().deleteProfileAvatar(account.getSelfRecipientAddress());
}