From 19a08f01e8c33d1d0be86db7fe16ed2e3e4708a9 Mon Sep 17 00:00:00 2001 From: Katherine Yen Date: Wed, 16 Aug 2023 13:45:16 -0700 Subject: [PATCH] Write certain profile data as bytes instead of strings to dynamo and represent those fields as byte arrays on `VersionedProfile` --- .../controllers/ProfileController.java | 44 ++- .../grpc/ProfileGrpcService.java | 17 +- .../textsecuregcm/storage/Profiles.java | 57 ++-- .../storage/ProfilesManager.java | 4 +- .../storage/VersionedProfile.java | 91 ++---- .../ByteArrayBase64WithPaddingAdapter.java | 27 ++ .../controllers/ProfileControllerTest.java | 262 ++++++++++-------- .../grpc/ProfileGrpcServiceTest.java | 33 ++- .../storage/ProfilesManagerTest.java | 71 +++-- .../textsecuregcm/storage/ProfilesTest.java | 217 +++++++-------- .../tests/util/ProfileHelper.java | 20 ++ 11 files changed, 438 insertions(+), 405 deletions(-) create mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/util/ByteArrayBase64WithPaddingAdapter.java create mode 100644 service/src/test/java/org/whispersystems/textsecuregcm/tests/util/ProfileHelper.java diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java index b4a5ccd8b..64350467a 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java @@ -35,6 +35,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.function.Function; import java.util.stream.Collectors; +import javax.annotation.Nullable; import javax.validation.Valid; import javax.validation.constraints.NotNull; import javax.ws.rs.BadRequestException; @@ -176,15 +177,15 @@ public class ProfileController { dynamicConfigurationManager.getConfiguration().getPaymentsConfiguration().getDisallowedPrefixes().stream() .anyMatch(prefix -> auth.getAccount().getNumber().startsWith(prefix)); - if (hasDisallowedPrefix && currentProfile.map(VersionedProfile::getPaymentAddress).isEmpty()) { + if (hasDisallowedPrefix && currentProfile.map(VersionedProfile::paymentAddress).isEmpty()) { return Response.status(Response.Status.FORBIDDEN).build(); } } Optional currentAvatar = Optional.empty(); - if (currentProfile.isPresent() && currentProfile.get().getAvatar() != null && currentProfile.get().getAvatar() + if (currentProfile.isPresent() && currentProfile.get().avatar() != null && currentProfile.get().avatar() .startsWith("profiles/")) { - currentAvatar = Optional.of(currentProfile.get().getAvatar()); + currentAvatar = Optional.of(currentProfile.get().avatar()); } final String avatar = switch (request.getAvatarChange()) { @@ -196,11 +197,11 @@ public class ProfileController { profilesManager.set(auth.getAccount().getUuid(), new VersionedProfile( request.getVersion(), - request.getName(), + decodeFromBase64(request.getName()), avatar, - request.getAboutEmoji(), - request.getAbout(), - request.getPaymentAddress(), + decodeFromBase64(request.getAboutEmoji()), + decodeFromBase64(request.getAbout()), + decodeFromBase64(request.getPaymentAddress()), request.getCommitment().serialize())); if (request.getAvatarChange() != CreateProfileRequest.AvatarChange.UNCHANGED) { @@ -406,16 +407,17 @@ public class ProfileController { VERSION_NOT_FOUND_COUNTER.increment(); } - final String name = maybeProfile.map(VersionedProfile::getName).orElse(null); - final String about = maybeProfile.map(VersionedProfile::getAbout).orElse(null); - final String aboutEmoji = maybeProfile.map(VersionedProfile::getAboutEmoji).orElse(null); - final String avatar = maybeProfile.map(VersionedProfile::getAvatar).orElse(null); + final String name = maybeProfile.map(VersionedProfile::name).map(ProfileController::encodeToBase64).orElse(null); + final String about = maybeProfile.map(VersionedProfile::about).map(ProfileController::encodeToBase64).orElse(null); + final String aboutEmoji = maybeProfile.map(VersionedProfile::aboutEmoji).map(ProfileController::encodeToBase64).orElse(null); + final String avatar = maybeProfile.map(VersionedProfile::avatar).orElse(null); // 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. final String paymentAddress = maybeProfile .filter(p -> account.getCurrentProfileVersion().map(v -> v.equals(version)).orElse(true)) - .map(VersionedProfile::getPaymentAddress) + .map(VersionedProfile::paymentAddress) + .map(ProfileController::encodeToBase64) .orElse(null); return new VersionedProfileResponse( @@ -454,7 +456,7 @@ public class ProfileController { final Instant expiration) { try { - final ProfileKeyCommitment commitment = new ProfileKeyCommitment(profile.getCommitment()); + final ProfileKeyCommitment commitment = new ProfileKeyCommitment(profile.commitment()); final ProfileKeyCredentialRequest request = new ProfileKeyCredentialRequest( HexFormat.of().parseHex(encodedCredentialRequest)); @@ -516,4 +518,20 @@ public class ProfileController { private boolean isSelfProfileRequest(final Optional maybeRequester, final AciServiceIdentifier targetIdentifier) { return maybeRequester.map(requester -> requester.getUuid().equals(targetIdentifier.uuid())).orElse(false); } + + @Nullable + private static byte[] decodeFromBase64(@Nullable final String input) { + if (input == null) { + return null; + } + return Base64.getDecoder().decode(input); + } + + @Nullable + private static String encodeToBase64(@Nullable final byte[] input) { + if (input == null) { + return null; + } + return Base64.getEncoder().encodeToString(input); + } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/grpc/ProfileGrpcService.java b/service/src/main/java/org/whispersystems/textsecuregcm/grpc/ProfileGrpcService.java index 0701df3cc..1f8a0775e 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/grpc/ProfileGrpcService.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/grpc/ProfileGrpcService.java @@ -25,7 +25,6 @@ import software.amazon.awssdk.services.s3.S3AsyncClient; import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; import java.time.Clock; import java.util.ArrayList; -import java.util.Base64; import java.util.List; import java.util.Map; import java.util.Optional; @@ -85,14 +84,14 @@ public class ProfileGrpcService extends ReactorProfileGrpc.ProfileImplBase { final boolean hasDisallowedPrefix = dynamicConfigurationManager.getConfiguration().getPaymentsConfiguration().getDisallowedPrefixes().stream() .anyMatch(prefix -> accountAndMaybeProfile.getT1().getNumber().startsWith(prefix)); - if (hasDisallowedPrefix && accountAndMaybeProfile.getT2().map(VersionedProfile::getPaymentAddress).isEmpty()) { + if (hasDisallowedPrefix && accountAndMaybeProfile.getT2().map(VersionedProfile::paymentAddress).isEmpty()) { throw Status.PERMISSION_DENIED.asRuntimeException(); } } }) .flatMap(accountAndMaybeProfile -> { final Account account = accountAndMaybeProfile.getT1(); - final Optional currentAvatar = accountAndMaybeProfile.getT2().map(VersionedProfile::getAvatar) + final Optional 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()); @@ -107,11 +106,11 @@ public class ProfileGrpcService extends ReactorProfileGrpc.ProfileImplBase { final Mono profileSetMono = Mono.fromFuture(profilesManager.setAsync(account.getUuid(), new VersionedProfile( request.getVersion(), - encodeToBase64(request.getName().toByteArray()), + request.getName().toByteArray(), avatarData.finalAvatar().orElse(null), - encodeToBase64(request.getAboutEmoji().toByteArray()), - encodeToBase64(request.getAbout().toByteArray()), - encodeToBase64(request.getPaymentAddress().toByteArray()), + request.getAboutEmoji().toByteArray(), + request.getAbout().toByteArray(), + request.getPaymentAddress().toByteArray(), request.getCommitment().toByteArray()))); final List> updates = new ArrayList<>(2); @@ -163,8 +162,4 @@ public class ProfileGrpcService extends ReactorProfileGrpc.ProfileImplBase { throw Status.INVALID_ARGUMENT.withDescription(errorMessage).asRuntimeException(); } - - private static String encodeToBase64(byte[] input) { - return Base64.getEncoder().encodeToString(input); - } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/Profiles.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/Profiles.java index 5c65745da..b62bdbff5 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/Profiles.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/Profiles.java @@ -11,7 +11,6 @@ import com.google.common.annotations.VisibleForTesting; import io.micrometer.core.instrument.Metrics; import io.micrometer.core.instrument.Timer; import java.util.ArrayList; -import java.util.Base64; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -47,19 +46,19 @@ public class Profiles { @VisibleForTesting static final String ATTR_VERSION = "V"; - // User's name; string + // User's name; byte array private static final String ATTR_NAME = "N"; // Avatar path/filename; string private static final String ATTR_AVATAR = "A"; - // Bio/about text; string + // Bio/about text; byte array private static final String ATTR_ABOUT = "B"; - // Bio/about emoji; string + // Bio/about emoji; byte array private static final String ATTR_EMOJI = "E"; - // Payment address; string + // Payment address; byte array private static final String ATTR_PAYMENT_ADDRESS = "P"; // Commitment; byte array @@ -91,7 +90,7 @@ public class Profiles { SET_PROFILES_TIMER.record(() -> { dynamoDbClient.updateItem(UpdateItemRequest.builder() .tableName(tableName) - .key(buildPrimaryKey(uuid, profile.getVersion())) + .key(buildPrimaryKey(uuid, profile.version())) .updateExpression(buildUpdateExpression(profile)) .expressionAttributeNames(UPDATE_EXPRESSION_ATTRIBUTE_NAMES) .expressionAttributeValues(buildUpdateExpressionAttributeValues(profile)) @@ -102,7 +101,7 @@ public class Profiles { public CompletableFuture setAsync(final UUID uuid, final VersionedProfile profile) { return AsyncTimerUtil.record(SET_PROFILES_TIMER, () -> dynamoDbAsyncClient.updateItem(UpdateItemRequest.builder() .tableName(tableName) - .key(buildPrimaryKey(uuid, profile.getVersion())) + .key(buildPrimaryKey(uuid, profile.version())) .updateExpression(buildUpdateExpression(profile)) .expressionAttributeNames(UPDATE_EXPRESSION_ATTRIBUTE_NAMES) .expressionAttributeValues(buildUpdateExpressionAttributeValues(profile)) @@ -122,31 +121,31 @@ public class Profiles { final List updatedAttributes = new ArrayList<>(5); final List deletedAttributes = new ArrayList<>(5); - if (StringUtils.isNotBlank(profile.getName())) { + if (profile.name() != null) { updatedAttributes.add("name"); } else { deletedAttributes.add("name"); } - if (StringUtils.isNotBlank(profile.getAvatar())) { + if (StringUtils.isNotBlank(profile.avatar())) { updatedAttributes.add("avatar"); } else { deletedAttributes.add("avatar"); } - if (StringUtils.isNotBlank(profile.getAbout())) { + if (profile.about() != null) { updatedAttributes.add("about"); } else { deletedAttributes.add("about"); } - if (StringUtils.isNotBlank(profile.getAboutEmoji())) { + if (profile.aboutEmoji() != null) { updatedAttributes.add("aboutEmoji"); } else { deletedAttributes.add("aboutEmoji"); } - if (StringUtils.isNotBlank(profile.getPaymentAddress())) { + if (profile.paymentAddress() != null) { updatedAttributes.add("paymentAddress"); } else { deletedAttributes.add("paymentAddress"); @@ -177,26 +176,26 @@ public class Profiles { static Map buildUpdateExpressionAttributeValues(final VersionedProfile profile) { final Map expressionValues = new HashMap<>(); - expressionValues.put(":commitment", AttributeValues.fromByteArray(profile.getCommitment())); + expressionValues.put(":commitment", AttributeValues.fromByteArray(profile.commitment())); - if (StringUtils.isNotBlank(profile.getName())) { - expressionValues.put(":name", AttributeValues.fromString(profile.getName())); + if (profile.name() != null) { + expressionValues.put(":name", AttributeValues.fromByteArray(profile.name())); } - if (StringUtils.isNotBlank(profile.getAvatar())) { - expressionValues.put(":avatar", AttributeValues.fromString(profile.getAvatar())); + if (StringUtils.isNotBlank(profile.avatar())) { + expressionValues.put(":avatar", AttributeValues.fromString(profile.avatar())); } - if (StringUtils.isNotBlank(profile.getAbout())) { - expressionValues.put(":about", AttributeValues.fromString(profile.getAbout())); + if (profile.about() != null) { + expressionValues.put(":about", AttributeValues.fromByteArray(profile.about())); } - if (StringUtils.isNotBlank(profile.getAboutEmoji())) { - expressionValues.put(":aboutEmoji", AttributeValues.fromString(profile.getAboutEmoji())); + if (profile.aboutEmoji() != null) { + expressionValues.put(":aboutEmoji", AttributeValues.fromByteArray(profile.aboutEmoji())); } - if (StringUtils.isNotBlank(profile.getPaymentAddress())) { - expressionValues.put(":paymentAddress", AttributeValues.fromString(profile.getPaymentAddress())); + if (profile.paymentAddress() != null) { + expressionValues.put(":paymentAddress", AttributeValues.fromByteArray(profile.paymentAddress())); } return expressionValues; @@ -228,21 +227,21 @@ public class Profiles { private static VersionedProfile fromItem(final Map item) { return new VersionedProfile( AttributeValues.getString(item, ATTR_VERSION, null), - getBase64EncodedBytes(item, ATTR_NAME, PARSE_BYTE_ARRAY_COUNTER_NAME), + getBytes(item, ATTR_NAME), AttributeValues.getString(item, ATTR_AVATAR, null), - getBase64EncodedBytes(item, ATTR_EMOJI, PARSE_BYTE_ARRAY_COUNTER_NAME), - getBase64EncodedBytes(item, ATTR_ABOUT, PARSE_BYTE_ARRAY_COUNTER_NAME), - getBase64EncodedBytes(item, ATTR_PAYMENT_ADDRESS, PARSE_BYTE_ARRAY_COUNTER_NAME), + getBytes(item, ATTR_EMOJI), + getBytes(item, ATTR_ABOUT), + getBytes(item, ATTR_PAYMENT_ADDRESS), AttributeValues.getByteArray(item, ATTR_COMMITMENT, null)); } - private static String getBase64EncodedBytes(final Map item, final String attributeName, final String counterName) { + private static byte[] getBytes(final Map item, final String attributeName) { final AttributeValue attributeValue = item.get(attributeName); if (attributeValue == null) { return null; } - return Base64.getEncoder().encodeToString(AttributeValues.extractByteArray(attributeValue, counterName)); + return AttributeValues.extractByteArray(attributeValue, PARSE_BYTE_ARRAY_COUNTER_NAME); } public void deleteAll(final UUID uuid) { diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/ProfilesManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/ProfilesManager.java index 6b6e4cdde..7274f0a53 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/ProfilesManager.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/ProfilesManager.java @@ -77,7 +77,7 @@ public class ProfilesManager { try { final String profileJson = mapper.writeValueAsString(profile); - cacheCluster.useCluster(connection -> connection.sync().hset(getCacheKey(uuid), profile.getVersion(), profileJson)); + cacheCluster.useCluster(connection -> connection.sync().hset(getCacheKey(uuid), profile.version(), profileJson)); } catch (JsonProcessingException e) { throw new IllegalArgumentException(e); } @@ -93,7 +93,7 @@ public class ProfilesManager { } return cacheCluster.withCluster(connection -> - connection.async().hset(getCacheKey(uuid), profile.getVersion(), profileJson)) + connection.async().hset(getCacheKey(uuid), profile.version(), profileJson)) .thenRun(Util.NOOP) .toCompletableFuture(); } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/VersionedProfile.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/VersionedProfile.java index 38cd0cf52..2535c9b9f 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/VersionedProfile.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/VersionedProfile.java @@ -10,84 +10,29 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.whispersystems.textsecuregcm.util.ByteArrayAdapter; +import org.whispersystems.textsecuregcm.util.ByteArrayBase64WithPaddingAdapter; import java.util.Arrays; import java.util.Objects; -public class VersionedProfile { +public record VersionedProfile (String version, + @JsonSerialize(using = ByteArrayBase64WithPaddingAdapter.Serializing.class) + @JsonDeserialize(using = ByteArrayBase64WithPaddingAdapter.Deserializing.class) + byte[] name, - private final String version; - private final String name; - private final String avatar; - private final String aboutEmoji; - private final String about; - private final String paymentAddress; + String avatar, - @JsonSerialize(using = ByteArrayAdapter.Serializing.class) - @JsonDeserialize(using = ByteArrayAdapter.Deserializing.class) - private byte[] commitment; + @JsonSerialize(using = ByteArrayBase64WithPaddingAdapter.Serializing.class) + @JsonDeserialize(using = ByteArrayBase64WithPaddingAdapter.Deserializing.class) + byte[] aboutEmoji, - @JsonCreator - public VersionedProfile( - @JsonProperty("version") final String version, - @JsonProperty("name") final String name, - @JsonProperty("avatar") final String avatar, - @JsonProperty("aboutEmoji") final String aboutEmoji, - @JsonProperty("about") final String about, - @JsonProperty("paymentAddress") final String paymentAddress, - @JsonProperty("commitment") final byte[] commitment) { - this.version = version; - this.name = name; - this.avatar = avatar; - this.aboutEmoji = aboutEmoji; - this.about = about; - this.paymentAddress = paymentAddress; - this.commitment = commitment; - } + @JsonSerialize(using = ByteArrayBase64WithPaddingAdapter.Serializing.class) + @JsonDeserialize(using = ByteArrayBase64WithPaddingAdapter.Deserializing.class) + byte[] about, - public String getVersion() { - return version; - } + @JsonSerialize(using = ByteArrayBase64WithPaddingAdapter.Serializing.class) + @JsonDeserialize(using = ByteArrayBase64WithPaddingAdapter.Deserializing.class) + byte[] paymentAddress, - public String getName() { - return name; - } - - public String getAvatar() { - return avatar; - } - - public String getAboutEmoji() { - return aboutEmoji; - } - - public String getAbout() { - return about; - } - - public String getPaymentAddress() { - return paymentAddress; - } - - public byte[] getCommitment() { - return commitment; - } - - @Override - public boolean equals(final Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - final VersionedProfile that = (VersionedProfile) o; - return Objects.equals(version, that.version) && Objects.equals(name, that.name) && Objects.equals(avatar, - that.avatar) && Objects.equals(aboutEmoji, that.aboutEmoji) && Objects.equals(about, that.about) - && Objects.equals(paymentAddress, that.paymentAddress) && Arrays.equals(commitment, that.commitment); - } - - @Override - public int hashCode() { - int result = Objects.hash(version, name, avatar, aboutEmoji, about, paymentAddress); - result = 31 * result + Arrays.hashCode(commitment); - return result; - } -} + @JsonSerialize(using = ByteArrayAdapter.Serializing.class) + @JsonDeserialize(using = ByteArrayAdapter.Deserializing.class) + byte[] commitment) {} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/util/ByteArrayBase64WithPaddingAdapter.java b/service/src/main/java/org/whispersystems/textsecuregcm/util/ByteArrayBase64WithPaddingAdapter.java new file mode 100644 index 000000000..78188a478 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/util/ByteArrayBase64WithPaddingAdapter.java @@ -0,0 +1,27 @@ +package org.whispersystems.textsecuregcm.util; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import java.io.IOException; +import java.util.Base64; + +public class ByteArrayBase64WithPaddingAdapter { + public static class Serializing extends JsonSerializer { + @Override + public void serialize(byte[] bytes, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) + throws IOException { + jsonGenerator.writeString(Base64.getEncoder().encodeToString(bytes)); + } + } + + public static class Deserializing extends JsonDeserializer { + @Override + public byte[] deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { + return Base64.getDecoder().decode(jsonParser.getValueAsString()); + } + } +} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ProfileControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ProfileControllerTest.java index 40fa886fb..072d8dd50 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ProfileControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ProfileControllerTest.java @@ -48,7 +48,6 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedHashMap; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; -import org.apache.commons.lang3.RandomStringUtils; import org.assertj.core.api.Condition; import org.glassfish.jersey.server.ServerProperties; import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; @@ -108,6 +107,7 @@ import org.whispersystems.textsecuregcm.storage.ProfilesManager; import org.whispersystems.textsecuregcm.storage.VersionedProfile; import org.whispersystems.textsecuregcm.tests.util.AccountsHelper; import org.whispersystems.textsecuregcm.tests.util.AuthHelper; +import org.whispersystems.textsecuregcm.tests.util.ProfileHelper; import org.whispersystems.textsecuregcm.util.SystemMapper; import org.whispersystems.textsecuregcm.util.TestClock; import org.whispersystems.textsecuregcm.util.Util; @@ -226,9 +226,13 @@ class ProfileControllerTest { when(accountsManager.getByAccountIdentifier(AuthHelper.VALID_UUID)).thenReturn(Optional.of(capabilitiesAccount)); when(accountsManager.getByServiceIdentifier(new AciServiceIdentifier(AuthHelper.VALID_UUID))).thenReturn(Optional.of(capabilitiesAccount)); + final byte[] name = ProfileHelper.generateRandomByteArray(81); + final byte[] emoji = ProfileHelper.generateRandomByteArray(60); + final byte[] about = ProfileHelper.generateRandomByteArray(156); + when(profilesManager.get(eq(AuthHelper.VALID_UUID), eq("someversion"))).thenReturn(Optional.empty()); when(profilesManager.get(eq(AuthHelper.VALID_UUID_TWO), eq("validversion"))).thenReturn(Optional.of(new VersionedProfile( - "validversion", "validname", "profiles/validavatar", "emoji", "about", null, "validcommitmnet".getBytes()))); + "validversion", name, "profiles/validavatar", emoji, about, null, "validcommitmnet".getBytes()))); clearInvocations(rateLimiter); clearInvocations(accountsManager); @@ -409,14 +413,14 @@ class ProfileControllerTest { @Test void testSetProfileWantAvatarUpload() throws InvalidInputException { final ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID)); + final byte[] name = ProfileHelper.generateRandomByteArray(81); final ProfileAvatarUploadAttributes uploadAttributes = resources.getJerseyTest() .target("/v1/profile/") .request() .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)) .put(Entity.entity(new CreateProfileRequest(commitment, "someversion", - "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678", - null, null, + ProfileHelper.encodeToBase64(name), null, null, null, true, false, List.of()), MediaType.APPLICATION_JSON_TYPE), ProfileAvatarUploadAttributes.class); final ArgumentCaptor profileArgumentCaptor = ArgumentCaptor.forClass(VersionedProfile.class); @@ -426,24 +430,24 @@ class ProfileControllerTest { verifyNoMoreInteractions(s3client); - assertThat(profileArgumentCaptor.getValue().getCommitment()).isEqualTo(commitment.serialize()); - assertThat(profileArgumentCaptor.getValue().getAvatar()).isEqualTo(uploadAttributes.getKey()); - assertThat(profileArgumentCaptor.getValue().getVersion()).isEqualTo("someversion"); - assertThat(profileArgumentCaptor.getValue().getName()).isEqualTo("123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678"); - assertThat(profileArgumentCaptor.getValue().getAboutEmoji()).isNull(); - assertThat(profileArgumentCaptor.getValue().getAbout()).isNull(); + assertThat(profileArgumentCaptor.getValue().commitment()).isEqualTo(commitment.serialize()); + assertThat(profileArgumentCaptor.getValue().avatar()).isEqualTo(uploadAttributes.getKey()); + assertThat(profileArgumentCaptor.getValue().version()).isEqualTo("someversion"); + assertThat(profileArgumentCaptor.getValue().name()).isEqualTo(name); + assertThat(profileArgumentCaptor.getValue().aboutEmoji()).isNull(); + assertThat(profileArgumentCaptor.getValue().about()).isNull(); } @Test void testSetProfileWantAvatarUploadWithBadProfileSize() throws InvalidInputException { final ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID)); + final String name = ProfileHelper.generateRandomBase64FromByteArray(82); try (final Response response = resources.getJerseyTest() .target("/v1/profile/") .request() .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)) - .put(Entity.entity(new CreateProfileRequest(commitment, "someversion", - "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", + .put(Entity.entity(new CreateProfileRequest(commitment, "someversion", name, null, null, null, true, false, List.of()), MediaType.APPLICATION_JSON_TYPE))) { assertThat(response.getStatus()).isEqualTo(422); @@ -453,6 +457,7 @@ class ProfileControllerTest { @Test void testSetProfileWithoutAvatarUpload() throws InvalidInputException { final ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID)); + final byte[] name = ProfileHelper.generateRandomByteArray(81); clearInvocations(AuthHelper.VALID_ACCOUNT_TWO); @@ -460,7 +465,7 @@ class ProfileControllerTest { .target("/v1/profile/") .request() .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO)) - .put(Entity.entity(new CreateProfileRequest(commitment, "anotherversion", "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678", null, null, + .put(Entity.entity(new CreateProfileRequest(commitment, "anotherversion", ProfileHelper.encodeToBase64(name), null, null, null, false, false, List.of()), MediaType.APPLICATION_JSON_TYPE))) { assertThat(response.getStatus()).isEqualTo(200); @@ -473,26 +478,26 @@ class ProfileControllerTest { verifyNoMoreInteractions(s3client); - assertThat(profileArgumentCaptor.getValue().getCommitment()).isEqualTo(commitment.serialize()); - assertThat(profileArgumentCaptor.getValue().getAvatar()).isNull(); - assertThat(profileArgumentCaptor.getValue().getVersion()).isEqualTo("anotherversion"); - assertThat(profileArgumentCaptor.getValue().getName()).isEqualTo("123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678"); - assertThat(profileArgumentCaptor.getValue().getAboutEmoji()).isNull(); - assertThat(profileArgumentCaptor.getValue().getAbout()).isNull(); + assertThat(profileArgumentCaptor.getValue().commitment()).isEqualTo(commitment.serialize()); + assertThat(profileArgumentCaptor.getValue().avatar()).isNull(); + assertThat(profileArgumentCaptor.getValue().version()).isEqualTo("anotherversion"); + assertThat(profileArgumentCaptor.getValue().name()).isEqualTo(name); + assertThat(profileArgumentCaptor.getValue().aboutEmoji()).isNull(); + assertThat(profileArgumentCaptor.getValue().about()).isNull(); } } @Test void testSetProfileWithAvatarUploadAndPreviousAvatar() throws InvalidInputException { final ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID_TWO)); + final byte[] name = ProfileHelper.generateRandomByteArray(81); resources.getJerseyTest() .target("/v1/profile/") .request() .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO)) .put(Entity.entity(new CreateProfileRequest(commitment, "validversion", - "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678", - null, null, + ProfileHelper.encodeToBase64(name), null, null, null, true, false, List.of()), MediaType.APPLICATION_JSON_TYPE), ProfileAvatarUploadAttributes.class); final ArgumentCaptor profileArgumentCaptor = ArgumentCaptor.forClass(VersionedProfile.class); @@ -501,24 +506,24 @@ class ProfileControllerTest { verify(profilesManager, times(1)).set(eq(AuthHelper.VALID_UUID_TWO), profileArgumentCaptor.capture()); verify(s3client, times(1)).deleteObject(eq(DeleteObjectRequest.builder().bucket("profilesBucket").key("profiles/validavatar").build())); - assertThat(profileArgumentCaptor.getValue().getCommitment()).isEqualTo(commitment.serialize()); - assertThat(profileArgumentCaptor.getValue().getAvatar()).startsWith("profiles/"); - assertThat(profileArgumentCaptor.getValue().getVersion()).isEqualTo("validversion"); - assertThat(profileArgumentCaptor.getValue().getName()).isEqualTo("123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678"); - assertThat(profileArgumentCaptor.getValue().getAboutEmoji()).isNull(); - assertThat(profileArgumentCaptor.getValue().getAbout()).isNull(); + assertThat(profileArgumentCaptor.getValue().commitment()).isEqualTo(commitment.serialize()); + assertThat(profileArgumentCaptor.getValue().avatar()).startsWith("profiles/"); + assertThat(profileArgumentCaptor.getValue().version()).isEqualTo("validversion"); + assertThat(profileArgumentCaptor.getValue().name()).isEqualTo(name); + assertThat(profileArgumentCaptor.getValue().aboutEmoji()).isNull(); + assertThat(profileArgumentCaptor.getValue().about()).isNull(); } @Test void testSetProfileClearPreviousAvatar() throws InvalidInputException { final ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID_TWO)); + final byte[] name = ProfileHelper.generateRandomByteArray(81); try (final Response response = resources.getJerseyTest() .target("/v1/profile/") .request() .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO)) - .put(Entity.entity(new CreateProfileRequest(commitment, "validversion", - "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678", + .put(Entity.entity(new CreateProfileRequest(commitment, "validversion", ProfileHelper.encodeToBase64(name), null, null, null, false, false, List.of()), MediaType.APPLICATION_JSON_TYPE))) { assertThat(response.getStatus()).isEqualTo(200); @@ -530,25 +535,25 @@ class ProfileControllerTest { verify(profilesManager, times(1)).set(eq(AuthHelper.VALID_UUID_TWO), profileArgumentCaptor.capture()); verify(s3client, times(1)).deleteObject(eq(DeleteObjectRequest.builder().bucket("profilesBucket").key("profiles/validavatar").build())); - assertThat(profileArgumentCaptor.getValue().getCommitment()).isEqualTo(commitment.serialize()); - assertThat(profileArgumentCaptor.getValue().getAvatar()).isNull(); - assertThat(profileArgumentCaptor.getValue().getVersion()).isEqualTo("validversion"); - assertThat(profileArgumentCaptor.getValue().getName()).isEqualTo("123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678"); - assertThat(profileArgumentCaptor.getValue().getAboutEmoji()).isNull(); - assertThat(profileArgumentCaptor.getValue().getAbout()).isNull(); + assertThat(profileArgumentCaptor.getValue().commitment()).isEqualTo(commitment.serialize()); + assertThat(profileArgumentCaptor.getValue().avatar()).isNull(); + assertThat(profileArgumentCaptor.getValue().version()).isEqualTo("validversion"); + assertThat(profileArgumentCaptor.getValue().name()).isEqualTo(name); + assertThat(profileArgumentCaptor.getValue().aboutEmoji()).isNull(); + assertThat(profileArgumentCaptor.getValue().about()).isNull(); } } @Test void testSetProfileWithSameAvatar() throws InvalidInputException { final ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID_TWO)); + final byte[] name = ProfileHelper.generateRandomByteArray(81); try (final Response response = resources.getJerseyTest() .target("/v1/profile/") .request() .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO)) - .put(Entity.entity(new CreateProfileRequest(commitment, "validversion", - "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678", + .put(Entity.entity(new CreateProfileRequest(commitment, "validversion", ProfileHelper.encodeToBase64(name), null, null, null, true, true, List.of()), MediaType.APPLICATION_JSON_TYPE))) { assertThat(response.getStatus()).isEqualTo(200); @@ -560,25 +565,25 @@ class ProfileControllerTest { verify(profilesManager, times(1)).set(eq(AuthHelper.VALID_UUID_TWO), profileArgumentCaptor.capture()); verify(s3client, never()).deleteObject(any(DeleteObjectRequest.class)); - assertThat(profileArgumentCaptor.getValue().getCommitment()).isEqualTo(commitment.serialize()); - assertThat(profileArgumentCaptor.getValue().getAvatar()).isEqualTo("profiles/validavatar"); - assertThat(profileArgumentCaptor.getValue().getVersion()).isEqualTo("validversion"); - assertThat(profileArgumentCaptor.getValue().getName()).isEqualTo("123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678"); - assertThat(profileArgumentCaptor.getValue().getAboutEmoji()).isNull(); - assertThat(profileArgumentCaptor.getValue().getAbout()).isNull(); + assertThat(profileArgumentCaptor.getValue().commitment()).isEqualTo(commitment.serialize()); + assertThat(profileArgumentCaptor.getValue().avatar()).isEqualTo("profiles/validavatar"); + assertThat(profileArgumentCaptor.getValue().version()).isEqualTo("validversion"); + assertThat(profileArgumentCaptor.getValue().name()).isEqualTo(name); + assertThat(profileArgumentCaptor.getValue().aboutEmoji()).isNull(); + assertThat(profileArgumentCaptor.getValue().about()).isNull(); } } @Test void testSetProfileClearPreviousAvatarDespiteSameAvatarFlagSet() throws InvalidInputException { final ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID_TWO)); + final byte[] name = ProfileHelper.generateRandomByteArray(81); try (final Response ignored = resources.getJerseyTest() .target("/v1/profile/") .request() .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO)) - .put(Entity.entity(new CreateProfileRequest(commitment, "validversion", - "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678", + .put(Entity.entity(new CreateProfileRequest(commitment, "validversion", ProfileHelper.encodeToBase64(name), null, null, null, false, true, List.of()), MediaType.APPLICATION_JSON_TYPE))) { @@ -588,25 +593,25 @@ class ProfileControllerTest { verify(profilesManager, times(1)).set(eq(AuthHelper.VALID_UUID_TWO), profileArgumentCaptor.capture()); verify(s3client, times(1)).deleteObject(eq(DeleteObjectRequest.builder().bucket("profilesBucket").key("profiles/validavatar").build())); - assertThat(profileArgumentCaptor.getValue().getCommitment()).isEqualTo(commitment.serialize()); - assertThat(profileArgumentCaptor.getValue().getAvatar()).isNull(); - assertThat(profileArgumentCaptor.getValue().getVersion()).isEqualTo("validversion"); - assertThat(profileArgumentCaptor.getValue().getName()).isEqualTo("123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678"); - assertThat(profileArgumentCaptor.getValue().getAboutEmoji()).isNull(); - assertThat(profileArgumentCaptor.getValue().getAbout()).isNull(); + assertThat(profileArgumentCaptor.getValue().commitment()).isEqualTo(commitment.serialize()); + assertThat(profileArgumentCaptor.getValue().avatar()).isNull(); + assertThat(profileArgumentCaptor.getValue().version()).isEqualTo("validversion"); + assertThat(profileArgumentCaptor.getValue().name()).isEqualTo(name); + assertThat(profileArgumentCaptor.getValue().aboutEmoji()).isNull(); + assertThat(profileArgumentCaptor.getValue().about()).isNull(); } } @Test void testSetProfileWithSameAvatarDespiteNoPreviousAvatar() throws InvalidInputException { final ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID)); + final byte[] name = ProfileHelper.generateRandomByteArray(81); try (final Response response = resources.getJerseyTest() .target("/v1/profile/") .request() .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)) - .put(Entity.entity(new CreateProfileRequest(commitment, "validversion", - "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678", + .put(Entity.entity(new CreateProfileRequest(commitment, "validversion", ProfileHelper.encodeToBase64(name), null, null, null, true, true, List.of()), MediaType.APPLICATION_JSON_TYPE))) { assertThat(response.getStatus()).isEqualTo(200); @@ -618,12 +623,12 @@ class ProfileControllerTest { verify(profilesManager, times(1)).set(eq(AuthHelper.VALID_UUID), profileArgumentCaptor.capture()); verify(s3client, never()).deleteObject(any(DeleteObjectRequest.class)); - assertThat(profileArgumentCaptor.getValue().getCommitment()).isEqualTo(commitment.serialize()); - assertThat(profileArgumentCaptor.getValue().getAvatar()).isNull(); - assertThat(profileArgumentCaptor.getValue().getVersion()).isEqualTo("validversion"); - assertThat(profileArgumentCaptor.getValue().getName()).isEqualTo("123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678"); - assertThat(profileArgumentCaptor.getValue().getAboutEmoji()).isNull(); - assertThat(profileArgumentCaptor.getValue().getAbout()).isNull(); + assertThat(profileArgumentCaptor.getValue().commitment()).isEqualTo(commitment.serialize()); + assertThat(profileArgumentCaptor.getValue().avatar()).isNull(); + assertThat(profileArgumentCaptor.getValue().version()).isEqualTo("validversion"); + assertThat(profileArgumentCaptor.getValue().name()).isEqualTo(name); + assertThat(profileArgumentCaptor.getValue().aboutEmoji()).isNull(); + assertThat(profileArgumentCaptor.getValue().about()).isNull(); } } @@ -631,14 +636,15 @@ class ProfileControllerTest { void testSetProfileExtendedName() throws InvalidInputException { final ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID_TWO)); - final String name = RandomStringUtils.randomAlphabetic(380); + final byte[] name = ProfileHelper.generateRandomByteArray(285); resources.getJerseyTest() .target("/v1/profile/") .request() .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO)) .put(Entity.entity( - new CreateProfileRequest(commitment, "validversion", name, null, null, null, true, false, List.of()), + new CreateProfileRequest(commitment, "validversion", ProfileHelper.encodeToBase64(name), + null, null, null, true, false, List.of()), MediaType.APPLICATION_JSON_TYPE), ProfileAvatarUploadAttributes.class); final ArgumentCaptor profileArgumentCaptor = ArgumentCaptor.forClass(VersionedProfile.class); @@ -647,12 +653,12 @@ class ProfileControllerTest { verify(profilesManager, times(1)).set(eq(AuthHelper.VALID_UUID_TWO), profileArgumentCaptor.capture()); verify(s3client, times(1)).deleteObject(eq(DeleteObjectRequest.builder().bucket("profilesBucket").key("profiles/validavatar").build())); - assertThat(profileArgumentCaptor.getValue().getCommitment()).isEqualTo(commitment.serialize()); - assertThat(profileArgumentCaptor.getValue().getAvatar()).startsWith("profiles/"); - assertThat(profileArgumentCaptor.getValue().getVersion()).isEqualTo("validversion"); - assertThat(profileArgumentCaptor.getValue().getName()).isEqualTo(name); - assertThat(profileArgumentCaptor.getValue().getAboutEmoji()).isNull(); - assertThat(profileArgumentCaptor.getValue().getAbout()).isNull(); + assertThat(profileArgumentCaptor.getValue().commitment()).isEqualTo(commitment.serialize()); + assertThat(profileArgumentCaptor.getValue().avatar()).startsWith("profiles/"); + assertThat(profileArgumentCaptor.getValue().version()).isEqualTo("validversion"); + assertThat(profileArgumentCaptor.getValue().name()).isEqualTo(name); + assertThat(profileArgumentCaptor.getValue().aboutEmoji()).isNull(); + assertThat(profileArgumentCaptor.getValue().about()).isNull(); } @Test @@ -661,16 +667,17 @@ class ProfileControllerTest { clearInvocations(AuthHelper.VALID_ACCOUNT_TWO); - final String name = RandomStringUtils.randomAlphabetic(380); - final String emoji = RandomStringUtils.randomAlphanumeric(80); - final String text = RandomStringUtils.randomAlphanumeric(720); + final byte[] name = ProfileHelper.generateRandomByteArray(81); + final byte[] emoji = ProfileHelper.generateRandomByteArray(60); + final byte[] about = ProfileHelper.generateRandomByteArray(156); try (final Response response = resources.getJerseyTest() .target("/v1/profile/") .request() .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO)) .put(Entity.entity( - new CreateProfileRequest(commitment, "anotherversion", name, emoji, text, null, false, false, List.of()), + new CreateProfileRequest(commitment, "anotherversion", ProfileHelper.encodeToBase64(name), + ProfileHelper.encodeToBase64(emoji), ProfileHelper.encodeToBase64(about), null, false, false, List.of()), MediaType.APPLICATION_JSON_TYPE))) { assertThat(response.getStatus()).isEqualTo(200); @@ -684,13 +691,13 @@ class ProfileControllerTest { verifyNoMoreInteractions(s3client); final VersionedProfile profile = profileArgumentCaptor.getValue(); - assertThat(profile.getCommitment()).isEqualTo(commitment.serialize()); - assertThat(profile.getAvatar()).isNull(); - assertThat(profile.getVersion()).isEqualTo("anotherversion"); - assertThat(profile.getName()).isEqualTo(name); - assertThat(profile.getAboutEmoji()).isEqualTo(emoji); - assertThat(profile.getAbout()).isEqualTo(text); - assertThat(profile.getPaymentAddress()).isNull(); + assertThat(profile.commitment()).isEqualTo(commitment.serialize()); + assertThat(profile.avatar()).isNull(); + assertThat(profile.version()).isEqualTo("anotherversion"); + assertThat(profile.name()).isEqualTo(name); + assertThat(profile.aboutEmoji()).isEqualTo(emoji); + assertThat(profile.about()).isEqualTo(about); + assertThat(profile.paymentAddress()).isNull(); } } @@ -700,15 +707,16 @@ class ProfileControllerTest { clearInvocations(AuthHelper.VALID_ACCOUNT_TWO); - final String name = RandomStringUtils.randomAlphabetic(380); - final String paymentAddress = RandomStringUtils.randomAlphanumeric(776); + final byte[] name = ProfileHelper.generateRandomByteArray(81); + final byte[] paymentAddress = ProfileHelper.generateRandomByteArray(582); try (final Response response = resources.getJerseyTest() .target("/v1/profile") .request() .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO)) .put(Entity.entity( - new CreateProfileRequest(commitment, "yetanotherversion", name, null, null, paymentAddress, false, false, + new CreateProfileRequest(commitment, "yetanotherversion", ProfileHelper.encodeToBase64(name), + null, null, ProfileHelper.encodeToBase64(paymentAddress), false, false, List.of()), MediaType.APPLICATION_JSON_TYPE))) { assertThat(response.getStatus()).isEqualTo(200); @@ -722,13 +730,13 @@ class ProfileControllerTest { verifyNoMoreInteractions(s3client); final VersionedProfile profile = profileArgumentCaptor.getValue(); - assertThat(profile.getCommitment()).isEqualTo(commitment.serialize()); - assertThat(profile.getAvatar()).isNull(); - assertThat(profile.getVersion()).isEqualTo("yetanotherversion"); - assertThat(profile.getName()).isEqualTo(name); - assertThat(profile.getAboutEmoji()).isNull(); - assertThat(profile.getAbout()).isNull(); - assertThat(profile.getPaymentAddress()).isEqualTo(paymentAddress); + assertThat(profile.commitment()).isEqualTo(commitment.serialize()); + assertThat(profile.avatar()).isNull(); + assertThat(profile.version()).isEqualTo("yetanotherversion"); + assertThat(profile.name()).isEqualTo(name); + assertThat(profile.aboutEmoji()).isNull(); + assertThat(profile.about()).isNull(); + assertThat(profile.paymentAddress()).isEqualTo(paymentAddress); } } @@ -741,15 +749,16 @@ class ProfileControllerTest { clearInvocations(AuthHelper.VALID_ACCOUNT_TWO); - final String name = RandomStringUtils.randomAlphabetic(380); - final String paymentAddress = RandomStringUtils.randomAlphanumeric(776); + final String name = ProfileHelper.generateRandomBase64FromByteArray(81); + final String paymentAddress = ProfileHelper.generateRandomBase64FromByteArray(582); try (final Response response = resources.getJerseyTest() .target("/v1/profile") .request() .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO)) .put(Entity.entity( - new CreateProfileRequest(commitment, "yetanotherversion", name, null, null, paymentAddress, false, false, + new CreateProfileRequest(commitment, "yetanotherversion", name, + null, null, paymentAddress, false, false, List.of()), MediaType.APPLICATION_JSON_TYPE))) { assertThat(response.getStatus()).isEqualTo(403); @@ -767,24 +776,25 @@ class ProfileControllerTest { .thenReturn(List.of(AuthHelper.VALID_NUMBER_TWO.substring(0, 3))); final ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID)); + final byte[] name = ProfileHelper.generateRandomByteArray(81); + final byte[] paymentAddress = ProfileHelper.generateRandomByteArray(582); clearInvocations(AuthHelper.VALID_ACCOUNT_TWO); when(profilesManager.get(eq(AuthHelper.VALID_UUID_TWO), any())) .thenReturn(Optional.of( - new VersionedProfile("1", "name", null, null, null, - existingPaymentAddressOnProfile ? RandomStringUtils.randomAlphanumeric(776) : null, + new VersionedProfile("1", name, null, null, null, + existingPaymentAddressOnProfile ? ProfileHelper.generateRandomByteArray(582) : null, commitment.serialize()))); - final String name = RandomStringUtils.randomAlphabetic(380); - final String paymentAddress = RandomStringUtils.randomAlphanumeric(776); try (final Response response = resources.getJerseyTest() .target("/v1/profile") .request() .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO)) .put(Entity.entity( - new CreateProfileRequest(commitment, "yetanotherversion", name, null, null, paymentAddress, false, false, + new CreateProfileRequest(commitment, "yetanotherversion", ProfileHelper.encodeToBase64(name), + null, null, ProfileHelper.encodeToBase64(paymentAddress), false, false, List.of()), MediaType.APPLICATION_JSON_TYPE))) { if (existingPaymentAddressOnProfile) { @@ -799,13 +809,13 @@ class ProfileControllerTest { verifyNoMoreInteractions(s3client); final VersionedProfile profile = profileArgumentCaptor.getValue(); - assertThat(profile.getCommitment()).isEqualTo(commitment.serialize()); - assertThat(profile.getAvatar()).isNull(); - assertThat(profile.getVersion()).isEqualTo("yetanotherversion"); - assertThat(profile.getName()).isEqualTo(name); - assertThat(profile.getAboutEmoji()).isNull(); - assertThat(profile.getAbout()).isNull(); - assertThat(profile.getPaymentAddress()).isEqualTo(paymentAddress); + assertThat(profile.commitment()).isEqualTo(commitment.serialize()); + assertThat(profile.avatar()).isNull(); + assertThat(profile.version()).isEqualTo("yetanotherversion"); + assertThat(profile.name()).isEqualTo(name); + assertThat(profile.aboutEmoji()).isNull(); + assertThat(profile.about()).isNull(); + assertThat(profile.paymentAddress()).isEqualTo(paymentAddress); } else { assertThat(response.getStatus()).isEqualTo(403); assertThat(response.hasEntity()).isFalse(); @@ -817,6 +827,13 @@ class ProfileControllerTest { @Test void testGetProfileByVersion() throws RateLimitExceededException { + final byte[] name = ProfileHelper.generateRandomByteArray(81); + final byte[] emoji = ProfileHelper.generateRandomByteArray(60); + final byte[] about = ProfileHelper.generateRandomByteArray(156); + + when(profilesManager.get(eq(AuthHelper.VALID_UUID_TWO), eq("validversion"))).thenReturn(Optional.of(new VersionedProfile( + "validversion", name, "profiles/validavatar", emoji, about, null, "validcommitmnet".getBytes()))); + final VersionedProfileResponse profile = resources.getJerseyTest() .target("/v1/profile/" + AuthHelper.VALID_UUID_TWO + "/validversion") .request() @@ -824,9 +841,9 @@ class ProfileControllerTest { .get(VersionedProfileResponse.class); assertThat(profile.getBaseProfileResponse().getIdentityKey()).isEqualTo(ACCOUNT_TWO_IDENTITY_KEY); - assertThat(profile.getName()).isEqualTo("validname"); - assertThat(profile.getAbout()).isEqualTo("about"); - assertThat(profile.getAboutEmoji()).isEqualTo("emoji"); + assertThat(profile.getName()).isEqualTo(ProfileHelper.encodeToBase64(name)); + assertThat(profile.getAbout()).isEqualTo(ProfileHelper.encodeToBase64(about)); + assertThat(profile.getAboutEmoji()).isEqualTo(ProfileHelper.encodeToBase64(emoji)); assertThat(profile.getAvatar()).isEqualTo("profiles/validavatar"); assertThat(profile.getBaseProfileResponse().getCapabilities().gv1Migration()).isTrue(); assertThat(profile.getBaseProfileResponse().getUuid()).isEqualTo(new AciServiceIdentifier(AuthHelper.VALID_UUID_TWO)); @@ -845,8 +862,8 @@ class ProfileControllerTest { clearInvocations(AuthHelper.VALID_ACCOUNT_TWO); - final String name = RandomStringUtils.randomAlphabetic(380); - final String paymentAddress = RandomStringUtils.randomAlphanumeric(776); + final String name = ProfileHelper.generateRandomBase64FromByteArray(81); + final String paymentAddress = ProfileHelper.generateRandomBase64FromByteArray(582); try (final Response response = resources.getJerseyTest() .target("/v1/profile") @@ -865,8 +882,9 @@ class ProfileControllerTest { @Test void testGetProfileReturnsNoPaymentAddressIfCurrentVersionMismatch() { + final byte[] paymentAddress = ProfileHelper.generateRandomByteArray(582); when(profilesManager.get(AuthHelper.VALID_UUID_TWO, "validversion")).thenReturn( - Optional.of(new VersionedProfile(null, null, null, null, null, "paymentaddress", null))); + Optional.of(new VersionedProfile(null, null, null, null, null, paymentAddress, null))); { final VersionedProfileResponse profile = resources.getJerseyTest() @@ -875,7 +893,7 @@ class ProfileControllerTest { .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)) .get(VersionedProfileResponse.class); - assertThat(profile.getPaymentAddress()).isEqualTo("paymentaddress"); + assertThat(profile.getPaymentAddress()).isEqualTo(ProfileHelper.encodeToBase64(paymentAddress)); } when(profileAccount.getCurrentProfileVersion()).thenReturn(Optional.of("validversion")); @@ -887,7 +905,7 @@ class ProfileControllerTest { .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)) .get(VersionedProfileResponse.class); - assertThat(profile.getPaymentAddress()).isEqualTo("paymentaddress"); + assertThat(profile.getPaymentAddress()).isEqualTo(ProfileHelper.encodeToBase64(paymentAddress)); } when(profileAccount.getCurrentProfileVersion()).thenReturn(Optional.of("someotherversion")); @@ -934,15 +952,15 @@ class ProfileControllerTest { clearInvocations(AuthHelper.VALID_ACCOUNT_TWO); - final String name = RandomStringUtils.randomAlphabetic(380); - final String emoji = RandomStringUtils.randomAlphanumeric(80); - final String text = RandomStringUtils.randomAlphanumeric(720); + final String name = ProfileHelper.generateRandomBase64FromByteArray(81); + final String emoji = ProfileHelper.generateRandomBase64FromByteArray(60); + final String about = ProfileHelper.generateRandomBase64FromByteArray(156); try (final Response response = resources.getJerseyTest() .target("/v1/profile/") .request() .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO)) - .put(Entity.entity(new CreateProfileRequest(commitment, "anotherversion", name, emoji, text, null, false, false, + .put(Entity.entity(new CreateProfileRequest(commitment, "anotherversion", name, emoji, about, null, false, false, List.of("TEST2")), MediaType.APPLICATION_JSON_TYPE))) { assertThat(response.getStatus()).isEqualTo(200); @@ -965,7 +983,7 @@ class ProfileControllerTest { .target("/v1/profile/") .request() .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO)) - .put(Entity.entity(new CreateProfileRequest(commitment, "anotherversion", name, emoji, text, null, false, false, + .put(Entity.entity(new CreateProfileRequest(commitment, "anotherversion", name, emoji, about, null, false, false, List.of("TEST3", "TEST2")), MediaType.APPLICATION_JSON_TYPE))) { assertThat(response.getStatus()).isEqualTo(200); @@ -991,7 +1009,7 @@ class ProfileControllerTest { .target("/v1/profile/") .request() .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO)) - .put(Entity.entity(new CreateProfileRequest(commitment, "anotherversion", name, emoji, text, null, false, false, + .put(Entity.entity(new CreateProfileRequest(commitment, "anotherversion", name, emoji, about, null, false, false, List.of("TEST2", "TEST3")), MediaType.APPLICATION_JSON_TYPE))) { assertThat(response.getStatus()).isEqualTo(200); @@ -1017,7 +1035,7 @@ class ProfileControllerTest { .target("/v1/profile/") .request() .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO)) - .put(Entity.entity(new CreateProfileRequest(commitment, "anotherversion", name, emoji, text, null, false, false, + .put(Entity.entity(new CreateProfileRequest(commitment, "anotherversion", name, emoji, about, null, false, false, List.of("TEST1")), MediaType.APPLICATION_JSON_TYPE))) { assertThat(response.getStatus()).isEqualTo(200); @@ -1055,7 +1073,7 @@ class ProfileControllerTest { final ProfileKeyCommitment profileKeyCommitment = profileKey.getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID)); final VersionedProfile versionedProfile = mock(VersionedProfile.class); - when(versionedProfile.getCommitment()).thenReturn(profileKeyCommitment.serialize()); + when(versionedProfile.commitment()).thenReturn(profileKeyCommitment.serialize()); final ProfileKeyCredentialRequestContext profileKeyCredentialRequestContext = clientZkProfile.createProfileKeyCredentialRequestContext(new ServiceId.Aci(AuthHelper.VALID_UUID), profileKey); @@ -1112,9 +1130,9 @@ class ProfileControllerTest { clearInvocations(AuthHelper.VALID_ACCOUNT_TWO); - final String name = RandomStringUtils.randomAlphabetic(380); - final String emoji = RandomStringUtils.randomAlphanumeric(80); - final String text = RandomStringUtils.randomAlphanumeric(720); + final String name = ProfileHelper.generateRandomBase64FromByteArray(81); + final String emoji = ProfileHelper.generateRandomBase64FromByteArray(60); + final String text = ProfileHelper.generateRandomBase64FromByteArray(156); when(AuthHelper.VALID_ACCOUNT_TWO.getBadges()).thenReturn(List.of( new AccountBadge("TEST", Instant.ofEpochSecond(42 + 86400), true) diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/grpc/ProfileGrpcServiceTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/grpc/ProfileGrpcServiceTest.java index ac018d788..3174ef794 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/grpc/ProfileGrpcServiceTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/grpc/ProfileGrpcServiceTest.java @@ -39,7 +39,6 @@ import org.whispersystems.textsecuregcm.tests.util.AuthHelper; import software.amazon.awssdk.services.s3.S3AsyncClient; import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; import java.time.Clock; -import java.util.Base64; import java.util.Collections; import java.util.List; import java.util.Map; @@ -136,8 +135,8 @@ public class ProfileGrpcServiceTest { when(account.getNumber()).thenReturn(phoneNumber); when(account.getBadges()).thenReturn(Collections.emptyList()); - when(profile.getPaymentAddress()).thenReturn(null); - when(profile.getAvatar()).thenReturn(""); + when(profile.paymentAddress()).thenReturn(null); + when(profile.avatar()).thenReturn(""); when(accountsManager.getByAccountIdentifierAsync(any())).thenReturn(CompletableFuture.completedFuture(Optional.of(account))); when(accountsManager.updateAsync(any(), any())).thenReturn(CompletableFuture.completedFuture(null)); @@ -178,13 +177,13 @@ public class ProfileGrpcServiceTest { final VersionedProfile profile = profileArgumentCaptor.getValue(); - assertThat(profile.getCommitment()).isEqualTo(commitment); - assertThat(profile.getAvatar()).isNull(); - assertThat(profile.getVersion()).isEqualTo(VERSION); - assertThat(profile.getName()).isEqualTo(encodeToBase64(VALID_NAME)); - assertThat(profile.getAboutEmoji()).isEqualTo(encodeToBase64(validAboutEmoji)); - assertThat(profile.getAbout()).isEqualTo(encodeToBase64(validAbout)); - assertThat(profile.getPaymentAddress()).isEqualTo(encodeToBase64(validPaymentAddress)); + assertThat(profile.commitment()).isEqualTo(commitment); + assertThat(profile.avatar()).isNull(); + assertThat(profile.version()).isEqualTo(VERSION); + assertThat(profile.name()).isEqualTo(VALID_NAME); + assertThat(profile.aboutEmoji()).isEqualTo(validAboutEmoji); + assertThat(profile.about()).isEqualTo(validAbout); + assertThat(profile.paymentAddress()).isEqualTo(validPaymentAddress); } @ParameterizedTest @@ -201,7 +200,7 @@ public class ProfileGrpcServiceTest { .setCommitment(ByteString.copyFrom(commitment)) .build(); - when(profile.getAvatar()).thenReturn(currentAvatar); + when(profile.avatar()).thenReturn(currentAvatar); when(profilesManager.getAsync(any(), anyString())).thenReturn(CompletableFuture.completedFuture( hasPreviousProfile ? Optional.of(profile) : Optional.empty())); @@ -301,7 +300,7 @@ public class ProfileGrpcServiceTest { final byte[] validPaymentAddress = new byte[582]; if (hasExistingPaymentAddress) { - when(profile.getPaymentAddress()).thenReturn(encodeToBase64(validPaymentAddress)); + when(profile.paymentAddress()).thenReturn(validPaymentAddress); } final SetProfileRequest request = SetProfileRequest.newBuilder() @@ -328,7 +327,13 @@ public class ProfileGrpcServiceTest { } } - private static String encodeToBase64(byte[] input) { - return Base64.getEncoder().encodeToString(input); + @Test + void test() { + record Person(String firstName, String lastName) {}; + + Person rob = new Person("rob", "l"); + Person mike = new Person("rob", "l"); + + assertEquals(rob, mike); } } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/ProfilesManagerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/ProfilesManagerTest.java index 628ddac50..5f2c8c424 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/ProfilesManagerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/ProfilesManagerTest.java @@ -5,7 +5,7 @@ package org.whispersystems.textsecuregcm.storage; -import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -21,15 +21,18 @@ import static org.mockito.Mockito.when; import io.lettuce.core.RedisException; import io.lettuce.core.cluster.api.async.RedisAdvancedClusterAsyncCommands; import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands; -import java.util.Base64; import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; +import org.signal.libsignal.protocol.ServiceId; +import org.signal.libsignal.zkgroup.InvalidInputException; +import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster; import org.whispersystems.textsecuregcm.tests.util.MockRedisFuture; +import org.whispersystems.textsecuregcm.tests.util.ProfileHelper; import org.whispersystems.textsecuregcm.tests.util.RedisClusterHelper; @Timeout(value = 10, threadMode = Timeout.ThreadMode.SEPARATE_THREAD) @@ -57,17 +60,21 @@ public class ProfilesManagerTest { } @Test - public void testGetProfileInCache() { - UUID uuid = UUID.randomUUID(); - - when(commands.hget(eq("profiles::" + uuid), eq("someversion"))).thenReturn("{\"version\": \"someversion\", \"name\": \"somename\", \"avatar\": \"someavatar\", \"commitment\":\"" + Base64.getEncoder().encodeToString("somecommitment".getBytes()) + "\"}"); + public void testGetProfileInCache() throws InvalidInputException { + final UUID uuid = UUID.randomUUID(); + final byte[] name = ProfileHelper.generateRandomByteArray(81); + final byte[] commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(uuid)).serialize(); + when(commands.hget(eq("profiles::" + uuid), eq("someversion"))).thenReturn(String.format( + "{\"version\": \"someversion\", \"name\": \"%s\", \"avatar\": \"someavatar\", \"commitment\":\"%s\"}", + ProfileHelper.encodeToBase64(name), + ProfileHelper.encodeToBase64(commitment))); Optional profile = profilesManager.get(uuid, "someversion"); assertTrue(profile.isPresent()); - assertEquals(profile.get().getName(), "somename"); - assertEquals(profile.get().getAvatar(), "someavatar"); - assertThat(profile.get().getCommitment()).isEqualTo("somecommitment".getBytes()); + assertArrayEquals(profile.get().name(), name); + assertEquals(profile.get().avatar(), "someavatar"); + assertArrayEquals(profile.get().commitment(), commitment); verify(commands, times(1)).hget(eq("profiles::" + uuid), eq("someversion")); verifyNoMoreInteractions(commands); @@ -75,18 +82,22 @@ public class ProfilesManagerTest { } @Test - public void testGetProfileAsyncInCache() { - UUID uuid = UUID.randomUUID(); + public void testGetProfileAsyncInCache() throws InvalidInputException { + final UUID uuid = UUID.randomUUID(); + final byte[] name = ProfileHelper.generateRandomByteArray(81); + final byte[] commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(uuid)).serialize(); when(asyncCommands.hget(eq("profiles::" + uuid), eq("someversion"))).thenReturn( - MockRedisFuture.completedFuture("{\"version\": \"someversion\", \"name\": \"somename\", \"avatar\": \"someavatar\", \"commitment\":\"" + Base64.getEncoder().encodeToString("somecommitment".getBytes()) + "\"}")); + MockRedisFuture.completedFuture(String.format("{\"version\": \"someversion\", \"name\": \"%s\", \"avatar\": \"someavatar\", \"commitment\":\"%s\"}", + ProfileHelper.encodeToBase64(name), + ProfileHelper.encodeToBase64(commitment)))); Optional profile = profilesManager.getAsync(uuid, "someversion").join(); assertTrue(profile.isPresent()); - assertEquals(profile.get().getName(), "somename"); - assertEquals(profile.get().getAvatar(), "someavatar"); - assertThat(profile.get().getCommitment()).isEqualTo("somecommitment".getBytes()); + assertArrayEquals(profile.get().name(), name); + assertEquals(profile.get().avatar(), "someavatar"); + assertArrayEquals(profile.get().commitment(), commitment); verify(asyncCommands, times(1)).hget(eq("profiles::" + uuid), eq("someversion")); verifyNoMoreInteractions(asyncCommands); @@ -95,8 +106,9 @@ public class ProfilesManagerTest { @Test public void testGetProfileNotInCache() { - UUID uuid = UUID.randomUUID(); - VersionedProfile profile = new VersionedProfile("someversion", "somename", "someavatar", null, null, + final UUID uuid = UUID.randomUUID(); + final byte[] name = ProfileHelper.generateRandomByteArray(81); + final VersionedProfile profile = new VersionedProfile("someversion", name, "someavatar", null, null, null, "somecommitment".getBytes()); when(commands.hget(eq("profiles::" + uuid), eq("someversion"))).thenReturn(null); @@ -117,8 +129,9 @@ public class ProfilesManagerTest { @Test public void testGetProfileAsyncNotInCache() { - UUID uuid = UUID.randomUUID(); - VersionedProfile profile = new VersionedProfile("someversion", "somename", "someavatar", null, null, + final UUID uuid = UUID.randomUUID(); + final byte[] name = ProfileHelper.generateRandomByteArray(81); + final VersionedProfile profile = new VersionedProfile("someversion", name, "someavatar", null, null, null, "somecommitment".getBytes()); when(asyncCommands.hget(eq("profiles::" + uuid), eq("someversion"))).thenReturn(MockRedisFuture.completedFuture(null)); @@ -140,8 +153,9 @@ public class ProfilesManagerTest { @Test public void testGetProfileBrokenCache() { - UUID uuid = UUID.randomUUID(); - VersionedProfile profile = new VersionedProfile("someversion", "somename", "someavatar", null, null, + final UUID uuid = UUID.randomUUID(); + final byte[] name = ProfileHelper.generateRandomByteArray(81); + final VersionedProfile profile = new VersionedProfile("someversion", name, "someavatar", null, null, null, "somecommitment".getBytes()); when(commands.hget(eq("profiles::" + uuid), eq("someversion"))).thenThrow(new RedisException("Connection lost")); @@ -162,8 +176,9 @@ public class ProfilesManagerTest { @Test public void testGetProfileAsyncBrokenCache() { - UUID uuid = UUID.randomUUID(); - VersionedProfile profile = new VersionedProfile("someversion", "somename", "someavatar", null, null, + final UUID uuid = UUID.randomUUID(); + final byte[] name = ProfileHelper.generateRandomByteArray(81); + final VersionedProfile profile = new VersionedProfile("someversion", name, "someavatar", null, null, null, "somecommitment".getBytes()); when(asyncCommands.hget(eq("profiles::" + uuid), eq("someversion"))).thenReturn(MockRedisFuture.failedFuture(new RedisException("Connection lost"))); @@ -185,8 +200,9 @@ public class ProfilesManagerTest { @Test public void testSet() { - UUID uuid = UUID.randomUUID(); - VersionedProfile profile = new VersionedProfile("someversion", "somename", "someavatar", null, null, + final UUID uuid = UUID.randomUUID(); + final byte[] name = ProfileHelper.generateRandomByteArray(81); + final VersionedProfile profile = new VersionedProfile("someversion", name, "someavatar", null, null, null, "somecommitment".getBytes()); profilesManager.set(uuid, profile); @@ -200,8 +216,9 @@ public class ProfilesManagerTest { @Test public void testSetAsync() { - UUID uuid = UUID.randomUUID(); - VersionedProfile profile = new VersionedProfile("someversion", "somename", "someavatar", null, null, + final UUID uuid = UUID.randomUUID(); + final byte[] name = ProfileHelper.generateRandomByteArray(81); + final VersionedProfile profile = new VersionedProfile("someversion", name, "someavatar", null, null, null, "somecommitment".getBytes()); when(asyncCommands.hset(eq("profiles::" + uuid), eq("someversion"), anyString())).thenReturn(MockRedisFuture.completedFuture(null)); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/ProfilesTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/ProfilesTest.java index 69f96fd54..94c6e2160 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/ProfilesTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/ProfilesTest.java @@ -15,6 +15,7 @@ import org.junit.jupiter.params.provider.MethodSource; import org.signal.libsignal.protocol.ServiceId; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.profiles.ProfileKey; +import org.whispersystems.textsecuregcm.tests.util.ProfileHelper; import org.whispersystems.textsecuregcm.util.AttributeValues; import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; @@ -22,10 +23,8 @@ import software.amazon.awssdk.services.dynamodb.model.AttributeValue; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -import java.util.Base64; import java.util.Map; import java.util.Optional; -import java.util.Random; import java.util.UUID; import java.util.stream.Stream; @@ -45,10 +44,10 @@ public class ProfilesTest { Tables.PROFILES.tableName()); final byte[] commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(ACI)).serialize(); final String version = "someVersion"; - final String name = generateRandomBase64FromByteArray(81); - final String validAboutEmoji = generateRandomBase64FromByteArray(60); - final String validAbout = generateRandomBase64FromByteArray(156); - final String avatar = "profiles/" + generateRandomBase64FromByteArray(16); + final byte[] name = ProfileHelper.generateRandomByteArray(81); + final byte[] validAboutEmoji = ProfileHelper.generateRandomByteArray(60); + final byte[] validAbout = ProfileHelper.generateRandomByteArray(156); + final String avatar = "profiles/" + ProfileHelper.generateRandomBase64FromByteArray(16); validProfile = new VersionedProfile(version, name, avatar, validAboutEmoji, validAbout, null, commitment); } @@ -57,28 +56,28 @@ public class ProfilesTest { void testSetGet() { profiles.set(ACI, validProfile); - Optional retrieved = profiles.get(ACI, validProfile.getVersion()); + Optional retrieved = profiles.get(ACI, validProfile.version()); assertThat(retrieved.isPresent()).isTrue(); - assertThat(retrieved.get().getName()).isEqualTo(validProfile.getName()); - assertThat(retrieved.get().getAvatar()).isEqualTo(validProfile.getAvatar()); - assertThat(retrieved.get().getCommitment()).isEqualTo(validProfile.getCommitment()); - assertThat(retrieved.get().getAbout()).isEqualTo(validProfile.getAbout()); - assertThat(retrieved.get().getAboutEmoji()).isEqualTo(validProfile.getAboutEmoji()); + assertThat(retrieved.get().name()).isEqualTo(validProfile.name()); + assertThat(retrieved.get().avatar()).isEqualTo(validProfile.avatar()); + assertThat(retrieved.get().commitment()).isEqualTo(validProfile.commitment()); + assertThat(retrieved.get().about()).isEqualTo(validProfile.about()); + assertThat(retrieved.get().aboutEmoji()).isEqualTo(validProfile.aboutEmoji()); } @Test void testSetGetAsync() { profiles.setAsync(ACI, validProfile).join(); - Optional retrieved = profiles.getAsync(ACI, validProfile.getVersion()).join(); + Optional retrieved = profiles.getAsync(ACI, validProfile.version()).join(); assertThat(retrieved.isPresent()).isTrue(); - assertThat(retrieved.get().getName()).isEqualTo(validProfile.getName()); - assertThat(retrieved.get().getAvatar()).isEqualTo(validProfile.getAvatar()); - assertThat(retrieved.get().getCommitment()).isEqualTo(validProfile.getCommitment()); - assertThat(retrieved.get().getAbout()).isEqualTo(validProfile.getAbout()); - assertThat(retrieved.get().getAboutEmoji()).isEqualTo(validProfile.getAboutEmoji()); + assertThat(retrieved.get().name()).isEqualTo(validProfile.name()); + assertThat(retrieved.get().avatar()).isEqualTo(validProfile.avatar()); + assertThat(retrieved.get().commitment()).isEqualTo(validProfile.commitment()); + assertThat(retrieved.get().about()).isEqualTo(validProfile.about()); + assertThat(retrieved.get().aboutEmoji()).isEqualTo(validProfile.aboutEmoji()); } @Test @@ -88,12 +87,12 @@ public class ProfilesTest { profiles.deleteAll(ACI); final String version = "someVersion"; - final String name = generateRandomBase64FromByteArray(81); - final String differentAvatar = "profiles/" + generateRandomBase64FromByteArray(16); - final String differentEmoji = generateRandomBase64FromByteArray(60); - final String differentAbout = generateRandomBase64FromByteArray(156); - final String paymentAddress = generateRandomBase64FromByteArray(582); - final byte[] commitment = new ProfileKey(generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize(); + final byte[] name = ProfileHelper.generateRandomByteArray(81); + final String differentAvatar = "profiles/" + ProfileHelper.generateRandomBase64FromByteArray(16); + final byte[] differentEmoji = ProfileHelper.generateRandomByteArray(60); + final byte[] differentAbout = ProfileHelper.generateRandomByteArray(156); + final byte[] paymentAddress = ProfileHelper.generateRandomByteArray(582); + final byte[] commitment = new ProfileKey(ProfileHelper.generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize(); VersionedProfile updatedProfile = new VersionedProfile(version, name, differentAvatar, differentEmoji, differentAbout, paymentAddress, commitment); @@ -103,18 +102,18 @@ public class ProfilesTest { Optional retrieved = profiles.get(ACI, version); assertThat(retrieved.isPresent()).isTrue(); - assertThat(retrieved.get().getName()).isEqualTo(updatedProfile.getName()); - assertThat(retrieved.get().getAvatar()).isEqualTo(updatedProfile.getAvatar()); - assertThat(retrieved.get().getCommitment()).isEqualTo(updatedProfile.getCommitment()); - assertThat(retrieved.get().getAbout()).isEqualTo(updatedProfile.getAbout()); - assertThat(retrieved.get().getAboutEmoji()).isEqualTo(updatedProfile.getAboutEmoji()); + assertThat(retrieved.get().name()).isEqualTo(updatedProfile.name()); + assertThat(retrieved.get().avatar()).isEqualTo(updatedProfile.avatar()); + assertThat(retrieved.get().commitment()).isEqualTo(updatedProfile.commitment()); + assertThat(retrieved.get().about()).isEqualTo(updatedProfile.about()); + assertThat(retrieved.get().aboutEmoji()).isEqualTo(updatedProfile.aboutEmoji()); } @Test void testSetGetNullOptionalFields() throws InvalidInputException { final String version = "someVersion"; - final String name = generateRandomBase64FromByteArray(81); - final byte[] commitment = new ProfileKey(generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize(); + final byte[] name = ProfileHelper.generateRandomByteArray(81); + final byte[] commitment = new ProfileKey(ProfileHelper.generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize(); VersionedProfile profile = new VersionedProfile(version, name, null, null, null, null, commitment); @@ -123,47 +122,47 @@ public class ProfilesTest { Optional retrieved = profiles.get(ACI, version); assertThat(retrieved.isPresent()).isTrue(); - assertThat(retrieved.get().getName()).isEqualTo(profile.getName()); - assertThat(retrieved.get().getAvatar()).isEqualTo(profile.getAvatar()); - assertThat(retrieved.get().getCommitment()).isEqualTo(profile.getCommitment()); - assertThat(retrieved.get().getAbout()).isEqualTo(profile.getAbout()); - assertThat(retrieved.get().getAboutEmoji()).isEqualTo(profile.getAboutEmoji()); + assertThat(retrieved.get().name()).isEqualTo(profile.name()); + assertThat(retrieved.get().avatar()).isEqualTo(profile.avatar()); + assertThat(retrieved.get().commitment()).isEqualTo(profile.commitment()); + assertThat(retrieved.get().about()).isEqualTo(profile.about()); + assertThat(retrieved.get().aboutEmoji()).isEqualTo(profile.aboutEmoji()); } @Test void testSetReplace() throws InvalidInputException { profiles.set(ACI, validProfile); - Optional retrieved = profiles.get(ACI, validProfile.getVersion()); + Optional retrieved = profiles.get(ACI, validProfile.version()); assertThat(retrieved.isPresent()).isTrue(); - assertThat(retrieved.get().getName()).isEqualTo(validProfile.getName()); - assertThat(retrieved.get().getAvatar()).isEqualTo(validProfile.getAvatar()); - assertThat(retrieved.get().getCommitment()).isEqualTo(validProfile.getCommitment()); - assertThat(retrieved.get().getAbout()).isEqualTo(validProfile.getAbout()); - assertThat(retrieved.get().getAboutEmoji()).isEqualTo(validProfile.getAboutEmoji()); - assertThat(retrieved.get().getPaymentAddress()).isNull(); + assertThat(retrieved.get().name()).isEqualTo(validProfile.name()); + assertThat(retrieved.get().avatar()).isEqualTo(validProfile.avatar()); + assertThat(retrieved.get().commitment()).isEqualTo(validProfile.commitment()); + assertThat(retrieved.get().about()).isEqualTo(validProfile.about()); + assertThat(retrieved.get().aboutEmoji()).isEqualTo(validProfile.aboutEmoji()); + assertThat(retrieved.get().paymentAddress()).isNull(); - final String differentName = generateRandomBase64FromByteArray(81); - final String differentEmoji = generateRandomBase64FromByteArray(60); - final String differentAbout = generateRandomBase64FromByteArray(156); - final String differentAvatar = "profiles/" + generateRandomBase64FromByteArray(16); - final byte[] differentCommitment = new ProfileKey(generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize(); + final byte[] differentName = ProfileHelper.generateRandomByteArray(81); + final byte[] differentEmoji = ProfileHelper.generateRandomByteArray(60); + final byte[] differentAbout = ProfileHelper.generateRandomByteArray(156); + final String differentAvatar = "profiles/" + ProfileHelper.generateRandomBase64FromByteArray(16); + final byte[] differentCommitment = new ProfileKey(ProfileHelper.generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize(); - VersionedProfile updated = new VersionedProfile(validProfile.getVersion(), differentName, differentAvatar, differentEmoji, differentAbout, null, + VersionedProfile updated = new VersionedProfile(validProfile.version(), differentName, differentAvatar, differentEmoji, differentAbout, null, differentCommitment); profiles.set(ACI, updated); - retrieved = profiles.get(ACI, updated.getVersion()); + retrieved = profiles.get(ACI, updated.version()); assertThat(retrieved.isPresent()).isTrue(); - assertThat(retrieved.get().getName()).isEqualTo(updated.getName()); - assertThat(retrieved.get().getAbout()).isEqualTo(updated.getAbout()); - assertThat(retrieved.get().getAboutEmoji()).isEqualTo(updated.getAboutEmoji()); - assertThat(retrieved.get().getAvatar()).isEqualTo(updated.getAvatar()); + assertThat(retrieved.get().name()).isEqualTo(updated.name()); + assertThat(retrieved.get().about()).isEqualTo(updated.about()); + assertThat(retrieved.get().aboutEmoji()).isEqualTo(updated.aboutEmoji()); + assertThat(retrieved.get().avatar()).isEqualTo(updated.avatar()); // Commitment should be unchanged after an overwrite - assertThat(retrieved.get().getCommitment()).isEqualTo(validProfile.getCommitment()); + assertThat(retrieved.get().commitment()).isEqualTo(validProfile.commitment()); } @Test @@ -171,17 +170,17 @@ public class ProfilesTest { final String versionOne = "versionOne"; final String versionTwo = "versionTwo"; - final String nameOne = generateRandomBase64FromByteArray(81); - final String nameTwo = generateRandomBase64FromByteArray(81); + final byte[] nameOne = ProfileHelper.generateRandomByteArray(81); + final byte[] nameTwo = ProfileHelper.generateRandomByteArray(81); - final String avatarOne = "profiles/" + generateRandomBase64FromByteArray(16); - final String avatarTwo = "profiles/" + generateRandomBase64FromByteArray(16); + final String avatarOne = "profiles/" + ProfileHelper.generateRandomBase64FromByteArray(16); + final String avatarTwo = "profiles/" + ProfileHelper.generateRandomBase64FromByteArray(16); - final String aboutEmoji = generateRandomBase64FromByteArray(60); - final String about = generateRandomBase64FromByteArray(156); + final byte[] aboutEmoji = ProfileHelper.generateRandomByteArray(60); + final byte[] about = ProfileHelper.generateRandomByteArray(156); - final byte[] commitmentOne = new ProfileKey(generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize(); - final byte[] commitmentTwo = new ProfileKey(generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize(); + final byte[] commitmentOne = new ProfileKey(ProfileHelper.generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize(); + final byte[] commitmentTwo = new ProfileKey(ProfileHelper.generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize(); VersionedProfile profileOne = new VersionedProfile(versionOne, nameOne, avatarOne, null, null, null, commitmentOne); @@ -193,20 +192,20 @@ public class ProfilesTest { Optional retrieved = profiles.get(ACI, versionOne); assertThat(retrieved.isPresent()).isTrue(); - assertThat(retrieved.get().getName()).isEqualTo(profileOne.getName()); - assertThat(retrieved.get().getAvatar()).isEqualTo(profileOne.getAvatar()); - assertThat(retrieved.get().getCommitment()).isEqualTo(profileOne.getCommitment()); - assertThat(retrieved.get().getAbout()).isEqualTo(profileOne.getAbout()); - assertThat(retrieved.get().getAboutEmoji()).isEqualTo(profileOne.getAboutEmoji()); + assertThat(retrieved.get().name()).isEqualTo(profileOne.name()); + assertThat(retrieved.get().avatar()).isEqualTo(profileOne.avatar()); + assertThat(retrieved.get().commitment()).isEqualTo(profileOne.commitment()); + assertThat(retrieved.get().about()).isEqualTo(profileOne.about()); + assertThat(retrieved.get().aboutEmoji()).isEqualTo(profileOne.aboutEmoji()); retrieved = profiles.get(ACI, versionTwo); assertThat(retrieved.isPresent()).isTrue(); - assertThat(retrieved.get().getName()).isEqualTo(profileTwo.getName()); - assertThat(retrieved.get().getAvatar()).isEqualTo(profileTwo.getAvatar()); - assertThat(retrieved.get().getCommitment()).isEqualTo(profileTwo.getCommitment()); - assertThat(retrieved.get().getAbout()).isEqualTo(profileTwo.getAbout()); - assertThat(retrieved.get().getAboutEmoji()).isEqualTo(profileTwo.getAboutEmoji()); + assertThat(retrieved.get().name()).isEqualTo(profileTwo.name()); + assertThat(retrieved.get().avatar()).isEqualTo(profileTwo.avatar()); + assertThat(retrieved.get().commitment()).isEqualTo(profileTwo.commitment()); + assertThat(retrieved.get().about()).isEqualTo(profileTwo.about()); + assertThat(retrieved.get().aboutEmoji()).isEqualTo(profileTwo.aboutEmoji()); } @Test @@ -224,17 +223,17 @@ public class ProfilesTest { final String versionOne = "versionOne"; final String versionTwo = "versionTwo"; - final String nameOne = generateRandomBase64FromByteArray(81); - final String nameTwo = generateRandomBase64FromByteArray(81); + final byte[] nameOne = ProfileHelper.generateRandomByteArray(81); + final byte[] nameTwo = ProfileHelper.generateRandomByteArray(81); - final String aboutEmoji = generateRandomBase64FromByteArray(60); - final String about = generateRandomBase64FromByteArray(156); + final byte[] aboutEmoji = ProfileHelper.generateRandomByteArray(60); + final byte[] about = ProfileHelper.generateRandomByteArray(156); - final String avatarOne = "profiles/" + generateRandomBase64FromByteArray(16); - final String avatarTwo = "profiles/" + generateRandomBase64FromByteArray(16); + final String avatarOne = "profiles/" + ProfileHelper.generateRandomBase64FromByteArray(16); + final String avatarTwo = "profiles/" + ProfileHelper.generateRandomBase64FromByteArray(16); - final byte[] commitmentOne = new ProfileKey(generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize(); - final byte[] commitmentTwo = new ProfileKey(generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize(); + final byte[] commitmentOne = new ProfileKey(ProfileHelper.generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize(); + final byte[] commitmentTwo = new ProfileKey(ProfileHelper.generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize(); VersionedProfile profileOne = new VersionedProfile(versionOne, nameOne, avatarOne, null, null, null, commitmentOne); @@ -262,12 +261,12 @@ public class ProfilesTest { private static Stream buildUpdateExpression() throws InvalidInputException { final String version = "someVersion"; - final String name = generateRandomBase64FromByteArray(81); - final String avatar = "profiles/" + generateRandomBase64FromByteArray(16);; - final String emoji = generateRandomBase64FromByteArray(60); - final String about = generateRandomBase64FromByteArray(156); - final String paymentAddress = generateRandomBase64FromByteArray(582); - final byte[] commitment = new ProfileKey(generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize(); + final byte[] name = ProfileHelper.generateRandomByteArray(81); + final String avatar = "profiles/" + ProfileHelper.generateRandomBase64FromByteArray(16);; + final byte[] emoji = ProfileHelper.generateRandomByteArray(60); + final byte[] about = ProfileHelper.generateRandomByteArray(156); + final byte[] paymentAddress = ProfileHelper.generateRandomByteArray(582); + final byte[] commitment = new ProfileKey(ProfileHelper.generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize(); return Stream.of( Arguments.of( @@ -304,67 +303,57 @@ public class ProfilesTest { private static Stream buildUpdateExpressionAttributeValues() throws InvalidInputException { final String version = "someVersion"; - final String name = generateRandomBase64FromByteArray(81); - final String avatar = "profiles/" + generateRandomBase64FromByteArray(16);; - final String emoji = generateRandomBase64FromByteArray(60); - final String about = generateRandomBase64FromByteArray(156); - final String paymentAddress = generateRandomBase64FromByteArray(582); - final byte[] commitment = new ProfileKey(generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize(); + final byte[] name = ProfileHelper.generateRandomByteArray(81); + final String avatar = "profiles/" + ProfileHelper.generateRandomBase64FromByteArray(16);; + final byte[] emoji = ProfileHelper.generateRandomByteArray(60); + final byte[] about = ProfileHelper.generateRandomByteArray(156); + final byte[] paymentAddress = ProfileHelper.generateRandomByteArray(582); + final byte[] commitment = new ProfileKey(ProfileHelper.generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize(); return Stream.of( Arguments.of( new VersionedProfile(version, name, avatar, emoji, about, paymentAddress, commitment), Map.of( ":commitment", AttributeValues.fromByteArray(commitment), - ":name", AttributeValues.fromString(name), + ":name", AttributeValues.fromByteArray(name), ":avatar", AttributeValues.fromString(avatar), - ":aboutEmoji", AttributeValues.fromString(emoji), - ":about", AttributeValues.fromString(about), - ":paymentAddress", AttributeValues.fromString(paymentAddress))), + ":aboutEmoji", AttributeValues.fromByteArray(emoji), + ":about", AttributeValues.fromByteArray(about), + ":paymentAddress", AttributeValues.fromByteArray(paymentAddress))), Arguments.of( new VersionedProfile(version, name, avatar, emoji, about, null, commitment), Map.of( ":commitment", AttributeValues.fromByteArray(commitment), - ":name", AttributeValues.fromString(name), + ":name", AttributeValues.fromByteArray(name), ":avatar", AttributeValues.fromString(avatar), - ":aboutEmoji", AttributeValues.fromString(emoji), - ":about", AttributeValues.fromString(about))), + ":aboutEmoji", AttributeValues.fromByteArray(emoji), + ":about", AttributeValues.fromByteArray(about))), Arguments.of( new VersionedProfile(version, name, avatar, emoji, null, null, commitment), Map.of( ":commitment", AttributeValues.fromByteArray(commitment), - ":name", AttributeValues.fromString(name), + ":name",AttributeValues.fromByteArray(name), ":avatar", AttributeValues.fromString(avatar), - ":aboutEmoji", AttributeValues.fromString(emoji))), + ":aboutEmoji", AttributeValues.fromByteArray(emoji))), Arguments.of( new VersionedProfile(version, name, avatar, null, null, null, commitment), Map.of( ":commitment", AttributeValues.fromByteArray(commitment), - ":name", AttributeValues.fromString(name), + ":name", AttributeValues.fromByteArray(name), ":avatar", AttributeValues.fromString(avatar))), Arguments.of( new VersionedProfile(version, name, null, null, null, null, commitment), Map.of( ":commitment", AttributeValues.fromByteArray(commitment), - ":name", AttributeValues.fromString(name))), + ":name", AttributeValues.fromByteArray(name))), Arguments.of( new VersionedProfile(version, null, null, null, null, null, commitment), Map.of(":commitment", AttributeValues.fromByteArray(commitment))) ); } - - private static String generateRandomBase64FromByteArray(final int byteArrayLength) { - return Base64.getEncoder().encodeToString(generateRandomByteArray(byteArrayLength)); - } - - private static byte[] generateRandomByteArray(final int length) { - byte[] byteArray = new byte[length]; - new Random().nextBytes(byteArray); - return byteArray; - } } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/ProfileHelper.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/ProfileHelper.java new file mode 100644 index 000000000..e8b721997 --- /dev/null +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/ProfileHelper.java @@ -0,0 +1,20 @@ +package org.whispersystems.textsecuregcm.tests.util; + +import java.util.Base64; +import java.util.Random; + +public class ProfileHelper { + public static String generateRandomBase64FromByteArray(final int byteArrayLength) { + return encodeToBase64(generateRandomByteArray(byteArrayLength)); + } + + public static byte[] generateRandomByteArray(final int length) { + byte[] byteArray = new byte[length]; + new Random().nextBytes(byteArray); + return byteArray; + } + + public static String encodeToBase64(final byte[] input) { + return Base64.getEncoder().encodeToString(input); + } +}