Return a badge with additional properties when fetching your own profile
This commit is contained in:
parent
5c1cde1b28
commit
44bc90e5ab
|
@ -6,7 +6,9 @@
|
|||
package org.whispersystems.textsecuregcm.badges;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import java.net.URL;
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -20,6 +22,7 @@ import java.util.stream.Collectors;
|
|||
import org.whispersystems.textsecuregcm.configuration.BadgeConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.BadgesConfiguration;
|
||||
import org.whispersystems.textsecuregcm.entities.Badge;
|
||||
import org.whispersystems.textsecuregcm.entities.SelfBadge;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountBadge;
|
||||
|
||||
public class ConfiguredProfileBadgeConverter implements ProfileBadgeConverter {
|
||||
|
@ -54,7 +57,8 @@ public class ConfiguredProfileBadgeConverter implements ProfileBadgeConverter {
|
|||
@Override
|
||||
public List<Badge> convert(
|
||||
final List<Locale> acceptableLanguages,
|
||||
final List<AccountBadge> accountBadges) {
|
||||
final List<AccountBadge> accountBadges,
|
||||
final boolean isSelf) {
|
||||
if (accountBadges.isEmpty() && badgeIdsEnabledForAll.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
|
@ -95,23 +99,45 @@ public class ConfiguredProfileBadgeConverter implements ProfileBadgeConverter {
|
|||
&& knownBadges.containsKey(accountBadge.getId()))
|
||||
.map(accountBadge -> {
|
||||
BadgeConfiguration configuration = knownBadges.get(accountBadge.getId());
|
||||
return new Badge(
|
||||
return newBadge(
|
||||
isSelf,
|
||||
accountBadge.getId(),
|
||||
configuration.getCategory(),
|
||||
configuration.getImageUrl(),
|
||||
resourceBundle.getString(accountBadge.getId() + "_name"),
|
||||
resourceBundle.getString(accountBadge.getId() + "_description"));
|
||||
resourceBundle.getString(accountBadge.getId() + "_description"),
|
||||
accountBadge.getExpiration(),
|
||||
accountBadge.isVisible());
|
||||
})
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
badges.addAll(badgeIdsEnabledForAll.stream().filter(knownBadges::containsKey).map(id -> {
|
||||
BadgeConfiguration configuration = knownBadges.get(id);
|
||||
return new Badge(
|
||||
return newBadge(
|
||||
isSelf,
|
||||
id,
|
||||
configuration.getCategory(),
|
||||
configuration.getImageUrl(),
|
||||
resourceBundle.getString(id + "_name"),
|
||||
resourceBundle.getString(id + "_description"));
|
||||
resourceBundle.getString(id + "_description"),
|
||||
now.plus(Duration.ofDays(1)),
|
||||
true);
|
||||
}).collect(Collectors.toList()));
|
||||
return badges;
|
||||
}
|
||||
|
||||
private Badge newBadge(
|
||||
final boolean isSelf,
|
||||
final String id,
|
||||
final String category,
|
||||
final URL imageUrl,
|
||||
final String name,
|
||||
final String description,
|
||||
final Instant expiration,
|
||||
final boolean visible) {
|
||||
if (isSelf) {
|
||||
return new SelfBadge(id, category, imageUrl, name, description, expiration, visible);
|
||||
} else {
|
||||
return new Badge(id, category, imageUrl, name, description);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,5 +16,5 @@ public interface ProfileBadgeConverter {
|
|||
* Converts the {@link AccountBadge}s for an account into the objects
|
||||
* that can be returned on a profile fetch.
|
||||
*/
|
||||
List<Badge> convert(List<Locale> acceptableLanguages, List<AccountBadge> accountBadges);
|
||||
List<Badge> convert(List<Locale> acceptableLanguages, List<AccountBadge> accountBadges, boolean isSelf);
|
||||
}
|
||||
|
|
|
@ -243,8 +243,11 @@ public class ProfileController {
|
|||
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
boolean isSelf = false;
|
||||
if (requestAccount.isPresent()) {
|
||||
rateLimiters.getProfileLimiter().validate(requestAccount.get().getUuid());
|
||||
UUID authedUuid = requestAccount.get().getUuid();
|
||||
rateLimiters.getProfileLimiter().validate(authedUuid);
|
||||
isSelf = uuid.equals(authedUuid);
|
||||
}
|
||||
|
||||
Optional<Account> accountProfile = accountsManager.get(uuid);
|
||||
|
@ -282,7 +285,7 @@ public class ProfileController {
|
|||
UserCapabilities.createForAccount(accountProfile.get()),
|
||||
username.orElse(null),
|
||||
null,
|
||||
profileBadgeConverter.convert(acceptableLanguages, accountProfile.get().getBadges()),
|
||||
profileBadgeConverter.convert(acceptableLanguages, accountProfile.get().getBadges(), isSelf),
|
||||
credential.orElse(null)));
|
||||
} catch (InvalidInputException e) {
|
||||
logger.info("Bad profile request", e);
|
||||
|
@ -291,26 +294,28 @@ public class ProfileController {
|
|||
}
|
||||
|
||||
|
||||
@Timed
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Path("/username/{username}")
|
||||
public Profile getProfileByUsername(
|
||||
@Auth AuthenticatedAccount auth,
|
||||
@Context ContainerRequestContext containerRequestContext,
|
||||
@PathParam("username") String username)
|
||||
throws RateLimitExceededException {
|
||||
rateLimiters.getUsernameLookupLimiter().validate(auth.getAccount().getUuid());
|
||||
@Timed
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Path("/username/{username}")
|
||||
public Profile getProfileByUsername(
|
||||
@Auth AuthenticatedAccount auth,
|
||||
@Context ContainerRequestContext containerRequestContext,
|
||||
@PathParam("username") String username)
|
||||
throws RateLimitExceededException {
|
||||
rateLimiters.getUsernameLookupLimiter().validate(auth.getAccount().getUuid());
|
||||
|
||||
username = username.toLowerCase();
|
||||
username = username.toLowerCase();
|
||||
|
||||
Optional<UUID> uuid = usernamesManager.get(username);
|
||||
Optional<UUID> uuid = usernamesManager.get(username);
|
||||
|
||||
if (uuid.isEmpty()) {
|
||||
throw new WebApplicationException(Response.status(Response.Status.NOT_FOUND).build());
|
||||
}
|
||||
if (uuid.isEmpty()) {
|
||||
throw new WebApplicationException(Response.status(Response.Status.NOT_FOUND).build());
|
||||
}
|
||||
|
||||
Optional<Account> accountProfile = accountsManager.get(uuid.get());
|
||||
final boolean isSelf = auth.getAccount().getUuid().equals(uuid.get());
|
||||
|
||||
Optional<Account> accountProfile = accountsManager.get(uuid.get());
|
||||
|
||||
if (accountProfile.isEmpty()) {
|
||||
throw new WebApplicationException(Response.status(Response.Status.NOT_FOUND).build());
|
||||
|
@ -328,7 +333,10 @@ public class ProfileController {
|
|||
UserCapabilities.createForAccount(accountProfile.get()),
|
||||
username,
|
||||
accountProfile.get().getUuid(),
|
||||
profileBadgeConverter.convert(getAcceptableLanguagesForRequest(containerRequestContext), accountProfile.get().getBadges()),
|
||||
profileBadgeConverter.convert(
|
||||
getAcceptableLanguagesForRequest(containerRequestContext),
|
||||
accountProfile.get().getBadges(),
|
||||
isSelf),
|
||||
null);
|
||||
}
|
||||
|
||||
|
@ -382,8 +390,11 @@ public class ProfileController {
|
|||
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
boolean isSelf = false;
|
||||
if (auth.isPresent()) {
|
||||
rateLimiters.getProfileLimiter().validate(auth.get().getAccount().getUuid());
|
||||
UUID authedUuid = auth.get().getAccount().getUuid();
|
||||
rateLimiters.getProfileLimiter().validate(authedUuid);
|
||||
isSelf = authedUuid.equals(identifier);
|
||||
}
|
||||
|
||||
Optional<Account> accountProfile = accountsManager.get(identifier);
|
||||
|
@ -403,11 +414,13 @@ public class ProfileController {
|
|||
UserCapabilities.createForAccount(accountProfile.get()),
|
||||
username.orElse(null),
|
||||
null,
|
||||
profileBadgeConverter.convert(getAcceptableLanguagesForRequest(containerRequestContext), accountProfile.get().getBadges()),
|
||||
profileBadgeConverter.convert(
|
||||
getAcceptableLanguagesForRequest(containerRequestContext),
|
||||
accountProfile.get().getBadges(),
|
||||
isSelf),
|
||||
null);
|
||||
}
|
||||
|
||||
|
||||
@Deprecated
|
||||
@Timed
|
||||
@GET
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright 2021 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.net.URL;
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Extension of the Badge object returned when asking for one's own badges.
|
||||
*/
|
||||
public class SelfBadge extends Badge {
|
||||
private final Instant expiration;
|
||||
private final boolean visible;
|
||||
|
||||
@JsonCreator
|
||||
|
||||
public SelfBadge(
|
||||
@JsonProperty("id") final String id,
|
||||
@JsonProperty("category") final String category,
|
||||
@JsonProperty("imageUrl") final URL imageUrl,
|
||||
@JsonProperty("name") final String name,
|
||||
@JsonProperty("description") final String description,
|
||||
@JsonProperty("expiration") final Instant expiration,
|
||||
@JsonProperty("visible") final boolean visible) {
|
||||
super(id, category, imageUrl, name, description);
|
||||
this.expiration = expiration;
|
||||
this.visible = visible;
|
||||
}
|
||||
|
||||
public Instant getExpiration() {
|
||||
return expiration;
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
return visible;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
if (!super.equals(o)) {
|
||||
return false;
|
||||
}
|
||||
SelfBadge selfBadge = (SelfBadge) o;
|
||||
return visible == selfBadge.visible && Objects.equals(expiration, selfBadge.expiration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(super.hashCode(), expiration, visible);
|
||||
}
|
||||
}
|
|
@ -111,7 +111,7 @@ public class ConfiguredProfileBadgeConverterTest {
|
|||
BadgesConfiguration badgesConfiguration = createBadges(1);
|
||||
ConfiguredProfileBadgeConverter badgeConverter = new ConfiguredProfileBadgeConverter(clock, badgesConfiguration,
|
||||
resourceBundleFactory);
|
||||
assertThat(badgeConverter.convert(List.of(Locale.getDefault()), List.of())).isNotNull().isEmpty();
|
||||
assertThat(badgeConverter.convert(List.of(Locale.getDefault()), List.of(), false)).isNotNull().isEmpty();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
|
@ -123,11 +123,13 @@ public class ConfiguredProfileBadgeConverterTest {
|
|||
setupResourceBundle(Locale.getDefault());
|
||||
|
||||
if (expectedBadge != null) {
|
||||
assertThat(badgeConverter.convert(List.of(), List.of(new AccountBadge(name, expiration, visible)))).isNotNull()
|
||||
assertThat(badgeConverter.convert(List.of(), List.of(new AccountBadge(name, expiration, visible)),
|
||||
false)).isNotNull()
|
||||
.hasSize(1)
|
||||
.containsOnly(expectedBadge);
|
||||
} else {
|
||||
assertThat(badgeConverter.convert(List.of(), List.of(new AccountBadge(name, expiration, visible)))).isNotNull()
|
||||
assertThat(badgeConverter.convert(List.of(), List.of(new AccountBadge(name, expiration, visible)),
|
||||
false)).isNotNull()
|
||||
.isEmpty();
|
||||
}
|
||||
}
|
||||
|
@ -161,7 +163,7 @@ public class ConfiguredProfileBadgeConverterTest {
|
|||
|
||||
ArgumentCaptor<Control> controlArgumentCaptor = setupResourceBundle(enGb);
|
||||
badgeConverter.convert(List.of(enGb, en, esUs),
|
||||
List.of(new AccountBadge(idFor(0), Instant.ofEpochSecond(43), true)));
|
||||
List.of(new AccountBadge(idFor(0), Instant.ofEpochSecond(43), true)), false);
|
||||
Control control = controlArgumentCaptor.getValue();
|
||||
|
||||
assertThatNullPointerException().isThrownBy(() -> control.getFormats(null));
|
||||
|
@ -186,7 +188,7 @@ public class ConfiguredProfileBadgeConverterTest {
|
|||
// this should always terminate at the system default locale since the development defined bundle should get
|
||||
// returned at that point anyhow
|
||||
badgeConverter.convert(List.of(enGb, Locale.getDefault(), en, esUs),
|
||||
List.of(new AccountBadge(idFor(0), Instant.ofEpochSecond(43), true)));
|
||||
List.of(new AccountBadge(idFor(0), Instant.ofEpochSecond(43), true)), false);
|
||||
Control control2 = controlArgumentCaptor.getValue();
|
||||
|
||||
assertThat(control2.getFallbackLocale(ConfiguredProfileBadgeConverter.BASE_NAME, enGb)).isEqualTo(
|
||||
|
|
|
@ -117,7 +117,7 @@ class ProfileControllerTest {
|
|||
profilesManager,
|
||||
usernamesManager,
|
||||
dynamicConfigurationManager,
|
||||
(acceptableLanguages, accountBadges) -> List.of(
|
||||
(acceptableLanguages, accountBadges, isSelf) -> List.of(
|
||||
new Badge("TEST", "other", makeURL("https://example.com/badge/test"), "Test Badge", "This badge is in unit tests.")
|
||||
),
|
||||
new BadgesConfiguration(List.of(
|
||||
|
|
Loading…
Reference in New Issue