1
0
mirror of https://github.com/google/nomulus synced 2026-01-07 22:15:30 +00:00

Anonymize support users in console history, add minor UI updates (#2851)

This commit is contained in:
Pavlo Tkach
2025-10-17 14:57:40 -04:00
committed by GitHub
parent b144aafb22
commit 51b579871a
5 changed files with 132 additions and 7 deletions

View File

@@ -15,15 +15,19 @@
package google.registry.ui.server.console;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.Action.Method.GET;
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import com.google.common.base.Strings;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.console.ConsolePermission;
import google.registry.model.console.ConsoleUpdateHistory;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.User;
import google.registry.model.console.UserRoles;
import google.registry.request.Action;
import google.registry.request.Action.GaeService;
import google.registry.request.Action.GkeService;
@@ -43,8 +47,8 @@ public class ConsoleHistoryDataAction extends ConsoleApiAction {
private static final String SQL_USER_HISTORY =
"""
SELECT * FROM "ConsoleUpdateHistory"
WHERE acting_user = :actingUser
SELECT * FROM "ConsoleUpdateHistory"
WHERE acting_user = :actingUser
""";
private static final String SQL_REGISTRAR_HISTORY =
@@ -59,14 +63,18 @@ public class ConsoleHistoryDataAction extends ConsoleApiAction {
private final String registrarId;
private final Optional<String> consoleUserEmail;
private final String supportEmail;
@Inject
public ConsoleHistoryDataAction(
ConsoleApiParams consoleApiParams,
@Config("supportEmail") String supportEmail,
@Parameter("registrarId") String registrarId,
@Parameter("consoleUserEmail") Optional<String> consoleUserEmail) {
super(consoleApiParams);
this.registrarId = registrarId;
this.consoleUserEmail = consoleUserEmail;
this.supportEmail = supportEmail;
}
@Override
@@ -95,7 +103,9 @@ public class ConsoleHistoryDataAction extends ConsoleApiAction {
.setHint("org.hibernate.fetchSize", 1000)
.getResultList());
consoleApiParams.response().setPayload(consoleApiParams.gson().toJson(queryResult));
List<ConsoleUpdateHistory> formattedHistoryList =
replaceActiveUserIfNecessary(queryResult, user);
consoleApiParams.response().setPayload(consoleApiParams.gson().toJson(formattedHistoryList));
consoleApiParams.response().setStatus(SC_OK);
}
@@ -110,7 +120,39 @@ public class ConsoleHistoryDataAction extends ConsoleApiAction {
.setParameter("registrarId", registrarId)
.setHint("org.hibernate.fetchSize", 1000)
.getResultList());
consoleApiParams.response().setPayload(consoleApiParams.gson().toJson(queryResult));
List<ConsoleUpdateHistory> formattedHistoryList =
replaceActiveUserIfNecessary(queryResult, user);
consoleApiParams.response().setPayload(consoleApiParams.gson().toJson(formattedHistoryList));
consoleApiParams.response().setStatus(SC_OK);
}
/** Anonymizes support users in history logs if the user viewing the log is a registrar user. */
private List<ConsoleUpdateHistory> replaceActiveUserIfNecessary(
List<ConsoleUpdateHistory> historyList, User requestingUser) {
// Check if the user *viewing* the history is a registrar user (not a support user)
if (GlobalRole.NONE.equals(requestingUser.getUserRoles().getGlobalRole())) {
User genericSupportUser = // Fixed typo
new User.Builder()
.setEmailAddress(this.supportEmail)
.setUserRoles(new UserRoles.Builder().build()) // Simplified roles
.build();
return historyList.stream()
.map(
history -> {
// Check if the user who performed the action was a support user
if (!GlobalRole.NONE.equals(
history.getActingUser().getUserRoles().getGlobalRole())) {
return history.asBuilder().setActingUser(genericSupportUser).build();
}
// If acting user was a registrar user show them as-is
return history;
})
.collect(toImmutableList());
}
// If the viewing user is a support user, return the list unmodified
return historyList;
}
}

View File

