Update credentials and calling gRPC services to new error model

This commit is contained in:
ravi-signal
2026-01-28 17:36:12 -05:00
committed by GitHub
parent 8023a9346f
commit 2a7e99e9f0
5 changed files with 57 additions and 69 deletions

View File

@@ -15,19 +15,16 @@ import java.util.concurrent.TimeUnit;
import org.signal.chat.credentials.AuthCheckResult;
import org.signal.chat.credentials.CheckSvrCredentialsRequest;
import org.signal.chat.credentials.CheckSvrCredentialsResponse;
import org.signal.chat.credentials.ReactorExternalServiceCredentialsAnonymousGrpc;
import org.signal.chat.credentials.SimpleExternalServiceCredentialsAnonymousGrpc;
import org.whispersystems.textsecuregcm.WhisperServerConfiguration;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsSelector;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuples;
public class ExternalServiceCredentialsAnonymousGrpcService extends
ReactorExternalServiceCredentialsAnonymousGrpc.ExternalServiceCredentialsAnonymousImplBase {
SimpleExternalServiceCredentialsAnonymousGrpc.ExternalServiceCredentialsAnonymousImplBase {
private static final long MAX_SVR_PASSWORD_AGE_SECONDS = TimeUnit.DAYS.toSeconds(30);
@@ -54,36 +51,34 @@ public class ExternalServiceCredentialsAnonymousGrpcService extends
}
@Override
public Mono<CheckSvrCredentialsResponse> checkSvrCredentials(final CheckSvrCredentialsRequest request) {
public CheckSvrCredentialsResponse checkSvrCredentials(final CheckSvrCredentialsRequest request) {
final List<String> tokens = request.getPasswordsList();
if (tokens.size() > 10) {
throw GrpcExceptions.fieldViolation("passwordsList", "At most 10 passwords may be provided");
}
final List<ExternalServiceCredentialsSelector.CredentialInfo> credentials = ExternalServiceCredentialsSelector.check(
tokens,
svrCredentialsGenerator,
MAX_SVR_PASSWORD_AGE_SECONDS);
return Mono.fromFuture(() -> accountsManager.getByE164Async(request.getNumber()))
// the username associated with the provided number
.map(maybeAccount -> maybeAccount.map(Account::getUuid)
.map(svrCredentialsGenerator::generateForUuid)
.map(ExternalServiceCredentials::username))
.flatMapMany(maybeMatchingUsername -> Flux.fromIterable(credentials)
.map(credential -> Tuples.of(maybeMatchingUsername, credential)))
.reduce(CheckSvrCredentialsResponse.newBuilder(), ((builder, usernameAndCredentialInfo) -> {
final Optional<String> matchingUsername = usernameAndCredentialInfo.getT1();
final ExternalServiceCredentialsSelector.CredentialInfo credentialInfo = usernameAndCredentialInfo.getT2();
final AuthCheckResult authCheckResult;
if (!credentialInfo.valid()) {
authCheckResult = AuthCheckResult.AUTH_CHECK_RESULT_INVALID;
} else {
final String username = credentialInfo.credentials().username();
// does this credential match the account id for the e164 provided in the request?
authCheckResult = matchingUsername.map(username::equals).orElse(false)
? AuthCheckResult.AUTH_CHECK_RESULT_MATCH
: AuthCheckResult.AUTH_CHECK_RESULT_NO_MATCH;
}
return builder.putMatches(credentialInfo.token(), authCheckResult);
}))
.map(CheckSvrCredentialsResponse.Builder::build);
// the username associated with the provided number
final Optional<String> maybeUsername = accountsManager.getByE164(request.getNumber())
.map(Account::getUuid)
.map(svrCredentialsGenerator::generateForUuid)
.map(ExternalServiceCredentials::username);
final CheckSvrCredentialsResponse.Builder builder = CheckSvrCredentialsResponse.newBuilder();
for (ExternalServiceCredentialsSelector.CredentialInfo credentialInfo : credentials) {
final AuthCheckResult authCheckResult;
if (!credentialInfo.valid()) {
authCheckResult = AuthCheckResult.AUTH_CHECK_RESULT_INVALID;
} else {
final String username = credentialInfo.credentials().username();
// does this credential match the account id for the e164 provided in the request?
authCheckResult = maybeUsername.map(username::equals).orElse(false)
? AuthCheckResult.AUTH_CHECK_RESULT_MATCH
: AuthCheckResult.AUTH_CHECK_RESULT_NO_MATCH;
}
builder.putMatches(credentialInfo.token(), authCheckResult);
}
return builder.build();
}
}

View File

@@ -8,22 +8,21 @@ package org.whispersystems.textsecuregcm.grpc;
import static java.util.Objects.requireNonNull;
import com.google.common.annotations.VisibleForTesting;
import io.grpc.Status;
import java.time.Clock;
import java.util.Map;
import org.signal.chat.credentials.ExternalServiceType;
import org.signal.chat.credentials.GetExternalServiceCredentialsRequest;
import org.signal.chat.credentials.GetExternalServiceCredentialsResponse;
import org.signal.chat.credentials.ReactorExternalServiceCredentialsGrpc;
import org.signal.chat.credentials.SimpleExternalServiceCredentialsGrpc;
import org.whispersystems.textsecuregcm.WhisperServerConfiguration;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.auth.grpc.AuthenticatedDevice;
import org.whispersystems.textsecuregcm.auth.grpc.AuthenticationUtil;
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
import reactor.core.publisher.Mono;
public class ExternalServiceCredentialsGrpcService extends ReactorExternalServiceCredentialsGrpc.ExternalServiceCredentialsImplBase {
public class ExternalServiceCredentialsGrpcService extends SimpleExternalServiceCredentialsGrpc.ExternalServiceCredentialsImplBase {
private final Map<ExternalServiceType, ExternalServiceCredentialsGenerator> credentialsGeneratorByType;
@@ -48,21 +47,20 @@ public class ExternalServiceCredentialsGrpcService extends ReactorExternalServic
}
@Override
public Mono<GetExternalServiceCredentialsResponse> getExternalServiceCredentials(final GetExternalServiceCredentialsRequest request) {
public GetExternalServiceCredentialsResponse getExternalServiceCredentials(final GetExternalServiceCredentialsRequest request)
throws RateLimitExceededException {
final ExternalServiceCredentialsGenerator credentialsGenerator = this.credentialsGeneratorByType
.get(request.getExternalService());
if (credentialsGenerator == null) {
return Mono.error(Status.INVALID_ARGUMENT.asException());
throw GrpcExceptions.fieldViolation("externalService", "Invalid external service type");
}
final AuthenticatedDevice authenticatedDevice = AuthenticationUtil.requireAuthenticatedDevice();
return rateLimiters.forDescriptor(RateLimiters.For.EXTERNAL_SERVICE_CREDENTIALS).validateReactive(authenticatedDevice.accountIdentifier())
.then(Mono.fromSupplier(() -> {
final ExternalServiceCredentials externalServiceCredentials = credentialsGenerator
.generateForUuid(authenticatedDevice.accountIdentifier());
return GetExternalServiceCredentialsResponse.newBuilder()
.setUsername(externalServiceCredentials.username())
.setPassword(externalServiceCredentials.password())
.build();
}));
rateLimiters.forDescriptor(RateLimiters.For.EXTERNAL_SERVICE_CREDENTIALS).validate(authenticatedDevice.accountIdentifier());
final ExternalServiceCredentials externalServiceCredentials = credentialsGenerator
.generateForUuid(authenticatedDevice.accountIdentifier());
return GetExternalServiceCredentialsResponse.newBuilder()
.setUsername(externalServiceCredentials.username())
.setPassword(externalServiceCredentials.password())
.build();
}
}