diff --git a/pom.xml b/pom.xml
index 1e89b301a..8adf7b631 100644
--- a/pom.xml
+++ b/pom.xml
@@ -181,8 +181,8 @@
maven-compiler-plugin
3.2
- 1.7
- 1.7
+ 1.8
+ 1.8
diff --git a/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java b/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java
index e5c2f0b24..fc47c6038 100644
--- a/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java
+++ b/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java
@@ -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 getTestDevices() {
Map results = new HashMap<>();
diff --git a/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java
index 802aae7d0..fa5e7df24 100644
--- a/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java
+++ b/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java
@@ -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()
.setAuthenticator(deviceAuthenticator)
diff --git a/src/main/java/org/whispersystems/textsecuregcm/configuration/S3Configuration.java b/src/main/java/org/whispersystems/textsecuregcm/configuration/AttachmentsConfiguration.java
similarity index 89%
rename from src/main/java/org/whispersystems/textsecuregcm/configuration/S3Configuration.java
rename to src/main/java/org/whispersystems/textsecuregcm/configuration/AttachmentsConfiguration.java
index 83d726774..5dbe3bdce 100644
--- a/src/main/java/org/whispersystems/textsecuregcm/configuration/S3Configuration.java
+++ b/src/main/java/org/whispersystems/textsecuregcm/configuration/AttachmentsConfiguration.java
@@ -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;
}
}
diff --git a/src/main/java/org/whispersystems/textsecuregcm/configuration/ProfilesConfiguration.java b/src/main/java/org/whispersystems/textsecuregcm/configuration/ProfilesConfiguration.java
new file mode 100644
index 000000000..cd893460f
--- /dev/null
+++ b/src/main/java/org/whispersystems/textsecuregcm/configuration/ProfilesConfiguration.java
@@ -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;
+ }
+
+}
diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentController.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentController.java
index 82f380fd3..450fbeb24 100644
--- a/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentController.java
+++ b/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentController.java
@@ -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;
diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java
index d5cde3ede..d73f74870 100644
--- a/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java
+++ b/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java
@@ -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 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 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);
+ }
}
diff --git a/src/main/java/org/whispersystems/textsecuregcm/entities/Profile.java b/src/main/java/org/whispersystems/textsecuregcm/entities/Profile.java
index e45dad9c5..72a49dfab 100644
--- a/src/main/java/org/whispersystems/textsecuregcm/entities/Profile.java
+++ b/src/main/java/org/whispersystems/textsecuregcm/entities/Profile.java
@@ -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;
+ }
+
}
diff --git a/src/main/java/org/whispersystems/textsecuregcm/entities/ProfileAvatarUploadAttributes.java b/src/main/java/org/whispersystems/textsecuregcm/entities/ProfileAvatarUploadAttributes.java
new file mode 100644
index 000000000..12ed0651e
--- /dev/null
+++ b/src/main/java/org/whispersystems/textsecuregcm/entities/ProfileAvatarUploadAttributes.java
@@ -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;
+ }
+
+}
diff --git a/src/main/java/org/whispersystems/textsecuregcm/s3/PolicySigner.java b/src/main/java/org/whispersystems/textsecuregcm/s3/PolicySigner.java
new file mode 100644
index 000000000..0f4bb1a98
--- /dev/null
+++ b/src/main/java/org/whispersystems/textsecuregcm/s3/PolicySigner.java
@@ -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);
+ }
+ }
+
+}
diff --git a/src/main/java/org/whispersystems/textsecuregcm/s3/PostPolicyGenerator.java b/src/main/java/org/whispersystems/textsecuregcm/s3/PostPolicyGenerator.java
new file mode 100644
index 000000000..f148eb2ac
--- /dev/null
+++ b/src/main/java/org/whispersystems/textsecuregcm/s3/PostPolicyGenerator.java
@@ -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 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);
+ }
+ }
+
+}
diff --git a/src/main/java/org/whispersystems/textsecuregcm/util/UrlSigner.java b/src/main/java/org/whispersystems/textsecuregcm/s3/UrlSigner.java
similarity index 89%
rename from src/main/java/org/whispersystems/textsecuregcm/util/UrlSigner.java
rename to src/main/java/org/whispersystems/textsecuregcm/s3/UrlSigner.java
index f59790571..05457a5aa 100644
--- a/src/main/java/org/whispersystems/textsecuregcm/util/UrlSigner.java
+++ b/src/main/java/org/whispersystems/textsecuregcm/s3/UrlSigner.java
@@ -14,7 +14,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-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) {
diff --git a/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java b/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java
index 91656a08b..48b4535b8 100644
--- a/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java
+++ b/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java
@@ -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;
+ }
}
diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ProfileControllerTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ProfileControllerTest.java
index 31d68abf0..c105cbb08 100644
--- a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ProfileControllerTest.java
+++ b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ProfileControllerTest.java
@@ -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);
}
diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/s3/PolicySignerTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/s3/PolicySignerTest.java
new file mode 100644
index 000000000..665bc80a5
--- /dev/null
+++ b/src/test/java/org/whispersystems/textsecuregcm/tests/s3/PolicySignerTest.java
@@ -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");
+ }
+}
diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/util/UrlSignerTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/util/UrlSignerTest.java
index 510029f3d..b5bf1054a 100644
--- a/src/test/java/org/whispersystems/textsecuregcm/tests/util/UrlSignerTest.java
+++ b/src/test/java/org/whispersystems/textsecuregcm/tests/util/UrlSignerTest.java
@@ -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);