Convert Profiles gRPC to SimpleGrpc

This commit is contained in:
Chris Eager
2025-07-23 09:23:48 -05:00
committed by GitHub
parent 5d80ac73da
commit de60752219
5 changed files with 215 additions and 234 deletions

View File

@@ -15,7 +15,7 @@ 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.signal.chat.profile.SimpleProfileAnonymousGrpc;
import org.signal.libsignal.zkgroup.ServerSecretParams;
import org.signal.libsignal.zkgroup.profiles.ServerZkProfileOperations;
import org.whispersystems.textsecuregcm.auth.UnidentifiedAccessUtil;
@@ -25,9 +25,8 @@ 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 {
public class ProfileAnonymousGrpcService extends SimpleProfileAnonymousGrpc.ProfileAnonymousImplBase {
private final AccountsManager accountsManager;
private final ProfilesManager profilesManager;
private final ProfileBadgeConverter profileBadgeConverter;
@@ -47,72 +46,68 @@ public class ProfileAnonymousGrpcService extends ReactorProfileAnonymousGrpc.Pro
}
@Override
public Mono<GetUnversionedProfileResponse> getUnversionedProfile(final GetUnversionedProfileAnonymousRequest request) {
public GetUnversionedProfileResponse getUnversionedProfile(final GetUnversionedProfileAnonymousRequest request) throws StatusException {
final ServiceIdentifier targetIdentifier =
ServiceIdentifierUtil.fromGrpcServiceIdentifier(request.getRequest().getServiceIdentifier());
// Callers must be authenticated to request unversioned profiles by PNI
if (targetIdentifier.identityType() == IdentityType.PNI) {
throw Status.UNAUTHENTICATED.asRuntimeException();
throw Status.UNAUTHENTICATED.asException();
}
final Mono<Account> account = switch (request.getAuthenticationCase()) {
final Account account = switch (request.getAuthenticationCase()) {
case GROUP_SEND_TOKEN -> {
try {
groupSendTokenUtil.checkGroupSendToken(request.getGroupSendToken(), targetIdentifier);
groupSendTokenUtil.checkGroupSendToken(request.getGroupSendToken(), targetIdentifier);
yield Mono.fromFuture(() -> accountsManager.getByServiceIdentifierAsync(targetIdentifier))
.flatMap(Mono::justOrEmpty)
.switchIfEmpty(Mono.error(Status.NOT_FOUND.asException()));
} catch (final StatusException e) {
yield Mono.error(e);
}
yield accountsManager.getByServiceIdentifier(targetIdentifier)
.orElseThrow(Status.NOT_FOUND::asException);
}
case UNIDENTIFIED_ACCESS_KEY ->
getTargetAccountAndValidateUnidentifiedAccess(targetIdentifier, request.getUnidentifiedAccessKey().toByteArray());
default -> Mono.error(Status.INVALID_ARGUMENT.asException());
default -> throw Status.INVALID_ARGUMENT.asException();
};
return account.map(targetAccount -> ProfileGrpcHelper.buildUnversionedProfileResponse(targetIdentifier,
return ProfileGrpcHelper.buildUnversionedProfileResponse(targetIdentifier,
null,
targetAccount,
profileBadgeConverter));
account,
profileBadgeConverter);
}
@Override
public Mono<GetVersionedProfileResponse> getVersionedProfile(final GetVersionedProfileAnonymousRequest request) {
public GetVersionedProfileResponse getVersionedProfile(final GetVersionedProfileAnonymousRequest request) throws StatusException {
final ServiceIdentifier targetIdentifier = ServiceIdentifierUtil.fromGrpcServiceIdentifier(request.getRequest().getAccountIdentifier());
if (targetIdentifier.identityType() != IdentityType.ACI) {
throw Status.INVALID_ARGUMENT.withDescription("Expected ACI service identifier").asRuntimeException();
throw Status.INVALID_ARGUMENT.withDescription("Expected ACI service identifier").asException();
}
return getTargetAccountAndValidateUnidentifiedAccess(targetIdentifier, request.getUnidentifiedAccessKey().toByteArray())
.flatMap(targetAccount -> ProfileGrpcHelper.getVersionedProfile(targetAccount, profilesManager, request.getRequest().getVersion()));
final Account targetAccount = getTargetAccountAndValidateUnidentifiedAccess(targetIdentifier, request.getUnidentifiedAccessKey().toByteArray());
return ProfileGrpcHelper.getVersionedProfile(targetAccount, profilesManager, request.getRequest().getVersion());
}
@Override
public Mono<GetExpiringProfileKeyCredentialResponse> getExpiringProfileKeyCredential(
final GetExpiringProfileKeyCredentialAnonymousRequest request) {
public GetExpiringProfileKeyCredentialResponse getExpiringProfileKeyCredential(
final GetExpiringProfileKeyCredentialAnonymousRequest request) throws StatusException {
final ServiceIdentifier targetIdentifier = ServiceIdentifierUtil.fromGrpcServiceIdentifier(request.getRequest().getAccountIdentifier());
if (targetIdentifier.identityType() != IdentityType.ACI) {
throw Status.INVALID_ARGUMENT.withDescription("Expected ACI service identifier").asRuntimeException();
throw Status.INVALID_ARGUMENT.withDescription("Expected ACI service identifier").asException();
}
if (request.getRequest().getCredentialType() != CredentialType.CREDENTIAL_TYPE_EXPIRING_PROFILE_KEY) {
throw Status.INVALID_ARGUMENT.withDescription("Expected expiring profile key credential type").asRuntimeException();
throw Status.INVALID_ARGUMENT.withDescription("Expected expiring profile key credential type").asException();
}
return getTargetAccountAndValidateUnidentifiedAccess(targetIdentifier, request.getUnidentifiedAccessKey().toByteArray())
.flatMap(account -> ProfileGrpcHelper.getExpiringProfileKeyCredentialResponse(account.getUuid(),
request.getRequest().getVersion(), request.getRequest().getCredentialRequest().toByteArray(), profilesManager, zkProfileOperations));
final Account account = getTargetAccountAndValidateUnidentifiedAccess(
targetIdentifier, request.getUnidentifiedAccessKey().toByteArray());
return ProfileGrpcHelper.getExpiringProfileKeyCredentialResponse(account.getUuid(),
request.getRequest().getVersion(), request.getRequest().getCredentialRequest().toByteArray(), profilesManager, zkProfileOperations);
}
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()));
private Account getTargetAccountAndValidateUnidentifiedAccess(final ServiceIdentifier targetIdentifier, final byte[] unidentifiedAccessKey) throws StatusException {
return accountsManager.getByServiceIdentifier(targetIdentifier)
.filter(targetAccount -> UnidentifiedAccessUtil.checkUnidentifiedAccess(targetAccount, unidentifiedAccessKey))
.orElseThrow(Status.UNAUTHENTICATED::asException);
}
}

View File

@@ -12,6 +12,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import io.grpc.StatusException;
import org.signal.chat.profile.Badge;
import org.signal.chat.profile.BadgeSvg;
import org.signal.chat.profile.GetExpiringProfileKeyCredentialResponse;
@@ -30,36 +31,31 @@ import org.whispersystems.textsecuregcm.storage.DeviceCapability;
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,
static 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 String requestVersion) throws StatusException {
final GetVersionedProfileResponse.Builder responseBuilder = GetVersionedProfileResponse.newBuilder();
final VersionedProfile profile = profilesManager.get(account.getUuid(), requestVersion)
.orElseThrow(Status.NOT_FOUND.withDescription("Profile version not found")::asException);
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);
maybeProfile.map(VersionedProfile::phoneNumberSharing).map(ByteString::copyFrom).ifPresent(responseBuilder::setPhoneNumberSharing);
final GetVersionedProfileResponse.Builder responseBuilder = GetVersionedProfileResponse.newBuilder();
// 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);
responseBuilder
.setName(ByteString.copyFrom(profile.name()))
.setAbout(ByteString.copyFrom(profile.about()))
.setAboutEmoji(ByteString.copyFrom(profile.aboutEmoji()))
.setAvatar(profile.avatar())
.setPhoneNumberSharing(ByteString.copyFrom(profile.phoneNumberSharing()));
return responseBuilder.build();
});
// 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.
if (account.getCurrentProfileVersion().map(v -> v.equals(requestVersion)).orElse(true)) {
responseBuilder.setPaymentAddress(ByteString.copyFrom(profile.paymentAddress()));
}
return responseBuilder.build();
}
@VisibleForTesting
@@ -127,27 +123,26 @@ public class ProfileGrpcHelper {
return responseBuilder.build();
}
static Mono<GetExpiringProfileKeyCredentialResponse> getExpiringProfileKeyCredentialResponse(
static GetExpiringProfileKeyCredentialResponse getExpiringProfileKeyCredentialResponse(
final UUID targetUuid,
final String version,
final byte[] encodedCredentialRequest,
final ProfilesManager profilesManager,
final ServerZkProfileOperations zkProfileOperations) {
return Mono.fromFuture(profilesManager.getAsync(targetUuid, version))
.flatMap(Mono::justOrEmpty)
.map(profile -> {
final ExpiringProfileKeyCredentialResponse profileKeyCredentialResponse;
try {
profileKeyCredentialResponse = ProfileHelper.getExpiringProfileKeyCredential(encodedCredentialRequest,
profile, new ServiceId.Aci(targetUuid), zkProfileOperations);
} catch (VerificationFailedException | InvalidInputException e) {
throw Status.INVALID_ARGUMENT.withCause(e).asRuntimeException();
}
final ServerZkProfileOperations zkProfileOperations) throws StatusException {
return GetExpiringProfileKeyCredentialResponse.newBuilder()
.setProfileKeyCredential(ByteString.copyFrom(profileKeyCredentialResponse.serialize()))
.build();
})
.switchIfEmpty(Mono.error(Status.NOT_FOUND.withDescription("Profile version not found").asException()));
final VersionedProfile profile = profilesManager.get(targetUuid, version)
.orElseThrow(Status.NOT_FOUND.withDescription("Profile version not found")::asException);
final ExpiringProfileKeyCredentialResponse profileKeyCredentialResponse;
try {
profileKeyCredentialResponse = ProfileHelper.getExpiringProfileKeyCredential(encodedCredentialRequest,
profile, new ServiceId.Aci(targetUuid), zkProfileOperations);
} catch (VerificationFailedException | InvalidInputException e) {
throw Status.INVALID_ARGUMENT.withCause(e).asException();
}
return GetExpiringProfileKeyCredentialResponse.newBuilder()
.setProfileKeyCredential(ByteString.copyFrom(profileKeyCredentialResponse.serialize()))
.build();
}
}

