Allow, but do not require, message delivery to devices without active delivery channels

This commit is contained in:
Jon Chambers
2024-06-25 09:53:31 -04:00
committed by GitHub
parent f5ce34fb69
commit d306cafbcc
24 changed files with 189 additions and 281 deletions

View File

@@ -22,12 +22,12 @@ import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.util.Pair;
/**
* This {@link WebsocketRefreshRequirementProvider} observes intra-request changes in {@link Account#isEnabled()} and
* This {@link WebsocketRefreshRequirementProvider} observes intra-request changes in
* {@link Device#hasMessageDeliveryChannel()}.
* <p>
* If a change in {@link Account#isEnabled()} or any associated {@link Device#hasMessageDeliveryChannel()} is observed, then any active
* WebSocket connections for the account must be closed in order for clients to get a refreshed
* {@link io.dropwizard.auth.Auth} object with a current device list.
* If a change in any associated {@link Device#hasMessageDeliveryChannel()} is observed, then any active WebSocket
* connections for the account must be closed in order for clients to get a refreshed {@link io.dropwizard.auth.Auth}
* object with a current device list.
*
* @see AuthenticatedAccount
*/
@@ -48,9 +48,8 @@ public class AuthEnablementRefreshRequirementProvider implements WebsocketRefres
@Override
public void handleRequestFiltered(final RequestEvent requestEvent) {
if (requestEvent.getUriInfo().getMatchedResourceMethod().getInvocable().getHandlingMethod().getAnnotation(ChangesDeviceEnabledState.class) != null) {
// The authenticated principal, if any, will be available after filters have run.
// Now that the account is known, capture a snapshot of `isEnabled` for the account's devices before carrying out
// the requests business logic.
// The authenticated principal, if any, will be available after filters have run. Now that the account is known,
// capture a snapshot of the account's devices before carrying out the requests business logic.
ContainerRequestUtil.getAuthenticatedAccount(requestEvent.getContainerRequest()).ifPresent(account ->
setAccount(requestEvent.getContainerRequest(), account));
}
@@ -66,8 +65,8 @@ public class AuthEnablementRefreshRequirementProvider implements WebsocketRefres
@Override
public List<Pair<UUID, Byte>> handleRequestFinished(final RequestEvent requestEvent) {
// Now that the request is finished, check whether `isEnabled` changed for any of the devices. If the value did
// change or if a devices was added or removed, all devices must disconnect and reauthenticate.
// Now that the request is finished, check whether `hasMessageDeliveryChannel` changed for any of the devices. If
// the value did change or if a devices was added or removed, all devices must disconnect and reauthenticate.
if (requestEvent.getContainerRequest().getProperty(DEVICES_ENABLED) != null) {
@SuppressWarnings("unchecked") final Map<Byte, Boolean> initialDevicesEnabled =

View File

@@ -18,6 +18,8 @@ import java.util.Optional;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public class OptionalAccess {
public static String ALL_DEVICES_SELECTOR = "*";
public static void verify(Optional<Account> requestAccount,
Optional<Anonymous> accessKey,
Optional<Account> targetAccount,
@@ -26,12 +28,12 @@ public class OptionalAccess {
try {
verify(requestAccount, accessKey, targetAccount);
if (!deviceSelector.equals("*")) {
if (!ALL_DEVICES_SELECTOR.equals(deviceSelector)) {
byte deviceId = Byte.parseByte(deviceSelector);
Optional<Device> targetDevice = targetAccount.get().getDevice(deviceId);
if (targetDevice.isPresent() && targetDevice.get().hasMessageDeliveryChannel()) {
if (targetDevice.isPresent()) {
return;
}
@@ -48,11 +50,10 @@ public class OptionalAccess {
public static void verify(Optional<Account> requestAccount,
Optional<Anonymous> accessKey,
Optional<Account> targetAccount)
{
Optional<Account> targetAccount) {
if (requestAccount.isPresent()) {
// Authenticated requests are never unauthorized; if the target exists and is enabled, return OK, otherwise throw not-found.
if (targetAccount.isPresent() && targetAccount.get().isEnabled()) {
// Authenticated requests are never unauthorized; if the target exists, return OK, otherwise throw not-found.
if (targetAccount.isPresent()) {
return;
} else {
throw new NotFoundException();
@@ -63,7 +64,7 @@ public class OptionalAccess {
// has unrestricted unidentified access, callers need to supply a fake access key. Likewise, if
// the target account does not exist, we *also* report unauthorized here (*not* not-found,
// since that would provide a free exists check).
if (accessKey.isEmpty() || !targetAccount.map(Account::isEnabled).orElse(false)) {
if (accessKey.isEmpty() || targetAccount.isEmpty()) {
throw new NotAuthorizedException(Response.Status.UNAUTHORIZED);
}