Profile gRPC: Define getVersionedProfile endpoint

This commit is contained in:
Katherine Yen
2023-08-30 14:47:11 -07:00
committed by GitHub
parent 5afc058f90
commit dd18fcaea2
7 changed files with 354 additions and 4 deletions

View File

@@ -3,6 +3,8 @@ package org.whispersystems.textsecuregcm.grpc;
import io.grpc.Status;
import org.signal.chat.profile.GetUnversionedProfileAnonymousRequest;
import org.signal.chat.profile.GetUnversionedProfileResponse;
import org.signal.chat.profile.GetVersionedProfileAnonymousRequest;
import org.signal.chat.profile.GetVersionedProfileResponse;
import org.signal.chat.profile.ReactorProfileAnonymousGrpc;
import org.whispersystems.textsecuregcm.auth.UnidentifiedAccessUtil;
import org.whispersystems.textsecuregcm.badges.ProfileBadgeConverter;
@@ -10,16 +12,20 @@ import org.whispersystems.textsecuregcm.identity.IdentityType;
import org.whispersystems.textsecuregcm.identity.ServiceIdentifier;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.ProfilesManager;
import reactor.core.publisher.Mono;
public class ProfileAnonymousGrpcService extends ReactorProfileAnonymousGrpc.ProfileAnonymousImplBase {
private final AccountsManager accountsManager;
private final ProfilesManager profilesManager;
private final ProfileBadgeConverter profileBadgeConverter;
public ProfileAnonymousGrpcService(
final AccountsManager accountsManager,
final ProfilesManager profilesManager,
final ProfileBadgeConverter profileBadgeConverter) {
this.accountsManager = accountsManager;
this.profilesManager = profilesManager;
this.profileBadgeConverter = profileBadgeConverter;
}
@@ -40,8 +46,20 @@ public class ProfileAnonymousGrpcService extends ReactorProfileAnonymousGrpc.Pro
profileBadgeConverter));
}
private Mono<Account> getTargetAccountAndValidateUnidentifiedAccess(final ServiceIdentifier targetIdentifier, final byte[] unidentifiedAccessKey) {
return Mono.fromFuture(() -> accountsManager.getByServiceIdentifierAsync(targetIdentifier))
@Override
public Mono<GetVersionedProfileResponse> getVersionedProfile(final GetVersionedProfileAnonymousRequest request) {
final ServiceIdentifier targetIdentifier = ServiceIdentifierUtil.fromGrpcServiceIdentifier(request.getRequest().getAccountIdentifier());
if (targetIdentifier.identityType() != IdentityType.ACI) {
throw Status.INVALID_ARGUMENT.withDescription("Expected ACI service identifier").asRuntimeException();
}
return getTargetAccountAndValidateUnidentifiedAccess(targetIdentifier, request.getUnidentifiedAccessKey().toByteArray())
.flatMap(targetAccount -> ProfileGrpcHelper.getVersionedProfile(targetAccount, profilesManager, request.getRequest().getVersion()));
}
private Mono<Account> getTargetAccountAndValidateUnidentifiedAccess(final ServiceIdentifier targetIdentifier, final byte[] unidentifiedAccessKey) {
return Mono.fromFuture(() -> accountsManager.getByServiceIdentifierAsync(targetIdentifier))
.flatMap(Mono::justOrEmpty)
.filter(targetAccount -> UnidentifiedAccessUtil.checkUnidentifiedAccess(targetAccount, unidentifiedAccessKey))
.switchIfEmpty(Mono.error(Status.UNAUTHENTICATED.asException()));

View File

@@ -5,18 +5,51 @@ import com.google.protobuf.ByteString;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import io.grpc.Status;
import org.signal.chat.profile.Badge;
import org.signal.chat.profile.BadgeSvg;
import org.signal.chat.profile.GetUnversionedProfileResponse;
import org.signal.chat.profile.GetVersionedProfileResponse;
import org.signal.chat.profile.UserCapabilities;
import org.whispersystems.textsecuregcm.auth.UnidentifiedAccessChecksum;
import org.whispersystems.textsecuregcm.badges.ProfileBadgeConverter;
import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier;
import org.whispersystems.textsecuregcm.identity.ServiceIdentifier;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.ProfilesManager;
import org.whispersystems.textsecuregcm.storage.VersionedProfile;
import org.whispersystems.textsecuregcm.util.ProfileHelper;
import reactor.core.publisher.Mono;
public class ProfileGrpcHelper {
static Mono<GetVersionedProfileResponse> getVersionedProfile(final Account account,
final ProfilesManager profilesManager,
final String requestVersion) {
return Mono.fromFuture(() -> profilesManager.getAsync(account.getUuid(), requestVersion))
.map(maybeProfile -> {
if (maybeProfile.isEmpty()) {
throw Status.NOT_FOUND.withDescription("Profile version not found").asRuntimeException();
}
final GetVersionedProfileResponse.Builder responseBuilder = GetVersionedProfileResponse.newBuilder();
maybeProfile.map(VersionedProfile::name).map(ByteString::copyFrom).ifPresent(responseBuilder::setName);
maybeProfile.map(VersionedProfile::about).map(ByteString::copyFrom).ifPresent(responseBuilder::setAbout);
maybeProfile.map(VersionedProfile::aboutEmoji).map(ByteString::copyFrom).ifPresent(responseBuilder::setAboutEmoji);
maybeProfile.map(VersionedProfile::avatar).ifPresent(responseBuilder::setAvatar);
// Allow requests where either the version matches the latest version on Account or the latest version on Account
// is empty to read the payment address.
maybeProfile
.filter(p -> account.getCurrentProfileVersion().map(v -> v.equals(requestVersion)).orElse(true))
.map(VersionedProfile::paymentAddress)
.map(ByteString::copyFrom)
.ifPresent(responseBuilder::setPaymentAddress);
return responseBuilder.build();
});
}
@VisibleForTesting
static List<Badge> buildBadges(final List<org.whispersystems.textsecuregcm.entities.Badge> badges) {
final ArrayList<Badge> grpcBadges = new ArrayList<>();

View File

@@ -4,6 +4,8 @@ import com.google.protobuf.ByteString;
import io.grpc.Status;
import org.signal.chat.profile.GetUnversionedProfileRequest;
import org.signal.chat.profile.GetUnversionedProfileResponse;
import org.signal.chat.profile.GetVersionedProfileRequest;
import org.signal.chat.profile.GetVersionedProfileResponse;
import org.signal.chat.profile.SetProfileRequest.AvatarChange;
import org.signal.chat.profile.ProfileAvatarUploadAttributes;
import org.signal.chat.profile.ReactorProfileGrpc;
@@ -15,6 +17,7 @@ import org.whispersystems.textsecuregcm.badges.ProfileBadgeConverter;
import org.whispersystems.textsecuregcm.configuration.BadgeConfiguration;
import org.whispersystems.textsecuregcm.configuration.BadgesConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.identity.IdentityType;
import org.whispersystems.textsecuregcm.identity.ServiceIdentifier;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.s3.PolicySigner;
@@ -169,6 +172,20 @@ public class ProfileGrpcService extends ReactorProfileGrpc.ProfileImplBase {
profileBadgeConverter));
}
@Override
public Mono<GetVersionedProfileResponse> getVersionedProfile(final GetVersionedProfileRequest request) {
final AuthenticatedDevice authenticatedDevice = AuthenticationUtil.requireAuthenticatedDevice();
final ServiceIdentifier targetIdentifier =
ServiceIdentifierUtil.fromGrpcServiceIdentifier(request.getAccountIdentifier());
if (targetIdentifier.identityType() != IdentityType.ACI) {
throw Status.INVALID_ARGUMENT.withDescription("Expected ACI service identifier").asRuntimeException();
}
return validateRateLimitAndGetAccount(authenticatedDevice.accountIdentifier(), targetIdentifier)
.flatMap(account -> ProfileGrpcHelper.getVersionedProfile(account, profilesManager, request.getVersion()));
}
private Mono<Account> validateRateLimitAndGetAccount(final UUID requesterUuid,
final ServiceIdentifier targetIdentifier) {
return rateLimiters.getProfileLimiter().validateReactive(requesterUuid)