View File

@@ -9,13 +9,13 @@ import com.google.protobuf.ByteString;
import io.grpc.Status;
import java.time.Clock;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
import io.grpc.StatusException;
import org.signal.chat.profile.CredentialType;
import org.signal.chat.profile.GetExpiringProfileKeyCredentialRequest;
import org.signal.chat.profile.GetExpiringProfileKeyCredentialResponse;
@@ -24,10 +24,10 @@ import org.signal.chat.profile.GetUnversionedProfileResponse;
import org.signal.chat.profile.GetVersionedProfileRequest;
import org.signal.chat.profile.GetVersionedProfileResponse;
import org.signal.chat.profile.ProfileAvatarUploadAttributes;
import org.signal.chat.profile.ReactorProfileGrpc;
import org.signal.chat.profile.SetProfileRequest;
import org.signal.chat.profile.SetProfileRequest.AvatarChange;
import org.signal.chat.profile.SetProfileResponse;
import org.signal.chat.profile.SimpleProfileGrpc;
import org.signal.libsignal.zkgroup.profiles.ServerZkProfileOperations;
import org.whispersystems.textsecuregcm.auth.grpc.AuthenticatedDevice;
import org.whispersystems.textsecuregcm.auth.grpc.AuthenticationUtil;
@@ -35,6 +35,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.controllers.RateLimitExceededException;
import org.whispersystems.textsecuregcm.identity.IdentityType;
import org.whispersystems.textsecuregcm.identity.ServiceIdentifier;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
@@ -48,10 +49,8 @@ import org.whispersystems.textsecuregcm.storage.ProfilesManager;
import org.whispersystems.textsecuregcm.storage.VersionedProfile;
import org.whispersystems.textsecuregcm.util.Pair;
import org.whispersystems.textsecuregcm.util.ProfileHelper;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public class ProfileGrpcService extends ReactorProfileGrpc.ProfileImplBase {
public class ProfileGrpcService extends SimpleProfileGrpc.ProfileImplBase {
private final Clock clock;
private final AccountsManager accountsManager;
@@ -93,133 +92,132 @@ public class ProfileGrpcService extends ReactorProfileGrpc.ProfileImplBase {
}
@Override
public Mono<SetProfileResponse> setProfile(final SetProfileRequest request) {
public SetProfileResponse setProfile(final SetProfileRequest request) throws StatusException {
validateRequest(request);
return Mono.fromSupplier(AuthenticationUtil::requireAuthenticatedDevice)
.flatMap(authenticatedDevice -> Mono.zip(
Mono.fromFuture(() -> accountsManager.getByAccountIdentifierAsync(authenticatedDevice.accountIdentifier()))
.map(maybeAccount -> maybeAccount.orElseThrow(Status.UNAUTHENTICATED::asRuntimeException)),
Mono.fromFuture(() -> profilesManager.getAsync(authenticatedDevice.accountIdentifier(), request.getVersion()))
))
.doOnNext(accountAndMaybeProfile -> {
if (!request.getPaymentAddress().isEmpty()) {
final boolean hasDisallowedPrefix =
dynamicConfigurationManager.getConfiguration().getPaymentsConfiguration().getDisallowedPrefixes().stream()
.anyMatch(prefix -> accountAndMaybeProfile.getT1().getNumber().startsWith(prefix));
if (hasDisallowedPrefix && accountAndMaybeProfile.getT2().map(VersionedProfile::paymentAddress).isEmpty()) {
throw Status.PERMISSION_DENIED.asRuntimeException();
}
}
})
.flatMap(accountAndMaybeProfile -> {
final Account account = accountAndMaybeProfile.getT1();
final Optional<String> currentAvatar = accountAndMaybeProfile.getT2().map(VersionedProfile::avatar)
.filter(avatar -> avatar.startsWith("profiles/"));
final AvatarData avatarData = switch (AvatarChangeUtil.fromGrpcAvatarChange(request.getAvatarChange())) {
case AVATAR_CHANGE_UNCHANGED -> new AvatarData(currentAvatar, currentAvatar, Optional.empty());
case AVATAR_CHANGE_CLEAR -> new AvatarData(currentAvatar, Optional.empty(), Optional.empty());
case AVATAR_CHANGE_UPDATE -> {
final String updateAvatarObjectName = ProfileHelper.generateAvatarObjectName();
yield new AvatarData(currentAvatar, Optional.of(updateAvatarObjectName),
Optional.of(generateAvatarUploadForm(updateAvatarObjectName)));
}
};
final Mono<Void> profileSetMono = Mono.fromFuture(() -> profilesManager.setAsync(account.getUuid(),
new VersionedProfile(
request.getVersion(),
request.getName().toByteArray(),
avatarData.finalAvatar().orElse(null),
request.getAboutEmoji().toByteArray(),
request.getAbout().toByteArray(),
request.getPaymentAddress().toByteArray(),
request.getPhoneNumberSharing().toByteArray(),
request.getCommitment().toByteArray())));
final AuthenticatedDevice authenticatedDevice = AuthenticationUtil.requireAuthenticatedDevice();
final List<Mono<?>> updates = new ArrayList<>(2);
final Account account = accountsManager.getByAccountIdentifier(
authenticatedDevice.accountIdentifier()).orElseThrow(Status.UNAUTHENTICATED::asException);
final Optional<VersionedProfile> maybeProfile = profilesManager.get(
authenticatedDevice.accountIdentifier(), request.getVersion());
updates.add(Mono.fromFuture(() -> accountsManager.updateAsync(account, a -> {
if (!request.getPaymentAddress().isEmpty()) {
final boolean hasDisallowedPrefix =
dynamicConfigurationManager.getConfiguration().getPaymentsConfiguration().getDisallowedPrefixes().stream()
.anyMatch(prefix -> account.getNumber().startsWith(prefix));
if (hasDisallowedPrefix && maybeProfile.map(VersionedProfile::paymentAddress).isEmpty()) {
throw Status.PERMISSION_DENIED.asException();
}
}
final List<AccountBadge> updatedBadges = Optional.of(request.getBadgeIdsList())
.map(badges -> ProfileHelper.mergeBadgeIdsWithExistingAccountBadges(clock, badgeConfigurationMap, badges, a.getBadges()))
.orElseGet(a::getBadges);
final Optional<String> currentAvatar = maybeProfile.map(VersionedProfile::avatar)
.filter(avatar -> avatar.startsWith("profiles/"));
a.setBadges(clock, updatedBadges);
a.setCurrentProfileVersion(request.getVersion());
})));
final AvatarData avatarData = switch (AvatarChangeUtil.fromGrpcAvatarChange(request.getAvatarChange())) {
case AVATAR_CHANGE_UNCHANGED -> new AvatarData(currentAvatar, currentAvatar, Optional.empty());
case AVATAR_CHANGE_CLEAR -> new AvatarData(currentAvatar, Optional.empty(), Optional.empty());
case AVATAR_CHANGE_UPDATE -> {
final String updateAvatarObjectName = ProfileHelper.generateAvatarObjectName();
yield new AvatarData(currentAvatar, Optional.of(updateAvatarObjectName),
Optional.of(generateAvatarUploadForm(updateAvatarObjectName)));
}
};
if (request.getAvatarChange() != AvatarChange.AVATAR_CHANGE_UNCHANGED && avatarData.currentAvatar().isPresent()) {
updates.add(Mono.fromFuture(() -> profilesManager.deleteAvatar(avatarData.currentAvatar.get())));
}
return profileSetMono.thenMany(Flux.merge(updates)).then(Mono.just(avatarData));
})
.map(avatarData -> avatarData.uploadAttributes()
.map(avatarUploadAttributes -> SetProfileResponse.newBuilder().setAttributes(avatarUploadAttributes).build())
.orElse(SetProfileResponse.newBuilder().build())
);
profilesManager.set(account.getUuid(),
new VersionedProfile(
request.getVersion(),
request.getName().toByteArray(),
avatarData.finalAvatar().orElse(null),
request.getAboutEmoji().toByteArray(),
request.getAbout().toByteArray(),
request.getPaymentAddress().toByteArray(),
request.getPhoneNumberSharing().toByteArray(),
request.getCommitment().toByteArray()));
accountsManager.update(account, a -> {
final List<AccountBadge> updatedBadges = Optional.of(request.getBadgeIdsList())
.map(badges -> ProfileHelper.mergeBadgeIdsWithExistingAccountBadges(clock, badgeConfigurationMap, badges,
a.getBadges()))
.orElseGet(a::getBadges);
a.setBadges(clock, updatedBadges);
a.setCurrentProfileVersion(request.getVersion());
});
if (request.getAvatarChange() != AvatarChange.AVATAR_CHANGE_UNCHANGED && avatarData.currentAvatar().isPresent()) {
profilesManager.deleteAvatar(avatarData.currentAvatar().get());
}
return avatarData.uploadAttributes()
.map(avatarUploadAttributes -> SetProfileResponse.newBuilder().setAttributes(avatarUploadAttributes).build())
.orElse(SetProfileResponse.newBuilder().build());
}
@Override
public Mono<GetUnversionedProfileResponse> getUnversionedProfile(final GetUnversionedProfileRequest request) {
public GetUnversionedProfileResponse getUnversionedProfile(final GetUnversionedProfileRequest request) throws StatusException, RateLimitExceededException {
final AuthenticatedDevice authenticatedDevice = AuthenticationUtil.requireAuthenticatedDevice();
final ServiceIdentifier targetIdentifier =
ServiceIdentifierUtil.fromGrpcServiceIdentifier(request.getServiceIdentifier());
return validateRateLimitAndGetAccount(authenticatedDevice.accountIdentifier(), targetIdentifier)
.map(targetAccount -> ProfileGrpcHelper.buildUnversionedProfileResponse(targetIdentifier,
final Account targetAccount = validateRateLimitAndGetAccount(authenticatedDevice.accountIdentifier(), targetIdentifier);
return ProfileGrpcHelper.buildUnversionedProfileResponse(targetIdentifier,
authenticatedDevice.accountIdentifier(),
targetAccount,
profileBadgeConverter));
profileBadgeConverter);
}
@Override
public Mono<GetVersionedProfileResponse> getVersionedProfile(final GetVersionedProfileRequest request) {
public GetVersionedProfileResponse getVersionedProfile(final GetVersionedProfileRequest request) throws StatusException, RateLimitExceededException {
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();
throw Status.INVALID_ARGUMENT.withDescription("Expected ACI service identifier").asException();
}
return validateRateLimitAndGetAccount(authenticatedDevice.accountIdentifier(), targetIdentifier)
.flatMap(account -> ProfileGrpcHelper.getVersionedProfile(account, profilesManager, request.getVersion()));
final Account account = validateRateLimitAndGetAccount(authenticatedDevice.accountIdentifier(), targetIdentifier);
return ProfileGrpcHelper.getVersionedProfile(account, profilesManager, request.getVersion());
}
@Override
public Mono<GetExpiringProfileKeyCredentialResponse> getExpiringProfileKeyCredential(
final GetExpiringProfileKeyCredentialRequest request) {
public GetExpiringProfileKeyCredentialResponse getExpiringProfileKeyCredential(
final GetExpiringProfileKeyCredentialRequest request) throws StatusException, RateLimitExceededException {
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();
throw Status.INVALID_ARGUMENT.withDescription("Expected ACI service identifier").asException();
}
if (request.getCredentialType() != CredentialType.CREDENTIAL_TYPE_EXPIRING_PROFILE_KEY) {
throw Status.INVALID_ARGUMENT.withDescription("Expected expiring profile key credential type").asRuntimeException();
throw Status.INVALID_ARGUMENT.withDescription("Expected expiring profile key credential type").asException();
}
return validateRateLimitAndGetAccount(authenticatedDevice.accountIdentifier(), targetIdentifier)
.flatMap(targetAccount -> ProfileGrpcHelper.getExpiringProfileKeyCredentialResponse(targetAccount.getUuid(),
request.getVersion(), request.getCredentialRequest().toByteArray(), profilesManager, zkProfileOperations));
final Account targetAccount = validateRateLimitAndGetAccount(authenticatedDevice.accountIdentifier(), targetIdentifier);
return ProfileGrpcHelper.getExpiringProfileKeyCredentialResponse(targetAccount.getUuid(),
request.getVersion(), request.getCredentialRequest().toByteArray(), profilesManager, zkProfileOperations);
}
private Mono<Account> validateRateLimitAndGetAccount(final UUID requesterUuid,
final ServiceIdentifier targetIdentifier) {
return rateLimiters.getProfileLimiter().validateReactive(requesterUuid)
.then(Mono.fromFuture(() -> accountsManager.getByServiceIdentifierAsync(targetIdentifier))
.flatMap(Mono::justOrEmpty))
.switchIfEmpty(Mono.error(Status.NOT_FOUND.asException()));
private Account validateRateLimitAndGetAccount(final UUID requesterUuid,
final ServiceIdentifier targetIdentifier) throws RateLimitExceededException, StatusException {
rateLimiters.getProfileLimiter().validate(requesterUuid);
return accountsManager.getByServiceIdentifier(targetIdentifier).orElseThrow(Status.NOT_FOUND::asException);
}
private void validateRequest(final SetProfileRequest request) {
private void validateRequest(final SetProfileRequest request) throws StatusException {
if (request.getVersion().isEmpty()) {
throw Status.INVALID_ARGUMENT.withDescription("Missing version").asRuntimeException();
throw Status.INVALID_ARGUMENT.withDescription("Missing version").asException();
}
if (request.getCommitment().isEmpty()) {
throw Status.INVALID_ARGUMENT.withDescription("Missing profile commitment").asRuntimeException();
throw Status.INVALID_ARGUMENT.withDescription("Missing profile commitment").asException();
}
checkByteStringLength(request.getName(), "Invalid name length", List.of(81, 285));
@@ -228,7 +226,9 @@ public class ProfileGrpcService extends ReactorProfileGrpc.ProfileImplBase {
checkByteStringLength(request.getPaymentAddress(), "Invalid mobile coin address length", List.of(0, 582));
}
private static void checkByteStringLength(final ByteString byteString, final String errorMessage, final List<Integer> allowedLengths) {
private static void checkByteStringLength(final ByteString byteString, final String errorMessage,
final List<Integer> allowedLengths) throws StatusException {
final int byteStringLength = byteString.toByteArray().length;
for (int allowedLength : allowedLengths) {
@@ -237,7 +237,7 @@ public class ProfileGrpcService extends ReactorProfileGrpc.ProfileImplBase {
}
}
throw Status.INVALID_ARGUMENT.withDescription(errorMessage).asRuntimeException();
throw Status.INVALID_ARGUMENT.withDescription(errorMessage).asException();
}
private ProfileAvatarUploadAttributes generateAvatarUploadForm(final String objectName) {