Add support for capabilities
This commit is contained in:
parent
62a10047ca
commit
c623f70caa
|
@ -487,7 +487,7 @@ public class AccountController {
|
|||
device.setFetchesMessages(attributes.getFetchesMessages());
|
||||
device.setName(attributes.getName());
|
||||
device.setLastSeen(Util.todayInMillis());
|
||||
device.setUnauthenticatedDeliverySupported(attributes.getUnidentifiedAccessKey() != null);
|
||||
device.setCapabilities(attributes.getCapabilities());
|
||||
device.setRegistrationId(attributes.getRegistrationId());
|
||||
device.setSignalingKey(attributes.getSignalingKey());
|
||||
device.setUserAgent(userAgent);
|
||||
|
@ -599,7 +599,7 @@ public class AccountController {
|
|||
device.setFetchesMessages(accountAttributes.getFetchesMessages());
|
||||
device.setRegistrationId(accountAttributes.getRegistrationId());
|
||||
device.setName(accountAttributes.getName());
|
||||
device.setUnauthenticatedDeliverySupported(accountAttributes.getUnidentifiedAccessKey() != null);
|
||||
device.setCapabilities(accountAttributes.getCapabilities());
|
||||
device.setCreated(System.currentTimeMillis());
|
||||
device.setLastSeen(Util.todayInMillis());
|
||||
device.setUserAgent(userAgent);
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.whispersystems.textsecuregcm.sqs.DirectoryQueue;
|
|||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.storage.Device.DeviceCapabilities;
|
||||
import org.whispersystems.textsecuregcm.storage.MessagesManager;
|
||||
import org.whispersystems.textsecuregcm.storage.PendingDevicesManager;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
@ -221,7 +222,15 @@ public class DeviceController {
|
|||
@Path("/unauthenticated_delivery")
|
||||
public void setUnauthenticatedDelivery(@Auth Account account) {
|
||||
assert(account.getAuthenticatedDevice().isPresent());
|
||||
account.getAuthenticatedDevice().get().setUnauthenticatedDeliverySupported(true);
|
||||
// Deprecated
|
||||
}
|
||||
|
||||
@Timed
|
||||
@PUT
|
||||
@Path("/capabilities")
|
||||
public void setCapabiltities(@Auth Account account, @Valid DeviceCapabilities capabilities) {
|
||||
assert(account.getAuthenticatedDevice().isPresent());
|
||||
account.getAuthenticatedDevice().get().setCapabilities(capabilities);
|
||||
accounts.update(account);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ import org.whispersystems.textsecuregcm.auth.UnidentifiedAccessChecksum;
|
|||
import org.whispersystems.textsecuregcm.configuration.CdnConfiguration;
|
||||
import org.whispersystems.textsecuregcm.entities.Profile;
|
||||
import org.whispersystems.textsecuregcm.entities.ProfileAvatarUploadAttributes;
|
||||
import org.whispersystems.textsecuregcm.entities.UserCapabilities;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.s3.PolicySigner;
|
||||
import org.whispersystems.textsecuregcm.s3.PostPolicyGenerator;
|
||||
|
@ -102,8 +103,9 @@ public class ProfileController {
|
|||
return new Profile(accountProfile.get().getProfileName(),
|
||||
accountProfile.get().getAvatar(),
|
||||
accountProfile.get().getIdentityKey(),
|
||||
accountProfile.get().isUnauthenticatedDeliverySupported() ? UnidentifiedAccessChecksum.generateFor(accountProfile.get().getUnidentifiedAccessKey()) : null,
|
||||
accountProfile.get().isUnrestrictedUnidentifiedAccess());
|
||||
UnidentifiedAccessChecksum.generateFor(accountProfile.get().getUnidentifiedAccessKey()),
|
||||
accountProfile.get().isUnrestrictedUnidentifiedAccess(),
|
||||
new UserCapabilities(accountProfile.get().isUuidAddressingSupported()));
|
||||
}
|
||||
|
||||
@Timed
|
||||
|
|
|
@ -19,6 +19,8 @@ package org.whispersystems.textsecuregcm.entities;
|
|||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.storage.Device.DeviceCapabilities;
|
||||
|
||||
public class AccountAttributes {
|
||||
|
||||
|
@ -47,6 +49,9 @@ public class AccountAttributes {
|
|||
@JsonProperty
|
||||
private boolean unrestrictedUnidentifiedAccess;
|
||||
|
||||
@JsonProperty
|
||||
private DeviceCapabilities capabilities;
|
||||
|
||||
public AccountAttributes() {}
|
||||
|
||||
@VisibleForTesting
|
||||
|
@ -95,4 +100,8 @@ public class AccountAttributes {
|
|||
public boolean isUnrestrictedUnidentifiedAccess() {
|
||||
return unrestrictedUnidentifiedAccess;
|
||||
}
|
||||
|
||||
public DeviceCapabilities getCapabilities() {
|
||||
return capabilities;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,10 +3,6 @@ package org.whispersystems.textsecuregcm.entities;
|
|||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import javax.validation.constraints.Max;
|
||||
|
||||
public class Profile {
|
||||
|
||||
@JsonProperty
|
||||
|
@ -24,14 +20,21 @@ public class Profile {
|
|||
@JsonProperty
|
||||
private boolean unrestrictedUnidentifiedAccess;
|
||||
|
||||
@JsonProperty
|
||||
private UserCapabilities capabilities;
|
||||
|
||||
public Profile() {}
|
||||
|
||||
public Profile(String name, String avatar, String identityKey, String unidentifiedAccess, boolean unrestrictedUnidentifiedAccess) {
|
||||
public Profile(String name, String avatar, String identityKey,
|
||||
String unidentifiedAccess, boolean unrestrictedUnidentifiedAccess,
|
||||
UserCapabilities capabilities)
|
||||
{
|
||||
this.name = name;
|
||||
this.avatar = avatar;
|
||||
this.identityKey = identityKey;
|
||||
this.unidentifiedAccess = unidentifiedAccess;
|
||||
this.unrestrictedUnidentifiedAccess = unrestrictedUnidentifiedAccess;
|
||||
this.capabilities = capabilities;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
|
@ -59,4 +62,9 @@ public class Profile {
|
|||
return unrestrictedUnidentifiedAccess;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public UserCapabilities getCapabilities() {
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class UserCapabilities {
|
||||
@JsonProperty
|
||||
private boolean uuid;
|
||||
|
||||
public UserCapabilities() {}
|
||||
|
||||
public UserCapabilities(boolean uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public boolean isUuid() {
|
||||
return uuid;
|
||||
}
|
||||
}
|
|
@ -20,7 +20,6 @@ package org.whispersystems.textsecuregcm.storage;
|
|||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import org.whispersystems.textsecuregcm.auth.AmbiguousIdentifier;
|
||||
|
||||
import javax.security.auth.Subject;
|
||||
|
@ -33,8 +32,6 @@ import java.util.concurrent.TimeUnit;
|
|||
|
||||
public class Account implements Principal {
|
||||
|
||||
static final int MEMCACHE_VERION = 5;
|
||||
|
||||
@JsonIgnore
|
||||
private UUID uuid;
|
||||
|
||||
|
@ -114,7 +111,7 @@ public class Account implements Principal {
|
|||
}
|
||||
|
||||
public void removeDevice(long deviceId) {
|
||||
this.devices.remove(new Device(deviceId, null, null, null, null, null, null, null, false, 0, null, 0, 0, "NA", false, 0));
|
||||
this.devices.remove(new Device(deviceId, null, null, null, null, null, null, null, false, 0, null, 0, 0, "NA", 0, null));
|
||||
}
|
||||
|
||||
public Set<Device> getDevices() {
|
||||
|
@ -135,8 +132,8 @@ public class Account implements Principal {
|
|||
return Optional.empty();
|
||||
}
|
||||
|
||||
public boolean isUnauthenticatedDeliverySupported() {
|
||||
return devices.stream().filter(Device::isEnabled).allMatch(Device::isUnauthenticatedDeliverySupported);
|
||||
public boolean isUuidAddressingSupported() {
|
||||
return devices.stream().filter(Device::isEnabled).allMatch(device -> device.getCapabilities().isUuid());
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.whispersystems.textsecuregcm.storage;
|
|||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials;
|
||||
import org.whispersystems.textsecuregcm.entities.UserCapabilities;
|
||||
import org.whispersystems.textsecuregcm.entities.SignedPreKey;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
|
@ -77,7 +78,7 @@ public class Device {
|
|||
private String userAgent;
|
||||
|
||||
@JsonProperty
|
||||
private boolean unauthenticatedDelivery;
|
||||
private DeviceCapabilities capabilities;
|
||||
|
||||
public Device() {}
|
||||
|
||||
|
@ -86,7 +87,7 @@ public class Device {
|
|||
String voipApnId, boolean fetchesMessages,
|
||||
int registrationId, SignedPreKey signedPreKey,
|
||||
long lastSeen, long created, String userAgent,
|
||||
boolean unauthenticatedDelivery, long uninstalledFeedback)
|
||||
long uninstalledFeedback, DeviceCapabilities capabilities)
|
||||
{
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
|
@ -102,8 +103,8 @@ public class Device {
|
|||
this.lastSeen = lastSeen;
|
||||
this.created = created;
|
||||
this.userAgent = userAgent;
|
||||
this.unauthenticatedDelivery = unauthenticatedDelivery;
|
||||
this.uninstalledFeedback = uninstalledFeedback;
|
||||
this.capabilities = capabilities;
|
||||
}
|
||||
|
||||
public String getApnId() {
|
||||
|
@ -178,14 +179,6 @@ public class Device {
|
|||
this.name = name;
|
||||
}
|
||||
|
||||
public boolean isUnauthenticatedDeliverySupported() {
|
||||
return unauthenticatedDelivery;
|
||||
}
|
||||
|
||||
public void setUnauthenticatedDeliverySupported(boolean unauthenticatedDelivery) {
|
||||
this.unauthenticatedDelivery = unauthenticatedDelivery;
|
||||
}
|
||||
|
||||
public void setAuthenticationCredentials(AuthenticationCredentials credentials) {
|
||||
this.authToken = credentials.getHashedAuthenticationToken();
|
||||
this.salt = credentials.getSalt();
|
||||
|
@ -195,6 +188,14 @@ public class Device {
|
|||
return new AuthenticationCredentials(authToken, salt);
|
||||
}
|
||||
|
||||
public DeviceCapabilities getCapabilities() {
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
public void setCapabilities(DeviceCapabilities capabilities) {
|
||||
this.capabilities = capabilities;
|
||||
}
|
||||
|
||||
public String getSignalingKey() {
|
||||
return signalingKey;
|
||||
}
|
||||
|
@ -262,4 +263,20 @@ public class Device {
|
|||
public int hashCode() {
|
||||
return (int)this.id;
|
||||
}
|
||||
|
||||
public static class DeviceCapabilities {
|
||||
@JsonProperty
|
||||
private boolean uuid;
|
||||
|
||||
public DeviceCapabilities() {}
|
||||
|
||||
public DeviceCapabilities(boolean uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public boolean isUuid() {
|
||||
return uuid;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -85,13 +85,13 @@ public class MessageControllerTest {
|
|||
@Before
|
||||
public void setup() throws Exception {
|
||||
Set<Device> singleDeviceList = new HashSet<Device>() {{
|
||||
add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 111, new SignedPreKey(333, "baz", "boop"), System.currentTimeMillis(), System.currentTimeMillis(), "Test", true, 0));
|
||||
add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 111, new SignedPreKey(333, "baz", "boop"), System.currentTimeMillis(), System.currentTimeMillis(), "Test", 0, new Device.DeviceCapabilities(true)));
|
||||
}};
|
||||
|
||||
Set<Device> multiDeviceList = new HashSet<Device>() {{
|
||||
add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 222, new SignedPreKey(111, "foo", "bar"), System.currentTimeMillis(), System.currentTimeMillis(), "Test", true, 0));
|
||||
add(new Device(2, null, "foo", "bar", "baz", "isgcm", null, null, false, 333, new SignedPreKey(222, "oof", "rab"), System.currentTimeMillis(), System.currentTimeMillis(), "Test", true, 0));
|
||||
add(new Device(3, null, "foo", "bar", "baz", "isgcm", null, null, false, 444, null, System.currentTimeMillis() - TimeUnit.DAYS.toMillis(31), System.currentTimeMillis(), "Test", true, 0));
|
||||
add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 222, new SignedPreKey(111, "foo", "bar"), System.currentTimeMillis(), System.currentTimeMillis(), "Test", 0, new Device.DeviceCapabilities(true)));
|
||||
add(new Device(2, null, "foo", "bar", "baz", "isgcm", null, null, false, 333, new SignedPreKey(222, "oof", "rab"), System.currentTimeMillis(), System.currentTimeMillis(), "Test", 0, new Device.DeviceCapabilities(true)));
|
||||
add(new Device(3, null, "foo", "bar", "baz", "isgcm", null, null, false, 444, null, System.currentTimeMillis() - TimeUnit.DAYS.toMillis(31), System.currentTimeMillis(), "Test", 0, new Device.DeviceCapabilities(false)));
|
||||
}};
|
||||
|
||||
Account singleDeviceAccount = new Account(SINGLE_DEVICE_RECIPIENT, SINGLE_DEVICE_UUID, singleDeviceList, "1234".getBytes());
|
||||
|
|
|
@ -63,9 +63,22 @@ public class ProfileControllerTest {
|
|||
when(profileAccount.getAvatar()).thenReturn("profiles/bang");
|
||||
when(profileAccount.getAvatarDigest()).thenReturn("buh");
|
||||
when(profileAccount.isEnabled()).thenReturn(true);
|
||||
when(profileAccount.isUuidAddressingSupported()).thenReturn(false);
|
||||
|
||||
Account capabilitiesAccount = mock(Account.class);
|
||||
|
||||
when(capabilitiesAccount.getIdentityKey()).thenReturn("barz");
|
||||
when(capabilitiesAccount.getProfileName()).thenReturn("bazz");
|
||||
when(capabilitiesAccount.getAvatar()).thenReturn("profiles/bangz");
|
||||
when(capabilitiesAccount.getAvatarDigest()).thenReturn("buz");
|
||||
when(capabilitiesAccount.isEnabled()).thenReturn(true);
|
||||
when(capabilitiesAccount.isUuidAddressingSupported()).thenReturn(true);
|
||||
|
||||
when(accountsManager.get(AuthHelper.VALID_NUMBER_TWO)).thenReturn(Optional.of(profileAccount));
|
||||
when(accountsManager.get(argThat((ArgumentMatcher<AmbiguousIdentifier>) identifier -> identifier != null && identifier.hasNumber() && identifier.getNumber().equals(AuthHelper.VALID_NUMBER_TWO)))).thenReturn(Optional.of(profileAccount));
|
||||
|
||||
when(accountsManager.get(AuthHelper.VALID_NUMBER)).thenReturn(Optional.of(capabilitiesAccount));
|
||||
when(accountsManager.get(argThat((ArgumentMatcher<AmbiguousIdentifier>) identifier -> identifier != null && identifier.hasNumber() && identifier.getNumber().equals(AuthHelper.VALID_NUMBER)))).thenReturn(Optional.of(capabilitiesAccount));
|
||||
}
|
||||
|
||||
|
||||
|
@ -80,6 +93,7 @@ public class ProfileControllerTest {
|
|||
assertThat(profile.getIdentityKey()).isEqualTo("bar");
|
||||
assertThat(profile.getName()).isEqualTo("baz");
|
||||
assertThat(profile.getAvatar()).isEqualTo("profiles/bang");
|
||||
assertThat(profile.getCapabilities().isUuid()).isFalse();
|
||||
|
||||
verify(accountsManager, times(1)).get(argThat((ArgumentMatcher<AmbiguousIdentifier>) identifier -> identifier != null && identifier.hasNumber() && identifier.getNumber().equals(AuthHelper.VALID_NUMBER_TWO)));
|
||||
verify(rateLimiters, times(1)).getProfileLimiter();
|
||||
|
@ -107,4 +121,15 @@ public class ProfileControllerTest {
|
|||
assertThat(response.getStatus()).isEqualTo(401);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProfileCapabilities() throws Exception {
|
||||
Profile profile= resources.getJerseyTest()
|
||||
.target("/v1/profile/" + AuthHelper.VALID_NUMBER)
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.get(Profile.class);
|
||||
|
||||
assertThat(profile.getCapabilities().isUuid()).isTrue();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -23,6 +23,10 @@ public class AccountTest {
|
|||
private final Device recentSecondaryDevice = mock(Device.class);
|
||||
private final Device oldSecondaryDevice = mock(Device.class);
|
||||
|
||||
private final Device uuidCapableDevice = mock(Device.class);
|
||||
private final Device uuidIncapableDevice = mock(Device.class);
|
||||
private final Device uuidIncapableExpiredDevice = mock(Device.class);
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
when(oldMasterDevice.getLastSeen()).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(366));
|
||||
|
@ -44,6 +48,18 @@ public class AccountTest {
|
|||
when(oldSecondaryDevice.getLastSeen()).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(366));
|
||||
when(oldSecondaryDevice.isEnabled()).thenReturn(false);
|
||||
when(oldSecondaryDevice.getId()).thenReturn(2L);
|
||||
|
||||
when(uuidCapableDevice.getCapabilities()).thenReturn(new Device.DeviceCapabilities(true));
|
||||
when(uuidCapableDevice.getLastSeen()).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1));
|
||||
when(uuidCapableDevice.isEnabled()).thenReturn(true);
|
||||
|
||||
when(uuidIncapableDevice.getCapabilities()).thenReturn(new Device.DeviceCapabilities(false));
|
||||
when(uuidIncapableDevice.getLastSeen()).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1));
|
||||
when(uuidIncapableDevice.isEnabled()).thenReturn(true);
|
||||
|
||||
when(uuidIncapableExpiredDevice.getCapabilities()).thenReturn(new Device.DeviceCapabilities(false));
|
||||
when(uuidIncapableExpiredDevice.getLastSeen()).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(31));
|
||||
when(uuidIncapableExpiredDevice.isEnabled()).thenReturn(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -80,4 +96,25 @@ public class AccountTest {
|
|||
assertFalse(oldPrimaryAccount.isEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCapabilities() {
|
||||
Account uuidCapable = new Account("+14152222222", UUID.randomUUID(), new HashSet<Device>() {{
|
||||
add(uuidCapableDevice);
|
||||
}}, "1234".getBytes());
|
||||
|
||||
Account uuidIncapable = new Account("+14152222222", UUID.randomUUID(), new HashSet<Device>() {{
|
||||
add(uuidCapableDevice);
|
||||
add(uuidIncapableDevice);
|
||||
}}, "1234".getBytes());
|
||||
|
||||
Account uuidCapableWithExpiredIncapable = new Account("+14152222222", UUID.randomUUID(), new HashSet<Device>() {{
|
||||
add(uuidCapableDevice);
|
||||
add(uuidIncapableExpiredDevice);
|
||||
}}, "1234".getBytes());
|
||||
|
||||
assertTrue(uuidCapable.isUuidAddressingSupported());
|
||||
assertFalse(uuidIncapable.isUuidAddressingSupported());
|
||||
assertTrue(uuidCapableWithExpiredIncapable.isUuidAddressingSupported());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -270,7 +270,7 @@ public class AccountsTest {
|
|||
private Device generateDevice(long id) {
|
||||
Random random = new Random(System.currentTimeMillis());
|
||||
SignedPreKey signedPreKey = new SignedPreKey(random.nextInt(), "testPublicKey-" + random.nextInt(), "testSignature-" + random.nextInt());
|
||||
return new Device(id, "testName-" + random.nextInt(), "testAuthToken-" + random.nextInt(), "testSalt-" + random.nextInt(), null, "testGcmId-" + random.nextInt(), "testApnId-" + random.nextInt(), "testVoipApnId-" + random.nextInt(), random.nextBoolean(), random.nextInt(), signedPreKey, random.nextInt(), random.nextInt(), "testUserAgent-" + random.nextInt(), random.nextBoolean(), 0);
|
||||
return new Device(id, "testName-" + random.nextInt(), "testAuthToken-" + random.nextInt(), "testSalt-" + random.nextInt(), null, "testGcmId-" + random.nextInt(), "testApnId-" + random.nextInt(), "testVoipApnId-" + random.nextInt(), random.nextBoolean(), random.nextInt(), signedPreKey, random.nextInt(), random.nextInt(), "testUserAgent-" + random.nextInt() , 0, new Device.DeviceCapabilities(random.nextBoolean()));
|
||||
}
|
||||
|
||||
private Account generateAccount(String number, UUID uuid) {
|
||||
|
|
Loading…
Reference in New Issue