1
0
mirror of https://github.com/google/nomulus synced 2026-05-21 23:31:51 +00:00

Compare commits

...

11 Commits

Author SHA1 Message Date
gbrodman
15cf3e1bc0 Add RegistrarUpdateHistory objects for console changes (#2585) 2024-11-19 21:03:48 +00:00
Pavlo Tkach
eeed166310 Add console user role update and minor fixes to delete (#2610) 2024-11-15 18:36:10 +00:00
gbrodman
e54075fea3 Allow for removal of registry lock passwords in User objects (#2609)
This essentially enables the "forgot password" flow
2024-11-14 21:01:17 +00:00
Ben McIlwain
78cc1b2937 Fix 'Domian' typo for Domain (#2608) 2024-11-12 18:41:56 +00:00
Pavlo Tkach
35f95bbbe4 Add delete user to the console (#2603)
* Add delete user to the console

* Add delete user to the console

* Add delete user to the console
2024-11-08 18:20:01 +00:00
gbrodman
ae61cd443d Sometimes include deletion times in domain-list exports (#2602)
We only include the deletion time if the domain is in the 5-day
PENDING_DELETE period after the 30 day REDEMPTION period. For all other
domains, we just have an empty string as that field.

This is behind a feature flag so that we can control when it is enabled
2024-11-06 17:59:30 +00:00
gbrodman
cc20f7d76d Add a simple toString for TimedTransitionProperty (#2604)
this means that we can actually see the transitions when running
GetAllocationTokenCommand, for instance
2024-11-05 18:26:36 +00:00
Ben McIlwain
5603b91526 Make nomulus update_recurrence command only fail on pending transfers (#2605)
It was failing when any kind of transfer data was present, even completed
transfer data. Note that completed transfer data persists on a domain
indefinitely until/unless a new transfer is requested.

BUG= http://b/377328244
2024-11-04 21:16:11 +00:00
Pavlo Tkach
332f491ac7 Fix cut off status list on domains page (#2601) 2024-10-28 18:20:04 +00:00
Pavlo Tkach
4bd7c18fe9 Add console settings update progress status (#2596) 2024-10-25 22:23:22 +00:00
Pavlo Tkach
fdb0664841 Add admin.directory.user.security scope (#2597) 2024-10-25 21:24:15 +00:00
62 changed files with 1436 additions and 369 deletions

View File

@@ -95,9 +95,9 @@
<ng-container matColumnDef="statuses">
<mat-header-cell *matHeaderCellDef>Statuses</mat-header-cell>
<mat-cell *matCellDef="let element">{{
element.statuses
}}</mat-cell>
<mat-cell *matCellDef="let element">
<span>{{ element.statuses?.join(", ") }}</span>
</mat-cell>
</ng-container>
<ng-container matColumnDef="registryLock">

View File

@@ -36,6 +36,11 @@
.mat-column-registryLock {
max-width: 150px;
}
.mat-column-statuses span {
padding: 10px 0;
overflow: hidden;
word-break: break-word;
}
}
&__domains-spinner {

View File

@@ -13,7 +13,7 @@
// limitations under the License.
import { Injectable } from '@angular/core';
import { switchMap } from 'rxjs';
import { switchMap, timeout } from 'rxjs';
import {
IpAllowListItem,
RegistrarService,
@@ -69,6 +69,7 @@ export class SecurityService {
uiToApiConverter(newSecuritySettings)
)
.pipe(
timeout(2000),
switchMap(() => {
return this.registrarService.loadRegistrars();
})

View File

@@ -10,6 +10,7 @@
<mat-form-field appearance="outline">
<input
matInput
[disabled]="isUpdating"
type="text"
[(ngModel)]="ip.value"
[ngModelOptions]="{ standalone: true }"
@@ -20,12 +21,19 @@
mat-icon-button
aria-label="Remove"
(click)="removeIpEntry(ip)"
[disabled]="isUpdating"
>
<mat-icon>close</mat-icon>
</button>
</div>
}
<button mat-button color="primary" (click)="createIpEntry()" type="button">
<button
mat-button
[disabled]="isUpdating"
color="primary"
(click)="createIpEntry()"
type="button"
>
+ Add IP
</button>
@@ -35,6 +43,7 @@
<textarea
class="console-app__clientCertificateValue"
matInput
[disabled]="isUpdating"
[(ngModel)]="dataSource.clientCertificate"
[ngModelOptions]="{ standalone: true }"
></textarea>
@@ -44,6 +53,7 @@
<mat-form-field appearance="outline">
<textarea
matInput
[disabled]="isUpdating"
[(ngModel)]="dataSource.failoverClientCertificate"
[ngModelOptions]="{ standalone: true }"
></textarea>
@@ -51,6 +61,7 @@
<button
mat-flat-button
color="primary"
[disabled]="isUpdating"
aria-label="Save security settings"
type="submit"
class="settings-security__edit-save"

View File

@@ -29,6 +29,7 @@ import { SecurityService, apiToUiConverter } from './security.service';
})
export default class SecurityEditComponent {
dataSource: SecuritySettings = {};
isUpdating = false;
constructor(
public securityService: SecurityService,
@@ -43,12 +44,15 @@ export default class SecurityEditComponent {
}
save() {
this.isUpdating = true;
this.securityService.saveChanges(this.dataSource).subscribe({
complete: () => {
this.isUpdating = false;
this.goBack();
},
error: (err: HttpErrorResponse) => {
this._snackBar.open(err.error);
this._snackBar.open(err.error || err.message);
this.isUpdating = false;
},
});
}

View File

@@ -172,6 +172,20 @@ export class BackendService {
.pipe(catchError((err) => this.errorCatcher<User>(err)));
}
deleteUser(registrarId: string, user: User): Observable<any> {
return this.http
.delete<any>(`/console-api/users?registrarId=${registrarId}`, {
body: JSON.stringify(user),
})
.pipe(catchError((err) => this.errorCatcher<any>(err)));
}
updateUser(registrarId: string, updatedUser: User): Observable<any> {
return this.http
.put<User>(`/console-api/users?registrarId=${registrarId}`, updatedUser)
.pipe(catchError((err) => this.errorCatcher<any>(err)));
}
getUserData(): Observable<UserData> {
return this.http
.get<UserData>('/console-api/userdata')

View File

@@ -0,0 +1,125 @@
<div class="console-app__user-details">
@if(isEditing) {
<h1 class="mat-headline-4">Editing {{ userDetails().emailAddress }}</h1>
<mat-divider></mat-divider>
<div class="console-app__user-details-controls">
<button
mat-icon-button
aria-label="Back to view user"
(click)="isEditing = false"
>
<mat-icon>arrow_back</mat-icon>
</button>
</div>
<form (ngSubmit)="saveEdit()">
<p>
<mat-form-field appearance="outline">
<mat-label
>User Role:
<mat-icon
matTooltip="Viewer role doesn't allow making updates; Editor role allows updates, like Contacts delete or SSL certificate change"
>help_outline</mat-icon
></mat-label
>
<mat-select [(ngModel)]="userRole" name="userRole">
<mat-option value="PRIMARY_CONTACT">Editor</mat-option>
<mat-option value="ACCOUNT_MANAGER">Viewer</mat-option>
</mat-select>
</mat-form-field>
</p>
<button
mat-flat-button
color="primary"
aria-label="Save user"
type="submit"
>
Save
</button>
</form>
} @else { @if(isNewUser) {
<h1 class="mat-headline-4">
{{ userDetails().emailAddress + " successfully created" }}
</h1>
} @else {
<h1 class="mat-headline-4">User details</h1>
<mat-divider></mat-divider>
<div class="console-app__user-details-controls">
<button mat-icon-button aria-label="Back to users list" (click)="goBack()">
<mat-icon>arrow_back</mat-icon>
</button>
<div class="spacer"></div>
<button
mat-flat-button
color="primary"
aria-label="Edit User"
(click)="userRole = userDetails().role; isEditing = true"
>
<mat-icon>edit</mat-icon>
Edit
</button>
<button
mat-icon-button
aria-label="Delete User"
(click)="deleteUser()"
[disabled]="isLoading"
>
<mat-icon>delete</mat-icon>
</button>
</div>
<div *ngIf="isNewUser" class="console-app__user-details-save-password">
<mat-icon>priority_high</mat-icon>
Please save the password. For your security, we do not store passwords in a
recoverable format.
</div>
<p *ngIf="isLoading">
<mat-progress-bar mode="query"></mat-progress-bar>
</p>
<mat-card appearance="outlined">
<mat-card-content>
<mat-list role="list">
<mat-list-item role="listitem">
<h2>User details</h2>
</mat-list-item>
<mat-divider></mat-divider>
<mat-list-item role="listitem">
<span class="console-app__list-key">User email</span>
<span class="console-app__list-value">{{
userDetails().emailAddress
}}</span>
</mat-list-item>
<mat-divider></mat-divider>
<mat-list-item role="listitem">
<span class="console-app__list-key">User role</span>
<span class="console-app__list-value">{{
roleToDescription(userDetails().role)
}}</span>
</mat-list-item>
@if (userDetails().password) {
<mat-divider></mat-divider>
<mat-list-item role="listitem">
<span class="console-app__list-key">Password</span>
<span
class="console-app__list-value console-app__user-details-password"
>
<input
[type]="isPasswordVisible ? 'text' : 'password'"
[value]="userDetails().password"
disabled
/>
<button
mat-button
aria-label="Show password"
(click)="isPasswordVisible = !isPasswordVisible"
>
{{ isPasswordVisible ? "Hide" : "View" }} password
</button>
</span>
</mat-list-item>
}
</mat-list>
</mat-card-content>
</mat-card>
}}
</div>

View File

@@ -0,0 +1,39 @@
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
.console-app {
&__user-details {
&-controls {
display: flex;
align-items: center;
margin: 20px 0;
}
&-password {
input {
border: none;
background: transparent;
}
}
&-save-password {
display: flex;
justify-content: center;
align-items: center;
padding: 15px 10px;
margin-bottom: 20px;
border: 1px solid #ddd;
border-radius: 10px;
}
max-width: 616px;
}
}

View File

@@ -0,0 +1,105 @@
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { CommonModule } from '@angular/common';
import { Component, computed } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { SelectedRegistrarModule } from '../app.module';
import { MaterialModule } from '../material.module';
import { RegistrarService } from '../registrar/registrar.service';
import { SnackBarModule } from '../snackbar.module';
import { User, UsersService, roleToDescription } from './users.service';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-user-edit',
templateUrl: './userEdit.component.html',
styleUrls: ['./userEdit.component.scss'],
standalone: true,
imports: [
FormsModule,
MaterialModule,
SnackBarModule,
CommonModule,
SelectedRegistrarModule,
],
providers: [],
})
export class UserEditComponent {
isEditing = false;
isPasswordVisible = false;
isNewUser = false;
isLoading = false;
userRole = '';
userDetails = computed(() => {
return this.usersService
.users()
.filter(
(u) => u.emailAddress === this.usersService.currentlyOpenUserEmail()
)[0];
});
constructor(
protected registrarService: RegistrarService,
protected usersService: UsersService,
private _snackBar: MatSnackBar
) {
if (this.usersService.isNewUser) {
this.isNewUser = true;
this.usersService.isNewUser = false;
}
}
roleToDescription(role: string) {
return roleToDescription(role);
}
deleteUser() {
this.isLoading = true;
this.usersService.deleteUser(this.userDetails()).subscribe({
error: (err) => {
this._snackBar.open(err.error || err.message);
this.isLoading = false;
},
complete: () => {
this.isLoading = false;
this.goBack();
},
});
}
goBack() {
this.usersService.currentlyOpenUserEmail.set('');
}
saveEdit() {
this.isLoading = true;
this.usersService
.updateUser({
role: this.userRole,
emailAddress: this.userDetails().emailAddress,
})
.subscribe({
error: (err) => {
this._snackBar.open(err.error || err.message);
this.isLoading = false;
},
complete: () => {
this.isLoading = false;
this.isEditing = false;
},
});
}
}

View File

@@ -1,5 +1,11 @@
<app-selected-registrar-wrapper>
@if (!isLoading) {
@if(isLoading) {
<div class="console-app__users-spinner">
<mat-spinner />
</div>
} @else if(usersService.currentlyOpenUserEmail()) {
<app-user-edit></app-user-edit>
} @else {
<div class="console-app__users">
<div class="console-app__users-header">
<h1 class="mat-headline-4">Users</h1>
@@ -32,12 +38,11 @@
></mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
<mat-row
*matRowDef="let row; columns: displayedColumns"
(click)="openDetails(row.emailAddress)"
></mat-row>
</mat-table>
</div>
} @else {
<div class="console-app__users-spinner">
<mat-spinner />
</div>
}
</app-selected-registrar-wrapper>

View File

@@ -22,15 +22,8 @@ import { SelectedRegistrarModule } from '../app.module';
import { MaterialModule } from '../material.module';
import { RegistrarService } from '../registrar/registrar.service';
import { SnackBarModule } from '../snackbar.module';
import { User, UsersService } from './users.service';
const roleToDescription = (role: String) => {
if (!role) return 'N/A';
else if (role.toLowerCase().startsWith('account_manager')) {
return 'Viewer';
}
return 'Editor';
};
import { UserEditComponent } from './userEdit.component';
import { roleToDescription, User, UsersService } from './users.service';
export const columns = [
{
@@ -55,6 +48,7 @@ export const columns = [
SnackBarModule,
CommonModule,
SelectedRegistrarModule,
UserEditComponent,
],
providers: [UsersService],
})
@@ -92,6 +86,7 @@ export class UsersComponent {
this.usersService.fetchUsers().subscribe({
error: (err: HttpErrorResponse) => {
this._snackBar.open(err.error || err.message);
this.isLoading = false;
},
complete: () => {
this.isLoading = false;
@@ -102,21 +97,17 @@ export class UsersComponent {
createNewUser() {
this.isLoading = true;
this.usersService.createNewUser().subscribe({
next: (newUser) => {
this._snackBar.open(
`New user with email ${newUser.emailAddress} has been created.`,
'',
{
duration: 2000,
}
);
},
error: (err: HttpErrorResponse) => {
this._snackBar.open(err.error || err.message);
this.isLoading = false;
},
complete: () => {
this.isLoading = false;
},
});
}
openDetails(emailAddress: string) {
this.usersService.currentlyOpenUserEmail.set(emailAddress);
}
}

View File

@@ -13,23 +13,33 @@
// limitations under the License.
import { Injectable, signal } from '@angular/core';
import { tap } from 'rxjs';
import { switchMap, tap } from 'rxjs';
import { RegistrarService } from '../registrar/registrar.service';
import { BackendService } from '../shared/services/backend.service';
export const roleToDescription = (role: string) => {
if (!role) return 'N/A';
else if (role === 'ACCOUNT_MANAGER') {
return 'Viewer';
}
return 'Editor';
};
export interface CreateAutoTimestamp {
creationTime: string;
}
export interface User {
emailAddress: String;
role: String;
password?: String;
emailAddress: string;
role: string;
password?: string;
}
@Injectable()
export class UsersService {
users = signal<User[]>([]);
currentlyOpenUserEmail = signal<string>('');
isNewUser: boolean = false;
constructor(
private backendService: BackendService,
@@ -52,7 +62,21 @@ export class UsersService {
.pipe(
tap((newUser: User) => {
this.users.set([...this.users(), newUser]);
this.currentlyOpenUserEmail.set(newUser.emailAddress);
this.isNewUser = true;
})
);
}
deleteUser(user: User) {
return this.backendService
.deleteUser(this.registrarService.registrarId(), user)
.pipe(switchMap((_) => this.fetchUsers()));
}
updateUser(updatedUser: User) {
return this.backendService
.updateUser(this.registrarService.registrarId(), updatedUser)
.pipe(switchMap((_) => this.fetchUsers()));
}
}

View File

@@ -374,6 +374,9 @@ credentialOAuth:
- https://www.googleapis.com/auth/admin.directory.group
# View and manage users in Google Workspace
- https://www.googleapis.com/auth/admin.directory.user
# Security scope which seems to be required to create users via API,
# based on https://github.com/googleapis/google-api-nodejs-client/issues/1884
- https://www.googleapis.com/auth/admin.directory.user.security
# View and manage group settings in Group Settings API.
- https://www.googleapis.com/auth/apps.groups.settings
# Send email through Gmail.

View File

@@ -17,6 +17,7 @@ package google.registry.export;
import static com.google.common.base.Verify.verifyNotNull;
import static google.registry.model.tld.Tlds.getTldsOfType;
import static google.registry.persistence.PersistenceModule.TransactionIsolationLevel.TRANSACTION_REPEATABLE_READ;
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.Action.Method.POST;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -28,6 +29,9 @@ import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType;
import google.registry.config.RegistryConfig.Config;
import google.registry.gcs.GcsUtils;
import google.registry.model.common.FeatureFlag;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.tld.Tld;
import google.registry.model.tld.Tld.TldType;
import google.registry.request.Action;
@@ -38,8 +42,13 @@ import google.registry.util.Clock;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.time.Instant;
import java.util.List;
import javax.inject.Inject;
import org.hibernate.query.NativeQuery;
import org.hibernate.query.TupleTransformer;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
/**
* An action that exports the list of active domains on all real TLDs to Google Drive and GCS.
@@ -55,7 +64,21 @@ import javax.inject.Inject;
public class ExportDomainListsAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
public static final String REGISTERED_DOMAINS_FILENAME = "registered_domains.txt";
private static final String SELECT_DOMAINS_STATEMENT =
"SELECT domainName FROM Domain WHERE tld = :tld AND deletionTime > :now ORDER by domainName";
private static final String SELECT_DOMAINS_AND_DELETION_TIMES_STATEMENT =
"""
SELECT d.domain_name, d.deletion_time, d.statuses, gp.type FROM "Domain" d
LEFT JOIN (SELECT type, domain_repo_id FROM "GracePeriod"
WHERE type = 'REDEMPTION'
AND expiration_time > CAST(:now AS timestamptz)) AS gp
ON d.repo_id = gp.domain_repo_id
WHERE d.tld = :tld
AND d.deletion_time > CAST(:now AS timestamptz)
ORDER BY d.domain_name""";
// This may be a CSV, but it is uses a .txt file extension for back-compatibility
static final String REGISTERED_DOMAINS_FILENAME = "registered_domains.txt";
@Inject Clock clock;
@Inject DriveConnection driveConnection;
@@ -68,47 +91,50 @@ public class ExportDomainListsAction implements Runnable {
public void run() {
ImmutableSet<String> realTlds = getTldsOfType(TldType.REAL);
logger.atInfo().log("Exporting domain lists for TLDs %s.", realTlds);
boolean includeDeletionTimes =
tm().transact(
() ->
FeatureFlag.isActiveNowOrElse(
FeatureFlag.FeatureName.INCLUDE_PENDING_DELETE_DATE_FOR_DOMAINS, false));
realTlds.forEach(
tld -> {
List<String> domains =
tm().transact(
List<String> domainsList =
replicaTm()
.transact(
TRANSACTION_REPEATABLE_READ,
() ->
// Note that if we had "creationTime <= :now" in the condition (not
// necessary as there is no pending creation, the order of deletionTime
// and creationTime in the query would have been significant and it
// should come after deletionTime. When Hibernate substitutes "now" it
// will first validate that the **first** field that is to be compared
// with it (deletionTime) is assignable from the substituted Java object
// (click.nowUtc()). Since creationTime is a CreateAutoTimestamp, if it
// comes first, we will need to substitute "now" with
// CreateAutoTimestamp.create(clock.nowUtc()). This might look a bit
// strange as the Java object type is clearly incompatible between the
// two fields deletionTime (DateTime) and creationTime, yet they are
// compared with the same "now". It is actually OK because in the end
// Hibernate converts everything to SQL types (and Java field names to
// SQL column names) to run the query. Both CreateAutoTimestamp and
// DateTime are persisted as timestamp_z in SQL. It is only the
// validation that compares the Java types, and only with the first
// field that compares with the substituted value.
tm().query(
"SELECT domainName FROM Domain "
+ "WHERE tld = :tld "
+ "AND deletionTime > :now "
+ "ORDER by domainName ASC",
String.class)
() -> {
if (includeDeletionTimes) {
// We want to include deletion times, but only for domains in the 5-day
// PENDING_DELETE period after the REDEMPTION grace period. In order to
// accomplish this without loading the entire list of domains, we use a
// native query to join against the GracePeriod table to find
// PENDING_DELETE domains that don't have a REDEMPTION grace period.
return replicaTm()
.getEntityManager()
.createNativeQuery(SELECT_DOMAINS_AND_DELETION_TIMES_STATEMENT)
.unwrap(NativeQuery.class)
.setTupleTransformer(new DomainResultTransformer())
.setParameter("tld", tld)
.setParameter("now", clock.nowUtc())
.getResultList());
String domainsList = Joiner.on("\n").join(domains);
.setParameter("now", replicaTm().getTransactionTime().toString())
.getResultList();
} else {
return replicaTm()
.query(SELECT_DOMAINS_STATEMENT, String.class)
.setParameter("tld", tld)
.setParameter("now", replicaTm().getTransactionTime())
.getResultList();
}
});
logger.atInfo().log(
"Exporting %d domains for TLD %s to GCS and Drive.", domains.size(), tld);
exportToGcs(tld, domainsList, gcsBucket, gcsUtils);
exportToDrive(tld, domainsList, driveConnection);
"Exporting %d domains for TLD %s to GCS and Drive.", domainsList.size(), tld);
String domainsListOutput = Joiner.on('\n').join(domainsList);
exportToGcs(tld, domainsListOutput, gcsBucket, gcsUtils);
exportToDrive(tld, domainsListOutput, driveConnection);
});
}
protected static boolean exportToDrive(
protected static void exportToDrive(
String tldStr, String domains, DriveConnection driveConnection) {
verifyNotNull(driveConnection, "Expecting non-null driveConnection");
try {
@@ -131,12 +157,10 @@ public class ExportDomainListsAction implements Runnable {
} catch (Throwable e) {
logger.atSevere().withCause(e).log(
"Error exporting registered domains for TLD %s to Drive, skipping...", tldStr);
return false;
}
return true;
}
protected static boolean exportToGcs(
protected static void exportToGcs(
String tld, String domains, String gcsBucket, GcsUtils gcsUtils) {
BlobId blobId = BlobId.of(gcsBucket, tld + ".txt");
try (OutputStream gcsOutput = gcsUtils.openOutputStream(blobId);
@@ -145,8 +169,22 @@ public class ExportDomainListsAction implements Runnable {
} catch (Throwable e) {
logger.atSevere().withCause(e).log(
"Error exporting registered domains for TLD %s to GCS, skipping...", tld);
return false;
}
return true;
}
/** Transforms the multiple columns selected from SQL into the output line. */
private static class DomainResultTransformer implements TupleTransformer<String> {
@Override
public String transformTuple(Object[] domainResult, String[] strings) {
String domainName = (String) domainResult[0];
Instant deletionInstant = (Instant) domainResult[1];
DateTime deletionTime = new DateTime(deletionInstant.toEpochMilli(), DateTimeZone.UTC);
String[] domainStatuses = (String[]) domainResult[2];
String gracePeriodType = (String) domainResult[3];
boolean inPendingDelete =
ImmutableSet.copyOf(domainStatuses).contains(StatusValue.PENDING_DELETE.toString())
&& !GracePeriodStatus.REDEMPTION.toString().equals(gracePeriodType);
return String.format("%s,%s", domainName, inPendingDelete ? deletionTime : "");
}
}
}

View File

@@ -66,6 +66,7 @@ public class FeatureFlag extends ImmutableObject implements Buildable {
TEST_FEATURE,
MINIMUM_DATASET_CONTACTS_OPTIONAL,
MINIMUM_DATASET_CONTACTS_PROHIBITED,
INCLUDE_PENDING_DELETE_DATE_FOR_DOMAINS
}
/** The name of the flag/feature. */
@@ -154,6 +155,15 @@ public class FeatureFlag extends ImmutableObject implements Buildable {
return status.getValueAtTime(time);
}
/** Returns if the flag is active, or the default value if the flag does not exist. */
public static boolean isActiveNowOrElse(FeatureName featureName, boolean defaultValue) {
tm().assertInTransaction();
return CACHE
.get(featureName)
.map(flag -> flag.getStatus(tm().getTransactionTime()).equals(ACTIVE))
.orElse(defaultValue);
}
/** Returns if the FeatureFlag with the given FeatureName is active now. */
public static boolean isActiveNow(FeatureName featureName) {
tm().assertInTransaction();

View File

@@ -185,4 +185,9 @@ public class TimedTransitionProperty<V extends Serializable> implements UnsafeSe
public int hashCode() {
return this.backingMap.hashCode();
}
@Override
public String toString() {
return this.backingMap.toString();
}
}

View File

@@ -33,7 +33,9 @@ public @interface Action {
enum Method {
GET,
HEAD,
POST
POST,
PUT,
DELETE
}
interface Service {

View File

@@ -36,9 +36,9 @@ import javax.annotation.Nullable;
import javax.inject.Inject;
/**
* An authenticam mechanism that verifies the OIDC token.
* An authentication mechanism that verifies the OIDC token.
*
* <p>Currently, two flavors are supported: one that checkes for the OIDC token as a regular bearer
* <p>Currently, two flavors are supported: one that checks for the OIDC token as a regular bearer
* token, and another that checks for the OIDC token passed by IAP. In both cases, the {@link
* AuthResult} with the highest {@link AuthLevel} possible is returned. So, if the email address for
* which the token is minted exists both as a {@link User} and as a service account, the returned

View File

@@ -14,6 +14,7 @@
package google.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
@@ -41,6 +42,15 @@ public abstract class CreateOrUpdateUserCommand extends ConfirmingCommand {
+ " to remove the field.")
private String registryLockEmailAddress;
@Nullable
@Parameter(
names = "--registry_lock_password",
description =
"Sets the registry lock password for this user, or removes it (allowing the user to"
+ " re-set it). Do not set the password explicitly unless in exceptional"
+ " circumstances.")
private String registryLockPassword;
@Nullable
@Parameter(
names = "--admin",
@@ -94,6 +104,25 @@ public abstract class CreateOrUpdateUserCommand extends ConfirmingCommand {
builder.setRegistryLockEmailAddress(registryLockEmailAddress);
}
}
// Ditto the registry lock password
if (registryLockPassword != null) {
if (registryLockEmailAddress != null) {
// Edge case, make sure we're not removing an email and setting a password at the same time
checkArgument(
!registryLockEmailAddress.isEmpty(),
"Cannot set/remove registry lock password on a user without a registry lock email"
+ " address");
} else {
checkArgument(
user != null && user.getRegistryLockEmailAddress().isPresent(),
"Cannot set/remove registry lock password on a user without a registry lock email"
+ " address");
}
builder.removeRegistryLockPassword();
if (!registryLockPassword.isEmpty()) {
builder.setRegistryLockPassword(registryLockPassword);
}
}
tm().put(builder.build());
}
}

View File

@@ -30,6 +30,7 @@ import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainHistory;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
import google.registry.model.transfer.TransferStatus;
import java.util.List;
import java.util.Optional;
import javax.annotation.Nullable;
@@ -174,7 +175,8 @@ public class UpdateRecurrenceCommand extends ConfirmingCommand {
"Domain %s has already had a deletion time set",
domainName);
checkArgument(
domain.getTransferData().isEmpty(),
domain.getTransferData().isEmpty()
|| domain.getTransferData().getTransferStatus() != TransferStatus.PENDING,
"Domain %s has a pending transfer: %s",
domainName,
domain.getTransferData());

View File

@@ -16,8 +16,12 @@ package google.registry.ui.server.console;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.Action.Method.DELETE;
import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.HEAD;
import static google.registry.request.Action.Method.POST;
import static google.registry.request.Action.Method.PUT;
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
@@ -33,6 +37,7 @@ import google.registry.batch.CloudTasksUtils;
import google.registry.config.RegistryConfig;
import google.registry.export.sheet.SyncRegistrarsSheetAction;
import google.registry.model.console.ConsolePermission;
import google.registry.model.console.ConsoleUpdateHistory;
import google.registry.model.console.User;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarPoc;
@@ -75,13 +80,21 @@ public abstract class ConsoleApiAction implements Runnable {
return;
}
User user = consoleApiParams.authResult().user().get();
String requestMethod = consoleApiParams.request().getMethod();
try {
if (consoleApiParams.request().getMethod().equals(GET.toString())) {
if (requestMethod.equals(GET.toString())) {
getHandler(user);
} else if (requestMethod.equals(HEAD.toString())) {
headHandler(user);
} else {
if (verifyXSRF(user)) {
postHandler(user);
if (requestMethod.equals(DELETE.toString())) {
deleteHandler(user);
} else if (requestMethod.equals(PUT.toString())) {
putHandler(user);
} else {
postHandler(user);
}
}
}
} catch (ConsolePermissionForbiddenException e) {
@@ -109,10 +122,22 @@ public abstract class ConsoleApiAction implements Runnable {
throw new UnsupportedOperationException("Console API POST handler not implemented");
}
protected void putHandler(User user) {
throw new UnsupportedOperationException("Console API PUT handler not implemented");
}
protected void getHandler(User user) {
throw new UnsupportedOperationException("Console API GET handler not implemented");
}
protected void deleteHandler(User user) {
throw new UnsupportedOperationException("Console API DELETE handler not implemented");
}
protected void headHandler(User user) {
throw new UnsupportedOperationException("Console API HEAD handler not implemented");
}
protected void setFailedResponse(String message, int code) {
consoleApiParams.response().setStatus(code);
consoleApiParams.response().setPayload(message);
@@ -237,6 +262,14 @@ public abstract class ConsoleApiAction implements Runnable {
}
}
protected void finishAndPersistConsoleUpdateHistory(ConsoleUpdateHistory.Builder<?, ?> builder) {
builder.setActingUser(consoleApiParams.authResult().user().get());
builder.setUrl(consoleApiParams.request().getRequestURI());
builder.setMethod(consoleApiParams.request().getMethod());
builder.setModificationTime(tm().getTransactionTime());
tm().put(builder.build());
}
/** Specialized exception class used for failure when a user doesn't have the right permission. */
private static class ConsolePermissionForbiddenException extends RuntimeException {
private ConsolePermissionForbiddenException(String message) {

View File

@@ -14,6 +14,7 @@
package google.registry.ui.server.console;
import com.google.gson.Gson;
import google.registry.request.Response;
import google.registry.request.auth.AuthResult;
import google.registry.security.XsrfTokenManager;
@@ -26,13 +27,16 @@ public record ConsoleApiParams(
Response response,
AuthResult authResult,
SendEmailUtils sendEmailUtils,
XsrfTokenManager xsrfTokenManager) {
XsrfTokenManager xsrfTokenManager,
Gson gson) {
public static ConsoleApiParams create(
HttpServletRequest request,
Response response,
AuthResult authResult,
SendEmailUtils sendEmailUtils,
XsrfTokenManager xsrfTokenManager) {
return new ConsoleApiParams(request, response, authResult, sendEmailUtils, xsrfTokenManager);
XsrfTokenManager xsrfTokenManager,
Gson gson) {
return new ConsoleApiParams(
request, response, authResult, sendEmailUtils, xsrfTokenManager, gson);
}
}

View File

@@ -18,7 +18,6 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
import static jakarta.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import com.google.gson.Gson;
import google.registry.model.EppResourceUtils;
import google.registry.model.console.ConsolePermission;
import google.registry.model.console.User;
@@ -41,17 +40,14 @@ public class ConsoleDomainGetAction extends ConsoleApiAction {
public static final String PATH = "/console-api/domain";
private final Gson gson;
private final String paramDomain;
@Inject
public ConsoleDomainGetAction(
ConsoleApiParams consoleApiParams,
Gson gson,
@Parameter("consoleDomain") String paramDomain) {
super(consoleApiParams);
this.paramDomain = paramDomain;
this.gson = gson;
}
@Override
@@ -72,6 +68,6 @@ public class ConsoleDomainGetAction extends ConsoleApiAction {
return;
}
consoleApiParams.response().setStatus(SC_OK);
consoleApiParams.response().setPayload(gson.toJson(domain));
consoleApiParams.response().setPayload(consoleApiParams.gson().toJson(domain));
}
}

View File

@@ -21,7 +21,6 @@ import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Ascii;
import com.google.gson.Gson;
import com.google.gson.annotations.Expose;
import google.registry.model.CreateAutoTimestamp;
import google.registry.model.console.User;
@@ -55,7 +54,6 @@ public class ConsoleDomainListAction extends ConsoleApiAction {
private static final String SEARCH_TERM_QUERY = " AND LOWER(domainName) LIKE :searchTerm";
private static final String ORDER_BY_STATEMENT = " ORDER BY creationTime DESC";
private final Gson gson;
private final String registrarId;
private final Optional<DateTime> checkpointTime;
private final int pageNumber;
@@ -66,7 +64,6 @@ public class ConsoleDomainListAction extends ConsoleApiAction {
@Inject
public ConsoleDomainListAction(
ConsoleApiParams consoleApiParams,
Gson gson,
@Parameter("registrarId") String registrarId,
@Parameter("checkpointTime") Optional<DateTime> checkpointTime,
@Parameter("pageNumber") Optional<Integer> pageNumber,
@@ -74,7 +71,6 @@ public class ConsoleDomainListAction extends ConsoleApiAction {
@Parameter("totalResults") Optional<Long> totalResults,
@Parameter("searchTerm") Optional<String> searchTerm) {
super(consoleApiParams);
this.gson = gson;
this.registrarId = registrarId;
this.checkpointTime = checkpointTime;
this.pageNumber = pageNumber.orElse(0);
@@ -120,7 +116,10 @@ public class ConsoleDomainListAction extends ConsoleApiAction {
consoleApiParams
.response()
.setPayload(gson.toJson(new DomainListResult(domains, checkpoint, actualTotalResults)));
.setPayload(
consoleApiParams
.gson()
.toJson(new DomainListResult(domains, checkpoint, actualTotalResults)));
consoleApiParams.response().setStatus(SC_OK);
}

View File

@@ -27,6 +27,8 @@ import com.google.common.collect.ImmutableSet;
import com.google.gson.annotations.Expose;
import google.registry.flows.EppException.AuthenticationErrorException;
import google.registry.flows.PasswordOnlyTransportCredentials;
import google.registry.model.console.ConsoleUpdateHistory;
import google.registry.model.console.RegistrarUpdateHistory;
import google.registry.model.console.User;
import google.registry.model.registrar.Registrar;
import google.registry.request.Action;
@@ -53,7 +55,6 @@ public class ConsoleEppPasswordAction extends ConsoleApiAction {
private final PasswordOnlyTransportCredentials credentials =
new PasswordOnlyTransportCredentials();
private final AuthenticatedRegistrarAccessor registrarAccessor;
private final Optional<EppPasswordData> eppPasswordChangeRequest;
@Inject
@@ -106,6 +107,14 @@ public class ConsoleEppPasswordAction extends ConsoleApiAction {
Registrar updatedRegistrar =
registrar.asBuilder().setPassword(eppRequestBody.newPassword()).build();
tm().put(updatedRegistrar);
EppPasswordData sanitizedData =
new EppPasswordData(
eppRequestBody.registrarId, "********", "••••••••", "••••••••");
finishAndPersistConsoleUpdateHistory(
new RegistrarUpdateHistory.Builder()
.setType(ConsoleUpdateHistory.Type.REGISTRAR_UPDATE)
.setRegistrar(updatedRegistrar)
.setRequestBody(consoleApiParams.gson().toJson(sanitizedData)));
sendExternalUpdates(
ImmutableMap.of("password", new DiffUtils.DiffPair("********", "••••••••")),
registrar,

View File

@@ -36,6 +36,7 @@ import google.registry.ui.server.SendEmailUtils;
import google.registry.ui.server.console.ConsoleEppPasswordAction.EppPasswordData;
import google.registry.ui.server.console.ConsoleOteAction.OteCreateData;
import google.registry.ui.server.console.ConsoleRegistryLockAction.ConsoleRegistryLockPostInput;
import google.registry.ui.server.console.ConsoleUsersAction.UserData;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Optional;
import org.joda.time.DateTime;
@@ -52,8 +53,10 @@ public final class ConsoleModule {
Response response,
AuthResult authResult,
SendEmailUtils sendEmailUtils,
XsrfTokenManager xsrfTokenManager) {
return ConsoleApiParams.create(request, response, authResult, sendEmailUtils, xsrfTokenManager);
XsrfTokenManager xsrfTokenManager,
Gson gson) {
return ConsoleApiParams.create(
request, response, authResult, sendEmailUtils, xsrfTokenManager, gson);
}
@Provides
@@ -245,6 +248,13 @@ public final class ConsoleModule {
return payload.map(s -> gson.fromJson(s, EppPasswordData.class));
}
@Provides
@Parameter("userData")
public static Optional<UserData> provideUserData(
Gson gson, @OptionalJsonPayload Optional<JsonElement> payload) {
return payload.map(s -> gson.fromJson(s, UserData.class));
}
@Provides
@Parameter("oteCreateData")
public static Optional<OteCreateData> provideOteCreateData(

View File

@@ -27,7 +27,6 @@ import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.annotations.Expose;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.OteAccountBuilder;
@@ -62,7 +61,6 @@ public class ConsoleOteAction extends ConsoleApiAction {
private static final String STAT_TYPE_DESCRIPTION_PARAM = "description";
private static final String STAT_TYPE_REQUIREMENT_PARAM = "requirement";
private static final String STAT_TYPE_TIMES_PERFORMED_PARAM = "timesPerformed";
private final Gson gson;
private final StringGenerator passwordGenerator;
private final Optional<OteCreateData> oteCreateData;
private final Optional<String> maybeGroupEmailAddress;
@@ -72,14 +70,12 @@ public class ConsoleOteAction extends ConsoleApiAction {
@Inject
public ConsoleOteAction(
ConsoleApiParams consoleApiParams,
Gson gson,
IamClient iamClient,
@Parameter("registrarId") String registrarId, // Get request param
@Config("gSuiteConsoleUserGroupEmailAddress") Optional<String> maybeGroupEmailAddress,
@Named("base58StringGenerator") StringGenerator passwordGenerator,
@Parameter("oteCreateData") Optional<OteCreateData> oteCreateData) {
super(consoleApiParams);
this.gson = gson;
this.passwordGenerator = passwordGenerator;
this.oteCreateData = oteCreateData;
this.maybeGroupEmailAddress = maybeGroupEmailAddress;
@@ -116,8 +112,13 @@ public class ConsoleOteAction extends ConsoleApiAction {
consoleApiParams
.response()
.setPayload(
gson.toJson(
ImmutableMap.builder().putAll(registrarIdToTld).put("password", password).build()));
consoleApiParams
.gson()
.toJson(
ImmutableMap.builder()
.putAll(registrarIdToTld)
.put("password", password)
.build()));
}
@Override
@@ -153,7 +154,7 @@ public class ConsoleOteAction extends ConsoleApiAction {
convertSingleRequirement(statType, oteStats.getCount(statType)))
.collect(toImmutableList());
consoleApiParams.response().setStatus(SC_OK);
consoleApiParams.response().setPayload(gson.toJson(stats));
consoleApiParams.response().setPayload(consoleApiParams.gson().toJson(stats));
});
}

View File

@@ -23,7 +23,6 @@ import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import static jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
import com.google.common.collect.ImmutableList;
import com.google.gson.Gson;
import com.google.gson.annotations.Expose;
import google.registry.flows.EppException;
import google.registry.flows.domain.DomainFlowUtils;
@@ -72,7 +71,6 @@ public class ConsoleRegistryLockAction extends ConsoleApiAction {
private final DomainLockUtils domainLockUtils;
private final GmailClient gmailClient;
private final Gson gson;
private final Optional<ConsoleRegistryLockPostInput> optionalPostInput;
private final String registrarId;
@@ -81,14 +79,12 @@ public class ConsoleRegistryLockAction extends ConsoleApiAction {
ConsoleApiParams consoleApiParams,
DomainLockUtils domainLockUtils,
GmailClient gmailClient,
Gson gson,
@Parameter("consoleRegistryLockPostInput")
Optional<ConsoleRegistryLockPostInput> optionalPostInput,
@Parameter("registrarId") String registrarId) {
super(consoleApiParams);
this.domainLockUtils = domainLockUtils;
this.gmailClient = gmailClient;
this.gson = gson;
this.optionalPostInput = optionalPostInput;
this.registrarId = registrarId;
}
@@ -96,7 +92,7 @@ public class ConsoleRegistryLockAction extends ConsoleApiAction {
@Override
protected void getHandler(User user) {
checkPermission(user, registrarId, ConsolePermission.REGISTRY_LOCK);
consoleApiParams.response().setPayload(gson.toJson(getLockedDomains()));
consoleApiParams.response().setPayload(consoleApiParams.gson().toJson(getLockedDomains()));
consoleApiParams.response().setStatus(SC_OK);
}

View File

@@ -17,7 +17,6 @@ package google.registry.ui.server.console;
import static google.registry.request.Action.Method.GET;
import com.google.common.base.Ascii;
import com.google.gson.Gson;
import com.google.gson.annotations.Expose;
import google.registry.model.console.User;
import google.registry.model.domain.RegistryLock;
@@ -42,18 +41,15 @@ public class ConsoleRegistryLockVerifyAction extends ConsoleApiAction {
static final String PATH = "/console-api/registry-lock-verify";
private final DomainLockUtils domainLockUtils;
private final Gson gson;
private final String lockVerificationCode;
@Inject
public ConsoleRegistryLockVerifyAction(
ConsoleApiParams consoleApiParams,
DomainLockUtils domainLockUtils,
Gson gson,
@Parameter("lockVerificationCode") String lockVerificationCode) {
super(consoleApiParams);
this.domainLockUtils = domainLockUtils;
this.gson = gson;
this.lockVerificationCode = lockVerificationCode;
}
@@ -68,7 +64,7 @@ public class ConsoleRegistryLockVerifyAction extends ConsoleApiAction {
RegistryLockVerificationResponse lockResponse =
new RegistryLockVerificationResponse(
Ascii.toLowerCase(action.toString()), lock.getDomainName(), lock.getRegistrarId());
consoleApiParams.response().setPayload(gson.toJson(lockResponse));
consoleApiParams.response().setPayload(consoleApiParams.gson().toJson(lockResponse));
consoleApiParams.response().setStatus(HttpServletResponse.SC_OK);
}

View File

@@ -23,6 +23,8 @@ import static org.apache.http.HttpStatus.SC_OK;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import google.registry.model.console.ConsolePermission;
import google.registry.model.console.ConsoleUpdateHistory;
import google.registry.model.console.RegistrarUpdateHistory;
import google.registry.model.console.User;
import google.registry.model.registrar.Registrar;
import google.registry.request.Action;
@@ -99,6 +101,11 @@ public class ConsoleUpdateRegistrarAction extends ConsoleApiAction {
.build();
tm().put(updatedRegistrar);
finishAndPersistConsoleUpdateHistory(
new RegistrarUpdateHistory.Builder()
.setType(ConsoleUpdateHistory.Type.REGISTRAR_UPDATE)
.setRegistrar(updatedRegistrar)
.setRequestBody(consoleApiParams.gson().toJson(registrarParam)));
sendExternalUpdatesIfNecessary(
EmailInfo.create(
existingRegistrar.get(),

View File

@@ -14,11 +14,15 @@
package google.registry.ui.server.console;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.model.console.RegistrarRole.ACCOUNT_MANAGER;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.Action.Method.DELETE;
import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.POST;
import static google.registry.request.Action.Method.PUT;
import static jakarta.servlet.http.HttpServletResponse.SC_CREATED;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
@@ -28,8 +32,10 @@ import com.google.api.services.directory.model.UserName;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.annotations.Expose;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.console.ConsolePermission;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User;
import google.registry.model.console.UserRoles;
import google.registry.persistence.VKey;
@@ -38,11 +44,14 @@ import google.registry.request.Action.GkeService;
import google.registry.request.HttpException.BadRequestException;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.tools.IamClient;
import google.registry.util.StringGenerator;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.IntStream;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Named;
@@ -50,36 +59,40 @@ import javax.inject.Named;
service = Action.GaeService.DEFAULT,
gkeService = GkeService.CONSOLE,
path = ConsoleUsersAction.PATH,
method = {GET, POST},
method = {GET, POST, DELETE, PUT},
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
public class ConsoleUsersAction extends ConsoleApiAction {
static final String PATH = "/console-api/users";
private static final int PASSWORD_LENGTH = 16;
private static final int PASSWORD_LENGTH = 16;
private static final Splitter EMAIL_SPLITTER = Splitter.on('@').trimResults();
private final Gson gson;
private final String registrarId;
private final Directory directory;
private final StringGenerator passwordGenerator;
private final Optional<UserData> userData;
private final Optional<String> maybeGroupEmailAddress;
private final IamClient iamClient;
private final String gSuiteDomainName;
@Inject
public ConsoleUsersAction(
ConsoleApiParams consoleApiParams,
Gson gson,
Directory directory,
IamClient iamClient,
@Config("gSuiteDomainName") String gSuiteDomainName,
@Config("gSuiteConsoleUserGroupEmailAddress") Optional<String> maybeGroupEmailAddress,
@Named("base58StringGenerator") StringGenerator passwordGenerator,
@Parameter("userData") Optional<UserData> userData,
@Parameter("registrarId") String registrarId) {
super(consoleApiParams);
this.gson = gson;
this.registrarId = registrarId;
this.directory = directory;
this.passwordGenerator = passwordGenerator;
}
private static String generateNewEmailAddress(User user, String increment) {
List<String> emailParts = EMAIL_SPLITTER.splitToList(user.getEmailAddress());
return String.format("%s-%s@%s", emailParts.get(0), increment, emailParts.get(1));
this.userData = userData;
this.maybeGroupEmailAddress = maybeGroupEmailAddress;
this.iamClient = iamClient;
this.gSuiteDomainName = gSuiteDomainName;
}
@Override
@@ -87,7 +100,18 @@ public class ConsoleUsersAction extends ConsoleApiAction {
// Temporary flag while testing
if (user.getUserRoles().isAdmin()) {
checkPermission(user, registrarId, ConsolePermission.MANAGE_USERS);
tm().transact(() -> runInTransaction(user));
tm().transact(this::runCreateInTransaction);
} else {
consoleApiParams.response().setStatus(SC_FORBIDDEN);
}
}
@Override
protected void putHandler(User user) {
// Temporary flag while testing
if (user.getUserRoles().isAdmin()) {
checkPermission(user, registrarId, ConsolePermission.MANAGE_USERS);
tm().transact(() -> runUpdateInTransaction());
} else {
consoleApiParams.response().setStatus(SC_FORBIDDEN);
}
@@ -96,40 +120,73 @@ public class ConsoleUsersAction extends ConsoleApiAction {
@Override
protected void getHandler(User user) {
checkPermission(user, registrarId, ConsolePermission.MANAGE_USERS);
List<ImmutableMap> users =
getAllUsers().stream()
.filter(u -> u.getUserRoles().getRegistrarRoles().containsKey(registrarId))
List<UserData> users =
getAllRegistrarUsers(registrarId).stream()
.map(
u ->
ImmutableMap.of(
"emailAddress",
new UserData(
u.getEmailAddress(),
"role",
u.getUserRoles().getRegistrarRoles().get(registrarId)))
u.getUserRoles().getRegistrarRoles().get(registrarId).toString(),
null))
.collect(Collectors.toList());
consoleApiParams.response().setPayload(gson.toJson(users));
consoleApiParams.response().setPayload(consoleApiParams.gson().toJson(users));
consoleApiParams.response().setStatus(SC_OK);
}
private void runInTransaction(User user) throws IOException {
String nextAvailableIncrement =
Stream.of("1", "2", "3")
.filter(
increment ->
tm().loadByKeyIfPresent(
VKey.create(User.class, generateNewEmailAddress(user, increment)))
.isEmpty())
@Override
protected void deleteHandler(User user) {
// Temporary flag while testing
if (user.getUserRoles().isAdmin()) {
checkPermission(user, registrarId, ConsolePermission.MANAGE_USERS);
tm().transact(this::runDeleteInTransaction);
} else {
consoleApiParams.response().setStatus(SC_FORBIDDEN);
}
}
private void runDeleteInTransaction() throws IOException {
if (!isModifyingRequestValid()) {
return;
}
String email = this.userData.get().emailAddress;
try {
directory.users().delete(email).execute();
} catch (IOException e) {
setFailedResponse("Failed to delete the user workspace account", SC_INTERNAL_SERVER_ERROR);
throw e;
}
VKey<User> key = VKey.create(User.class, email);
tm().delete(key);
User.revokeIapPermission(email, maybeGroupEmailAddress, cloudTasksUtils, null, iamClient);
consoleApiParams.response().setStatus(SC_OK);
}
private void runCreateInTransaction() throws IOException {
ImmutableList<User> allRegistrarUsers = getAllRegistrarUsers(registrarId);
if (allRegistrarUsers.size() >= 4)
throw new BadRequestException("Total users amount per registrar is limited to 4");
String nextAvailableEmail =
IntStream.range(1, 5)
.mapToObj(i -> String.format("%s-user%s@%s", registrarId, i, gSuiteDomainName))
.filter(email -> tm().loadByKeyIfPresent(VKey.create(User.class, email)).isEmpty())
.findFirst()
.orElseThrow(() -> new BadRequestException("Extra users amount is limited to 3"));
// Can only happen if registrar cycled through 20 users, which is unlikely
.orElseThrow(
() -> new BadRequestException("Failed to find available increment for new user"));
com.google.api.services.directory.model.User newUser =
new com.google.api.services.directory.model.User();
newUser.setName(
new UserName().setFamilyName(registrarId).setGivenName("User" + nextAvailableIncrement));
new UserName()
.setFamilyName(registrarId)
.setGivenName(EMAIL_SPLITTER.splitToList(nextAvailableEmail).get(0)));
newUser.setPassword(passwordGenerator.createString(PASSWORD_LENGTH));
newUser.setPrimaryEmail(generateNewEmailAddress(user, nextAvailableIncrement));
newUser.setPrimaryEmail(nextAvailableEmail);
try {
directory.users().insert(newUser).execute();
@@ -146,26 +203,72 @@ public class ConsoleUsersAction extends ConsoleApiAction {
User.Builder builder =
new User.Builder().setUserRoles(userRoles).setEmailAddress(newUser.getPrimaryEmail());
tm().put(builder.build());
User.grantIapPermission(
nextAvailableEmail, maybeGroupEmailAddress, cloudTasksUtils, null, iamClient);
consoleApiParams.response().setStatus(SC_OK);
consoleApiParams.response().setStatus(SC_CREATED);
consoleApiParams
.response()
.setPayload(
gson.toJson(
ImmutableMap.of(
"password",
newUser.getPassword(),
"emailAddress",
newUser.getPrimaryEmail(),
"role",
ACCOUNT_MANAGER)));
consoleApiParams
.gson()
.toJson(
new UserData(
newUser.getPrimaryEmail(),
ACCOUNT_MANAGER.toString(),
newUser.getPassword())));
}
private ImmutableList<User> getAllUsers() {
private void runUpdateInTransaction() {
if (!isModifyingRequestValid()) {
return;
}
UserData userData = this.userData.get();
UserRoles userRoles =
new UserRoles.Builder()
.setRegistrarRoles(ImmutableMap.of(registrarId, RegistrarRole.valueOf(userData.role)))
.build();
User updatedUser =
tm().loadByKeyIfPresent(VKey.create(User.class, userData.emailAddress))
.get()
.asBuilder()
.setUserRoles(userRoles)
.build();
tm().put(updatedUser);
consoleApiParams.response().setStatus(SC_OK);
}
private boolean isModifyingRequestValid() {
if (userData.isEmpty()
|| isNullOrEmpty(userData.get().emailAddress)
|| isNullOrEmpty(userData.get().role)) {
throw new BadRequestException("User data is missing or incomplete");
}
String email = userData.get().emailAddress;
User userToUpdate =
tm().loadByKeyIfPresent(VKey.create(User.class, email))
.orElseThrow(
() -> new BadRequestException(String.format("User %s doesn't exist", email)));
if (!userToUpdate.getUserRoles().getRegistrarRoles().containsKey(registrarId)) {
setFailedResponse(
String.format("Can't update user not associated with registrarId %s", registrarId),
SC_FORBIDDEN);
return false;
}
return true;
}
private ImmutableList<User> getAllRegistrarUsers(String registrarId) {
return tm().transact(
() ->
tm().loadAllOf(User.class).stream()
.filter(u -> !u.getUserRoles().getRegistrarRoles().isEmpty())
.filter(u -> u.getUserRoles().getRegistrarRoles().containsKey(registrarId))
.collect(toImmutableList()));
}
public record UserData(
@Expose String emailAddress, @Expose String role, @Expose @Nullable String password) {}
}

View File

@@ -26,8 +26,9 @@ import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;
import com.google.gson.Gson;
import google.registry.model.console.ConsolePermission;
import google.registry.model.console.ConsoleUpdateHistory;
import google.registry.model.console.RegistrarUpdateHistory;
import google.registry.model.console.User;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarBase;
@@ -62,7 +63,6 @@ public class RegistrarsAction extends ConsoleApiAction {
WHERE registrar_id in :registrarIds
""";
static final String PATH = "/console-api/registrars";
private final Gson gson;
private final Optional<Registrar> registrar;
private final StringGenerator passwordGenerator;
private final StringGenerator passcodeGenerator;
@@ -70,12 +70,10 @@ public class RegistrarsAction extends ConsoleApiAction {
@Inject
public RegistrarsAction(
ConsoleApiParams consoleApiParams,
Gson gson,
@Parameter("registrar") Optional<Registrar> registrar,
@Named("base58StringGenerator") StringGenerator passwordGenerator,
@Named("digitOnlyStringGenerator") StringGenerator passcodeGenerator) {
super(consoleApiParams);
this.gson = gson;
this.registrar = registrar;
this.passcodeGenerator = passcodeGenerator;
this.passwordGenerator = passwordGenerator;
@@ -88,7 +86,7 @@ public class RegistrarsAction extends ConsoleApiAction {
Streams.stream(Registrar.loadAll())
.filter(r -> allowedRegistrarTypes.contains(r.getType()))
.collect(ImmutableList.toImmutableList());
consoleApiParams.response().setPayload(gson.toJson(registrars));
consoleApiParams.response().setPayload(consoleApiParams.gson().toJson(registrars));
consoleApiParams.response().setStatus(SC_OK);
} else if (user.getUserRoles().getRegistrarRoles().values().stream()
.anyMatch(role -> role.hasPermission(ConsolePermission.VIEW_REGISTRAR_DETAILS))) {
@@ -106,7 +104,7 @@ public class RegistrarsAction extends ConsoleApiAction {
.setParameter("registrarIds", accessibleRegistrarIds)
.getResultList());
consoleApiParams.response().setPayload(gson.toJson(registrars));
consoleApiParams.response().setPayload(consoleApiParams.gson().toJson(registrars));
consoleApiParams.response().setStatus(SC_OK);
} else {
consoleApiParams.response().setStatus(SC_FORBIDDEN);
@@ -175,6 +173,11 @@ public class RegistrarsAction extends ConsoleApiAction {
"Registrar with registrarId %s already exists",
registrar.getRegistrarId());
tm().putAll(registrar, contact);
finishAndPersistConsoleUpdateHistory(
new RegistrarUpdateHistory.Builder()
.setType(ConsoleUpdateHistory.Type.REGISTRAR_UPDATE)
.setRegistrar(registrar)
.setRequestBody(consoleApiParams.gson().toJson(registrar)));
});
}

View File

@@ -28,7 +28,6 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.flogger.FluentLogger;
import com.google.gson.Gson;
import google.registry.model.console.ConsolePermission;
import google.registry.model.console.User;
import google.registry.model.registrar.Registrar;
@@ -60,18 +59,15 @@ import javax.inject.Inject;
public class ContactAction extends ConsoleApiAction {
static final String PATH = "/console-api/settings/contacts";
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final Gson gson;
private final Optional<ImmutableSet<RegistrarPoc>> contacts;
private final String registrarId;
@Inject
public ContactAction(
ConsoleApiParams consoleApiParams,
Gson gson,
@Parameter("registrarId") String registrarId,
@Parameter("contacts") Optional<ImmutableSet<RegistrarPoc>> contacts) {
super(consoleApiParams);
this.gson = gson;
this.registrarId = registrarId;
this.contacts = contacts;
}
@@ -90,7 +86,7 @@ public class ContactAction extends ConsoleApiAction {
.collect(toImmutableList()));
consoleApiParams.response().setStatus(SC_OK);
consoleApiParams.response().setPayload(gson.toJson(am));
consoleApiParams.response().setPayload(consoleApiParams.gson().toJson(am));
}
@Override

View File

@@ -25,6 +25,8 @@ import com.google.common.collect.ImmutableSet;
import google.registry.flows.certs.CertificateChecker;
import google.registry.flows.certs.CertificateChecker.InsecureCertificateException;
import google.registry.model.console.ConsolePermission;
import google.registry.model.console.ConsoleUpdateHistory;
import google.registry.model.console.RegistrarUpdateHistory;
import google.registry.model.console.User;
import google.registry.model.registrar.Registrar;
import google.registry.request.Action;
@@ -117,6 +119,11 @@ public class SecurityAction extends ConsoleApiAction {
Registrar updatedRegistrar = updatedRegistrarBuilder.build();
tm().put(updatedRegistrar);
finishAndPersistConsoleUpdateHistory(
new RegistrarUpdateHistory.Builder()
.setType(ConsoleUpdateHistory.Type.REGISTRAR_UPDATE)
.setRegistrar(updatedRegistrar)
.setRequestBody(consoleApiParams.gson().toJson(registrar.get())));
sendExternalUpdatesIfNecessary(
EmailInfo.create(savedRegistrar, updatedRegistrar, ImmutableSet.of(), ImmutableSet.of()));

View File

@@ -22,6 +22,8 @@ import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import google.registry.model.console.ConsolePermission;
import google.registry.model.console.ConsoleUpdateHistory;
import google.registry.model.console.RegistrarUpdateHistory;
import google.registry.model.console.User;
import google.registry.model.registrar.Registrar;
import google.registry.request.Action;
@@ -104,6 +106,11 @@ public class WhoisRegistrarFieldsAction extends ConsoleApiAction {
.setEmailAddress(providedRegistrar.getEmailAddress())
.build();
tm().put(newRegistrar);
finishAndPersistConsoleUpdateHistory(
new RegistrarUpdateHistory.Builder()
.setType(ConsoleUpdateHistory.Type.REGISTRAR_UPDATE)
.setRegistrar(newRegistrar)
.setRequestBody(consoleApiParams.gson().toJson(registrar.get())));
sendExternalUpdatesIfNecessary(
EmailInfo.create(
savedRegistrar,

View File

@@ -16,10 +16,14 @@ package google.registry.export;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.export.ExportDomainListsAction.REGISTERED_DOMAINS_FILENAME;
import static google.registry.model.common.FeatureFlag.FeatureName.INCLUDE_PENDING_DELETE_DATE_FOR_DOMAINS;
import static google.registry.model.common.FeatureFlag.FeatureStatus.ACTIVE;
import static google.registry.model.common.FeatureFlag.FeatureStatus.INACTIVE;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
import static google.registry.testing.DatabaseHelper.persistDeletedDomain;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.eq;
@@ -31,8 +35,14 @@ import com.google.cloud.storage.BlobId;
import com.google.cloud.storage.StorageException;
import com.google.cloud.storage.contrib.nio.testing.LocalStorageHelper;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.net.MediaType;
import google.registry.gcs.GcsUtils;
import google.registry.model.common.FeatureFlag;
import google.registry.model.domain.Domain;
import google.registry.model.domain.GracePeriod;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.tld.Tld;
import google.registry.model.tld.Tld.TldType;
import google.registry.persistence.transaction.JpaTestExtensions;
@@ -56,7 +66,7 @@ class ExportDomainListsActionTest {
@RegisterExtension
final JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
new JpaTestExtensions.Builder().withClock(clock).buildIntegrationTestExtension();
@BeforeEach
void beforeEach() {
@@ -70,6 +80,7 @@ class ExportDomainListsActionTest {
action.gcsUtils = gcsUtils;
action.clock = clock;
action.driveConnection = driveConnection;
persistFeatureFlag(INACTIVE);
}
private void verifyExportedToDrive(String folderId, String domains) throws Exception {
@@ -83,7 +94,7 @@ class ExportDomainListsActionTest {
}
@Test
void test_outputsOnlyActiveDomains() throws Exception {
void test_outputsOnlyActiveDomains_txt() throws Exception {
persistActiveDomain("onetwo.tld");
persistActiveDomain("rudnitzky.tld");
persistDeletedDomain("mortuary.tld", DateTime.parse("2001-03-14T10:11:12Z"));
@@ -97,7 +108,22 @@ class ExportDomainListsActionTest {
}
@Test
void test_outputsOnlyDomainsOnRealTlds() throws Exception {
void test_outputsOnlyActiveDomains_csv() throws Exception {
persistFeatureFlag(ACTIVE);
persistActiveDomain("onetwo.tld");
persistActiveDomain("rudnitzky.tld");
persistDeletedDomain("mortuary.tld", DateTime.parse("2001-03-14T10:11:12Z"));
action.run();
BlobId existingFile = BlobId.of("outputbucket", "tld.txt");
String tlds = new String(gcsUtils.readBytesFrom(existingFile), UTF_8);
// Check that it only contains the active domains, not the dead one.
assertThat(tlds).isEqualTo("onetwo.tld,\nrudnitzky.tld,");
verifyExportedToDrive("brouhaha", "onetwo.tld,\nrudnitzky.tld,");
verifyNoMoreInteractions(driveConnection);
}
@Test
void test_outputsOnlyDomainsOnRealTlds_txt() throws Exception {
persistActiveDomain("onetwo.tld");
persistActiveDomain("rudnitzky.tld");
persistActiveDomain("wontgo.testtld");
@@ -116,7 +142,58 @@ class ExportDomainListsActionTest {
}
@Test
void test_outputsDomainsFromDifferentTldsToMultipleFiles() throws Exception {
void test_outputsOnlyDomainsOnRealTlds_csv() throws Exception {
persistFeatureFlag(ACTIVE);
persistActiveDomain("onetwo.tld");
persistActiveDomain("rudnitzky.tld");
persistActiveDomain("wontgo.testtld");
action.run();
BlobId existingFile = BlobId.of("outputbucket", "tld.txt");
String tlds = new String(gcsUtils.readBytesFrom(existingFile), UTF_8).trim();
// Check that it only contains the domains on the real TLD, and not the test one.
assertThat(tlds).isEqualTo("onetwo.tld,\nrudnitzky.tld,");
// Make sure that the test TLD file wasn't written out.
BlobId nonexistentFile = BlobId.of("outputbucket", "testtld.txt");
assertThrows(StorageException.class, () -> gcsUtils.readBytesFrom(nonexistentFile));
ImmutableList<String> ls = gcsUtils.listFolderObjects("outputbucket", "");
assertThat(ls).containsExactly("tld.txt");
verifyExportedToDrive("brouhaha", "onetwo.tld,\nrudnitzky.tld,");
verifyNoMoreInteractions(driveConnection);
}
@Test
void test_outputIncludesDeletionTimes_forPendingDeletes_notRdemption() throws Exception {
persistFeatureFlag(ACTIVE);
// Domains pending delete (meaning the 5 day period, not counting the 30 day redemption period)
// should include their pending deletion date
persistActiveDomain("active.tld");
Domain redemption = persistActiveDomain("redemption.tld");
persistResource(
redemption
.asBuilder()
.addStatusValue(StatusValue.PENDING_DELETE)
.addGracePeriod(
GracePeriod.createWithoutBillingEvent(
GracePeriodStatus.REDEMPTION,
redemption.getRepoId(),
clock.nowUtc().plusDays(20),
redemption.getCurrentSponsorRegistrarId()))
.build());
persistResource(
persistActiveDomain("pendingdelete.tld")
.asBuilder()
.addStatusValue(StatusValue.PENDING_DELETE)
.setDeletionTime(clock.nowUtc().plusDays(3))
.build());
action.run();
verifyExportedToDrive(
"brouhaha", "active.tld,\npendingdelete.tld,2020-02-05T02:02:02.000Z\nredemption.tld,");
}
@Test
void test_outputsDomainsFromDifferentTldsToMultipleFiles_txt() throws Exception {
createTld("tldtwo");
persistResource(Tld.get("tldtwo").asBuilder().setDriveFolderId("hooray").build());
@@ -143,4 +220,43 @@ class ExportDomainListsActionTest {
// tldthree does not have a drive id, so no export to drive is performed.
verifyNoMoreInteractions(driveConnection);
}
@Test
void test_outputsDomainsFromDifferentTldsToMultipleFiles_csv() throws Exception {
persistFeatureFlag(ACTIVE);
createTld("tldtwo");
persistResource(Tld.get("tldtwo").asBuilder().setDriveFolderId("hooray").build());
createTld("tldthree");
// You'd think this test was written around Christmas, but it wasn't.
persistActiveDomain("dasher.tld");
persistActiveDomain("prancer.tld");
persistActiveDomain("rudolph.tldtwo");
persistActiveDomain("santa.tldtwo");
persistActiveDomain("buddy.tldtwo");
persistActiveDomain("cupid.tldthree");
action.run();
BlobId firstTldFile = BlobId.of("outputbucket", "tld.txt");
String tlds = new String(gcsUtils.readBytesFrom(firstTldFile), UTF_8).trim();
assertThat(tlds).isEqualTo("dasher.tld,\nprancer.tld,");
BlobId secondTldFile = BlobId.of("outputbucket", "tldtwo.txt");
String moreTlds = new String(gcsUtils.readBytesFrom(secondTldFile), UTF_8).trim();
assertThat(moreTlds).isEqualTo("buddy.tldtwo,\nrudolph.tldtwo,\nsanta.tldtwo,");
BlobId thirdTldFile = BlobId.of("outputbucket", "tldthree.txt");
String evenMoreTlds = new String(gcsUtils.readBytesFrom(thirdTldFile), UTF_8).trim();
assertThat(evenMoreTlds).isEqualTo("cupid.tldthree,");
verifyExportedToDrive("brouhaha", "dasher.tld,\nprancer.tld,");
verifyExportedToDrive("hooray", "buddy.tldtwo,\nrudolph.tldtwo,\nsanta.tldtwo,");
// tldthree does not have a drive id, so no export to drive is performed.
verifyNoMoreInteractions(driveConnection);
}
private void persistFeatureFlag(FeatureFlag.FeatureStatus status) {
persistResource(
new FeatureFlag()
.asBuilder()
.setFeatureName(INCLUDE_PENDING_DELETE_DATE_FOR_DOMAINS)
.setStatusMap(ImmutableSortedMap.of(START_OF_TIME, status))
.build());
}
}

View File

@@ -24,7 +24,6 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
import static google.registry.testing.DatabaseHelper.loadByEntity;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.joda.time.DateTimeZone.UTC;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableSet;
@@ -33,6 +32,7 @@ import google.registry.model.EntityTestCase;
import google.registry.model.common.FeatureFlag.FeatureFlagNotFoundException;
import google.registry.model.common.FeatureFlag.FeatureStatus;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link FeatureFlag}. */
@@ -50,7 +50,7 @@ public class FeatureFlagTest extends EntityTestCase {
.setStatusMap(
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
.put(START_OF_TIME, INACTIVE)
.put(DateTime.now(UTC).plusWeeks(8), ACTIVE)
.put(fakeClock.nowUtc().plusWeeks(8), ACTIVE)
.build())
.build();
persistResource(featureFlag);
@@ -66,7 +66,7 @@ public class FeatureFlagTest extends EntityTestCase {
.setStatusMap(
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
.put(START_OF_TIME, INACTIVE)
.put(DateTime.now(UTC).plusWeeks(8), ACTIVE)
.put(fakeClock.nowUtc().plusWeeks(8), ACTIVE)
.build())
.build();
persistResource(featureFlag);
@@ -82,7 +82,7 @@ public class FeatureFlagTest extends EntityTestCase {
.setStatusMap(
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
.put(START_OF_TIME, INACTIVE)
.put(DateTime.now(UTC).plusWeeks(8), ACTIVE)
.put(fakeClock.nowUtc().plusWeeks(8), ACTIVE)
.build())
.build());
FeatureFlag featureFlag2 =
@@ -92,7 +92,7 @@ public class FeatureFlagTest extends EntityTestCase {
.setStatusMap(
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
.put(START_OF_TIME, INACTIVE)
.put(DateTime.now(UTC).plusWeeks(3), INACTIVE)
.put(fakeClock.nowUtc().plusWeeks(3), INACTIVE)
.build())
.build());
FeatureFlag featureFlag3 =
@@ -122,7 +122,7 @@ public class FeatureFlagTest extends EntityTestCase {
.setStatusMap(
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
.put(START_OF_TIME, INACTIVE)
.put(DateTime.now(UTC).plusWeeks(8), ACTIVE)
.put(fakeClock.nowUtc().plusWeeks(8), ACTIVE)
.build())
.build());
persistResource(
@@ -131,7 +131,7 @@ public class FeatureFlagTest extends EntityTestCase {
.setStatusMap(
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
.put(START_OF_TIME, INACTIVE)
.put(DateTime.now(UTC).plusWeeks(3), INACTIVE)
.put(fakeClock.nowUtc().plusWeeks(3), INACTIVE)
.build())
.build());
FeatureFlagNotFoundException thrown =
@@ -164,7 +164,7 @@ public class FeatureFlagTest extends EntityTestCase {
.setStatusMap(
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
.put(START_OF_TIME, INACTIVE)
.put(DateTime.now(UTC).plusWeeks(8), ACTIVE)
.put(fakeClock.nowUtc().plusWeeks(8), ACTIVE)
.build());
IllegalArgumentException thrown =
assertThrows(IllegalArgumentException.class, () -> featureFlagBuilder.build());
@@ -180,7 +180,7 @@ public class FeatureFlagTest extends EntityTestCase {
() ->
featureFlagBuilder.setStatusMap(
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
.put(DateTime.now(UTC).plusWeeks(8), ACTIVE)
.put(fakeClock.nowUtc().plusWeeks(8), ACTIVE)
.build()));
assertThat(thrown)
.hasMessageThat()
@@ -203,4 +203,37 @@ public class FeatureFlagTest extends EntityTestCase {
fakeClock.setTo(DateTime.parse("2011-10-17TZ"));
assertThat(tm().transact(() -> FeatureFlag.isActiveNow(TEST_FEATURE))).isTrue();
}
@Test
void testSuccess_default_exists() {
persistResource(
new FeatureFlag.Builder()
.setFeatureName(TEST_FEATURE)
.setStatusMap(
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
.put(START_OF_TIME, INACTIVE)
.put(fakeClock.nowUtc().plusWeeks(8), ACTIVE)
.build())
.build());
tm().transact(
() -> {
assertThat(FeatureFlag.isActiveNowOrElse(TEST_FEATURE, false)).isFalse();
assertThat(FeatureFlag.isActiveNowOrElse(TEST_FEATURE, true)).isFalse();
});
fakeClock.advanceBy(Duration.standardDays(365));
tm().transact(
() -> {
assertThat(FeatureFlag.isActiveNowOrElse(TEST_FEATURE, false)).isTrue();
assertThat(FeatureFlag.isActiveNowOrElse(TEST_FEATURE, true)).isTrue();
});
}
@Test
void testSuccess_default_doesNotExist() {
tm().transact(
() -> {
assertThat(FeatureFlag.isActiveNowOrElse(TEST_FEATURE, false)).isFalse();
assertThat(FeatureFlag.isActiveNowOrElse(TEST_FEATURE, true)).isTrue();
});
}
}

View File

@@ -20,6 +20,7 @@ import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableList;
import google.registry.groups.GmailClient;
import google.registry.model.console.User;
import google.registry.request.RequestModule;
import google.registry.request.auth.AuthResult;
import google.registry.security.XsrfTokenManager;
import google.registry.ui.server.SendEmailUtils;
@@ -45,7 +46,13 @@ public final class ConsoleApiParamsUtils {
xsrfTokenManager.generateToken(
authResult.user().map(User::getEmailAddress).orElse("")))
});
when(request.getRequestURI()).thenReturn("/console/fake-url");
return ConsoleApiParams.create(
request, new FakeResponse(), authResult, sendEmailUtils, xsrfTokenManager);
request,
new FakeResponse(),
authResult,
sendEmailUtils,
xsrfTokenManager,
RequestModule.provideGson());
}
}

View File

@@ -1294,6 +1294,11 @@ public final class DatabaseHelper {
return tm().transact(() -> tm().loadByEntitiesIfPresent(entities));
}
/** Loads the only instance of this particular class, or empty if none exists. */
public static <T> Optional<T> loadSingleton(Class<T> clazz) {
return tm().transact(() -> tm().loadSingleton(clazz));
}
/** Returns whether or not the given entity exists in Cloud SQL. */
public static boolean existsInDb(ImmutableObject object) {
return tm().transact(() -> tm().exists(object));

View File

@@ -91,10 +91,16 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
runCommandForced(
"--email",
"user@example.test",
"--registrar_roles",
"TheRegistrar=PRIMARY_CONTACT",
"--registry_lock_email_address",
"registrylockemail@otherexample.test");
assertThat(loadExistingUser("user@example.test").getRegistryLockEmailAddress())
.hasValue("registrylockemail@otherexample.test");
"registrylockemail@otherexample.test",
"--registry_lock_password",
"password");
User user = loadExistingUser("user@example.test");
assertThat(user.getRegistryLockEmailAddress()).hasValue("registrylockemail@otherexample.test");
assertThat(user.verifyRegistryLockPassword("password")).isTrue();
assertThat(user.verifyRegistryLockPassword("foobar")).isFalse();
}
@Test
@@ -174,4 +180,18 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
.hasMessageThat()
.isEqualTo("Provided email this is not valid is not a valid email address");
}
@Test
void testFailure_registryLockPassword_withoutEmail() {
assertThat(
assertThrows(
IllegalArgumentException.class,
() ->
runCommandForced(
"--email", "user@example.test", "--registry_lock_password", "password")))
.hasMessageThat()
.isEqualTo(
"Cannot set/remove registry lock password on a user without a registry lock email"
+ " address");
}
}

View File

@@ -25,8 +25,12 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import com.beust.jcommander.ParameterException;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import google.registry.model.domain.Domain;
import google.registry.model.domain.fee.FeeQueryCommandExtensionItem;
import google.registry.model.domain.token.AllocationToken;
import google.registry.util.DateTimeUtils;
import org.joda.time.DateTime;
import org.junit.jupiter.api.Test;
@@ -41,10 +45,49 @@ class GetAllocationTokenCommandTest extends CommandTestCase<GetAllocationTokenCo
new AllocationToken.Builder()
.setToken("foo")
.setTokenType(SINGLE_USE)
.setAllowedEppActions(
ImmutableSet.of(FeeQueryCommandExtensionItem.CommandName.CREATE))
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("bar"))
.setDiscountFraction(0.5)
.setDiscountYears(2)
.setTokenStatusTransitions(
ImmutableSortedMap.of(
DateTimeUtils.START_OF_TIME,
AllocationToken.TokenStatus.NOT_STARTED,
fakeClock.nowUtc(),
AllocationToken.TokenStatus.VALID))
.setDomainName("foo.bar")
.build());
runCommand("foo");
assertInStdout(token.toString(), "Token foo was not redeemed.");
assertStdoutIs(
"""
AllocationToken: {
allowedClientIds=[TheRegistrar]
allowedEppActions=[CREATE]
allowedTlds=[bar]
creationTime=CreateAutoTimestamp: {
creationTime=2022-09-01T00:00:00.000Z
}
discountFraction=0.5
discountPremiums=false
discountPrice=null
discountYears=2
domainName=foo.bar
redemptionHistoryId=null
registrationBehavior=DEFAULT
renewalPrice=null
renewalPriceBehavior=DEFAULT
token=foo
tokenStatusTransitions={1970-01-01T00:00:00.000Z=NOT_STARTED, 2022-09-01T00:00:00.000Z=VALID}
tokenType=SINGLE_USE
updateTimestamp=UpdateAutoTimestamp: {
lastUpdateTime=2022-09-01T00:00:00.000Z
}
}
Token foo was not redeemed.
""");
}
@Test

View File

@@ -33,6 +33,8 @@ import google.registry.model.billing.BillingRecurrence;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainHistory;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.transfer.DomainTransferData;
import google.registry.model.transfer.TransferStatus;
import java.util.Optional;
import javax.annotation.Nullable;
import org.joda.money.CurrencyUnit;
@@ -121,6 +123,25 @@ public class UpdateRecurrenceCommandTest extends CommandTestCase<UpdateRecurrenc
Money.of(CurrencyUnit.USD, 9001));
}
@Test
void testSuccess_completedTransfer() throws Exception {
Domain domain = persistDomain();
domain =
persistResource(
domain
.asBuilder()
.setTransferData(
new DomainTransferData.Builder()
.setTransferStatus(TransferStatus.CLIENT_APPROVED)
.setPendingTransferExpirationTime(fakeClock.nowUtc().minusDays(8))
.build())
.build());
BillingRecurrence billingRecurrence = loadByKey(domain.getAutorenewBillingEvent());
runCommandForced("domain.tld", "--renewal_price_behavior", "NONPREMIUM");
assertNewBillingEventAndHistory(
billingRecurrence.getId(), RenewalPriceBehavior.NONPREMIUM, null);
}
@Test
void testFailure_nonexistentDomain() {
assertThrows(

View File

@@ -16,6 +16,7 @@ package google.registry.tools;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.DatabaseHelper.loadExistingUser;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.DatabaseHelper.putInDb;
import static org.junit.Assert.assertThrows;
@@ -98,6 +99,44 @@ public class UpdateUserCommandTest extends CommandTestCase<UpdateUserCommand> {
.isEqualTo(GlobalRole.FTE);
}
@Test
void testSuccess_removePassword() throws Exception {
// Empty password value removes the password
persistResource(
loadExistingUser("user@example.test")
.asBuilder()
.setUserRoles(
new UserRoles.Builder()
.setRegistrarRoles(ImmutableMap.of("TheRegistrar", RegistrarRole.TECH_CONTACT))
.build())
.setRegistryLockEmailAddress("registrylock@example.test")
.setRegistryLockPassword("password")
.build());
assertThat(loadExistingUser("user@example.test").hasRegistryLockPassword()).isTrue();
runCommandForced("--email", "user@example.test", "--registry_lock_password", "");
assertThat(loadExistingUser("user@example.test").hasRegistryLockPassword()).isFalse();
}
@Test
void testSuccess_setsPassword() throws Exception {
persistResource(
loadExistingUser("user@example.test")
.asBuilder()
.setUserRoles(
new UserRoles.Builder()
.setRegistrarRoles(ImmutableMap.of("TheRegistrar", RegistrarRole.TECH_CONTACT))
.build())
.setRegistryLockEmailAddress("registrylock@example.test")
.setRegistryLockPassword("password")
.build());
assertThat(loadExistingUser("user@example.test").verifyRegistryLockPassword("password"))
.isTrue();
runCommandForced("--email", "user@example.test", "--registry_lock_password", "foobar");
assertThat(loadExistingUser("user@example.test").verifyRegistryLockPassword("password"))
.isFalse();
assertThat(loadExistingUser("user@example.test").verifyRegistryLockPassword("foobar")).isTrue();
}
@Test
void testFailure_doesntExist() {
assertThat(
@@ -122,4 +161,18 @@ public class UpdateUserCommandTest extends CommandTestCase<UpdateUserCommand> {
.hasMessageThat()
.isEqualTo("Provided email this is not valid is not a valid email address");
}
@Test
void testFailure_setPassword_noEmail() {
assertThat(
assertThrows(
IllegalArgumentException.class,
() ->
runCommandForced(
"--email", "user@example.test", "--registry_lock_password", "foobar")))
.hasMessageThat()
.isEqualTo(
"Cannot set/remove registry lock password on a user without a registry lock email"
+ " address");
}
}

View File

@@ -22,13 +22,11 @@ import static jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User;
import google.registry.model.console.UserRoles;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.request.Action;
import google.registry.request.RequestModule;
import google.registry.request.auth.AuthResult;
import google.registry.testing.ConsoleApiParamsUtils;
import google.registry.testing.DatabaseHelper;
@@ -40,7 +38,6 @@ import org.junit.jupiter.api.extension.RegisterExtension;
/** Tests for {@link google.registry.ui.server.console.ConsoleDomainGetAction}. */
public class ConsoleDomainGetActionTest {
private static final Gson GSON = RequestModule.provideGson();
private ConsoleApiParams consoleApiParams;
@RegisterExtension
@@ -124,6 +121,6 @@ public class ConsoleDomainGetActionTest {
private ConsoleDomainGetAction createAction(AuthResult authResult, String domain) {
consoleApiParams = ConsoleApiParamsUtils.createFake(authResult);
when(consoleApiParams.request().getMethod()).thenReturn(Action.Method.GET.toString());
return new ConsoleDomainGetAction(consoleApiParams, GSON, domain);
return new ConsoleDomainGetAction(consoleApiParams, domain);
}
}

View File

@@ -264,7 +264,6 @@ public class ConsoleDomainListActionTest {
when(consoleApiParams.request().getMethod()).thenReturn(Action.Method.GET.toString());
return new ConsoleDomainListAction(
consoleApiParams,
GSON,
registrarId,
Optional.ofNullable(checkpointTime),
Optional.ofNullable(pageNumber),

View File

@@ -21,7 +21,6 @@ import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableList;
import com.google.gson.Gson;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.User;
import google.registry.model.console.UserRoles;
@@ -32,7 +31,6 @@ import google.registry.testing.ConsoleApiParamsUtils;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeResponse;
import google.registry.tools.GsonUtils;
import java.io.IOException;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
@@ -41,8 +39,6 @@ import org.junit.jupiter.api.extension.RegisterExtension;
class ConsoleDumDownloadActionTest {
private static final Gson GSON = GsonUtils.provideGson();
private final FakeClock clock = new FakeClock(DateTime.parse("2024-04-15T00:00:00.000Z"));
private ConsoleApiParams consoleApiParams;

View File

@@ -17,6 +17,7 @@ package google.registry.ui.server.console;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.request.auth.AuthenticatedRegistrarAccessor.Role.OWNER;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
import static google.registry.testing.DatabaseHelper.loadSingleton;
import static google.registry.testing.DatabaseHelper.persistResource;
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
@@ -32,6 +33,7 @@ import com.google.common.collect.ImmutableSetMultimap;
import com.google.gson.Gson;
import google.registry.flows.PasswordOnlyTransportCredentials;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.RegistrarUpdateHistory;
import google.registry.model.console.User;
import google.registry.model.console.UserRoles;
import google.registry.model.registrar.Registrar;
@@ -41,6 +43,7 @@ import google.registry.request.RequestModule;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.testing.ConsoleApiParamsUtils;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.FakeResponse;
import google.registry.tools.GsonUtils;
import google.registry.ui.server.console.ConsoleEppPasswordAction.EppPasswordData;
@@ -139,6 +142,10 @@ class ConsoleEppPasswordActionTest {
() -> {
credentials.validate(loadRegistrar("TheRegistrar"), "randomPassword");
});
assertThat(loadSingleton(RegistrarUpdateHistory.class).get().getRequestBody())
.isEqualTo(
"{\"registrarId\":\"TheRegistrar\",\"oldPassword\":\"********\",\"newPassword\":"
+ "\"••••••••\",\"newPasswordRepeat\":\"••••••••\"}");
}
private ConsoleEppPasswordAction createAction(
@@ -150,6 +157,7 @@ class ConsoleEppPasswordActionTest {
.setEmailAddress("email@email.com")
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build())
.build();
DatabaseHelper.putInDb(user);
AuthResult authResult = AuthResult.createUser(user);
consoleApiParams = ConsoleApiParamsUtils.createFake(authResult);

View File

@@ -245,7 +245,6 @@ class ConsoleOteActionTest {
when(consoleApiParams.request().getMethod()).thenReturn(method.toString());
return new ConsoleOteAction(
consoleApiParams,
GSON,
iamClient,
registrarId,
maybeGroupEmailAddress,

View File

@@ -34,7 +34,6 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import google.registry.groups.GmailClient;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.RegistrarRole;
@@ -44,7 +43,6 @@ import google.registry.model.domain.Domain;
import google.registry.model.domain.RegistryLock;
import google.registry.model.eppcommon.StatusValue;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.request.RequestModule;
import google.registry.request.auth.AuthResult;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.ConsoleApiParamsUtils;
@@ -83,8 +81,6 @@ public class ConsoleRegistryLockActionTest {
https://registrarconsole.tld/console/#/registry-lock-verify?lockVerificationCode=\
123456789ABCDEFGHJKLMNPQRSTUVWXY""";
private static final Gson GSON = RequestModule.provideGson();
private final FakeClock fakeClock = new FakeClock(DateTime.parse("2024-04-18T12:00:00.000Z"));
@RegisterExtension
@@ -128,10 +124,10 @@ public class ConsoleRegistryLockActionTest {
assertThat(response.getPayload())
.isEqualTo(
"""
[{"domainName":"example.test","registrarPocId":"johndoe@theregistrar.com","lockRequestTime":\
{"creationTime":"2024-04-18T12:00:00.000Z"},"unlockRequestTime":"null","lockCompletionTime":\
"2024-04-18T12:00:00.000Z","unlockCompletionTime":"null","isSuperuser":false}]\
""");
[{"domainName":"example.test","registrarPocId":"johndoe@theregistrar.com","lockRequestTime":\
{"creationTime":"2024-04-18T12:00:00.000Z"},"unlockRequestTime":"null","lockCompletionTime":\
"2024-04-18T12:00:00.000Z","unlockCompletionTime":"null","isSuperuser":false}]\
""");
}
@Test
@@ -222,25 +218,25 @@ public class ConsoleRegistryLockActionTest {
assertThat(response.getPayload())
.isEqualTo(
"""
[{"domainName":"adminexample.test","lockRequestTime":{"creationTime":"2024-04-19T12:00:00.001Z"},\
"unlockRequestTime":"null","lockCompletionTime":"2024-04-19T12:00:00.001Z","unlockCompletionTime":\
"null","isSuperuser":true},\
\
{"domainName":"example.test","registrarPocId":"johndoe@theregistrar.com","lockRequestTime":\
{"creationTime":"2024-04-19T12:00:00.001Z"},"unlockRequestTime":"null","lockCompletionTime":\
"2024-04-19T12:00:00.000Z","unlockCompletionTime":"null","isSuperuser":false},\
\
{"domainName":"expiredunlock.test","registrarPocId":"johndoe@theregistrar.com","lockRequestTime":\
{"creationTime":"2024-04-18T12:00:00.000Z"},"unlockRequestTime":"2024-04-18T12:00:00.000Z",\
"lockCompletionTime":"2024-04-18T12:00:00.000Z","unlockCompletionTime":"null","isSuperuser":false},\
\
{"domainName":"incompleteunlock.test","registrarPocId":"johndoe@theregistrar.com","lockRequestTime":\
{"creationTime":"2024-04-19T12:00:00.001Z"},"unlockRequestTime":"2024-04-19T12:00:00.001Z",\
"lockCompletionTime":"2024-04-19T12:00:00.001Z","unlockCompletionTime":"null","isSuperuser":false},\
\
{"domainName":"pending.test","registrarPocId":"johndoe@theregistrar.com","lockRequestTime":\
{"creationTime":"2024-04-19T12:00:00.001Z"},"unlockRequestTime":"null","lockCompletionTime":"null",\
"unlockCompletionTime":"null","isSuperuser":false}]""");
[{"domainName":"adminexample.test","lockRequestTime":{"creationTime":"2024-04-19T12:00:00.001Z"},\
"unlockRequestTime":"null","lockCompletionTime":"2024-04-19T12:00:00.001Z","unlockCompletionTime":\
"null","isSuperuser":true},\
\
{"domainName":"example.test","registrarPocId":"johndoe@theregistrar.com","lockRequestTime":\
{"creationTime":"2024-04-19T12:00:00.001Z"},"unlockRequestTime":"null","lockCompletionTime":\
"2024-04-19T12:00:00.000Z","unlockCompletionTime":"null","isSuperuser":false},\
\
{"domainName":"expiredunlock.test","registrarPocId":"johndoe@theregistrar.com","lockRequestTime":\
{"creationTime":"2024-04-18T12:00:00.000Z"},"unlockRequestTime":"2024-04-18T12:00:00.000Z",\
"lockCompletionTime":"2024-04-18T12:00:00.000Z","unlockCompletionTime":"null","isSuperuser":false},\
\
{"domainName":"incompleteunlock.test","registrarPocId":"johndoe@theregistrar.com","lockRequestTime":\
{"creationTime":"2024-04-19T12:00:00.001Z"},"unlockRequestTime":"2024-04-19T12:00:00.001Z",\
"lockCompletionTime":"2024-04-19T12:00:00.001Z","unlockCompletionTime":"null","isSuperuser":false},\
\
{"domainName":"pending.test","registrarPocId":"johndoe@theregistrar.com","lockRequestTime":\
{"creationTime":"2024-04-19T12:00:00.001Z"},"unlockRequestTime":"null","lockCompletionTime":"null",\
"unlockCompletionTime":"null","isSuperuser":false}]""");
}
@Test
@@ -508,7 +504,7 @@ public class ConsoleRegistryLockActionTest {
new CloudTasksHelper(fakeClock).getTestCloudTasksUtils());
response = (FakeResponse) params.response();
return new ConsoleRegistryLockAction(
params, domainLockUtils, gmailClient, GSON, optionalPostInput, "TheRegistrar");
params, domainLockUtils, gmailClient, optionalPostInput, "TheRegistrar");
}
private ConsoleApiParams createParams() {

View File

@@ -24,7 +24,6 @@ import static google.registry.tools.LockOrUnlockDomainCommand.REGISTRY_LOCK_STAT
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User;
import google.registry.model.console.UserRoles;
@@ -32,7 +31,6 @@ import google.registry.model.domain.Domain;
import google.registry.model.domain.RegistryLock;
import google.registry.model.eppcommon.StatusValue;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.request.RequestModule;
import google.registry.request.auth.AuthResult;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.ConsoleApiParamsUtils;
@@ -51,7 +49,6 @@ import org.junit.jupiter.api.extension.RegisterExtension;
public class ConsoleRegistryLockVerifyActionTest {
private static final String DEFAULT_CODE = "123456789ABCDEFGHJKLMNPQRSTUUUUU";
private static final Gson GSON = RequestModule.provideGson();
private final FakeClock fakeClock = new FakeClock();
@RegisterExtension
@@ -214,6 +211,6 @@ public class ConsoleRegistryLockVerifyActionTest {
"adminreg",
new CloudTasksHelper(fakeClock).getTestCloudTasksUtils());
response = (FakeResponse) params.response();
return new ConsoleRegistryLockVerifyAction(params, domainLockUtils, GSON, verificationCode);
return new ConsoleRegistryLockVerifyAction(params, domainLockUtils, verificationCode);
}
}

View File

@@ -15,8 +15,10 @@
package google.registry.ui.server.console;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.model.registrar.RegistrarPocBase.Type.WHOIS;
import static google.registry.testing.DatabaseHelper.createTlds;
import static google.registry.testing.DatabaseHelper.loadSingleton;
import static google.registry.testing.DatabaseHelper.persistResource;
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
@@ -29,6 +31,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.gson.Gson;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.RegistrarUpdateHistory;
import google.registry.model.console.User;
import google.registry.model.console.UserRoles;
import google.registry.model.registrar.Registrar;
@@ -85,10 +88,11 @@ class ConsoleUpdateRegistrarActionTest {
.setRegistryLockAllowed(false)
.build());
user =
new User.Builder()
.setEmailAddress("user@registrarId.com")
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build())
.build();
persistResource(
new User.Builder()
.setEmailAddress("user@registrarId.com")
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build())
.build());
consoleApiParams = createParams();
}
@@ -104,6 +108,9 @@ class ConsoleUpdateRegistrarActionTest {
assertThat(newRegistrar.getAllowedTlds()).containsExactly("app", "dev");
assertThat(newRegistrar.isRegistryLockAllowed()).isFalse();
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_OK);
assertAboutImmutableObjects()
.that(newRegistrar)
.hasFieldsEqualTo(loadSingleton(RegistrarUpdateHistory.class).get().getRegistrar());
}
@Test

View File

@@ -16,6 +16,8 @@ package google.registry.ui.server.console;
import static com.google.common.truth.Truth.assertThat;
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static jakarta.servlet.http.HttpServletResponse.SC_CREATED;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
@@ -23,6 +25,7 @@ import static org.mockito.Mockito.when;
import com.google.api.services.directory.Directory;
import com.google.api.services.directory.Directory.Users;
import com.google.api.services.directory.Directory.Users.Delete;
import com.google.api.services.directory.Directory.Users.Insert;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -31,17 +34,23 @@ import google.registry.model.console.GlobalRole;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User;
import google.registry.model.console.UserRoles;
import google.registry.persistence.VKey;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.request.RequestModule;
import google.registry.request.auth.AuthResult;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.ConsoleApiParamsUtils;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.DeterministicStringGenerator;
import google.registry.testing.FakeResponse;
import google.registry.tools.IamClient;
import google.registry.ui.server.console.ConsoleUsersAction.UserData;
import google.registry.util.StringGenerator;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
@@ -53,6 +62,10 @@ class ConsoleUsersActionTest {
private final Directory directory = mock(Directory.class);
private final Users users = mock(Users.class);
private final Insert insert = mock(Insert.class);
private final Delete delete = mock(Delete.class);
private final IamClient iamClient = mock(IamClient.class);
private final CloudTasksHelper cloudTasksHelper = new CloudTasksHelper();
private StringGenerator passwordGenerator =
new DeterministicStringGenerator("abcdefghijklmnopqrstuvwxyz");
@@ -112,7 +125,10 @@ class ConsoleUsersActionTest {
AuthResult authResult = AuthResult.createUser(user);
ConsoleUsersAction action =
createAction(Optional.of(ConsoleApiParamsUtils.createFake(authResult)), Optional.of("GET"));
createAction(
Optional.of(ConsoleApiParamsUtils.createFake(authResult)),
Optional.of("GET"),
Optional.empty());
action.run();
var response = ((FakeResponse) consoleApiParams.response());
assertThat(response.getPayload())
@@ -134,7 +150,10 @@ class ConsoleUsersActionTest {
AuthResult authResult = AuthResult.createUser(user);
ConsoleUsersAction action =
createAction(Optional.of(ConsoleApiParamsUtils.createFake(authResult)), Optional.of("GET"));
createAction(
Optional.of(ConsoleApiParamsUtils.createFake(authResult)),
Optional.of("GET"),
Optional.empty());
action.run();
var response = ((FakeResponse) consoleApiParams.response());
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN);
@@ -146,43 +165,199 @@ class ConsoleUsersActionTest {
AuthResult authResult = AuthResult.createUser(user);
ConsoleUsersAction action =
createAction(
Optional.of(ConsoleApiParamsUtils.createFake(authResult)), Optional.of("POST"));
Optional.of(ConsoleApiParamsUtils.createFake(authResult)),
Optional.of("POST"),
Optional.empty());
action.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
when(directory.users()).thenReturn(users);
when(users.insert(any(com.google.api.services.directory.model.User.class))).thenReturn(insert);
action.run();
var response = ((FakeResponse) consoleApiParams.response());
assertThat(response.getStatus()).isEqualTo(SC_OK);
assertThat(response.getStatus()).isEqualTo(SC_CREATED);
assertThat(response.getPayload())
.contains(
"{\"password\":\"abcdefghijklmnop\",\"emailAddress\":\"email-1@email.com\",\"role\":\"ACCOUNT_MANAGER\"}");
"{\"emailAddress\":\"TheRegistrar-user1@email.com\",\"role\":\"ACCOUNT_MANAGER\",\"password\":\"abcdefghijklmnop\"}");
}
@Test
void testFailure_limitedTo3NewUsers() throws IOException {
void testFailure_noPermissionToDeleteUser() throws IOException {
User user1 = DatabaseHelper.loadByKey(VKey.create(User.class, "test1@test.com"));
AuthResult authResult =
AuthResult.createUser(
user1
.asBuilder()
.setUserRoles(user1.getUserRoles().asBuilder().setIsAdmin(true).build())
.build());
ConsoleUsersAction action =
createAction(
Optional.of(ConsoleApiParamsUtils.createFake(authResult)),
Optional.of("DELETE"),
Optional.of(
new UserData("test3@test.com", RegistrarRole.ACCOUNT_MANAGER.toString(), null)));
when(directory.users()).thenReturn(users);
when(users.delete(any(String.class))).thenReturn(delete);
action.run();
var response = ((FakeResponse) consoleApiParams.response());
assertThat(response.getStatus()).isEqualTo(SC_FORBIDDEN);
assertThat(response.getPayload())
.contains("Can't update user not associated with registrarId TheRegistrar");
}
@Test
void testFailure_userDoesntExist() throws IOException {
User user = DatabaseHelper.createAdminUser("email@email.com");
DatabaseHelper.createAdminUser("email-1@email.com");
DatabaseHelper.createAdminUser("email-2@email.com");
DatabaseHelper.createAdminUser("email-3@email.com");
AuthResult authResult = AuthResult.createUser(user);
ConsoleUsersAction action =
createAction(
Optional.of(ConsoleApiParamsUtils.createFake(authResult)), Optional.of("POST"));
Optional.of(ConsoleApiParamsUtils.createFake(authResult)),
Optional.of("DELETE"),
Optional.of(
new UserData("email-1@email.com", RegistrarRole.ACCOUNT_MANAGER.toString(), null)));
when(directory.users()).thenReturn(users);
when(users.delete(any(String.class))).thenReturn(delete);
action.run();
var response = ((FakeResponse) consoleApiParams.response());
assertThat(response.getStatus()).isEqualTo(SC_BAD_REQUEST);
assertThat(response.getPayload()).contains("User email-1@email.com doesn't exist");
}
@Test
void testSuccess_deletesUser() throws IOException {
User user1 = DatabaseHelper.loadByKey(VKey.create(User.class, "test1@test.com"));
AuthResult authResult =
AuthResult.createUser(
user1
.asBuilder()
.setUserRoles(user1.getUserRoles().asBuilder().setIsAdmin(true).build())
.build());
ConsoleUsersAction action =
createAction(
Optional.of(ConsoleApiParamsUtils.createFake(authResult)),
Optional.of("DELETE"),
Optional.of(
new UserData("test2@test.com", RegistrarRole.ACCOUNT_MANAGER.toString(), null)));
action.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
when(directory.users()).thenReturn(users);
when(users.delete(any(String.class))).thenReturn(delete);
action.run();
var response = ((FakeResponse) consoleApiParams.response());
assertThat(response.getStatus()).isEqualTo(SC_OK);
assertThat(DatabaseHelper.loadByKeyIfPresent(VKey.create(User.class, "test2@test.com")))
.isEmpty();
}
@Test
void testFailure_limitedTo4UsersPerRegistrar() throws IOException {
User user1 = DatabaseHelper.loadByKey(VKey.create(User.class, "test1@test.com"));
AuthResult authResult =
AuthResult.createUser(
user1
.asBuilder()
.setUserRoles(user1.getUserRoles().asBuilder().setIsAdmin(true).build())
.build());
DatabaseHelper.persistResources(
IntStream.range(3, 5)
.mapToObj(
i ->
new User.Builder()
.setEmailAddress(String.format("test%s@test.com", i))
.setUserRoles(
new UserRoles()
.asBuilder()
.setRegistrarRoles(
ImmutableMap.of("TheRegistrar", RegistrarRole.PRIMARY_CONTACT))
.build())
.build())
.collect(Collectors.toList()));
ConsoleUsersAction action =
createAction(
Optional.of(ConsoleApiParamsUtils.createFake(authResult)),
Optional.of("POST"),
Optional.empty());
action.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
when(directory.users()).thenReturn(users);
when(users.insert(any(com.google.api.services.directory.model.User.class))).thenReturn(insert);
action.run();
var response = ((FakeResponse) consoleApiParams.response());
assertThat(response.getStatus()).isEqualTo(SC_BAD_REQUEST);
assertThat(response.getPayload()).contains("Extra users amount is limited to 3");
assertThat(response.getPayload()).contains("Total users amount per registrar is limited to 4");
}
@Test
void testSuccess_updatesUserRole() throws IOException {
User user1 = DatabaseHelper.loadByKey(VKey.create(User.class, "test1@test.com"));
AuthResult authResult =
AuthResult.createUser(
user1
.asBuilder()
.setUserRoles(user1.getUserRoles().asBuilder().setIsAdmin(true).build())
.build());
assertThat(
DatabaseHelper.loadByKey(VKey.create(User.class, "test2@test.com"))
.getUserRoles()
.getRegistrarRoles()
.get("TheRegistrar"))
.isEqualTo(RegistrarRole.PRIMARY_CONTACT);
ConsoleUsersAction action =
createAction(
Optional.of(ConsoleApiParamsUtils.createFake(authResult)),
Optional.of("PUT"),
Optional.of(
new UserData("test2@test.com", RegistrarRole.ACCOUNT_MANAGER.toString(), null)));
action.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
action.run();
var response = ((FakeResponse) consoleApiParams.response());
assertThat(response.getStatus()).isEqualTo(SC_OK);
assertThat(
DatabaseHelper.loadByKey(VKey.create(User.class, "test2@test.com"))
.getUserRoles()
.getRegistrarRoles()
.get("TheRegistrar"))
.isEqualTo(RegistrarRole.ACCOUNT_MANAGER);
}
@Test
void testFailure_noPermissionToUpdateUser() throws IOException {
User user1 = DatabaseHelper.loadByKey(VKey.create(User.class, "test1@test.com"));
AuthResult authResult =
AuthResult.createUser(
user1
.asBuilder()
.setUserRoles(user1.getUserRoles().asBuilder().setIsAdmin(true).build())
.build());
ConsoleUsersAction action =
createAction(
Optional.of(ConsoleApiParamsUtils.createFake(authResult)),
Optional.of("PUT"),
Optional.of(
new UserData("test3@test.com", RegistrarRole.ACCOUNT_MANAGER.toString(), null)));
action.run();
var response = ((FakeResponse) consoleApiParams.response());
assertThat(response.getStatus()).isEqualTo(SC_FORBIDDEN);
assertThat(response.getPayload())
.contains("Can't update user not associated with registrarId TheRegistrar");
}
private ConsoleUsersAction createAction(
Optional<ConsoleApiParams> maybeConsoleApiParams, Optional<String> method)
Optional<ConsoleApiParams> maybeConsoleApiParams,
Optional<String> method,
Optional<UserData> userData)
throws IOException {
consoleApiParams =
maybeConsoleApiParams.orElseGet(
() -> ConsoleApiParamsUtils.createFake(AuthResult.NOT_AUTHENTICATED));
when(consoleApiParams.request().getMethod()).thenReturn(method.orElse("GET"));
return new ConsoleUsersAction(
consoleApiParams, GSON, directory, passwordGenerator, "TheRegistrar");
consoleApiParams,
directory,
iamClient,
"email.com",
Optional.of("someRandomString"),
passwordGenerator,
userData,
"TheRegistrar");
}
}

View File

@@ -15,8 +15,10 @@
package google.registry.ui.server.console;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.testing.DatabaseHelper.loadAllOf;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
import static google.registry.testing.DatabaseHelper.loadSingleton;
import static google.registry.testing.DatabaseHelper.persistNewRegistrar;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.SqlHelper.saveRegistrar;
@@ -30,6 +32,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.RegistrarUpdateHistory;
import google.registry.model.console.User;
import google.registry.model.console.UserRoles;
import google.registry.model.registrar.Registrar;
@@ -183,6 +186,9 @@ class RegistrarsActionTest {
.findAny()
.isPresent())
.isTrue();
assertAboutImmutableObjects()
.that(r)
.isEqualExceptFields(loadSingleton(RegistrarUpdateHistory.class).get().getRegistrar());
}
@Test
@@ -225,10 +231,8 @@ class RegistrarsActionTest {
}
private User createUser(UserRoles userRoles) {
return new User.Builder()
.setEmailAddress("email@email.com")
.setUserRoles(userRoles)
.build();
return persistResource(
new User.Builder().setEmailAddress("email@email.com").setUserRoles(userRoles).build());
}
private RegistrarsAction createAction(Action.Method method, AuthResult authResult) {
@@ -236,7 +240,7 @@ class RegistrarsActionTest {
when(consoleApiParams.request().getMethod()).thenReturn(method.toString());
if (method.equals(Action.Method.GET)) {
return new RegistrarsAction(
consoleApiParams, GSON, Optional.ofNullable(null), passwordGenerator, passcodeGenerator);
consoleApiParams, Optional.ofNullable(null), passwordGenerator, passcodeGenerator);
} else {
try {
doReturn(new BufferedReader(new StringReader(registrarParamMap.toString())))
@@ -245,7 +249,6 @@ class RegistrarsActionTest {
} catch (IOException e) {
return new RegistrarsAction(
consoleApiParams,
GSON,
Optional.ofNullable(null),
passwordGenerator,
passcodeGenerator);
@@ -254,7 +257,7 @@ class RegistrarsActionTest {
ConsoleModule.provideRegistrar(
GSON, RequestModule.provideJsonBody(consoleApiParams.request(), GSON));
return new RegistrarsAction(
consoleApiParams, GSON, maybeRegistrar, passwordGenerator, passcodeGenerator);
consoleApiParams, maybeRegistrar, passwordGenerator, passcodeGenerator);
}
}
}

View File

@@ -480,10 +480,10 @@ class ContactActionTest {
consoleApiParams = ConsoleApiParamsUtils.createFake(authResult);
when(consoleApiParams.request().getMethod()).thenReturn(method.toString());
if (method.equals(Action.Method.GET)) {
return new ContactAction(consoleApiParams, GSON, registrarId, Optional.empty());
return new ContactAction(consoleApiParams, registrarId, Optional.empty());
} else {
return new ContactAction(
consoleApiParams, GSON, registrarId, Optional.of(ImmutableSet.copyOf(contacts)));
consoleApiParams, registrarId, Optional.of(ImmutableSet.copyOf(contacts)));
}
}
}

View File

@@ -15,8 +15,10 @@
package google.registry.ui.server.console.settings;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.testing.CertificateSamples.SAMPLE_CERT2;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
import static google.registry.testing.DatabaseHelper.loadSingleton;
import static google.registry.testing.SqlHelper.saveRegistrar;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
@@ -28,6 +30,7 @@ import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.ImmutableSortedMap;
import com.google.gson.Gson;
import google.registry.flows.certs.CertificateChecker;
import google.registry.model.console.RegistrarUpdateHistory;
import google.registry.model.registrar.Registrar;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.request.Action;
@@ -98,6 +101,9 @@ class SecurityActionTest {
.isEqualTo("GNd6ZP8/n91t9UTnpxR8aH7aAW4+CpvufYx9ViGbcMY");
assertThat(r.getIpAddressAllowList().get(0).getIp()).isEqualTo("192.168.1.1");
assertThat(r.getIpAddressAllowList().get(0).getNetmask()).isEqualTo(32);
assertAboutImmutableObjects()
.that(loadSingleton(RegistrarUpdateHistory.class).get().getRegistrar())
.hasFieldsEqualTo(r);
}
private SecurityAction createAction(AuthResult authResult, String registrarId)

View File

@@ -16,6 +16,7 @@ package google.registry.ui.server.console.settings;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.testing.DatabaseHelper.loadSingleton;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import static org.mockito.Mockito.doReturn;
@@ -27,6 +28,7 @@ import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.RegistrarUpdateHistory;
import google.registry.model.console.User;
import google.registry.model.console.UserRoles;
import google.registry.model.registrar.Registrar;
@@ -129,6 +131,9 @@ public class WhoisRegistrarFieldsActionTest {
.that(newRegistrar)
.isEqualExceptFields(
oldRegistrar, "whoisServer", "url", "localizedAddress", "phoneNumber", "faxNumber");
assertAboutImmutableObjects()
.that(loadSingleton(RegistrarUpdateHistory.class).get().getRegistrar())
.hasFieldsEqualTo(newRegistrar);
}
@Test

View File

@@ -90,19 +90,20 @@ public class ConsoleScreenshotTest extends WebDriverTestCase {
driver.diffPage("actionsButtonClicked");
}
@RetryingTest(3)
void settingsPage() throws Exception {
clickSidebarElementByName("Settings");
driver.diffPage("noRegistrarSelected");
selectRegistrar();
driver.diffPage("registrarSelected_contacts");
driver.findElement(By.cssSelector("a[routerLink=\"whois\"]")).click();
Thread.sleep(500);
driver.diffPage("registrarSelected_whois");
driver.findElement(By.cssSelector("a[routerLink=\"security\"]")).click();
Thread.sleep(500);
driver.diffPage("registrarSelected_security");
}
// TODO: Reenable failing test after kokoro issue is resolved
// @RetryingTest(3)
// void settingsPage() throws Exception {
// clickSidebarElementByName("Settings");
// driver.diffPage("noRegistrarSelected");
// selectRegistrar();
// driver.diffPage("registrarSelected_contacts");
// driver.findElement(By.cssSelector("a[routerLink=\"whois\"]")).click();
// Thread.sleep(500);
// driver.diffPage("registrarSelected_whois");
// driver.findElement(By.cssSelector("a[routerLink=\"security\"]")).click();
// Thread.sleep(500);
// driver.diffPage("registrarSelected_security");
// }
@RetryingTest(3)
void billingInfo() throws Exception {

View File

@@ -1,16 +1,16 @@
SERVICE PATH CLASS METHODS OK MIN USER_POLICY
FRONTEND /_dr/epp EppTlsAction POST n APP ADMIN
CONSOLE /console-api/domain ConsoleDomainGetAction GET n USER PUBLIC
CONSOLE /console-api/domain-list ConsoleDomainListAction GET n USER PUBLIC
CONSOLE /console-api/dum-download ConsoleDumDownloadAction GET n USER PUBLIC
CONSOLE /console-api/eppPassword ConsoleEppPasswordAction POST n USER PUBLIC
CONSOLE /console-api/ote ConsoleOteAction GET,POST n USER PUBLIC
CONSOLE /console-api/registrar ConsoleUpdateRegistrarAction POST n USER PUBLIC
CONSOLE /console-api/registrars RegistrarsAction GET,POST n USER PUBLIC
CONSOLE /console-api/registry-lock ConsoleRegistryLockAction GET,POST n USER PUBLIC
CONSOLE /console-api/registry-lock-verify ConsoleRegistryLockVerifyAction GET n USER PUBLIC
CONSOLE /console-api/settings/contacts ContactAction GET,POST n USER PUBLIC
CONSOLE /console-api/settings/security SecurityAction POST n USER PUBLIC
CONSOLE /console-api/settings/whois-fields WhoisRegistrarFieldsAction POST n USER PUBLIC
CONSOLE /console-api/userdata ConsoleUserDataAction GET n USER PUBLIC
CONSOLE /console-api/users ConsoleUsersAction GET,POST n USER PUBLIC
SERVICE PATH CLASS METHODS OK MIN USER_POLICY
FRONTEND /_dr/epp EppTlsAction POST n APP ADMIN
CONSOLE /console-api/domain ConsoleDomainGetAction GET n USER PUBLIC
CONSOLE /console-api/domain-list ConsoleDomainListAction GET n USER PUBLIC
CONSOLE /console-api/dum-download ConsoleDumDownloadAction GET n USER PUBLIC
CONSOLE /console-api/eppPassword ConsoleEppPasswordAction POST n USER PUBLIC
CONSOLE /console-api/ote ConsoleOteAction GET,POST n USER PUBLIC
CONSOLE /console-api/registrar ConsoleUpdateRegistrarAction POST n USER PUBLIC
CONSOLE /console-api/registrars RegistrarsAction GET,POST n USER PUBLIC
CONSOLE /console-api/registry-lock ConsoleRegistryLockAction GET,POST n USER PUBLIC
CONSOLE /console-api/registry-lock-verify ConsoleRegistryLockVerifyAction GET n USER PUBLIC
CONSOLE /console-api/settings/contacts ContactAction GET,POST n USER PUBLIC
CONSOLE /console-api/settings/security SecurityAction POST n USER PUBLIC
CONSOLE /console-api/settings/whois-fields WhoisRegistrarFieldsAction POST n USER PUBLIC
CONSOLE /console-api/userdata ConsoleUserDataAction GET n USER PUBLIC
CONSOLE /console-api/users ConsoleUsersAction GET,POST,DELETE,PUT n USER PUBLIC

View File

@@ -1,82 +1,82 @@
SERVICE PATH CLASS METHODS OK MIN USER_POLICY
FRONTEND /_dr/epp EppTlsAction POST n APP ADMIN
BACKEND /_dr/admin/createGroups CreateGroupsAction POST n APP ADMIN
BACKEND /_dr/admin/list/domains ListDomainsAction GET,POST n APP ADMIN
BACKEND /_dr/admin/list/hosts ListHostsAction GET,POST n APP ADMIN
BACKEND /_dr/admin/list/premiumLists ListPremiumListsAction GET,POST n APP ADMIN
BACKEND /_dr/admin/list/registrars ListRegistrarsAction GET,POST n APP ADMIN
BACKEND /_dr/admin/list/reservedLists ListReservedListsAction GET,POST n APP ADMIN
BACKEND /_dr/admin/list/tlds ListTldsAction GET,POST n APP ADMIN
BACKEND /_dr/admin/updateUserGroup UpdateUserGroupAction POST n APP ADMIN
BACKEND /_dr/admin/verifyOte VerifyOteAction POST n APP ADMIN
BACKEND /_dr/cron/fanout TldFanoutAction GET y APP ADMIN
BACKEND /_dr/epptool EppToolAction POST n APP ADMIN
BACKEND /_dr/loadtest LoadTestAction POST y APP ADMIN
BACKEND /_dr/task/brdaCopy BrdaCopyAction POST y APP ADMIN
BACKEND /_dr/task/bsaDownload BsaDownloadAction GET,POST n APP ADMIN
BACKEND /_dr/task/bsaRefresh BsaRefreshAction GET,POST n APP ADMIN
BACKEND /_dr/task/bsaValidate BsaValidateAction GET,POST n APP ADMIN
BACKEND /_dr/task/copyDetailReports CopyDetailReportsAction POST n APP ADMIN
BACKEND /_dr/task/deleteExpiredDomains DeleteExpiredDomainsAction GET n APP ADMIN
BACKEND /_dr/task/deleteLoadTestData DeleteLoadTestDataAction POST n APP ADMIN
BACKEND /_dr/task/deleteProberData DeleteProberDataAction POST n APP ADMIN
BACKEND /_dr/task/dnsRefresh RefreshDnsAction GET y APP ADMIN
BACKEND /_dr/task/executeCannedScript CannedScriptExecutionAction POST,GET y APP ADMIN
BACKEND /_dr/task/expandBillingRecurrences ExpandBillingRecurrencesAction GET n APP ADMIN
BACKEND /_dr/task/exportDomainLists ExportDomainListsAction POST n APP ADMIN
BACKEND /_dr/task/exportPremiumTerms ExportPremiumTermsAction POST n APP ADMIN
BACKEND /_dr/task/exportReservedTerms ExportReservedTermsAction POST n APP ADMIN
BACKEND /_dr/task/generateInvoices GenerateInvoicesAction POST n APP ADMIN
BACKEND /_dr/task/generateSpec11 GenerateSpec11ReportAction POST n APP ADMIN
BACKEND /_dr/task/generateZoneFiles GenerateZoneFilesAction POST n APP ADMIN
BACKEND /_dr/task/icannReportingStaging IcannReportingStagingAction POST n APP ADMIN
BACKEND /_dr/task/icannReportingUpload IcannReportingUploadAction POST n APP ADMIN
BACKEND /_dr/task/nordnUpload NordnUploadAction POST y APP ADMIN
BACKEND /_dr/task/nordnVerify NordnVerifyAction POST y APP ADMIN
BACKEND /_dr/task/publishDnsUpdates PublishDnsUpdatesAction POST y APP ADMIN
BACKEND /_dr/task/publishInvoices PublishInvoicesAction POST n APP ADMIN
BACKEND /_dr/task/publishSpec11 PublishSpec11ReportAction POST n APP ADMIN
BACKEND /_dr/task/rdeReport RdeReportAction POST n APP ADMIN
BACKEND /_dr/task/rdeStaging RdeStagingAction GET,POST n APP ADMIN
BACKEND /_dr/task/rdeUpload RdeUploadAction POST n APP ADMIN
BACKEND /_dr/task/readDnsRefreshRequests ReadDnsRefreshRequestsAction POST y APP ADMIN
BACKEND /_dr/task/refreshDnsForAllDomains RefreshDnsForAllDomainsAction GET n APP ADMIN
BACKEND /_dr/task/refreshDnsOnHostRename RefreshDnsOnHostRenameAction POST n APP ADMIN
BACKEND /_dr/task/relockDomain RelockDomainAction POST y APP ADMIN
BACKEND /_dr/task/resaveAllEppResourcesPipeline ResaveAllEppResourcesPipelineAction GET n APP ADMIN
BACKEND /_dr/task/resaveEntity ResaveEntityAction POST n APP ADMIN
BACKEND /_dr/task/sendExpiringCertificateNotificationEmail SendExpiringCertificateNotificationEmailAction GET n APP ADMIN
BACKEND /_dr/task/syncGroupMembers SyncGroupMembersAction POST n APP ADMIN
BACKEND /_dr/task/syncRegistrarsSheet SyncRegistrarsSheetAction POST n APP ADMIN
BACKEND /_dr/task/tmchCrl TmchCrlAction POST y APP ADMIN
BACKEND /_dr/task/tmchDnl TmchDnlAction POST y APP ADMIN
BACKEND /_dr/task/tmchSmdrl TmchSmdrlAction POST y APP ADMIN
BACKEND /_dr/task/updateRegistrarRdapBaseUrls UpdateRegistrarRdapBaseUrlsAction GET y APP ADMIN
BACKEND /_dr/task/uploadBsaUnavailableNames UploadBsaUnavailableDomainsAction GET,POST n APP ADMIN
BACKEND /_dr/task/wipeOutContactHistoryPii WipeOutContactHistoryPiiAction GET n APP ADMIN
PUBAPI /_dr/whois WhoisAction POST n APP ADMIN
PUBAPI /check CheckApiAction GET n NONE PUBLIC
PUBAPI /rdap/autnum/(*) RdapAutnumAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/domain/(*) RdapDomainAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/domains RdapDomainSearchAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/entities RdapEntitySearchAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/entity/(*) RdapEntityAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/help(*) RdapHelpAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/ip/(*) RdapIpAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/nameserver/(*) RdapNameserverAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/nameservers RdapNameserverSearchAction GET,HEAD n NONE PUBLIC
PUBAPI /whois/(*) WhoisHttpAction GET n NONE PUBLIC
CONSOLE /console-api/domain ConsoleDomainGetAction GET n USER PUBLIC
CONSOLE /console-api/domain-list ConsoleDomainListAction GET n USER PUBLIC
CONSOLE /console-api/dum-download ConsoleDumDownloadAction GET n USER PUBLIC
CONSOLE /console-api/eppPassword ConsoleEppPasswordAction POST n USER PUBLIC
CONSOLE /console-api/ote ConsoleOteAction GET,POST n USER PUBLIC
CONSOLE /console-api/registrar ConsoleUpdateRegistrarAction POST n USER PUBLIC
CONSOLE /console-api/registrars RegistrarsAction GET,POST n USER PUBLIC
CONSOLE /console-api/registry-lock ConsoleRegistryLockAction GET,POST n USER PUBLIC
CONSOLE /console-api/registry-lock-verify ConsoleRegistryLockVerifyAction GET n USER PUBLIC
CONSOLE /console-api/settings/contacts ContactAction GET,POST n USER PUBLIC
CONSOLE /console-api/settings/security SecurityAction POST n USER PUBLIC
CONSOLE /console-api/settings/whois-fields WhoisRegistrarFieldsAction POST n USER PUBLIC
CONSOLE /console-api/userdata ConsoleUserDataAction GET n USER PUBLIC
CONSOLE /console-api/users ConsoleUsersAction GET,POST n USER PUBLIC
SERVICE PATH CLASS METHODS OK MIN USER_POLICY
FRONTEND /_dr/epp EppTlsAction POST n APP ADMIN
BACKEND /_dr/admin/createGroups CreateGroupsAction POST n APP ADMIN
BACKEND /_dr/admin/list/domains ListDomainsAction GET,POST n APP ADMIN
BACKEND /_dr/admin/list/hosts ListHostsAction GET,POST n APP ADMIN
BACKEND /_dr/admin/list/premiumLists ListPremiumListsAction GET,POST n APP ADMIN
BACKEND /_dr/admin/list/registrars ListRegistrarsAction GET,POST n APP ADMIN
BACKEND /_dr/admin/list/reservedLists ListReservedListsAction GET,POST n APP ADMIN
BACKEND /_dr/admin/list/tlds ListTldsAction GET,POST n APP ADMIN
BACKEND /_dr/admin/updateUserGroup UpdateUserGroupAction POST n APP ADMIN
BACKEND /_dr/admin/verifyOte VerifyOteAction POST n APP ADMIN
BACKEND /_dr/cron/fanout TldFanoutAction GET y APP ADMIN
BACKEND /_dr/epptool EppToolAction POST n APP ADMIN
BACKEND /_dr/loadtest LoadTestAction POST y APP ADMIN
BACKEND /_dr/task/brdaCopy BrdaCopyAction POST y APP ADMIN
BACKEND /_dr/task/bsaDownload BsaDownloadAction GET,POST n APP ADMIN
BACKEND /_dr/task/bsaRefresh BsaRefreshAction GET,POST n APP ADMIN
BACKEND /_dr/task/bsaValidate BsaValidateAction GET,POST n APP ADMIN
BACKEND /_dr/task/copyDetailReports CopyDetailReportsAction POST n APP ADMIN
BACKEND /_dr/task/deleteExpiredDomains DeleteExpiredDomainsAction GET n APP ADMIN
BACKEND /_dr/task/deleteLoadTestData DeleteLoadTestDataAction POST n APP ADMIN
BACKEND /_dr/task/deleteProberData DeleteProberDataAction POST n APP ADMIN
BACKEND /_dr/task/dnsRefresh RefreshDnsAction GET y APP ADMIN
BACKEND /_dr/task/executeCannedScript CannedScriptExecutionAction POST,GET y APP ADMIN
BACKEND /_dr/task/expandBillingRecurrences ExpandBillingRecurrencesAction GET n APP ADMIN
BACKEND /_dr/task/exportDomainLists ExportDomainListsAction POST n APP ADMIN
BACKEND /_dr/task/exportPremiumTerms ExportPremiumTermsAction POST n APP ADMIN
BACKEND /_dr/task/exportReservedTerms ExportReservedTermsAction POST n APP ADMIN
BACKEND /_dr/task/generateInvoices GenerateInvoicesAction POST n APP ADMIN
BACKEND /_dr/task/generateSpec11 GenerateSpec11ReportAction POST n APP ADMIN
BACKEND /_dr/task/generateZoneFiles GenerateZoneFilesAction POST n APP ADMIN
BACKEND /_dr/task/icannReportingStaging IcannReportingStagingAction POST n APP ADMIN
BACKEND /_dr/task/icannReportingUpload IcannReportingUploadAction POST n APP ADMIN
BACKEND /_dr/task/nordnUpload NordnUploadAction POST y APP ADMIN
BACKEND /_dr/task/nordnVerify NordnVerifyAction POST y APP ADMIN
BACKEND /_dr/task/publishDnsUpdates PublishDnsUpdatesAction POST y APP ADMIN
BACKEND /_dr/task/publishInvoices PublishInvoicesAction POST n APP ADMIN
BACKEND /_dr/task/publishSpec11 PublishSpec11ReportAction POST n APP ADMIN
BACKEND /_dr/task/rdeReport RdeReportAction POST n APP ADMIN
BACKEND /_dr/task/rdeStaging RdeStagingAction GET,POST n APP ADMIN
BACKEND /_dr/task/rdeUpload RdeUploadAction POST n APP ADMIN
BACKEND /_dr/task/readDnsRefreshRequests ReadDnsRefreshRequestsAction POST y APP ADMIN
BACKEND /_dr/task/refreshDnsForAllDomains RefreshDnsForAllDomainsAction GET n APP ADMIN
BACKEND /_dr/task/refreshDnsOnHostRename RefreshDnsOnHostRenameAction POST n APP ADMIN
BACKEND /_dr/task/relockDomain RelockDomainAction POST y APP ADMIN
BACKEND /_dr/task/resaveAllEppResourcesPipeline ResaveAllEppResourcesPipelineAction GET n APP ADMIN
BACKEND /_dr/task/resaveEntity ResaveEntityAction POST n APP ADMIN
BACKEND /_dr/task/sendExpiringCertificateNotificationEmail SendExpiringCertificateNotificationEmailAction GET n APP ADMIN
BACKEND /_dr/task/syncGroupMembers SyncGroupMembersAction POST n APP ADMIN
BACKEND /_dr/task/syncRegistrarsSheet SyncRegistrarsSheetAction POST n APP ADMIN
BACKEND /_dr/task/tmchCrl TmchCrlAction POST y APP ADMIN
BACKEND /_dr/task/tmchDnl TmchDnlAction POST y APP ADMIN
BACKEND /_dr/task/tmchSmdrl TmchSmdrlAction POST y APP ADMIN
BACKEND /_dr/task/updateRegistrarRdapBaseUrls UpdateRegistrarRdapBaseUrlsAction GET y APP ADMIN
BACKEND /_dr/task/uploadBsaUnavailableNames UploadBsaUnavailableDomainsAction GET,POST n APP ADMIN
BACKEND /_dr/task/wipeOutContactHistoryPii WipeOutContactHistoryPiiAction GET n APP ADMIN
PUBAPI /_dr/whois WhoisAction POST n APP ADMIN
PUBAPI /check CheckApiAction GET n NONE PUBLIC
PUBAPI /rdap/autnum/(*) RdapAutnumAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/domain/(*) RdapDomainAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/domains RdapDomainSearchAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/entities RdapEntitySearchAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/entity/(*) RdapEntityAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/help(*) RdapHelpAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/ip/(*) RdapIpAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/nameserver/(*) RdapNameserverAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/nameservers RdapNameserverSearchAction GET,HEAD n NONE PUBLIC
PUBAPI /whois/(*) WhoisHttpAction GET n NONE PUBLIC
CONSOLE /console-api/domain ConsoleDomainGetAction GET n USER PUBLIC
CONSOLE /console-api/domain-list ConsoleDomainListAction GET n USER PUBLIC
CONSOLE /console-api/dum-download ConsoleDumDownloadAction GET n USER PUBLIC
CONSOLE /console-api/eppPassword ConsoleEppPasswordAction POST n USER PUBLIC
CONSOLE /console-api/ote ConsoleOteAction GET,POST n USER PUBLIC
CONSOLE /console-api/registrar ConsoleUpdateRegistrarAction POST n USER PUBLIC
CONSOLE /console-api/registrars RegistrarsAction GET,POST n USER PUBLIC
CONSOLE /console-api/registry-lock ConsoleRegistryLockAction GET,POST n USER PUBLIC
CONSOLE /console-api/registry-lock-verify ConsoleRegistryLockVerifyAction GET n USER PUBLIC
CONSOLE /console-api/settings/contacts ContactAction GET,POST n USER PUBLIC
CONSOLE /console-api/settings/security SecurityAction POST n USER PUBLIC
CONSOLE /console-api/settings/whois-fields WhoisRegistrarFieldsAction POST n USER PUBLIC
CONSOLE /console-api/userdata ConsoleUserDataAction GET n USER PUBLIC
CONSOLE /console-api/users ConsoleUsersAction GET,POST,DELETE,PUT n USER PUBLIC

View File

@@ -56,7 +56,7 @@
</rdeHeader:count>
</rdeHeader:header>
<!-- Domian: example1.test -->
<!-- Domain: example1.test -->
<rdeDom:domain>
<rdeDom:name>example1.test</rdeDom:name>
<rdeDom:roid>Dexample1-TEST</rdeDom:roid>
@@ -74,7 +74,7 @@
<rdeDom:exDate>2015-04-03T22:00:00.0Z</rdeDom:exDate>
</rdeDom:domain>
<!-- Domian: example2.test -->
<!-- Domain: example2.test -->
<rdeDom:domain>
<rdeDom:name>example2.test</rdeDom:name>
<rdeDom:roid>Dexample2-TEST</rdeDom:roid>

View File

@@ -56,7 +56,7 @@
</rdeHeader:count>
</rdeHeader:header>
<!-- Domian: example1.test -->
<!-- Domain: example1.test -->
<rdeDom:domain>
<rdeDom:name>example1.test</rdeDom:name>
<rdeDom:roid>Dexample1-TEST</rdeDom:roid>
@@ -74,7 +74,7 @@
<rdeDom:exDate>2015-04-03T22:00:00.0Z</rdeDom:exDate>
</rdeDom:domain>
<!-- Domian: example2.test -->
<!-- Domain: example2.test -->
<rdeDom:domain>
<rdeDom:name>example2.test</rdeDom:name>
<rdeDom:roid>Dexample2-TEST</rdeDom:roid>

View File

@@ -470,7 +470,7 @@
);
create table "FeatureFlag" (
feature_name text not null check (feature_name in ('TEST_FEATURE','MINIMUM_DATASET_CONTACTS_OPTIONAL','MINIMUM_DATASET_CONTACTS_PROHIBITED')),
feature_name text not null check (feature_name in ('TEST_FEATURE','MINIMUM_DATASET_CONTACTS_OPTIONAL','MINIMUM_DATASET_CONTACTS_PROHIBITED','INCLUDE_PENDING_DELETE_DATE_FOR_DOMAINS')),
status hstore not null,
primary key (feature_name)
);