Add support for getting (limited) profiles by phone number identifier
This commit is contained in:
parent
355996bafc
commit
5816f76bbe
|
@ -13,6 +13,7 @@ import java.time.Duration;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
@ -228,7 +229,7 @@ public class ProfileController {
|
||||||
throws RateLimitExceededException {
|
throws RateLimitExceededException {
|
||||||
|
|
||||||
final Optional<Account> maybeRequester = auth.map(AuthenticatedAccount::getAccount);
|
final Optional<Account> maybeRequester = auth.map(AuthenticatedAccount::getAccount);
|
||||||
final Account targetAccount = verifyPermissionToReceiveProfile(maybeRequester, accessKey, uuid);
|
final Account targetAccount = verifyPermissionToReceiveAccountIdentityProfile(maybeRequester, accessKey, uuid);
|
||||||
|
|
||||||
return buildVersionedProfileResponse(targetAccount,
|
return buildVersionedProfileResponse(targetAccount,
|
||||||
version,
|
version,
|
||||||
|
@ -251,7 +252,7 @@ public class ProfileController {
|
||||||
throws RateLimitExceededException {
|
throws RateLimitExceededException {
|
||||||
|
|
||||||
final Optional<Account> maybeRequester = auth.map(AuthenticatedAccount::getAccount);
|
final Optional<Account> maybeRequester = auth.map(AuthenticatedAccount::getAccount);
|
||||||
final Account targetAccount = verifyPermissionToReceiveProfile(maybeRequester, accessKey, uuid);
|
final Account targetAccount = verifyPermissionToReceiveAccountIdentityProfile(maybeRequester, accessKey, uuid);
|
||||||
final boolean isSelf = isSelfProfileRequest(maybeRequester, uuid);
|
final boolean isSelf = isSelfProfileRequest(maybeRequester, uuid);
|
||||||
|
|
||||||
switch (credentialType) {
|
switch (credentialType) {
|
||||||
|
@ -293,12 +294,30 @@ public class ProfileController {
|
||||||
@QueryParam("ca") boolean useCaCertificate)
|
@QueryParam("ca") boolean useCaCertificate)
|
||||||
throws RateLimitExceededException {
|
throws RateLimitExceededException {
|
||||||
|
|
||||||
|
final Optional<Account> maybeAccountByPni = accountsManager.getByPhoneNumberIdentifier(identifier);
|
||||||
final Optional<Account> maybeRequester = auth.map(AuthenticatedAccount::getAccount);
|
final Optional<Account> maybeRequester = auth.map(AuthenticatedAccount::getAccount);
|
||||||
final Account targetAccount = verifyPermissionToReceiveProfile(maybeRequester, accessKey, identifier);
|
|
||||||
|
|
||||||
return buildBaseProfileResponse(targetAccount,
|
final BaseProfileResponse profileResponse;
|
||||||
isSelfProfileRequest(maybeRequester, identifier),
|
|
||||||
containerRequestContext);
|
if (maybeAccountByPni.isPresent()) {
|
||||||
|
if (maybeRequester.isEmpty()) {
|
||||||
|
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
|
||||||
|
} else {
|
||||||
|
rateLimiters.getProfileLimiter().validate(maybeRequester.get().getUuid());
|
||||||
|
}
|
||||||
|
|
||||||
|
OptionalAccess.verify(maybeRequester, Optional.empty(), maybeAccountByPni);
|
||||||
|
|
||||||
|
profileResponse = buildBaseProfileResponseForPhoneNumberIdentity(maybeAccountByPni.get());
|
||||||
|
} else {
|
||||||
|
final Account targetAccount = verifyPermissionToReceiveAccountIdentityProfile(maybeRequester, accessKey, identifier);
|
||||||
|
|
||||||
|
profileResponse = buildBaseProfileResponseForAccountIdentity(targetAccount,
|
||||||
|
isSelfProfileRequest(maybeRequester, identifier),
|
||||||
|
containerRequestContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
return profileResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ProfileKeyCredentialProfileResponse buildProfileKeyCredentialProfileResponse(final Account account,
|
private ProfileKeyCredentialProfileResponse buildProfileKeyCredentialProfileResponse(final Account account,
|
||||||
|
@ -354,11 +373,12 @@ public class ProfileController {
|
||||||
.map(VersionedProfile::getPaymentAddress)
|
.map(VersionedProfile::getPaymentAddress)
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
|
|
||||||
return new VersionedProfileResponse(buildBaseProfileResponse(account, isSelf, containerRequestContext),
|
return new VersionedProfileResponse(
|
||||||
|
buildBaseProfileResponseForAccountIdentity(account, isSelf, containerRequestContext),
|
||||||
name, about, aboutEmoji, avatar, paymentAddress);
|
name, about, aboutEmoji, avatar, paymentAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
private BaseProfileResponse buildBaseProfileResponse(final Account account,
|
private BaseProfileResponse buildBaseProfileResponseForAccountIdentity(final Account account,
|
||||||
final boolean isSelf,
|
final boolean isSelf,
|
||||||
final ContainerRequestContext containerRequestContext) {
|
final ContainerRequestContext containerRequestContext) {
|
||||||
|
|
||||||
|
@ -373,6 +393,15 @@ public class ProfileController {
|
||||||
account.getUuid());
|
account.getUuid());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private BaseProfileResponse buildBaseProfileResponseForPhoneNumberIdentity(final Account account) {
|
||||||
|
return new BaseProfileResponse(account.getPhoneNumberIdentityKey(),
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
UserCapabilities.createForAccount(account),
|
||||||
|
Collections.emptyList(),
|
||||||
|
account.getPhoneNumberIdentifier());
|
||||||
|
}
|
||||||
|
|
||||||
@Timed
|
@Timed
|
||||||
@GET
|
@GET
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@ -388,7 +417,7 @@ public class ProfileController {
|
||||||
final Account targetAccount = accountsManager.getByUsername(username).orElseThrow(NotFoundException::new);
|
final Account targetAccount = accountsManager.getByUsername(username).orElseThrow(NotFoundException::new);
|
||||||
final boolean isSelf = auth.getAccount().getUuid().equals(targetAccount.getUuid());
|
final boolean isSelf = auth.getAccount().getUuid().equals(targetAccount.getUuid());
|
||||||
|
|
||||||
return buildBaseProfileResponse(targetAccount, isSelf, containerRequestContext);
|
return buildBaseProfileResponseForAccountIdentity(targetAccount, isSelf, containerRequestContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ProfileKeyCredentialResponse getProfileCredential(final String encodedProfileCredentialRequest,
|
private ProfileKeyCredentialResponse getProfileCredential(final String encodedProfileCredentialRequest,
|
||||||
|
@ -507,7 +536,7 @@ public class ProfileController {
|
||||||
* @throws NotAuthorizedException if the requester is not authorized to receive the target account's profile or if the
|
* @throws NotAuthorizedException if the requester is not authorized to receive the target account's profile or if the
|
||||||
* requester was not authenticated and did not present an anonymous access key
|
* requester was not authenticated and did not present an anonymous access key
|
||||||
*/
|
*/
|
||||||
private Account verifyPermissionToReceiveProfile(final Optional<Account> maybeRequester,
|
private Account verifyPermissionToReceiveAccountIdentityProfile(final Optional<Account> maybeRequester,
|
||||||
final Optional<Anonymous> maybeAccessKey,
|
final Optional<Anonymous> maybeAccessKey,
|
||||||
final UUID targetUuid) throws RateLimitExceededException {
|
final UUID targetUuid) throws RateLimitExceededException {
|
||||||
|
|
||||||
|
|
|
@ -172,7 +172,9 @@ class ProfileControllerTest {
|
||||||
profileAccount = mock(Account.class);
|
profileAccount = mock(Account.class);
|
||||||
|
|
||||||
when(profileAccount.getIdentityKey()).thenReturn("bar");
|
when(profileAccount.getIdentityKey()).thenReturn("bar");
|
||||||
|
when(profileAccount.getPhoneNumberIdentityKey()).thenReturn("baz");
|
||||||
when(profileAccount.getUuid()).thenReturn(AuthHelper.VALID_UUID_TWO);
|
when(profileAccount.getUuid()).thenReturn(AuthHelper.VALID_UUID_TWO);
|
||||||
|
when(profileAccount.getPhoneNumberIdentifier()).thenReturn(AuthHelper.VALID_PNI_TWO);
|
||||||
when(profileAccount.isEnabled()).thenReturn(true);
|
when(profileAccount.isEnabled()).thenReturn(true);
|
||||||
when(profileAccount.isGroupsV2Supported()).thenReturn(false);
|
when(profileAccount.isGroupsV2Supported()).thenReturn(false);
|
||||||
when(profileAccount.isGv1MigrationSupported()).thenReturn(false);
|
when(profileAccount.isGv1MigrationSupported()).thenReturn(false);
|
||||||
|
@ -195,6 +197,7 @@ class ProfileControllerTest {
|
||||||
|
|
||||||
when(accountsManager.getByE164(AuthHelper.VALID_NUMBER_TWO)).thenReturn(Optional.of(profileAccount));
|
when(accountsManager.getByE164(AuthHelper.VALID_NUMBER_TWO)).thenReturn(Optional.of(profileAccount));
|
||||||
when(accountsManager.getByAccountIdentifier(AuthHelper.VALID_UUID_TWO)).thenReturn(Optional.of(profileAccount));
|
when(accountsManager.getByAccountIdentifier(AuthHelper.VALID_UUID_TWO)).thenReturn(Optional.of(profileAccount));
|
||||||
|
when(accountsManager.getByPhoneNumberIdentifier(AuthHelper.VALID_PNI_TWO)).thenReturn(Optional.of(profileAccount));
|
||||||
when(accountsManager.getByUsername("n00bkiller")).thenReturn(Optional.of(profileAccount));
|
when(accountsManager.getByUsername("n00bkiller")).thenReturn(Optional.of(profileAccount));
|
||||||
|
|
||||||
when(accountsManager.getByE164(AuthHelper.VALID_NUMBER)).thenReturn(Optional.of(capabilitiesAccount));
|
when(accountsManager.getByE164(AuthHelper.VALID_NUMBER)).thenReturn(Optional.of(capabilitiesAccount));
|
||||||
|
@ -218,7 +221,7 @@ class ProfileControllerTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testProfileGetByUuid() throws RateLimitExceededException {
|
void testProfileGetByAci() throws RateLimitExceededException {
|
||||||
BaseProfileResponse profile = resources.getJerseyTest()
|
BaseProfileResponse profile = resources.getJerseyTest()
|
||||||
.target("/v1/profile/" + AuthHelper.VALID_UUID_TWO)
|
.target("/v1/profile/" + AuthHelper.VALID_UUID_TWO)
|
||||||
.request()
|
.request()
|
||||||
|
@ -234,7 +237,7 @@ class ProfileControllerTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testProfileGetByUuidRateLimited() throws RateLimitExceededException {
|
void testProfileGetByAciRateLimited() throws RateLimitExceededException {
|
||||||
doThrow(new RateLimitExceededException(Duration.ofSeconds(13))).when(rateLimiter).validate(AuthHelper.VALID_UUID);
|
doThrow(new RateLimitExceededException(Duration.ofSeconds(13))).when(rateLimiter).validate(AuthHelper.VALID_UUID);
|
||||||
|
|
||||||
Response response= resources.getJerseyTest()
|
Response response= resources.getJerseyTest()
|
||||||
|
@ -248,7 +251,7 @@ class ProfileControllerTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testProfileGetByUuidUnidentified() throws RateLimitExceededException {
|
void testProfileGetByAciUnidentified() throws RateLimitExceededException {
|
||||||
BaseProfileResponse profile = resources.getJerseyTest()
|
BaseProfileResponse profile = resources.getJerseyTest()
|
||||||
.target("/v1/profile/" + AuthHelper.VALID_UUID_TWO)
|
.target("/v1/profile/" + AuthHelper.VALID_UUID_TWO)
|
||||||
.request()
|
.request()
|
||||||
|
@ -264,7 +267,7 @@ class ProfileControllerTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testProfileGetByUuidUnidentifiedBadKey() {
|
void testProfileGetByAciUnidentifiedBadKey() {
|
||||||
final Response response = resources.getJerseyTest()
|
final Response response = resources.getJerseyTest()
|
||||||
.target("/v1/profile/" + AuthHelper.VALID_UUID_TWO)
|
.target("/v1/profile/" + AuthHelper.VALID_UUID_TWO)
|
||||||
.request()
|
.request()
|
||||||
|
@ -275,7 +278,7 @@ class ProfileControllerTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testProfileGetByUuidUnidentifiedAccountNotFound() {
|
void testProfileGetByAciUnidentifiedAccountNotFound() {
|
||||||
final Response response = resources.getJerseyTest()
|
final Response response = resources.getJerseyTest()
|
||||||
.target("/v1/profile/" + UUID.randomUUID())
|
.target("/v1/profile/" + UUID.randomUUID())
|
||||||
.request()
|
.request()
|
||||||
|
@ -285,6 +288,64 @@ class ProfileControllerTest {
|
||||||
assertThat(response.getStatus()).isEqualTo(401);
|
assertThat(response.getStatus()).isEqualTo(401);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testProfileGetByPni() throws RateLimitExceededException {
|
||||||
|
BaseProfileResponse profile = resources.getJerseyTest()
|
||||||
|
.target("/v1/profile/" + AuthHelper.VALID_PNI_TWO)
|
||||||
|
.request()
|
||||||
|
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||||
|
.get(BaseProfileResponse.class);
|
||||||
|
|
||||||
|
assertThat(profile.getIdentityKey()).isEqualTo("baz");
|
||||||
|
assertThat(profile.getBadges()).isEmpty();
|
||||||
|
assertThat(profile.getUuid()).isEqualTo(AuthHelper.VALID_PNI_TWO);
|
||||||
|
assertThat(profile.getCapabilities()).isNotNull();
|
||||||
|
assertThat(profile.isUnrestrictedUnidentifiedAccess()).isFalse();
|
||||||
|
assertThat(profile.getUnidentifiedAccess()).isNull();
|
||||||
|
|
||||||
|
verify(accountsManager).getByPhoneNumberIdentifier(AuthHelper.VALID_PNI_TWO);
|
||||||
|
verify(rateLimiter, times(1)).validate(AuthHelper.VALID_UUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testProfileGetByPniRateLimited() throws RateLimitExceededException {
|
||||||
|
doThrow(new RateLimitExceededException(Duration.ofSeconds(13))).when(rateLimiter).validate(AuthHelper.VALID_UUID);
|
||||||
|
|
||||||
|
Response response= resources.getJerseyTest()
|
||||||
|
.target("/v1/profile/" + AuthHelper.VALID_PNI_TWO)
|
||||||
|
.request()
|
||||||
|
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||||
|
.get();
|
||||||
|
|
||||||
|
assertThat(response.getStatus()).isEqualTo(413);
|
||||||
|
assertThat(response.getHeaderString("Retry-After")).isEqualTo(String.valueOf(Duration.ofSeconds(13).toSeconds()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testProfileGetByPniUnidentified() throws RateLimitExceededException {
|
||||||
|
final Response response = resources.getJerseyTest()
|
||||||
|
.target("/v1/profile/" + AuthHelper.VALID_PNI_TWO)
|
||||||
|
.request()
|
||||||
|
.header(OptionalAccess.UNIDENTIFIED, AuthHelper.getUnidentifiedAccessHeader("1337".getBytes()))
|
||||||
|
.get();
|
||||||
|
|
||||||
|
assertThat(response.getStatus()).isEqualTo(401);
|
||||||
|
|
||||||
|
verify(accountsManager).getByPhoneNumberIdentifier(AuthHelper.VALID_PNI_TWO);
|
||||||
|
verify(rateLimiter, never()).validate(AuthHelper.VALID_UUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testProfileGetByPniUnidentifiedBadKey() {
|
||||||
|
final Response response = resources.getJerseyTest()
|
||||||
|
.target("/v1/profile/" + AuthHelper.VALID_PNI_TWO)
|
||||||
|
.request()
|
||||||
|
.header(OptionalAccess.UNIDENTIFIED, AuthHelper.getUnidentifiedAccessHeader("incorrect".getBytes()))
|
||||||
|
.get();
|
||||||
|
|
||||||
|
assertThat(response.getStatus()).isEqualTo(401);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testProfileGetByUsername() throws RateLimitExceededException {
|
void testProfileGetByUsername() throws RateLimitExceededException {
|
||||||
BaseProfileResponse profile = resources.getJerseyTest()
|
BaseProfileResponse profile = resources.getJerseyTest()
|
||||||
|
|
|
@ -42,6 +42,7 @@ public class AuthHelper {
|
||||||
|
|
||||||
public static final String VALID_NUMBER_TWO = "+201511111110";
|
public static final String VALID_NUMBER_TWO = "+201511111110";
|
||||||
public static final UUID VALID_UUID_TWO = UUID.randomUUID();
|
public static final UUID VALID_UUID_TWO = UUID.randomUUID();
|
||||||
|
public static final UUID VALID_PNI_TWO = UUID.randomUUID();
|
||||||
public static final String VALID_PASSWORD_TWO = "baz";
|
public static final String VALID_PASSWORD_TWO = "baz";
|
||||||
|
|
||||||
public static final String VALID_NUMBER_3 = "+14445556666";
|
public static final String VALID_NUMBER_3 = "+14445556666";
|
||||||
|
@ -139,6 +140,7 @@ public class AuthHelper {
|
||||||
when(VALID_ACCOUNT.getPhoneNumberIdentifier()).thenReturn(VALID_PNI);
|
when(VALID_ACCOUNT.getPhoneNumberIdentifier()).thenReturn(VALID_PNI);
|
||||||
when(VALID_ACCOUNT_TWO.getNumber()).thenReturn(VALID_NUMBER_TWO);
|
when(VALID_ACCOUNT_TWO.getNumber()).thenReturn(VALID_NUMBER_TWO);
|
||||||
when(VALID_ACCOUNT_TWO.getUuid()).thenReturn(VALID_UUID_TWO);
|
when(VALID_ACCOUNT_TWO.getUuid()).thenReturn(VALID_UUID_TWO);
|
||||||
|
when(VALID_ACCOUNT_TWO.getPhoneNumberIdentifier()).thenReturn(VALID_PNI_TWO);
|
||||||
when(DISABLED_ACCOUNT.getNumber()).thenReturn(DISABLED_NUMBER);
|
when(DISABLED_ACCOUNT.getNumber()).thenReturn(DISABLED_NUMBER);
|
||||||
when(DISABLED_ACCOUNT.getUuid()).thenReturn(DISABLED_UUID);
|
when(DISABLED_ACCOUNT.getUuid()).thenReturn(DISABLED_UUID);
|
||||||
when(UNDISCOVERABLE_ACCOUNT.getNumber()).thenReturn(UNDISCOVERABLE_NUMBER);
|
when(UNDISCOVERABLE_ACCOUNT.getNumber()).thenReturn(UNDISCOVERABLE_NUMBER);
|
||||||
|
@ -169,6 +171,7 @@ public class AuthHelper {
|
||||||
|
|
||||||
when(ACCOUNTS_MANAGER.getByE164(VALID_NUMBER_TWO)).thenReturn(Optional.of(VALID_ACCOUNT_TWO));
|
when(ACCOUNTS_MANAGER.getByE164(VALID_NUMBER_TWO)).thenReturn(Optional.of(VALID_ACCOUNT_TWO));
|
||||||
when(ACCOUNTS_MANAGER.getByAccountIdentifier(VALID_UUID_TWO)).thenReturn(Optional.of(VALID_ACCOUNT_TWO));
|
when(ACCOUNTS_MANAGER.getByAccountIdentifier(VALID_UUID_TWO)).thenReturn(Optional.of(VALID_ACCOUNT_TWO));
|
||||||
|
when(ACCOUNTS_MANAGER.getByPhoneNumberIdentifier(VALID_PNI_TWO)).thenReturn(Optional.of(VALID_ACCOUNT_TWO));
|
||||||
|
|
||||||
when(ACCOUNTS_MANAGER.getByE164(DISABLED_NUMBER)).thenReturn(Optional.of(DISABLED_ACCOUNT));
|
when(ACCOUNTS_MANAGER.getByE164(DISABLED_NUMBER)).thenReturn(Optional.of(DISABLED_ACCOUNT));
|
||||||
when(ACCOUNTS_MANAGER.getByAccountIdentifier(DISABLED_UUID)).thenReturn(Optional.of(DISABLED_ACCOUNT));
|
when(ACCOUNTS_MANAGER.getByAccountIdentifier(DISABLED_UUID)).thenReturn(Optional.of(DISABLED_ACCOUNT));
|
||||||
|
|
Loading…
Reference in New Issue