mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-19 23:38:07 +01:00
Allow primary devices to change names of linked devices
This commit is contained in:
@@ -7,6 +7,7 @@ package org.whispersystems.textsecuregcm.controllers;
|
||||
import io.dropwizard.auth.Auth;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import java.util.Base64;
|
||||
import java.util.Objects;
|
||||
@@ -19,6 +20,7 @@ import javax.validation.constraints.NotNull;
|
||||
import javax.ws.rs.BadRequestException;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DELETE;
|
||||
import javax.ws.rs.ForbiddenException;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.HEAD;
|
||||
import javax.ws.rs.HeaderParam;
|
||||
@@ -27,6 +29,7 @@ import javax.ws.rs.PUT;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
@@ -192,10 +195,39 @@ public class AccountController {
|
||||
|
||||
@PUT
|
||||
@Path("/name/")
|
||||
public void setName(@Mutable @Auth AuthenticatedDevice auth, @NotNull @Valid DeviceName deviceName) {
|
||||
Account account = auth.getAccount();
|
||||
Device device = auth.getAuthenticatedDevice();
|
||||
accounts.updateDevice(account, device.getId(), d -> d.setName(deviceName.getDeviceName()));
|
||||
@Operation(summary = "Set a device's encrypted name",
|
||||
description = """
|
||||
Sets the encrypted name for the specified device. Primary devices may change the name of any device associated
|
||||
with their account, but linked devices may only change their own name. If no device ID is specified, then the
|
||||
authenticated device's ID will be used.
|
||||
""")
|
||||
@ApiResponse(responseCode = "204", description = "Device name changed successfully")
|
||||
@ApiResponse(responseCode = "404", description = "No device found with the given ID")
|
||||
@ApiResponse(responseCode = "403", description = "Not authorized to change the name of the device with the given ID")
|
||||
public void setName(@Mutable @Auth final AuthenticatedDevice auth,
|
||||
@NotNull @Valid final DeviceName deviceName,
|
||||
|
||||
@Nullable
|
||||
@QueryParam("deviceId")
|
||||
@Schema(description = "The ID of the device for which to set a name; if omitted, the authenticated device will be targeted for a name change",
|
||||
requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
final Byte deviceId) {
|
||||
|
||||
final Account account = auth.getAccount();
|
||||
final byte targetDeviceId = deviceId == null ? auth.getAuthenticatedDevice().getId() : deviceId;
|
||||
|
||||
if (account.getDevice(targetDeviceId).isEmpty()) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
final boolean mayChangeName = auth.getAuthenticatedDevice().isPrimary() ||
|
||||
auth.getAuthenticatedDevice().getId() == targetDeviceId;
|
||||
|
||||
if (!mayChangeName) {
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
|
||||
accounts.updateDevice(account, targetDeviceId, d -> d.setName(deviceName.deviceName()));
|
||||
}
|
||||
|
||||
@PUT
|
||||
|
||||
@@ -5,25 +5,15 @@
|
||||
|
||||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
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 javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
public class DeviceName {
|
||||
|
||||
@JsonProperty
|
||||
@JsonSerialize(using = ByteArrayAdapter.Serializing.class)
|
||||
@JsonDeserialize(using = ByteArrayAdapter.Deserializing.class)
|
||||
@NotEmpty
|
||||
@Size(max = 225)
|
||||
private byte[] deviceName;
|
||||
|
||||
public DeviceName() {}
|
||||
|
||||
public byte[] getDeviceName() {
|
||||
return deviceName;
|
||||
}
|
||||
public record DeviceName(@JsonSerialize(using = ByteArrayAdapter.Serializing.class)
|
||||
@JsonDeserialize(using = ByteArrayAdapter.Deserializing.class)
|
||||
@NotEmpty
|
||||
@Size(max = 225)
|
||||
byte[] deviceName) {
|
||||
}
|
||||
|
||||
@@ -6,13 +6,15 @@
|
||||
package org.whispersystems.textsecuregcm.grpc;
|
||||
|
||||
import io.grpc.Status;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
|
||||
public class DeviceIdUtil {
|
||||
|
||||
static byte validate(int deviceId) {
|
||||
if (deviceId > Byte.MAX_VALUE) {
|
||||
if (deviceId < Device.PRIMARY_ID || deviceId > Byte.MAX_VALUE) {
|
||||
throw Status.INVALID_ARGUMENT.withDescription("Device ID is out of range").asRuntimeException();
|
||||
}
|
||||
|
||||
return (byte) deviceId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +89,17 @@ public class DevicesGrpcService extends ReactorDevicesGrpc.DevicesImplBase {
|
||||
public Mono<SetDeviceNameResponse> setDeviceName(final SetDeviceNameRequest request) {
|
||||
final AuthenticatedDevice authenticatedDevice = AuthenticationUtil.requireAuthenticatedDevice();
|
||||
|
||||
final byte deviceId = DeviceIdUtil.validate(request.getId());
|
||||
|
||||
final boolean mayChangeName = authenticatedDevice.deviceId() == Device.PRIMARY_ID ||
|
||||
authenticatedDevice.deviceId() == deviceId;
|
||||
|
||||
if (!mayChangeName) {
|
||||
throw Status.PERMISSION_DENIED
|
||||
.withDescription("Authenticated device is not authorized to change target device name")
|
||||
.asRuntimeException();
|
||||
}
|
||||
|
||||
if (request.getName().isEmpty()) {
|
||||
throw Status.INVALID_ARGUMENT.withDescription("Must specify a device name").asRuntimeException();
|
||||
}
|
||||
@@ -100,7 +111,12 @@ public class DevicesGrpcService extends ReactorDevicesGrpc.DevicesImplBase {
|
||||
|
||||
return Mono.fromFuture(() -> accountsManager.getByAccountIdentifierAsync(authenticatedDevice.accountIdentifier()))
|
||||
.map(maybeAccount -> maybeAccount.orElseThrow(Status.UNAUTHENTICATED::asRuntimeException))
|
||||
.flatMap(account -> Mono.fromFuture(() -> accountsManager.updateDeviceAsync(account, authenticatedDevice.deviceId(),
|
||||
.doOnNext(account -> {
|
||||
if (account.getDevice(deviceId).isEmpty()) {
|
||||
throw Status.NOT_FOUND.withDescription("No device found with given ID").asRuntimeException();
|
||||
}
|
||||
})
|
||||
.flatMap(account -> Mono.fromFuture(() -> accountsManager.updateDeviceAsync(account, deviceId,
|
||||
device -> device.setName(request.getName().toByteArray()))))
|
||||
.thenReturn(SetDeviceNameResponse.newBuilder().build());
|
||||
}
|
||||
|
||||
@@ -47,7 +47,9 @@ public class KeysAnonymousGrpcService extends ReactorKeysAnonymousGrpc.KeysAnony
|
||||
final ServiceIdentifier serviceIdentifier =
|
||||
ServiceIdentifierUtil.fromGrpcServiceIdentifier(request.getRequest().getTargetIdentifier());
|
||||
|
||||
final byte deviceId = DeviceIdUtil.validate(request.getRequest().getDeviceId());
|
||||
final byte deviceId = request.getRequest().hasDeviceId()
|
||||
? DeviceIdUtil.validate(request.getRequest().getDeviceId())
|
||||
: KeysGrpcHelper.ALL_DEVICES;
|
||||
|
||||
return switch (request.getAuthorizationCase()) {
|
||||
case GROUP_SEND_TOKEN ->
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
package org.whispersystems.textsecuregcm.grpc;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.protobuf.ByteString;
|
||||
import io.grpc.Status;
|
||||
import org.signal.chat.common.EcPreKey;
|
||||
@@ -26,7 +25,6 @@ import reactor.util.function.Tuples;
|
||||
|
||||
class KeysGrpcHelper {
|
||||
|
||||
@VisibleForTesting
|
||||
static final byte ALL_DEVICES = 0;
|
||||
|
||||
static Mono<GetPreKeysResponse> getPreKeys(final Account targetAccount,
|
||||
|
||||
@@ -122,7 +122,9 @@ public class KeysGrpcService extends ReactorKeysGrpc.KeysImplBase {
|
||||
final ServiceIdentifier targetIdentifier =
|
||||
ServiceIdentifierUtil.fromGrpcServiceIdentifier(request.getTargetIdentifier());
|
||||
|
||||
final byte deviceId = DeviceIdUtil.validate(request.getDeviceId());
|
||||
final byte deviceId = request.hasDeviceId()
|
||||
? DeviceIdUtil.validate(request.getDeviceId())
|
||||
: KeysGrpcHelper.ALL_DEVICES;
|
||||
|
||||
final String rateLimitKey = authenticatedDevice.accountIdentifier() + "." +
|
||||
authenticatedDevice.deviceId() + "__" +
|
||||
|
||||
Reference in New Issue
Block a user