mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-19 18:48:03 +01:00
Add controllers/service implementations for receiving call quality survey responses
This commit is contained in:
committed by
Jon Chambers
parent
c68e3103c4
commit
9378b9a6e6
@@ -107,6 +107,7 @@ import org.whispersystems.textsecuregcm.controllers.AccountControllerV2;
|
||||
import org.whispersystems.textsecuregcm.controllers.ArchiveController;
|
||||
import org.whispersystems.textsecuregcm.controllers.AttachmentControllerV4;
|
||||
import org.whispersystems.textsecuregcm.controllers.CallLinkController;
|
||||
import org.whispersystems.textsecuregcm.controllers.CallQualitySurveyController;
|
||||
import org.whispersystems.textsecuregcm.controllers.CallRoutingControllerV2;
|
||||
import org.whispersystems.textsecuregcm.controllers.CertificateController;
|
||||
import org.whispersystems.textsecuregcm.controllers.ChallengeController;
|
||||
@@ -142,6 +143,7 @@ import org.whispersystems.textsecuregcm.filters.RestDeprecationFilter;
|
||||
import org.whispersystems.textsecuregcm.filters.TimestampResponseFilter;
|
||||
import org.whispersystems.textsecuregcm.grpc.AccountsAnonymousGrpcService;
|
||||
import org.whispersystems.textsecuregcm.grpc.AccountsGrpcService;
|
||||
import org.whispersystems.textsecuregcm.grpc.CallQualitySurveyGrpcService;
|
||||
import org.whispersystems.textsecuregcm.grpc.ErrorMappingInterceptor;
|
||||
import org.whispersystems.textsecuregcm.grpc.ExternalServiceCredentialsAnonymousGrpcService;
|
||||
import org.whispersystems.textsecuregcm.grpc.ExternalServiceCredentialsGrpcService;
|
||||
@@ -875,6 +877,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||
|
||||
final List<ServerServiceDefinition> unauthenticatedServices = Stream.of(
|
||||
new AccountsAnonymousGrpcService(accountsManager, rateLimiters),
|
||||
new CallQualitySurveyGrpcService(callQualitySurveyManager, rateLimiters),
|
||||
new KeysAnonymousGrpcService(accountsManager, keysManager, zkSecretParams, Clock.systemUTC()),
|
||||
new PaymentsGrpcService(currencyManager),
|
||||
ExternalServiceCredentialsAnonymousGrpcService.create(accountsManager, config),
|
||||
@@ -1052,6 +1055,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||
new ArchiveController(accountsManager, backupAuthManager, backupManager, backupMetrics),
|
||||
new CallRoutingControllerV2(rateLimiters, cloudflareTurnCredentialsManager),
|
||||
new CallLinkController(rateLimiters, callingGenericZkSecretParams),
|
||||
new CallQualitySurveyController(callQualitySurveyManager),
|
||||
new CertificateController(accountsManager, new CertificateGenerator(config.getDeliveryCertificate().certificate(),
|
||||
config.getDeliveryCertificate().ecPrivateKey(), config.getDeliveryCertificate().expiresDays()),
|
||||
zkAuthOperations, callingGenericZkSecretParams, clock),
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2025 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.controllers;
|
||||
|
||||
import com.google.common.net.HttpHeaders;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import io.dropwizard.auth.Auth;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.headers.Header;
|
||||
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.ForbiddenException;
|
||||
import jakarta.ws.rs.HeaderParam;
|
||||
import jakarta.ws.rs.PUT;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.WebApplicationException;
|
||||
import jakarta.ws.rs.container.ContainerRequestContext;
|
||||
import jakarta.ws.rs.core.Context;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.util.Optional;
|
||||
import org.signal.chat.calling.quality.SubmitCallQualitySurveyRequest;
|
||||
import org.whispersystems.textsecuregcm.auth.AuthenticatedDevice;
|
||||
import org.whispersystems.textsecuregcm.filters.RemoteAddressFilter;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimitedByIp;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.metrics.CallQualitySurveyManager;
|
||||
|
||||
@Path("/v1/call_quality_survey")
|
||||
@io.swagger.v3.oas.annotations.tags.Tag(name = "Account")
|
||||
public class CallQualitySurveyController {
|
||||
|
||||
private final CallQualitySurveyManager callQualitySurveyManager;
|
||||
|
||||
public CallQualitySurveyController(final CallQualitySurveyManager callQualitySurveyManager) {
|
||||
this.callQualitySurveyManager = callQualitySurveyManager;
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(summary = "Submit survey response", description = "Submits a call quality survey response")
|
||||
@ApiResponse(responseCode = "204", description = "The survey response was submitted successfully")
|
||||
@ApiResponse(responseCode = "422", description = "The survey response could not be parsed")
|
||||
@ApiResponse(responseCode = "429", description = "Too many attempts", headers = @Header(
|
||||
name = "Retry-After",
|
||||
description = "If present, an positive integer indicating the number of seconds before a subsequent attempt could succeed"))
|
||||
@RateLimitedByIp(RateLimiters.For.SUBMIT_CALL_QUALITY_SURVERY)
|
||||
public void submitCallQualitySurvey(@Auth final Optional<AuthenticatedDevice> authenticatedDevice,
|
||||
@RequestBody(description = "A serialized survey response protobuf entity")
|
||||
@NotNull final byte[] surveyResponse,
|
||||
@HeaderParam(HttpHeaders.USER_AGENT) final String userAgentString,
|
||||
@Context final ContainerRequestContext requestContext) {
|
||||
|
||||
if (authenticatedDevice.isPresent()) {
|
||||
throw new ForbiddenException("must not use authenticated connection for call quality survey submissions");
|
||||
}
|
||||
|
||||
final SubmitCallQualitySurveyRequest submitCallQualitySurveyRequest;
|
||||
|
||||
try {
|
||||
submitCallQualitySurveyRequest = SubmitCallQualitySurveyRequest.parseFrom(surveyResponse);
|
||||
} catch (final InvalidProtocolBufferException e) {
|
||||
throw new WebApplicationException(422);
|
||||
}
|
||||
|
||||
final String remoteAddress = (String) requestContext.getProperty(RemoteAddressFilter.REMOTE_ADDRESS_ATTRIBUTE_NAME);
|
||||
|
||||
callQualitySurveyManager.submitCallQualitySurvey(submitCallQualitySurveyRequest, remoteAddress, userAgentString);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2025 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.grpc;
|
||||
|
||||
import org.signal.chat.calling.quality.SimpleCallQualityGrpc;
|
||||
import org.signal.chat.calling.quality.SubmitCallQualitySurveyRequest;
|
||||
import org.signal.chat.calling.quality.SubmitCallQualitySurveyResponse;
|
||||
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.metrics.CallQualitySurveyManager;
|
||||
|
||||
public class CallQualitySurveyGrpcService extends SimpleCallQualityGrpc.CallQualityImplBase {
|
||||
|
||||
private final CallQualitySurveyManager callQualitySurveyManager;
|
||||
private final RateLimiters rateLimiters;
|
||||
|
||||
public CallQualitySurveyGrpcService(final CallQualitySurveyManager callQualitySurveyManager,
|
||||
final RateLimiters rateLimiters) {
|
||||
|
||||
this.callQualitySurveyManager = callQualitySurveyManager;
|
||||
this.rateLimiters = rateLimiters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubmitCallQualitySurveyResponse submitCallQualitySurvey(final SubmitCallQualitySurveyRequest request)
|
||||
throws RateLimitExceededException {
|
||||
|
||||
final String remoteAddress = RequestAttributesUtil.getRemoteAddress().getHostAddress();
|
||||
|
||||
rateLimiters.getSubmitCallQualitySurveyLimiter().validate(remoteAddress);
|
||||
|
||||
callQualitySurveyManager.submitCallQualitySurvey(request,
|
||||
remoteAddress,
|
||||
RequestAttributesUtil.getUserAgent().orElse(null));
|
||||
|
||||
return SubmitCallQualitySurveyResponse.getDefaultInstance();
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,7 @@ public class RateLimiters extends BaseRateLimiters<RateLimiters.For> {
|
||||
RECORD_DEVICE_TRANSFER_REQUEST("recordDeviceTransferRequest", new RateLimiterConfig(10, Duration.ofMillis(100), true)),
|
||||
WAIT_FOR_DEVICE_TRANSFER_REQUEST("waitForDeviceTransferRequest", new RateLimiterConfig(10, Duration.ofMillis(100), true)),
|
||||
DEVICE_CHECK_CHALLENGE("deviceCheckChallenge", new RateLimiterConfig(10, Duration.ofMinutes(1), false)),
|
||||
SUBMIT_CALL_QUALITY_SURVERY("submitCallQualitySurvey", new RateLimiterConfig(100, Duration.ofMinutes(1), true))
|
||||
;
|
||||
|
||||
private final String id;
|
||||
@@ -221,4 +222,8 @@ public class RateLimiters extends BaseRateLimiters<RateLimiters.For> {
|
||||
public RateLimiter getKeyTransparencyMonitorLimiter() {
|
||||
return forDescriptor(For.KEY_TRANSPARENCY_MONITOR_PER_IP);
|
||||
}
|
||||
|
||||
public RateLimiter getSubmitCallQualitySurveyLimiter() {
|
||||
return forDescriptor(For.SUBMIT_CALL_QUALITY_SURVERY);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user