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);