@@ -23,6 +23,7 @@ import static org.apache.http.HttpStatus.SC_OK;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import google.registry.model.console.ConsolePermission;
import google.registry.model.console.ConsoleUpdateHistory;
import google.registry.model.console.User;
@@ -47,6 +48,8 @@ import org.joda.time.DateTime;
method = {POST},
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
public class ConsoleUpdateRegistrarAction extends ConsoleApiAction {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final String CHANGE_LOG_ENTRY = "%s updated on %s, old -> %s, new -> %s";
static final String PATH = "/console-api/registrar";
private final Optional<Registrar> registrar;
@@ -124,6 +127,9 @@ public class ConsoleUpdateRegistrarAction extends ConsoleApiAction {
new ConsoleUpdateHistory.Builder()
.setType(ConsoleUpdateHistory.Type.REGISTRAR_UPDATE)
.setDescription(updatedRegistrar.getRegistrarId()));
logConsoleChangesIfNecessary(updatedRegistrar, existingRegistrar.get());
sendExternalUpdatesIfNecessary(
EmailInfo.create(
existingRegistrar.get(),
@@ -134,4 +140,25 @@ public class ConsoleUpdateRegistrarAction extends ConsoleApiAction {
consoleApiParams.response().setStatus(SC_OK);
}
private void logConsoleChangesIfNecessary(
Registrar updatedRegistrar, Registrar existingRegistrar) {
if (!updatedRegistrar.getAllowedTlds().containsAll(existingRegistrar.getAllowedTlds())) {
logger.atInfo().log(
CHANGE_LOG_ENTRY,
"Allowed TLDs",
updatedRegistrar.getRegistrarId(),
existingRegistrar.getAllowedTlds(),
updatedRegistrar.getAllowedTlds());
}
if (updatedRegistrar.isRegistryLockAllowed() != existingRegistrar.isRegistryLockAllowed()) {
logger.atInfo().log(
CHANGE_LOG_ENTRY,
"Registry lock",
updatedRegistrar.getRegistrarId(),
existingRegistrar.isRegistryLockAllowed(),
updatedRegistrar.isRegistryLockAllowed());
}
}
}

View File

@@ -40,8 +40,10 @@ import org.junit.jupiter.api.Test;
class ConsoleHistoryDataActionTest extends ConsoleActionBaseTestCase {
private static final String SUPPORT_EMAIL = "supportEmailTest@test.com";
private static final Gson GSON = new Gson();
private User noPermissionUser;
private User registrarPrimaryUser;
@BeforeEach
void beforeEach() {
@@ -56,6 +58,17 @@ class ConsoleHistoryDataActionTest extends ConsoleActionBaseTestCase {
.build())
.build());
registrarPrimaryUser =
DatabaseHelper.persistResource(
new User.Builder()
.setEmailAddress("primary@example.com")
.setUserRoles(
new UserRoles.Builder()
.setRegistrarRoles(
ImmutableMap.of("TheRegistrar", RegistrarRole.PRIMARY_CONTACT))
.build())
.build());
DatabaseHelper.persistResources(
ImmutableList.of(
new ConsoleUpdateHistory.Builder()
@@ -85,7 +98,7 @@ class ConsoleHistoryDataActionTest extends ConsoleActionBaseTestCase {
}
@Test
void testSuccess_getByRegistrar() {
void testSuccess_getByRegistrar_notAnonymizedForSupportUser() {
ConsoleHistoryDataAction action =
createAction(AuthResult.createUser(fteUser), "TheRegistrar", Optional.empty());
action.run();
@@ -93,6 +106,35 @@ class ConsoleHistoryDataActionTest extends ConsoleActionBaseTestCase {
List<Map<String, Object>> payload = GSON.fromJson(response.getPayload(), List.class);
assertThat(payload.stream().map(record -> record.get("description")).collect(toImmutableList()))
.containsExactly("TheRegistrar|Some change", "TheRegistrar|Another change");
// Assert that the support user sees the real acting users
List<String> actingUserEmails =
payload.stream()
.map(record -> (Map<String, Object>) record.get("actingUser"))
.map(userMap -> (String) userMap.get("emailAddress"))
.collect(toImmutableList());
assertThat(actingUserEmails).containsExactly("fte@email.tld", "no.perms@example.com");
}
@Test
void testSuccess_getByRegistrar_anonymizedForRegistrarUser() {
ConsoleHistoryDataAction action =
createAction(AuthResult.createUser(registrarPrimaryUser), "TheRegistrar", Optional.empty());
action.run();
assertThat(response.getStatus()).isEqualTo(SC_OK);
List<Map<String, Object>> payload = GSON.fromJson(response.getPayload(), List.class);
assertThat(payload.stream().map(record -> record.get("description")).collect(toImmutableList()))
.containsExactly("TheRegistrar|Some change", "TheRegistrar|Another change");
// Assert that the registrar user sees the anonymized support user
List<String> actingUserEmails =
payload.stream()
.map(record -> (Map<String, Object>) record.get("actingUser"))
.map(userMap -> (String) userMap.get("emailAddress"))
.collect(toImmutableList());
assertThat(actingUserEmails).containsExactly(SUPPORT_EMAIL, "no.perms@example.com");
}
@Test
@@ -104,6 +146,13 @@ class ConsoleHistoryDataActionTest extends ConsoleActionBaseTestCase {
List<Map<String, Object>> payload = GSON.fromJson(response.getPayload(), List.class);
assertThat(payload.stream().map(record -> record.get("description")).collect(toImmutableList()))
.containsExactly("TheRegistrar|Some change", "OtherRegistrar|Some change");
List<String> actingUserEmails =
payload.stream()
.map(record -> (Map<String, Object>) record.get("actingUser"))
.map(userMap -> (String) userMap.get("emailAddress"))
.collect(toImmutableList());
assertThat(actingUserEmails).containsExactly("fte@email.tld", "fte@email.tld");
}
@Test
@@ -148,6 +197,7 @@ class ConsoleHistoryDataActionTest extends ConsoleActionBaseTestCase {
consoleApiParams = ConsoleApiParamsUtils.createFake(authResult);
when(consoleApiParams.request().getMethod()).thenReturn("GET");
response = (FakeResponse) consoleApiParams.response();
return new ConsoleHistoryDataAction(consoleApiParams, registrarId, consoleUserEmail);
return new ConsoleHistoryDataAction(
consoleApiParams, SUPPORT_EMAIL, registrarId, consoleUserEmail);
}
}