Remove obsolete ArtController

This commit is contained in:
Chris Eager 2024-12-19 17:27:14 -06:00 committed by Chris Eager
parent 0593e9e89f
commit 8c3ebdcbab
12 changed files with 14 additions and 182 deletions

View File

@ -89,9 +89,6 @@ paymentsService.userAuthenticationTokenSharedSecret: AAAAAAAAAAAAAAAAAAAAAAAAAAA
paymentsService.fixerApiKey: unset paymentsService.fixerApiKey: unset
paymentsService.coinMarketCapApiKey: unset paymentsService.coinMarketCapApiKey: unset
artService.userAuthenticationTokenSharedSecret: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= # base64-encoded 32-byte secret not shared with any external service, but used in ArtController
artService.userAuthenticationTokenUserIdSecret: AAAAAAAAAAA= # base64-encoded secret to obscure user phone numbers from Sticker Creator
currentReportingKey.secret: AAAAAAAAAAA= currentReportingKey.secret: AAAAAAAAAAA=
currentReportingKey.salt: AAAAAAAAAAA= currentReportingKey.salt: AAAAAAAAAAA=

View File

@ -331,10 +331,6 @@ paymentsService:
coinMarketCapCurrencyIds: coinMarketCapCurrencyIds:
MOB: 7878 MOB: 7878
artService:
userAuthenticationTokenSharedSecret: secret://artService.userAuthenticationTokenSharedSecret
userAuthenticationTokenUserIdSecret: secret://artService.userAuthenticationTokenUserIdSecret
badges: badges:
badges: badges:
- id: TEST - id: TEST

View File

@ -17,7 +17,6 @@ import org.whispersystems.textsecuregcm.attachments.TusConfiguration;
import org.whispersystems.textsecuregcm.configuration.ApnConfiguration; import org.whispersystems.textsecuregcm.configuration.ApnConfiguration;
import org.whispersystems.textsecuregcm.configuration.AppleAppStoreConfiguration; import org.whispersystems.textsecuregcm.configuration.AppleAppStoreConfiguration;
import org.whispersystems.textsecuregcm.configuration.AppleDeviceCheckConfiguration; import org.whispersystems.textsecuregcm.configuration.AppleDeviceCheckConfiguration;
import org.whispersystems.textsecuregcm.configuration.ArtServiceConfiguration;
import org.whispersystems.textsecuregcm.configuration.AwsCredentialsProviderFactory; import org.whispersystems.textsecuregcm.configuration.AwsCredentialsProviderFactory;
import org.whispersystems.textsecuregcm.configuration.BadgesConfiguration; import org.whispersystems.textsecuregcm.configuration.BadgesConfiguration;
import org.whispersystems.textsecuregcm.configuration.BraintreeConfiguration; import org.whispersystems.textsecuregcm.configuration.BraintreeConfiguration;
@ -217,11 +216,6 @@ public class WhisperServerConfiguration extends Configuration {
@JsonProperty @JsonProperty
private PaymentsServiceConfiguration paymentsService; private PaymentsServiceConfiguration paymentsService;
@Valid
@NotNull
@JsonProperty
private ArtServiceConfiguration artService;
@Valid @Valid
@NotNull @NotNull
@JsonProperty @JsonProperty
@ -469,10 +463,6 @@ public class WhisperServerConfiguration extends Configuration {
return paymentsService; return paymentsService;
} }
public ArtServiceConfiguration getArtServiceConfiguration() {
return artService;
}
public ZkConfig getZkConfig() { public ZkConfig getZkConfig() {
return zkConfig; return zkConfig;
} }

View File

