mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-20 09:57:59 +01:00
Add experiment to test standalone registration service
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
package org.whispersystems.textsecuregcm.registration;
|
||||
|
||||
import io.grpc.CallCredentials;
|
||||
import io.grpc.Metadata;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
class ApiKeyCallCredentials extends CallCredentials {
|
||||
|
||||
private final String apiKey;
|
||||
|
||||
private static final Metadata.Key<String> API_KEY_METADATA_KEY =
|
||||
Metadata.Key.of("x-signal-api-key", Metadata.ASCII_STRING_MARSHALLER);
|
||||
|
||||
ApiKeyCallCredentials(final String apiKey) {
|
||||
this.apiKey = apiKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyRequestMetadata(final RequestInfo requestInfo,
|
||||
final Executor appExecutor,
|
||||
final MetadataApplier applier) {
|
||||
|
||||
final Metadata metadata = new Metadata();
|
||||
metadata.put(API_KEY_METADATA_KEY, apiKey);
|
||||
|
||||
applier.apply(metadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void thisUsesUnstableApi() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright 2022 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.registration;
|
||||
|
||||
public enum ClientType {
|
||||
IOS,
|
||||
ANDROID_WITH_FCM,
|
||||
ANDROID_WITHOUT_FCM,
|
||||
UNKNOWN
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright 2022 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.registration;
|
||||
|
||||
/**
|
||||
* A message transport is a medium via which verification codes can be delivered to a destination phone.
|
||||
*/
|
||||
public enum MessageTransport {
|
||||
SMS,
|
||||
VOICE
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package org.whispersystems.textsecuregcm.registration;
|
||||
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
||||
import com.google.i18n.phonenumbers.Phonenumber;
|
||||
import com.google.protobuf.ByteString;
|
||||
import io.dropwizard.lifecycle.Managed;
|
||||
import io.grpc.ChannelCredentials;
|
||||
import io.grpc.Deadline;
|
||||
import io.grpc.Grpc;
|
||||
import io.grpc.ManagedChannel;
|
||||
import io.grpc.TlsChannelCredentials;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.signal.registration.rpc.CheckVerificationCodeRequest;
|
||||
import org.signal.registration.rpc.CheckVerificationCodeResponse;
|
||||
import org.signal.registration.rpc.RegistrationServiceGrpc;
|
||||
import org.signal.registration.rpc.SendVerificationCodeRequest;
|
||||
|
||||
public class RegistrationServiceClient implements Managed {
|
||||
|
||||
private final ManagedChannel channel;
|
||||
private final RegistrationServiceGrpc.RegistrationServiceFutureStub stub;
|
||||
private final Executor callbackExecutor;
|
||||
|
||||
public RegistrationServiceClient(final String host,
|
||||
final int port,
|
||||
final String apiKey,
|
||||
final String caCertificatePem,
|
||||
final Executor callbackExecutor) throws IOException {
|
||||
|
||||
try (final ByteArrayInputStream certificateInputStream = new ByteArrayInputStream(caCertificatePem.getBytes(StandardCharsets.UTF_8))) {
|
||||
final ChannelCredentials tlsChannelCredentials = TlsChannelCredentials.newBuilder()
|
||||
.trustManager(certificateInputStream)
|
||||
.build();
|
||||
|
||||
this.channel = Grpc.newChannelBuilderForAddress(host, port, tlsChannelCredentials).build();
|
||||
}
|
||||
|
||||
this.stub = RegistrationServiceGrpc.newFutureStub(channel)
|
||||
.withCallCredentials(new ApiKeyCallCredentials(apiKey));
|
||||
|
||||
this.callbackExecutor = callbackExecutor;
|
||||
}
|
||||
|
||||
public CompletableFuture<byte[]> sendRegistrationCode(final Phonenumber.PhoneNumber phoneNumber,
|
||||
final MessageTransport messageTransport,
|
||||
final ClientType clientType,
|
||||
@Nullable final String acceptLanguage,
|
||||
final Duration timeout) {
|
||||
|
||||
final long e164 = Long.parseLong(
|
||||
PhoneNumberUtil.getInstance().format(phoneNumber, PhoneNumberUtil.PhoneNumberFormat.E164).substring(1));
|
||||
|
||||
final SendVerificationCodeRequest.Builder requestBuilder = SendVerificationCodeRequest.newBuilder()
|
||||
.setE164(e164)
|
||||
.setTransport(getRpcMessageTransport(messageTransport))
|
||||
.setClientType(getRpcClientType(clientType));
|
||||
|
||||
if (StringUtils.isNotBlank(acceptLanguage)) {
|
||||
requestBuilder.setAcceptLanguage(acceptLanguage);
|
||||
}
|
||||
|
||||
return toCompletableFuture(stub.withDeadline(toDeadline(timeout))
|
||||
.sendVerificationCode(requestBuilder.build()))
|
||||
.thenApply(response -> response.getSessionId().toByteArray());
|
||||
}
|
||||
|
||||
public CompletableFuture<Boolean> checkVerificationCode(final byte[] sessionId,
|
||||
final String verificationCode,
|
||||
final Duration timeout) {
|
||||
|
||||
return toCompletableFuture(stub.withDeadline(toDeadline(timeout))
|
||||
.checkVerificationCode(CheckVerificationCodeRequest.newBuilder()
|
||||
.setSessionId(ByteString.copyFrom(sessionId))
|
||||
.setVerificationCode(verificationCode)
|
||||
.build()))
|
||||
.thenApply(CheckVerificationCodeResponse::getVerified);
|
||||
}
|
||||
|
||||
private static Deadline toDeadline(final Duration timeout) {
|
||||
return Deadline.after(timeout.toMillis(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
private static org.signal.registration.rpc.ClientType getRpcClientType(final ClientType clientType) {
|
||||
return switch (clientType) {
|
||||
case IOS -> org.signal.registration.rpc.ClientType.CLIENT_TYPE_IOS;
|
||||
case ANDROID_WITH_FCM -> org.signal.registration.rpc.ClientType.CLIENT_TYPE_ANDROID_WITH_FCM;
|
||||
case ANDROID_WITHOUT_FCM -> org.signal.registration.rpc.ClientType.CLIENT_TYPE_ANDROID_WITHOUT_FCM;
|
||||
case UNKNOWN -> org.signal.registration.rpc.ClientType.CLIENT_TYPE_UNSPECIFIED;
|
||||
};
|
||||
}
|
||||
|
||||
private static org.signal.registration.rpc.MessageTransport getRpcMessageTransport(final MessageTransport transport) {
|
||||
return switch (transport) {
|
||||
case SMS -> org.signal.registration.rpc.MessageTransport.MESSAGE_TRANSPORT_SMS;
|
||||
case VOICE -> org.signal.registration.rpc.MessageTransport.MESSAGE_TRANSPORT_VOICE;
|
||||
};
|
||||
}
|
||||
|
||||
private <T> CompletableFuture<T> toCompletableFuture(final ListenableFuture<T> listenableFuture) {
|
||||
final CompletableFuture<T> completableFuture = new CompletableFuture<>();
|
||||
|
||||
Futures.addCallback(listenableFuture, new FutureCallback<T>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable final T result) {
|
||||
completableFuture.complete(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(final Throwable throwable) {
|
||||
completableFuture.completeExceptionally(throwable);
|
||||
}
|
||||
}, callbackExecutor);
|
||||
|
||||
return completableFuture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() throws Exception {
|
||||
if (channel != null) {
|
||||
channel.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user