mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-21 11:28:05 +01:00
Allow primary devices to change names of linked devices
This commit is contained in:
@@ -18,6 +18,7 @@ import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@@ -63,6 +64,7 @@ import org.whispersystems.textsecuregcm.entities.AccountIdentifierResponse;
|
||||
import org.whispersystems.textsecuregcm.entities.AccountIdentityResponse;
|
||||
import org.whispersystems.textsecuregcm.entities.ApnRegistrationId;
|
||||
import org.whispersystems.textsecuregcm.entities.ConfirmUsernameHashRequest;
|
||||
import org.whispersystems.textsecuregcm.entities.DeviceName;
|
||||
import org.whispersystems.textsecuregcm.entities.EncryptedUsername;
|
||||
import org.whispersystems.textsecuregcm.entities.GcmRegistrationId;
|
||||
import org.whispersystems.textsecuregcm.entities.RegistrationLock;
|
||||
@@ -81,6 +83,7 @@ import org.whispersystems.textsecuregcm.mappers.NonNormalizedPhoneNumberExceptio
|
||||
import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.UsernameHashNotAvailableException;
|
||||
import org.whispersystems.textsecuregcm.storage.UsernameReservationNotFoundException;
|
||||
@@ -999,4 +1002,72 @@ class AccountControllerTest {
|
||||
verify(AuthHelper.VALID_ACCOUNT).setUsernameLinkDetails(eq(newHandle.usernameLinkHandle()), eq(encryptedUsername));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetDeviceName() {
|
||||
try (final Response response = resources.getJerseyTest()
|
||||
.target("/v1/accounts/name/")
|
||||
.request()
|
||||
.header(HttpHeaders.AUTHORIZATION, AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_3, Device.PRIMARY_ID, AuthHelper.VALID_PASSWORD_3_PRIMARY))
|
||||
.put(Entity.json(new DeviceName(TestRandomUtil.nextBytes(64))))) {
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(204);
|
||||
verify(accountsManager).updateDevice(eq(AuthHelper.VALID_ACCOUNT_3), eq(Device.PRIMARY_ID), any());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetLinkedDeviceNameFromPrimary() {
|
||||
try (final Response response = resources.getJerseyTest()
|
||||
.target("/v1/accounts/name/")
|
||||
.queryParam("deviceId", AuthHelper.VALID_DEVICE_3_LINKED_ID)
|
||||
.request()
|
||||
.header(HttpHeaders.AUTHORIZATION, AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_3, Device.PRIMARY_ID, AuthHelper.VALID_PASSWORD_3_PRIMARY))
|
||||
.put(Entity.json(new DeviceName(TestRandomUtil.nextBytes(64))))) {
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(204);
|
||||
verify(accountsManager).updateDevice(eq(AuthHelper.VALID_ACCOUNT_3), eq(AuthHelper.VALID_DEVICE_3_LINKED_ID), any());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetLinkedDeviceNameFromLinked() {
|
||||
try (final Response response = resources.getJerseyTest()
|
||||
.target("/v1/accounts/name/")
|
||||
.queryParam("deviceId", AuthHelper.VALID_DEVICE_3_LINKED_ID)
|
||||
.request()
|
||||
.header(HttpHeaders.AUTHORIZATION, AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_3, AuthHelper.VALID_DEVICE_3_LINKED_ID, AuthHelper.VALID_PASSWORD_3_LINKED))
|
||||
.put(Entity.json(new DeviceName(TestRandomUtil.nextBytes(64))))) {
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(204);
|
||||
verify(accountsManager).updateDevice(eq(AuthHelper.VALID_ACCOUNT_3), eq(AuthHelper.VALID_DEVICE_3_LINKED_ID), any());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetDeviceNameDeviceNotFound() {
|
||||
try (final Response response = resources.getJerseyTest()
|
||||
.target("/v1/accounts/name/")
|
||||
.queryParam("deviceId", Device.MAXIMUM_DEVICE_ID)
|
||||
.request()
|
||||
.header(HttpHeaders.AUTHORIZATION, AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_3, AuthHelper.VALID_PASSWORD_3_PRIMARY))
|
||||
.put(Entity.json(new DeviceName(TestRandomUtil.nextBytes(64))))) {
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(404);
|
||||
verify(accountsManager, never()).updateDevice(any(), anyByte(), any());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetPrimaryDeviceNameFromLinked() {
|
||||
try (final Response response = resources.getJerseyTest()
|
||||
.target("/v1/accounts/name/")
|
||||
.queryParam("deviceId", Device.PRIMARY_ID)
|
||||
.request()
|
||||
.header(HttpHeaders.AUTHORIZATION, AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_3, AuthHelper.VALID_DEVICE_3_LINKED_ID, AuthHelper.VALID_PASSWORD_3_LINKED))
|
||||
.put(Entity.json(new DeviceName(TestRandomUtil.nextBytes(64))))) {
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(403);
|
||||
verify(accountsManager, never()).updateDevice(any(), anyByte(), any());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
import javax.annotation.Nullable;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
@@ -187,12 +186,66 @@ class DevicesGrpcServiceTest extends SimpleBaseGrpcTest<DevicesGrpcService, Devi
|
||||
final byte[] deviceName = TestRandomUtil.nextBytes(128);
|
||||
|
||||
final SetDeviceNameResponse ignored = authenticatedServiceStub().setDeviceName(SetDeviceNameRequest.newBuilder()
|
||||
.setId(deviceId)
|
||||
.setName(ByteString.copyFrom(deviceName))
|
||||
.build());
|
||||
|
||||
verify(device).setName(deviceName);
|
||||
}
|
||||
|
||||
@Test
|
||||
void setLinkedDeviceNameFromPrimary() {
|
||||
mockAuthenticationInterceptor().setAuthenticatedDevice(AUTHENTICATED_ACI, Device.PRIMARY_ID);
|
||||
|
||||
final byte deviceId = Device.PRIMARY_ID + 1;
|
||||
|
||||
final Device device = mock(Device.class);
|
||||
when(authenticatedAccount.getDevice(deviceId)).thenReturn(Optional.of(device));
|
||||
|
||||
final byte[] deviceName = TestRandomUtil.nextBytes(128);
|
||||
|
||||
final SetDeviceNameResponse ignored = authenticatedServiceStub().setDeviceName(SetDeviceNameRequest.newBuilder()
|
||||
.setId(deviceId)
|
||||
.setName(ByteString.copyFrom(deviceName))
|
||||
.build());
|
||||
|
||||
verify(device).setName(deviceName);
|
||||
}
|
||||
|
||||
@Test
|
||||
void setPrimaryDeviceNameFromLinkedDevice() {
|
||||
mockAuthenticationInterceptor().setAuthenticatedDevice(AUTHENTICATED_ACI, (byte) (Device.PRIMARY_ID + 1));
|
||||
|
||||
final byte deviceId = Device.PRIMARY_ID;
|
||||
|
||||
final Device device = mock(Device.class);
|
||||
when(authenticatedAccount.getDevice(deviceId)).thenReturn(Optional.of(device));
|
||||
|
||||
final byte[] deviceName = TestRandomUtil.nextBytes(128);
|
||||
|
||||
assertStatusException(Status.PERMISSION_DENIED,
|
||||
() -> authenticatedServiceStub().setDeviceName(SetDeviceNameRequest.newBuilder()
|
||||
.setId(deviceId)
|
||||
.setName(ByteString.copyFrom(deviceName))
|
||||
.build()));
|
||||
|
||||
verify(device, never()).setName(deviceName);
|
||||
}
|
||||
|
||||
@Test
|
||||
void setDeviceNameNotFound() {
|
||||
mockAuthenticationInterceptor().setAuthenticatedDevice(AUTHENTICATED_ACI, Device.PRIMARY_ID);
|
||||
when(authenticatedAccount.getDevice(anyByte())).thenReturn(Optional.empty());
|
||||
|
||||
final byte[] deviceName = TestRandomUtil.nextBytes(128);
|
||||
|
||||
assertStatusException(Status.NOT_FOUND,
|
||||
() -> authenticatedServiceStub().setDeviceName(SetDeviceNameRequest.newBuilder()
|
||||
.setId(Device.PRIMARY_ID + 1)
|
||||
.setName(ByteString.copyFrom(deviceName))
|
||||
.build()));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
void setDeviceNameIllegalArgument(final SetDeviceNameRequest request) {
|
||||
@@ -203,11 +256,25 @@ class DevicesGrpcServiceTest extends SimpleBaseGrpcTest<DevicesGrpcService, Devi
|
||||
private static Stream<Arguments> setDeviceNameIllegalArgument() {
|
||||
return Stream.of(
|
||||
// No device name
|
||||
Arguments.of(SetDeviceNameRequest.newBuilder().build()),
|
||||
Arguments.of(SetDeviceNameRequest.newBuilder()
|
||||
.setId(Device.PRIMARY_ID)
|
||||
.build()),
|
||||
|
||||
// Excessively-long device name
|
||||
Arguments.of(SetDeviceNameRequest.newBuilder()
|
||||
.setName(ByteString.copyFrom(RandomStringUtils.randomAlphanumeric(1024).getBytes(StandardCharsets.UTF_8)))
|
||||
.setId(Device.PRIMARY_ID)
|
||||
.setName(ByteString.copyFrom(TestRandomUtil.nextBytes(1024)))
|
||||
.build()),
|
||||
|
||||
// No device ID
|
||||
Arguments.of(SetDeviceNameRequest.newBuilder()
|
||||
.setName(ByteString.copyFrom(TestRandomUtil.nextBytes(32)))
|
||||
.build()),
|
||||
|
||||
// Out-of-bounds device ID
|
||||
Arguments.of(SetDeviceNameRequest.newBuilder()
|
||||
.setId(Device.MAXIMUM_DEVICE_ID + 1)
|
||||
.setName(ByteString.copyFrom(TestRandomUtil.nextBytes(32)))
|
||||
.build())
|
||||
);
|
||||
}
|
||||
|
||||
@@ -27,8 +27,6 @@ import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.mockito.Mock;
|
||||
import org.signal.chat.common.EcPreKey;
|
||||
import org.signal.chat.common.EcSignedPreKey;
|
||||
@@ -306,9 +304,8 @@ class KeysAnonymousGrpcServiceTest extends SimpleBaseGrpcTest<KeysAnonymousGrpcS
|
||||
verifyNoInteractions(keysManager);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(bytes = {KeysGrpcHelper.ALL_DEVICES, 1})
|
||||
void getPreKeysDeviceNotFound(final byte deviceId) {
|
||||
@Test
|
||||
void getPreKeysDeviceNotFound() {
|
||||
final UUID accountIdentifier = UUID.randomUUID();
|
||||
|
||||
final byte[] unidentifiedAccessKey = TestRandomUtil.nextBytes(UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH);
|
||||
@@ -329,7 +326,7 @@ class KeysAnonymousGrpcServiceTest extends SimpleBaseGrpcTest<KeysAnonymousGrpcS
|
||||
.setTargetIdentifier(ServiceIdentifier.newBuilder()
|
||||
.setIdentityType(org.signal.chat.common.IdentityType.IDENTITY_TYPE_ACI)
|
||||
.setUuid(UUIDUtil.toByteString(accountIdentifier)))
|
||||
.setDeviceId(deviceId))
|
||||
.setDeviceId(Device.PRIMARY_ID))
|
||||
.build());
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ 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.junit.jupiter.params.provider.ValueSource;
|
||||
import org.mockito.Mock;
|
||||
import org.signal.chat.common.EcPreKey;
|
||||
import org.signal.chat.common.EcSignedPreKey;
|
||||
@@ -592,9 +591,8 @@ class KeysGrpcServiceTest extends SimpleBaseGrpcTest<KeysGrpcService, KeysGrpc.K
|
||||
.build()));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(bytes = {KeysGrpcHelper.ALL_DEVICES, 1})
|
||||
void getPreKeysDeviceNotFound(final byte deviceId) {
|
||||
@Test
|
||||
void getPreKeysDeviceNotFound() {
|
||||
final UUID accountIdentifier = UUID.randomUUID();
|
||||
|
||||
final Account targetAccount = mock(Account.class);
|
||||
@@ -611,7 +609,7 @@ class KeysGrpcServiceTest extends SimpleBaseGrpcTest<KeysGrpcService, KeysGrpc.K
|
||||
.setIdentityType(org.signal.chat.common.IdentityType.IDENTITY_TYPE_ACI)
|
||||
.setUuid(UUIDUtil.toByteString(accountIdentifier))
|
||||
.build())
|
||||
.setDeviceId(deviceId)
|
||||
.setDeviceId(Device.PRIMARY_ID)
|
||||
.build()));
|
||||
}
|
||||
|
||||
|
||||
@@ -105,6 +105,8 @@ public class AuthHelper {
|
||||
public static Device VALID_DEVICE_3_PRIMARY = mock(Device.class);
|
||||
public static Device VALID_DEVICE_3_LINKED = mock(Device.class);
|
||||
|
||||
public static final byte VALID_DEVICE_3_LINKED_ID = Device.PRIMARY_ID + 1;
|
||||
|
||||
private static SaltedTokenHash VALID_CREDENTIALS = mock(SaltedTokenHash.class);
|
||||
private static SaltedTokenHash VALID_CREDENTIALS_TWO = mock(SaltedTokenHash.class);
|
||||
private static SaltedTokenHash VALID_CREDENTIALS_3_PRIMARY = mock(SaltedTokenHash.class);
|
||||
@@ -136,7 +138,7 @@ public class AuthHelper {
|
||||
when(VALID_DEVICE_TWO.getId()).thenReturn(Device.PRIMARY_ID);
|
||||
when(UNDISCOVERABLE_DEVICE.getId()).thenReturn(Device.PRIMARY_ID);
|
||||
when(VALID_DEVICE_3_PRIMARY.getId()).thenReturn(Device.PRIMARY_ID);
|
||||
when(VALID_DEVICE_3_LINKED.getId()).thenReturn((byte) 2);
|
||||
when(VALID_DEVICE_3_LINKED.getId()).thenReturn(VALID_DEVICE_3_LINKED_ID);
|
||||
|
||||
when(UNDISCOVERABLE_DEVICE.isPrimary()).thenReturn(true);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user