]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java
3187fca1877c0a934bc324ff2a8a0c272ab0d120
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / helper / GroupV2Helper.java
1 package org.asamk.signal.manager.helper;
2
3 import com.google.protobuf.InvalidProtocolBufferException;
4
5 import org.asamk.signal.manager.groups.GroupLinkPassword;
6 import org.asamk.signal.manager.groups.GroupLinkState;
7 import org.asamk.signal.manager.groups.GroupPermission;
8 import org.asamk.signal.manager.groups.GroupUtils;
9 import org.asamk.signal.manager.storage.groups.GroupInfoV2;
10 import org.asamk.signal.manager.storage.recipients.Profile;
11 import org.asamk.signal.manager.storage.recipients.RecipientId;
12 import org.asamk.signal.manager.util.IOUtils;
13 import org.signal.storageservice.protos.groups.AccessControl;
14 import org.signal.storageservice.protos.groups.GroupChange;
15 import org.signal.storageservice.protos.groups.Member;
16 import org.signal.storageservice.protos.groups.local.DecryptedGroup;
17 import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
18 import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo;
19 import org.signal.storageservice.protos.groups.local.DecryptedPendingMember;
20 import org.signal.zkgroup.InvalidInputException;
21 import org.signal.zkgroup.VerificationFailedException;
22 import org.signal.zkgroup.groups.GroupMasterKey;
23 import org.signal.zkgroup.groups.GroupSecretParams;
24 import org.signal.zkgroup.groups.UuidCiphertext;
25 import org.slf4j.Logger;
26 import org.slf4j.LoggerFactory;
27 import org.whispersystems.libsignal.util.Pair;
28 import org.whispersystems.libsignal.util.guava.Optional;
29 import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
30 import org.whispersystems.signalservice.api.groupsv2.GroupCandidate;
31 import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException;
32 import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api;
33 import org.whispersystems.signalservice.api.groupsv2.GroupsV2AuthorizationString;
34 import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
35 import org.whispersystems.signalservice.api.groupsv2.InvalidGroupStateException;
36 import org.whispersystems.signalservice.api.groupsv2.NotAbleToApplyGroupV2ChangeException;
37 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
38 import org.whispersystems.signalservice.api.util.UuidUtil;
39
40 import java.io.File;
41 import java.io.FileInputStream;
42 import java.io.IOException;
43 import java.io.InputStream;
44 import java.util.Set;
45 import java.util.UUID;
46 import java.util.concurrent.TimeUnit;
47 import java.util.stream.Collectors;
48
49 public class GroupV2Helper {
50
51 private final static Logger logger = LoggerFactory.getLogger(GroupV2Helper.class);
52
53 private final ProfileKeyCredentialProvider profileKeyCredentialProvider;
54
55 private final ProfileProvider profileProvider;
56
57 private final SelfRecipientIdProvider selfRecipientIdProvider;
58
59 private final GroupsV2Operations groupsV2Operations;
60
61 private final GroupsV2Api groupsV2Api;
62
63 private final SignalServiceAddressResolver addressResolver;
64
65 public GroupV2Helper(
66 final ProfileKeyCredentialProvider profileKeyCredentialProvider,
67 final ProfileProvider profileProvider,
68 final SelfRecipientIdProvider selfRecipientIdProvider,
69 final GroupsV2Operations groupsV2Operations,
70 final GroupsV2Api groupsV2Api,
71 final SignalServiceAddressResolver addressResolver
72 ) {
73 this.profileKeyCredentialProvider = profileKeyCredentialProvider;
74 this.profileProvider = profileProvider;
75 this.selfRecipientIdProvider = selfRecipientIdProvider;
76 this.groupsV2Operations = groupsV2Operations;
77 this.groupsV2Api = groupsV2Api;
78 this.addressResolver = addressResolver;
79 }
80
81 public DecryptedGroup getDecryptedGroup(final GroupSecretParams groupSecretParams) {
82 try {
83 final var groupsV2AuthorizationString = getGroupAuthForToday(groupSecretParams);
84 return groupsV2Api.getGroup(groupSecretParams, groupsV2AuthorizationString);
85 } catch (IOException | VerificationFailedException | InvalidGroupStateException e) {
86 logger.warn("Failed to retrieve Group V2 info, ignoring: {}", e.getMessage());
87 return null;
88 }
89 }
90
91 public DecryptedGroupJoinInfo getDecryptedGroupJoinInfo(
92 GroupMasterKey groupMasterKey, GroupLinkPassword password
93 ) throws IOException, GroupLinkNotActiveException {
94 var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey);
95
96 return groupsV2Api.getGroupJoinInfo(groupSecretParams,
97 Optional.fromNullable(password).transform(GroupLinkPassword::serialize),
98 getGroupAuthForToday(groupSecretParams));
99 }
100
101 public Pair<GroupInfoV2, DecryptedGroup> createGroup(
102 String name, Set<RecipientId> members, File avatarFile
103 ) throws IOException {
104 final var avatarBytes = readAvatarBytes(avatarFile);
105 final var newGroup = buildNewGroup(name, members, avatarBytes);
106 if (newGroup == null) {
107 return null;
108 }
109
110 final var groupSecretParams = newGroup.getGroupSecretParams();
111
112 final GroupsV2AuthorizationString groupAuthForToday;
113 final DecryptedGroup decryptedGroup;
114 try {
115 groupAuthForToday = getGroupAuthForToday(groupSecretParams);
116 groupsV2Api.putNewGroup(newGroup, groupAuthForToday);
117 decryptedGroup = groupsV2Api.getGroup(groupSecretParams, groupAuthForToday);
118 } catch (IOException | VerificationFailedException | InvalidGroupStateException e) {
119 logger.warn("Failed to create V2 group: {}", e.getMessage());
120 return null;
121 }
122 if (decryptedGroup == null) {
123 logger.warn("Failed to create V2 group, unknown error!");
124 return null;
125 }
126
127 final var groupId = GroupUtils.getGroupIdV2(groupSecretParams);
128 final var masterKey = groupSecretParams.getMasterKey();
129 var g = new GroupInfoV2(groupId, masterKey);
130
131 return new Pair<>(g, decryptedGroup);
132 }
133
134 private byte[] readAvatarBytes(final File avatarFile) throws IOException {
135 final byte[] avatarBytes;
136 try (InputStream avatar = avatarFile == null ? null : new FileInputStream(avatarFile)) {
137 avatarBytes = avatar == null ? null : IOUtils.readFully(avatar);
138 }
139 return avatarBytes;
140 }
141
142 private GroupsV2Operations.NewGroup buildNewGroup(
143 String name, Set<RecipientId> members, byte[] avatar
144 ) {
145 final var profileKeyCredential = profileKeyCredentialProvider.getProfileKeyCredential(selfRecipientIdProvider.getSelfRecipientId());
146 if (profileKeyCredential == null) {
147 logger.warn("Cannot create a V2 group as self does not have a versioned profile");
148 return null;
149 }
150
151 if (!areMembersValid(members)) return null;
152
153 var self = new GroupCandidate(getSelfUuid(), Optional.fromNullable(profileKeyCredential));
154 var candidates = members.stream()
155 .map(member -> new GroupCandidate(addressResolver.resolveSignalServiceAddress(member).getUuid(),
156 Optional.fromNullable(profileKeyCredentialProvider.getProfileKeyCredential(member))))
157 .collect(Collectors.toSet());
158
159 final var groupSecretParams = GroupSecretParams.generate();
160 return groupsV2Operations.createNewGroup(groupSecretParams,
161 name,
162 Optional.fromNullable(avatar),
163 self,
164 candidates,
165 Member.Role.DEFAULT,
166 0);
167 }
168
169 private boolean areMembersValid(final Set<RecipientId> members) {
170 final var noGv2Capability = members.stream()
171 .map(profileProvider::getProfile)
172 .filter(profile -> profile != null && !profile.getCapabilities().contains(Profile.Capability.gv2))
173 .collect(Collectors.toSet());
174 if (noGv2Capability.size() > 0) {
175 logger.warn("Cannot create a V2 group as some members don't support Groups V2: {}",
176 noGv2Capability.stream().map(Profile::getDisplayName).collect(Collectors.joining(", ")));
177 return false;
178 }
179
180 return true;
181 }
182
183 public Pair<DecryptedGroup, GroupChange> updateGroup(
184 GroupInfoV2 groupInfoV2, String name, String description, File avatarFile
185 ) throws IOException {
186 final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey());
187 var groupOperations = groupsV2Operations.forGroup(groupSecretParams);
188
189 var change = name != null ? groupOperations.createModifyGroupTitle(name) : GroupChange.Actions.newBuilder();
190
191 if (description != null) {
192 change.setModifyDescription(groupOperations.createModifyGroupDescriptionAction(description));
193 }
194
195 if (avatarFile != null) {
196 final var avatarBytes = readAvatarBytes(avatarFile);
197 var avatarCdnKey = groupsV2Api.uploadAvatar(avatarBytes,
198 groupSecretParams,
199 getGroupAuthForToday(groupSecretParams));
200 change.setModifyAvatar(GroupChange.Actions.ModifyAvatarAction.newBuilder().setAvatar(avatarCdnKey));
201 }
202
203 final var uuid = getSelfUuid();
204 change.setSourceUuid(UuidUtil.toByteString(uuid));
205
206 return commitChange(groupInfoV2, change);
207 }
208
209 public Pair<DecryptedGroup, GroupChange> addMembers(
210 GroupInfoV2 groupInfoV2, Set<RecipientId> newMembers
211 ) throws IOException {
212 GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
213
214 if (!areMembersValid(newMembers)) {
215 throw new IOException("Failed to update group");
216 }
217
218 var candidates = newMembers.stream()
219 .map(member -> new GroupCandidate(addressResolver.resolveSignalServiceAddress(member).getUuid(),
220 Optional.fromNullable(profileKeyCredentialProvider.getProfileKeyCredential(member))))
221 .collect(Collectors.toSet());
222
223 final var uuid = getSelfUuid();
224 final var change = groupOperations.createModifyGroupMembershipChange(candidates, uuid);
225
226 change.setSourceUuid(UuidUtil.toByteString(uuid));
227
228 return commitChange(groupInfoV2, change);
229 }
230
231 public Pair<DecryptedGroup, GroupChange> leaveGroup(
232 GroupInfoV2 groupInfoV2, Set<RecipientId> membersToMakeAdmin
233 ) throws IOException {
234 var pendingMembersList = groupInfoV2.getGroup().getPendingMembersList();
235 final var selfUuid = getSelfUuid();
236 var selfPendingMember = DecryptedGroupUtil.findPendingByUuid(pendingMembersList, selfUuid);
237
238 if (selfPendingMember.isPresent()) {
239 return revokeInvites(groupInfoV2, Set.of(selfPendingMember.get()));
240 }
241
242 final var adminUuids = membersToMakeAdmin.stream()
243 .map(addressResolver::resolveSignalServiceAddress)
244 .map(SignalServiceAddress::getUuid)
245 .collect(Collectors.toList());
246 final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
247 return commitChange(groupInfoV2, groupOperations.createLeaveAndPromoteMembersToAdmin(selfUuid, adminUuids));
248 }
249
250 public Pair<DecryptedGroup, GroupChange> removeMembers(
251 GroupInfoV2 groupInfoV2, Set<RecipientId> members
252 ) throws IOException {
253 final var memberUuids = members.stream()
254 .map(addressResolver::resolveSignalServiceAddress)
255 .map(SignalServiceAddress::getUuid)
256 .collect(Collectors.toSet());
257 return ejectMembers(groupInfoV2, memberUuids);
258 }
259
260 public Pair<DecryptedGroup, GroupChange> revokeInvitedMembers(
261 GroupInfoV2 groupInfoV2, Set<RecipientId> members
262 ) throws IOException {
263 var pendingMembersList = groupInfoV2.getGroup().getPendingMembersList();
264 final var memberUuids = members.stream()
265 .map(addressResolver::resolveSignalServiceAddress)
266 .map(SignalServiceAddress::getUuid)
267 .map(uuid -> DecryptedGroupUtil.findPendingByUuid(pendingMembersList, uuid))
268 .filter(Optional::isPresent)
269 .map(Optional::get)
270 .collect(Collectors.toSet());
271 return revokeInvites(groupInfoV2, memberUuids);
272 }
273
274 public Pair<DecryptedGroup, GroupChange> resetGroupLinkPassword(GroupInfoV2 groupInfoV2) throws IOException {
275 final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
276 final var newGroupLinkPassword = GroupLinkPassword.createNew().serialize();
277 final var change = groupOperations.createModifyGroupLinkPasswordChange(newGroupLinkPassword);
278 return commitChange(groupInfoV2, change);
279 }
280
281 public Pair<DecryptedGroup, GroupChange> setGroupLinkState(
282 GroupInfoV2 groupInfoV2, GroupLinkState state
283 ) throws IOException {
284 final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
285
286 final var accessRequired = toAccessControl(state);
287 final var requiresNewPassword = state != GroupLinkState.DISABLED && groupInfoV2.getGroup()
288 .getInviteLinkPassword()
289 .isEmpty();
290
291 final var change = requiresNewPassword ? groupOperations.createModifyGroupLinkPasswordAndRightsChange(
292 GroupLinkPassword.createNew().serialize(),
293 accessRequired) : groupOperations.createChangeJoinByLinkRights(accessRequired);
294 return commitChange(groupInfoV2, change);
295 }
296
297 public Pair<DecryptedGroup, GroupChange> setEditDetailsPermission(
298 GroupInfoV2 groupInfoV2, GroupPermission permission
299 ) throws IOException {
300 final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
301
302 final var accessRequired = toAccessControl(permission);
303 final var change = groupOperations.createChangeAttributesRights(accessRequired);
304 return commitChange(groupInfoV2, change);
305 }
306
307 public Pair<DecryptedGroup, GroupChange> setAddMemberPermission(
308 GroupInfoV2 groupInfoV2, GroupPermission permission
309 ) throws IOException {
310 final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
311
312 final var accessRequired = toAccessControl(permission);
313 final var change = groupOperations.createChangeMembershipRights(accessRequired);
314 return commitChange(groupInfoV2, change);
315 }
316
317 public GroupChange joinGroup(
318 GroupMasterKey groupMasterKey,
319 GroupLinkPassword groupLinkPassword,
320 DecryptedGroupJoinInfo decryptedGroupJoinInfo
321 ) throws IOException {
322 final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey);
323 final var groupOperations = groupsV2Operations.forGroup(groupSecretParams);
324
325 final var selfRecipientId = this.selfRecipientIdProvider.getSelfRecipientId();
326 final var profileKeyCredential = profileKeyCredentialProvider.getProfileKeyCredential(selfRecipientId);
327 if (profileKeyCredential == null) {
328 throw new IOException("Cannot join a V2 group as self does not have a versioned profile");
329 }
330
331 var requestToJoin = decryptedGroupJoinInfo.getAddFromInviteLink() == AccessControl.AccessRequired.ADMINISTRATOR;
332 var change = requestToJoin
333 ? groupOperations.createGroupJoinRequest(profileKeyCredential)
334 : groupOperations.createGroupJoinDirect(profileKeyCredential);
335
336 change.setSourceUuid(UuidUtil.toByteString(addressResolver.resolveSignalServiceAddress(selfRecipientId)
337 .getUuid()));
338
339 return commitChange(groupSecretParams, decryptedGroupJoinInfo.getRevision(), change, groupLinkPassword);
340 }
341
342 public Pair<DecryptedGroup, GroupChange> acceptInvite(GroupInfoV2 groupInfoV2) throws IOException {
343 final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
344
345 final var selfRecipientId = this.selfRecipientIdProvider.getSelfRecipientId();
346 final var profileKeyCredential = profileKeyCredentialProvider.getProfileKeyCredential(selfRecipientId);
347 if (profileKeyCredential == null) {
348 throw new IOException("Cannot join a V2 group as self does not have a versioned profile");
349 }
350
351 final var change = groupOperations.createAcceptInviteChange(profileKeyCredential);
352
353 final var uuid = addressResolver.resolveSignalServiceAddress(selfRecipientId).getUuid();
354 change.setSourceUuid(UuidUtil.toByteString(uuid));
355
356 return commitChange(groupInfoV2, change);
357 }
358
359 public Pair<DecryptedGroup, GroupChange> setMemberAdmin(
360 GroupInfoV2 groupInfoV2, RecipientId recipientId, boolean admin
361 ) throws IOException {
362 final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
363 final var address = addressResolver.resolveSignalServiceAddress(recipientId);
364 final var newRole = admin ? Member.Role.ADMINISTRATOR : Member.Role.DEFAULT;
365 final var change = groupOperations.createChangeMemberRole(address.getUuid(), newRole);
366 return commitChange(groupInfoV2, change);
367 }
368
369 public Pair<DecryptedGroup, GroupChange> setMessageExpirationTimer(
370 GroupInfoV2 groupInfoV2, int messageExpirationTimer
371 ) throws IOException {
372 final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
373 final var change = groupOperations.createModifyGroupTimerChange(messageExpirationTimer);
374 return commitChange(groupInfoV2, change);
375 }
376
377 public Pair<DecryptedGroup, GroupChange> setIsAnnouncementGroup(
378 GroupInfoV2 groupInfoV2, boolean isAnnouncementGroup
379 ) throws IOException {
380 final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
381 final var change = groupOperations.createAnnouncementGroupChange(isAnnouncementGroup);
382 return commitChange(groupInfoV2, change);
383 }
384
385 private AccessControl.AccessRequired toAccessControl(final GroupLinkState state) {
386 switch (state) {
387 case DISABLED:
388 return AccessControl.AccessRequired.UNSATISFIABLE;
389 case ENABLED:
390 return AccessControl.AccessRequired.ANY;
391 case ENABLED_WITH_APPROVAL:
392 return AccessControl.AccessRequired.ADMINISTRATOR;
393 default:
394 throw new AssertionError();
395 }
396 }
397
398 private AccessControl.AccessRequired toAccessControl(final GroupPermission permission) {
399 switch (permission) {
400 case EVERY_MEMBER:
401 return AccessControl.AccessRequired.MEMBER;
402 case ONLY_ADMINS:
403 return AccessControl.AccessRequired.ADMINISTRATOR;
404 default:
405 throw new AssertionError();
406 }
407 }
408
409 private GroupsV2Operations.GroupOperations getGroupOperations(final GroupInfoV2 groupInfoV2) {
410 final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey());
411 return groupsV2Operations.forGroup(groupSecretParams);
412 }
413
414 private Pair<DecryptedGroup, GroupChange> revokeInvites(
415 GroupInfoV2 groupInfoV2, Set<DecryptedPendingMember> pendingMembers
416 ) throws IOException {
417 final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
418 final var uuidCipherTexts = pendingMembers.stream().map(member -> {
419 try {
420 return new UuidCiphertext(member.getUuidCipherText().toByteArray());
421 } catch (InvalidInputException e) {
422 throw new AssertionError(e);
423 }
424 }).collect(Collectors.toSet());
425 return commitChange(groupInfoV2, groupOperations.createRemoveInvitationChange(uuidCipherTexts));
426 }
427
428 private Pair<DecryptedGroup, GroupChange> ejectMembers(
429 GroupInfoV2 groupInfoV2, Set<UUID> uuids
430 ) throws IOException {
431 final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
432 return commitChange(groupInfoV2, groupOperations.createRemoveMembersChange(uuids));
433 }
434
435 private Pair<DecryptedGroup, GroupChange> commitChange(
436 GroupInfoV2 groupInfoV2, GroupChange.Actions.Builder change
437 ) throws IOException {
438 final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey());
439 final var groupOperations = groupsV2Operations.forGroup(groupSecretParams);
440 final var previousGroupState = groupInfoV2.getGroup();
441 final var nextRevision = previousGroupState.getRevision() + 1;
442 final var changeActions = change.setRevision(nextRevision).build();
443 final DecryptedGroupChange decryptedChange;
444 final DecryptedGroup decryptedGroupState;
445
446 try {
447 decryptedChange = groupOperations.decryptChange(changeActions, getSelfUuid());
448 decryptedGroupState = DecryptedGroupUtil.apply(previousGroupState, decryptedChange);
449 } catch (VerificationFailedException | InvalidGroupStateException | NotAbleToApplyGroupV2ChangeException e) {
450 throw new IOException(e);
451 }
452
453 var signedGroupChange = groupsV2Api.patchGroup(changeActions,
454 getGroupAuthForToday(groupSecretParams),
455 Optional.absent());
456
457 return new Pair<>(decryptedGroupState, signedGroupChange);
458 }
459
460 private GroupChange commitChange(
461 GroupSecretParams groupSecretParams,
462 int currentRevision,
463 GroupChange.Actions.Builder change,
464 GroupLinkPassword password
465 ) throws IOException {
466 final var nextRevision = currentRevision + 1;
467 final var changeActions = change.setRevision(nextRevision).build();
468
469 return groupsV2Api.patchGroup(changeActions,
470 getGroupAuthForToday(groupSecretParams),
471 Optional.fromNullable(password).transform(GroupLinkPassword::serialize));
472 }
473
474 public DecryptedGroup getUpdatedDecryptedGroup(
475 DecryptedGroup group, byte[] signedGroupChange, GroupMasterKey groupMasterKey
476 ) {
477 try {
478 final var decryptedGroupChange = getDecryptedGroupChange(signedGroupChange, groupMasterKey);
479 if (decryptedGroupChange == null) {
480 return null;
481 }
482 return DecryptedGroupUtil.apply(group, decryptedGroupChange);
483 } catch (NotAbleToApplyGroupV2ChangeException e) {
484 return null;
485 }
486 }
487
488 private DecryptedGroupChange getDecryptedGroupChange(byte[] signedGroupChange, GroupMasterKey groupMasterKey) {
489 if (signedGroupChange != null) {
490 var groupOperations = groupsV2Operations.forGroup(GroupSecretParams.deriveFromMasterKey(groupMasterKey));
491
492 try {
493 return groupOperations.decryptChange(GroupChange.parseFrom(signedGroupChange), true).orNull();
494 } catch (VerificationFailedException | InvalidGroupStateException | InvalidProtocolBufferException e) {
495 return null;
496 }
497 }
498
499 return null;
500 }
501
502 private static int currentTimeDays() {
503 return (int) TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis());
504 }
505
506 private GroupsV2AuthorizationString getGroupAuthForToday(
507 final GroupSecretParams groupSecretParams
508 ) throws IOException {
509 final var today = currentTimeDays();
510 // Returns credentials for the next 7 days
511 final var credentials = groupsV2Api.getCredentials(today);
512 // TODO cache credentials until they expire
513 var authCredentialResponse = credentials.get(today);
514 final var uuid = getSelfUuid();
515 try {
516 return groupsV2Api.getGroupsV2AuthorizationString(uuid, today, groupSecretParams, authCredentialResponse);
517 } catch (VerificationFailedException e) {
518 throw new IOException(e);
519 }
520 }
521
522 private UUID getSelfUuid() {
523 return addressResolver.resolveSignalServiceAddress(this.selfRecipientIdProvider.getSelfRecipientId()).getUuid();
524 }
525 }