mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-23 02:58:04 +01:00
Generic credential auth endpoint for call links
This commit is contained in:
@@ -8,9 +8,14 @@ package org.whispersystems.textsecuregcm.tests.auth;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.whispersystems.textsecuregcm.util.HmacUtils.hmac256TruncatedToHexString;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
|
||||
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
|
||||
@@ -18,6 +23,7 @@ import org.whispersystems.textsecuregcm.util.MockUtils;
|
||||
import org.whispersystems.textsecuregcm.util.MutableClock;
|
||||
|
||||
class ExternalServiceCredentialsGeneratorTest {
|
||||
private static final String PREFIX = "prefix";
|
||||
|
||||
private static final String E164 = "+14152222222";
|
||||
|
||||
@@ -27,6 +33,42 @@ class ExternalServiceCredentialsGeneratorTest {
|
||||
|
||||
private static final String TIME_SECONDS_STRING = Long.toString(TIME_SECONDS);
|
||||
|
||||
private static final String USERNAME_TIMESTAMP = PREFIX + ":" + Instant.ofEpochSecond(TIME_SECONDS).truncatedTo(ChronoUnit.DAYS).getEpochSecond();
|
||||
|
||||
private static final MutableClock clock = MockUtils.mutableClock(TIME_MILLIS);
|
||||
|
||||
private static final ExternalServiceCredentialsGenerator standardGenerator = ExternalServiceCredentialsGenerator
|
||||
.builder(new byte[32])
|
||||
.withClock(clock)
|
||||
.build();
|
||||
|
||||
private static final ExternalServiceCredentials standardCredentials = standardGenerator.generateFor(E164);
|
||||
|
||||
private static final ExternalServiceCredentialsGenerator usernameIsTimestampGenerator = ExternalServiceCredentialsGenerator
|
||||
.builder(new byte[32])
|
||||
.withUsernameTimestampTruncatorAndPrefix(timestamp -> timestamp.truncatedTo(ChronoUnit.DAYS), PREFIX)
|
||||
.withClock(clock)
|
||||
.build();
|
||||
|
||||
private static final ExternalServiceCredentials usernameIsTimestampCredentials = usernameIsTimestampGenerator.generateWithTimestampAsUsername();
|
||||
|
||||
@BeforeEach
|
||||
public void before() throws Exception {
|
||||
clock.setTimeMillis(TIME_MILLIS);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInvalidConstructor() {
|
||||
assertThrows(RuntimeException.class, () -> ExternalServiceCredentialsGenerator
|
||||
.builder(new byte[32])
|
||||
.withUsernameTimestampTruncatorAndPrefix(null, PREFIX)
|
||||
.build());
|
||||
|
||||
assertThrows(RuntimeException.class, () -> ExternalServiceCredentialsGenerator
|
||||
.builder(new byte[32])
|
||||
.withUsernameTimestampTruncatorAndPrefix(timestamp -> timestamp.truncatedTo(ChronoUnit.DAYS), null)
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGenerateDerivedUsername() {
|
||||
@@ -42,18 +84,13 @@ class ExternalServiceCredentialsGeneratorTest {
|
||||
|
||||
@Test
|
||||
void testGenerateNoDerivedUsername() {
|
||||
final ExternalServiceCredentialsGenerator generator = ExternalServiceCredentialsGenerator
|
||||
.builder(new byte[32])
|
||||
.build();
|
||||
final ExternalServiceCredentials credentials = generator.generateFor(E164);
|
||||
assertEquals(credentials.username(), E164);
|
||||
assertTrue(credentials.password().startsWith(E164));
|
||||
assertEquals(credentials.password().split(":").length, 3);
|
||||
assertEquals(standardCredentials.username(), E164);
|
||||
assertTrue(standardCredentials.password().startsWith(E164));
|
||||
assertEquals(standardCredentials.password().split(":").length, 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotPrependUsername() throws Exception {
|
||||
final MutableClock clock = MockUtils.mutableClock(TIME_MILLIS);
|
||||
final ExternalServiceCredentialsGenerator generator = ExternalServiceCredentialsGenerator
|
||||
.builder(new byte[32])
|
||||
.prependUsername(false)
|
||||
@@ -65,52 +102,68 @@ class ExternalServiceCredentialsGeneratorTest {
|
||||
assertEquals(credentials.password().split(":").length, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithUsernameIsTimestamp() {
|
||||
assertEquals(USERNAME_TIMESTAMP, usernameIsTimestampCredentials.username());
|
||||
|
||||
final String[] passwordComponents = usernameIsTimestampCredentials.password().split(":");
|
||||
assertEquals(USERNAME_TIMESTAMP, passwordComponents[0] + ":" + passwordComponents[1]);
|
||||
assertEquals(hmac256TruncatedToHexString(new byte[32], USERNAME_TIMESTAMP, 10), passwordComponents[2]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateValid() throws Exception {
|
||||
final MutableClock clock = MockUtils.mutableClock(TIME_MILLIS);
|
||||
final ExternalServiceCredentialsGenerator generator = ExternalServiceCredentialsGenerator
|
||||
.builder(new byte[32])
|
||||
.withClock(clock)
|
||||
.build();
|
||||
final ExternalServiceCredentials credentials = generator.generateFor(E164);
|
||||
assertEquals(generator.validateAndGetTimestamp(credentials).orElseThrow(), TIME_SECONDS);
|
||||
assertEquals(standardGenerator.validateAndGetTimestamp(standardCredentials).orElseThrow(), TIME_SECONDS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateValidWithUsernameIsTimestamp() {
|
||||
final long expectedTimestamp = Instant.ofEpochSecond(TIME_SECONDS).truncatedTo(ChronoUnit.DAYS).getEpochSecond();
|
||||
assertEquals(expectedTimestamp, usernameIsTimestampGenerator.validateAndGetTimestamp(usernameIsTimestampCredentials).orElseThrow());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateInvalid() throws Exception {
|
||||
final MutableClock clock = MockUtils.mutableClock(TIME_MILLIS);
|
||||
final ExternalServiceCredentialsGenerator generator = ExternalServiceCredentialsGenerator
|
||||
.builder(new byte[32])
|
||||
.withClock(clock)
|
||||
.build();
|
||||
final ExternalServiceCredentials credentials = generator.generateFor(E164);
|
||||
final ExternalServiceCredentials corruptedStandardUsername = new ExternalServiceCredentials(
|
||||
standardCredentials.username(), standardCredentials.password().replace(E164, E164 + "0"));
|
||||
final ExternalServiceCredentials corruptedStandardTimestamp = new ExternalServiceCredentials(
|
||||
standardCredentials.username(), standardCredentials.password().replace(TIME_SECONDS_STRING, TIME_SECONDS_STRING + "0"));
|
||||
final ExternalServiceCredentials corruptedStandardPassword = new ExternalServiceCredentials(
|
||||
standardCredentials.username(), standardCredentials.password() + "0");
|
||||
|
||||
final ExternalServiceCredentials corruptedUsername = new ExternalServiceCredentials(
|
||||
credentials.username(), credentials.password().replace(E164, E164 + "0"));
|
||||
final ExternalServiceCredentials corruptedTimestamp = new ExternalServiceCredentials(
|
||||
credentials.username(), credentials.password().replace(TIME_SECONDS_STRING, TIME_SECONDS_STRING + "0"));
|
||||
final ExternalServiceCredentials corruptedPassword = new ExternalServiceCredentials(
|
||||
credentials.username(), credentials.password() + "0");
|
||||
final ExternalServiceCredentials corruptedUsernameTimestamp = new ExternalServiceCredentials(
|
||||
usernameIsTimestampCredentials.username(), usernameIsTimestampCredentials.password().replace(USERNAME_TIMESTAMP, USERNAME_TIMESTAMP
|
||||
+ "0"));
|
||||
final ExternalServiceCredentials corruptedUsernameTimestampPassword = new ExternalServiceCredentials(
|
||||
usernameIsTimestampCredentials.username(), usernameIsTimestampCredentials.password() + "0");
|
||||
|
||||
assertTrue(generator.validateAndGetTimestamp(corruptedUsername).isEmpty());
|
||||
assertTrue(generator.validateAndGetTimestamp(corruptedTimestamp).isEmpty());
|
||||
assertTrue(generator.validateAndGetTimestamp(corruptedPassword).isEmpty());
|
||||
assertTrue(standardGenerator.validateAndGetTimestamp(corruptedStandardUsername).isEmpty());
|
||||
assertTrue(standardGenerator.validateAndGetTimestamp(corruptedStandardTimestamp).isEmpty());
|
||||
assertTrue(standardGenerator.validateAndGetTimestamp(corruptedStandardPassword).isEmpty());
|
||||
|
||||
assertTrue(usernameIsTimestampGenerator.validateAndGetTimestamp(corruptedUsernameTimestamp).isEmpty());
|
||||
assertTrue(usernameIsTimestampGenerator.validateAndGetTimestamp(corruptedUsernameTimestampPassword).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateWithExpiration() throws Exception {
|
||||
final MutableClock clock = MockUtils.mutableClock(TIME_MILLIS);
|
||||
final ExternalServiceCredentialsGenerator generator = ExternalServiceCredentialsGenerator
|
||||
.builder(new byte[32])
|
||||
.withClock(clock)
|
||||
.build();
|
||||
final ExternalServiceCredentials credentials = generator.generateFor(E164);
|
||||
|
||||
final long elapsedSeconds = 10000;
|
||||
clock.incrementSeconds(elapsedSeconds);
|
||||
|
||||
assertEquals(generator.validateAndGetTimestamp(credentials, elapsedSeconds + 1).orElseThrow(), TIME_SECONDS);
|
||||
assertTrue(generator.validateAndGetTimestamp(credentials, elapsedSeconds - 1).isEmpty());
|
||||
assertEquals(standardGenerator.validateAndGetTimestamp(standardCredentials, elapsedSeconds + 1).orElseThrow(), TIME_SECONDS);
|
||||
assertTrue(standardGenerator.validateAndGetTimestamp(standardCredentials, elapsedSeconds - 1).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetIdentityFromSignature() {
|
||||
final String identity = standardGenerator.identityFromSignature(standardCredentials.password()).orElseThrow();
|
||||
assertEquals(E164, identity);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetIdentityFromSignatureIsTimestamp() {
|
||||
final String identity = usernameIsTimestampGenerator.identityFromSignature(usernameIsTimestampCredentials.password()).orElseThrow();
|
||||
assertEquals(USERNAME_TIMESTAMP, identity);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.whispersystems.textsecuregcm.tests.controllers;
|
||||
|
||||
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
|
||||
import io.dropwizard.testing.junit5.ResourceExtension;
|
||||
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mockito;
|
||||
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
|
||||
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
|
||||
import org.whispersystems.textsecuregcm.configuration.CallLinkConfiguration;
|
||||
import org.whispersystems.textsecuregcm.controllers.CallLinkController;
|
||||
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
|
||||
import org.whispersystems.textsecuregcm.util.MockUtils;
|
||||
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
|
||||
@ExtendWith(DropwizardExtensionsSupport.class)
|
||||
public class CallLinkControllerTest {
|
||||
private static final CallLinkConfiguration callLinkConfiguration = MockUtils.buildMock(
|
||||
CallLinkConfiguration.class,
|
||||
cfg -> Mockito.when(cfg.userAuthenticationTokenSharedSecret()).thenReturn(new byte[32])
|
||||
);
|
||||
private static final ExternalServiceCredentialsGenerator callLinkCredentialsGenerator = CallLinkController.credentialsGenerator(callLinkConfiguration);
|
||||
|
||||
private static final ResourceExtension resources = ResourceExtension.builder()
|
||||
.setMapper(SystemMapper.jsonMapper())
|
||||
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
|
||||
.addResource(new CallLinkController(callLinkCredentialsGenerator))
|
||||
.build();
|
||||
|
||||
@Test
|
||||
void testGetAuth() {
|
||||
ExternalServiceCredentials credentials = resources.getJerseyTest()
|
||||
.target("/v1/call-link/auth")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||
.get(ExternalServiceCredentials.class);
|
||||
|
||||
assertThat(credentials.username()).isNotEmpty();
|
||||
assertThat(credentials.password()).isNotEmpty();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user