Support for setting profile names and avatars

// FREEBIE
This commit is contained in:
Moxie Marlinspike 2017-06-15 18:30:21 -07:00
parent 8ea805e4e3
commit 322548f078
16 changed files with 443 additions and 49 deletions

View File

@ -181,8 +181,8 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>

View File

@ -21,11 +21,12 @@ import org.whispersystems.textsecuregcm.configuration.ApnConfiguration;
import org.whispersystems.textsecuregcm.configuration.FederationConfiguration;
import org.whispersystems.textsecuregcm.configuration.GcmConfiguration;
import org.whispersystems.textsecuregcm.configuration.MaxDeviceConfiguration;
import org.whispersystems.textsecuregcm.configuration.ProfilesConfiguration;
import org.whispersystems.textsecuregcm.configuration.PushConfiguration;
import org.whispersystems.textsecuregcm.configuration.RateLimitsConfiguration;
import org.whispersystems.textsecuregcm.configuration.RedPhoneConfiguration;
import org.whispersystems.textsecuregcm.configuration.RedisConfiguration;
import org.whispersystems.textsecuregcm.configuration.S3Configuration;
import org.whispersystems.textsecuregcm.configuration.AttachmentsConfiguration;
import org.whispersystems.textsecuregcm.configuration.TestDeviceConfiguration;
import org.whispersystems.textsecuregcm.configuration.TurnConfiguration;
import org.whispersystems.textsecuregcm.configuration.TwilioConfiguration;
@ -57,7 +58,12 @@ public class WhisperServerConfiguration extends Configuration {
@NotNull
@Valid
@JsonProperty
private S3Configuration s3;
private AttachmentsConfiguration attachments;
@NotNull
@Valid
@JsonProperty
private ProfilesConfiguration profiles;
@NotNull
@Valid
@ -145,8 +151,8 @@ public class WhisperServerConfiguration extends Configuration {
return httpClient;
}
public S3Configuration getS3Configuration() {
return s3;
public AttachmentsConfiguration getAttachmentsConfiguration() {
return attachments;
}
public RedisConfiguration getCacheConfiguration() {
@ -193,6 +199,10 @@ public class WhisperServerConfiguration extends Configuration {
return apn;
}
public ProfilesConfiguration getProfilesConfiguration() {
return profiles;
}
public Map<String, Integer> getTestDevices() {
Map<String, Integer> results = new HashMap<>();

View File

@ -66,6 +66,7 @@ import org.whispersystems.textsecuregcm.push.GCMSender;
import org.whispersystems.textsecuregcm.push.PushSender;
import org.whispersystems.textsecuregcm.push.ReceiptSender;
import org.whispersystems.textsecuregcm.push.WebsocketSender;
import org.whispersystems.textsecuregcm.s3.UrlSigner;
import org.whispersystems.textsecuregcm.sms.SmsSender;
import org.whispersystems.textsecuregcm.sms.TwilioSmsSender;
import org.whispersystems.textsecuregcm.storage.Account;
@ -81,7 +82,6 @@ import org.whispersystems.textsecuregcm.storage.PendingDevices;
import org.whispersystems.textsecuregcm.storage.PendingDevicesManager;
import org.whispersystems.textsecuregcm.storage.PubSubManager;
import org.whispersystems.textsecuregcm.util.Constants;
import org.whispersystems.textsecuregcm.util.UrlSigner;
import org.whispersystems.textsecuregcm.websocket.AuthenticatedConnectListener;
import org.whispersystems.textsecuregcm.websocket.DeadLetterHandler;
import org.whispersystems.textsecuregcm.websocket.ProvisioningConnectListener;
@ -183,7 +183,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
ApnFallbackManager apnFallbackManager = new ApnFallbackManager(apnSender, pubSubManager);
TwilioSmsSender twilioSmsSender = new TwilioSmsSender(config.getTwilioConfiguration());
SmsSender smsSender = new SmsSender(twilioSmsSender);
UrlSigner urlSigner = new UrlSigner(config.getS3Configuration());
UrlSigner urlSigner = new UrlSigner(config.getAttachmentsConfiguration());
PushSender pushSender = new PushSender(apnFallbackManager, gcmSender, apnSender, websocketSender, config.getPushConfiguration().getQueueSize());
ReceiptSender receiptSender = new ReceiptSender(accountsManager, pushSender, federatedClientManager);
TurnTokenGenerator turnTokenGenerator = new TurnTokenGenerator(config.getTurnConfiguration());
@ -197,7 +197,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
AttachmentController attachmentController = new AttachmentController(rateLimiters, federatedClientManager, urlSigner);
KeysController keysController = new KeysController(rateLimiters, keys, accountsManager, federatedClientManager);
MessageController messageController = new MessageController(rateLimiters, pushSender, receiptSender, accountsManager, messagesManager, federatedClientManager);
ProfileController profileController = new ProfileController(rateLimiters , accountsManager);
ProfileController profileController = new ProfileController(rateLimiters , accountsManager, config.getProfilesConfiguration());
environment.jersey().register(new AuthDynamicFeature(new BasicCredentialAuthFilter.Builder<Account>()
.setAuthenticator(deviceAuthenticator)

View File

@ -19,7 +19,7 @@ package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.NotEmpty;
public class S3Configuration {
public class AttachmentsConfiguration {
@NotEmpty
@JsonProperty
@ -31,7 +31,7 @@ public class S3Configuration {
@NotEmpty
@JsonProperty
private String attachmentsBucket;
private String bucket;
public String getAccessKey() {
return accessKey;
@ -41,7 +41,7 @@ public class S3Configuration {
return accessSecret;
}
public String getAttachmentsBucket() {
return attachmentsBucket;
public String getBucket() {
return bucket;
}
}

View File

@ -0,0 +1,39 @@
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.NotEmpty;
public class ProfilesConfiguration {
@NotEmpty
@JsonProperty
private String accessKey;
@NotEmpty
@JsonProperty
private String accessSecret;
@NotEmpty
@JsonProperty
private String bucket;
@NotEmpty
@JsonProperty
private String region;
public String getAccessKey() {
return accessKey;
}
public String getAccessSecret() {
return accessSecret;
}
public String getBucket() {
return bucket;
}
public String getRegion() {
return region;
}
}

View File

@ -28,7 +28,7 @@ import org.whispersystems.textsecuregcm.federation.NoSuchPeerException;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.util.Conversions;
import org.whispersystems.textsecuregcm.util.UrlSigner;
import org.whispersystems.textsecuregcm.s3.UrlSigner;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
@ -40,7 +40,6 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.net.URL;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import io.dropwizard.auth.Auth;

View File

@ -1,38 +1,83 @@
package org.whispersystems.textsecuregcm.controllers;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.codahale.metrics.annotation.Timed;
import com.google.common.base.Optional;
import org.apache.commons.codec.binary.Base64;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.valuehandling.UnwrapValidatedValue;
import org.whispersystems.textsecuregcm.configuration.ProfilesConfiguration;
import org.whispersystems.textsecuregcm.entities.Profile;
import org.whispersystems.textsecuregcm.entities.ProfileAvatarUploadAttributes;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.s3.PolicySigner;
import org.whispersystems.textsecuregcm.s3.PostPolicyGenerator;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.util.Pair;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.security.SecureRandom;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import io.dropwizard.auth.Auth;
@Path("/v1/profile")
public class ProfileController {
private final RateLimiters rateLimiters;
private final AccountsManager accountsManager;
private final RateLimiters rateLimiters;
private final AccountsManager accountsManager;
public ProfileController(RateLimiters rateLimiters, AccountsManager accountsManager) {
this.rateLimiters = rateLimiters;
this.accountsManager = accountsManager;
private final PolicySigner policySigner;
private final PostPolicyGenerator policyGenerator;
private final AmazonS3 s3client;
private final String bucket;
public ProfileController(RateLimiters rateLimiters,
AccountsManager accountsManager,
ProfilesConfiguration profilesConfiguration)
{
AWSCredentials credentials = new BasicAWSCredentials(profilesConfiguration.getAccessKey(), profilesConfiguration.getAccessSecret());
AWSCredentialsProvider credentialsProvider = new AWSStaticCredentialsProvider(credentials);
this.rateLimiters = rateLimiters;
this.accountsManager = accountsManager;
this.bucket = profilesConfiguration.getBucket();
this.s3client = AmazonS3Client.builder()
.withCredentials(credentialsProvider)
.withRegion(profilesConfiguration.getRegion())
.build();
this.policyGenerator = new PostPolicyGenerator(profilesConfiguration.getRegion(),
profilesConfiguration.getBucket(),
profilesConfiguration.getAccessKey());
this.policySigner = new PolicySigner(profilesConfiguration.getAccessSecret(),
profilesConfiguration.getRegion());
}
@Timed
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/{number}")
public Profile getProfile(@Auth Account account, @PathParam("number") String number)
public Profile getProfile(@Auth Account account,
@PathParam("number") String number,
@QueryParam("ca") boolean useCaCertificate)
throws RateLimitExceededException
{
rateLimiters.getProfileLimiter().validate(account.getNumber());
@ -43,8 +88,47 @@ public class ProfileController {
throw new WebApplicationException(Response.status(404).build());
}
return new Profile(accountProfile.get().getIdentityKey());
return new Profile(accountProfile.get().getName(),
accountProfile.get().getAvatar(),
accountProfile.get().getIdentityKey());
}
@Timed
@PUT
@Produces(MediaType.APPLICATION_JSON)
@Path("/name/{name}")
public void setProfile(@Auth Account account, @PathParam("name") @UnwrapValidatedValue(true) @Length(min = 72,max= 72) Optional<String> name) {
account.setName(name.orNull());
accountsManager.update(account);
}
@Timed
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/form/avatar")
public ProfileAvatarUploadAttributes getAvatarUploadForm(@Auth Account account) {
String previousAvatar = account.getAvatar();
ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC);
String objectName = generateAvatarObjectName();
Pair<String, String> policy = policyGenerator.createFor(now, objectName);
String signature = policySigner.getSignature(now, policy.second());
if (previousAvatar != null && previousAvatar.startsWith("profiles/")) {
s3client.deleteObject(bucket, previousAvatar);
}
account.setAvatar(objectName);
accountsManager.update(account);
return new ProfileAvatarUploadAttributes(objectName, policy.first(), "private", "AWS4-HMAC-SHA256",
now.format(PostPolicyGenerator.AWS_DATE_TIME), policy.second(), signature);
}
private String generateAvatarObjectName() {
byte[] object = new byte[16];
new SecureRandom().nextBytes(object);
return "profiles/" + Base64.encodeBase64URLSafeString(object);
}
}

View File

@ -3,14 +3,26 @@ 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
private String identityKey;
@JsonProperty
private String name;
@JsonProperty
private String avatar;
public Profile() {}
public Profile(String identityKey) {
public Profile(String name, String avatar, String identityKey) {
this.name = name;
this.avatar = avatar;
this.identityKey = identityKey;
}
@ -18,4 +30,15 @@ public class Profile {
public String getIdentityKey() {
return identityKey;
}
@VisibleForTesting
public String getName() {
return name;
}
@VisibleForTesting
public String getAvatar() {
return avatar;
}
}

View File

@ -0,0 +1,44 @@
package org.whispersystems.textsecuregcm.entities;
import com.fasterxml.jackson.annotation.JsonProperty;
public class ProfileAvatarUploadAttributes {
@JsonProperty
private String key;
@JsonProperty
private String credential;
@JsonProperty
private String acl;
@JsonProperty
private String algorithm;
@JsonProperty
private String date;
@JsonProperty
private String policy;
@JsonProperty
private String signature;
public ProfileAvatarUploadAttributes() {}
public ProfileAvatarUploadAttributes(String key, String credential,
String acl, String algorithm,
String date, String policy,
String signature)
{
this.key = key;
this.credential = credential;
this.acl = acl;
this.algorithm = algorithm;
this.date = date;
this.policy = policy;
this.signature = signature;
}
}

View File

@ -0,0 +1,48 @@
package org.whispersystems.textsecuregcm.s3;
import com.amazonaws.util.Base16Lower;
import com.google.common.annotations.VisibleForTesting;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
public class PolicySigner {
private final String awsAccessSecret;
private final String region;
public PolicySigner(String awsAccessSecret, String region) {
this.awsAccessSecret = awsAccessSecret;
this.region = region;
}
public String getSignature(ZonedDateTime now, String policy) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(("AWS4" + awsAccessSecret).getBytes("UTF-8"), "HmacSHA256"));
byte[] dateKey = mac.doFinal(now.format(DateTimeFormatter.ofPattern("yyyyMMdd")).getBytes("UTF-8"));
mac.init(new SecretKeySpec(dateKey, "HmacSHA256"));
byte[] dateRegionKey = mac.doFinal(region.getBytes("UTF-8"));
mac.init(new SecretKeySpec(dateRegionKey, "HmacSHA256"));
byte[] dateRegionServiceKey = mac.doFinal("s3".getBytes("UTF-8"));
mac.init(new SecretKeySpec(dateRegionServiceKey, "HmacSHA256"));
byte[] signingKey = mac.doFinal("aws4_request".getBytes("UTF-8"));
mac.init(new SecretKeySpec(signingKey, "HmacSHA256"));
return Base16Lower.encodeAsString(mac.doFinal(policy.getBytes("UTF-8")));
} catch (NoSuchAlgorithmException | InvalidKeyException | UnsupportedEncodingException e) {
throw new AssertionError(e);
}
}
}

View File

@ -0,0 +1,51 @@
package org.whispersystems.textsecuregcm.s3;
import org.apache.commons.codec.binary.Base64;
import org.whispersystems.textsecuregcm.util.Pair;
import java.io.UnsupportedEncodingException;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
public class PostPolicyGenerator {
public static final DateTimeFormatter AWS_DATE_TIME = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmssX");
private static final DateTimeFormatter CREDENTIAL_DATE = DateTimeFormatter.ofPattern("yyyyMMdd" );
private final String region;
private final String bucket;
private final String awsAccessId;
public PostPolicyGenerator(String region, String bucket, String awsAccessId) {
this.region = region;
this.bucket = bucket;
this.awsAccessId = awsAccessId;
}
public Pair<String, String> createFor(ZonedDateTime now, String object) {
try {
String expiration = now.plusMinutes(30).format(DateTimeFormatter.ISO_INSTANT);
String credentialDate = now.format(CREDENTIAL_DATE);
String requestDate = now.format(AWS_DATE_TIME );
String credential = String.format("%s/%s/%s/s3/aws4_request", awsAccessId, credentialDate, region);
String policy = String.format("{ \"expiration\": \"%s\",\n" +
" \"conditions\": [\n" +
" {\"bucket\": \"%s\"},\n" +
" {\"key\": \"%s\"},\n" +
" {\"acl\": \"private\"},\n" +
" [\"starts-with\", \"$Content-Type\", \"\"],\n" +
"\n" +
" {\"x-amz-credential\": \"%s\"},\n" +
" {\"x-amz-algorithm\": \"AWS4-HMAC-SHA256\"},\n" +
" {\"x-amz-date\": \"%s\" }\n" +
" ]\n" +
"}", expiration, bucket, object, credential, requestDate);
return new Pair<>(credential, Base64.encodeBase64String(policy.getBytes("UTF-8")));
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e);
}
}
}

View File

@ -14,7 +14,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecuregcm.util;
package org.whispersystems.textsecuregcm.s3;
import com.amazonaws.HttpMethod;
import com.amazonaws.auth.AWSCredentials;
@ -23,7 +23,7 @@ import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.S3ClientOptions;
import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest;
import org.whispersystems.textsecuregcm.configuration.S3Configuration;
import org.whispersystems.textsecuregcm.configuration.AttachmentsConfiguration;
import java.net.URL;
import java.util.Date;
@ -35,9 +35,9 @@ public class UrlSigner {
private final AWSCredentials credentials;
private final String bucket;
public UrlSigner(S3Configuration config) {
public UrlSigner(AttachmentsConfiguration config) {
this.credentials = new BasicAWSCredentials(config.getAccessKey(), config.getAccessSecret());
this.bucket = config.getAttachmentsBucket();
this.bucket = config.getBucket();
}
public URL getPreSignedUrl(long attachmentId, HttpMethod method) {

View File

@ -39,6 +39,15 @@ public class Account {
@JsonProperty
private String identityKey;
@JsonProperty
private String name;
@JsonProperty
private String avatar;
@JsonProperty
private String avatarDigest;
@JsonIgnore
private Device authenticatedDevice;
@ -171,4 +180,28 @@ public class Account {
return lastSeen;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public String getAvatarDigest() {
return avatarDigest;
}
public void setAvatarDigest(String avatarDigest) {
this.avatarDigest = avatarDigest;
}
}

View File

@ -1,7 +1,12 @@
package org.whispersystems.textsecuregcm.tests.controllers;
import com.google.common.base.Optional;
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.whispersystems.dropwizard.simpleauth.AuthValueFactoryProvider;
import org.whispersystems.textsecuregcm.configuration.ProfilesConfiguration;
import org.whispersystems.textsecuregcm.controllers.ProfileController;
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
import org.whispersystems.textsecuregcm.entities.Profile;
@ -9,34 +14,68 @@ import org.whispersystems.textsecuregcm.limits.RateLimiter;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
import org.whispersystems.textsecuregcm.util.SystemMapper;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import io.dropwizard.testing.junit.ResourceTestRule;
import static org.assertj.core.api.Java6Assertions.assertThat;
import static org.mockito.Mockito.*;
public class ProfileControllerTest {
private static AccountsManager accountsManager = mock(AccountsManager.class );
private static RateLimiters rateLimiters = mock(RateLimiters.class );
private static RateLimiter rateLimiter = mock(RateLimiter.class );
private static ProfilesConfiguration configuration = mock(ProfilesConfiguration.class);
static {
when(configuration.getAccessKey()).thenReturn("accessKey");
when(configuration.getAccessSecret()).thenReturn("accessSecret");
when(configuration.getRegion()).thenReturn("us-east-1");
when(configuration.getBucket()).thenReturn("profile-bucket");
}
@ClassRule
public static final ResourceTestRule resources = ResourceTestRule.builder()
.addProvider(AuthHelper.getAuthFilter())
.addProvider(new AuthValueFactoryProvider.Binder())
.setMapper(SystemMapper.getMapper())
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
.addResource(new ProfileController(rateLimiters,
accountsManager,
configuration))
.build();
@Before
public void setup() throws Exception {
when(rateLimiters.getProfileLimiter()).thenReturn(rateLimiter);
Account profileAccount = mock(Account.class);
when(profileAccount.getIdentityKey()).thenReturn("bar");
when(profileAccount.getName()).thenReturn("baz");
when(profileAccount.getAvatar()).thenReturn("profiles/bang");
when(profileAccount.getAvatarDigest()).thenReturn("buh");
when(accountsManager.get(AuthHelper.VALID_NUMBER_TWO)).thenReturn(Optional.of(profileAccount));
}
@Test
public void testProfileGet() throws RateLimitExceededException {
Account requestAccount = mock(Account.class );
Account profileAccount = mock(Account.class );
RateLimiter rateLimiter = mock(RateLimiter.class );
RateLimiters rateLimiters = mock(RateLimiters.class );
AccountsManager accountsManager = mock(AccountsManager.class);
Profile profile= resources.getJerseyTest()
.target("/v1/profile/" + AuthHelper.VALID_NUMBER_TWO)
.request()
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
.get(Profile.class);
when(rateLimiters.getProfileLimiter()).thenReturn(rateLimiter);
when(requestAccount.getNumber()).thenReturn("foo");
when(profileAccount.getIdentityKey()).thenReturn("bar");
when(accountsManager.get(eq("baz"))).thenReturn(Optional.of(profileAccount));
assertThat(profile.getIdentityKey()).isEqualTo("bar");
assertThat(profile.getName()).isEqualTo("baz");
assertThat(profile.getAvatar()).isEqualTo("profiles/bang");
ProfileController profileController = new ProfileController(rateLimiters, accountsManager);
Profile result = profileController.getProfile(requestAccount, "baz");
assertEquals(result.getIdentityKey(), "bar");
verify(accountsManager, times(1)).get(eq("baz"));
verify(accountsManager, times(1)).get(AuthHelper.VALID_NUMBER_TWO);
verify(rateLimiters, times(1)).getProfileLimiter();
verify(rateLimiter, times(1)).validate("foo");
verify(rateLimiter, times(1)).validate(AuthHelper.VALID_NUMBER);
}

View File

@ -0,0 +1,24 @@
package org.whispersystems.textsecuregcm.tests.s3;
import org.junit.Test;
import org.whispersystems.textsecuregcm.s3.PolicySigner;
import java.io.UnsupportedEncodingException;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import static org.junit.Assert.assertEquals;
public class PolicySignerTest {
@Test
public void testSignature() throws UnsupportedEncodingException {
Instant time = Instant.parse("2015-12-29T00:00:00Z");
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(time, ZoneOffset.UTC);
String encodedPolicy = "eyAiZXhwaXJhdGlvbiI6ICIyMDE1LTEyLTMwVDEyOjAwOjAwLjAwMFoiLA0KICAiY29uZGl0aW9ucyI6IFsNCiAgICB7ImJ1Y2tldCI6ICJzaWd2NGV4YW1wbGVidWNrZXQifSwNCiAgICBbInN0YXJ0cy13aXRoIiwgIiRrZXkiLCAidXNlci91c2VyMS8iXSwNCiAgICB7ImFjbCI6ICJwdWJsaWMtcmVhZCJ9LA0KICAgIHsic3VjY2Vzc19hY3Rpb25fcmVkaXJlY3QiOiAiaHR0cDovL3NpZ3Y0ZXhhbXBsZWJ1Y2tldC5zMy5hbWF6b25hd3MuY29tL3N1Y2Nlc3NmdWxfdXBsb2FkLmh0bWwifSwNCiAgICBbInN0YXJ0cy13aXRoIiwgIiRDb250ZW50LVR5cGUiLCAiaW1hZ2UvIl0sDQogICAgeyJ4LWFtei1tZXRhLXV1aWQiOiAiMTQzNjUxMjM2NTEyNzQifSwNCiAgICB7IngtYW16LXNlcnZlci1zaWRlLWVuY3J5cHRpb24iOiAiQUVTMjU2In0sDQogICAgWyJzdGFydHMtd2l0aCIsICIkeC1hbXotbWV0YS10YWciLCAiIl0sDQoNCiAgICB7IngtYW16LWNyZWRlbnRpYWwiOiAiQUtJQUlPU0ZPRE5ON0VYQU1QTEUvMjAxNTEyMjkvdXMtZWFzdC0xL3MzL2F3czRfcmVxdWVzdCJ9LA0KICAgIHsieC1hbXotYWxnb3JpdGhtIjogIkFXUzQtSE1BQy1TSEEyNTYifSwNCiAgICB7IngtYW16LWRhdGUiOiAiMjAxNTEyMjlUMDAwMDAwWiIgfQ0KICBdDQp9";
PolicySigner policySigner = new PolicySigner("wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", "us-east-1");
assertEquals(policySigner.getSignature(zonedDateTime, encodedPolicy), "8afdbf4008c03f22c2cd3cdb72e4afbb1f6a588f3255ac628749a66d7f09699e");
}
}

View File

@ -2,8 +2,8 @@ package org.whispersystems.textsecuregcm.tests.util;
import com.amazonaws.HttpMethod;
import org.junit.Test;
import org.whispersystems.textsecuregcm.configuration.S3Configuration;
import org.whispersystems.textsecuregcm.util.UrlSigner;
import org.whispersystems.textsecuregcm.configuration.AttachmentsConfiguration;
import org.whispersystems.textsecuregcm.s3.UrlSigner;
import java.net.URL;
@ -15,10 +15,10 @@ public class UrlSignerTest {
@Test
public void testTransferAcceleration() {
S3Configuration configuration = mock(S3Configuration.class);
AttachmentsConfiguration configuration = mock(AttachmentsConfiguration.class);
when(configuration.getAccessKey()).thenReturn("foo");
when(configuration.getAccessSecret()).thenReturn("bar");
when(configuration.getAttachmentsBucket()).thenReturn("attachments-test");
when(configuration.getBucket()).thenReturn("attachments-test");
UrlSigner signer = new UrlSigner(configuration);
URL url = signer.getPreSignedUrl(1234, HttpMethod.GET);