OpenAPI support
This commit is contained in:
parent
047f4a1c00
commit
c3f4956ead
|
@ -0,0 +1,33 @@
|
|||
name: Update Documentation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
cache: 'maven'
|
||||
- name: Compile and Build OpenAPI file
|
||||
run: ./mvnw compile
|
||||
- name: Update Documentation
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
cp -r api-doc/target/openapi/signal-server-openapi.yaml /tmp/
|
||||
git config user.email "github@signal.org"
|
||||
git config user.name "Documentation Updater"
|
||||
git fetch origin gh-pages
|
||||
git checkout gh-pages
|
||||
cp /tmp/signal-server-openapi.yaml .
|
||||
git commit -a -m "Updating documentation"
|
||||
git push origin gh-pages
|
|
@ -1,6 +1,9 @@
|
|||
name: Service CI
|
||||
|
||||
on: [push]
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- gh-pages
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>TextSecureServer</artifactId>
|
||||
<groupId>org.whispersystems.textsecure</groupId>
|
||||
<version>JGITVER</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>api-doc</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.whispersystems.textsecure</groupId>
|
||||
<artifactId>service</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-maven-plugin</artifactId>
|
||||
<version>2.2.8</version>
|
||||
<configuration>
|
||||
<outputFileName>signal-server-openapi</outputFileName>
|
||||
<outputPath>${project.build.directory}/openapi</outputPath>
|
||||
<outputFormat>YAML</outputFormat>
|
||||
<configurationFilePath>${project.basedir}/src/main/resources/openapi/openapi-configuration.yaml
|
||||
</configurationFilePath>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>compile</phase>
|
||||
<goals>
|
||||
<goal>resolve</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.openapi;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.type.SimpleType;
|
||||
import io.dropwizard.auth.Auth;
|
||||
import io.swagger.v3.jaxrs2.ResolvedParameter;
|
||||
import io.swagger.v3.jaxrs2.ext.AbstractOpenAPIExtension;
|
||||
import io.swagger.v3.jaxrs2.ext.OpenAPIExtension;
|
||||
import io.swagger.v3.oas.models.Components;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.Set;
|
||||
import javax.ws.rs.Consumes;
|
||||
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
|
||||
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount;
|
||||
|
||||
/**
|
||||
* One of the extension mechanisms of Swagger Core library (OpenAPI processor) is via custom implementations
|
||||
* of the {@link AbstractOpenAPIExtension} class.
|
||||
* <p/>
|
||||
* The purpose of this extension is to customize certain aspects of the OpenAPI model generation on a lower level.
|
||||
* This extension works in coordination with {@link OpenApiReader} that has access to the model on a higher level.
|
||||
* <p/>
|
||||
* The extension is enabled by being listed in {@code META-INF/services/io.swagger.v3.jaxrs2.ext.OpenAPIExtension} file.
|
||||
* @see ServiceLoader
|
||||
* @see OpenApiReader
|
||||
* @see <a href="https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Extensions">Swagger 2.X Extensions</a>
|
||||
*/
|
||||
public class OpenApiExtension extends AbstractOpenAPIExtension {
|
||||
|
||||
public static final ResolvedParameter AUTHENTICATED_ACCOUNT = new ResolvedParameter();
|
||||
|
||||
public static final ResolvedParameter OPTIONAL_AUTHENTICATED_ACCOUNT = new ResolvedParameter();
|
||||
|
||||
public static final ResolvedParameter DISABLED_PERMITTED_AUTHENTICATED_ACCOUNT = new ResolvedParameter();
|
||||
|
||||
public static final ResolvedParameter OPTIONAL_DISABLED_PERMITTED_AUTHENTICATED_ACCOUNT = new ResolvedParameter();
|
||||
|
||||
/**
|
||||
* When parsing endpoint methods, Swagger will treat the first parameter not annotated as header/path/query param
|
||||
* as a request body (and will ignore other not annotated parameters). In our case, this behavior conflicts with
|
||||
* the {@code @Auth}-annotated parameters. Here we're checking if parameters are known to be anything other than
|
||||
* a body and return an appropriate {@link ResolvedParameter} representation.
|
||||
*/
|
||||
@Override
|
||||
public ResolvedParameter extractParameters(
|
||||
final List<Annotation> annotations,
|
||||
final Type type,
|
||||
final Set<Type> typesToSkip,
|
||||
final Components components,
|
||||
final Consumes classConsumes,
|
||||
final Consumes methodConsumes,
|
||||
final boolean includeRequestBody,
|
||||
final JsonView jsonViewAnnotation,
|
||||
final Iterator<OpenAPIExtension> chain) {
|
||||
|
||||
if (annotations.stream().anyMatch(a -> a.annotationType().equals(Auth.class))) {
|
||||
// this is the case of authenticated endpoint,
|
||||
if (type instanceof SimpleType simpleType
|
||||
&& simpleType.getRawClass().equals(AuthenticatedAccount.class)) {
|
||||
return AUTHENTICATED_ACCOUNT;
|
||||
}
|
||||
if (type instanceof SimpleType simpleType
|
||||
&& simpleType.getRawClass().equals(DisabledPermittedAuthenticatedAccount.class)) {
|
||||
return DISABLED_PERMITTED_AUTHENTICATED_ACCOUNT;
|
||||
}
|
||||
if (type instanceof SimpleType simpleType
|
||||
&& isOptionalOfType(simpleType, AuthenticatedAccount.class)) {
|
||||
return OPTIONAL_AUTHENTICATED_ACCOUNT;
|
||||
}
|
||||
if (type instanceof SimpleType simpleType
|
||||
&& isOptionalOfType(simpleType, DisabledPermittedAuthenticatedAccount.class)) {
|
||||
return OPTIONAL_DISABLED_PERMITTED_AUTHENTICATED_ACCOUNT;
|
||||
}
|
||||
}
|
||||
|
||||
return super.extractParameters(
|
||||
annotations,
|
||||
type,
|
||||
typesToSkip,
|
||||
components,
|
||||
classConsumes,
|
||||
methodConsumes,
|
||||
includeRequestBody,
|
||||
jsonViewAnnotation,
|
||||
chain);
|
||||
}
|
||||
|
||||
private static boolean isOptionalOfType(final SimpleType simpleType, final Class<?> expectedType) {
|
||||
if (!simpleType.getRawClass().equals(Optional.class)) {
|
||||
return false;
|
||||
}
|
||||
final List<JavaType> typeParameters = simpleType.getBindings().getTypeParameters();
|
||||
if (typeParameters.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return typeParameters.get(0) instanceof SimpleType optionalParameterType
|
||||
&& optionalParameterType.getRawClass().equals(expectedType);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.openapi;
|
||||
|
||||
import static com.google.common.base.MoreObjects.firstNonNull;
|
||||
import static org.signal.openapi.OpenApiExtension.AUTHENTICATED_ACCOUNT;
|
||||
import static org.signal.openapi.OpenApiExtension.DISABLED_PERMITTED_AUTHENTICATED_ACCOUNT;
|
||||
import static org.signal.openapi.OpenApiExtension.OPTIONAL_AUTHENTICATED_ACCOUNT;
|
||||
import static org.signal.openapi.OpenApiExtension.OPTIONAL_DISABLED_PERMITTED_AUTHENTICATED_ACCOUNT;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import io.swagger.v3.jaxrs2.Reader;
|
||||
import io.swagger.v3.jaxrs2.ResolvedParameter;
|
||||
import io.swagger.v3.oas.models.Operation;
|
||||
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import javax.ws.rs.Consumes;
|
||||
|
||||
/**
|
||||
* One of the extension mechanisms of Swagger Core library (OpenAPI processor) is via custom implementations
|
||||
* of the {@link Reader} class.
|
||||
* <p/>
|
||||
* The purpose of this extension is to customize certain aspects of the OpenAPI model generation on a higher level.
|
||||
* This extension works in coordination with {@link OpenApiExtension} that has access to the model on a lower level.
|
||||
* <p/>
|
||||
* The extension is enabled by being listed in {@code resources/openapi/openapi-configuration.yaml} file.
|
||||
* @see OpenApiExtension
|
||||
* @see <a href="https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Extensions">Swagger 2.X Extensions</a>
|
||||
*/
|
||||
public class OpenApiReader extends Reader {
|
||||
|
||||
private static final String AUTHENTICATED_ACCOUNT_AUTH_SCHEMA = "authenticatedAccount";
|
||||
|
||||
|
||||
/**
|
||||
* Overriding this method allows converting a resolved parameter into other operation entities,
|
||||
* in this case, into security requirements.
|
||||
*/
|
||||
@Override
|
||||
protected ResolvedParameter getParameters(
|
||||
final Type type,
|
||||
final List<Annotation> annotations,
|
||||
final Operation operation,
|
||||
final Consumes classConsumes,
|
||||
final Consumes methodConsumes,
|
||||
final JsonView jsonViewAnnotation) {
|
||||
final ResolvedParameter resolved = super.getParameters(
|
||||
type, annotations, operation, classConsumes, methodConsumes, jsonViewAnnotation);
|
||||
|
||||
if (resolved == AUTHENTICATED_ACCOUNT || resolved == DISABLED_PERMITTED_AUTHENTICATED_ACCOUNT) {
|
||||
operation.setSecurity(ImmutableList.<SecurityRequirement>builder()
|
||||
.addAll(firstNonNull(operation.getSecurity(), Collections.emptyList()))
|
||||
.add(new SecurityRequirement().addList(AUTHENTICATED_ACCOUNT_AUTH_SCHEMA))
|
||||
.build());
|
||||
}
|
||||
if (resolved == OPTIONAL_AUTHENTICATED_ACCOUNT || resolved == OPTIONAL_DISABLED_PERMITTED_AUTHENTICATED_ACCOUNT) {
|
||||
operation.setSecurity(ImmutableList.<SecurityRequirement>builder()
|
||||
.addAll(firstNonNull(operation.getSecurity(), Collections.emptyList()))
|
||||
.add(new SecurityRequirement().addList(AUTHENTICATED_ACCOUNT_AUTH_SCHEMA))
|
||||
.add(new SecurityRequirement())
|
||||
.build());
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
org.signal.openapi.OpenApiExtension
|
|
@ -0,0 +1,25 @@
|
|||
resourcePackages:
|
||||
- org.whispersystems.textsecuregcm
|
||||
prettyPrint: true
|
||||
cacheTTL: 0
|
||||
readerClass: org.signal.openapi.OpenApiReader
|
||||
openAPI:
|
||||
info:
|
||||
title: Signal Server API
|
||||
license:
|
||||
name: AGPL-3.0-only
|
||||
url: https://www.gnu.org/licenses/agpl-3.0.txt
|
||||
servers:
|
||||
- url: https://chat.signal.org
|
||||
description: Production service
|
||||
- url: https://chat.staging.signal.org
|
||||
description: Staging service
|
||||
components:
|
||||
securitySchemes:
|
||||
authenticatedAccount:
|
||||
type: http
|
||||
scheme: basic
|
||||
description: |
|
||||
Account authentication is based on Basic authentication schema,
|
||||
where `username` has a format of `<user_id>[.<device_id>]`. If `device_id` is not specified,
|
||||
user's `main` device is assumed.
|
3
pom.xml
3
pom.xml
|
@ -38,10 +38,11 @@
|
|||
</pluginRepositories>
|
||||
|
||||
<modules>
|
||||
<module>api-doc</module>
|
||||
<module>event-logger</module>
|
||||
<module>redis-dispatch</module>
|
||||
<module>websocket-resources</module>
|
||||
<module>service</module>
|
||||
<module>websocket-resources</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
|
|
|
@ -11,6 +11,18 @@
|
|||
<artifactId>service</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-jaxrs2</artifactId>
|
||||
<version>2.2.8</version>
|
||||
<exclusions>
|
||||
<!-- org.yaml:snakeyaml is causing a dependency convergence error -->
|
||||
<exclusion>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
|
|
|
@ -111,6 +111,7 @@ import org.whispersystems.textsecuregcm.util.Util;
|
|||
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
@Path("/v1/accounts")
|
||||
@io.swagger.v3.oas.annotations.tags.Tag(name = "Account")
|
||||
public class AccountController {
|
||||
public static final int MAXIMUM_USERNAME_HASHES_LIST_LENGTH = 20;
|
||||
public static final int USERNAME_HASH_LENGTH = 32;
|
||||
|
|
|
@ -43,6 +43,7 @@ import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
|||
import org.whispersystems.textsecuregcm.storage.ChangeNumberManager;
|
||||
|
||||
@Path("/v2/accounts")
|
||||
@io.swagger.v3.oas.annotations.tags.Tag(name = "Account")
|
||||
public class AccountControllerV2 {
|
||||
|
||||
private static final String CHANGE_NUMBER_COUNTER_NAME = name(AccountControllerV2.class, "changeNumber");
|
||||
|
|
|
@ -7,6 +7,7 @@ package org.whispersystems.textsecuregcm.controllers;
|
|||
|
||||
import com.codahale.metrics.annotation.Timed;
|
||||
import io.dropwizard.auth.Auth;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import java.util.UUID;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
|
@ -19,6 +20,7 @@ import org.whispersystems.textsecuregcm.configuration.ArtServiceConfiguration;
|
|||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
|
||||
@Path("/v1/art")
|
||||
@Tag(name = "Art")
|
||||
public class ArtController {
|
||||
private final ExternalServiceCredentialsGenerator artServiceCredentialsGenerator;
|
||||
private final RateLimiters rateLimiters;
|
||||
|
|
|
@ -7,6 +7,7 @@ package org.whispersystems.textsecuregcm.controllers;
|
|||
|
||||
import com.codahale.metrics.annotation.Timed;
|
||||
import io.dropwizard.auth.Auth;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import java.security.SecureRandom;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
|
@ -24,6 +25,7 @@ import org.whispersystems.textsecuregcm.util.Conversions;
|
|||
import org.whispersystems.textsecuregcm.util.Pair;
|
||||
|
||||
@Path("/v2/attachments")
|
||||
@Tag(name = "Attachments")
|
||||
public class AttachmentControllerV2 {
|
||||
|
||||
private final PostPolicyGenerator policyGenerator;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2013-2021 Signal Messenger, LLC
|
||||
* Copyright 2013 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
|
@ -7,6 +7,7 @@ package org.whispersystems.textsecuregcm.controllers;
|
|||
|
||||
import com.codahale.metrics.annotation.Timed;
|
||||
import io.dropwizard.auth.Auth;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.SecureRandom;
|
||||
|
@ -29,6 +30,7 @@ import org.whispersystems.textsecuregcm.limits.RateLimiter;
|
|||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
|
||||
@Path("/v3/attachments")
|
||||
@Tag(name = "Attachments")
|
||||
public class AttachmentControllerV3 {
|
||||
|
||||
@Nonnull
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2013-2022 Signal Messenger, LLC
|
||||
* Copyright 2013 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
|
@ -11,6 +11,7 @@ import com.codahale.metrics.annotation.Timed;
|
|||
import com.google.common.annotations.VisibleForTesting;
|
||||
import io.dropwizard.auth.Auth;
|
||||
import io.micrometer.core.instrument.Metrics;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
|
@ -42,6 +43,7 @@ import org.whispersystems.textsecuregcm.util.Util;
|
|||
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
@Path("/v1/certificate")
|
||||
@Tag(name = "Certificate")
|
||||
public class CertificateController {
|
||||
|
||||
private final CertificateGenerator certificateGenerator;
|
||||
|
|
|
@ -12,6 +12,7 @@ import com.google.common.net.HttpHeaders;
|
|||
import io.dropwizard.auth.Auth;
|
||||
import io.micrometer.core.instrument.Metrics;
|
||||
import io.micrometer.core.instrument.Tags;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import java.io.IOException;
|
||||
import java.util.NoSuchElementException;
|
||||
import javax.validation.Valid;
|
||||
|
@ -33,6 +34,7 @@ import org.whispersystems.textsecuregcm.push.NotPushRegisteredException;
|
|||
import org.whispersystems.textsecuregcm.util.HeaderUtils;
|
||||
|
||||
@Path("/v1/challenge")
|
||||
@Tag(name = "Challenge")
|
||||
public class ChallengeController {
|
||||
|
||||
private final RateLimitChallengeManager rateLimitChallengeManager;
|
||||
|
|
|
@ -8,6 +8,7 @@ import com.codahale.metrics.annotation.Timed;
|
|||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.net.HttpHeaders;
|
||||
import io.dropwizard.auth.Auth;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
@ -50,6 +51,7 @@ import org.whispersystems.textsecuregcm.util.Util;
|
|||
import org.whispersystems.textsecuregcm.util.VerificationCode;
|
||||
|
||||
@Path("/v1/devices")
|
||||
@Tag(name = "Devices")
|
||||
public class DeviceController {
|
||||
|
||||
private static final int MAX_DEVICES = 6;
|
||||
|
|
|
@ -6,6 +6,7 @@ package org.whispersystems.textsecuregcm.controllers;
|
|||
|
||||
import com.codahale.metrics.annotation.Timed;
|
||||
import io.dropwizard.auth.Auth;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.PUT;
|
||||
|
@ -18,6 +19,7 @@ import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator
|
|||
import org.whispersystems.textsecuregcm.configuration.DirectoryClientConfiguration;
|
||||
|
||||
@Path("/v1/directory")
|
||||
@Tag(name = "Directory")
|
||||
public class DirectoryController {
|
||||
|
||||
private final ExternalServiceCredentialsGenerator directoryServiceTokenGenerator;
|
||||
|
|
|
@ -7,6 +7,7 @@ package org.whispersystems.textsecuregcm.controllers;
|
|||
import com.codahale.metrics.annotation.Timed;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import io.dropwizard.auth.Auth;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import java.time.Clock;
|
||||
import java.util.UUID;
|
||||
import javax.ws.rs.GET;
|
||||
|
@ -20,6 +21,7 @@ import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator
|
|||
import org.whispersystems.textsecuregcm.configuration.DirectoryV2ClientConfiguration;
|
||||
|
||||
@Path("/v2/directory")
|
||||
@Tag(name = "Directory")
|
||||
public class DirectoryV2Controller {
|
||||
|
||||
private final ExternalServiceCredentialsGenerator directoryServiceTokenGenerator;
|
||||
|
|
|
@ -7,6 +7,7 @@ package org.whispersystems.textsecuregcm.controllers;
|
|||
|
||||
import com.codahale.metrics.annotation.Timed;
|
||||
import io.dropwizard.auth.Auth;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
|
@ -42,6 +43,7 @@ import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
|||
import org.whispersystems.textsecuregcm.storage.RedeemedReceiptsManager;
|
||||
|
||||
@Path("/v1/donation")
|
||||
@Tag(name = "Donations")
|
||||
public class DonationController {
|
||||
|
||||
public interface ReceiptCredentialPresentationFactory {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2013-2021 Signal Messenger, LLC
|
||||
* Copyright 2013 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
|
@ -11,6 +11,7 @@ import com.codahale.metrics.annotation.Timed;
|
|||
import io.dropwizard.auth.Auth;
|
||||
import io.micrometer.core.instrument.Metrics;
|
||||
import io.micrometer.core.instrument.Tags;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
@ -24,6 +25,7 @@ import org.whispersystems.websocket.session.WebSocketSessionContext;
|
|||
|
||||
|
||||
@Path("/v1/keepalive")
|
||||
@Tag(name = "Keep Alive")
|
||||
public class KeepAliveController {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(KeepAliveController.class);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2013-2021 Signal Messenger, LLC
|
||||
* Copyright 2013 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.controllers;
|
||||
|
@ -11,6 +11,7 @@ import com.google.common.net.HttpHeaders;
|
|||
import io.dropwizard.auth.Auth;
|
||||
import io.micrometer.core.instrument.Metrics;
|
||||
import io.micrometer.core.instrument.Tags;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
|
@ -53,6 +54,7 @@ import org.whispersystems.textsecuregcm.storage.Keys;
|
|||
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
@Path("/v2/keys")
|
||||
@Tag(name = "Keys")
|
||||
public class KeysController {
|
||||
|
||||
private final RateLimiters rateLimiters;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2013-2022 Signal Messenger, LLC
|
||||
* Copyright 2013 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.controllers;
|
||||
|
@ -108,6 +108,7 @@ import reactor.core.scheduler.Schedulers;
|
|||
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
@Path("/v1/messages")
|
||||
@io.swagger.v3.oas.annotations.tags.Tag(name = "Messages")
|
||||
public class MessageController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MessageController.class);
|
||||
|
|
|
@ -7,6 +7,7 @@ package org.whispersystems.textsecuregcm.controllers;
|
|||
|
||||
import com.codahale.metrics.annotation.Timed;
|
||||
import io.dropwizard.auth.Auth;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
|
@ -19,6 +20,7 @@ import org.whispersystems.textsecuregcm.currency.CurrencyConversionManager;
|
|||
import org.whispersystems.textsecuregcm.entities.CurrencyConversionEntityList;
|
||||
|
||||
@Path("/v1/payments")
|
||||
@Tag(name = "Payments")
|
||||
public class PaymentsController {
|
||||
|
||||
private final ExternalServiceCredentialsGenerator paymentsServiceCredentialsGenerator;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2013-2021 Signal Messenger, LLC
|
||||
* Copyright 2013 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
|
@ -14,6 +14,7 @@ import io.dropwizard.auth.Auth;
|
|||
import io.micrometer.core.instrument.Counter;
|
||||
import io.micrometer.core.instrument.Metrics;
|
||||
import io.micrometer.core.instrument.Tags;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.vavr.Tuple;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
@ -111,6 +112,7 @@ import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
|
|||
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
@Path("/v1/profile")
|
||||
@Tag(name = "Profile")
|
||||
public class ProfileController {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ProfileController.class);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* Copyright 2013 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
|
@ -7,6 +7,7 @@ package org.whispersystems.textsecuregcm.controllers;
|
|||
|
||||
import com.codahale.metrics.annotation.Timed;
|
||||
import io.dropwizard.auth.Auth;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import java.util.Base64;
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
@ -25,6 +26,7 @@ import org.whispersystems.textsecuregcm.push.ProvisioningManager;
|
|||
import org.whispersystems.textsecuregcm.websocket.ProvisioningAddress;
|
||||
|
||||
@Path("/v1/provisioning")
|
||||
@Tag(name = "Provisioning")
|
||||
public class ProvisioningController {
|
||||
|
||||
private final RateLimiters rateLimiters;
|
||||
|
|
|
@ -44,6 +44,7 @@ import org.whispersystems.textsecuregcm.util.HeaderUtils;
|
|||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
@Path("/v1/registration")
|
||||
@io.swagger.v3.oas.annotations.tags.Tag(name = "Registration")
|
||||
public class RegistrationController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(RegistrationController.class);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2013-2022 Signal Messenger, LLC
|
||||
* Copyright 2013 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
|
@ -8,6 +8,7 @@ package org.whispersystems.textsecuregcm.controllers;
|
|||
import com.codahale.metrics.annotation.Timed;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import io.dropwizard.auth.Auth;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
|
@ -44,6 +45,7 @@ import org.whispersystems.textsecuregcm.util.Conversions;
|
|||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
@Path("/v1/config")
|
||||
@Tag(name = "Remote Config")
|
||||
public class RemoteConfigController {
|
||||
|
||||
private final RemoteConfigsManager remoteConfigsManager;
|
||||
|
|
|
@ -9,6 +9,9 @@ import static java.util.Objects.requireNonNull;
|
|||
|
||||
import com.codahale.metrics.annotation.Timed;
|
||||
import io.dropwizard.auth.Auth;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import java.time.Clock;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
@ -37,6 +40,7 @@ import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
|||
import org.whispersystems.textsecuregcm.util.UUIDUtil;
|
||||
|
||||
@Path("/v1/backup")
|
||||
@Tag(name = "Secure Value Recovery")
|
||||
public class SecureBackupController {
|
||||
|
||||
private static final long MAX_AGE_SECONDS = TimeUnit.DAYS.toSeconds(30);
|
||||
|
@ -70,6 +74,15 @@ public class SecureBackupController {
|
|||
@GET
|
||||
@Path("/auth")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(
|
||||
summary = "Generate credentials for SVR",
|
||||
description = """
|
||||
Generate SVR service credentials. Generated credentials have an expiration time of 30 days
|
||||
(however, the TTL is fully controlled by the server side and may change even for already generated credentials).
|
||||
"""
|
||||
)
|
||||
@ApiResponse(responseCode = "200", description = "`JSON` with generated credentials.", useReturnTypeSchema = true)
|
||||
@ApiResponse(responseCode = "401", description = "Account authentication check failed.")
|
||||
public ExternalServiceCredentials getAuth(final @Auth AuthenticatedAccount auth) {
|
||||
return credentialsGenerator.generateForUuid(auth.getAccount().getUuid());
|
||||
}
|
||||
|
@ -80,6 +93,18 @@ public class SecureBackupController {
|
|||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@RateLimitedByIp(RateLimiters.For.BACKUP_AUTH_CHECK)
|
||||
@Operation(
|
||||
summary = "Check SVR credentials",
|
||||
description = """
|
||||
Over time, clients may wind up with multiple sets of KBS authentication credentials in cloud storage.
|
||||
To determine which set is most current and should be used to communicate with SVR to retrieve a master password
|
||||
(from which a registration recovery password can be derived), clients should call this endpoint
|
||||
with a list of stored credentials. The response will identify which (if any) set of credentials are appropriate for communicating with SVR.
|
||||
"""
|
||||
)
|
||||
@ApiResponse(responseCode = "200", description = "`JSON` with the check results.", useReturnTypeSchema = true)
|
||||
@ApiResponse(responseCode = "422", description = "Provided list of KBS credentials could not be parsed")
|
||||
@ApiResponse(responseCode = "400", description = "`POST` request body is not a valid `JSON`")
|
||||
public AuthCheckResponse authCheck(@NotNull @Valid final AuthCheckRequest request) {
|
||||
final Map<String, AuthCheckResponse.Result> results = new HashMap<>();
|
||||
final Map<String, Pair<UUID, Long>> tokenToUuid = new HashMap<>();
|
||||
|
|
|
@ -7,6 +7,7 @@ package org.whispersystems.textsecuregcm.controllers;
|
|||
|
||||
import com.codahale.metrics.annotation.Timed;
|
||||
import io.dropwizard.auth.Auth;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
|
@ -17,6 +18,7 @@ import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator
|
|||
import org.whispersystems.textsecuregcm.configuration.SecureStorageServiceConfiguration;
|
||||
|
||||
@Path("/v1/storage")
|
||||
@Tag(name = "Secure Storage")
|
||||
public class SecureStorageController {
|
||||
|
||||
private final ExternalServiceCredentialsGenerator storageServiceCredentialsGenerator;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2013-2021 Signal Messenger, LLC
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
|
@ -7,6 +7,9 @@ package org.whispersystems.textsecuregcm.controllers;
|
|||
|
||||
import com.codahale.metrics.annotation.Timed;
|
||||
import io.dropwizard.auth.Auth;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
|
@ -17,6 +20,7 @@ import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator
|
|||
import org.whispersystems.textsecuregcm.configuration.SecureValueRecovery2Configuration;
|
||||
|
||||
@Path("/v2/backup")
|
||||
@Tag(name = "Secure Value Recovery")
|
||||
public class SecureValueRecovery2Controller {
|
||||
|
||||
public static ExternalServiceCredentialsGenerator credentialsGenerator(final SecureValueRecovery2Configuration cfg) {
|
||||
|
@ -28,7 +32,7 @@ public class SecureValueRecovery2Controller {
|
|||
|
||||
private final ExternalServiceCredentialsGenerator backupServiceCredentialGenerator;
|
||||
|
||||
public SecureValueRecovery2Controller(ExternalServiceCredentialsGenerator backupServiceCredentialGenerator) {
|
||||
public SecureValueRecovery2Controller(final ExternalServiceCredentialsGenerator backupServiceCredentialGenerator) {
|
||||
this.backupServiceCredentialGenerator = backupServiceCredentialGenerator;
|
||||
}
|
||||
|
||||
|
@ -36,7 +40,16 @@ public class SecureValueRecovery2Controller {
|
|||
@GET
|
||||
@Path("/auth")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public ExternalServiceCredentials getAuth(@Auth AuthenticatedAccount auth) {
|
||||
@Operation(
|
||||
summary = "Generate credentials for SVR2",
|
||||
description = """
|
||||
Generate SVR2 service credentials. Generated credentials have an expiration time of 30 days
|
||||
(however, the TTL is fully controlled by the server side and may change even for already generated credentials).
|
||||
"""
|
||||
)
|
||||
@ApiResponse(responseCode = "200", description = "`JSON` with generated credentials.", useReturnTypeSchema = true)
|
||||
@ApiResponse(responseCode = "401", description = "Account authentication check failed.")
|
||||
public ExternalServiceCredentials getAuth(@Auth final AuthenticatedAccount auth) {
|
||||
return backupServiceCredentialGenerator.generateFor(auth.getAccount().getUuid().toString());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
/*
|
||||
* Copyright 2013-2021 Signal Messenger, LLC
|
||||
* 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 java.security.SecureRandom;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
|
@ -29,6 +30,7 @@ import org.whispersystems.textsecuregcm.util.Constants;
|
|||
import org.whispersystems.textsecuregcm.util.Pair;
|
||||
|
||||
@Path("/v1/sticker")
|
||||
@Tag(name = "Stickers")
|
||||
public class StickerController {
|
||||
|
||||
private final RateLimiters rateLimiters;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2021-2022 Signal Messenger, LLC
|
||||
* Copyright 2021 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
|
@ -99,6 +99,7 @@ import org.whispersystems.textsecuregcm.subscriptions.SubscriptionProcessorManag
|
|||
import org.whispersystems.textsecuregcm.util.ExactlySize;
|
||||
|
||||
@Path("/v1/subscription")
|
||||
@io.swagger.v3.oas.annotations.tags.Tag(name = "Subscriptions")
|
||||
public class SubscriptionController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(SubscriptionController.class);
|
||||
|
|
|
@ -84,6 +84,7 @@ import org.whispersystems.textsecuregcm.util.Pair;
|
|||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
@Path("/v1/verification")
|
||||
@io.swagger.v3.oas.annotations.tags.Tag(name = "Verification")
|
||||
public class VerificationController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(VerificationController.class);
|
||||
|
|
|
@ -5,12 +5,15 @@
|
|||
|
||||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.util.List;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
import org.whispersystems.textsecuregcm.util.E164;
|
||||
|
||||
public record AuthCheckRequest(@NotNull @E164 String number,
|
||||
public record AuthCheckRequest(@Schema(description = "The e164-formatted phone number.")
|
||||
@NotNull @E164 String number,
|
||||
@Schema(description = "A list of SVR auth values, previously retrieved from `/v1/backup/auth`; may contain at most 10.")
|
||||
@NotEmpty @Size(max = 10) List<String> passwords) {
|
||||
}
|
||||
|
|
|
@ -6,10 +6,12 @@
|
|||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.util.Map;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
public record AuthCheckResponse(@NotNull Map<String, Result> matches) {
|
||||
public record AuthCheckResponse(@Schema(description = "A dictionary with the auth check results: `KBS Credentials -> 'match'/'no-match'/'invalid'`")
|
||||
@NotNull Map<String, Result> matches) {
|
||||
|
||||
public enum Result {
|
||||
MATCH("match"),
|
||||
|
|
Loading…
Reference in New Issue