Skip to content

Commit 51b5798

Browse files
authored
Anonymize support users in console history, add minor UI updates (#2851)
1 parent b144aaf commit 51b5798

File tree

5 files changed

+132
-7
lines changed

5 files changed

+132
-7
lines changed

console-webapp/src/app/history/historyList.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export class HistoryListComponent {
5656
return { main: 'N/A', detail: null };
5757
}
5858
const parts = description.split('|');
59-
const detail = parts.length > 1 ? parts[1].replace(/_/g, ' ') : null;
59+
const detail = parts.length > 1 ? parts[1].replace(/_/g, ' ') : parts[0];
6060

6161
return {
6262
main: parts[0],

console-webapp/src/app/settings/contact/contactDetails.component.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ <h1>Contact Details</h1>
5757
[(ngModel)]="contactService.contactInEdit.emailAddress"
5858
[ngModelOptions]="{ standalone: true }"
5959
[disabled]="emailAddressIsDisabled()"
60+
[matTooltip]="
61+
emailAddressIsDisabled()
62+
? 'Reach out to registry customer support to update email address'
63+
: ''
64+
"
6065
/>
6166
</mat-form-field>
6267

@@ -84,6 +89,7 @@ <h1>Contact Details</h1>
8489
<h1>Contact Type</h1>
8590
<p class="console-app__contact-required">
8691
<mat-icon color="accent">error</mat-icon>Required to select at least one
92+
(primary contact can't be updated)
8793
</p>
8894
<div class="">
8995
<ng-container

core/src/main/java/google/registry/ui/server/console/ConsoleHistoryDataAction.java

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,19 @@
1515
package google.registry.ui.server.console;
1616

1717
import static com.google.common.base.Preconditions.checkArgument;
18+
import static com.google.common.collect.ImmutableList.toImmutableList;
1819
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
1920
import static google.registry.request.Action.Method.GET;
2021
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
2122
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
2223

2324
import com.google.common.base.Strings;
25+
import google.registry.config.RegistryConfig.Config;
2426
import google.registry.model.console.ConsolePermission;
2527
import google.registry.model.console.ConsoleUpdateHistory;
28+
import google.registry.model.console.GlobalRole;
2629
import google.registry.model.console.User;
30+
import google.registry.model.console.UserRoles;
2731
import google.registry.request.Action;
2832
import google.registry.request.Action.GaeService;
2933
import google.registry.request.Action.GkeService;
@@ -43,8 +47,8 @@ public class ConsoleHistoryDataAction extends ConsoleApiAction {
4347

4448
private static final String SQL_USER_HISTORY =
4549
"""
46-
SELECT * FROM "ConsoleUpdateHistory"
47-
WHERE acting_user = :actingUser
50+
SELECT * FROM "ConsoleUpdateHistory"
51+
WHERE acting_user = :actingUser
4852
""";
4953

5054
private static final String SQL_REGISTRAR_HISTORY =
@@ -59,14 +63,18 @@ public class ConsoleHistoryDataAction extends ConsoleApiAction {
5963
private final String registrarId;
6064
private final Optional<String> consoleUserEmail;
6165

66+
private final String supportEmail;
67+
6268
@Inject
6369
public ConsoleHistoryDataAction(
6470
ConsoleApiParams consoleApiParams,
71+
@Config("supportEmail") String supportEmail,
6572
@Parameter("registrarId") String registrarId,
6673
@Parameter("consoleUserEmail") Optional<String> consoleUserEmail) {
6774
super(consoleApiParams);
6875
this.registrarId = registrarId;
6976
this.consoleUserEmail = consoleUserEmail;
77+
this.supportEmail = supportEmail;
7078
}
7179

7280
@Override
@@ -95,7 +103,9 @@ private void historyByUser(User user, String consoleUserEmail) {
95103
.setHint("org.hibernate.fetchSize", 1000)
96104
.getResultList());
97105

98-
consoleApiParams.response().setPayload(consoleApiParams.gson().toJson(queryResult));
106+
List<ConsoleUpdateHistory> formattedHistoryList =
107+
replaceActiveUserIfNecessary(queryResult, user);
108+
consoleApiParams.response().setPayload(consoleApiParams.gson().toJson(formattedHistoryList));
99109
consoleApiParams.response().setStatus(SC_OK);
100110
}
101111

@@ -110,7 +120,39 @@ private void historyByRegistrarId(User user, String registrarId) {
110120
.setParameter("registrarId", registrarId)
111121
.setHint("org.hibernate.fetchSize", 1000)
112122
.getResultList());
113-
consoleApiParams.response().setPayload(consoleApiParams.gson().toJson(queryResult));
123+
124+
List<ConsoleUpdateHistory> formattedHistoryList =
125+
replaceActiveUserIfNecessary(queryResult, user);
126+
consoleApiParams.response().setPayload(consoleApiParams.gson().toJson(formattedHistoryList));
114127
consoleApiParams.response().setStatus(SC_OK);
115128
}
129+
130+
/** Anonymizes support users in history logs if the user viewing the log is a registrar user. */
131+
private List<ConsoleUpdateHistory> replaceActiveUserIfNecessary(
132+
List<ConsoleUpdateHistory> historyList, User requestingUser) {
133+
// Check if the user *viewing* the history is a registrar user (not a support user)
134+
if (GlobalRole.NONE.equals(requestingUser.getUserRoles().getGlobalRole())) {
135+
User genericSupportUser = // Fixed typo
136+
new User.Builder()
137+
.setEmailAddress(this.supportEmail)
138+
.setUserRoles(new UserRoles.Builder().build()) // Simplified roles
139+
.build();
140+
141+
return historyList.stream()
142+
.map(
143+
history -> {
144+
// Check if the user who performed the action was a support user
145+
if (!GlobalRole.NONE.equals(
146+
history.getActingUser().getUserRoles().getGlobalRole())) {
147+
return history.asBuilder().setActingUser(genericSupportUser).build();
148+
}
149+
// If acting user was a registrar user show them as-is
150+
return history;
151+
})
152+
.collect(toImmutableList());
153+
}
154+
155+
// If the viewing user is a support user, return the list unmodified
156+
return historyList;
157+
}
116158
}

core/src/main/java/google/registry/ui/server/console/ConsoleUpdateRegistrarAction.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
import com.google.common.base.Strings;
2525
import com.google.common.collect.ImmutableSet;
26+
import com.google.common.flogger.FluentLogger;
2627
import google.registry.model.console.ConsolePermission;
2728
import google.registry.model.console.ConsoleUpdateHistory;
2829
import google.registry.model.console.User;
@@ -47,6 +48,8 @@
4748
method = {POST},
4849
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
4950
public class ConsoleUpdateRegistrarAction extends ConsoleApiAction {
51+
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
52+
private static final String CHANGE_LOG_ENTRY = "%s updated on %s, old -> %s, new -> %s";
5053
static final String PATH = "/console-api/registrar";
5154
private final Optional<Registrar> registrar;
5255

@@ -124,6 +127,9 @@ protected void postHandler(User user) {
124127
new ConsoleUpdateHistory.Builder()
125128
.setType(ConsoleUpdateHistory.Type.REGISTRAR_UPDATE)
126129
.setDescription(updatedRegistrar.getRegistrarId()));
130+
131+
logConsoleChangesIfNecessary(updatedRegistrar, existingRegistrar.get());
132+
127133
sendExternalUpdatesIfNecessary(
128134
EmailInfo.create(
129135
existingRegistrar.get(),
@@ -134,4 +140,25 @@ protected void postHandler(User user) {
134140

135141
consoleApiParams.response().setStatus(SC_OK);
136142
}
143+
144+
private void logConsoleChangesIfNecessary(
145+
Registrar updatedRegistrar, Registrar existingRegistrar) {
146+
if (!updatedRegistrar.getAllowedTlds().containsAll(existingRegistrar.getAllowedTlds())) {
147+
logger.atInfo().log(
148+
CHANGE_LOG_ENTRY,
149+
"Allowed TLDs",
150+
updatedRegistrar.getRegistrarId(),
151+
existingRegistrar.getAllowedTlds(),
152+
updatedRegistrar.getAllowedTlds());
153+
}
154+
155+
if (updatedRegistrar.isRegistryLockAllowed() != existingRegistrar.isRegistryLockAllowed()) {
156+
logger.atInfo().log(
157+
CHANGE_LOG_ENTRY,
158+
"Registry lock",
159+
updatedRegistrar.getRegistrarId(),
160+
existingRegistrar.isRegistryLockAllowed(),
161+
updatedRegistrar.isRegistryLockAllowed());
162+
}
163+
}
137164
}

core/src/test/java/google/registry/ui/server/console/ConsoleHistoryDataActionTest.java

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@
4040

4141
class ConsoleHistoryDataActionTest extends ConsoleActionBaseTestCase {
4242

43+
private static final String SUPPORT_EMAIL = "[email protected]";
4344
private static final Gson GSON = new Gson();
4445
private User noPermissionUser;
46+
private User registrarPrimaryUser;
4547

4648
@BeforeEach
4749
void beforeEach() {
@@ -56,6 +58,17 @@ void beforeEach() {
5658
.build())
5759
.build());
5860

61+
registrarPrimaryUser =
62+
DatabaseHelper.persistResource(
63+
new User.Builder()
64+
.setEmailAddress("[email protected]")
65+
.setUserRoles(
66+
new UserRoles.Builder()
67+
.setRegistrarRoles(
68+
ImmutableMap.of("TheRegistrar", RegistrarRole.PRIMARY_CONTACT))
69+
.build())
70+
.build());
71+
5972
DatabaseHelper.persistResources(
6073
ImmutableList.of(
6174
new ConsoleUpdateHistory.Builder()
@@ -85,14 +98,43 @@ void beforeEach() {
8598
}
8699

87100
@Test
88-
void testSuccess_getByRegistrar() {
101+
void testSuccess_getByRegistrar_notAnonymizedForSupportUser() {
89102
ConsoleHistoryDataAction action =
90103
createAction(AuthResult.createUser(fteUser), "TheRegistrar", Optional.empty());
91104
action.run();
92105
assertThat(response.getStatus()).isEqualTo(SC_OK);
93106
List<Map<String, Object>> payload = GSON.fromJson(response.getPayload(), List.class);
94107
assertThat(payload.stream().map(record -> record.get("description")).collect(toImmutableList()))
95108
.containsExactly("TheRegistrar|Some change", "TheRegistrar|Another change");
109+
110+
// Assert that the support user sees the real acting users
111+
List<String> actingUserEmails =
112+
payload.stream()
113+
.map(record -> (Map<String, Object>) record.get("actingUser"))
114+
.map(userMap -> (String) userMap.get("emailAddress"))
115+
.collect(toImmutableList());
116+
117+
assertThat(actingUserEmails).containsExactly("[email protected]", "[email protected]");
118+
}
119+
120+
@Test
121+
void testSuccess_getByRegistrar_anonymizedForRegistrarUser() {
122+
ConsoleHistoryDataAction action =
123+
createAction(AuthResult.createUser(registrarPrimaryUser), "TheRegistrar", Optional.empty());
124+
action.run();
125+
assertThat(response.getStatus()).isEqualTo(SC_OK);
126+
List<Map<String, Object>> payload = GSON.fromJson(response.getPayload(), List.class);
127+
assertThat(payload.stream().map(record -> record.get("description")).collect(toImmutableList()))
128+
.containsExactly("TheRegistrar|Some change", "TheRegistrar|Another change");
129+
130+
// Assert that the registrar user sees the anonymized support user
131+
List<String> actingUserEmails =
132+
payload.stream()
133+
.map(record -> (Map<String, Object>) record.get("actingUser"))
134+
.map(userMap -> (String) userMap.get("emailAddress"))
135+
.collect(toImmutableList());
136+
137+
assertThat(actingUserEmails).containsExactly(SUPPORT_EMAIL, "[email protected]");
96138
}
97139

98140
@Test
@@ -104,6 +146,13 @@ void testSuccess_getByUser() {
104146
List<Map<String, Object>> payload = GSON.fromJson(response.getPayload(), List.class);
105147
assertThat(payload.stream().map(record -> record.get("description")).collect(toImmutableList()))
106148
.containsExactly("TheRegistrar|Some change", "OtherRegistrar|Some change");
149+
150+
List<String> actingUserEmails =
151+
payload.stream()
152+
.map(record -> (Map<String, Object>) record.get("actingUser"))
153+
.map(userMap -> (String) userMap.get("emailAddress"))
154+
.collect(toImmutableList());
155+
assertThat(actingUserEmails).containsExactly("[email protected]", "[email protected]");
107156
}
108157

109158
@Test
@@ -148,6 +197,7 @@ private ConsoleHistoryDataAction createAction(
148197
consoleApiParams = ConsoleApiParamsUtils.createFake(authResult);
149198
when(consoleApiParams.request().getMethod()).thenReturn("GET");
150199
response = (FakeResponse) consoleApiParams.response();
151-
return new ConsoleHistoryDataAction(consoleApiParams, registrarId, consoleUserEmail);
200+
return new ConsoleHistoryDataAction(
201+
consoleApiParams, SUPPORT_EMAIL, registrarId, consoleUserEmail);
152202
}
153203
}

0 commit comments

Comments
 (0)