@ -108,7 +108,6 @@ import org.whispersystems.textsecuregcm.configuration.secrets.SecretsModule;
import org.whispersystems.textsecuregcm.controllers.AccountController; import org.whispersystems.textsecuregcm.controllers.AccountController;
import org.whispersystems.textsecuregcm.controllers.AccountControllerV2; import org.whispersystems.textsecuregcm.controllers.AccountControllerV2;
import org.whispersystems.textsecuregcm.controllers.ArchiveController; import org.whispersystems.textsecuregcm.controllers.ArchiveController;
import org.whispersystems.textsecuregcm.controllers.ArtController;
import org.whispersystems.textsecuregcm.controllers.AttachmentControllerV4; import org.whispersystems.textsecuregcm.controllers.AttachmentControllerV4;
import org.whispersystems.textsecuregcm.controllers.CallLinkController; import org.whispersystems.textsecuregcm.controllers.CallLinkController;
import org.whispersystems.textsecuregcm.controllers.CallRoutingController; import org.whispersystems.textsecuregcm.controllers.CallRoutingController;
@ -214,9 +213,6 @@ import org.whispersystems.textsecuregcm.storage.AccountLockManager;
import org.whispersystems.textsecuregcm.storage.AccountPrincipalSupplier; import org.whispersystems.textsecuregcm.storage.AccountPrincipalSupplier;
import org.whispersystems.textsecuregcm.storage.Accounts; import org.whispersystems.textsecuregcm.storage.Accounts;
import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.devicecheck.AppleDeviceCheckManager;
import org.whispersystems.textsecuregcm.storage.devicecheck.AppleDeviceCheckTrustAnchor;
import org.whispersystems.textsecuregcm.storage.devicecheck.AppleDeviceChecks;
import org.whispersystems.textsecuregcm.storage.ChangeNumberManager; import org.whispersystems.textsecuregcm.storage.ChangeNumberManager;
import org.whispersystems.textsecuregcm.storage.ClientPublicKeys; import org.whispersystems.textsecuregcm.storage.ClientPublicKeys;
import org.whispersystems.textsecuregcm.storage.ClientPublicKeysManager; import org.whispersystems.textsecuregcm.storage.ClientPublicKeysManager;
@ -244,6 +240,9 @@ import org.whispersystems.textsecuregcm.storage.SubscriptionManager;
import org.whispersystems.textsecuregcm.storage.Subscriptions; import org.whispersystems.textsecuregcm.storage.Subscriptions;
import org.whispersystems.textsecuregcm.storage.VerificationSessionManager; import org.whispersystems.textsecuregcm.storage.VerificationSessionManager;
import org.whispersystems.textsecuregcm.storage.VerificationSessions; import org.whispersystems.textsecuregcm.storage.VerificationSessions;
import org.whispersystems.textsecuregcm.storage.devicecheck.AppleDeviceCheckManager;
import org.whispersystems.textsecuregcm.storage.devicecheck.AppleDeviceCheckTrustAnchor;
import org.whispersystems.textsecuregcm.storage.devicecheck.AppleDeviceChecks;
import org.whispersystems.textsecuregcm.subscriptions.AppleAppStoreManager; import org.whispersystems.textsecuregcm.subscriptions.AppleAppStoreManager;
import org.whispersystems.textsecuregcm.subscriptions.BankMandateTranslator; import org.whispersystems.textsecuregcm.subscriptions.BankMandateTranslator;
import org.whispersystems.textsecuregcm.subscriptions.BraintreeManager; import org.whispersystems.textsecuregcm.subscriptions.BraintreeManager;
@ -582,8 +581,6 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
config.getSecureStorageServiceConfiguration()); config.getSecureStorageServiceConfiguration());
ExternalServiceCredentialsGenerator paymentsCredentialsGenerator = PaymentsController.credentialsGenerator( ExternalServiceCredentialsGenerator paymentsCredentialsGenerator = PaymentsController.credentialsGenerator(
config.getPaymentsServiceConfiguration()); config.getPaymentsServiceConfiguration());
ExternalServiceCredentialsGenerator artCredentialsGenerator = ArtController.credentialsGenerator(
config.getArtServiceConfiguration());
ExternalServiceCredentialsGenerator svr2CredentialsGenerator = SecureValueRecovery2Controller.credentialsGenerator( ExternalServiceCredentialsGenerator svr2CredentialsGenerator = SecureValueRecovery2Controller.credentialsGenerator(
config.getSvr2Configuration()); config.getSvr2Configuration());
@ -1101,7 +1098,6 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
usernameHashZkProofVerifier), usernameHashZkProofVerifier),
new AccountControllerV2(accountsManager, changeNumberManager, phoneVerificationTokenManager, new AccountControllerV2(accountsManager, changeNumberManager, phoneVerificationTokenManager,
registrationLockVerificationManager, rateLimiters), registrationLockVerificationManager, rateLimiters),
new ArtController(rateLimiters, artCredentialsGenerator),
new AttachmentControllerV4(rateLimiters, gcsAttachmentGenerator, tusAttachmentGenerator, new AttachmentControllerV4(rateLimiters, gcsAttachmentGenerator, tusAttachmentGenerator,
experimentEnrollmentManager), experimentEnrollmentManager),
new ArchiveController(backupAuthManager, backupManager), new ArchiveController(backupAuthManager, backupManager),

View File

