Add a base64url gRPC validator and apply it to backup copy
This commit is contained in:
parent
5bb7edcade
commit
c4a48dd1e6
@ -17,6 +17,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.grpc.validators.Base64UrlFieldValidator;
|
||||
import org.whispersystems.textsecuregcm.grpc.validators.E164FieldValidator;
|
||||
import org.whispersystems.textsecuregcm.grpc.validators.EnumSpecifiedFieldValidator;
|
||||
import org.whispersystems.textsecuregcm.grpc.validators.ExactlySizeFieldValidator;
|
||||
@ -37,7 +38,8 @@ public class ValidatingInterceptor implements ServerInterceptor {
|
||||
"org.signal.chat.require.e164", new E164FieldValidator(),
|
||||
"org.signal.chat.require.exactlySize", new ExactlySizeFieldValidator(),
|
||||
"org.signal.chat.require.range", new RangeFieldValidator(),
|
||||
"org.signal.chat.require.size", new SizeFieldValidator()
|
||||
"org.signal.chat.require.size", new SizeFieldValidator(),
|
||||
"org.signal.chat.require.base64url", new Base64UrlFieldValidator()
|
||||
);
|
||||
|
||||
@Override
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.grpc.validators;
|
||||
|
||||
import com.google.protobuf.Descriptors;
|
||||
import org.whispersystems.textsecuregcm.util.ImpossiblePhoneNumberException;
|
||||
import org.whispersystems.textsecuregcm.util.NonNormalizedPhoneNumberException;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/// Validate that a string field is a valid base64 url string (padded or unpadded)
|
||||
public class Base64UrlFieldValidator extends BaseFieldValidator<Boolean> {
|
||||
|
||||
public Base64UrlFieldValidator() {
|
||||
super("base64url", Set.of(Descriptors.FieldDescriptor.Type.STRING), MissingOptionalAction.SUCCEED, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean resolveExtensionValue(final Object extensionValue) throws FieldValidationException {
|
||||
return requireFlagExtension(extensionValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void validateStringValue(
|
||||
final Boolean extensionValue,
|
||||
final String fieldValue) throws FieldValidationException {
|
||||
try {
|
||||
Base64.getUrlDecoder().decode(fieldValue);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new FieldValidationException("value is not valid base64 url");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -390,7 +390,7 @@ message CopyMediaItem {
|
||||
uint32 source_attachment_cdn = 1 [(require.range).min = 1, (require.range).max = 3];
|
||||
|
||||
// The attachment key of the object to copy into the backup
|
||||
string source_key = 2 [(require.nonEmpty) = true];
|
||||
string source_key = 2 [(require.nonEmpty) = true, (require.base64url) = true];
|
||||
|
||||
// The length of the source attachment before the encryption applied by the
|
||||
// copy operation
|
||||
|
||||
@ -147,6 +147,21 @@ extend google.protobuf.FieldOptions {
|
||||
*````
|
||||
*/
|
||||
optional bool present = 70007;
|
||||
|
||||
/*
|
||||
* Requires a value of a string field to be a valid base64 URL string. The
|
||||
* string may be padded or unpadded. If the field is `optional`, this check
|
||||
* allows a value to be not set.
|
||||
*
|
||||
* ```
|
||||
* import "org/signal/chat/require.proto";
|
||||
*
|
||||
* message Data {
|
||||
* string myString = 1 [(require.base64url)];
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
optional bool base64url = 70008;
|
||||
}
|
||||
|
||||
message SizeConstraint {
|
||||
|
||||
@ -103,6 +103,25 @@ public class ValidatingInterceptorTest {
|
||||
));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"1====", "zzz?", "123/", "123+"})
|
||||
public void testBase64UrlValidationFailure(final String invalidBase64Url) {
|
||||
assertStatusException(Status.INVALID_ARGUMENT, () -> stub.validationsEndpoint(
|
||||
builderWithValidDefaults()
|
||||
.setBase64Url(invalidBase64Url)
|
||||
.build()
|
||||
));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"abc=", "ab==", "aa", "ab", "abc", "A_b-Cd"})
|
||||
public void testBase64UrlPermittedValues(final String validBase64Url) {
|
||||
assertDoesNotThrow(() -> stub.validationsEndpoint(
|
||||
builderWithValidDefaults()
|
||||
.setBase64Url(validBase64Url)
|
||||
.build()));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = {0, 1, 2, 3, 4, 6, 1000})
|
||||
public void testExactlySizeValidationFailure(final int size) throws Exception {
|
||||
@ -450,7 +469,8 @@ public class ValidatingInterceptorTest {
|
||||
.setI32Range(15)
|
||||
.setNested(NestedMessage.getDefaultInstance())
|
||||
.addRepeatedNested(NestedMessage.getDefaultInstance())
|
||||
.putMapNested("test", NestedMessage.getDefaultInstance());
|
||||
.putMapNested("test", NestedMessage.getDefaultInstance())
|
||||
.setBase64Url("123aBc_-");
|
||||
}
|
||||
|
||||
private static void assertStatusException(final Status expected, final Executable serviceCall) {
|
||||
|
||||
@ -77,6 +77,8 @@ message ValidationsRequest {
|
||||
RequirePresentMessage oneOfMessage = 31 [(require.present) = true];
|
||||
bytes oneOfNonEmptyBytes = 32 [(require.nonEmpty) = true];
|
||||
}
|
||||
|
||||
optional string base64url = 33 [(require.base64url) = true];
|
||||
}
|
||||
|
||||
message NestedMessage {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user