mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-21 19:58:03 +01:00
Add /v2/accounts/data_report
This commit is contained in:
@@ -13,12 +13,17 @@ import io.dropwizard.auth.Auth;
|
||||
import io.micrometer.core.instrument.Metrics;
|
||||
import io.micrometer.core.instrument.Tag;
|
||||
import io.micrometer.core.instrument.Tags;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.ws.rs.BadRequestException;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.ForbiddenException;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.HeaderParam;
|
||||
import javax.ws.rs.PUT;
|
||||
import javax.ws.rs.Path;
|
||||
@@ -29,6 +34,7 @@ import javax.ws.rs.core.Response;
|
||||
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
|
||||
import org.whispersystems.textsecuregcm.auth.PhoneVerificationTokenManager;
|
||||
import org.whispersystems.textsecuregcm.auth.RegistrationLockVerificationManager;
|
||||
import org.whispersystems.textsecuregcm.entities.AccountDataReportResponse;
|
||||
import org.whispersystems.textsecuregcm.entities.AccountIdentityResponse;
|
||||
import org.whispersystems.textsecuregcm.entities.ChangeNumberRequest;
|
||||
import org.whispersystems.textsecuregcm.entities.MismatchedDevices;
|
||||
@@ -144,4 +150,31 @@ public class AccountControllerV2 {
|
||||
accountsManager.update(auth.getAccount(), a -> a.setDiscoverableByPhoneNumber(
|
||||
phoneNumberDiscoverability.discoverableByPhoneNumber()));
|
||||
}
|
||||
|
||||
@Timed
|
||||
@GET
|
||||
@Path("/data_report")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(summary = "Produces a report of non-ephemeral account data stored by the service")
|
||||
@ApiResponse(responseCode = "200",
|
||||
description = "Response with data report. A plain text representation is a field in the response.",
|
||||
useReturnTypeSchema = true)
|
||||
public AccountDataReportResponse getAccountDataReport(@Auth final AuthenticatedAccount auth) {
|
||||
|
||||
final Account account = auth.getAccount();
|
||||
|
||||
return new AccountDataReportResponse(UUID.randomUUID(), Instant.now(),
|
||||
new AccountDataReportResponse.AccountAndDevicesDataReport(
|
||||
new AccountDataReportResponse.AccountDataReport(
|
||||
account.getNumber(),
|
||||
account.getBadges().stream().map(AccountDataReportResponse.BadgeDataReport::new).toList(),
|
||||
account.isUnrestrictedUnidentifiedAccess(),
|
||||
account.isDiscoverableByPhoneNumber()),
|
||||
account.getDevices().stream().map(device ->
|
||||
new AccountDataReportResponse.DeviceDataReport(
|
||||
device.getId(),
|
||||
Instant.ofEpochMilli(device.getLastSeen()),
|
||||
Instant.ofEpochMilli(device.getCreated()),
|
||||
device.getUserAgent())).toList()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.InstantSerializer;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import javax.annotation.Nullable;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountBadge;
|
||||
|
||||
public record AccountDataReportResponse(UUID reportId,
|
||||
@JsonSerialize(using = InstantSerializer.class)
|
||||
@JsonFormat(pattern = DATE_FORMAT, timezone = UTC)
|
||||
Instant reportTimestamp,
|
||||
AccountAndDevicesDataReport data) {
|
||||
|
||||
private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
|
||||
private static final String UTC = "Etc/UTC";
|
||||
|
||||
@JsonProperty
|
||||
@Schema(description = "A plaintext representation of the data report")
|
||||
String text() {
|
||||
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
|
||||
// header
|
||||
builder.append(String.format("""
|
||||
Report ID: %s
|
||||
Report timestamp: %s
|
||||
|
||||
""",
|
||||
reportId,
|
||||
reportTimestamp.truncatedTo(ChronoUnit.SECONDS)));
|
||||
|
||||
// account
|
||||
builder.append(String.format("""
|
||||
# Account
|
||||
Phone number: %s
|
||||
Allow sealed sender from anyone: %s
|
||||
Find account by phone number: %s
|
||||
""",
|
||||
data.account.phoneNumber(),
|
||||
data.account.allowSealedSenderFromAnyone(),
|
||||
data.account.findAccountByPhoneNumber()));
|
||||
|
||||
// badges
|
||||
builder.append("Badges:");
|
||||
|
||||
if (data.account.badges().isEmpty()) {
|
||||
builder.append(" None\n");
|
||||
} else {
|
||||
builder.append("\n");
|
||||
data.account.badges().forEach(badgeDataReport -> builder.append(String.format("""
|
||||
- ID: %s
|
||||
Expiration: %s
|
||||
Visible: %s
|
||||
""",
|
||||
badgeDataReport.id(),
|
||||
badgeDataReport.expiration().truncatedTo(ChronoUnit.SECONDS),
|
||||
badgeDataReport.visible())));
|
||||
}
|
||||
|
||||
// devices
|
||||
builder.append("\n# Devices\n");
|
||||
|
||||
data.devices().forEach(deviceDataReport ->
|
||||
builder.append(String.format("""
|
||||
- ID: %s
|
||||
Created: %s
|
||||
Last seen: %s
|
||||
User-agent: %s
|
||||
""",
|
||||
deviceDataReport.id(),
|
||||
deviceDataReport.created().truncatedTo(ChronoUnit.SECONDS),
|
||||
deviceDataReport.lastSeen().truncatedTo(ChronoUnit.SECONDS),
|
||||
deviceDataReport.userAgent())));
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
|
||||
public record AccountAndDevicesDataReport(AccountDataReport account,
|
||||
List<DeviceDataReport> devices) {
|
||||
|
||||
}
|
||||
|
||||
public record AccountDataReport(String phoneNumber, List<BadgeDataReport> badges, boolean allowSealedSenderFromAnyone,
|
||||
boolean findAccountByPhoneNumber) {
|
||||
|
||||
}
|
||||
|
||||
public record DeviceDataReport(long id,
|
||||
@JsonFormat(pattern = DATE_FORMAT, timezone = UTC)
|
||||
Instant lastSeen,
|
||||
@JsonFormat(pattern = DATE_FORMAT, timezone = UTC)
|
||||
Instant created,
|
||||
@Nullable String userAgent) {
|
||||
|
||||
|
||||
}
|
||||
|
||||
public record BadgeDataReport(String id,
|
||||
@JsonFormat(pattern = DATE_FORMAT, timezone = UTC)
|
||||
Instant expiration,
|
||||
boolean visible) {
|
||||
|
||||
public BadgeDataReport(AccountBadge badge) {
|
||||
this(badge.getId(), badge.getExpiration(), badge.isVisible());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user