Factor `DeviceActivationRequest` out into its own record
This commit is contained in:
parent
1a5327aece
commit
ae7cb8036e
|
@ -21,7 +21,6 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
|
@ -95,7 +94,7 @@ public class RegistrationController {
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
@Operation(summary = "Registers an account",
|
@Operation(summary = "Registers an account",
|
||||||
description = """
|
description = """
|
||||||
Registers a new account or attempts to “re-register” an existing account. It is expected that a well-behaved client
|
Registers a new account or attempts to “re-register” an existing account. It is expected that a well-behaved client
|
||||||
could make up to three consecutive calls to this API:
|
could make up to three consecutive calls to this API:
|
||||||
1. gets 423 from existing registration lock \n
|
1. gets 423 from existing registration lock \n
|
||||||
2. gets 409 from device available for transfer \n
|
2. gets 409 from device available for transfer \n
|
||||||
|
@ -150,10 +149,10 @@ public class RegistrationController {
|
||||||
if (registrationRequest.supportsAtomicAccountCreation()) {
|
if (registrationRequest.supportsAtomicAccountCreation()) {
|
||||||
assert registrationRequest.aciIdentityKey().isPresent();
|
assert registrationRequest.aciIdentityKey().isPresent();
|
||||||
assert registrationRequest.pniIdentityKey().isPresent();
|
assert registrationRequest.pniIdentityKey().isPresent();
|
||||||
assert registrationRequest.aciSignedPreKey().isPresent();
|
assert registrationRequest.deviceActivationRequest().aciSignedPreKey().isPresent();
|
||||||
assert registrationRequest.pniSignedPreKey().isPresent();
|
assert registrationRequest.deviceActivationRequest().pniSignedPreKey().isPresent();
|
||||||
assert registrationRequest.aciPqLastResortPreKey().isPresent();
|
assert registrationRequest.deviceActivationRequest().aciPqLastResortPreKey().isPresent();
|
||||||
assert registrationRequest.pniPqLastResortPreKey().isPresent();
|
assert registrationRequest.deviceActivationRequest().pniPqLastResortPreKey().isPresent();
|
||||||
|
|
||||||
account = accounts.update(account, a -> {
|
account = accounts.update(account, a -> {
|
||||||
a.setIdentityKey(registrationRequest.aciIdentityKey().get());
|
a.setIdentityKey(registrationRequest.aciIdentityKey().get());
|
||||||
|
@ -161,19 +160,19 @@ public class RegistrationController {
|
||||||
|
|
||||||
final Device device = a.getMasterDevice().orElseThrow();
|
final Device device = a.getMasterDevice().orElseThrow();
|
||||||
|
|
||||||
device.setSignedPreKey(registrationRequest.aciSignedPreKey().get());
|
device.setSignedPreKey(registrationRequest.deviceActivationRequest().aciSignedPreKey().get());
|
||||||
device.setPhoneNumberIdentitySignedPreKey(registrationRequest.pniSignedPreKey().get());
|
device.setPhoneNumberIdentitySignedPreKey(registrationRequest.deviceActivationRequest().pniSignedPreKey().get());
|
||||||
|
|
||||||
registrationRequest.apnToken().ifPresent(apnRegistrationId -> {
|
registrationRequest.deviceActivationRequest().apnToken().ifPresent(apnRegistrationId -> {
|
||||||
device.setApnId(apnRegistrationId.apnRegistrationId());
|
device.setApnId(apnRegistrationId.apnRegistrationId());
|
||||||
device.setVoipApnId(apnRegistrationId.voipRegistrationId());
|
device.setVoipApnId(apnRegistrationId.voipRegistrationId());
|
||||||
});
|
});
|
||||||
|
|
||||||
registrationRequest.gcmToken().ifPresent(gcmRegistrationId ->
|
registrationRequest.deviceActivationRequest().gcmToken().ifPresent(gcmRegistrationId ->
|
||||||
device.setGcmId(gcmRegistrationId.gcmRegistrationId()));
|
device.setGcmId(gcmRegistrationId.gcmRegistrationId()));
|
||||||
|
|
||||||
keys.storePqLastResort(a.getUuid(), Map.of(Device.MASTER_ID, registrationRequest.aciPqLastResortPreKey().get()));
|
keys.storePqLastResort(a.getUuid(), Map.of(Device.MASTER_ID, registrationRequest.deviceActivationRequest().aciPqLastResortPreKey().get()));
|
||||||
keys.storePqLastResort(a.getPhoneNumberIdentifier(), Map.of(Device.MASTER_ID, registrationRequest.pniPqLastResortPreKey().get()));
|
keys.storePqLastResort(a.getPhoneNumberIdentifier(), Map.of(Device.MASTER_ID, registrationRequest.deviceActivationRequest().pniPqLastResortPreKey().get()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
package org.whispersystems.textsecuregcm.entities;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
|
import javax.validation.Valid;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public record DeviceActivationRequest(@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = """
|
||||||
|
A signed EC pre-key to be associated with this account's ACI. If provided, an account
|
||||||
|
will be created "atomically," and all other properties needed for atomic account
|
||||||
|
creation must also be present.
|
||||||
|
""")
|
||||||
|
Optional<@Valid SignedPreKey> aciSignedPreKey,
|
||||||
|
|
||||||
|
@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = """
|
||||||
|
A signed EC pre-key to be associated with this account's PNI. If provided, an account
|
||||||
|
will be created "atomically," and all other properties needed for atomic account
|
||||||
|
creation must also be present.
|
||||||
|
""")
|
||||||
|
Optional<@Valid SignedPreKey> pniSignedPreKey,
|
||||||
|
|
||||||
|
@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = """
|
||||||
|
A signed Kyber-1024 "last resort" pre-key to be associated with this account's ACI. If
|
||||||
|
provided, an account will be created "atomically," and all other properties needed for
|
||||||
|
atomic account creation must also be present.
|
||||||
|
""")
|
||||||
|
Optional<@Valid SignedPreKey> aciPqLastResortPreKey,
|
||||||
|
|
||||||
|
@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = """
|
||||||
|
A signed Kyber-1024 "last resort" pre-key to be associated with this account's PNI. If
|
||||||
|
provided, an account will be created "atomically," and all other properties needed for
|
||||||
|
atomic account creation must also be present.
|
||||||
|
""")
|
||||||
|
Optional<@Valid SignedPreKey> pniPqLastResortPreKey,
|
||||||
|
|
||||||
|
@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = """
|
||||||
|
An APNs token set for the account's primary device. If provided, the account's primary
|
||||||
|
device will be notified of new messages via push notifications to the given token. If
|
||||||
|
creating an account "atomically," callers must provide exactly one of an APNs token
|
||||||
|
set, an FCM token, or an `AccountAttributes` entity with `fetchesMessages` set to
|
||||||
|
`true`.
|
||||||
|
""")
|
||||||
|
Optional<@Valid ApnRegistrationId> apnToken,
|
||||||
|
|
||||||
|
@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = """
|
||||||
|
An FCM/GCM token for the account's primary device. If provided, the account's primary
|
||||||
|
device will be notified of new messages via push notifications to the given token. If
|
||||||
|
creating an account "atomically," callers must provide exactly one of an APNs token
|
||||||
|
set, an FCM token, or an `AccountAttributes` entity with `fetchesMessages` set to
|
||||||
|
`true`.
|
||||||
|
""")
|
||||||
|
Optional<@Valid GcmRegistrationId> gcmToken) {
|
||||||
|
}
|
|
@ -5,6 +5,9 @@
|
||||||
|
|
||||||
package org.whispersystems.textsecuregcm.entities;
|
package org.whispersystems.textsecuregcm.entities;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonUnwrapped;
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
@ -59,58 +62,38 @@ public record RegistrationRequest(@Schema(requiredMode = Schema.RequiredMode.NOT
|
||||||
""")
|
""")
|
||||||
Optional<String> pniIdentityKey,
|
Optional<String> pniIdentityKey,
|
||||||
|
|
||||||
@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = """
|
@JsonUnwrapped
|
||||||
A signed EC pre-key to be associated with this account's ACI. If provided, an account
|
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
||||||
will be created "atomically," and all other properties needed for atomic account
|
DeviceActivationRequest deviceActivationRequest) implements PhoneVerificationRequest {
|
||||||
creation must also be present.
|
|
||||||
""")
|
|
||||||
Optional<@Valid SignedPreKey> aciSignedPreKey,
|
|
||||||
|
|
||||||
@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = """
|
@JsonCreator
|
||||||
A signed EC pre-key to be associated with this account's PNI. If provided, an account
|
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||||
will be created "atomically," and all other properties needed for atomic account
|
public RegistrationRequest(@JsonProperty("sessionId") String sessionId,
|
||||||
creation must also be present.
|
@JsonProperty("recoveryPassword") byte[] recoveryPassword,
|
||||||
""")
|
@JsonProperty("accountAttributes") AccountAttributes accountAttributes,
|
||||||
Optional<@Valid SignedPreKey> pniSignedPreKey,
|
@JsonProperty("skipDeviceTransfer") boolean skipDeviceTransfer,
|
||||||
|
@JsonProperty("aciIdentityKey") Optional<String> aciIdentityKey,
|
||||||
|
@JsonProperty("pniIdentityKey") Optional<String> pniIdentityKey,
|
||||||
|
@JsonProperty("aciSignedPreKey") Optional<@Valid SignedPreKey> aciSignedPreKey,
|
||||||
|
@JsonProperty("pniSignedPreKey") Optional<@Valid SignedPreKey> pniSignedPreKey,
|
||||||
|
@JsonProperty("aciPqLastResortPreKey") Optional<@Valid SignedPreKey> aciPqLastResortPreKey,
|
||||||
|
@JsonProperty("pniPqLastResortPreKey") Optional<@Valid SignedPreKey> pniPqLastResortPreKey,
|
||||||
|
@JsonProperty("apnToken") Optional<@Valid ApnRegistrationId> apnToken,
|
||||||
|
@JsonProperty("gcmToken") Optional<@Valid GcmRegistrationId> gcmToken) {
|
||||||
|
|
||||||
@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = """
|
// This may seem a little verbose, but at the time of writing, Jackson struggles with `@JsonUnwrapped` members in
|
||||||
A signed Kyber-1024 "last resort" pre-key to be associated with this account's ACI. If
|
// records, and this is a workaround. Please see
|
||||||
provided, an account will be created "atomically," and all other properties needed for
|
// https://github.com/FasterXML/jackson-databind/issues/3726#issuecomment-1525396869 for additional context.
|
||||||
atomic account creation must also be present.
|
this(sessionId, recoveryPassword, accountAttributes, skipDeviceTransfer, aciIdentityKey, pniIdentityKey,
|
||||||
""")
|
new DeviceActivationRequest(aciSignedPreKey, pniSignedPreKey, aciPqLastResortPreKey, pniPqLastResortPreKey, apnToken, gcmToken));
|
||||||
Optional<@Valid SignedPreKey> aciPqLastResortPreKey,
|
}
|
||||||
|
|
||||||
@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = """
|
|
||||||
A signed Kyber-1024 "last resort" pre-key to be associated with this account's PNI. If
|
|
||||||
provided, an account will be created "atomically," and all other properties needed for
|
|
||||||
atomic account creation must also be present.
|
|
||||||
""")
|
|
||||||
Optional<@Valid SignedPreKey> pniPqLastResortPreKey,
|
|
||||||
|
|
||||||
@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = """
|
|
||||||
An APNs token set for the account's primary device. If provided, the account's primary
|
|
||||||
device will be notified of new messages via push notifications to the given token. If
|
|
||||||
creating an account "atomically," callers must provide exactly one of an APNs token
|
|
||||||
set, an FCM token, or an `AccountAttributes` entity with `fetchesMessages` set to
|
|
||||||
`true`.
|
|
||||||
""")
|
|
||||||
Optional<@Valid ApnRegistrationId> apnToken,
|
|
||||||
|
|
||||||
@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = """
|
|
||||||
An FCM/GCM token for the account's primary device. If provided, the account's primary
|
|
||||||
device will be notified of new messages via push notifications to the given token. If
|
|
||||||
creating an account "atomically," callers must provide exactly one of an APNs token
|
|
||||||
set, an FCM token, or an `AccountAttributes` entity with `fetchesMessages` set to
|
|
||||||
`true`.
|
|
||||||
""")
|
|
||||||
Optional<@Valid GcmRegistrationId> gcmToken) implements PhoneVerificationRequest {
|
|
||||||
|
|
||||||
@AssertTrue
|
@AssertTrue
|
||||||
public boolean isEverySignedKeyValid() {
|
public boolean isEverySignedKeyValid() {
|
||||||
return validatePreKeySignature(aciIdentityKey(), aciSignedPreKey())
|
return validatePreKeySignature(aciIdentityKey(), deviceActivationRequest().aciSignedPreKey())
|
||||||
&& validatePreKeySignature(pniIdentityKey(), pniSignedPreKey())
|
&& validatePreKeySignature(pniIdentityKey(), deviceActivationRequest().pniSignedPreKey())
|
||||||
&& validatePreKeySignature(aciIdentityKey(), aciPqLastResortPreKey())
|
&& validatePreKeySignature(aciIdentityKey(), deviceActivationRequest().aciPqLastResortPreKey())
|
||||||
&& validatePreKeySignature(pniIdentityKey(), pniPqLastResortPreKey());
|
&& validatePreKeySignature(pniIdentityKey(), deviceActivationRequest().pniPqLastResortPreKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||||
|
@ -128,10 +111,10 @@ public record RegistrationRequest(@Schema(requiredMode = Schema.RequiredMode.NOT
|
||||||
final boolean hasNoAtomicAccountCreationParameters =
|
final boolean hasNoAtomicAccountCreationParameters =
|
||||||
aciIdentityKey().isEmpty()
|
aciIdentityKey().isEmpty()
|
||||||
&& pniIdentityKey().isEmpty()
|
&& pniIdentityKey().isEmpty()
|
||||||
&& aciSignedPreKey().isEmpty()
|
&& deviceActivationRequest().aciSignedPreKey().isEmpty()
|
||||||
&& pniSignedPreKey().isEmpty()
|
&& deviceActivationRequest().pniSignedPreKey().isEmpty()
|
||||||
&& aciPqLastResortPreKey().isEmpty()
|
&& deviceActivationRequest().aciPqLastResortPreKey().isEmpty()
|
||||||
&& pniPqLastResortPreKey().isEmpty();
|
&& deviceActivationRequest().pniPqLastResortPreKey().isEmpty();
|
||||||
|
|
||||||
return supportsAtomicAccountCreation() || hasNoAtomicAccountCreationParameters;
|
return supportsAtomicAccountCreation() || hasNoAtomicAccountCreationParameters;
|
||||||
}
|
}
|
||||||
|
@ -140,18 +123,18 @@ public record RegistrationRequest(@Schema(requiredMode = Schema.RequiredMode.NOT
|
||||||
return hasExactlyOneMessageDeliveryChannel()
|
return hasExactlyOneMessageDeliveryChannel()
|
||||||
&& aciIdentityKey().isPresent()
|
&& aciIdentityKey().isPresent()
|
||||||
&& pniIdentityKey().isPresent()
|
&& pniIdentityKey().isPresent()
|
||||||
&& aciSignedPreKey().isPresent()
|
&& deviceActivationRequest().aciSignedPreKey().isPresent()
|
||||||
&& pniSignedPreKey().isPresent()
|
&& deviceActivationRequest().pniSignedPreKey().isPresent()
|
||||||
&& aciPqLastResortPreKey().isPresent()
|
&& deviceActivationRequest().aciPqLastResortPreKey().isPresent()
|
||||||
&& pniPqLastResortPreKey().isPresent();
|
&& deviceActivationRequest().pniPqLastResortPreKey().isPresent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
boolean hasExactlyOneMessageDeliveryChannel() {
|
boolean hasExactlyOneMessageDeliveryChannel() {
|
||||||
if (accountAttributes.getFetchesMessages()) {
|
if (accountAttributes.getFetchesMessages()) {
|
||||||
return apnToken.isEmpty() && gcmToken.isEmpty();
|
return deviceActivationRequest().apnToken().isEmpty() && deviceActivationRequest().gcmToken().isEmpty();
|
||||||
} else {
|
} else {
|
||||||
return apnToken.isPresent() ^ gcmToken.isPresent();
|
return deviceActivationRequest().apnToken().isPresent() ^ deviceActivationRequest().gcmToken().isPresent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue