From 35116f9229a1cf0a0b826e16458db2a97fa5f358 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Sat, 4 May 2019 12:31:50 -0700 Subject: [PATCH] Clean up concepts of enabled account state 1) Rename "active" methods to be "enabled," since they aren't really about "activity." 2) Make authentication fail if a device or account is in dissabled state. 3) Let some controllers authenticate accounts that are in a disabled state. --- .../textsecuregcm/WhisperServerService.java | 26 ++++-- .../auth/AccountAuthenticator.java | 64 +------------ .../auth/BaseAccountAuthenticator.java | 89 +++++++++++++++++++ .../auth/DisabledPermittedAccount.java | 31 +++++++ ...DisabledPermittedAccountAuthenticator.java | 22 +++++ .../textsecuregcm/auth/OptionalAccess.java | 11 ++- .../controllers/AccountController.java | 47 +++++----- .../controllers/AttachmentControllerV1.java | 2 - .../controllers/CertificateController.java | 1 - .../controllers/DeviceController.java | 6 +- .../controllers/KeysController.java | 23 ++--- .../controllers/MessageController.java | 4 +- .../controllers/ProfileController.java | 2 +- .../entities/ApnRegistrationId.java | 9 ++ .../entities/GcmRegistrationId.java | 9 ++ .../textsecuregcm/storage/Account.java | 14 +-- .../textsecuregcm/storage/AccountCleaner.java | 4 +- .../storage/AccountsManager.java | 2 +- .../textsecuregcm/storage/Device.java | 10 +-- .../storage/DirectoryReconciler.java | 6 +- .../WebSocketAccountAuthenticator.java | 32 +++---- .../tests/auth/OptionalAccessTest.java | 18 ++-- .../controllers/AccountControllerTest.java | 53 ++++++++++- .../controllers/AttachmentControllerTest.java | 19 +++- .../CertificateControllerTest.java | 18 +++- .../controllers/DeviceControllerTest.java | 25 +++++- .../controllers/DirectoryControllerTest.java | 18 +++- .../tests/controllers/KeyControllerTest.java | 65 ++++++++++++-- .../controllers/MessageControllerTest.java | 21 ++++- .../controllers/ProfileControllerTest.java | 19 +++- .../TransparentDataControllerTest.java | 5 +- .../VoiceVerificationControllerTest.java | 7 +- .../tests/storage/AccountCleanerTest.java | 24 ++--- .../tests/storage/AccountTest.java | 18 ++-- .../storage/DirectoryReconcilerTest.java | 8 +- .../textsecuregcm/tests/util/AuthHelper.java | 69 ++++++++++---- 36 files changed, 570 insertions(+), 231 deletions(-) create mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/auth/BaseAccountAuthenticator.java create mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/auth/DisabledPermittedAccount.java create mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/auth/DisabledPermittedAccountAuthenticator.java diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java index 8cba0735d..f9b2adf59 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java @@ -21,6 +21,8 @@ import com.codahale.metrics.jdbi3.strategies.DefaultNameStrategy; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.DeserializationFeature; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.eclipse.jetty.servlets.CrossOriginFilter; import org.jdbi.v3.core.Jdbi; @@ -28,6 +30,8 @@ import org.whispersystems.dispatch.DispatchManager; import org.whispersystems.textsecuregcm.auth.AccountAuthenticator; import org.whispersystems.textsecuregcm.auth.CertificateGenerator; import org.whispersystems.textsecuregcm.auth.DirectoryCredentialsGenerator; +import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccount; +import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccountAuthenticator; import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator; import org.whispersystems.textsecuregcm.controllers.AccountController; import org.whispersystems.textsecuregcm.controllers.AttachmentControllerV1; @@ -89,9 +93,11 @@ import java.util.Optional; import static com.codahale.metrics.MetricRegistry.name; import io.dropwizard.Application; -import io.dropwizard.auth.AuthDynamicFeature; -import io.dropwizard.auth.AuthValueFactoryProvider; +import io.dropwizard.auth.AuthFilter; +import io.dropwizard.auth.PolymorphicAuthDynamicFeature; +import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider; import io.dropwizard.auth.basic.BasicCredentialAuthFilter; +import io.dropwizard.auth.basic.BasicCredentials; import io.dropwizard.db.DataSourceFactory; import io.dropwizard.db.PooledDataSourceFactory; import io.dropwizard.jdbi3.JdbiFactory; @@ -184,9 +190,11 @@ public class WhisperServerService extends Application() - .setAuthenticator(deviceAuthenticator) - .buildAuthFilter())); - environment.jersey().register(new AuthValueFactoryProvider.Binder<>(Account.class)); + AuthFilter accountAuthFilter = new BasicCredentialAuthFilter.Builder().setAuthenticator(accountAuthenticator).buildAuthFilter (); + AuthFilter disabledPermittedAccountAuthFilter = new BasicCredentialAuthFilter.Builder().setAuthenticator(disabledPermittedAccountAuthenticator).buildAuthFilter(); + + environment.jersey().register(new PolymorphicAuthDynamicFeature<>(ImmutableMap.of(Account.class, accountAuthFilter, + DisabledPermittedAccount.class, disabledPermittedAccountAuthFilter))); + environment.jersey().register(new PolymorphicAuthValueFactoryProvider.Binder<>(ImmutableSet.of(Account.class, DisabledPermittedAccount.class))); environment.jersey().register(new AccountController(pendingAccountsManager, accountsManager, abusiveHostRules, rateLimiters, smsSender, directoryQueue, messagesManager, turnTokenGenerator, config.getTestDevices(), recaptchaClient)); environment.jersey().register(new DeviceController(pendingDevicesManager, accountsManager, messagesManager, directoryQueue, rateLimiters, config.getMaxDevices())); @@ -242,7 +252,7 @@ public class WhisperServerService extends Application { - - private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME); - private final Meter authenticationFailedMeter = metricRegistry.meter(name(getClass(), "authentication", "failed" )); - private final Meter authenticationSucceededMeter = metricRegistry.meter(name(getClass(), "authentication", "succeeded")); - - private final Logger logger = LoggerFactory.getLogger(AccountAuthenticator.class); - - private final AccountsManager accountsManager; +public class AccountAuthenticator extends BaseAccountAuthenticator implements Authenticator { public AccountAuthenticator(AccountsManager accountsManager) { - this.accountsManager = accountsManager; + super(accountsManager); } @Override - public Optional authenticate(BasicCredentials basicCredentials) - throws AuthenticationException - { - try { - AuthorizationHeader authorizationHeader = AuthorizationHeader.fromUserAndPassword(basicCredentials.getUsername(), basicCredentials.getPassword()); - Optional account = accountsManager.get(authorizationHeader.getNumber()); - - if (!account.isPresent()) { - return Optional.empty(); - } - - Optional device = account.get().getDevice(authorizationHeader.getDeviceId()); - - if (!device.isPresent()) { - return Optional.empty(); - } - - if (!device.get().isMaster() && device.get().isIdleInactive()) { - return Optional.empty(); - } - - if (device.get().getAuthenticationCredentials().verify(basicCredentials.getPassword())) { - authenticationSucceededMeter.mark(); - account.get().setAuthenticatedDevice(device.get()); - updateLastSeen(account.get(), device.get()); - return account; - } - - authenticationFailedMeter.mark(); - return Optional.empty(); - } catch (InvalidAuthorizationHeaderException iahe) { - return Optional.empty(); - } - } - - private void updateLastSeen(Account account, Device device) { - if (device.getLastSeen() != Util.todayInMillis()) { - device.setLastSeen(Util.todayInMillis()); - accountsManager.update(account); - } + public Optional authenticate(BasicCredentials basicCredentials) { + return super.authenticate(basicCredentials, true); } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/auth/BaseAccountAuthenticator.java b/service/src/main/java/org/whispersystems/textsecuregcm/auth/BaseAccountAuthenticator.java new file mode 100644 index 000000000..49075ecee --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/auth/BaseAccountAuthenticator.java @@ -0,0 +1,89 @@ +package org.whispersystems.textsecuregcm.auth; + +import com.codahale.metrics.Meter; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.SharedMetricRegistries; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.whispersystems.textsecuregcm.storage.Account; +import org.whispersystems.textsecuregcm.storage.AccountsManager; +import org.whispersystems.textsecuregcm.storage.Device; +import org.whispersystems.textsecuregcm.util.Constants; +import org.whispersystems.textsecuregcm.util.Util; + +import java.util.Optional; + +import static com.codahale.metrics.MetricRegistry.name; +import io.dropwizard.auth.basic.BasicCredentials; + +public class BaseAccountAuthenticator { + + private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME); + private final Meter authenticationFailedMeter = metricRegistry.meter(name(getClass(), "authentication", "failed" )); + private final Meter authenticationSucceededMeter = metricRegistry.meter(name(getClass(), "authentication", "succeeded" )); + private final Meter noSuchAccountMeter = metricRegistry.meter(name(getClass(), "authentication", "noSuchAccount" )); + private final Meter noSuchDeviceMeter = metricRegistry.meter(name(getClass(), "authentication", "noSuchDevice" )); + private final Meter accountDisabledMeter = metricRegistry.meter(name(getClass(), "authentication", "accountDisabled")); + private final Meter deviceDisabledMeter = metricRegistry.meter(name(getClass(), "authentication", "deviceDisabled" )); + private final Meter invalidAuthHeaderMeter = metricRegistry.meter(name(getClass(), "authentication", "invalidHeader" )); + + private final Logger logger = LoggerFactory.getLogger(AccountAuthenticator.class); + + private final AccountsManager accountsManager; + + public BaseAccountAuthenticator(AccountsManager accountsManager) { + this.accountsManager = accountsManager; + } + + public Optional authenticate(BasicCredentials basicCredentials, boolean enabledRequired) { + try { + AuthorizationHeader authorizationHeader = AuthorizationHeader.fromUserAndPassword(basicCredentials.getUsername(), basicCredentials.getPassword()); + Optional account = accountsManager.get(authorizationHeader.getNumber()); + + if (!account.isPresent()) { + noSuchAccountMeter.mark(); + return Optional.empty(); + } + + Optional device = account.get().getDevice(authorizationHeader.getDeviceId()); + + if (!device.isPresent()) { + noSuchDeviceMeter.mark(); + return Optional.empty(); + } + + if (enabledRequired) { + if (!device.get().isEnabled()) { + deviceDisabledMeter.mark(); + return Optional.empty(); + } + + if (!account.get().isEnabled()) { + accountDisabledMeter.mark(); + return Optional.empty(); + } + } + + if (device.get().getAuthenticationCredentials().verify(basicCredentials.getPassword())) { + authenticationSucceededMeter.mark(); + account.get().setAuthenticatedDevice(device.get()); + updateLastSeen(account.get(), device.get()); + return account; + } + + authenticationFailedMeter.mark(); + return Optional.empty(); + } catch (InvalidAuthorizationHeaderException iahe) { + invalidAuthHeaderMeter.mark(); + return Optional.empty(); + } + } + + private void updateLastSeen(Account account, Device device) { + if (device.getLastSeen() != Util.todayInMillis()) { + device.setLastSeen(Util.todayInMillis()); + accountsManager.update(account); + } + } + +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/auth/DisabledPermittedAccount.java b/service/src/main/java/org/whispersystems/textsecuregcm/auth/DisabledPermittedAccount.java new file mode 100644 index 000000000..743e4a606 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/auth/DisabledPermittedAccount.java @@ -0,0 +1,31 @@ +package org.whispersystems.textsecuregcm.auth; + +import org.whispersystems.textsecuregcm.storage.Account; + +import javax.security.auth.Subject; +import java.security.Principal; + +public class DisabledPermittedAccount implements Principal { + + private final Account account; + + public DisabledPermittedAccount(Account account) { + this.account = account; + } + + public Account getAccount() { + return account; + } + + // Principal implementation + + @Override + public String getName() { + return null; + } + + @Override + public boolean implies(Subject subject) { + return false; + } +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/auth/DisabledPermittedAccountAuthenticator.java b/service/src/main/java/org/whispersystems/textsecuregcm/auth/DisabledPermittedAccountAuthenticator.java new file mode 100644 index 000000000..b442c9213 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/auth/DisabledPermittedAccountAuthenticator.java @@ -0,0 +1,22 @@ +package org.whispersystems.textsecuregcm.auth; + +import org.whispersystems.textsecuregcm.storage.Account; +import org.whispersystems.textsecuregcm.storage.AccountsManager; + +import java.util.Optional; + +import io.dropwizard.auth.Authenticator; +import io.dropwizard.auth.basic.BasicCredentials; + +public class DisabledPermittedAccountAuthenticator extends BaseAccountAuthenticator implements Authenticator { + + public DisabledPermittedAccountAuthenticator(AccountsManager accountsManager) { + super(accountsManager); + } + + @Override + public Optional authenticate(BasicCredentials credentials) { + Optional account = super.authenticate(credentials, false); + return account.map(DisabledPermittedAccount::new); + } +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/auth/OptionalAccess.java b/service/src/main/java/org/whispersystems/textsecuregcm/auth/OptionalAccess.java index 77eb5ecb7..c7271cdf9 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/auth/OptionalAccess.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/auth/OptionalAccess.java @@ -2,7 +2,6 @@ package org.whispersystems.textsecuregcm.auth; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.Device; -import org.whispersystems.textsecuregcm.util.Hex; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; @@ -27,7 +26,7 @@ public class OptionalAccess { Optional targetDevice = targetAccount.get().getDevice(deviceId); - if (targetDevice.isPresent() && targetDevice.get().isActive()) { + if (targetDevice.isPresent() && targetDevice.get().isEnabled()) { return; } @@ -46,23 +45,23 @@ public class OptionalAccess { Optional accessKey, Optional targetAccount) { - if (requestAccount.isPresent() && targetAccount.isPresent() && targetAccount.get().isActive()) { + if (requestAccount.isPresent() && targetAccount.isPresent() && targetAccount.get().isEnabled()) { return; } //noinspection ConstantConditions - if (requestAccount.isPresent() && (!targetAccount.isPresent() || (targetAccount.isPresent() && !targetAccount.get().isActive()))) { + if (requestAccount.isPresent() && (!targetAccount.isPresent() || (targetAccount.isPresent() && !targetAccount.get().isEnabled()))) { throw new WebApplicationException(Response.Status.NOT_FOUND); } - if (accessKey.isPresent() && targetAccount.isPresent() && targetAccount.get().isActive() && targetAccount.get().isUnrestrictedUnidentifiedAccess()) { + if (accessKey.isPresent() && targetAccount.isPresent() && targetAccount.get().isEnabled() && targetAccount.get().isUnrestrictedUnidentifiedAccess()) { return; } if (accessKey.isPresent() && targetAccount.isPresent() && targetAccount.get().getUnidentifiedAccessKey().isPresent() && - targetAccount.get().isActive() && + targetAccount.get().isEnabled() && MessageDigest.isEqual(accessKey.get().getAccessKey(), targetAccount.get().getUnidentifiedAccessKey().get())) { return; diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java index 042172e53..ece23b4c0 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java @@ -25,6 +25,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials; import org.whispersystems.textsecuregcm.auth.AuthorizationHeader; +import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccount; import org.whispersystems.textsecuregcm.auth.InvalidAuthorizationHeaderException; import org.whispersystems.textsecuregcm.auth.StoredVerificationCode; import org.whispersystems.textsecuregcm.auth.TurnToken; @@ -63,7 +64,6 @@ import javax.ws.rs.QueryParam; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import java.io.IOException; import java.security.MessageDigest; import java.security.SecureRandom; import java.util.Arrays; @@ -260,9 +260,10 @@ public class AccountController { @PUT @Path("/gcm/") @Consumes(MediaType.APPLICATION_JSON) - public void setGcmRegistrationId(@Auth Account account, @Valid GcmRegistrationId registrationId) { - Device device = account.getAuthenticatedDevice().get(); - boolean wasAccountActive = account.isActive(); + public void setGcmRegistrationId(@Auth DisabledPermittedAccount disabledPermittedAccount, @Valid GcmRegistrationId registrationId) { + Account account = disabledPermittedAccount.getAccount(); + Device device = account.getAuthenticatedDevice().get(); + boolean wasAccountEnabled = account.isEnabled(); if (device.getGcmId() != null && device.getGcmId().equals(registrationId.getGcmRegistrationId())) @@ -277,7 +278,7 @@ public class AccountController { accounts.update(account); - if (!wasAccountActive && account.isActive()) { + if (!wasAccountEnabled && account.isEnabled()) { directoryQueue.addRegisteredUser(account.getNumber()); } } @@ -285,14 +286,15 @@ public class AccountController { @Timed @DELETE @Path("/gcm/") - public void deleteGcmRegistrationId(@Auth Account account) { - Device device = account.getAuthenticatedDevice().get(); + public void deleteGcmRegistrationId(@Auth DisabledPermittedAccount disabledPermittedAccount) { + Account account = disabledPermittedAccount.getAccount(); + Device device = account.getAuthenticatedDevice().get(); device.setGcmId(null); device.setFetchesMessages(false); accounts.update(account); - if (!account.isActive()) { + if (!account.isEnabled()) { directoryQueue.deleteRegisteredUser(account.getNumber()); } } @@ -301,9 +303,10 @@ public class AccountController { @PUT @Path("/apn/") @Consumes(MediaType.APPLICATION_JSON) - public void setApnRegistrationId(@Auth Account account, @Valid ApnRegistrationId registrationId) { - Device device = account.getAuthenticatedDevice().get(); - boolean wasAccountActive = account.isActive(); + public void setApnRegistrationId(@Auth DisabledPermittedAccount disabledPermittedAccount, @Valid ApnRegistrationId registrationId) { + Account account = disabledPermittedAccount.getAccount(); + Device device = account.getAuthenticatedDevice().get(); + boolean wasAccountEnabled = account.isEnabled(); device.setApnId(registrationId.getApnRegistrationId()); device.setVoipApnId(registrationId.getVoipRegistrationId()); @@ -311,7 +314,7 @@ public class AccountController { device.setFetchesMessages(false); accounts.update(account); - if (!wasAccountActive && account.isActive()) { + if (!wasAccountEnabled && account.isEnabled()) { directoryQueue.addRegisteredUser(account.getNumber()); } } @@ -319,14 +322,15 @@ public class AccountController { @Timed @DELETE @Path("/apn/") - public void deleteApnRegistrationId(@Auth Account account) { - Device device = account.getAuthenticatedDevice().get(); + public void deleteApnRegistrationId(@Auth DisabledPermittedAccount disabledPermittedAccount) { + Account account = disabledPermittedAccount.getAccount(); + Device device = account.getAuthenticatedDevice().get(); device.setApnId(null); device.setFetchesMessages(false); accounts.update(account); - if (!account.isActive()) { + if (!account.isEnabled()) { directoryQueue.deleteRegisteredUser(account.getNumber()); } } @@ -351,7 +355,8 @@ public class AccountController { @Timed @PUT @Path("/name/") - public void setName(@Auth Account account, @Valid DeviceName deviceName) { + public void setName(@Auth DisabledPermittedAccount disabledPermittedAccount, @Valid DeviceName deviceName) { + Account account = disabledPermittedAccount.getAccount(); account.getAuthenticatedDevice().get().setName(deviceName.getDeviceName()); accounts.update(account); } @@ -359,7 +364,8 @@ public class AccountController { @Timed @DELETE @Path("/signaling_key") - public void removeSignalingKey(@Auth Account account) { + public void removeSignalingKey(@Auth DisabledPermittedAccount disabledPermittedAccount) { + Account account = disabledPermittedAccount.getAccount(); account.getAuthenticatedDevice().get().setSignalingKey(null); accounts.update(account); } @@ -368,11 +374,12 @@ public class AccountController { @PUT @Path("/attributes/") @Consumes(MediaType.APPLICATION_JSON) - public void setAccountAttributes(@Auth Account account, + public void setAccountAttributes(@Auth DisabledPermittedAccount disabledPermittedAccount, @HeaderParam("X-Signal-Agent") String userAgent, @Valid AccountAttributes attributes) { - Device device = account.getAuthenticatedDevice().get(); + Account account = disabledPermittedAccount.getAccount(); + Device device = account.getAuthenticatedDevice().get(); device.setFetchesMessages(attributes.getFetchesMessages()); device.setName(attributes.getName()); @@ -476,7 +483,7 @@ public class AccountController { newUserMeter.mark(); } - if (account.isActive()) { + if (account.isEnabled()) { directoryQueue.addRegisteredUser(number); } else { directoryQueue.deleteRegisteredUser(number); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentControllerV1.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentControllerV1.java index 606344c2d..1120e3673 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentControllerV1.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentControllerV1.java @@ -25,7 +25,6 @@ import org.whispersystems.textsecuregcm.entities.AttachmentUri; import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.s3.UrlSigner; import org.whispersystems.textsecuregcm.storage.Account; -import org.whispersystems.textsecuregcm.util.Conversions; import javax.ws.rs.GET; import javax.ws.rs.Path; @@ -34,7 +33,6 @@ import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import java.io.IOException; import java.net.URL; -import java.security.SecureRandom; import java.util.stream.Stream; import io.dropwizard.auth.Auth; diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/CertificateController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/CertificateController.java index 63bcaa218..9ac66cf1e 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/CertificateController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/CertificateController.java @@ -14,7 +14,6 @@ import javax.ws.rs.Produces; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; - import java.io.IOException; import java.security.InvalidKeyException; diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java index b6414d30c..5d08eb336 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java @@ -112,7 +112,7 @@ public class DeviceController { account.removeDevice(deviceId); accounts.update(account); - if (!account.isActive()) { + if (!account.isEnabled()) { directoryQueue.deleteRegisteredUser(account.getNumber()); } @@ -134,7 +134,7 @@ public class DeviceController { maxDeviceLimit = maxDeviceConfiguration.get(account.getNumber()); } - if (account.getActiveDeviceCount() >= maxDeviceLimit) { + if (account.getEnabledDeviceCount() >= maxDeviceLimit) { throw new DeviceLimitExceededException(account.getDevices().size(), MAX_DEVICES); } @@ -186,7 +186,7 @@ public class DeviceController { maxDeviceLimit = maxDeviceConfiguration.get(account.get().getNumber()); } - if (account.get().getActiveDeviceCount() >= maxDeviceLimit) { + if (account.get().getEnabledDeviceCount() >= maxDeviceLimit) { throw new DeviceLimitExceededException(account.get().getDevices().size(), MAX_DEVICES); } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeysController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeysController.java index 199ce7b44..ead9e87f6 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeysController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeysController.java @@ -20,6 +20,7 @@ import com.codahale.metrics.annotation.Timed; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.auth.Anonymous; +import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccount; import org.whispersystems.textsecuregcm.auth.OptionalAccess; import org.whispersystems.textsecuregcm.entities.PreKey; import org.whispersystems.textsecuregcm.entities.PreKeyCount; @@ -85,10 +86,11 @@ public class KeysController { @Timed @PUT @Consumes(MediaType.APPLICATION_JSON) - public void setKeys(@Auth Account account, @Valid PreKeyState preKeys) { - Device device = account.getAuthenticatedDevice().get(); - boolean wasAccountActive = account.isActive(); - boolean updateAccount = false; + public void setKeys(@Auth DisabledPermittedAccount disabledPermittedAccount, @Valid PreKeyState preKeys) { + Account account = disabledPermittedAccount.getAccount(); + Device device = account.getAuthenticatedDevice().get(); + boolean wasAccountEnabled = account.isEnabled(); + boolean updateAccount = false; if (!preKeys.getSignedPreKey().equals(device.getSignedPreKey())) { device.setSignedPreKey(preKeys.getSignedPreKey()); @@ -103,7 +105,7 @@ public class KeysController { if (updateAccount) { accounts.update(account); - if (!wasAccountActive && account.isActive()) { + if (!wasAccountEnabled && account.isEnabled()) { directoryQueue.addRegisteredUser(account.getNumber()); } } @@ -138,7 +140,7 @@ public class KeysController { List devices = new LinkedList<>(); for (Device device : target.get().getDevices()) { - if (device.isActive() && (deviceId.equals("*") || device.getId() == Long.parseLong(deviceId))) { + if (device.isEnabled() && (deviceId.equals("*") || device.getId() == Long.parseLong(deviceId))) { SignedPreKey signedPreKey = device.getSignedPreKey(); PreKey preKey = null; @@ -162,14 +164,15 @@ public class KeysController { @PUT @Path("/signed") @Consumes(MediaType.APPLICATION_JSON) - public void setSignedKey(@Auth Account account, @Valid SignedPreKey signedPreKey) { - Device device = account.getAuthenticatedDevice().get(); - boolean wasAccountActive = account.isActive(); + public void setSignedKey(@Auth DisabledPermittedAccount disabledPermittedAccount, @Valid SignedPreKey signedPreKey) { + Account account = disabledPermittedAccount.getAccount(); + Device device = account.getAuthenticatedDevice().get(); + boolean wasAccountEnabled = account.isEnabled(); device.setSignedPreKey(signedPreKey); accounts.update(account); - if (!wasAccountActive && account.isActive()) { + if (!wasAccountEnabled && account.isEnabled()) { directoryQueue.addRegisteredUser(account.getNumber()); } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java index ce9317554..b2eea9cb6 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java @@ -149,7 +149,7 @@ public class MessageController { } } - return new SendMessageResponse(!isSyncMessage && source.isPresent() && source.get().getActiveDeviceCount() > 1); + return new SendMessageResponse(!isSyncMessage && source.isPresent() && source.get().getEnabledDeviceCount() > 1); } catch (NoSuchUserException e) { throw new WebApplicationException(Response.status(404).build()); } catch (MismatchedDevicesException e) { @@ -301,7 +301,7 @@ public class MessageController { } for (Device device : account.getDevices()) { - if (device.isActive() && + if (device.isEnabled() && !(isSyncMessage && device.getId() == account.getAuthenticatedDevice().get().getId())) { accountDeviceIds.add(device.getId()); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java index 80cdb399b..d5c209667 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java @@ -10,8 +10,8 @@ import com.codahale.metrics.annotation.Timed; import org.apache.commons.codec.binary.Base64; import org.hibernate.validator.constraints.Length; import org.hibernate.validator.valuehandling.UnwrapValidatedValue; -import org.whispersystems.textsecuregcm.auth.OptionalAccess; import org.whispersystems.textsecuregcm.auth.Anonymous; +import org.whispersystems.textsecuregcm.auth.OptionalAccess; import org.whispersystems.textsecuregcm.auth.UnidentifiedAccessChecksum; import org.whispersystems.textsecuregcm.configuration.ProfilesConfiguration; import org.whispersystems.textsecuregcm.entities.Profile; diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/entities/ApnRegistrationId.java b/service/src/main/java/org/whispersystems/textsecuregcm/entities/ApnRegistrationId.java index ce214ed04..117f3b070 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/entities/ApnRegistrationId.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/entities/ApnRegistrationId.java @@ -17,6 +17,7 @@ package org.whispersystems.textsecuregcm.entities; import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.annotations.VisibleForTesting; import org.hibernate.validator.constraints.NotEmpty; public class ApnRegistrationId { @@ -28,6 +29,14 @@ public class ApnRegistrationId { @JsonProperty private String voipRegistrationId; + public ApnRegistrationId() {} + + @VisibleForTesting + public ApnRegistrationId(String apnRegistrationId, String voipRegistrationId) { + this.apnRegistrationId = apnRegistrationId; + this.voipRegistrationId = voipRegistrationId; + } + public String getApnRegistrationId() { return apnRegistrationId; } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/entities/GcmRegistrationId.java b/service/src/main/java/org/whispersystems/textsecuregcm/entities/GcmRegistrationId.java index dc1dfec01..51e0110f8 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/entities/GcmRegistrationId.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/entities/GcmRegistrationId.java @@ -17,6 +17,7 @@ package org.whispersystems.textsecuregcm.entities; import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.annotations.VisibleForTesting; import org.hibernate.validator.constraints.NotEmpty; public class GcmRegistrationId { @@ -25,9 +26,17 @@ public class GcmRegistrationId { @NotEmpty private String gcmRegistrationId; + public GcmRegistrationId() {} + + @VisibleForTesting + public GcmRegistrationId(String id) { + this.gcmRegistrationId = id; + } + public String getGcmRegistrationId() { return gcmRegistrationId; } + } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java index 28888a5cb..9249a816b 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java @@ -115,13 +115,13 @@ public class Account implements Principal { } public boolean isUnauthenticatedDeliverySupported() { - return devices.stream().filter(Device::isActive).allMatch(Device::isUnauthenticatedDeliverySupported); + return devices.stream().filter(Device::isEnabled).allMatch(Device::isUnauthenticatedDeliverySupported); } - public boolean isActive() { + public boolean isEnabled() { return - getMasterDevice().isPresent() && - getMasterDevice().get().isActive() && + getMasterDevice().isPresent() && + getMasterDevice().get().isEnabled() && getLastSeen() > (System.currentTimeMillis() - TimeUnit.DAYS.toMillis(365)); } @@ -129,7 +129,7 @@ public class Account implements Principal { long highestDevice = Device.MASTER_ID; for (Device device : devices) { - if (!device.isActive()) { + if (!device.isEnabled()) { return device.getId(); } else if (device.getId() > highestDevice) { highestDevice = device.getId(); @@ -139,11 +139,11 @@ public class Account implements Principal { return highestDevice + 1; } - public int getActiveDeviceCount() { + public int getEnabledDeviceCount() { int count = 0; for (Device device : devices) { - if (device.isActive()) count++; + if (device.isEnabled()) count++; } return count; diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountCleaner.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountCleaner.java index 534ba99c4..bfe5ab6fe 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountCleaner.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountCleaner.java @@ -53,8 +53,8 @@ public class AccountCleaner implements AccountDatabaseCrawlerListener { long nowMs = System.currentTimeMillis(); int accountUpdateCount = 0; for (Account account : chunkAccounts) { - if (account.getMasterDevice().isPresent() && - account.getMasterDevice().get().isActive() && + if (account.getMasterDevice().isPresent() && + account.getMasterDevice().get().isEnabled() && isAccountExpired(account, nowMs)) { expiredAccountsMeter.mark(); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java index 908c0950c..79a8eca75 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java @@ -93,7 +93,7 @@ public class AccountsManager { } private void updateDirectory(Account account) { - if (account.isActive()) { + if (account.isEnabled()) { byte[] token = Util.getContactToken(account.getNumber()); ClientContact clientContact = new ClientContact(token, null, true, true); directory.add(clientContact); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/Device.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/Device.java index 1abdb21aa..61d7cc43e 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/Device.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/Device.java @@ -191,17 +191,13 @@ public class Device { this.signalingKey = signalingKey; } - public boolean isActive() { + public boolean isEnabled() { boolean hasChannel = fetchesMessages || !Util.isEmpty(getApnId()) || !Util.isEmpty(getGcmId()); return (id == MASTER_ID && hasChannel && signedPreKey != null) || - (id != MASTER_ID && hasChannel && signedPreKey != null && !isIdleInactive()); + (id != MASTER_ID && hasChannel && signedPreKey != null && lastSeen > (System.currentTimeMillis() - TimeUnit.DAYS.toMillis(30))); } - - public boolean isIdleInactive() { - return id != MASTER_ID && lastSeen < (System.currentTimeMillis() - TimeUnit.DAYS.toMillis(30)); - } - + public boolean getFetchesMessages() { return fetchesMessages; } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/DirectoryReconciler.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/DirectoryReconciler.java index 68314b3e7..972e9230f 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/DirectoryReconciler.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/DirectoryReconciler.java @@ -20,7 +20,6 @@ import com.codahale.metrics.Meter; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.SharedMetricRegistries; import com.codahale.metrics.Timer; -import com.google.common.annotations.VisibleForTesting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.entities.ClientContact; @@ -80,7 +79,7 @@ public class DirectoryReconciler implements AccountDatabaseCrawlerListener { try { for (Account account : accounts) { - if (account.isActive()) { + if (account.isEnabled()) { byte[] token = Util.getContactToken(account.getNumber()); ClientContact clientContact = new ClientContact(token, null, true, true); directoryManager.add(batchOperation, clientContact); @@ -93,9 +92,10 @@ public class DirectoryReconciler implements AccountDatabaseCrawlerListener { } } + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") private DirectoryReconciliationRequest createChunkRequest(Optional fromNumber, List accounts) { List numbers = accounts.stream() - .filter(Account::isActive) + .filter(Account::isEnabled) .map(Account::getNumber) .collect(Collectors.toList()); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/websocket/WebSocketAccountAuthenticator.java b/service/src/main/java/org/whispersystems/textsecuregcm/websocket/WebSocketAccountAuthenticator.java index b71ec38f6..c4dfc1c4f 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/websocket/WebSocketAccountAuthenticator.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/websocket/WebSocketAccountAuthenticator.java @@ -3,8 +3,6 @@ package org.whispersystems.textsecuregcm.websocket; import org.eclipse.jetty.websocket.api.UpgradeRequest; import org.whispersystems.textsecuregcm.auth.AccountAuthenticator; import org.whispersystems.textsecuregcm.storage.Account; -import org.whispersystems.textsecuregcm.storage.Device; -import org.whispersystems.websocket.auth.AuthenticationException; import org.whispersystems.websocket.auth.WebSocketAuthenticator; import java.util.List; @@ -23,25 +21,21 @@ public class WebSocketAccountAuthenticator implements WebSocketAuthenticator authenticate(UpgradeRequest request) throws AuthenticationException { - try { - Map> parameters = request.getParameterMap(); - List usernames = parameters.get("login"); - List passwords = parameters.get("password"); + public AuthenticationResult authenticate(UpgradeRequest request) { + Map> parameters = request.getParameterMap(); + List usernames = parameters.get("login"); + List passwords = parameters.get("password"); - if (usernames == null || usernames.size() == 0 || - passwords == null || passwords.size() == 0) - { - return new AuthenticationResult<>(Optional.empty(), false); - } - - BasicCredentials credentials = new BasicCredentials(usernames.get(0).replace(" ", "+"), - passwords.get(0).replace(" ", "+")); - - return new AuthenticationResult<>(accountAuthenticator.authenticate(credentials), true); - } catch (io.dropwizard.auth.AuthenticationException e) { - throw new AuthenticationException(e); + if (usernames == null || usernames.size() == 0 || + passwords == null || passwords.size() == 0) + { + return new AuthenticationResult<>(Optional.empty(), false); } + + BasicCredentials credentials = new BasicCredentials(usernames.get(0).replace(" ", "+"), + passwords.get(0).replace(" ", "+")); + + return new AuthenticationResult<>(accountAuthenticator.authenticate(credentials), true); } } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/auth/OptionalAccessTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/auth/OptionalAccessTest.java index 049349f67..928d5d991 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/auth/OptionalAccessTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/auth/OptionalAccessTest.java @@ -29,7 +29,7 @@ public class OptionalAccessTest { @Test public void testUnidentifiedMissingTargetDevice() { Account account = mock(Account.class); - when(account.isActive()).thenReturn(true); + when(account.isEnabled()).thenReturn(true); when(account.getDevice(eq(10))).thenReturn(Optional.empty()); when(account.getUnidentifiedAccessKey()).thenReturn(Optional.of("1234".getBytes())); @@ -43,7 +43,7 @@ public class OptionalAccessTest { @Test public void testUnidentifiedBadTargetDevice() { Account account = mock(Account.class); - when(account.isActive()).thenReturn(true); + when(account.isEnabled()).thenReturn(true); when(account.getDevice(eq(10))).thenReturn(Optional.empty()); when(account.getUnidentifiedAccessKey()).thenReturn(Optional.of("1234".getBytes())); @@ -58,7 +58,7 @@ public class OptionalAccessTest { @Test public void testUnidentifiedBadCode() { Account account = mock(Account.class); - when(account.isActive()).thenReturn(true); + when(account.isEnabled()).thenReturn(true); when(account.getUnidentifiedAccessKey()).thenReturn(Optional.of("1234".getBytes())); try { @@ -72,7 +72,7 @@ public class OptionalAccessTest { @Test public void testIdentifiedMissingTarget() { Account account = mock(Account.class); - when(account.isActive()).thenReturn(true); + when(account.isEnabled()).thenReturn(true); try { OptionalAccess.verify(Optional.of(account), Optional.empty(), Optional.empty()); @@ -86,7 +86,7 @@ public class OptionalAccessTest { public void testUnsolicitedBadTarget() { Account account = mock(Account.class); when(account.isUnrestrictedUnidentifiedAccess()).thenReturn(false); - when(account.isActive()).thenReturn(true); + when(account.isEnabled()).thenReturn(true); try { OptionalAccess.verify(Optional.empty(), Optional.empty(), Optional.of(account)); @@ -101,7 +101,7 @@ public class OptionalAccessTest { Account account = mock(Account.class); Anonymous random = mock(Anonymous.class); when(account.isUnrestrictedUnidentifiedAccess()).thenReturn(true); - when(account.isActive()).thenReturn(true); + when(account.isEnabled()).thenReturn(true); OptionalAccess.verify(Optional.empty(), Optional.of(random), Optional.of(account)); } @@ -109,7 +109,7 @@ public class OptionalAccessTest { public void testUnidentifiedGoodTarget() { Account account = mock(Account.class); when(account.getUnidentifiedAccessKey()).thenReturn(Optional.of("1234".getBytes())); - when(account.isActive()).thenReturn(true); + when(account.isEnabled()).thenReturn(true); OptionalAccess.verify(Optional.empty(), Optional.of(new Anonymous(Base64.encodeBytes("1234".getBytes()))), Optional.of(account)); } @@ -117,7 +117,7 @@ public class OptionalAccessTest { public void testUnidentifiedInactive() { Account account = mock(Account.class); when(account.getUnidentifiedAccessKey()).thenReturn(Optional.of("1234".getBytes())); - when(account.isActive()).thenReturn(false); + when(account.isEnabled()).thenReturn(false); try { OptionalAccess.verify(Optional.empty(), Optional.of(new Anonymous(Base64.encodeBytes("1234".getBytes()))), Optional.of(account)); @@ -131,7 +131,7 @@ public class OptionalAccessTest { public void testIdentifiedGoodTarget() { Account source = mock(Account.class); Account target = mock(Account.class); - when(target.isActive()).thenReturn(true); + when(target.isEnabled()).thenReturn(true); OptionalAccess.verify(Optional.of(source), Optional.empty(), Optional.of(target));; } } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java index 41e2a9255..6d137e657 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java @@ -1,14 +1,18 @@ package org.whispersystems.textsecuregcm.tests.controllers; +import com.google.common.collect.ImmutableSet; import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccount; import org.whispersystems.textsecuregcm.auth.StoredVerificationCode; import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator; import org.whispersystems.textsecuregcm.controllers.AccountController; import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException; import org.whispersystems.textsecuregcm.entities.AccountAttributes; +import org.whispersystems.textsecuregcm.entities.ApnRegistrationId; +import org.whispersystems.textsecuregcm.entities.GcmRegistrationId; import org.whispersystems.textsecuregcm.entities.RegistrationLock; import org.whispersystems.textsecuregcm.entities.RegistrationLockFailure; import org.whispersystems.textsecuregcm.limits.RateLimiter; @@ -36,7 +40,7 @@ import java.util.HashMap; import java.util.Optional; import java.util.concurrent.TimeUnit; -import io.dropwizard.auth.AuthValueFactoryProvider; +import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider; import io.dropwizard.testing.junit.ResourceTestRule; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Matchers.anyString; @@ -80,7 +84,7 @@ public class AccountControllerTest { @Rule public final ResourceTestRule resources = ResourceTestRule.builder() .addProvider(AuthHelper.getAuthFilter()) - .addProvider(new AuthValueFactoryProvider.Binder<>(Account.class)) + .addProvider(new PolymorphicAuthValueFactoryProvider.Binder<>(ImmutableSet.of(Account.class, DisabledPermittedAccount.class))) .addProvider(new RateLimitExceededExceptionMapper()) .setMapper(SystemMapper.getMapper()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) @@ -505,6 +509,51 @@ public class AccountControllerTest { verify(AuthHelper.VALID_ACCOUNT, never()).setPin(anyString()); } + @Test + public void testSetPinDisabled() throws Exception { + Response response = + resources.getJerseyTest() + .target("/v1/accounts/pin/") + .request() + .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.DISABLED_NUMBER, AuthHelper.DISABLED_PASSWORD)) + .put(Entity.json(new RegistrationLock("31337"))); + + assertThat(response.getStatus()).isEqualTo(401); + + verify(AuthHelper.VALID_ACCOUNT, never()).setPin(anyString()); + } + + + @Test + public void testSetGcmId() throws Exception { + Response response = + resources.getJerseyTest() + .target("/v1/accounts/gcm/") + .request() + .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.DISABLED_NUMBER, AuthHelper.DISABLED_PASSWORD)) + .put(Entity.json(new GcmRegistrationId("c00lz0rz"))); + + assertThat(response.getStatus()).isEqualTo(204); + + verify(AuthHelper.DISABLED_DEVICE, times(1)).setGcmId(eq("c00lz0rz")); + verify(accountsManager, times(1)).update(eq(AuthHelper.DISABLED_ACCOUNT)); + } + + @Test + public void testSetApnId() throws Exception { + Response response = + resources.getJerseyTest() + .target("/v1/accounts/apn/") + .request() + .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.DISABLED_NUMBER, AuthHelper.DISABLED_PASSWORD)) + .put(Entity.json(new ApnRegistrationId("first", "second"))); + + assertThat(response.getStatus()).isEqualTo(204); + + verify(AuthHelper.DISABLED_DEVICE, times(1)).setApnId(eq("first")); + verify(AuthHelper.DISABLED_DEVICE, times(1)).setVoipApnId(eq("second")); + verify(accountsManager, times(1)).update(eq(AuthHelper.DISABLED_ACCOUNT)); + } } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AttachmentControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AttachmentControllerTest.java index ee396216f..841018a69 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AttachmentControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AttachmentControllerTest.java @@ -1,8 +1,10 @@ package org.whispersystems.textsecuregcm.tests.controllers; +import com.google.common.collect.ImmutableSet; import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; import org.junit.ClassRule; import org.junit.Test; +import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccount; import org.whispersystems.textsecuregcm.controllers.AttachmentControllerV1; import org.whispersystems.textsecuregcm.controllers.AttachmentControllerV2; import org.whispersystems.textsecuregcm.entities.AttachmentDescriptorV1; @@ -14,9 +16,10 @@ import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.tests.util.AuthHelper; import org.whispersystems.textsecuregcm.util.SystemMapper; +import javax.ws.rs.core.Response; import java.net.MalformedURLException; -import io.dropwizard.auth.AuthValueFactoryProvider; +import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider; import io.dropwizard.testing.junit.ResourceTestRule; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -34,7 +37,7 @@ public class AttachmentControllerTest { @ClassRule public static final ResourceTestRule resources = ResourceTestRule.builder() .addProvider(AuthHelper.getAuthFilter()) - .addProvider(new AuthValueFactoryProvider.Binder<>(Account.class)) + .addProvider(new PolymorphicAuthValueFactoryProvider.Binder<>(ImmutableSet.of(Account.class, DisabledPermittedAccount.class))) .setMapper(SystemMapper.getMapper()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addResource(new AttachmentControllerV1(rateLimiters, "accessKey", "accessSecret", "attachment-bucket")) @@ -67,6 +70,18 @@ public class AttachmentControllerTest { assertThat(descriptor.getSignature()).isNotBlank(); } + @Test + public void testV2FormDisabled() { + Response response = resources.getJerseyTest() + .target("/v2/attachments/form/upload") + .request() + .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.DISABLED_NUMBER, AuthHelper.DISABLED_PASSWORD)) + .get(); + + assertThat(response.getStatus()).isEqualTo(401); + } + + @Test public void testAcceleratedPut() { AttachmentDescriptorV1 descriptor = resources.getJerseyTest() diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/CertificateControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/CertificateControllerTest.java index c77aa8dd4..e786fb8a5 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/CertificateControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/CertificateControllerTest.java @@ -1,9 +1,11 @@ package org.whispersystems.textsecuregcm.tests.controllers; +import com.google.common.collect.ImmutableSet; import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; import org.junit.ClassRule; import org.junit.Test; import org.whispersystems.textsecuregcm.auth.CertificateGenerator; +import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccount; import org.whispersystems.textsecuregcm.auth.OptionalAccess; import org.whispersystems.textsecuregcm.controllers.CertificateController; import org.whispersystems.textsecuregcm.crypto.Curve; @@ -19,7 +21,7 @@ import javax.ws.rs.core.Response; import java.io.IOException; import java.util.Arrays; -import io.dropwizard.auth.AuthValueFactoryProvider; +import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider; import io.dropwizard.testing.junit.ResourceTestRule; import static junit.framework.TestCase.assertTrue; import static org.junit.Assert.assertEquals; @@ -46,7 +48,7 @@ public class CertificateControllerTest { @ClassRule public static final ResourceTestRule resources = ResourceTestRule.builder() .addProvider(AuthHelper.getAuthFilter()) - .addProvider(new AuthValueFactoryProvider.Binder<>(Account.class)) + .addProvider(new PolymorphicAuthValueFactoryProvider.Binder<>(ImmutableSet.of(Account.class, DisabledPermittedAccount.class))) .setMapper(SystemMapper.getMapper()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addResource(new CertificateController(certificateGenerator)) @@ -110,4 +112,16 @@ public class CertificateControllerTest { assertEquals(response.getStatus(), 401); } + @Test + public void testDisabledAuthentication() throws Exception { + Response response = resources.getJerseyTest() + .target("/v1/certificate/delivery") + .request() + .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.DISABLED_NUMBER, AuthHelper.DISABLED_PASSWORD)) + .get(); + + assertEquals(response.getStatus(), 401); + } + + } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DeviceControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DeviceControllerTest.java index ce5bf7d30..63a9e6682 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DeviceControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DeviceControllerTest.java @@ -16,10 +16,12 @@ */ package org.whispersystems.textsecuregcm.tests.controllers; +import com.google.common.collect.ImmutableSet; import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccount; import org.whispersystems.textsecuregcm.auth.StoredVerificationCode; import org.whispersystems.textsecuregcm.controllers.DeviceController; import org.whispersystems.textsecuregcm.entities.AccountAttributes; @@ -28,7 +30,11 @@ import org.whispersystems.textsecuregcm.limits.RateLimiter; import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.mappers.DeviceLimitExceededExceptionMapper; import org.whispersystems.textsecuregcm.sqs.DirectoryQueue; -import org.whispersystems.textsecuregcm.storage.*; +import org.whispersystems.textsecuregcm.storage.Account; +import org.whispersystems.textsecuregcm.storage.AccountsManager; +import org.whispersystems.textsecuregcm.storage.Device; +import org.whispersystems.textsecuregcm.storage.MessagesManager; +import org.whispersystems.textsecuregcm.storage.PendingDevicesManager; import org.whispersystems.textsecuregcm.tests.util.AuthHelper; import org.whispersystems.textsecuregcm.util.VerificationCode; @@ -41,7 +47,7 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.TimeUnit; -import io.dropwizard.auth.AuthValueFactoryProvider; +import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider; import io.dropwizard.testing.junit.ResourceTestRule; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; @@ -83,7 +89,7 @@ public class DeviceControllerTest { @Rule public final ResourceTestRule resources = ResourceTestRule.builder() .addProvider(AuthHelper.getAuthFilter()) - .addProvider(new AuthValueFactoryProvider.Binder<>(Account.class)) + .addProvider(new PolymorphicAuthValueFactoryProvider.Binder<>(ImmutableSet.of(Account.class, DisabledPermittedAccount.class))) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addProvider(new DeviceLimitExceededExceptionMapper()) .addResource(new DumbVerificationDeviceController(pendingDevicesManager, @@ -109,7 +115,7 @@ public class DeviceControllerTest { when(account.getNumber()).thenReturn(AuthHelper.VALID_NUMBER); // when(maxedAccount.getActiveDeviceCount()).thenReturn(6); when(account.getAuthenticatedDevice()).thenReturn(Optional.of(masterDevice)); - when(account.isActive()).thenReturn(false); + when(account.isEnabled()).thenReturn(false); when(pendingDevicesManager.getCodeForNumber(AuthHelper.VALID_NUMBER)).thenReturn(Optional.of(new StoredVerificationCode("5678901", System.currentTimeMillis()))); when(pendingDevicesManager.getCodeForNumber(AuthHelper.VALID_NUMBER_TWO)).thenReturn(Optional.of(new StoredVerificationCode("1112223", System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(31)))); @@ -141,6 +147,17 @@ public class DeviceControllerTest { verify(messagesManager).clear(eq(AuthHelper.VALID_NUMBER), eq(42L)); } + @Test + public void disabledDeviceRegisterTest() throws Exception { + Response response = resources.getJerseyTest() + .target("/v1/devices/provisioning/code") + .request() + .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.DISABLED_NUMBER, AuthHelper.DISABLED_PASSWORD)) + .get(); + + assertThat(response.getStatus()).isEqualTo(401); + } + @Test public void invalidDeviceRegisterTest() throws Exception { VerificationCode deviceCode = resources.getJerseyTest() diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DirectoryControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DirectoryControllerTest.java index eed9dd22b..009a418a5 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DirectoryControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DirectoryControllerTest.java @@ -1,5 +1,6 @@ package org.whispersystems.textsecuregcm.tests.controllers; +import com.google.common.collect.ImmutableSet; import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; import org.junit.Before; import org.junit.Rule; @@ -8,6 +9,7 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.whispersystems.textsecuregcm.auth.DirectoryCredentials; import org.whispersystems.textsecuregcm.auth.DirectoryCredentialsGenerator; +import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccount; import org.whispersystems.textsecuregcm.controllers.DirectoryController; import org.whispersystems.textsecuregcm.entities.ClientContactTokens; import org.whispersystems.textsecuregcm.entities.DirectoryFeedbackRequest; @@ -27,7 +29,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Optional; -import io.dropwizard.auth.AuthValueFactoryProvider; +import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider; import io.dropwizard.testing.junit.ResourceTestRule; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.anyListOf; @@ -47,7 +49,7 @@ public class DirectoryControllerTest { @Rule public final ResourceTestRule resources = ResourceTestRule.builder() .addProvider(AuthHelper.getAuthFilter()) - .addProvider(new AuthValueFactoryProvider.Binder<>(Account.class)) + .addProvider(new PolymorphicAuthValueFactoryProvider.Binder<>(ImmutableSet.of(Account.class, DisabledPermittedAccount.class))) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addResource(new DirectoryController(rateLimiters, directoryManager, @@ -148,6 +150,18 @@ public class DirectoryControllerTest { assertThat(token.getPassword()).isEqualTo(validCredentials.getPassword()); } + @Test + public void testDisabledGetAuthToken() { + Response response = + resources.getJerseyTest() + .target("/v1/directory/auth") + .request() + .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.DISABLED_NUMBER, AuthHelper.DISABLED_PASSWORD)) + .get(); + assertThat(response.getStatus()).isEqualTo(401); + } + + @Test public void testContactIntersection() throws Exception { List tokens = new LinkedList() {{ diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/KeyControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/KeyControllerTest.java index e4a07c175..5319c1b78 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/KeyControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/KeyControllerTest.java @@ -1,10 +1,12 @@ package org.whispersystems.textsecuregcm.tests.controllers; +import com.google.common.collect.ImmutableSet; import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; +import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccount; import org.whispersystems.textsecuregcm.auth.OptionalAccess; import org.whispersystems.textsecuregcm.controllers.KeysController; import org.whispersystems.textsecuregcm.entities.PreKey; @@ -31,7 +33,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; -import io.dropwizard.auth.AuthValueFactoryProvider; +import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider; import io.dropwizard.testing.junit.ResourceTestRule; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.*; @@ -67,7 +69,7 @@ public class KeyControllerTest { @Rule public final ResourceTestRule resources = ResourceTestRule.builder() .addProvider(AuthHelper.getAuthFilter()) - .addProvider(new AuthValueFactoryProvider.Binder<>(Account.class)) + .addProvider(new PolymorphicAuthValueFactoryProvider.Binder<>(ImmutableSet.of(Account.class, DisabledPermittedAccount.class))) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addResource(new KeysController(rateLimiters, keys, accounts, directoryQueue)) .build(); @@ -90,10 +92,10 @@ public class KeyControllerTest { when(sampleDevice2.getRegistrationId()).thenReturn(SAMPLE_REGISTRATION_ID2); when(sampleDevice3.getRegistrationId()).thenReturn(SAMPLE_REGISTRATION_ID2); when(sampleDevice4.getRegistrationId()).thenReturn(SAMPLE_REGISTRATION_ID4); - when(sampleDevice.isActive()).thenReturn(true); - when(sampleDevice2.isActive()).thenReturn(true); - when(sampleDevice3.isActive()).thenReturn(false); - when(sampleDevice4.isActive()).thenReturn(true); + when(sampleDevice.isEnabled()).thenReturn(true); + when(sampleDevice2.isEnabled()).thenReturn(true); + when(sampleDevice3.isEnabled()).thenReturn(false); + when(sampleDevice4.isEnabled()).thenReturn(true); when(sampleDevice.getSignedPreKey()).thenReturn(SAMPLE_SIGNED_KEY); when(sampleDevice2.getSignedPreKey()).thenReturn(SAMPLE_SIGNED_KEY2); when(sampleDevice3.getSignedPreKey()).thenReturn(SAMPLE_SIGNED_KEY3); @@ -109,7 +111,7 @@ public class KeyControllerTest { when(existsAccount.getDevice(4L)).thenReturn(Optional.of(sampleDevice4)); when(existsAccount.getDevice(22L)).thenReturn(Optional.empty()); when(existsAccount.getDevices()).thenReturn(allDevices); - when(existsAccount.isActive()).thenReturn(true); + when(existsAccount.isEnabled()).thenReturn(true); when(existsAccount.getIdentityKey()).thenReturn("existsidentitykey"); when(existsAccount.getNumber()).thenReturn(EXISTS_NUMBER); when(existsAccount.getUnidentifiedAccessKey()).thenReturn(Optional.of("1337".getBytes())); @@ -180,6 +182,21 @@ public class KeyControllerTest { verify(accounts).update(eq(AuthHelper.VALID_ACCOUNT)); } + @Test + public void disabledPutSignedPreKeyV2() throws Exception { + SignedPreKey test = new SignedPreKey(9999, "fooozzz", "baaarzzz"); + Response response = resources.getJerseyTest() + .target("/v2/keys/signed") + .request() + .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.DISABLED_NUMBER, AuthHelper.DISABLED_PASSWORD)) + .put(Entity.entity(test, MediaType.APPLICATION_JSON_TYPE)); + + assertThat(response.getStatus()).isEqualTo(204); + + verify(AuthHelper.DISABLED_DEVICE).setSignedPreKey(eq(test)); + verify(accounts).update(eq(AuthHelper.DISABLED_ACCOUNT)); + } + @Test public void validSingleRequestTestV2() throws Exception { PreKeyResponse result = resources.getJerseyTest() @@ -367,5 +384,39 @@ public class KeyControllerTest { verify(accounts).update(AuthHelper.VALID_ACCOUNT); } + @Test + public void disabledPutKeysTestV2() throws Exception { + final PreKey preKey = new PreKey(31337, "foobar"); + final SignedPreKey signedPreKey = new SignedPreKey(31338, "foobaz", "myvalidsig"); + final String identityKey = "barbar"; + + List preKeys = new LinkedList() {{ + add(preKey); + }}; + + PreKeyState preKeyState = new PreKeyState(identityKey, signedPreKey, preKeys); + + Response response = + resources.getJerseyTest() + .target("/v2/keys") + .request() + .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.DISABLED_NUMBER, AuthHelper.DISABLED_PASSWORD)) + .put(Entity.entity(preKeyState, MediaType.APPLICATION_JSON_TYPE)); + + assertThat(response.getStatus()).isEqualTo(204); + + ArgumentCaptor listCaptor = ArgumentCaptor.forClass(List.class); + verify(keys).store(eq(AuthHelper.DISABLED_NUMBER), eq(1L), listCaptor.capture()); + + List capturedList = listCaptor.getValue(); + assertThat(capturedList.size()).isEqualTo(1); + assertThat(capturedList.get(0).getKeyId()).isEqualTo(31337); + assertThat(capturedList.get(0).getPublicKey()).isEqualTo("foobar"); + + verify(AuthHelper.DISABLED_ACCOUNT).setIdentityKey(eq("barbar")); + verify(AuthHelper.DISABLED_DEVICE).setSignedPreKey(eq(signedPreKey)); + verify(accounts).update(AuthHelper.DISABLED_ACCOUNT); + } + } \ No newline at end of file diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/MessageControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/MessageControllerTest.java index d0b4bb664..42c1ea768 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/MessageControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/MessageControllerTest.java @@ -1,11 +1,13 @@ package org.whispersystems.textsecuregcm.tests.controllers; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableSet; import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; +import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccount; import org.whispersystems.textsecuregcm.auth.OptionalAccess; import org.whispersystems.textsecuregcm.controllers.MessageController; import org.whispersystems.textsecuregcm.entities.IncomingMessageList; @@ -38,7 +40,7 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.TimeUnit; -import io.dropwizard.auth.AuthValueFactoryProvider; +import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider; import io.dropwizard.testing.junit.ResourceTestRule; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; @@ -68,7 +70,7 @@ public class MessageControllerTest { @Rule public final ResourceTestRule resources = ResourceTestRule.builder() .addProvider(AuthHelper.getAuthFilter()) - .addProvider(new AuthValueFactoryProvider.Binder<>(Account.class)) + .addProvider(new PolymorphicAuthValueFactoryProvider.Binder<>(ImmutableSet.of(Account.class, DisabledPermittedAccount.class))) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addResource(new MessageController(rateLimiters, pushSender, receiptSender, accountsManager, messagesManager, apnFallbackManager)) @@ -95,7 +97,20 @@ public class MessageControllerTest { when(rateLimiters.getMessagesLimiter()).thenReturn(rateLimiter); } - + + @Test + public synchronized void testSendFromDisabledAccount() throws Exception { + Response response = + resources.getJerseyTest() + .target(String.format("/v1/messages/%s", SINGLE_DEVICE_RECIPIENT)) + .request() + .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.DISABLED_NUMBER, AuthHelper.DISABLED_PASSWORD)) + .put(Entity.entity(mapper.readValue(jsonFixture("fixtures/current_message_single_device.json"), IncomingMessageList.class), + MediaType.APPLICATION_JSON_TYPE)); + + assertThat("Unauthorized response", response.getStatus(), is(equalTo(401))); + } + @Test public synchronized void testSingleDeviceCurrent() throws Exception { Response response = diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ProfileControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ProfileControllerTest.java index fa427abc4..890215ae5 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ProfileControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ProfileControllerTest.java @@ -1,9 +1,11 @@ package org.whispersystems.textsecuregcm.tests.controllers; +import com.google.common.collect.ImmutableSet; import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; import org.junit.Before; import org.junit.ClassRule; import org.junit.Test; +import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccount; import org.whispersystems.textsecuregcm.configuration.ProfilesConfiguration; import org.whispersystems.textsecuregcm.controllers.ProfileController; import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException; @@ -18,7 +20,7 @@ import org.whispersystems.textsecuregcm.util.SystemMapper; import javax.ws.rs.core.Response; import java.util.Optional; -import io.dropwizard.auth.AuthValueFactoryProvider; +import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider; import io.dropwizard.testing.junit.ResourceTestRule; import static org.assertj.core.api.Java6Assertions.assertThat; import static org.mockito.Mockito.*; @@ -40,7 +42,7 @@ public class ProfileControllerTest { @ClassRule public static final ResourceTestRule resources = ResourceTestRule.builder() .addProvider(AuthHelper.getAuthFilter()) - .addProvider(new AuthValueFactoryProvider.Binder<>(Account.class)) + .addProvider(new PolymorphicAuthValueFactoryProvider.Binder<>(ImmutableSet.of(Account.class, DisabledPermittedAccount.class))) .setMapper(SystemMapper.getMapper()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addResource(new ProfileController(rateLimiters, @@ -58,7 +60,7 @@ public class ProfileControllerTest { when(profileAccount.getProfileName()).thenReturn("baz"); when(profileAccount.getAvatar()).thenReturn("profiles/bang"); when(profileAccount.getAvatarDigest()).thenReturn("buh"); - when(profileAccount.isActive()).thenReturn(true); + when(profileAccount.isEnabled()).thenReturn(true); when(accountsManager.get(AuthHelper.VALID_NUMBER_TWO)).thenReturn(Optional.of(profileAccount)); } @@ -91,4 +93,15 @@ public class ProfileControllerTest { assertThat(response.getStatus()).isEqualTo(401); } + @Test + public void testProfileGetDisabled() throws Exception { + Response response = resources.getJerseyTest() + .target("/v1/profile/" + AuthHelper.VALID_NUMBER_TWO) + .request() + .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.DISABLED_NUMBER, AuthHelper.DISABLED_PASSWORD)) + .get(); + + assertThat(response.getStatus()).isEqualTo(401); + } + } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/TransparentDataControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/TransparentDataControllerTest.java index 034d19c96..4f8941616 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/TransparentDataControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/TransparentDataControllerTest.java @@ -1,11 +1,13 @@ package org.whispersystems.textsecuregcm.tests.controllers; import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.collect.ImmutableSet; import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; import org.hamcrest.MatcherAssert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccount; import org.whispersystems.textsecuregcm.controllers.TransparentDataController; import org.whispersystems.textsecuregcm.entities.SignedPreKey; import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper; @@ -24,6 +26,7 @@ import java.util.Map; import java.util.Optional; import io.dropwizard.auth.AuthValueFactoryProvider; +import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider; import io.dropwizard.testing.junit.ResourceTestRule; import static junit.framework.TestCase.*; import static org.hamcrest.CoreMatchers.equalTo; @@ -42,7 +45,7 @@ public class TransparentDataControllerTest { @Rule public final ResourceTestRule resources = ResourceTestRule.builder() .addProvider(AuthHelper.getAuthFilter()) - .addProvider(new AuthValueFactoryProvider.Binder<>(Account.class)) + .addProvider(new PolymorphicAuthValueFactoryProvider.Binder<>(ImmutableSet.of(Account.class, DisabledPermittedAccount.class))) .addProvider(new RateLimitExceededExceptionMapper()) .setMapper(SystemMapper.getMapper()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/VoiceVerificationControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/VoiceVerificationControllerTest.java index 94229f017..9f09d7dfd 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/VoiceVerificationControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/VoiceVerificationControllerTest.java @@ -1,8 +1,10 @@ package org.whispersystems.textsecuregcm.tests.controllers; +import com.google.common.collect.ImmutableSet; import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; import org.junit.Rule; import org.junit.Test; +import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccount; import org.whispersystems.textsecuregcm.controllers.VoiceVerificationController; import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper; import org.whispersystems.textsecuregcm.storage.Account; @@ -11,10 +13,9 @@ import org.whispersystems.textsecuregcm.util.SystemMapper; import javax.ws.rs.core.Response; import java.util.Arrays; -import java.util.Collections; import java.util.HashSet; -import io.dropwizard.auth.AuthValueFactoryProvider; +import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider; import io.dropwizard.testing.FixtureHelpers; import io.dropwizard.testing.junit.ResourceTestRule; import static org.assertj.core.api.Assertions.assertThat; @@ -24,7 +25,7 @@ public class VoiceVerificationControllerTest { @Rule public final ResourceTestRule resources = ResourceTestRule.builder() .addProvider(AuthHelper.getAuthFilter()) - .addProvider(new AuthValueFactoryProvider.Binder<>(Account.class)) + .addProvider(new PolymorphicAuthValueFactoryProvider.Binder<>(ImmutableSet.of(Account.class, DisabledPermittedAccount.class))) .addProvider(new RateLimitExceededExceptionMapper()) .setMapper(SystemMapper.getMapper()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountCleanerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountCleanerTest.java index d109a4d7f..2bd788a58 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountCleanerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountCleanerTest.java @@ -67,43 +67,43 @@ public class AccountCleanerTest { @Before public void setup() { - when(activeUnexpiredDevice.isActive()).thenReturn(true); + when(activeUnexpiredDevice.isEnabled()).thenReturn(true); when(activeUnexpiredAccount.getLastSeen()).thenReturn(Long.MAX_VALUE); when(activeUnexpiredAccount.getMasterDevice()).thenReturn(Optional.of(activeUnexpiredDevice)); when(activeExpiredAccount.getNumber()).thenReturn(AuthHelper.VALID_NUMBER); - when(activeExpiredDevice.isActive()).thenReturn(true); + when(activeExpiredDevice.isEnabled()).thenReturn(true); when(activeExpiredAccount.getLastSeen()).thenReturn(0L); when(activeExpiredAccount.getMasterDevice()).thenReturn(Optional.of(activeExpiredDevice)); - when(inactiveUnexpiredDevice.isActive()).thenReturn(false); + when(inactiveUnexpiredDevice.isEnabled()).thenReturn(false); when(inactiveUnexpiredAccount.getLastSeen()).thenReturn(Long.MAX_VALUE); when(inactiveUnexpiredAccount.getMasterDevice()).thenReturn(Optional.of(inactiveUnexpiredDevice)); - when(inactiveExpiredDevice.isActive()).thenReturn(false); + when(inactiveExpiredDevice.isEnabled()).thenReturn(false); when(inactiveExpiredAccount.getLastSeen()).thenReturn(0L); when(inactiveExpiredAccount.getMasterDevice()).thenReturn(Optional.of(inactiveExpiredDevice)); this.nowMs = System.currentTimeMillis(); when(oldMasterDevice.getLastSeen()).thenReturn(nowMs - TimeUnit.DAYS.toMillis(366)); - when(oldMasterDevice.isActive()).thenReturn(true); + when(oldMasterDevice.isEnabled()).thenReturn(true); when(oldMasterDevice.getId()).thenReturn(Device.MASTER_ID); when(recentMasterDevice.getLastSeen()).thenReturn(nowMs - TimeUnit.DAYS.toMillis(1)); - when(recentMasterDevice.isActive()).thenReturn(true); + when(recentMasterDevice.isEnabled()).thenReturn(true); when(recentMasterDevice.getId()).thenReturn(Device.MASTER_ID); when(agingSecondaryDevice.getLastSeen()).thenReturn(nowMs - TimeUnit.DAYS.toMillis(31)); - when(agingSecondaryDevice.isActive()).thenReturn(false); + when(agingSecondaryDevice.isEnabled()).thenReturn(false); when(agingSecondaryDevice.getId()).thenReturn(2L); when(recentSecondaryDevice.getLastSeen()).thenReturn(nowMs - TimeUnit.DAYS.toMillis(1)); - when(recentSecondaryDevice.isActive()).thenReturn(true); + when(recentSecondaryDevice.isEnabled()).thenReturn(true); when(recentSecondaryDevice.getId()).thenReturn(2L); when(oldSecondaryDevice.getLastSeen()).thenReturn(nowMs - TimeUnit.DAYS.toMillis(366)); - when(oldSecondaryDevice.isActive()).thenReturn(false); + when(oldSecondaryDevice.isEnabled()).thenReturn(false); when(oldSecondaryDevice.getId()).thenReturn(2L); } @@ -114,9 +114,9 @@ public class AccountCleanerTest { accountCleaner.onCrawlChunk(Optional.empty(), Arrays.asList(activeUnexpiredAccount, inactiveUnexpiredAccount, inactiveExpiredAccount)); accountCleaner.onCrawlEnd(Optional.empty()); - verify(activeUnexpiredDevice, atLeastOnce()).isActive(); - verify(inactiveUnexpiredDevice, atLeastOnce()).isActive(); - verify(inactiveExpiredDevice, atLeastOnce()).isActive(); + verify(activeUnexpiredDevice, atLeastOnce()).isEnabled(); + verify(inactiveUnexpiredDevice, atLeastOnce()).isEnabled(); + verify(inactiveExpiredDevice, atLeastOnce()).isEnabled(); verifyNoMoreInteractions(activeUnexpiredDevice); verifyNoMoreInteractions(activeExpiredDevice); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountTest.java index 66e17bd87..e856f08a8 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountTest.java @@ -25,23 +25,23 @@ public class AccountTest { @Before public void setup() { when(oldMasterDevice.getLastSeen()).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(366)); - when(oldMasterDevice.isActive()).thenReturn(true); + when(oldMasterDevice.isEnabled()).thenReturn(true); when(oldMasterDevice.getId()).thenReturn(Device.MASTER_ID); when(recentMasterDevice.getLastSeen()).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1)); - when(recentMasterDevice.isActive()).thenReturn(true); + when(recentMasterDevice.isEnabled()).thenReturn(true); when(recentMasterDevice.getId()).thenReturn(Device.MASTER_ID); when(agingSecondaryDevice.getLastSeen()).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(31)); - when(agingSecondaryDevice.isActive()).thenReturn(false); + when(agingSecondaryDevice.isEnabled()).thenReturn(false); when(agingSecondaryDevice.getId()).thenReturn(2L); when(recentSecondaryDevice.getLastSeen()).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1)); - when(recentSecondaryDevice.isActive()).thenReturn(true); + when(recentSecondaryDevice.isEnabled()).thenReturn(true); when(recentSecondaryDevice.getId()).thenReturn(2L); when(oldSecondaryDevice.getLastSeen()).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(366)); - when(oldSecondaryDevice.isActive()).thenReturn(false); + when(oldSecondaryDevice.isEnabled()).thenReturn(false); when(oldSecondaryDevice.getId()).thenReturn(2L); } @@ -52,21 +52,21 @@ public class AccountTest { add(recentSecondaryDevice); }}, "1234".getBytes()); - assertTrue(recentAccount.isActive()); + assertTrue(recentAccount.isEnabled()); Account oldSecondaryAccount = new Account("+14152222222", new HashSet() {{ add(recentMasterDevice); add(agingSecondaryDevice); }}, "1234".getBytes()); - assertTrue(oldSecondaryAccount.isActive()); + assertTrue(oldSecondaryAccount.isEnabled()); Account agingPrimaryAccount = new Account("+14152222222", new HashSet() {{ add(oldMasterDevice); add(agingSecondaryDevice); }}, "1234".getBytes()); - assertTrue(agingPrimaryAccount.isActive()); + assertTrue(agingPrimaryAccount.isEnabled()); } @Test @@ -76,7 +76,7 @@ public class AccountTest { add(oldSecondaryDevice); }}, "1234".getBytes()); - assertFalse(oldPrimaryAccount.isActive()); + assertFalse(oldPrimaryAccount.isEnabled()); } } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/DirectoryReconcilerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/DirectoryReconcilerTest.java index 3e9337b03..3c7d0dc4a 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/DirectoryReconcilerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/DirectoryReconcilerTest.java @@ -57,9 +57,9 @@ public class DirectoryReconcilerTest { @Before public void setup() { when(activeAccount.getNumber()).thenReturn(VALID_NUMBER); - when(activeAccount.isActive()).thenReturn(true); + when(activeAccount.isEnabled()).thenReturn(true); when(inactiveAccount.getNumber()).thenReturn(INACTIVE_NUMBER); - when(inactiveAccount.isActive()).thenReturn(false); + when(inactiveAccount.isEnabled()).thenReturn(false); when(directoryManager.startBatchOperation()).thenReturn(batchOperationHandle); } @@ -69,9 +69,9 @@ public class DirectoryReconcilerTest { directoryReconciler.onCrawlChunk(Optional.of(VALID_NUMBER), Arrays.asList(activeAccount, inactiveAccount)); verify(activeAccount, times(2)).getNumber(); - verify(activeAccount, times(2)).isActive(); + verify(activeAccount, times(2)).isEnabled(); verify(inactiveAccount, times(2)).getNumber(); - verify(inactiveAccount, times(2)).isActive(); + verify(inactiveAccount, times(2)).isEnabled(); ArgumentCaptor request = ArgumentCaptor.forClass(DirectoryReconciliationRequest.class); verify(reconciliationClient, times(1)).sendChunk(request.capture()); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/AuthHelper.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/AuthHelper.java index df9cda79e..644dc2efb 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/AuthHelper.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/AuthHelper.java @@ -1,7 +1,10 @@ package org.whispersystems.textsecuregcm.tests.util; +import com.google.common.collect.ImmutableMap; import org.whispersystems.textsecuregcm.auth.AccountAuthenticator; import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials; +import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccount; +import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccountAuthenticator; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.Device; @@ -9,9 +12,10 @@ import org.whispersystems.textsecuregcm.util.Base64; import java.util.Optional; -import io.dropwizard.auth.AuthDynamicFeature; +import io.dropwizard.auth.AuthFilter; +import io.dropwizard.auth.PolymorphicAuthDynamicFeature; import io.dropwizard.auth.basic.BasicCredentialAuthFilter; -import static org.mockito.Matchers.anyLong; +import io.dropwizard.auth.basic.BasicCredentials; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -26,43 +30,76 @@ public class AuthHelper { public static final String INVVALID_NUMBER = "+14151111111"; public static final String INVALID_PASSWORD = "bar"; + public static final String DISABLED_NUMBER = "+78888888"; + public static final String DISABLED_PASSWORD = "poof"; + public static final String VALID_IDENTITY = "BcxxDU9FGMda70E7+Uvm7pnQcEdXQ64aJCpPUeRSfcFo"; - public static AccountsManager ACCOUNTS_MANAGER = mock(AccountsManager.class ); - public static Account VALID_ACCOUNT = mock(Account.class ); - public static Account VALID_ACCOUNT_TWO = mock(Account.class); - public static Device VALID_DEVICE = mock(Device.class ); - public static Device VALID_DEVICE_TWO = mock(Device.class); - private static AuthenticationCredentials VALID_CREDENTIALS = mock(AuthenticationCredentials.class); - private static AuthenticationCredentials VALID_CREDENTIALS_TWO = mock(AuthenticationCredentials.class); + public static AccountsManager ACCOUNTS_MANAGER = mock(AccountsManager.class); + public static Account VALID_ACCOUNT = mock(Account.class ); + public static Account VALID_ACCOUNT_TWO = mock(Account.class ); + public static Account DISABLED_ACCOUNT = mock(Account.class ); - public static AuthDynamicFeature getAuthFilter() { + public static Device VALID_DEVICE = mock(Device.class); + public static Device VALID_DEVICE_TWO = mock(Device.class); + public static Device DISABLED_DEVICE = mock(Device.class); + + private static AuthenticationCredentials VALID_CREDENTIALS = mock(AuthenticationCredentials.class); + private static AuthenticationCredentials VALID_CREDENTIALS_TWO = mock(AuthenticationCredentials.class); + private static AuthenticationCredentials DISABLED_CREDENTIALS = mock(AuthenticationCredentials.class); + + public static PolymorphicAuthDynamicFeature getAuthFilter() { when(VALID_CREDENTIALS.verify("foo")).thenReturn(true); when(VALID_CREDENTIALS_TWO.verify("baz")).thenReturn(true); + when(DISABLED_CREDENTIALS.verify(DISABLED_PASSWORD)).thenReturn(true); + when(VALID_DEVICE.getAuthenticationCredentials()).thenReturn(VALID_CREDENTIALS); when(VALID_DEVICE_TWO.getAuthenticationCredentials()).thenReturn(VALID_CREDENTIALS_TWO); + when(DISABLED_DEVICE.getAuthenticationCredentials()).thenReturn(DISABLED_CREDENTIALS); + when(VALID_DEVICE.isMaster()).thenReturn(true); when(VALID_DEVICE_TWO.isMaster()).thenReturn(true); + when(DISABLED_DEVICE.isMaster()).thenReturn(true); + when(VALID_DEVICE.getId()).thenReturn(1L); when(VALID_DEVICE_TWO.getId()).thenReturn(1L); + when(DISABLED_DEVICE.getId()).thenReturn(1L); + + when(VALID_DEVICE.isEnabled()).thenReturn(true); + when(VALID_DEVICE_TWO.isEnabled()).thenReturn(true); + when(DISABLED_DEVICE.isEnabled()).thenReturn(false); + when(VALID_ACCOUNT.getDevice(1L)).thenReturn(Optional.of(VALID_DEVICE)); when(VALID_ACCOUNT_TWO.getDevice(eq(1L))).thenReturn(Optional.of(VALID_DEVICE_TWO)); - when(VALID_ACCOUNT_TWO.getActiveDeviceCount()).thenReturn(6); + when(DISABLED_ACCOUNT.getDevice(eq(1L))).thenReturn(Optional.of(DISABLED_DEVICE)); + + when(VALID_ACCOUNT_TWO.getEnabledDeviceCount()).thenReturn(6); + when(VALID_ACCOUNT.getNumber()).thenReturn(VALID_NUMBER); when(VALID_ACCOUNT_TWO.getNumber()).thenReturn(VALID_NUMBER_TWO); + when(DISABLED_ACCOUNT.getNumber()).thenReturn(DISABLED_NUMBER); + when(VALID_ACCOUNT.getAuthenticatedDevice()).thenReturn(Optional.of(VALID_DEVICE)); when(VALID_ACCOUNT_TWO.getAuthenticatedDevice()).thenReturn(Optional.of(VALID_DEVICE_TWO)); + when(DISABLED_ACCOUNT.getAuthenticatedDevice()).thenReturn(Optional.of(DISABLED_DEVICE)); + when(VALID_ACCOUNT.getRelay()).thenReturn(Optional.empty()); when(VALID_ACCOUNT_TWO.getRelay()).thenReturn(Optional.empty()); - when(VALID_ACCOUNT.isActive()).thenReturn(true); - when(VALID_ACCOUNT_TWO.isActive()).thenReturn(true); + + when(VALID_ACCOUNT.isEnabled()).thenReturn(true); + when(VALID_ACCOUNT_TWO.isEnabled()).thenReturn(true); + when(DISABLED_ACCOUNT.isEnabled()).thenReturn(false); + when(VALID_ACCOUNT.getIdentityKey()).thenReturn(VALID_IDENTITY); when(ACCOUNTS_MANAGER.get(VALID_NUMBER)).thenReturn(Optional.of(VALID_ACCOUNT)); when(ACCOUNTS_MANAGER.get(VALID_NUMBER_TWO)).thenReturn(Optional.of(VALID_ACCOUNT_TWO)); + when(ACCOUNTS_MANAGER.get(DISABLED_NUMBER)).thenReturn(Optional.of(DISABLED_ACCOUNT)); - return new AuthDynamicFeature(new BasicCredentialAuthFilter.Builder() - .setAuthenticator(new AccountAuthenticator(ACCOUNTS_MANAGER)) - .buildAuthFilter()); + AuthFilter accountAuthFilter = new BasicCredentialAuthFilter.Builder().setAuthenticator(new AccountAuthenticator(ACCOUNTS_MANAGER)).buildAuthFilter (); + AuthFilter disabledPermittedAccountAuthFilter = new BasicCredentialAuthFilter.Builder().setAuthenticator(new DisabledPermittedAccountAuthenticator(ACCOUNTS_MANAGER)).buildAuthFilter(); + + return new PolymorphicAuthDynamicFeature<>(ImmutableMap.of(Account.class, accountAuthFilter, + DisabledPermittedAccount.class, disabledPermittedAccountAuthFilter)); } public static String getAuthHeader(String number, String password) {