@ -1,21 +0,0 @@
/*
* Copyright 2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import static org.apache.commons.lang3.ObjectUtils.firstNonNull;
import jakarta.validation.constraints.NotNull;
import java.time.Duration;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes;
import org.whispersystems.textsecuregcm.util.ExactlySize;
public record ArtServiceConfiguration(@ExactlySize(32) SecretBytes userAuthenticationTokenSharedSecret,
@NotNull SecretBytes userAuthenticationTokenUserIdSecret,
@NotNull Duration tokenExpiration) {
public ArtServiceConfiguration {
tokenExpiration = firstNonNull(tokenExpiration, Duration.ofDays(1));
}
}

View File

@ -1,52 +0,0 @@
/*
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.controllers;
import io.dropwizard.auth.Auth;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import java.util.UUID;
import org.whispersystems.textsecuregcm.auth.AuthenticatedDevice;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.configuration.ArtServiceConfiguration;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.websocket.auth.ReadOnly;
@Path("/v1/art")
@Tag(name = "Art")
public class ArtController {
private final ExternalServiceCredentialsGenerator artServiceCredentialsGenerator;
private final RateLimiters rateLimiters;
public static ExternalServiceCredentialsGenerator credentialsGenerator(final ArtServiceConfiguration cfg) {
return ExternalServiceCredentialsGenerator
.builder(cfg.userAuthenticationTokenSharedSecret())
.withUserDerivationKey(cfg.userAuthenticationTokenUserIdSecret())
.prependUsername(false)
.truncateSignature(false)
.build();
}
public ArtController(final RateLimiters rateLimiters,
final ExternalServiceCredentialsGenerator artServiceCredentialsGenerator) {
this.artServiceCredentialsGenerator = artServiceCredentialsGenerator;
this.rateLimiters = rateLimiters;
}
@GET
@Path("/auth")
@Produces(MediaType.APPLICATION_JSON)
public ExternalServiceCredentials getAuth(final @ReadOnly @Auth AuthenticatedDevice auth)
throws RateLimitExceededException {
final UUID uuid = auth.getAccount().getUuid();
rateLimiters.forDescriptor(RateLimiters.For.EXTERNAL_SERVICE_CREDENTIALS).validate(uuid);
return artServiceCredentialsGenerator.generateForUuid(uuid);
}
}

View File

@ -16,21 +16,11 @@ import org.apache.commons.lang3.tuple.Pair;
import org.signal.chat.credentials.ExternalServiceType; import org.signal.chat.credentials.ExternalServiceType;
import org.whispersystems.textsecuregcm.WhisperServerConfiguration; import org.whispersystems.textsecuregcm.WhisperServerConfiguration;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.configuration.ArtServiceConfiguration;
import org.whispersystems.textsecuregcm.configuration.DirectoryV2ClientConfiguration; import org.whispersystems.textsecuregcm.configuration.DirectoryV2ClientConfiguration;
import org.whispersystems.textsecuregcm.configuration.PaymentsServiceConfiguration; import org.whispersystems.textsecuregcm.configuration.PaymentsServiceConfiguration;
import org.whispersystems.textsecuregcm.configuration.SecureValueRecovery2Configuration; import org.whispersystems.textsecuregcm.configuration.SecureValueRecovery2Configuration;
enum ExternalServiceDefinitions { enum ExternalServiceDefinitions {
ART(ExternalServiceType.EXTERNAL_SERVICE_TYPE_ART, (chatConfig, clock) -> {
final ArtServiceConfiguration cfg = chatConfig.getArtServiceConfiguration();
return ExternalServiceCredentialsGenerator
.builder(cfg.userAuthenticationTokenSharedSecret())
.withUserDerivationKey(cfg.userAuthenticationTokenUserIdSecret())
.prependUsername(false)
.truncateSignature(false)
.build();
}),
DIRECTORY(ExternalServiceType.EXTERNAL_SERVICE_TYPE_DIRECTORY, (chatConfig, clock) -> { DIRECTORY(ExternalServiceType.EXTERNAL_SERVICE_TYPE_DIRECTORY, (chatConfig, clock) -> {
final DirectoryV2ClientConfiguration cfg = chatConfig.getDirectoryV2Configuration().getDirectoryV2ClientConfiguration(); final DirectoryV2ClientConfiguration cfg = chatConfig.getDirectoryV2Configuration().getDirectoryV2ClientConfiguration();
return ExternalServiceCredentialsGenerator return ExternalServiceCredentialsGenerator

View File

@ -49,11 +49,10 @@ service ExternalServiceCredentialsAnonymous {
enum ExternalServiceType { enum ExternalServiceType {
EXTERNAL_SERVICE_TYPE_UNSPECIFIED = 0; EXTERNAL_SERVICE_TYPE_UNSPECIFIED = 0;
EXTERNAL_SERVICE_TYPE_ART = 1; EXTERNAL_SERVICE_TYPE_DIRECTORY = 1;
EXTERNAL_SERVICE_TYPE_DIRECTORY = 2; EXTERNAL_SERVICE_TYPE_PAYMENTS = 2;
EXTERNAL_SERVICE_TYPE_PAYMENTS = 3; EXTERNAL_SERVICE_TYPE_STORAGE = 3;
EXTERNAL_SERVICE_TYPE_STORAGE = 4; EXTERNAL_SERVICE_TYPE_SVR = 4;
EXTERNAL_SERVICE_TYPE_SVR = 5;
} }
message GetExternalServiceCredentialsRequest { message GetExternalServiceCredentialsRequest {

View File

@ -1,56 +0,0 @@
/*
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.controllers;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.whispersystems.textsecuregcm.util.MockUtils.randomSecretBytes;
import io.dropwizard.auth.AuthValueFactoryProvider;
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
import io.dropwizard.testing.junit5.ResourceExtension;
import java.time.Duration;
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.whispersystems.textsecuregcm.auth.AuthenticatedDevice;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.configuration.ArtServiceConfiguration;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
import org.whispersystems.textsecuregcm.util.MockUtils;
import org.whispersystems.textsecuregcm.util.SystemMapper;
@ExtendWith(DropwizardExtensionsSupport.class)
class ArtControllerTest {
private static final ArtServiceConfiguration ART_SERVICE_CONFIGURATION = new ArtServiceConfiguration(
randomSecretBytes(32), randomSecretBytes(32), Duration.ofDays(1));
private static final ExternalServiceCredentialsGenerator artCredentialsGenerator = ArtController.credentialsGenerator(ART_SERVICE_CONFIGURATION);
private static final RateLimiters rateLimiters = mock(RateLimiters.class);
private static final ResourceExtension resources = ResourceExtension.builder()
.addProvider(AuthHelper.getAuthFilter())
.addProvider(new AuthValueFactoryProvider.Binder<>(AuthenticatedDevice.class))
.setMapper(SystemMapper.jsonMapper())
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
.addResource(new ArtController(rateLimiters, artCredentialsGenerator))
.build();
@Test
void testGetAuthToken() {
MockUtils.updateRateLimiterResponseToAllow(rateLimiters, RateLimiters.For.EXTERNAL_SERVICE_CREDENTIALS, AuthHelper.VALID_UUID);
final ExternalServiceCredentials token =
resources.getJerseyTest()
.target("/v1/art/auth")
.request()
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
.get(ExternalServiceCredentials.class);
assertThat(token.password()).isNotEmpty();
assertThat(token.username()).isNotEmpty();
}
}

View File

@ -43,7 +43,7 @@ import reactor.core.publisher.Mono;
public class ExternalServiceCredentialsGrpcServiceTest public class ExternalServiceCredentialsGrpcServiceTest
extends SimpleBaseGrpcTest<ExternalServiceCredentialsGrpcService, ExternalServiceCredentialsGrpc.ExternalServiceCredentialsBlockingStub> { extends SimpleBaseGrpcTest<ExternalServiceCredentialsGrpcService, ExternalServiceCredentialsGrpc.ExternalServiceCredentialsBlockingStub> {
private static final ExternalServiceCredentialsGenerator ART_CREDENTIALS_GENERATOR = Mockito.spy(ExternalServiceCredentialsGenerator private static final ExternalServiceCredentialsGenerator DIRECTORY_CREDENTIALS_GENERATOR = Mockito.spy(ExternalServiceCredentialsGenerator
.builder(TestRandomUtil.nextBytes(32)) .builder(TestRandomUtil.nextBytes(32))
.withUserDerivationKey(TestRandomUtil.nextBytes(32)) .withUserDerivationKey(TestRandomUtil.nextBytes(32))
.prependUsername(false) .prependUsername(false)
@ -62,14 +62,14 @@ public class ExternalServiceCredentialsGrpcServiceTest
@Override @Override
protected ExternalServiceCredentialsGrpcService createServiceBeforeEachTest() { protected ExternalServiceCredentialsGrpcService createServiceBeforeEachTest() {
return new ExternalServiceCredentialsGrpcService(Map.of( return new ExternalServiceCredentialsGrpcService(Map.of(
ExternalServiceType.EXTERNAL_SERVICE_TYPE_ART, ART_CREDENTIALS_GENERATOR, ExternalServiceType.EXTERNAL_SERVICE_TYPE_DIRECTORY, DIRECTORY_CREDENTIALS_GENERATOR,
ExternalServiceType.EXTERNAL_SERVICE_TYPE_PAYMENTS, PAYMENTS_CREDENTIALS_GENERATOR ExternalServiceType.EXTERNAL_SERVICE_TYPE_PAYMENTS, PAYMENTS_CREDENTIALS_GENERATOR
), rateLimiters); ), rateLimiters);
} }
static Stream<Arguments> testSuccess() { static Stream<Arguments> testSuccess() {
return Stream.of( return Stream.of(
Arguments.of(ExternalServiceType.EXTERNAL_SERVICE_TYPE_ART, ART_CREDENTIALS_GENERATOR), Arguments.of(ExternalServiceType.EXTERNAL_SERVICE_TYPE_DIRECTORY, DIRECTORY_CREDENTIALS_GENERATOR),
Arguments.of(ExternalServiceType.EXTERNAL_SERVICE_TYPE_PAYMENTS, PAYMENTS_CREDENTIALS_GENERATOR) Arguments.of(ExternalServiceType.EXTERNAL_SERVICE_TYPE_PAYMENTS, PAYMENTS_CREDENTIALS_GENERATOR)
); );
} }
@ -111,14 +111,14 @@ public class ExternalServiceCredentialsGrpcServiceTest
public void testRateLimitExceeded() throws Exception { public void testRateLimitExceeded() throws Exception {
final Duration retryAfter = MockUtils.updateRateLimiterResponseToFail( final Duration retryAfter = MockUtils.updateRateLimiterResponseToFail(
rateLimiters, RateLimiters.For.EXTERNAL_SERVICE_CREDENTIALS, AUTHENTICATED_ACI, Duration.ofSeconds(100)); rateLimiters, RateLimiters.For.EXTERNAL_SERVICE_CREDENTIALS, AUTHENTICATED_ACI, Duration.ofSeconds(100));
Mockito.reset(ART_CREDENTIALS_GENERATOR); Mockito.reset(DIRECTORY_CREDENTIALS_GENERATOR);
assertRateLimitExceeded( assertRateLimitExceeded(
retryAfter, retryAfter,
() -> authenticatedServiceStub().getExternalServiceCredentials( () -> authenticatedServiceStub().getExternalServiceCredentials(
GetExternalServiceCredentialsRequest.newBuilder() GetExternalServiceCredentialsRequest.newBuilder()
.setExternalService(ExternalServiceType.EXTERNAL_SERVICE_TYPE_ART) .setExternalService(ExternalServiceType.EXTERNAL_SERVICE_TYPE_DIRECTORY)
.build()), .build()),
ART_CREDENTIALS_GENERATOR DIRECTORY_CREDENTIALS_GENERATOR
); );
} }
@ -126,7 +126,7 @@ public class ExternalServiceCredentialsGrpcServiceTest
public void testUnauthenticatedCall() throws Exception { public void testUnauthenticatedCall() throws Exception {
assertStatusUnauthenticated(() -> unauthenticatedServiceStub().getExternalServiceCredentials( assertStatusUnauthenticated(() -> unauthenticatedServiceStub().getExternalServiceCredentials(
GetExternalServiceCredentialsRequest.newBuilder() GetExternalServiceCredentialsRequest.newBuilder()
.setExternalService(ExternalServiceType.EXTERNAL_SERVICE_TYPE_ART) .setExternalService(ExternalServiceType.EXTERNAL_SERVICE_TYPE_DIRECTORY)
.build())); .build()));
} }

View File

@ -159,9 +159,6 @@ paymentsService.userAuthenticationTokenSharedSecret: AAAAAAAAAAAAAAAAAAAAAAAAAAA
paymentsService.fixerApiKey: unset paymentsService.fixerApiKey: unset
paymentsService.coinMarketCapApiKey: unset paymentsService.coinMarketCapApiKey: unset
artService.userAuthenticationTokenSharedSecret: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= # base64-encoded 32-byte secret not shared with any external service, but used in ArtController
artService.userAuthenticationTokenUserIdSecret: AAAAAAAAAAA= # base64-encoded secret to obscure user phone numbers from Sticker Creator
currentReportingKey.secret: AAAAAAAAAAA= currentReportingKey.secret: AAAAAAAAAAA=
currentReportingKey.salt: AAAAAAAAAAA= currentReportingKey.salt: AAAAAAAAAAA=

View File

@ -326,10 +326,6 @@ paymentsService:
externalClients: externalClients:
type: stub type: stub
artService:
userAuthenticationTokenSharedSecret: secret://artService.userAuthenticationTokenSharedSecret
userAuthenticationTokenUserIdSecret: secret://artService.userAuthenticationTokenUserIdSecret
badges: badges:
badges: badges:
- id: TEST - id: TEST