mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-23 03:58:03 +01:00
Add /v1/registration
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
public enum RegistrationLockError {
|
||||
MISMATCH(RegistrationLockVerificationManager.FAILURE_HTTP_STATUS),
|
||||
RATE_LIMITED(413) // This will be changed to 429 in a future revision
|
||||
;
|
||||
|
||||
private final int expectedStatus;
|
||||
|
||||
RegistrationLockError(final int expectedStatus) {
|
||||
this.expectedStatus = expectedStatus;
|
||||
}
|
||||
|
||||
public int getExpectedStatus() {
|
||||
return expectedStatus;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiter;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.util.Pair;
|
||||
|
||||
class RegistrationLockVerificationManagerTest {
|
||||
|
||||
private final AccountsManager accountsManager = mock(AccountsManager.class);
|
||||
private final ClientPresenceManager clientPresenceManager = mock(ClientPresenceManager.class);
|
||||
private final ExternalServiceCredentialsGenerator backupServiceCredentialsGeneraor = mock(
|
||||
ExternalServiceCredentialsGenerator.class);
|
||||
private final RateLimiters rateLimiters = mock(RateLimiters.class);
|
||||
private final RegistrationLockVerificationManager registrationLockVerificationManager = new RegistrationLockVerificationManager(
|
||||
accountsManager, clientPresenceManager, backupServiceCredentialsGeneraor, rateLimiters);
|
||||
|
||||
private final RateLimiter pinLimiter = mock(RateLimiter.class);
|
||||
|
||||
private Account account;
|
||||
private StoredRegistrationLock existingRegistrationLock;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
when(rateLimiters.getPinLimiter()).thenReturn(pinLimiter);
|
||||
when(backupServiceCredentialsGeneraor.generateForUuid(any()))
|
||||
.thenReturn(mock(ExternalServiceCredentials.class));
|
||||
|
||||
account = mock(Account.class);
|
||||
when(account.getUuid()).thenReturn(UUID.randomUUID());
|
||||
when(account.getNumber()).thenReturn("+18005551212");
|
||||
existingRegistrationLock = mock(StoredRegistrationLock.class);
|
||||
when(account.getRegistrationLock()).thenReturn(existingRegistrationLock);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource
|
||||
void testErrors(RegistrationLockError error) throws Exception {
|
||||
|
||||
when(existingRegistrationLock.requiresClientRegistrationLock()).thenReturn(true);
|
||||
|
||||
final String submittedRegistrationLock = "reglock";
|
||||
|
||||
final Pair<Class<? extends Exception>, Consumer<Exception>> exceptionType = switch (error) {
|
||||
case MISMATCH -> {
|
||||
when(existingRegistrationLock.verify(submittedRegistrationLock)).thenReturn(false);
|
||||
yield new Pair<>(WebApplicationException.class, e -> {
|
||||
if (e instanceof WebApplicationException wae) {
|
||||
assertEquals(RegistrationLockVerificationManager.FAILURE_HTTP_STATUS, wae.getResponse().getStatus());
|
||||
} else {
|
||||
fail("Exception was not of expected type");
|
||||
}
|
||||
});
|
||||
}
|
||||
case RATE_LIMITED -> {
|
||||
when(existingRegistrationLock.verify(any())).thenReturn(true);
|
||||
doThrow(RateLimitExceededException.class).when(pinLimiter).validate(anyString());
|
||||
yield new Pair<>(RateLimitExceededException.class, ignored -> {
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
final Exception e = assertThrows(exceptionType.first(), () ->
|
||||
registrationLockVerificationManager.verifyRegistrationLock(account, submittedRegistrationLock));
|
||||
|
||||
exceptionType.second().accept(e);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
void testSuccess(final boolean requiresClientRegistrationLock, @Nullable final String submittedRegistrationLock) {
|
||||
|
||||
when(existingRegistrationLock.requiresClientRegistrationLock())
|
||||
.thenReturn(requiresClientRegistrationLock);
|
||||
when(existingRegistrationLock.verify(submittedRegistrationLock)).thenReturn(true);
|
||||
|
||||
assertDoesNotThrow(
|
||||
() -> registrationLockVerificationManager.verifyRegistrationLock(account, submittedRegistrationLock));
|
||||
}
|
||||
|
||||
static Stream<Arguments> testSuccess() {
|
||||
return Stream.of(
|
||||
Arguments.of(false, null),
|
||||
Arguments.of(true, null),
|
||||
Arguments.of(false, "reglock"),
|
||||
Arguments.of(true, "reglock")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -67,13 +67,14 @@ import org.mockito.stubbing.Answer;
|
||||
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
|
||||
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount;
|
||||
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
|
||||
import org.whispersystems.textsecuregcm.auth.RegistrationLockVerificationManager;
|
||||
import org.whispersystems.textsecuregcm.auth.SaltedTokenHash;
|
||||
import org.whispersystems.textsecuregcm.auth.StoredRegistrationLock;
|
||||
import org.whispersystems.textsecuregcm.auth.StoredVerificationCode;
|
||||
import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator;
|
||||
import org.whispersystems.textsecuregcm.captcha.AssessmentResult;
|
||||
import org.whispersystems.textsecuregcm.captcha.CaptchaChecker;
|
||||
import org.whispersystems.textsecuregcm.configuration.SecureStorageServiceConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.SecureBackupServiceConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicCaptchaConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
||||
import org.whispersystems.textsecuregcm.entities.AccountAttributes;
|
||||
@@ -184,12 +185,15 @@ class AccountControllerTest {
|
||||
|
||||
private byte[] registration_lock_key = new byte[32];
|
||||
|
||||
private static final SecureStorageServiceConfiguration STORAGE_CFG = MockUtils.buildMock(
|
||||
SecureStorageServiceConfiguration.class,
|
||||
cfg -> when(cfg.decodeUserAuthenticationTokenSharedSecret()).thenReturn(new byte[32]));
|
||||
private static final SecureBackupServiceConfiguration BACKUP_CFG = MockUtils.buildMock(
|
||||
SecureBackupServiceConfiguration.class,
|
||||
cfg -> when(cfg.getUserAuthenticationTokenSharedSecret()).thenReturn(new byte[32]));
|
||||
|
||||
private static final ExternalServiceCredentialsGenerator STORAGE_CREDENTIAL_GENERATOR = SecureStorageController
|
||||
.credentialsGenerator(STORAGE_CFG);
|
||||
private static final ExternalServiceCredentialsGenerator backupCredentialsGenerator = SecureBackupController.credentialsGenerator(
|
||||
BACKUP_CFG);
|
||||
|
||||
private static final RegistrationLockVerificationManager registrationLockVerificationManager = new RegistrationLockVerificationManager(
|
||||
accountsManager, clientPresenceManager, backupCredentialsGenerator, rateLimiters);
|
||||
|
||||
private static final ResourceExtension resources = ResourceExtension.builder()
|
||||
.addProvider(AuthHelper.getAuthFilter())
|
||||
@@ -214,9 +218,8 @@ class AccountControllerTest {
|
||||
captchaChecker,
|
||||
pushNotificationManager,
|
||||
changeNumberManager,
|
||||
registrationLockVerificationManager,
|
||||
registrationRecoveryPasswordsManager,
|
||||
STORAGE_CREDENTIAL_GENERATOR,
|
||||
clientPresenceManager,
|
||||
testClock))
|
||||
.build();
|
||||
|
||||
|
||||
@@ -0,0 +1,306 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.controllers;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
|
||||
import io.dropwizard.testing.junit5.ResourceExtension;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Stream;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.client.Entity;
|
||||
import javax.ws.rs.client.Invocation;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.Response;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.glassfish.jersey.server.ServerProperties;
|
||||
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.whispersystems.textsecuregcm.auth.RegistrationLockError;
|
||||
import org.whispersystems.textsecuregcm.auth.RegistrationLockVerificationManager;
|
||||
import org.whispersystems.textsecuregcm.entities.RegistrationSession;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiter;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.mappers.ImpossiblePhoneNumberExceptionMapper;
|
||||
import org.whispersystems.textsecuregcm.mappers.NonNormalizedPhoneNumberExceptionMapper;
|
||||
import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper;
|
||||
import org.whispersystems.textsecuregcm.registration.RegistrationServiceClient;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
||||
|
||||
@ExtendWith(DropwizardExtensionsSupport.class)
|
||||
class RegistrationControllerTest {
|
||||
|
||||
private static final String NUMBER = "+18005551212";
|
||||
private final AccountsManager accountsManager = mock(AccountsManager.class);
|
||||
private final RegistrationServiceClient registrationServiceClient = mock(RegistrationServiceClient.class);
|
||||
private final RegistrationLockVerificationManager registrationLockVerificationManager = mock(
|
||||
RegistrationLockVerificationManager.class);
|
||||
private final RateLimiters rateLimiters = mock(RateLimiters.class);
|
||||
|
||||
private final RateLimiter registrationLimiter = mock(RateLimiter.class);
|
||||
private final RateLimiter pinLimiter = mock(RateLimiter.class);
|
||||
|
||||
private final ResourceExtension resources = ResourceExtension.builder()
|
||||
.addProperty(ServerProperties.UNWRAP_COMPLETION_STAGE_IN_WRITER_ENABLE, Boolean.TRUE)
|
||||
.addProvider(new RateLimitExceededExceptionMapper())
|
||||
.addProvider(new ImpossiblePhoneNumberExceptionMapper())
|
||||
.addProvider(new NonNormalizedPhoneNumberExceptionMapper())
|
||||
.setMapper(SystemMapper.getMapper())
|
||||
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
|
||||
.addResource(
|
||||
new RegistrationController(accountsManager, registrationServiceClient, registrationLockVerificationManager,
|
||||
rateLimiters))
|
||||
.build();
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
when(rateLimiters.getRegistrationLimiter()).thenReturn(registrationLimiter);
|
||||
when(rateLimiters.getPinLimiter()).thenReturn(pinLimiter);
|
||||
}
|
||||
|
||||
@Test
|
||||
void unprocessableRequestJson() {
|
||||
final Invocation.Builder request = resources.getJerseyTest()
|
||||
.target("/v1/registration")
|
||||
.request();
|
||||
try (Response response = request.post(Entity.json(unprocessableJson()))) {
|
||||
assertEquals(400, response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingBasicAuthorization() {
|
||||
final Invocation.Builder request = resources.getJerseyTest()
|
||||
.target("/v1/registration")
|
||||
.request();
|
||||
try (Response response = request.post(Entity.json(requestJson("sessionId")))) {
|
||||
assertEquals(400, response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidBasicAuthorization() {
|
||||
final Invocation.Builder request = resources.getJerseyTest()
|
||||
.target("/v1/registration")
|
||||
.request()
|
||||
.header(HttpHeaders.AUTHORIZATION, "Basic but-invalid");
|
||||
try (Response response = request.post(Entity.json(invalidRequestJson()))) {
|
||||
assertEquals(401, response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidRequestBody() {
|
||||
final Invocation.Builder request = resources.getJerseyTest()
|
||||
.target("/v1/registration")
|
||||
.request()
|
||||
.header(HttpHeaders.AUTHORIZATION, authorizationHeader(NUMBER));
|
||||
try (Response response = request.post(Entity.json(invalidRequestJson()))) {
|
||||
assertEquals(422, response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void rateLimitedSession() throws Exception {
|
||||
final String sessionId = "sessionId";
|
||||
doThrow(RateLimitExceededException.class)
|
||||
.when(registrationLimiter).validate(encodeSessionId(sessionId));
|
||||
|
||||
final Invocation.Builder request = resources.getJerseyTest()
|
||||
.target("/v1/registration")
|
||||
.request()
|
||||
.header(HttpHeaders.AUTHORIZATION, authorizationHeader(NUMBER));
|
||||
try (Response response = request.post(Entity.json(requestJson(sessionId)))) {
|
||||
assertEquals(413, response.getStatus());
|
||||
// In the future, change to assertEquals(429, response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void registrationServiceTimeout() {
|
||||
when(registrationServiceClient.getSession(any(), any()))
|
||||
.thenReturn(CompletableFuture.failedFuture(new RuntimeException()));
|
||||
|
||||
final Invocation.Builder request = resources.getJerseyTest()
|
||||
.target("/v1/registration")
|
||||
.request()
|
||||
.header(HttpHeaders.AUTHORIZATION, authorizationHeader(NUMBER));
|
||||
try (Response response = request.post(Entity.json(requestJson("sessionId")))) {
|
||||
assertEquals(HttpStatus.SC_SERVICE_UNAVAILABLE, response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
void registrationServiceSessionCheck(@Nullable final RegistrationSession session, final int expectedStatus,
|
||||
final String message) {
|
||||
when(registrationServiceClient.getSession(any(), any()))
|
||||
.thenReturn(CompletableFuture.completedFuture(Optional.ofNullable(session)));
|
||||
|
||||
final Invocation.Builder request = resources.getJerseyTest()
|
||||
.target("/v1/registration")
|
||||
.request()
|
||||
.header(HttpHeaders.AUTHORIZATION, authorizationHeader(NUMBER));
|
||||
try (Response response = request.post(Entity.json(requestJson("sessionId")))) {
|
||||
assertEquals(expectedStatus, response.getStatus(), message);
|
||||
}
|
||||
}
|
||||
|
||||
static Stream<Arguments> registrationServiceSessionCheck() {
|
||||
return Stream.of(
|
||||
Arguments.of(null, 401, "session not found"),
|
||||
Arguments.of(new RegistrationSession("+18005551234", false), 400, "session number mismatch"),
|
||||
Arguments.of(new RegistrationSession(NUMBER, false), 401, "session not verified")
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(RegistrationLockError.class)
|
||||
void registrationLock(final RegistrationLockError error) throws Exception {
|
||||
when(registrationServiceClient.getSession(any(), any()))
|
||||
.thenReturn(CompletableFuture.completedFuture(Optional.of(new RegistrationSession(NUMBER, true))));
|
||||
|
||||
when(accountsManager.getByE164(any())).thenReturn(Optional.of(mock(Account.class)));
|
||||
|
||||
final Exception e = switch (error) {
|
||||
case MISMATCH -> new WebApplicationException(error.getExpectedStatus());
|
||||
case RATE_LIMITED -> new RateLimitExceededException(null);
|
||||
};
|
||||
doThrow(e)
|
||||
.when(registrationLockVerificationManager).verifyRegistrationLock(any(), any());
|
||||
|
||||
final Invocation.Builder request = resources.getJerseyTest()
|
||||
.target("/v1/registration")
|
||||
.request()
|
||||
.header(HttpHeaders.AUTHORIZATION, authorizationHeader(NUMBER));
|
||||
try (Response response = request.post(Entity.json(requestJson("sessionId")))) {
|
||||
assertEquals(error.getExpectedStatus(), response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({
|
||||
"false, false, false, 200",
|
||||
"true, false, false, 200",
|
||||
"true, false, true, 200",
|
||||
"true, true, false, 409",
|
||||
"true, true, true, 200"
|
||||
})
|
||||
void deviceTransferAvailable(final boolean existingAccount, final boolean transferSupported,
|
||||
final boolean skipDeviceTransfer, final int expectedStatus) throws Exception {
|
||||
when(registrationServiceClient.getSession(any(), any()))
|
||||
.thenReturn(CompletableFuture.completedFuture(Optional.of(new RegistrationSession(NUMBER, true))));
|
||||
|
||||
final Optional<Account> maybeAccount;
|
||||
if (existingAccount) {
|
||||
final Account account = mock(Account.class);
|
||||
when(account.isTransferSupported()).thenReturn(transferSupported);
|
||||
maybeAccount = Optional.of(account);
|
||||
} else {
|
||||
maybeAccount = Optional.empty();
|
||||
}
|
||||
when(accountsManager.getByE164(any())).thenReturn(maybeAccount);
|
||||
when(accountsManager.create(any(), any(), any(), any(), any())).thenReturn(mock(Account.class));
|
||||
|
||||
final Invocation.Builder request = resources.getJerseyTest()
|
||||
.target("/v1/registration")
|
||||
.request()
|
||||
.header(HttpHeaders.AUTHORIZATION, authorizationHeader(NUMBER));
|
||||
try (Response response = request.post(Entity.json(requestJson("sessionId", skipDeviceTransfer)))) {
|
||||
assertEquals(expectedStatus, response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
// this is functionally the same as deviceTransferAvailable(existingAccount=false)
|
||||
@Test
|
||||
void success() throws Exception {
|
||||
when(registrationServiceClient.getSession(any(), any()))
|
||||
.thenReturn(CompletableFuture.completedFuture(Optional.of(new RegistrationSession(NUMBER, true))));
|
||||
when(accountsManager.create(any(), any(), any(), any(), any()))
|
||||
.thenReturn(mock(Account.class));
|
||||
|
||||
final Invocation.Builder request = resources.getJerseyTest()
|
||||
.target("/v1/registration")
|
||||
.request()
|
||||
.header(HttpHeaders.AUTHORIZATION, authorizationHeader(NUMBER));
|
||||
try (Response response = request.post(Entity.json(requestJson("sessionId")))) {
|
||||
assertEquals(200, response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Valid request JSON with the give session ID and skipDeviceTransfer
|
||||
*/
|
||||
private static String requestJson(final String sessionId, final boolean skipDeviceTransfer) {
|
||||
return String.format("""
|
||||
{
|
||||
"sessionId": "%s",
|
||||
"accountAttributes": {},
|
||||
"skipDeviceTransfer": %s
|
||||
}
|
||||
""", encodeSessionId(sessionId), skipDeviceTransfer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Valid request JSON with the give session ID
|
||||
*/
|
||||
private static String requestJson(final String sessionId) {
|
||||
return requestJson(sessionId, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request JSON in the shape of {@link org.whispersystems.textsecuregcm.entities.RegistrationRequest}, but that fails
|
||||
* validation
|
||||
*/
|
||||
private static String invalidRequestJson() {
|
||||
return """
|
||||
{
|
||||
"sessionId": null,
|
||||
"accountAttributes": {},
|
||||
"skipDeviceTransfer": false
|
||||
}
|
||||
""";
|
||||
}
|
||||
|
||||
/**
|
||||
* Request JSON that cannot be marshalled into {@link org.whispersystems.textsecuregcm.entities.RegistrationRequest}
|
||||
*/
|
||||
private static String unprocessableJson() {
|
||||
return """
|
||||
{
|
||||
"sessionId": []
|
||||
}
|
||||
""";
|
||||
}
|
||||
|
||||
private static String authorizationHeader(final String number) {
|
||||
return "Basic " + Base64.getEncoder().encodeToString(
|
||||
String.format("%s:password", number).getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
private static String encodeSessionId(final String sessionId) {
|
||||
return Base64.getEncoder().encodeToString(sessionId.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user