mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-21 04:58:06 +01:00
gRPC API for external services credentials service
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.grpc;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import java.time.Clock;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
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.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;
|
||||
|
||||
public class ExternalServiceCredentialsAnonymousGrpcService extends
|
||||
ReactorExternalServiceCredentialsAnonymousGrpc.ExternalServiceCredentialsAnonymousImplBase {
|
||||
|
||||
private static final long MAX_SVR_PASSWORD_AGE_SECONDS = TimeUnit.DAYS.toSeconds(30);
|
||||
|
||||
private final ExternalServiceCredentialsGenerator svrCredentialsGenerator;
|
||||
|
||||
private final AccountsManager accountsManager;
|
||||
|
||||
|
||||
public static ExternalServiceCredentialsAnonymousGrpcService create(
|
||||
final AccountsManager accountsManager,
|
||||
final WhisperServerConfiguration chatConfiguration) {
|
||||
return new ExternalServiceCredentialsAnonymousGrpcService(
|
||||
accountsManager,
|
||||
ExternalServiceDefinitions.SVR.generatorFactory().apply(chatConfiguration, Clock.systemUTC())
|
||||
);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
ExternalServiceCredentialsAnonymousGrpcService(
|
||||
final AccountsManager accountsManager,
|
||||
final ExternalServiceCredentialsGenerator svrCredentialsGenerator) {
|
||||
this.accountsManager = requireNonNull(accountsManager);
|
||||
this.svrCredentialsGenerator = requireNonNull(svrCredentialsGenerator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<CheckSvrCredentialsResponse> checkSvrCredentials(final CheckSvrCredentialsRequest request) {
|
||||
final List<String> tokens = request.getPasswordsList();
|
||||
final List<ExternalServiceCredentialsSelector.CredentialInfo> credentials = ExternalServiceCredentialsSelector.check(
|
||||
tokens,
|
||||
svrCredentialsGenerator,
|
||||
MAX_SVR_PASSWORD_AGE_SECONDS);
|
||||
|
||||
// the username associated with the provided number
|
||||
final Optional<String> matchingUsername = accountsManager
|
||||
.getByE164(request.getNumber())
|
||||
.map(Account::getUuid)
|
||||
.map(svrCredentialsGenerator::generateForUuid)
|
||||
.map(ExternalServiceCredentials::username);
|
||||
|
||||
return Flux.fromIterable(credentials)
|
||||
.reduce(CheckSvrCredentialsResponse.newBuilder(), ((builder, credentialInfo) -> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
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.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.limits.RateLimiters;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public class ExternalServiceCredentialsGrpcService extends ReactorExternalServiceCredentialsGrpc.ExternalServiceCredentialsImplBase {
|
||||
|
||||
private final Map<ExternalServiceType, ExternalServiceCredentialsGenerator> credentialsGeneratorByType;
|
||||
|
||||
private final RateLimiters rateLimiters;
|
||||
|
||||
|
||||
public static ExternalServiceCredentialsGrpcService createForAllExternalServices(
|
||||
final WhisperServerConfiguration chatConfiguration,
|
||||
final RateLimiters rateLimiters) {
|
||||
return new ExternalServiceCredentialsGrpcService(
|
||||
ExternalServiceDefinitions.createExternalServiceList(chatConfiguration, Clock.systemUTC()),
|
||||
rateLimiters
|
||||
);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
ExternalServiceCredentialsGrpcService(
|
||||
final Map<ExternalServiceType, ExternalServiceCredentialsGenerator> credentialsGeneratorByType,
|
||||
final RateLimiters rateLimiters) {
|
||||
this.credentialsGeneratorByType = requireNonNull(credentialsGeneratorByType);
|
||||
this.rateLimiters = requireNonNull(rateLimiters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<GetExternalServiceCredentialsResponse> getExternalServiceCredentials(final GetExternalServiceCredentialsRequest request) {
|
||||
final ExternalServiceCredentialsGenerator credentialsGenerator = this.credentialsGeneratorByType
|
||||
.get(request.getExternalService());
|
||||
if (credentialsGenerator == null) {
|
||||
return Mono.error(Status.INVALID_ARGUMENT.asException());
|
||||
}
|
||||
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();
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.grpc;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.signal.chat.credentials.ExternalServiceType;
|
||||
import org.whispersystems.textsecuregcm.WhisperServerConfiguration;
|
||||
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
|
||||
import org.whispersystems.textsecuregcm.configuration.ArtServiceConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.DirectoryV2ClientConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.PaymentsServiceConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.SecureValueRecovery2Configuration;
|
||||
|
||||
enum ExternalServiceDefinitions {
|
||||
ART(ExternalServiceType.EXTERNAL_SERVICE_TYPE_ART, (chatConfig, clock) -> {
|
||||
final ArtServiceConfiguration cfg = chatConfig.getArtServiceConfiguration();
|
||||
return ExternalServiceCredentialsGenerator
|
||||
.builder(cfg.userAuthenticationTokenSharedSecret())
|
||||
.withUserDerivationKey(cfg.userAuthenticationTokenUserIdSecret())
|
||||
.prependUsername(false)
|
||||
.truncateSignature(false)
|
||||
.build();
|
||||
}),
|
||||
DIRECTORY(ExternalServiceType.EXTERNAL_SERVICE_TYPE_DIRECTORY, (chatConfig, clock) -> {
|
||||
final DirectoryV2ClientConfiguration cfg = chatConfig.getDirectoryV2Configuration().getDirectoryV2ClientConfiguration();
|
||||
return ExternalServiceCredentialsGenerator
|
||||
.builder(cfg.userAuthenticationTokenSharedSecret())
|
||||
.withUserDerivationKey(cfg.userIdTokenSharedSecret())
|
||||
.prependUsername(false)
|
||||
.withClock(clock)
|
||||
.build();
|
||||
}),
|
||||
PAYMENTS(ExternalServiceType.EXTERNAL_SERVICE_TYPE_PAYMENTS, (chatConfig, clock) -> {
|
||||
final PaymentsServiceConfiguration cfg = chatConfig.getPaymentsServiceConfiguration();
|
||||
return ExternalServiceCredentialsGenerator
|
||||
.builder(cfg.userAuthenticationTokenSharedSecret())
|
||||
.prependUsername(true)
|
||||
.build();
|
||||
}),
|
||||
SVR(ExternalServiceType.EXTERNAL_SERVICE_TYPE_SVR, (chatConfig, clock) -> {
|
||||
final SecureValueRecovery2Configuration cfg = chatConfig.getSvr2Configuration();
|
||||
return ExternalServiceCredentialsGenerator
|
||||
.builder(cfg.userAuthenticationTokenSharedSecret())
|
||||
.withUserDerivationKey(cfg.userIdTokenSharedSecret().value())
|
||||
.prependUsername(false)
|
||||
.withDerivedUsernameTruncateLength(16)
|
||||
.withClock(clock)
|
||||
.build();
|
||||
}),
|
||||
STORAGE(ExternalServiceType.EXTERNAL_SERVICE_TYPE_STORAGE, (chatConfig, clock) -> {
|
||||
final PaymentsServiceConfiguration cfg = chatConfig.getPaymentsServiceConfiguration();
|
||||
return ExternalServiceCredentialsGenerator
|
||||
.builder(cfg.userAuthenticationTokenSharedSecret())
|
||||
.prependUsername(true)
|
||||
.build();
|
||||
}),
|
||||
;
|
||||
|
||||
private final ExternalServiceType externalService;
|
||||
|
||||
private final BiFunction<WhisperServerConfiguration, Clock, ExternalServiceCredentialsGenerator> generatorFactory;
|
||||
|
||||
ExternalServiceDefinitions(
|
||||
final ExternalServiceType externalService,
|
||||
final BiFunction<WhisperServerConfiguration, Clock, ExternalServiceCredentialsGenerator> factory) {
|
||||
this.externalService = requireNonNull(externalService);
|
||||
this.generatorFactory = requireNonNull(factory);
|
||||
}
|
||||
|
||||
public static Map<ExternalServiceType, ExternalServiceCredentialsGenerator> createExternalServiceList(
|
||||
final WhisperServerConfiguration chatConfiguration,
|
||||
final Clock clock) {
|
||||
return Arrays.stream(values())
|
||||
.map(esd -> Pair.of(esd.externalService, esd.generatorFactory().apply(chatConfiguration, clock)))
|
||||
.collect(Collectors.toMap(Pair::getKey, Pair::getValue));
|
||||
}
|
||||
|
||||
public BiFunction<WhisperServerConfiguration, Clock, ExternalServiceCredentialsGenerator> generatorFactory() {
|
||||
return generatorFactory;
|
||||
}
|
||||
|
||||
ExternalServiceType externalService() {
|
||||
return externalService;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user