mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-20 05:38:04 +01:00
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.
This commit is contained in:
@@ -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<WhisperServerConfiguration
|
||||
APNSender apnSender = new APNSender(accountsManager, config.getApnConfiguration());
|
||||
GCMSender gcmSender = new GCMSender(accountsManager, config.getGcmConfiguration().getApiKey(), directoryQueue);
|
||||
WebsocketSender websocketSender = new WebsocketSender(messagesManager, pubSubManager);
|
||||
AccountAuthenticator deviceAuthenticator = new AccountAuthenticator(accountsManager );
|
||||
RateLimiters rateLimiters = new RateLimiters(config.getLimitsConfiguration(), cacheClient);
|
||||
|
||||
AccountAuthenticator accountAuthenticator = new AccountAuthenticator(accountsManager);
|
||||
DisabledPermittedAccountAuthenticator disabledPermittedAccountAuthenticator = new DisabledPermittedAccountAuthenticator(accountsManager);
|
||||
|
||||
ApnFallbackManager apnFallbackManager = new ApnFallbackManager(pushSchedulerClient, apnSender, accountsManager);
|
||||
TwilioSmsSender twilioSmsSender = new TwilioSmsSender(config.getTwilioConfiguration());
|
||||
SmsSender smsSender = new SmsSender(twilioSmsSender);
|
||||
@@ -222,10 +230,12 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||
MessageController messageController = new MessageController(rateLimiters, pushSender, receiptSender, accountsManager, messagesManager, apnFallbackManager);
|
||||
ProfileController profileController = new ProfileController(rateLimiters, accountsManager, config.getProfilesConfiguration());
|
||||
|
||||
environment.jersey().register(new AuthDynamicFeature(new BasicCredentialAuthFilter.Builder<Account>()
|
||||
.setAuthenticator(deviceAuthenticator)
|
||||
.buildAuthFilter()));
|
||||
environment.jersey().register(new AuthValueFactoryProvider.Binder<>(Account.class));
|
||||
AuthFilter<BasicCredentials, Account> accountAuthFilter = new BasicCredentialAuthFilter.Builder<Account>().setAuthenticator(accountAuthenticator).buildAuthFilter ();
|
||||
AuthFilter<BasicCredentials, DisabledPermittedAccount> disabledPermittedAccountAuthFilter = new BasicCredentialAuthFilter.Builder<DisabledPermittedAccount>().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<WhisperServerConfiguration
|
||||
|
||||
///
|
||||
WebSocketEnvironment webSocketEnvironment = new WebSocketEnvironment(environment, config.getWebSocketConfiguration(), 90000);
|
||||
webSocketEnvironment.setAuthenticator(new WebSocketAccountAuthenticator(deviceAuthenticator));
|
||||
webSocketEnvironment.setAuthenticator(new WebSocketAccountAuthenticator(accountAuthenticator));
|
||||
webSocketEnvironment.setConnectListener(new AuthenticatedConnectListener(pushSender, receiptSender, messagesManager, pubSubManager, apnFallbackManager));
|
||||
webSocketEnvironment.jersey().register(new KeepAliveController(pubSubManager));
|
||||
webSocketEnvironment.jersey().register(messageController);
|
||||
|
||||
@@ -16,79 +16,23 @@
|
||||
*/
|
||||
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.AuthenticationException;
|
||||
import io.dropwizard.auth.Authenticator;
|
||||
import io.dropwizard.auth.basic.BasicCredentials;
|
||||
|
||||
public class AccountAuthenticator implements Authenticator<BasicCredentials, Account> {
|
||||
|
||||
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<BasicCredentials, Account> {
|
||||
|
||||
public AccountAuthenticator(AccountsManager accountsManager) {
|
||||
this.accountsManager = accountsManager;
|
||||
super(accountsManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Account> authenticate(BasicCredentials basicCredentials)
|
||||
throws AuthenticationException
|
||||
{
|
||||
try {
|
||||
AuthorizationHeader authorizationHeader = AuthorizationHeader.fromUserAndPassword(basicCredentials.getUsername(), basicCredentials.getPassword());
|
||||
Optional<Account> account = accountsManager.get(authorizationHeader.getNumber());
|
||||
|
||||
if (!account.isPresent()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Optional<Device> 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<Account> authenticate(BasicCredentials basicCredentials) {
|
||||
return super.authenticate(basicCredentials, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<Account> authenticate(BasicCredentials basicCredentials, boolean enabledRequired) {
|
||||
try {
|
||||
AuthorizationHeader authorizationHeader = AuthorizationHeader.fromUserAndPassword(basicCredentials.getUsername(), basicCredentials.getPassword());
|
||||
Optional<Account> account = accountsManager.get(authorizationHeader.getNumber());
|
||||
|
||||
if (!account.isPresent()) {
|
||||
noSuchAccountMeter.mark();
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Optional<Device> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<BasicCredentials, DisabledPermittedAccount> {
|
||||
|
||||
public DisabledPermittedAccountAuthenticator(AccountsManager accountsManager) {
|
||||
super(accountsManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<DisabledPermittedAccount> authenticate(BasicCredentials credentials) {
|
||||
Optional<Account> account = super.authenticate(credentials, false);
|
||||
return account.map(DisabledPermittedAccount::new);
|
||||
}
|
||||
}
|
||||
@@ -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<Device> 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<Anonymous> accessKey,
|
||||
Optional<Account> 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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<PreKeyResponseItem> 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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<String> fromNumber, List<Account> accounts) {
|
||||
List<String> numbers = accounts.stream()
|
||||
.filter(Account::isActive)
|
||||
.filter(Account::isEnabled)
|
||||
.map(Account::getNumber)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
|
||||
@@ -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<Acc
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticationResult<Account> authenticate(UpgradeRequest request) throws AuthenticationException {
|
||||
try {
|
||||
Map<String, List<String>> parameters = request.getParameterMap();
|
||||
List<String> usernames = parameters.get("login");
|
||||
List<String> passwords = parameters.get("password");
|
||||
public AuthenticationResult<Account> authenticate(UpgradeRequest request) {
|
||||
Map<String, List<String>> parameters = request.getParameterMap();
|
||||
List<String> usernames = parameters.get("login");
|
||||
List<String> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user