1
0
mirror of https://github.com/google/nomulus synced 2026-01-16 02:33:16 +00:00

Compare commits

...

8 Commits

Author SHA1 Message Date
Harshita Sharma
2a67b04f3a testing 2025-07-29 20:42:42 +00:00
gbrodman
9f191e9392 Add Registry Lock password reset on front end (#2785)
This is only enabled for admins, for now at least. It sends an email to
the registry lock email address to reset it.
2025-07-28 20:23:39 +00:00
gbrodman
39c2a79898 Remove superfluous DatabaseHelper db methods (#2784)
Some of these have been around since the Datastore days and are no
longer relevant (dealing with things like Datastore foreign keys). Let's
simplify things.
2025-07-25 17:00:24 +00:00
Pavlo Tkach
e2e9d4cfc7 Add console history api (#2782) 2025-07-18 18:46:21 +00:00
gbrodman
2948dcc1be Add password reset request and verify console actions (#2775)
This works fairly similarly to the registry lock request and
verification mechanism. The request action generates a UUI which is
emailed (in link form) to the user in question. The frontend will send a
request to the verify action with the UUID and hopefully the action
should be finalized.

EPP password requests can be sent by anyone with edit-registrar
permissions and must be approved by an admin POC email.

Registry lock password resets can only be sent by primary contacts, and
are verified/performed by the user in question.
2025-07-17 21:33:29 +00:00
Pavlo Tkach
c5644d5c8b Add stream to the console dum download (#2783) 2025-07-16 18:56:20 +00:00
Ben McIlwain
514d24ed67 Implement the contacts prohibited feature flag for minimum data set (#2781)
This prohibits all contact data on create and update EPP flows for both domain
and contact flows. It also refactors how default values on FeatureFlags work, as
it's safer to specify a single default on the flag itself rather than have to
specify it independently at a number of callsites (and potentially end up having
an inconsistent value). Domain updates on existing domains that still have
contact data will fail unless all contact data is removed, as a forcing function
to require registrars to rectify the situation prior to being able to do any
other kind of domain changes.

Contact-related flows that are still allowed after this point: Updating a domain
to remove all contacts from it, and deleting a contact object.
2025-07-14 15:29:14 +00:00
gbrodman
c6868b771b Update RDAP response profile + tech impl guide versions (#2778)
This corresponds to the Feb 2024 response profile section 1.2 and
implementation guide 1.3 respectively, now that we comply (or are, at
least closer to complying), with the Feb 2024 versions.

This should probably depend on https://github.com/google/nomulus/pull/2771
because that includes a small change included in the Feb 2024 version

This also updates the documentation to reference the proper areas of the
specifications.
2025-07-09 21:02:33 +00:00
193 changed files with 2441 additions and 1114 deletions

View File

@@ -280,4 +280,15 @@ export class BackendService {
`/console-api/registry-lock-verify?lockVerificationCode=${lockVerificationCode}`
);
}
requestRegistryLockPasswordReset(
registrarId: string,
registryLockEmail: string
) {
return this.http.post('/console-api/password-reset-request', {
type: 'REGISTRY_LOCK',
registrarId,
registryLockEmail,
});
}
}

View File

@@ -80,7 +80,15 @@
roleToDescription(userDetails().role)
}}</span>
</mat-list-item>
@if (userDetails().password) {
@if (userDetails().registryLockEmailAddress) {
<mat-divider></mat-divider>
<mat-list-item role="listitem">
<span class="console-app__list-key">Registry Lock email</span>
<span class="console-app__list-value">{{
userDetails().registryLockEmailAddress
}}</span>
</mat-list-item>
} @if (userDetails().password) {
<mat-divider></mat-divider>
<mat-list-item role="listitem">
<span class="console-app__list-key">Password</span>

View File

@@ -35,5 +35,8 @@
border: 1px solid #ddd;
border-radius: 10px;
}
.console-app__list-key {
width: 160px;
}
}
}

View File

@@ -1,45 +1,57 @@
<form (ngSubmit)="saveEdit($event)" #form>
<p *ngIf="isNew()">
<mat-form-field appearance="outline">
<mat-label
>User name prefix:
<mat-icon
matTooltip="Prefix will be combined with registrar ID to create a unique user name - {prefix}.{registrarId}@registry.google"
>help_outline</mat-icon
></mat-label
>
<input
matInput
minlength="3"
maxlength="3"
[required]="true"
[(ngModel)]="user().emailAddress"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
</p>
<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)]="user().role" name="userRole">
<mat-option value="PRIMARY_CONTACT">Editor</mat-option>
<mat-option value="ACCOUNT_MANAGER">Viewer</mat-option>
</mat-select>
</mat-form-field>
</p>
<div class="console-app__user-edit">
<form (ngSubmit)="saveEdit($event)" #form>
<p *ngIf="isNew()">
<mat-form-field appearance="outline">
<mat-label
>User name prefix:
<mat-icon
matTooltip="Prefix will be combined with registrar ID to create a unique user name - {prefix}.{registrarId}@registry.google"
>help_outline</mat-icon
></mat-label
>
<input
matInput
minlength="3"
maxlength="3"
[required]="true"
[(ngModel)]="user().emailAddress"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
</p>
<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)]="user().role" 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"
aria-label="Save changes to the user"
>
Save
</button>
</form>
@if(userDataService.userData()?.isAdmin) {
<button
mat-flat-button
color="primary"
aria-label="Save user"
type="submit"
aria-label="Save changes to the user"
aria-label="Reset registry lock password"
(click)="requestRegistryLockPasswordReset()"
>
Save
Reset registry lock password
</button>
</form>
}
</div>

View File

@@ -0,0 +1,20 @@
// Copyright 2025 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-edit {
button {
display: block;
margin-bottom: 5px;
}
}

View File

@@ -17,13 +17,56 @@ import {
Component,
ElementRef,
EventEmitter,
Inject,
input,
Output,
ViewChild,
} from '@angular/core';
import { MaterialModule } from '../material.module';
import { FormsModule } from '@angular/forms';
import { User } from './users.service';
import { User, UsersService } from './users.service';
import { UserDataService } from '../shared/services/userData.service';
import { BackendService } from '../shared/services/backend.service';
import { RegistrarService } from '../registrar/registrar.service';
import {
MAT_DIALOG_DATA,
MatDialog,
MatDialogRef,
} from '@angular/material/dialog';
import { filter, switchMap, take } from 'rxjs';
import { MatSnackBar } from '@angular/material/snack-bar';
import { HttpErrorResponse } from '@angular/common/http';
@Component({
selector: 'app-reset-lock-password-dialog',
template: `
<h2 mat-dialog-title>Please confirm the password reset:</h2>
<mat-dialog-content>
This will send a registry lock password reset email to
{{ data.registryLockEmailAddress }}.
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button (click)="onCancel()">Cancel</button>
<button mat-button color="warn" (click)="onSave()">Confirm</button>
</mat-dialog-actions>
`,
imports: [CommonModule, MaterialModule],
})
export class ResetRegistryLockPasswordComponent {
constructor(
public dialogRef: MatDialogRef<ResetRegistryLockPasswordComponent>,
@Inject(MAT_DIALOG_DATA)
public data: { registryLockEmailAddress: string }
) {}
onSave(): void {
this.dialogRef.close(true);
}
onCancel(): void {
this.dialogRef.close(false);
}
}
@Component({
selector: 'app-user-edit-form',
@@ -39,12 +82,22 @@ export class UserEditFormComponent {
{
emailAddress: '',
role: 'ACCOUNT_MANAGER',
registryLockEmailAddress: '',
},
{ transform: (user: User) => structuredClone(user) }
);
@Output() onEditComplete = new EventEmitter<User>();
constructor(
protected userDataService: UserDataService,
private backendService: BackendService,
private resetRegistryLockPasswordDialog: MatDialog,
private registrarService: RegistrarService,
private usersService: UsersService,
private _snackBar: MatSnackBar
) {}
saveEdit(e: SubmitEvent) {
e.preventDefault();
if (this.form.nativeElement.checkValidity()) {
@@ -53,4 +106,34 @@ export class UserEditFormComponent {
this.form.nativeElement.reportValidity();
}
}
sendRegistryLockPasswordResetRequest() {
return this.backendService.requestRegistryLockPasswordReset(
this.registrarService.registrarId(),
this.user().registryLockEmailAddress!
);
}
requestRegistryLockPasswordReset() {
const dialogRef = this.resetRegistryLockPasswordDialog.open(
ResetRegistryLockPasswordComponent,
{
data: {
registryLockEmailAddress: this.user().registryLockEmailAddress,
},
}
);
dialogRef
.afterClosed()
.pipe(
take(1),
filter((result) => !!result)
)
.pipe(switchMap((_) => this.sendRegistryLockPasswordResetRequest()))
.subscribe({
next: (_) => this.usersService.currentlyOpenUserEmail.set(''),
error: (err: HttpErrorResponse) =>
this._snackBar.open(err.error || err.message),
});
}
}

View File

@@ -33,6 +33,7 @@ export interface User {
emailAddress: string;
role: string;
password?: string;
registryLockEmailAddress?: string;
}
@Injectable()

View File

@@ -17,7 +17,7 @@ import java.util.Optional
plugins {
id 'java-library'
id "org.flywaydb.flyway" version "11.0.1"
id "org.flywaydb.flyway" version "9.22.3"
id 'maven-publish'
}

View File

@@ -15,6 +15,7 @@
package google.registry.export;
import static com.google.common.base.Verify.verifyNotNull;
import static google.registry.model.common.FeatureFlag.FeatureName.INCLUDE_PENDING_DELETE_DATE_FOR_DOMAINS;
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;
@@ -75,7 +76,8 @@ public class ExportDomainListsAction implements Runnable {
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""";
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_FORMAT = "registered_domains_%s.txt";
@@ -93,10 +95,7 @@ public class ExportDomainListsAction implements Runnable {
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));
tm().transact(() -> FeatureFlag.isActiveNow(INCLUDE_PENDING_DELETE_DATE_FOR_DOMAINS));
realTlds.forEach(
tld -> {
List<String> domainsList =

View File

@@ -19,6 +19,7 @@ import static google.registry.flows.ResourceFlowUtils.verifyResourceDoesNotExist
import static google.registry.flows.contact.ContactFlowUtils.validateAsciiPostalInfo;
import static google.registry.flows.contact.ContactFlowUtils.validateContactAgainstPolicy;
import static google.registry.model.EppResourceUtils.createRepoId;
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_PROHIBITED;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.common.collect.ImmutableSet;
@@ -29,8 +30,10 @@ import google.registry.flows.FlowModule.RegistrarId;
import google.registry.flows.FlowModule.TargetId;
import google.registry.flows.MutatingFlow;
import google.registry.flows.annotations.ReportingSpec;
import google.registry.flows.exceptions.ContactsProhibitedException;
import google.registry.flows.exceptions.ResourceAlreadyExistsForThisClientException;
import google.registry.flows.exceptions.ResourceCreateContentionException;
import google.registry.model.common.FeatureFlag;
import google.registry.model.contact.Contact;
import google.registry.model.contact.ContactCommand.Create;
import google.registry.model.contact.ContactHistory;
@@ -47,6 +50,7 @@ import org.joda.time.DateTime;
* An EPP flow that creates a new contact.
*
* @error {@link google.registry.flows.FlowUtils.NotLoggedInException}
* @error {@link ContactsProhibitedException}
* @error {@link ResourceAlreadyExistsForThisClientException}
* @error {@link ResourceCreateContentionException}
* @error {@link ContactFlowUtils.BadInternationalizedPostalInfoException}
@@ -69,6 +73,9 @@ public final class ContactCreateFlow implements MutatingFlow {
extensionManager.register(MetadataExtension.class);
validateRegistrarIsLoggedIn(registrarId);
extensionManager.validate();
if (FeatureFlag.isActiveNow(MINIMUM_DATASET_CONTACTS_PROHIBITED)) {
throw new ContactsProhibitedException();
}
Create command = (Create) resourceCommand;
DateTime now = tm().getTransactionTime();
verifyResourceDoesNotExist(Contact.class, targetId, now, registrarId);

View File

@@ -24,6 +24,7 @@ import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfo;
import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
import static google.registry.flows.contact.ContactFlowUtils.validateAsciiPostalInfo;
import static google.registry.flows.contact.ContactFlowUtils.validateContactAgainstPolicy;
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_PROHIBITED;
import static google.registry.model.reporting.HistoryEntry.Type.CONTACT_UPDATE;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
@@ -35,7 +36,9 @@ import google.registry.flows.FlowModule.Superuser;
import google.registry.flows.FlowModule.TargetId;
import google.registry.flows.MutatingFlow;
import google.registry.flows.annotations.ReportingSpec;
import google.registry.flows.exceptions.ContactsProhibitedException;
import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException;
import google.registry.model.common.FeatureFlag;
import google.registry.model.contact.Contact;
import google.registry.model.contact.ContactCommand.Update;
import google.registry.model.contact.ContactCommand.Update.Change;
@@ -55,6 +58,7 @@ import org.joda.time.DateTime;
/**
* An EPP flow that updates a contact.
*
* @error {@link ContactsProhibitedException}
* @error {@link google.registry.flows.FlowUtils.NotLoggedInException}
* @error {@link google.registry.flows.ResourceFlowUtils.AddRemoveSameValueException}
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException}
@@ -92,6 +96,9 @@ public final class ContactUpdateFlow implements MutatingFlow {
extensionManager.register(MetadataExtension.class);
validateRegistrarIsLoggedIn(registrarId);
extensionManager.validate();
if (FeatureFlag.isActiveNow(MINIMUM_DATASET_CONTACTS_PROHIBITED)) {
throw new ContactsProhibitedException();
}
Update command = (Update) resourceCommand;
DateTime now = tm().getTransactionTime();
Contact existingContact = loadAndVerifyExistence(Contact.class, targetId, now);

View File

@@ -72,7 +72,9 @@ import google.registry.flows.custom.DomainCreateFlowCustomLogic;
import google.registry.flows.custom.DomainCreateFlowCustomLogic.BeforeResponseParameters;
import google.registry.flows.custom.DomainCreateFlowCustomLogic.BeforeResponseReturnData;
import google.registry.flows.custom.EntityChanges;
import google.registry.flows.domain.DomainFlowUtils.RegistrantProhibitedException;
import google.registry.flows.domain.token.AllocationTokenFlowUtils;
import google.registry.flows.exceptions.ContactsProhibitedException;
import google.registry.flows.exceptions.ResourceAlreadyExistsForThisClientException;
import google.registry.flows.exceptions.ResourceCreateContentionException;
import google.registry.model.ImmutableObject;
@@ -147,6 +149,7 @@ import org.joda.time.Duration;
* @error {@link DomainCreateFlow.NoGeneralRegistrationsInCurrentPhaseException}
* @error {@link DomainCreateFlow.NoTrademarkedRegistrationsBeforeSunriseException}
* @error {@link BulkDomainRegisteredForTooManyYearsException}
* @error {@link ContactsProhibitedException}
* @error {@link DomainCreateFlow.SignedMarksOnlyDuringSunriseException}
* @error {@link DomainFlowTmchUtils.NoMarksFoundMatchingDomainException}
* @error {@link DomainFlowTmchUtils.FoundMarkNotYetValidException}
@@ -194,6 +197,7 @@ import org.joda.time.Duration;
* @error {@link DomainFlowUtils.NameserversNotSpecifiedForTldWithNameserverAllowListException}
* @error {@link DomainFlowUtils.PremiumNameBlockedException}
* @error {@link DomainFlowUtils.RegistrantNotAllowedException}
* @error {@link RegistrantProhibitedException}
* @error {@link DomainFlowUtils.RegistrarMustBeActiveForThisOperationException}
* @error {@link DomainFlowUtils.TldDoesNotExistException}
* @error {@link DomainFlowUtils.TooManyDsRecordsException}
@@ -244,7 +248,7 @@ public final class DomainCreateFlow implements MutatingFlow {
verifyResourceDoesNotExist(Domain.class, targetId, now, registrarId);
// Validate that this is actually a legal domain name on a TLD that the registrar has access to.
InternetDomainName domainName = validateDomainName(command.getDomainName());
String domainLabel = domainName.parts().get(0);
String domainLabel = domainName.parts().getFirst();
Tld tld = Tld.get(domainName.parent().toString());
validateCreateCommandContactsAndNameservers(command, tld, domainName);
TldState tldState = tld.getTldState(now);

View File

@@ -25,7 +25,7 @@ import static com.google.common.collect.Sets.intersection;
import static com.google.common.collect.Sets.union;
import static google.registry.bsa.persistence.BsaLabelUtils.isLabelBlocked;
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_OPTIONAL;
import static google.registry.model.common.FeatureFlag.isActiveNow;
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_PROHIBITED;
import static google.registry.model.domain.Domain.MAX_REGISTRATION_YEARS;
import static google.registry.model.domain.token.AllocationToken.TokenType.REGISTER_BSA;
import static google.registry.model.tld.Tld.TldState.GENERAL_AVAILABILITY;
@@ -75,11 +75,13 @@ import google.registry.flows.EppException.ParameterValueSyntaxErrorException;
import google.registry.flows.EppException.RequiredParameterMissingException;
import google.registry.flows.EppException.StatusProhibitsOperationException;
import google.registry.flows.EppException.UnimplementedOptionException;
import google.registry.flows.exceptions.ContactsProhibitedException;
import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException;
import google.registry.model.EppResource;
import google.registry.model.billing.BillingBase.Flag;
import google.registry.model.billing.BillingBase.Reason;
import google.registry.model.billing.BillingRecurrence;
import google.registry.model.common.FeatureFlag;
import google.registry.model.contact.Contact;
import google.registry.model.domain.DesignatedContact;
import google.registry.model.domain.DesignatedContact.Type;
@@ -478,27 +480,37 @@ public class DomainFlowUtils {
}
}
static void validateRequiredContactsPresentIfRequiredForDataset(
/**
* Enforces the presence/absence of contact data depending on the minimum data set migration
* schedule.
*/
static void validateContactDataPresence(
Optional<VKey<Contact>> registrant, Set<DesignatedContact> contacts)
throws RequiredParameterMissingException {
// TODO(b/353347632): Change this flag check to a registry config check.
if (isActiveNow(MINIMUM_DATASET_CONTACTS_OPTIONAL)) {
// Contacts are not required once we have begun the migration to the minimum dataset
return;
}
if (registrant.isEmpty()) {
throw new MissingRegistrantException();
}
throws RequiredParameterMissingException, ParameterValuePolicyErrorException {
// TODO(b/353347632): Change these flag checks to a registry config check once minimum data set
// migration is completed.
if (FeatureFlag.isActiveNow(MINIMUM_DATASET_CONTACTS_PROHIBITED)) {
if (registrant.isPresent()) {
throw new RegistrantProhibitedException();
}
if (!contacts.isEmpty()) {
throw new ContactsProhibitedException();
}
} else if (!FeatureFlag.isActiveNow(MINIMUM_DATASET_CONTACTS_OPTIONAL)) {
if (registrant.isEmpty()) {
throw new MissingRegistrantException();
}
Set<Type> roles = new HashSet<>();
for (DesignatedContact contact : contacts) {
roles.add(contact.getType());
}
if (!roles.contains(Type.ADMIN)) {
throw new MissingAdminContactException();
}
if (!roles.contains(Type.TECH)) {
throw new MissingTechnicalContactException();
Set<Type> roles = new HashSet<>();
for (DesignatedContact contact : contacts) {
roles.add(contact.getType());
}
if (!roles.contains(Type.ADMIN)) {
throw new MissingAdminContactException();
}
if (!roles.contains(Type.TECH)) {
throw new MissingTechnicalContactException();
}
}
}
@@ -1042,8 +1054,7 @@ public class DomainFlowUtils {
String tldStr = tld.getTldStr();
validateRegistrantAllowedOnTld(tldStr, command.getRegistrantContactId());
validateNoDuplicateContacts(command.getContacts());
validateRequiredContactsPresentIfRequiredForDataset(
command.getRegistrant(), command.getContacts());
validateContactDataPresence(command.getRegistrant(), command.getContacts());
ImmutableSet<String> hostNames = command.getNameserverHostNames();
validateNameserversCountForTld(tldStr, domainName, hostNames.size());
validateNameserversAllowedOnTld(tldStr, hostNames);
@@ -1367,6 +1378,13 @@ public class DomainFlowUtils {
}
}
/** Having a registrant is prohibited by registry policy. */
static class RegistrantProhibitedException extends ParameterValuePolicyErrorException {
public RegistrantProhibitedException() {
super("Having a registrant is prohibited by registry policy");
}
}
/** Admin contact is required. */
static class MissingAdminContactException extends RequiredParameterMissingException {
public MissingAdminContactException() {

View File

@@ -30,6 +30,7 @@ import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
import static google.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld;
import static google.registry.flows.domain.DomainFlowUtils.cloneAndLinkReferences;
import static google.registry.flows.domain.DomainFlowUtils.updateDsData;
import static google.registry.flows.domain.DomainFlowUtils.validateContactDataPresence;
import static google.registry.flows.domain.DomainFlowUtils.validateContactsHaveTypes;
import static google.registry.flows.domain.DomainFlowUtils.validateDsData;
import static google.registry.flows.domain.DomainFlowUtils.validateFeesAckedIfPresent;
@@ -37,11 +38,10 @@ import static google.registry.flows.domain.DomainFlowUtils.validateNameserversAl
import static google.registry.flows.domain.DomainFlowUtils.validateNameserversCountForTld;
import static google.registry.flows.domain.DomainFlowUtils.validateNoDuplicateContacts;
import static google.registry.flows.domain.DomainFlowUtils.validateRegistrantAllowedOnTld;
import static google.registry.flows.domain.DomainFlowUtils.validateRequiredContactsPresentIfRequiredForDataset;
import static google.registry.flows.domain.DomainFlowUtils.verifyClientUpdateNotProhibited;
import static google.registry.flows.domain.DomainFlowUtils.verifyNotInPendingDelete;
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_OPTIONAL;
import static google.registry.model.common.FeatureFlag.isActiveNow;
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_PROHIBITED;
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_UPDATE;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
@@ -64,9 +64,11 @@ import google.registry.flows.custom.DomainUpdateFlowCustomLogic.BeforeSaveParame
import google.registry.flows.custom.EntityChanges;
import google.registry.flows.domain.DomainFlowUtils.MissingRegistrantException;
import google.registry.flows.domain.DomainFlowUtils.NameserversNotSpecifiedForTldWithNameserverAllowListException;
import google.registry.flows.domain.DomainFlowUtils.RegistrantProhibitedException;
import google.registry.model.ImmutableObject;
import google.registry.model.billing.BillingBase.Reason;
import google.registry.model.billing.BillingEvent;
import google.registry.model.common.FeatureFlag;
import google.registry.model.contact.Contact;
import google.registry.model.domain.DesignatedContact;
import google.registry.model.domain.Domain;
@@ -130,6 +132,7 @@ import org.joda.time.DateTime;
* @error {@link NameserversNotSpecifiedForTldWithNameserverAllowListException}
* @error {@link DomainFlowUtils.NotAuthorizedForTldException}
* @error {@link DomainFlowUtils.RegistrantNotAllowedException}
* @error {@link RegistrantProhibitedException}
* @error {@link DomainFlowUtils.SecDnsAllUsageException}
* @error {@link DomainFlowUtils.TooManyDsRecordsException}
* @error {@link DomainFlowUtils.TooManyNameserversException}
@@ -304,11 +307,12 @@ public final class DomainUpdateFlow implements MutatingFlow {
private Optional<VKey<Contact>> determineUpdatedRegistrant(Change change, Domain domain)
throws EppException {
// During phase 1 of minimum dataset transition, allow registrant to be removed
// During or after the minimum dataset transition, allow registrant to be removed.
if (change.getRegistrantContactId().isPresent()
&& change.getRegistrantContactId().get().isEmpty()) {
// TODO(b/353347632): Change this flag check to a registry config check.
if (isActiveNow(MINIMUM_DATASET_CONTACTS_OPTIONAL)) {
if (FeatureFlag.isActiveNow(MINIMUM_DATASET_CONTACTS_OPTIONAL)
|| FeatureFlag.isActiveNow(MINIMUM_DATASET_CONTACTS_PROHIBITED)) {
return Optional.empty();
} else {
throw new MissingRegistrantException();
@@ -325,8 +329,7 @@ public final class DomainUpdateFlow implements MutatingFlow {
* cause Domain update failure.
*/
private static void validateNewState(Domain newDomain) throws EppException {
validateRequiredContactsPresentIfRequiredForDataset(
newDomain.getRegistrant(), newDomain.getContacts());
validateContactDataPresence(newDomain.getRegistrant(), newDomain.getContacts());
validateDsData(newDomain.getDsData());
validateNameserversCountForTld(
newDomain.getTld(),

View File

@@ -0,0 +1,24 @@
// Copyright 2025 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.
package google.registry.flows.exceptions;
import google.registry.flows.EppException.ParameterValuePolicyErrorException;
/** Having contacts is prohibited by registry policy */
public class ContactsProhibitedException extends ParameterValuePolicyErrorException {
public ContactsProhibitedException() {
super("Having contacts is prohibited by registry policy");
}
}

View File

@@ -62,11 +62,31 @@ public class FeatureFlag extends ImmutableObject implements Buildable {
INACTIVE
}
/** The names of the feature flags that can be individually set. */
public enum FeatureName {
TEST_FEATURE,
MINIMUM_DATASET_CONTACTS_OPTIONAL,
MINIMUM_DATASET_CONTACTS_PROHIBITED,
INCLUDE_PENDING_DELETE_DATE_FOR_DOMAINS
/** Feature flag name used for testing only. */
TEST_FEATURE(FeatureStatus.INACTIVE),
/** If we're not requiring the presence of contact data on domain EPP commands. */
MINIMUM_DATASET_CONTACTS_OPTIONAL(FeatureStatus.INACTIVE),
/** If we're not permitting the presence of contact data on any EPP commands. */
MINIMUM_DATASET_CONTACTS_PROHIBITED(FeatureStatus.INACTIVE),
/**
* If we're including the upcoming domain drop date in the exported list of registered domains.
*/
INCLUDE_PENDING_DELETE_DATE_FOR_DOMAINS(FeatureStatus.INACTIVE);
private final FeatureStatus defaultStatus;
FeatureName(FeatureStatus defaultStatus) {
this.defaultStatus = defaultStatus;
}
FeatureStatus getDefaultStatus() {
return this.defaultStatus;
}
}
/** The name of the flag/feature. */
@@ -155,24 +175,24 @@ 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. */
/**
* Returns whether the flag is active now, or else the flag's default value if it doesn't exist.
*/
public static boolean isActiveNow(FeatureName featureName) {
tm().assertInTransaction();
return isActiveAt(featureName, tm().getTransactionTime());
}
/** Returns if the FeatureFlag with the given FeatureName is active at a given time. */
/**
* Returns whether the flag is active at the given time, or else the flag's default value if it
* doesn't exist.
*/
public static boolean isActiveAt(FeatureName featureName, DateTime dateTime) {
return FeatureFlag.get(featureName).getStatus(dateTime).equals(ACTIVE);
tm().assertInTransaction();
return CACHE
.get(featureName)
.map(flag -> flag.getStatus(dateTime).equals(ACTIVE))
.orElse(featureName.getDefaultStatus().equals(ACTIVE));
}
@Override

View File

@@ -16,6 +16,8 @@ package google.registry.model.console;
/** Permissions that users may have in the UI, either per-registrar or globally. */
public enum ConsolePermission {
AUDIT_ACTIVITY_BY_USER,
AUDIT_ACTIVITY_BY_REGISTRAR,
/** View basic information about a registrar. */
VIEW_REGISTRAR_DETAILS,
/** Edit basic information about a registrar. */

View File

@@ -55,6 +55,8 @@ public class ConsoleRoleDefinitions {
new ImmutableSet.Builder<ConsolePermission>()
.addAll(SUPPORT_AGENT_PERMISSIONS)
.add(
ConsolePermission.AUDIT_ACTIVITY_BY_USER,
ConsolePermission.AUDIT_ACTIVITY_BY_REGISTRAR,
ConsolePermission.MANAGE_REGISTRARS,
ConsolePermission.GET_REGISTRANT_EMAIL,
ConsolePermission.SUSPEND_DOMAIN,
@@ -111,6 +113,7 @@ public class ConsoleRoleDefinitions {
new ImmutableSet.Builder<ConsolePermission>()
.addAll(TECH_CONTACT_PERMISSIONS)
.add(ConsolePermission.MANAGE_USERS)
.add(ConsolePermission.AUDIT_ACTIVITY_BY_REGISTRAR)
.build();
private ConsoleRoleDefinitions() {}

View File

@@ -16,6 +16,7 @@ package google.registry.model.console;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.google.gson.annotations.Expose;
import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.annotations.IdAllocation;
@@ -45,6 +46,7 @@ public class ConsoleUpdateHistory extends ImmutableObject implements Buildable {
@Id @IdAllocation @Column Long revisionId;
@Column(nullable = false)
@Expose
DateTime modificationTime;
/** The HTTP method (e.g. POST, PUT) used to make this modification. */
@@ -54,6 +56,7 @@ public class ConsoleUpdateHistory extends ImmutableObject implements Buildable {
/** The type of modification. */
@Column(nullable = false)
@Enumerated(EnumType.STRING)
@Expose
Type type;
/** The URL of the action that was used to make the modification. */
@@ -61,11 +64,12 @@ public class ConsoleUpdateHistory extends ImmutableObject implements Buildable {
String url;
/** An optional further description of the action. */
String description;
@Expose String description;
/** The user that performed the modification. */
@JoinColumn(name = "actingUser", referencedColumnName = "emailAddress", nullable = false)
@ManyToOne
@Expose
User actingUser;
public Long getRevisionId() {
@@ -102,18 +106,24 @@ public class ConsoleUpdateHistory extends ImmutableObject implements Buildable {
}
public enum Type {
DUM_DOWNLOAD,
DOMAIN_DELETE,
DOMAIN_SUSPEND,
DOMAIN_UNSUSPEND,
EPP_PASSWORD_UPDATE,
REGISTRAR_CREATE,
REGISTRAR_CONTACTS_UPDATE,
REGISTRAR_SECURITY_UPDATE,
REGISTRAR_UPDATE,
REGISTRY_LOCK,
REGISTRY_UNLOCK,
USER_CREATE,
USER_DELETE,
USER_UPDATE
USER_UPDATE,
}
public static final String DESCRIPTION_SEPARATOR = "|";
public static class Builder extends Buildable.Builder<ConsoleUpdateHistory> {
public Builder() {}

View File

@@ -62,7 +62,7 @@ public class User extends UpdateAutoTimestampEntity implements Buildable {
@Id @Expose String emailAddress;
/** Optional external email address to use for registry lock confirmation emails. */
@Column String registryLockEmailAddress;
@Column @Expose String registryLockEmailAddress;
/** Roles (which grant permissions) associated with this user. */
@Expose
@@ -250,51 +250,50 @@ public class User extends UpdateAutoTimestampEntity implements Buildable {
}
@Override
public Builder<? extends User, ?> asBuilder() {
return new Builder<>(clone(this));
public Builder asBuilder() {
return new Builder(clone(this));
}
/** Builder for constructing immutable {@link User} objects. */
public static class Builder<T extends User, B extends Builder<T, B>>
extends GenericBuilder<T, B> {
public static class Builder extends Buildable.Builder<User> {
public Builder() {}
public Builder(T abstractUser) {
super(abstractUser);
public Builder(User user) {
super(user);
}
@Override
public T build() {
public User build() {
checkArgumentNotNull(getInstance().emailAddress, "Email address cannot be null");
checkArgumentNotNull(getInstance().userRoles, "User roles cannot be null");
return super.build();
}
public B setEmailAddress(String emailAddress) {
public Builder setEmailAddress(String emailAddress) {
getInstance().emailAddress = checkValidEmail(emailAddress);
return thisCastToDerived();
return this;
}
public B setRegistryLockEmailAddress(@Nullable String registryLockEmailAddress) {
public Builder setRegistryLockEmailAddress(@Nullable String registryLockEmailAddress) {
getInstance().registryLockEmailAddress =
registryLockEmailAddress == null ? null : checkValidEmail(registryLockEmailAddress);
return thisCastToDerived();
return this;
}
public B setUserRoles(UserRoles userRoles) {
public Builder setUserRoles(UserRoles userRoles) {
checkArgumentNotNull(userRoles, "User roles cannot be null");
getInstance().userRoles = userRoles;
return thisCastToDerived();
return this;
}
public B removeRegistryLockPassword() {
public Builder removeRegistryLockPassword() {
getInstance().registryLockPasswordHash = null;
getInstance().registryLockPasswordSalt = null;
return thisCastToDerived();
return this;
}
public B setRegistryLockPassword(String registryLockPassword) {
public Builder setRegistryLockPassword(String registryLockPassword) {
checkArgument(
getInstance().hasAnyRegistryLockPermission(), "User has no registry lock permission");
checkArgument(
@@ -304,7 +303,7 @@ public class User extends UpdateAutoTimestampEntity implements Buildable {
byte[] salt = SALT_SUPPLIER.get();
getInstance().registryLockPasswordSalt = base64().encode(salt);
getInstance().registryLockPasswordHash = hashPassword(registryLockPassword, salt);
return thisCastToDerived();
return this;
}
}
}

View File

@@ -18,7 +18,7 @@ import static google.registry.util.CollectionUtils.forceEmptyToNull;
import com.google.common.base.Ascii;
import com.google.common.collect.ImmutableList;
import google.registry.model.Buildable.GenericBuilder;
import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.domain.Period;
import google.registry.model.domain.fee.Fee;
@@ -77,8 +77,7 @@ public class FeeCheckResponseExtensionItemCommandV12 extends ImmutableObject {
}
/** Builder for {@link FeeCheckResponseExtensionItemCommandV12}. */
public static class Builder
extends GenericBuilder<FeeCheckResponseExtensionItemCommandV12, Builder> {
public static class Builder extends Buildable.Builder<FeeCheckResponseExtensionItemCommandV12> {
public Builder setCommandName(CommandName commandName) {
getInstance().commandName = Ascii.toLowerCase(commandName.name());

View File

@@ -690,8 +690,8 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
}
@Override
public Builder<? extends Registrar, ?> asBuilder() {
return new Builder<>(clone(this));
public Builder asBuilder() {
return new Builder(clone(this));
}
@Override
@@ -706,59 +706,58 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
}
/** A builder for constructing {@link Registrar}, since it is immutable. */
public static class Builder<T extends Registrar, B extends Builder<T, B>>
extends GenericBuilder<T, B> {
public static class Builder extends Buildable.Builder<Registrar> {
public Builder() {}
public Builder(T instance) {
public Builder(Registrar instance) {
super(instance);
}
public B setRegistrarId(String registrarId) {
public Builder setRegistrarId(String registrarId) {
// Registrar id must be [3,16] chars long. See "clIDType" in the base EPP schema of RFC 5730.
// (Need to validate this here as there's no matching EPP XSD for validation.)
checkArgument(
Range.closed(3, 16).contains(registrarId.length()),
"Registrar ID must be 3-16 characters long.");
getInstance().registrarId = registrarId;
return thisCastToDerived();
return this;
}
public B setIanaIdentifier(@Nullable Long ianaIdentifier) {
public Builder setIanaIdentifier(@Nullable Long ianaIdentifier) {
checkArgument(
ianaIdentifier == null || ianaIdentifier > 0, "IANA ID must be a positive number");
getInstance().ianaIdentifier = ianaIdentifier;
return thisCastToDerived();
return this;
}
public B setPoNumber(Optional<String> poNumber) {
public Builder setPoNumber(Optional<String> poNumber) {
getInstance().poNumber = poNumber.orElse(null);
return thisCastToDerived();
return this;
}
public B setBillingAccountMap(@Nullable Map<CurrencyUnit, String> billingAccountMap) {
public Builder setBillingAccountMap(@Nullable Map<CurrencyUnit, String> billingAccountMap) {
getInstance().billingAccountMap = nullToEmptyImmutableCopy(billingAccountMap);
return thisCastToDerived();
return this;
}
public B setRegistrarName(String registrarName) {
public Builder setRegistrarName(String registrarName) {
getInstance().registrarName = registrarName;
return thisCastToDerived();
return this;
}
public B setType(Type type) {
public Builder setType(Type type) {
getInstance().type = type;
return thisCastToDerived();
return this;
}
public B setState(State state) {
public Builder setState(State state) {
getInstance().state = state;
return thisCastToDerived();
return this;
}
public B setAllowedTlds(Set<String> allowedTlds) {
public Builder setAllowedTlds(Set<String> allowedTlds) {
getInstance().allowedTlds = ImmutableSortedSet.copyOf(assertTldsExist(allowedTlds));
return thisCastToDerived();
return this;
}
/**
@@ -771,7 +770,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
* {@code .now()} when saving the Registry entity to make sure it's actually saved before trying
* to set the allowed TLDs.
*/
public B setAllowedTldsUncached(Set<String> allowedTlds) {
public Builder setAllowedTldsUncached(Set<String> allowedTlds) {
ImmutableSet<VKey<Tld>> newTldKeys =
Sets.difference(allowedTlds, getInstance().getAllowedTlds()).stream()
.map(Tld::createVKey)
@@ -780,10 +779,10 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
Sets.difference(newTldKeys, tm().loadByKeysIfPresent(newTldKeys).keySet());
checkArgument(missingTldKeys.isEmpty(), "Trying to set nonexistent TLDs: %s", missingTldKeys);
getInstance().allowedTlds = ImmutableSortedSet.copyOf(allowedTlds);
return thisCastToDerived();
return this;
}
public B setClientCertificate(String clientCertificate, DateTime now) {
public Builder setClientCertificate(String clientCertificate, DateTime now) {
clientCertificate = emptyToNull(clientCertificate);
String clientCertificateHash = calculateHash(clientCertificate);
if (!Objects.equals(clientCertificate, getInstance().clientCertificate)
@@ -792,23 +791,23 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
getInstance().clientCertificateHash = clientCertificateHash;
getInstance().lastCertificateUpdateTime = now;
}
return thisCastToDerived();
return this;
}
public B setLastExpiringCertNotificationSentDate(DateTime now) {
public Builder setLastExpiringCertNotificationSentDate(DateTime now) {
checkArgumentNotNull(now, "Registrar lastExpiringCertNotificationSentDate cannot be null");
getInstance().lastExpiringCertNotificationSentDate = now;
return thisCastToDerived();
return this;
}
public B setLastExpiringFailoverCertNotificationSentDate(DateTime now) {
public Builder setLastExpiringFailoverCertNotificationSentDate(DateTime now) {
checkArgumentNotNull(
now, "Registrar lastExpiringFailoverCertNotificationSentDate cannot be null");
getInstance().lastExpiringFailoverCertNotificationSentDate = now;
return thisCastToDerived();
return this;
}
public B setFailoverClientCertificate(String clientCertificate, DateTime now) {
public Builder setFailoverClientCertificate(String clientCertificate, DateTime now) {
clientCertificate = emptyToNull(clientCertificate);
String clientCertificateHash = calculateHash(clientCertificate);
if (!Objects.equals(clientCertificate, getInstance().failoverClientCertificate)
@@ -817,13 +816,13 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
getInstance().failoverClientCertificateHash = clientCertificateHash;
getInstance().lastCertificateUpdateTime = now;
}
return thisCastToDerived();
return this;
}
public B setLastPocVerificationDate(DateTime now) {
public Builder setLastPocVerificationDate(DateTime now) {
checkArgumentNotNull(now, "Registrar lastPocVerificationDate cannot be null");
getInstance().lastPocVerificationDate = now;
return thisCastToDerived();
return this;
}
private static String calculateHash(String clientCertificate) {
@@ -855,75 +854,75 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
Objects.equals(newInstance.ianaIdentifier, registrar.getIanaIdentifier()));
}
public B setContactsRequireSyncing(boolean contactsRequireSyncing) {
public Builder setContactsRequireSyncing(boolean contactsRequireSyncing) {
getInstance().contactsRequireSyncing = contactsRequireSyncing;
return thisCastToDerived();
return this;
}
public B setIpAddressAllowList(Iterable<CidrAddressBlock> ipAddressAllowList) {
public Builder setIpAddressAllowList(Iterable<CidrAddressBlock> ipAddressAllowList) {
getInstance().ipAddressAllowList = ImmutableList.copyOf(ipAddressAllowList);
return thisCastToDerived();
return this;
}
public B setLocalizedAddress(RegistrarAddress localizedAddress) {
public Builder setLocalizedAddress(RegistrarAddress localizedAddress) {
getInstance().localizedAddress = localizedAddress;
return thisCastToDerived();
return this;
}
public B setInternationalizedAddress(RegistrarAddress internationalizedAddress) {
public Builder setInternationalizedAddress(RegistrarAddress internationalizedAddress) {
getInstance().internationalizedAddress = internationalizedAddress;
return thisCastToDerived();
return this;
}
public B setPhoneNumber(String phoneNumber) {
public Builder setPhoneNumber(String phoneNumber) {
getInstance().phoneNumber = (phoneNumber == null) ? null : checkValidPhoneNumber(phoneNumber);
return thisCastToDerived();
return this;
}
public B setFaxNumber(String faxNumber) {
public Builder setFaxNumber(String faxNumber) {
getInstance().faxNumber = (faxNumber == null) ? null : checkValidPhoneNumber(faxNumber);
return thisCastToDerived();
return this;
}
public B setEmailAddress(String emailAddress) {
public Builder setEmailAddress(String emailAddress) {
getInstance().emailAddress = checkValidEmail(emailAddress);
return thisCastToDerived();
return this;
}
public B setWhoisServer(String whoisServer) {
public Builder setWhoisServer(String whoisServer) {
getInstance().whoisServer = whoisServer;
return thisCastToDerived();
return this;
}
public B setRdapBaseUrls(Set<String> rdapBaseUrls) {
public Builder setRdapBaseUrls(Set<String> rdapBaseUrls) {
getInstance().rdapBaseUrls = ImmutableSet.copyOf(rdapBaseUrls);
return thisCastToDerived();
return this;
}
public B setBlockPremiumNames(boolean blockPremiumNames) {
public Builder setBlockPremiumNames(boolean blockPremiumNames) {
getInstance().blockPremiumNames = blockPremiumNames;
return thisCastToDerived();
return this;
}
public B setUrl(String url) {
public Builder setUrl(String url) {
getInstance().url = url;
return thisCastToDerived();
return this;
}
public B setIcannReferralEmail(String icannReferralEmail) {
public Builder setIcannReferralEmail(String icannReferralEmail) {
getInstance().icannReferralEmail = checkValidEmail(icannReferralEmail);
return thisCastToDerived();
return this;
}
public B setDriveFolderId(@Nullable String driveFolderId) {
public Builder setDriveFolderId(@Nullable String driveFolderId) {
checkArgument(
driveFolderId == null || !driveFolderId.contains("/"),
"Drive folder ID must not be a full URL");
getInstance().driveFolderId = driveFolderId;
return thisCastToDerived();
return this;
}
public B setPassword(String password) {
public Builder setPassword(String password) {
// Passwords must be [6,16] chars long. See "pwType" in the base EPP schema of RFC 5730.
checkArgument(
Range.closed(6, 16).contains(nullToEmpty(password).length()),
@@ -931,7 +930,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
byte[] salt = SALT_SUPPLIER.get();
getInstance().salt = base64().encode(salt);
getInstance().passwordHash = hashPassword(password, salt);
return thisCastToDerived();
return this;
}
/**
@@ -939,18 +938,18 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
*
* @throws IllegalArgumentException if provided passcode is not 5-digit numeric
*/
public B setPhonePasscode(String phonePasscode) {
public Builder setPhonePasscode(String phonePasscode) {
checkArgument(
phonePasscode == null || PHONE_PASSCODE_PATTERN.matcher(phonePasscode).matches(),
"Not a valid telephone passcode (must be 5 digits long): %s",
phonePasscode);
getInstance().phonePasscode = phonePasscode;
return thisCastToDerived();
return this;
}
public B setRegistryLockAllowed(boolean registryLockAllowed) {
public Builder setRegistryLockAllowed(boolean registryLockAllowed) {
getInstance().registryLockAllowed = registryLockAllowed;
return thisCastToDerived();
return this;
}
/**
@@ -958,14 +957,14 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
* and breaks the verification that an object has not been updated since it was copied.
*/
@VisibleForTesting
public B setLastUpdateTime(DateTime timestamp) {
public Builder setLastUpdateTime(DateTime timestamp) {
getInstance().setUpdateTimestamp(UpdateAutoTimestamp.create(timestamp));
return thisCastToDerived();
return this;
}
/** Build the registrar, nullifying empty fields. */
@Override
public T build() {
public Registrar build() {
checkArgumentNotNull(getInstance().type, "Registrar type cannot be null");
checkArgumentNotNull(getInstance().registrarName, "Registrar name cannot be null");
checkArgument(

View File

@@ -31,7 +31,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.gson.annotations.Expose;
import google.registry.model.Buildable.GenericBuilder;
import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.JsonMapBuilder;
import google.registry.model.Jsonifiable;
@@ -225,8 +225,8 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
return visibleInDomainWhoisAsAbuse;
}
public Builder<? extends RegistrarPoc, ?> asBuilder() {
return new Builder<>(clone(this));
public Builder asBuilder() {
return new Builder(clone(this));
}
public boolean isAllowedToSetRegistryLockPassword() {
@@ -332,17 +332,16 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
}
/** A builder for constructing a {@link RegistrarPoc}, since it is immutable. */
public static class Builder<T extends RegistrarPoc, B extends Builder<T, B>>
extends GenericBuilder<T, B> {
public static class Builder extends Buildable.Builder<RegistrarPoc> {
public Builder() {}
protected Builder(T instance) {
protected Builder(RegistrarPoc instance) {
super(instance);
}
/** Build the registrar, nullifying empty fields. */
@Override
public T build() {
public RegistrarPoc build() {
checkNotNull(getInstance().registrarId, "Registrar ID cannot be null");
checkValidEmail(getInstance().emailAddress);
// Check allowedToSetRegistryLockPassword here because if we want to allow the user to set
@@ -356,71 +355,71 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
return cloneEmptyToNull(super.build());
}
public B setName(String name) {
public Builder setName(String name) {
getInstance().name = name;
return thisCastToDerived();
return this;
}
public B setEmailAddress(String emailAddress) {
public Builder setEmailAddress(String emailAddress) {
getInstance().emailAddress = emailAddress;
return thisCastToDerived();
return this;
}
public B setRegistryLockEmailAddress(@Nullable String registryLockEmailAddress) {
public Builder setRegistryLockEmailAddress(@Nullable String registryLockEmailAddress) {
getInstance().registryLockEmailAddress = registryLockEmailAddress;
return thisCastToDerived();
return this;
}
public B setPhoneNumber(String phoneNumber) {
public Builder setPhoneNumber(String phoneNumber) {
getInstance().phoneNumber = phoneNumber;
return thisCastToDerived();
return this;
}
public B setRegistrarId(String registrarId) {
public Builder setRegistrarId(String registrarId) {
getInstance().registrarId = registrarId;
return thisCastToDerived();
return this;
}
public B setRegistrar(Registrar registrar) {
public Builder setRegistrar(Registrar registrar) {
getInstance().registrarId = registrar.getRegistrarId();
return thisCastToDerived();
return this;
}
public B setFaxNumber(String faxNumber) {
public Builder setFaxNumber(String faxNumber) {
getInstance().faxNumber = faxNumber;
return thisCastToDerived();
return this;
}
public B setTypes(Iterable<Type> types) {
public Builder setTypes(Iterable<Type> types) {
getInstance().types = ImmutableSet.copyOf(types);
return thisCastToDerived();
return this;
}
public B setVisibleInWhoisAsAdmin(boolean visible) {
public Builder setVisibleInWhoisAsAdmin(boolean visible) {
getInstance().visibleInWhoisAsAdmin = visible;
return thisCastToDerived();
return this;
}
public B setVisibleInWhoisAsTech(boolean visible) {
public Builder setVisibleInWhoisAsTech(boolean visible) {
getInstance().visibleInWhoisAsTech = visible;
return thisCastToDerived();
return this;
}
public B setVisibleInDomainWhoisAsAbuse(boolean visible) {
public Builder setVisibleInDomainWhoisAsAbuse(boolean visible) {
getInstance().visibleInDomainWhoisAsAbuse = visible;
return thisCastToDerived();
return this;
}
public B setAllowedToSetRegistryLockPassword(boolean allowedToSetRegistryLockPassword) {
public Builder setAllowedToSetRegistryLockPassword(boolean allowedToSetRegistryLockPassword) {
if (allowedToSetRegistryLockPassword) {
getInstance().registryLockPasswordSalt = null;
getInstance().registryLockPasswordHash = null;
}
getInstance().allowedToSetRegistryLockPassword = allowedToSetRegistryLockPassword;
return thisCastToDerived();
return this;
}
public B setRegistryLockPassword(String registryLockPassword) {
public Builder setRegistryLockPassword(String registryLockPassword) {
checkArgument(
getInstance().allowedToSetRegistryLockPassword,
"Not allowed to set registry lock password for this contact");
@@ -430,7 +429,7 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
getInstance().registryLockPasswordSalt = base64().encode(salt);
getInstance().registryLockPasswordHash = hashPassword(registryLockPassword, salt);
getInstance().allowedToSetRegistryLockPassword = false;
return thisCastToDerived();
return this;
}
}

View File

@@ -115,6 +115,7 @@ import google.registry.ui.server.console.ConsoleDomainGetAction;
import google.registry.ui.server.console.ConsoleDomainListAction;
import google.registry.ui.server.console.ConsoleDumDownloadAction;
import google.registry.ui.server.console.ConsoleEppPasswordAction;
import google.registry.ui.server.console.ConsoleHistoryDataAction;
import google.registry.ui.server.console.ConsoleModule;
import google.registry.ui.server.console.ConsoleOteAction;
import google.registry.ui.server.console.ConsoleRegistryLockAction;
@@ -122,6 +123,8 @@ import google.registry.ui.server.console.ConsoleRegistryLockVerifyAction;
import google.registry.ui.server.console.ConsoleUpdateRegistrarAction;
import google.registry.ui.server.console.ConsoleUserDataAction;
import google.registry.ui.server.console.ConsoleUsersAction;
import google.registry.ui.server.console.PasswordResetRequestAction;
import google.registry.ui.server.console.PasswordResetVerifyAction;
import google.registry.ui.server.console.RegistrarsAction;
import google.registry.ui.server.console.domains.ConsoleBulkDomainAction;
import google.registry.ui.server.console.settings.ContactAction;
@@ -183,6 +186,8 @@ interface RequestComponent {
ConsoleEppPasswordAction consoleEppPasswordAction();
ConsoleHistoryDataAction consoleHistoryDataAction();
ConsoleOteAction consoleOteAction();
ConsoleRegistryLockAction consoleRegistryLockAction();
@@ -249,6 +254,10 @@ interface RequestComponent {
NordnVerifyAction nordnVerifyAction();
PasswordResetRequestAction passwordResetRequestAction();
PasswordResetVerifyAction passwordResetVerifyAction();
PublishDnsUpdatesAction publishDnsUpdatesAction();
PublishInvoicesAction uploadInvoicesAction();
@@ -281,6 +290,8 @@ interface RequestComponent {
RdapNameserverSearchAction rdapNameserverSearchAction();
RdapRegistrarFieldsAction rdapRegistrarFieldsAction();
RdeReportAction rdeReportAction();
RdeReporter rdeReporter();
@@ -332,9 +343,7 @@ interface RequestComponent {
WhoisAction whoisAction();
WhoisHttpAction whoisHttpAction();
RdapRegistrarFieldsAction rdapRegistrarFieldsAction();
WipeOutContactHistoryPiiAction wipeOutContactHistoryPiiAction();
@Subcomponent.Builder

View File

@@ -38,6 +38,8 @@ import google.registry.ui.server.console.ConsoleRegistryLockVerifyAction;
import google.registry.ui.server.console.ConsoleUpdateRegistrarAction;
import google.registry.ui.server.console.ConsoleUserDataAction;
import google.registry.ui.server.console.ConsoleUsersAction;
import google.registry.ui.server.console.PasswordResetRequestAction;
import google.registry.ui.server.console.PasswordResetVerifyAction;
import google.registry.ui.server.console.RegistrarsAction;
import google.registry.ui.server.console.domains.ConsoleBulkDomainAction;
import google.registry.ui.server.console.settings.ContactAction;
@@ -84,6 +86,12 @@ public interface FrontendRequestComponent {
FlowComponent.Builder flowComponentBuilder();
PasswordResetRequestAction passwordResetRequestAction();
PasswordResetVerifyAction passwordResetVerifyAction();
RdapRegistrarFieldsAction rdapRegistrarFieldsAction();
ReadinessProbeActionFrontend readinessProbeActionFrontend();
ReadinessProbeConsoleAction readinessProbeConsoleAction();
@@ -92,8 +100,6 @@ public interface FrontendRequestComponent {
SecurityAction securityAction();
RdapRegistrarFieldsAction rdapRegistrarFieldsAction();
@Subcomponent.Builder
abstract class Builder implements RequestComponentBuilder<FrontendRequestComponent> {
@Override public abstract Builder requestModule(RequestModule requestModule);

View File

@@ -55,7 +55,7 @@ import java.util.Optional;
import org.joda.time.DateTime;
/**
* Base RDAP (new WHOIS) action for all requests.
* Base RDAP action for all requests.
*
* @see <a href="https://tools.ietf.org/html/rfc9082">RFC 9082: Registration Data Access Protocol
* (RDAP) Query Format</a>
@@ -138,7 +138,7 @@ public abstract class RdapActionBase implements Runnable {
// RFC7480 4.2 - servers receiving an RDAP request return an entity with a Content-Type header
// containing the RDAP-specific JSON media type.
response.setContentType(RESPONSE_MEDIA_TYPE);
// RDAP Technical Implementation Guide 1.13 - when responding to RDAP valid requests, we MUST
// RDAP Technical Implementation Guide 1.14 - when responding to RDAP valid requests, we MUST
// include the Access-Control-Allow-Origin, which MUST be "*" unless otherwise specified.
response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
try {

View File

@@ -26,7 +26,7 @@ import google.registry.request.auth.Auth;
import jakarta.inject.Inject;
/**
* RDAP (new WHOIS) action for RDAP autonomous system number requests.
* RDAP action for RDAP autonomous system number requests.
*
* <p>This feature is not implemented because it's only necessary for <i>address</i> registries like
* ARIN, not domain registries.

View File

@@ -41,14 +41,13 @@ final class RdapDataStructures {
// Conformance to RFC 9083
jsonArray.add("rdap_level_0");
// Conformance to the RDAP Response Profile V2.1
// Conformance to the RDAP Response Profile V2.2 (February 2024)
// (see section 1.2)
jsonArray.add("icann_rdap_response_profile_1");
// Conformance to the RDAP Technical Implementation Guide V2.2 (February 2024)
// (see section 1.3)
jsonArray.add("icann_rdap_response_profile_0");
// Conformance to the RDAP Technical Implementation Guide V2.1
// (see section 1.14)
jsonArray.add("icann_rdap_technical_implementation_guide_0");
jsonArray.add("icann_rdap_technical_implementation_guide_1");
return jsonArray;
}
}

View File

@@ -36,7 +36,7 @@ import google.registry.request.auth.Auth;
import jakarta.inject.Inject;
import java.util.Optional;
/** RDAP (new WHOIS) action for domain requests. */
/** RDAP action for domain requests. */
@Action(
service = GaeService.PUBAPI,
path = "/rdap/domain/",

View File

@@ -51,6 +51,7 @@ import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.util.NonFinalForTesting;
import jakarta.inject.Inject;
import jakarta.persistence.Query;
import jakarta.persistence.criteria.CriteriaBuilder;
import java.net.InetAddress;
import java.util.Comparator;
@@ -60,17 +61,15 @@ import java.util.stream.Stream;
import org.hibernate.Hibernate;
/**
* RDAP (new WHOIS) action for domain search requests.
* RDAP action for domain search requests.
*
* <p>All commands and responses conform to the RDAP spec as defined in RFCs 7480 through 7485.
* <p>All commands and responses conform to the RDAP spec as defined in STD 95 and its RFCs.
*
* @see <a href="http://tools.ietf.org/html/rfc9082">RFC 9082: Registration Data Access Protocol
* (RDAP) Query Format</a>
* @see <a href="http://tools.ietf.org/html/rfc9083">RFC 9083: JSON Responses for the Registration
* Data Access Protocol (RDAP)</a>
*/
// TODO: This isn't required by the RDAP Technical Implementation Guide, and hence should be
// deleted, at least until it's actually required.
@Action(
service = GaeService.PUBAPI,
path = "/rdap/domains",
@@ -442,7 +441,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
replicaTm()
.transact(
() -> {
jakarta.persistence.Query query =
Query query =
replicaTm()
.getEntityManager()
.createNativeQuery(queryBuilder.toString())

View File

@@ -37,14 +37,12 @@ import jakarta.inject.Inject;
import java.util.Optional;
/**
* RDAP (new WHOIS) action for entity (contact and registrar) requests. the ICANN operational
* profile dictates that the "handle" for registrars is to be the IANA registrar ID:
* RDAP action for entity (contact and registrar) requests. the ICANN operational profile dictates
* that the "handle" for registrars is to be the IANA registrar ID:
*
* <p>2.8.3. Registries MUST support lookup for entities with the registrar role within other
* objects using the handle (as described in 3.1.5 of RFC 9082). The handle of the entity with the
* registrar role MUST be equal to IANA Registrar ID. The entity with the registrar role in the RDAP
* response MUST contain a publicIDs member to identify the IANA Registrar ID from the IANAs
* Registrar ID registry. The type value of the publicID object MUST be equal to IANA Registrar ID.
* <p>2.4.1.Registry RDAP servers MUST support Registrar object lookup using an entity path request
* for entities with the registrar role using the handle (as described in 3.1.5 of RFC9082) where
* the handle of the entity with the registrar role is be [sic] equal to the IANA Registrar ID.
*/
@Action(
service = GaeService.PUBAPI,
@@ -104,7 +102,7 @@ public class RdapEntityAction extends RdapActionBase {
// query, it MUST reply with 404 response code.
//
// Note we don't do RFC7480 5.3 - returning a different code if we wish to say "this info
// exists but we don't want to show it to you", because we DON'T wish to say that.
// exists, but we don't want to show it to you", because we DON'T wish to say that.
throw new NotFoundException(pathSearchString + " not found");
}
}

View File

@@ -49,9 +49,9 @@ import java.util.List;
import java.util.Optional;
/**
* RDAP (new WHOIS) action for entity (contact and registrar) search requests.
* RDAP action for entity (contact and registrar) search requests.
*
* <p>All commands and responses conform to the RDAP spec as defined in RFCs 7480 through 7485.
* <p>All commands and responses conform to the RDAP spec as defined in STD 95 and its RFCs.
*
* <p>The RDAP specification lumps contacts and registrars together and calls them "entities", which
* is confusing for us, because "entity" means something else in SQL. But here, when we use the
@@ -76,8 +76,6 @@ import java.util.Optional;
* @see <a href="http://tools.ietf.org/html/rfc9083">RFC 9083: JSON Responses for the Registration
* Data Access Protocol (RDAP)</a>
*/
// TODO: This isn't required by the RDAP Technical Implementation Guide, and hence should be
// deleted, at least until it's actually required.
@Action(
service = GaeService.PUBAPI,
path = "/rdap/entities",

View File

@@ -28,7 +28,7 @@ import google.registry.request.auth.Auth;
import jakarta.inject.Inject;
import java.util.Optional;
/** RDAP (new WHOIS) action for help requests. */
/** RDAP action for help requests. */
@Action(
service = GaeService.PUBAPI,
path = RdapHelpAction.PATH,

View File

@@ -22,26 +22,18 @@ import google.registry.rdap.RdapDataStructures.Remark;
/**
* This file contains boilerplate required by the ICANN RDAP Profile.
*
* @see <a href="https://www.icann.org/resources/pages/rdap-operational-profile-2016-07-26-en">RDAP
* Operational Profile for gTLD Registries and Registrars</a>
* @see <a
* href="https://itp.cdn.icann.org/en/files/registry-operators/rdap-response-profile-21feb24-en.pdf">
* RDAP Response Profile</a>
*/
public class RdapIcannStandardInformation {
/** Required by ICANN RDAP Profile section 1.4.10. */
private static final Notice CONFORMANCE_NOTICE =
Notice.builder()
.setDescription(
"This response conforms to the RDAP Operational Profile for gTLD Registries and"
+ " Registrars version 1.0")
.build();
/** Required by ICANN RDAP Profile section 1.5.18. */
/** Required by RDAP Response Profile section 2.6.3. */
private static final Notice DOMAIN_STATUS_CODES_NOTICE =
Notice.builder()
.setTitle("Status Codes")
.setDescription(
"For more information on domain status codes, please visit"
+ " https://icann.org/epp")
"For more information on domain status codes, please visit https://icann.org/epp")
.addLink(
Link.builder()
.setRel("glossary")
@@ -50,7 +42,7 @@ public class RdapIcannStandardInformation {
.build())
.build();
/** Required by ICANN RDAP Response Profile section 2.11. */
/** Required by RDAP Response Profile section 2.10. */
private static final Notice INACCURACY_COMPLAINT_FORM_NOTICE =
Notice.builder()
.setTitle("RDDS Inaccuracy Complaint Form")
@@ -79,28 +71,16 @@ public class RdapIcannStandardInformation {
/** Boilerplate notices required by domain responses. */
static final ImmutableList<Notice> DOMAIN_BOILERPLATE_NOTICES =
ImmutableList.of(
CONFORMANCE_NOTICE,
// RDAP Response Profile 2.6.3
DOMAIN_STATUS_CODES_NOTICE,
// RDAP Response Profile 2.11
// RDAP Response Profile 2.10
INACCURACY_COMPLAINT_FORM_NOTICE);
/** Boilerplate notice for when a domain is blocked by BSA. */
static final ImmutableList<Notice> DOMAIN_BLOCKED_BY_BSA_BOILERPLATE_NOTICES =
ImmutableList.of(DOMAIN_BLOCKED_BY_BSA_NOTICE);
/** Boilerplate remarks required by nameserver and entity responses. */
static final ImmutableList<Notice> NAMESERVER_AND_ENTITY_BOILERPLATE_NOTICES =
ImmutableList.of(CONFORMANCE_NOTICE);
/**
* Required by ICANN RDAP Profile section 1.4.9, as corrected by Gustavo Lozano of ICANN.
*
* <p>Also mentioned in the RDAP Technical Implementation Guide 3.6.
*
* @see <a href="http://mm.icann.org/pipermail/gtld-tech/2016-October/000822.html">Questions about
* the ICANN RDAP Profile</a>
*/
/** Required by the RDAP Technical Implementation Guide 3.6. */
static final Remark SUMMARY_DATA_REMARK =
Remark.builder()
.setTitle("Incomplete Data")
@@ -109,14 +89,7 @@ public class RdapIcannStandardInformation {
.setType(Remark.Type.OBJECT_TRUNCATED_UNEXPLAINABLE)
.build();
/**
* Required by ICANN RDAP Profile section 1.4.8, as corrected by Gustavo Lozano of ICANN.
*
* <p>Also mentioned in the RDAP Technical Implementation Guide 3.5.
*
* @see <a href="http://mm.icann.org/pipermail/gtld-tech/2016-October/000822.html">Questions about
* the ICANN RDAP Profile</a>
*/
/** Required by the RDAP Technical Implementation Guide 3.5. */
static final Notice TRUNCATED_RESULT_SET_NOTICE =
Notice.builder()
.setTitle("Search Policy")
@@ -148,7 +121,9 @@ public class RdapIcannStandardInformation {
/**
* Included when requester is not logged in as the owner of the contact being returned.
*
* <p>Format required by ICANN RDAP Response Profile 15feb19 section 2.7.4.3.
* <p>>Note: if we were keeping this around, we'd want/need to implement the <a
* href="https://datatracker.ietf.org/doc/rfc9537/">official RDAP redaction spec</a> for contacts.
* We are getting rid of contacts in 2025 though so this should be unnecessary.
*/
static final Remark CONTACT_PERSONAL_DATA_HIDDEN_DATA_REMARK =
Remark.builder()
@@ -169,10 +144,9 @@ public class RdapIcannStandardInformation {
/**
* Included in ALL contact responses, even if the user is authorized.
*
* <p>Format required by ICANN RDAP Response Profile 15feb19 section 2.7.5.3.
*
* <p>NOTE that unlike other redacted fields, there's no allowance to give the email to authorized
* users or allow for registrar consent.
* <p>>Note: if we were keeping this around, we'd want/need to implement the <a
* href="https://datatracker.ietf.org/doc/rfc9537/">official RDAP redaction spec</a> for contacts.
* We are getting rid of contacts in 2025 though so this should be unnecessary.
*/
static final Remark CONTACT_EMAIL_REDACTED_FOR_DOMAIN =
Remark.builder()

View File

@@ -26,7 +26,7 @@ import google.registry.request.auth.Auth;
import jakarta.inject.Inject;
/**
* RDAP (new WHOIS) action for RDAP IP address requests.
* RDAP action for RDAP IP address requests.
*
* <p>This feature is not implemented because it's only necessary for <i>address</i> registries like
* ARIN, not domain registries.

View File

@@ -221,7 +221,7 @@ public class RdapJsonFormatter {
* Map of EPP event values to the RDAP equivalents.
*
* <p>Only has entries for optional events, either stated as optional in the RDAP Response Profile
* 15feb19, or not mentioned at all but thought to be useful anyway.
* section 2.3.2, or not mentioned at all but thought to be useful anyway.
*
* <p>Any required event should be added elsewhere, preferably without using HistoryEntries (so
* that we don't need to load HistoryEntries for "summary" responses).
@@ -292,8 +292,8 @@ public class RdapJsonFormatter {
* Creates a JSON object for a {@link Domain}.
*
* <p>NOTE that domain searches aren't in the spec yet - they're in the RFC 9082 that describes
* the query format, but they aren't in the RDAP Technical Implementation Guide 15feb19, meaning
* we don't have to implement them yet and the RDAP Response Profile doesn't apply to them.
* the query format, but they aren't in the RDAP Technical Implementation Guide, meaning we don't
* have to implement them yet and the RDAP Response Profile doesn't apply to them.
*
* <p>We're implementing domain searches anyway, BUT we won't have the response for searches
* conform to the RDAP Response Profile.
@@ -307,9 +307,9 @@ public class RdapJsonFormatter {
if (outputDataType != OutputDataType.FULL) {
builder.remarksBuilder().add(RdapIcannStandardInformation.SUMMARY_DATA_REMARK);
}
// RDAP Response Profile 15feb19 section 2.1 discusses the domain name.
// RDAP Response Profile section 2.1 discusses the domain name.
builder.setLdhName(domain.getDomainName());
// RDAP Response Profile 15feb19 section 2.2:
// RDAP Response Profile section 2.2:
// The domain handle MUST be the ROID
builder.setHandle(domain.getRepoId());
// If this is a summary (search result) - we'll return now. Since there's no requirement for
@@ -317,9 +317,9 @@ public class RdapJsonFormatter {
if (outputDataType == OutputDataType.SUMMARY) {
return builder.build();
}
// RDAP Response Profile 15feb19 section 2.3.1:
// RDAP Response Profile section 2.3.1:
// The domain object in the RDAP response MUST contain the following events:
// [registration, expiration, last update of RDAP database]
// [registration, expiration]
builder
.eventsBuilder()
.add(
@@ -333,14 +333,18 @@ public class RdapJsonFormatter {
.setEventAction(EventAction.EXPIRATION)
.setEventDate(domain.getRegistrationExpirationTime())
.build(),
// RDAP response profile section 1.5:
// The topmost object in the RDAP response MUST contain an event of "eventAction" type
// "last update of RDAP database" with a value equal to the timestamp when the RDAP
// database was last updated
Event.builder()
.setEventAction(EventAction.LAST_UPDATE_OF_RDAP_DATABASE)
.setEventDate(getRequestTime())
.build());
// RDAP Response Profile 15feb19 section 2.3.2 discusses optional events. We add some of those
// RDAP Response Profile section 2.3.2 discusses optional events. We add some of those
// here. We also add a few others we find interesting.
builder.eventsBuilder().addAll(makeOptionalEvents(domain));
// RDAP Response Profile 15feb19 section 2.4.1:
// RDAP Response Profile section 2.4.1:
// The domain object in the RDAP response MUST contain an entity with the Registrar role.
//
// See {@link createRdapRegistrarEntity} for details of section 2.4 conformance
@@ -378,8 +382,6 @@ public class RdapJsonFormatter {
// RDAP Response Profile 2.6.3, must have a notice about statuses. That is in {@link
// RdapIcannStandardInformation#domainBoilerplateNotices}
// Kick off the database loads of the nameservers that we will need, so it can load
// asynchronously while we load and process the contacts.
ImmutableSet<Host> loadedHosts =
replicaTm()
.transact(
@@ -424,12 +426,12 @@ public class RdapJsonFormatter {
}
// Add the nameservers to the data; the load was kicked off above for efficiency.
// RDAP Response Profile 2.9: we MUST have the nameservers
// RDAP Response Profile 2.8: we MUST have the nameservers
for (Host host : HOST_RESOURCE_ORDERING.immutableSortedCopy(loadedHosts)) {
builder.nameserversBuilder().add(createRdapNameserver(host, OutputDataType.INTERNAL));
}
// RDAP Response Profile 2.10 - MUST contain a secureDns member including at least a
// RDAP Response Profile 2.9 - MUST contain a secureDns member including at least a
// delegationSigned element. Other elements (e.g. dsData) MUST be included if the domain name is
// signed and the elements are stored in the Registry
//
@@ -454,13 +456,13 @@ public class RdapJsonFormatter {
builder.remarksBuilder().add(RdapIcannStandardInformation.SUMMARY_DATA_REMARK);
}
// We need the ldhName: RDAP Response Profile 2.9.1, 4.1
// We need the ldhName: RDAP Response Profile 2.8.1, 4.1
builder.setLdhName(host.getHostName());
// Handle is optional, but if given it MUST be the ROID.
// We will set it always as it's important as a "self link"
builder.setHandle(host.getRepoId());
// Status is optional for internal Nameservers - RDAP Response Profile 2.9.2
// Status is optional for internal Nameservers - RDAP Response Profile 2.8.2
// It isn't mentioned at all anywhere else. So we can just not put it at all?
//
// To be safe, we'll put it on the "FULL" version anyway
@@ -492,7 +494,7 @@ public class RdapJsonFormatter {
// For query responses - we MUST have all the ip addresses: RDAP Response Profile 4.2.
//
// However, it is optional for internal responses: RDAP Response Profile 2.9.2
// However, it is optional for internal responses: RDAP Response Profile 2.8.2
if (outputDataType != OutputDataType.INTERNAL) {
for (InetAddress inetAddress : host.getInetAddresses()) {
if (inetAddress instanceof Inet4Address) {
@@ -510,7 +512,7 @@ public class RdapJsonFormatter {
builder.entitiesBuilder().add(createRdapRegistrarEntity(registrar, OutputDataType.INTERNAL));
}
if (outputDataType != OutputDataType.INTERNAL) {
// Rdap Response Profile 4.4, must have "last update of RDAP database" response. But this is
// Rdap Response Profile 1.5, must have "last update of RDAP database" response. But this is
// only for direct query responses and not for internal objects.
builder.setLastUpdateOfRdapDatabaseEvent(
Event.builder()
@@ -535,10 +537,7 @@ public class RdapJsonFormatter {
Contact contact, Iterable<RdapEntity.Role> roles, OutputDataType outputDataType) {
RdapContactEntity.Builder contactBuilder = RdapContactEntity.builder();
// RDAP Response Profile 2.7.1, 2.7.3 - we MUST have the contacts. 2.7.4 discusses censoring of
// fields we don't want to show (as opposed to not having contacts at all) because of GDPR etc.
//
// 2.8 allows for unredacted output for authorized people.
// RDAP Response Profile 2.7.1, 2.7.3 - we MUST have the contacts
boolean isAuthorized =
rdapAuthorization.isAuthorizedForRegistrar(contact.getCurrentSponsorRegistrarId());
@@ -578,7 +577,7 @@ public class RdapJsonFormatter {
.add(RdapIcannStandardInformation.CONTACT_EMAIL_REDACTED_FOR_DOMAIN);
if (outputDataType != OutputDataType.INTERNAL) {
// Rdap Response Profile 2.7.6 must have "last update of RDAP database" response. But this is
// Rdap Response Profile 1.5 must have "last update of RDAP database" response. But this is
// only for direct query responses and not for internal objects. I'm not sure why it's in that
// section at all...
contactBuilder.setLastUpdateOfRdapDatabaseEvent(
@@ -656,8 +655,8 @@ public class RdapJsonFormatter {
* Creates a JSON object for a {@link Registrar}.
*
* <p>This object can be INTERNAL to the Domain and Nameserver responses, with requirements
* discussed in the RDAP Response Profile 15feb19 sections 2.4 (internal to Domain) and 4.3
* (internal to Namesever)
* discussed in the RDAP Response Profile sections 2.4 (internal to Domain) and 4.3 (internal to
* Namesever)
*
* @param registrar the registrar object from which the RDAP response
* @param outputDataType whether to generate FULL, SUMMARY, or INTERNAL data.
@@ -755,7 +754,6 @@ public class RdapJsonFormatter {
//
// Write the minimum, meaning only ABUSE for INTERNAL registrars, nothing for SUMMARY and
// everything for FULL.
//
if (outputDataType != OutputDataType.SUMMARY) {
ImmutableList<RdapContactEntity> registrarContacts =
registrar.getContactsFromReplica().stream()
@@ -776,7 +774,7 @@ public class RdapJsonFormatter {
builder.entitiesBuilder().addAll(registrarContacts);
}
// Rdap Response Profile 3.3, must have "last update of RDAP database" response. But this is
// Rdap Response Profile 1.5, must have "last update of RDAP database" response. But this is
// only for direct query responses and not for internal objects.
if (outputDataType != OutputDataType.INTERNAL) {
builder.setLastUpdateOfRdapDatabaseEvent(
@@ -934,8 +932,8 @@ public class RdapJsonFormatter {
* Creates the list of optional events to list in domain, nameserver, or contact replies.
*
* <p>Only has entries for optional events that won't be shown in "SUMMARY" versions of these
* objects. These are either stated as optional in the RDAP Response Profile 15feb19, or not
* mentioned at all but thought to be useful anyway.
* objects. These are either stated as optional in the RDAP Response Profile, or not mentioned at
* all but thought to be useful anyway.
*
* <p>Any required event should be added elsewhere, preferably without using HistoryEntries (so
* that we don't need to load HistoryEntries for "summary" responses).
@@ -974,7 +972,7 @@ public class RdapJsonFormatter {
lastChangeTime = modificationTime;
}
}
// RDAP Response Profile 15feb19 section 2.3.2.2:
// RDAP Response Profile section 2.3.2.2:
// The event of eventAction type last changed MUST be omitted if the domain name has not been
// updated since it was created
if (lastChangeTime.isAfter(creationTime)) {
@@ -991,7 +989,7 @@ public class RdapJsonFormatter {
/**
* Creates a vCard address entry: array of strings specifying the components of the address.
*
* <p>Rdap Response Profile 3.1.1: MUST contain the following fields: Street, City, Country Rdap
* <p>RDAP Response Profile 3.1.1: MUST contain the following fields: Street, City, Country Rdap
* Response Profile 3.1.2: optional fields: State/Province, Postal Code, Fax Number
*
* @see <a href="https://tools.ietf.org/html/rfc7095">RFC 7095: jCard: The JSON Format for

View File

@@ -33,7 +33,7 @@ import google.registry.request.auth.Auth;
import jakarta.inject.Inject;
import java.util.Optional;
/** RDAP (new WHOIS) action for nameserver requests. */
/** RDAP action for nameserver requests. */
@Action(
service = GaeService.PUBAPI,
path = "/rdap/nameserver/",
@@ -48,7 +48,7 @@ public class RdapNameserverAction extends RdapActionBase {
@Override
public RdapNameserver getJsonObjectForResource(String pathSearchString, boolean isHeadRequest) {
// RDAP Technical Implementation Guide 2.2.1 - we must support A-label (Punycode) and U-label
// RDAP Technical Implementation Guide 2.1.1 - we must support A-label (Punycode) and U-label
// (Unicode) formats. canonicalizeName will transform Unicode to Punycode so we support both.
pathSearchString = canonicalizeName(pathSearchString);
// The RDAP syntax is /rdap/nameserver/ns1.mydomain.com.

View File

@@ -47,9 +47,9 @@ import java.util.List;
import java.util.Optional;
/**
* RDAP (new WHOIS) action for nameserver search requests.
* RDAP action for nameserver search requests.
*
* <p>All commands and responses conform to the RDAP spec as defined in RFCs 7480 through 7485.
* <p>All commands and responses conform to the RDAP spec as defined in STD 95 and its RFCs.
*
* @see <a href="http://tools.ietf.org/html/rfc9082">RFC 9082: Registration Data Access Protocol
* (RDAP) Query Format</a>

View File

@@ -45,11 +45,7 @@ import java.util.Optional;
/** Object Classes defined in RFC 9083 section 5. */
final class RdapObjectClasses {
/**
* Temporary implementation of VCards.
*
* <p>Will create a better implementation soon.
*/
/** Rough implementation of VCards. */
@RestrictJsonNames({})
@AutoValue
public abstract static class Vcard implements Jsonable {
@@ -140,8 +136,8 @@ final class RdapObjectClasses {
public enum BoilerplateType {
DOMAIN(RdapIcannStandardInformation.DOMAIN_BOILERPLATE_NOTICES),
DOMAIN_BLOCKED_BY_BSA(RdapIcannStandardInformation.DOMAIN_BLOCKED_BY_BSA_BOILERPLATE_NOTICES),
NAMESERVER(RdapIcannStandardInformation.NAMESERVER_AND_ENTITY_BOILERPLATE_NOTICES),
ENTITY(RdapIcannStandardInformation.NAMESERVER_AND_ENTITY_BOILERPLATE_NOTICES),
NAMESERVER(ImmutableList.of()),
ENTITY(ImmutableList.of()),
OTHER(ImmutableList.of());
@SuppressWarnings("ImmutableEnumChecker") // immutable lists are, in fact, immutable
@@ -173,8 +169,8 @@ final class RdapObjectClasses {
* The Top Level JSON reply, Adds the required top-level boilerplate to a ReplyPayloadBase.
*
* <p>RFC 9083 specifies that the top-level object should include an entry indicating the
* conformance level. ICANN RDAP spec for 15feb19 mandates several additional entries, in sections
* 2.6.3, 2.11 of the Response Profile and 3.3, 3.5, of the Technical Implementation Guide.
* conformance level. The RDAP spec mandates several additional entries, in sections 2.6.3, 2.10
* of the Response Profile and 3.3, 3.5, of the Technical Implementation Guide.
*/
@AutoValue
@RestrictJsonNames({})
@@ -353,7 +349,7 @@ final class RdapObjectClasses {
*
* <p>Takes care of the name and unicode field.
*
* <p>See RDAP Response Profile 15feb19 sections 2.1 and 4.1.
* <p>See RDAP Response Profile sections 2.1 and 4.1.
*
* <p>Note the ldhName field is only required for non-IDN names or IDN names when the query was an
* A-label. It is optional for IDN names when the query was a U-label. Because we don't want to
@@ -471,7 +467,7 @@ final class RdapObjectClasses {
}
/**
* an integer representing the signature lifetime in seconds to be used when creating the RRSIG
* An integer representing the signature lifetime in seconds to be used when creating the RRSIG
* DS record in the parent zone [RFC5910].
*
* <p>Note that although it isn't given as optional in RFC 9083, in RFC5910 it's mentioned as

View File

@@ -44,7 +44,7 @@ import java.util.Objects;
import java.util.Optional;
/**
* Base RDAP (new WHOIS) action for domain, nameserver and entity search requests.
* Base RDAP action for domain, nameserver and entity search requests.
*
* @see <a href="https://tools.ietf.org/html/rfc9082">RFC 9082: Registration Data Access Protocol
* (RDAP) Query Format</a>
@@ -155,7 +155,6 @@ public abstract class RdapSearchActionBase extends RdapActionBase {
*/
<T extends EppResource> RdapResultSet<T> getMatchingResources(
CriteriaQueryBuilder<T> builder, boolean checkForVisibility, int querySizeLimit) {
replicaTm().assertInTransaction();
Optional<String> desiredRegistrar = getDesiredRegistrar();
if (desiredRegistrar.isPresent()) {
builder =

View File

@@ -39,8 +39,8 @@ public final class RdapSearchPattern {
/**
* Pattern for allowed LDH searches.
*
* <p>Based on RFC 9082 4.1. Must contains only alphanumeric plus dots and hyphens. A single
* whildcard asterix is allowed - but if exists must be the last character of a domain name label
* <p>Based on RFC 9082 4.1. Must contain only alphanumeric plus dots and hyphens. A single
* wildcard asterix is allowed - but if exists must be the last character of a domain name label
* (so exam* and exam*.com are allowed, but exam*le.com isn't allowd)
*
* <p>The prefix is in group(1), and the suffix without the dot (if it exists) is in group(4). If
@@ -123,7 +123,7 @@ public final class RdapSearchPattern {
* Creates a SearchPattern using the provided domain search pattern in LDH format.
*
* <p>The domain search pattern can have a single wildcard asterix that can match 0 or more
* charecters. If such an asterix exists - it must be at the end of a domain label.
* characters. If such an asterix exists - it must be at the end of a domain label.
*
* @param searchQuery the string containing the partial match pattern
* @throws UnprocessableEntityException if {@code pattern} does not meet the requirements of RFC
@@ -150,7 +150,7 @@ public final class RdapSearchPattern {
* Creates a SearchPattern using the provided domain search pattern in LDH or Unicode format.
*
* <p>The domain search pattern can have a single wildcard asterix that can match 0 or more
* charecters. If such an asterix exists - it must be at the end of a domain label.
* characters. If such an asterix exists - it must be at the end of a domain label.
*
* <p>In theory, according to RFC 9082 4.1 - we should make some checks about partial matching in
* unicode queries. We don't, but we might want to just disable partial matches for unicode inputs

View File

@@ -37,7 +37,7 @@ public final class RdapUtils {
*
* <p>Used for RDAP Technical Implementation Guide 2.4.2 - search of registrar by the fn element.
*
* <p>For convenience, we use case insensitive search.
* <p>For convenience, we use case-insensitive search.
*/
static Optional<Registrar> getRegistrarByName(String registrarName) {
return Streams.stream(Registrar.loadAllCached())

View File

@@ -16,6 +16,8 @@ package google.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.isNullOrEmpty;
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_OPTIONAL;
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_PROHIBITED;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.pricing.PricingEngineProxy.getPricesForDomainName;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
@@ -62,8 +64,8 @@ final class CreateDomainCommand extends CreateOrUpdateDomainCommand {
protected void initMutatingEppToolCommand() {
tm().transact(
() -> {
if (!FeatureFlag.isActiveNowOrElse(
FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_OPTIONAL, false)) {
if (!FeatureFlag.isActiveNow(MINIMUM_DATASET_CONTACTS_OPTIONAL)
&& !FeatureFlag.isActiveNow(MINIMUM_DATASET_CONTACTS_PROHIBITED)) {
checkArgumentNotNull(registrant, "Registrant must be specified");
checkArgument(!admins.isEmpty(), "At least one admin must be specified");
checkArgument(!techs.isEmpty(), "At least one tech must be specified");

View File

@@ -14,7 +14,6 @@
package google.registry.ui.server.console;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.Action.Method.GET;
import static org.joda.time.DateTimeZone.UTC;
@@ -24,6 +23,7 @@ import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.console.ConsolePermission;
import google.registry.model.console.ConsoleUpdateHistory;
import google.registry.model.console.User;
import google.registry.request.Action;
import google.registry.request.Action.GaeService;
@@ -34,7 +34,6 @@ import google.registry.util.Clock;
import jakarta.inject.Inject;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
import org.joda.time.DateTime;
@@ -100,27 +99,40 @@ public class ConsoleDumDownloadAction extends ConsoleApiAction {
consoleApiParams.response().setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return;
}
tm().transact(
() -> {
finishAndPersistConsoleUpdateHistory(
new ConsoleUpdateHistory.Builder()
.setType(ConsoleUpdateHistory.Type.DUM_DOWNLOAD)
.setDescription(registrarId));
});
consoleApiParams.response().setStatus(HttpServletResponse.SC_OK);
}
private void writeCsv(CSVPrinter printer) throws IOException {
String sql = SQL_TEMPLATE.replaceAll(":now", clock.nowUtc().toString());
// We deliberately don't want to use ImmutableList.copyOf because underlying list may contain
// large amount of records and that will degrade performance.
List<String> queryResult =
tm().transact(
() ->
tm().getEntityManager()
.createNativeQuery(sql)
.setParameter("registrarId", registrarId)
.setHint("org.hibernate.fetchSize", 1000)
.getResultList());
ImmutableList<String[]> formattedRecords =
queryResult.stream().map(r -> r.split(",")).collect(toImmutableList());
printer.printRecord(
ImmutableList.of("Domain Name", "Creation Time", "Expiration Time", "Domain Statuses"));
printer.printRecords(formattedRecords);
tm().transact(
() -> {
try (var resultStream =
tm().getEntityManager()
.createNativeQuery(sql, String.class)
.setParameter("registrarId", registrarId)
.setHint("org.hibernate.fetchSize", 1000)
.getResultStream()) {
resultStream.forEach(
row -> {
try {
printer.printRecord((Object[]) ((String) row).split(","));
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
});
}
}

View File

@@ -0,0 +1,116 @@
// Copyright 2025 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.
package google.registry.ui.server.console;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.Action.Method.GET;
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import com.google.common.base.Strings;
import google.registry.model.console.ConsolePermission;
import google.registry.model.console.ConsoleUpdateHistory;
import google.registry.model.console.User;
import google.registry.request.Action;
import google.registry.request.Action.GaeService;
import google.registry.request.Action.GkeService;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import jakarta.inject.Inject;
import java.util.List;
import java.util.Optional;
@Action(
service = GaeService.DEFAULT,
gkeService = GkeService.CONSOLE,
path = ConsoleHistoryDataAction.PATH,
method = {GET},
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
public class ConsoleHistoryDataAction extends ConsoleApiAction {
private static final String SQL_USER_HISTORY =
"""
SELECT * FROM "ConsoleUpdateHistory"
WHERE acting_user = :actingUser
""";
private static final String SQL_REGISTRAR_HISTORY =
"""
SELECT *
FROM "ConsoleUpdateHistory"
WHERE SPLIT_PART(description, '|', 1) = :registrarId;
""";
public static final String PATH = "/console-api/history";
private final String registrarId;
private final Optional<String> consoleUserEmail;
@Inject
public ConsoleHistoryDataAction(
ConsoleApiParams consoleApiParams,
@Parameter("registrarId") String registrarId,
@Parameter("consoleUserEmail") Optional<String> consoleUserEmail) {
super(consoleApiParams);
this.registrarId = registrarId;
this.consoleUserEmail = consoleUserEmail;
}
@Override
protected void getHandler(User user) {
if (this.consoleUserEmail.isPresent()) {
this.historyByUser(user, this.consoleUserEmail.get());
return;
}
this.historyByRegistrarId(user, this.registrarId);
}
private void historyByUser(User user, String consoleUserEmail) {
if (!user.getUserRoles().hasGlobalPermission(ConsolePermission.AUDIT_ACTIVITY_BY_USER)) {
setFailedResponse(
"User doesn't have a permission to check audit activity by user", SC_BAD_REQUEST);
return;
}
List<ConsoleUpdateHistory> queryResult =
tm().transact(
() ->
tm().getEntityManager()
.createNativeQuery(SQL_USER_HISTORY, ConsoleUpdateHistory.class)
.setParameter("actingUser", consoleUserEmail)
.setHint("org.hibernate.fetchSize", 1000)
.getResultList());
consoleApiParams.response().setPayload(consoleApiParams.gson().toJson(queryResult));
consoleApiParams.response().setStatus(SC_OK);
}
private void historyByRegistrarId(User user, String registrarId) {
checkArgument(!Strings.isNullOrEmpty(registrarId), "Empty registrarId param");
checkPermission(user, registrarId, ConsolePermission.AUDIT_ACTIVITY_BY_REGISTRAR);
List<ConsoleUpdateHistory> queryResult =
tm().transact(
() ->
tm().getEntityManager()
.createNativeQuery(SQL_REGISTRAR_HISTORY, ConsoleUpdateHistory.class)
.setParameter("registrarId", registrarId)
.setHint("org.hibernate.fetchSize", 1000)
.getResultList());
consoleApiParams.response().setPayload(consoleApiParams.gson().toJson(queryResult));
consoleApiParams.response().setStatus(SC_OK);
}
}

View File

@@ -36,6 +36,7 @@ import google.registry.ui.server.console.ConsoleEppPasswordAction.EppPasswordDat
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 google.registry.ui.server.console.PasswordResetRequestAction.PasswordResetRequestData;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Optional;
import org.joda.time.DateTime;
@@ -246,6 +247,12 @@ public final class ConsoleModule {
return extractRequiredParameter(req, "bulkDomainAction");
}
@Provides
@Parameter("resetRequestVerificationCode")
public static String provideResetRequestVerificationCode(HttpServletRequest req) {
return extractRequiredParameter(req, "resetRequestVerificationCode");
}
@Provides
@Parameter("eppPasswordChangeRequest")
public static Optional<EppPasswordData> provideEppPasswordChangeRequest(
@@ -273,4 +280,21 @@ public final class ConsoleModule {
Gson gson, @OptionalJsonPayload Optional<JsonElement> payload) {
return payload.map(e -> gson.fromJson(e, ConsoleRegistryLockPostInput.class));
}
@Provides
@Parameter("passwordResetRequestData")
public static PasswordResetRequestData providePasswordResetRequestData(
Gson gson, @OptionalJsonPayload Optional<JsonElement> payload) {
return payload
.map(e -> gson.fromJson(e, PasswordResetRequestData.class))
.orElseThrow(
() -> new IllegalArgumentException("Must provide password request reset data"));
}
@Provides
@Parameter("newPassword")
public static Optional<String> provideNewPassword(
Gson gson, @OptionalJsonPayload Optional<JsonElement> payload) {
return payload.map(e -> gson.fromJson(e, String.class));
}
}

View File

@@ -14,10 +14,12 @@
package google.registry.ui.server.console;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.Action.Method.GET;
import com.google.common.base.Ascii;
import com.google.gson.annotations.Expose;
import google.registry.model.console.ConsoleUpdateHistory;
import google.registry.model.console.User;
import google.registry.model.domain.RegistryLock;
import google.registry.request.Action;
@@ -64,6 +66,21 @@ public class ConsoleRegistryLockVerifyAction extends ConsoleApiAction {
RegistryLockVerificationResponse lockResponse =
new RegistryLockVerificationResponse(
Ascii.toLowerCase(action.toString()), lock.getDomainName(), lock.getRegistrarId());
tm().transact(
() -> {
finishAndPersistConsoleUpdateHistory(
new ConsoleUpdateHistory.Builder()
.setType(
action == RegistryLockAction.LOCKED
? ConsoleUpdateHistory.Type.REGISTRY_LOCK
: ConsoleUpdateHistory.Type.REGISTRY_UNLOCK)
.setDescription(
String.format(
"%s%s%s",
lock.getRegistrarId(),
ConsoleUpdateHistory.DESCRIPTION_SEPARATOR,
lockResponse)));
});
consoleApiParams.response().setPayload(consoleApiParams.gson().toJson(lockResponse));
consoleApiParams.response().setStatus(HttpServletResponse.SC_OK);
}

View File

@@ -35,6 +35,7 @@ import com.google.common.collect.ImmutableSet;
import com.google.gson.annotations.Expose;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.console.ConsolePermission;
import google.registry.model.console.ConsoleUpdateHistory;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User;
import google.registry.model.console.UserRoles;
@@ -118,6 +119,7 @@ public class ConsoleUsersAction extends ConsoleApiAction {
u ->
new UserData(
u.getEmailAddress(),
u.getRegistryLockEmailAddress().orElse(null),
u.getUserRoles().getRegistrarRoles().get(registrarId).toString(),
null))
.collect(Collectors.toList());
@@ -177,6 +179,12 @@ public class ConsoleUsersAction extends ConsoleApiAction {
tm().delete(key);
User.revokeIapPermission(email, maybeGroupEmailAddress, cloudTasksUtils, null, iamClient);
sendConfirmationEmail(registrarId, email, "Deleted user");
finishAndPersistConsoleUpdateHistory(
new ConsoleUpdateHistory.Builder()
.setType(ConsoleUpdateHistory.Type.USER_DELETE)
.setDescription(
String.format(
"%s%s%s", registrarId, ConsoleUpdateHistory.DESCRIPTION_SEPARATOR, email)));
}
consoleApiParams.response().setStatus(SC_OK);
@@ -230,7 +238,15 @@ public class ConsoleUsersAction extends ConsoleApiAction {
.setPayload(
consoleApiParams
.gson()
.toJson(new UserData(newEmail, ACCOUNT_MANAGER.toString(), newUser.getPassword())));
.toJson(
new UserData(
newEmail, null, ACCOUNT_MANAGER.toString(), newUser.getPassword())));
finishAndPersistConsoleUpdateHistory(
new ConsoleUpdateHistory.Builder()
.setType(ConsoleUpdateHistory.Type.USER_CREATE)
.setDescription(
String.format(
"%s%s%s", registrarId, ConsoleUpdateHistory.DESCRIPTION_SEPARATOR, newEmail)));
}
private void runUpdateInTransaction() {
@@ -245,6 +261,15 @@ public class ConsoleUsersAction extends ConsoleApiAction {
sendConfirmationEmail(registrarId, this.userData.get().emailAddress, "Updated user");
consoleApiParams.response().setStatus(SC_OK);
finishAndPersistConsoleUpdateHistory(
new ConsoleUpdateHistory.Builder()
.setType(ConsoleUpdateHistory.Type.USER_UPDATE)
.setDescription(
String.format(
"%s%s%s",
registrarId,
ConsoleUpdateHistory.DESCRIPTION_SEPARATOR,
this.userData.get().emailAddress)));
}
private boolean isModifyingRequestValid() {
@@ -323,5 +348,8 @@ public class ConsoleUsersAction extends ConsoleApiAction {
}
public record UserData(
@Expose String emailAddress, @Expose String role, @Expose @Nullable String password) {}
@Expose String emailAddress,
@Expose String registryLockEmailAddress,
@Expose String role,
@Expose @Nullable String password) {}
}

View File

@@ -0,0 +1,150 @@
// Copyright 2025 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.
package google.registry.ui.server.console;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.gson.annotations.Expose;
import google.registry.model.console.ConsolePermission;
import google.registry.model.console.PasswordResetRequest;
import google.registry.model.console.User;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.persistence.transaction.QueryComposer;
import google.registry.request.Action;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.util.EmailMessage;
import jakarta.inject.Inject;
import jakarta.mail.internet.AddressException;
import jakarta.mail.internet.InternetAddress;
import jakarta.servlet.http.HttpServletResponse;
import javax.annotation.Nullable;
@Action(
service = Action.GaeService.DEFAULT,
gkeService = Action.GkeService.CONSOLE,
path = PasswordResetRequestAction.PATH,
method = Action.Method.POST,
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
public class PasswordResetRequestAction extends ConsoleApiAction {
static final String PATH = "/console-api/password-reset-request";
static final String VERIFICATION_EMAIL_TEMPLATE =
"""
Please click the link below to perform the requested password reset. Note: this\
code will expire in one hour.
%s\
""";
private final PasswordResetRequestData passwordResetRequestData;
@Inject
public PasswordResetRequestAction(
ConsoleApiParams consoleApiParams,
@Parameter("passwordResetRequestData") PasswordResetRequestData passwordResetRequestData) {
super(consoleApiParams);
this.passwordResetRequestData = passwordResetRequestData;
}
@Override
protected void postHandler(User user) {
// Temporary flag when testing email sending etc
if (!user.getUserRoles().isAdmin()) {
setFailedResponse("", HttpServletResponse.SC_FORBIDDEN);
}
tm().transact(() -> performRequest(user));
consoleApiParams.response().setStatus(HttpServletResponse.SC_OK);
}
private void performRequest(User user) {
checkArgument(passwordResetRequestData.type != null, "Type cannot be null");
checkArgument(passwordResetRequestData.registrarId != null, "Registrar ID cannot be null");
PasswordResetRequest.Type type = passwordResetRequestData.type;
String registrarId = passwordResetRequestData.registrarId;
ConsolePermission requiredPermission;
String destinationEmail;
String emailSubject;
switch (type) {
case EPP:
requiredPermission = ConsolePermission.EDIT_REGISTRAR_DETAILS;
destinationEmail = getAdminPocEmail(registrarId);
emailSubject = "EPP password reset request";
break;
case REGISTRY_LOCK:
checkArgument(
passwordResetRequestData.registryLockEmail != null,
"Must provide registry lock email to reset");
requiredPermission = ConsolePermission.MANAGE_USERS;
destinationEmail = passwordResetRequestData.registryLockEmail;
checkUserExistsWithRegistryLockEmail(destinationEmail);
emailSubject = "Registry lock password reset request";
break;
default:
throw new IllegalArgumentException("Unknown type " + type);
}
checkPermission(user, registrarId, requiredPermission);
InternetAddress destinationAddress;
try {
destinationAddress = new InternetAddress(destinationEmail);
} catch (AddressException e) {
// Shouldn't happen
throw new RuntimeException(e);
}
PasswordResetRequest resetRequest =
new PasswordResetRequest.Builder()
.setRequester(user.getEmailAddress())
.setRegistrarId(registrarId)
.setType(type)
.setDestinationEmail(destinationEmail)
.build();
tm().put(resetRequest);
String verificationUrl =
String.format(
"https://%s/console/#/password-reset-verify?resetRequestVerificationCode=%s",
consoleApiParams.request().getServerName(), resetRequest.getVerificationCode());
String body = String.format(VERIFICATION_EMAIL_TEMPLATE, verificationUrl);
consoleApiParams
.sendEmailUtils()
.gmailClient
.sendEmail(EmailMessage.create(emailSubject, body, destinationAddress));
}
static User checkUserExistsWithRegistryLockEmail(String destinationEmail) {
return tm().createQueryComposer(User.class)
.where("registryLockEmailAddress", QueryComposer.Comparator.EQ, destinationEmail)
.first()
.orElseThrow(
() -> new IllegalArgumentException("Unknown user with lock email " + destinationEmail));
}
private String getAdminPocEmail(String registrarId) {
return RegistrarPoc.loadForRegistrar(registrarId).stream()
.filter(poc -> poc.getTypes().contains(RegistrarPoc.Type.ADMIN))
.map(RegistrarPoc::getEmailAddress)
.findAny()
.orElseThrow(() -> new IllegalStateException("No admin contacts found for " + registrarId));
}
public record PasswordResetRequestData(
@Expose PasswordResetRequest.Type type,
@Expose String registrarId,
@Expose @Nullable String registryLockEmail) {}
}

View File

@@ -0,0 +1,131 @@
// Copyright 2025 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.
package google.registry.ui.server.console;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.POST;
import static google.registry.ui.server.console.PasswordResetRequestAction.checkUserExistsWithRegistryLockEmail;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import google.registry.model.console.ConsolePermission;
import google.registry.model.console.PasswordResetRequest;
import google.registry.model.console.User;
import google.registry.model.registrar.Registrar;
import google.registry.persistence.VKey;
import google.registry.request.Action;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import jakarta.inject.Inject;
import jakarta.servlet.http.HttpServletResponse;
import java.util.Optional;
import org.joda.time.Duration;
@Action(
service = Action.GaeService.DEFAULT,
gkeService = Action.GkeService.CONSOLE,
path = PasswordResetVerifyAction.PATH,
method = {GET, POST},
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
public class PasswordResetVerifyAction extends ConsoleApiAction {
static final String PATH = "/console-api/password-reset-verify";
private final String verificationCode;
private final Optional<String> newPassword;
@Inject
public PasswordResetVerifyAction(
ConsoleApiParams consoleApiParams,
@Parameter("resetRequestVerificationCode") String verificationCode,
@Parameter("newPassword") Optional<String> newPassword) {
super(consoleApiParams);
this.verificationCode = verificationCode;
this.newPassword = newPassword;
}
@Override
protected void getHandler(User user) {
// Temporary flag when testing email sending etc
if (!user.getUserRoles().isAdmin()) {
setFailedResponse("", HttpServletResponse.SC_FORBIDDEN);
}
PasswordResetRequest request = tm().transact(() -> loadAndValidateResetRequest(user));
ImmutableMap<String, ?> result =
ImmutableMap.of("type", request.getType(), "registrarId", request.getRegistrarId());
consoleApiParams.response().setPayload(consoleApiParams.gson().toJson(result));
consoleApiParams.response().setStatus(HttpServletResponse.SC_OK);
}
@Override
protected void postHandler(User user) {
// Temporary flag when testing email sending etc
if (!user.getUserRoles().isAdmin()) {
setFailedResponse("", HttpServletResponse.SC_FORBIDDEN);
}
checkArgument(!Strings.isNullOrEmpty(newPassword.orElse(null)), "Password must be provided");
tm().transact(
() -> {
PasswordResetRequest request = loadAndValidateResetRequest(user);
switch (request.getType()) {
case EPP -> handleEppPasswordReset(request);
case REGISTRY_LOCK -> handleRegistryLockPasswordReset(request);
}
tm().put(request.asBuilder().setFulfillmentTime(tm().getTransactionTime()).build());
});
consoleApiParams.response().setStatus(HttpServletResponse.SC_OK);
}
private void handleEppPasswordReset(PasswordResetRequest request) {
Registrar registrar = Registrar.loadByRegistrarId(request.getRegistrarId()).get();
tm().put(registrar.asBuilder().setPassword(newPassword.get()).build());
}
private void handleRegistryLockPasswordReset(PasswordResetRequest request) {
User affectedUser = checkUserExistsWithRegistryLockEmail(request.getDestinationEmail());
tm().put(
affectedUser
.asBuilder()
.removeRegistryLockPassword()
.setRegistryLockPassword(newPassword.get())
.build());
}
private PasswordResetRequest loadAndValidateResetRequest(User user) {
PasswordResetRequest request =
tm().loadByKeyIfPresent(VKey.create(PasswordResetRequest.class, verificationCode))
.orElseThrow(this::createVerificationCodeException);
ConsolePermission requiredVerifyPermission =
switch (request.getType()) {
case EPP -> ConsolePermission.MANAGE_USERS;
case REGISTRY_LOCK -> ConsolePermission.REGISTRY_LOCK;
};
checkPermission(user, request.getRegistrarId(), requiredVerifyPermission);
if (request
.getRequestTime()
.plus(Duration.standardHours(1))
.isBefore(tm().getTransactionTime())) {
throw createVerificationCodeException();
}
return request;
}
private IllegalArgumentException createVerificationCodeException() {
return new IllegalArgumentException(
"Unknown, invalid, or expired verification code " + verificationCode);
}
}

View File

@@ -30,6 +30,7 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.flogger.FluentLogger;
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;
@@ -84,6 +85,7 @@ public class ContactAction extends ConsoleApiAction {
protected void deleteHandler(User user) {
updateContacts(
user,
"Deleted " + contact.get().getEmailAddress(),
(registrar, oldContacts) ->
oldContacts.stream()
.filter(
@@ -96,6 +98,7 @@ public class ContactAction extends ConsoleApiAction {
protected void postHandler(User user) {
updateContacts(
user,
"Created " + contact.get().getEmailAddress(),
(registrar, oldContacts) -> {
RegistrarPoc newContact = contact.get();
return ImmutableSet.<RegistrarPoc>builder()
@@ -121,6 +124,7 @@ public class ContactAction extends ConsoleApiAction {
protected void putHandler(User user) {
updateContacts(
user,
"Updated " + contact.get().getEmailAddress(),
(registrar, oldContacts) -> {
RegistrarPoc updatedContact = contact.get();
return oldContacts.stream()
@@ -146,6 +150,7 @@ public class ContactAction extends ConsoleApiAction {
private void updateContacts(
User user,
String historyDescription,
BiFunction<Registrar, ImmutableSet<RegistrarPoc>, ImmutableSet<RegistrarPoc>>
contactsUpdater) {
checkPermission(user, registrarId, ConsolePermission.EDIT_REGISTRAR_DETAILS);
@@ -176,6 +181,15 @@ public class ContactAction extends ConsoleApiAction {
tm().put(updatedRegistrar);
sendExternalUpdatesIfNecessary(
EmailInfo.create(registrar, updatedRegistrar, oldContacts, newContacts));
finishAndPersistConsoleUpdateHistory(
new ConsoleUpdateHistory.Builder()
.setType(ConsoleUpdateHistory.Type.REGISTRAR_CONTACTS_UPDATE)
.setDescription(
String.format(
"%s%s%s",
registrarId,
ConsoleUpdateHistory.DESCRIPTION_SEPARATOR,
historyDescription)));
});
consoleApiParams.response().setStatus(SC_OK);
}

View File

@@ -35,6 +35,7 @@ import google.registry.ui.server.console.ConsoleApiAction;
import google.registry.ui.server.console.ConsoleApiParams;
import jakarta.inject.Inject;
import java.util.Optional;
import java.util.StringJoiner;
/**
* Console action for editing fields on a registrar that are visible in WHOIS/RDAP.
@@ -82,19 +83,37 @@ public class RdapRegistrarFieldsAction extends ConsoleApiAction {
return;
}
Registrar newRegistrar =
savedRegistrar
.asBuilder()
.setLocalizedAddress(providedRegistrar.getLocalizedAddress())
.setPhoneNumber(providedRegistrar.getPhoneNumber())
.setFaxNumber(providedRegistrar.getFaxNumber())
.setEmailAddress(providedRegistrar.getEmailAddress())
.build();
StringJoiner updates = new StringJoiner(",");
var newRegistrarBuilder = savedRegistrar.asBuilder();
if (!providedRegistrar.getLocalizedAddress().equals(savedRegistrar.getLocalizedAddress())) {
newRegistrarBuilder.setLocalizedAddress(providedRegistrar.getLocalizedAddress());
updates.add("ADDRESS");
}
if (!providedRegistrar.getPhoneNumber().equals(savedRegistrar.getPhoneNumber())) {
newRegistrarBuilder.setPhoneNumber(providedRegistrar.getPhoneNumber());
updates.add("PHONE");
}
if (!providedRegistrar.getFaxNumber().equals(savedRegistrar.getPhoneNumber())) {
newRegistrarBuilder.setFaxNumber(providedRegistrar.getFaxNumber());
updates.add("FAX");
}
if (!providedRegistrar.getEmailAddress().equals(savedRegistrar.getEmailAddress())) {
newRegistrarBuilder.setEmailAddress(providedRegistrar.getEmailAddress());
updates.add("EMAIL");
}
var newRegistrar = newRegistrarBuilder.build();
tm().put(newRegistrar);
finishAndPersistConsoleUpdateHistory(
new ConsoleUpdateHistory.Builder()
.setType(ConsoleUpdateHistory.Type.REGISTRAR_UPDATE)
.setDescription(newRegistrar.getRegistrarId()));
.setDescription(
String.format(
"%s%s%s",
newRegistrar.getRegistrarId(),
ConsoleUpdateHistory.DESCRIPTION_SEPARATOR,
updates)));
sendExternalUpdatesIfNecessary(
EmailInfo.create(
savedRegistrar,

View File

@@ -39,6 +39,7 @@ import google.registry.ui.server.console.ConsoleApiAction;
import google.registry.ui.server.console.ConsoleApiParams;
import jakarta.inject.Inject;
import java.util.Optional;
import java.util.StringJoiner;
@Action(
service = GaeService.DEFAULT,
@@ -86,10 +87,15 @@ public class SecurityAction extends ConsoleApiAction {
private void setResponse(Registrar savedRegistrar) {
Registrar registrarParameter = registrar.get();
Registrar.Builder updatedRegistrarBuilder =
savedRegistrar
.asBuilder()
.setIpAddressAllowList(registrarParameter.getIpAddressAllowList());
Registrar.Builder updatedRegistrarBuilder = savedRegistrar.asBuilder();
StringJoiner updates = new StringJoiner(",");
if (!savedRegistrar
.getIpAddressAllowList()
.equals(registrarParameter.getIpAddressAllowList())) {
updatedRegistrarBuilder.setIpAddressAllowList(registrarParameter.getIpAddressAllowList());
updates.add("IP_CHANGE");
}
try {
if (!savedRegistrar
@@ -99,6 +105,7 @@ public class SecurityAction extends ConsoleApiAction {
String newClientCert = registrarParameter.getClientCertificate().get();
certificateChecker.validateCertificate(newClientCert);
updatedRegistrarBuilder.setClientCertificate(newClientCert, tm().getTransactionTime());
updates.add("PRIMARY_SSL_CERT_CHANGE");
}
}
if (!savedRegistrar
@@ -109,6 +116,7 @@ public class SecurityAction extends ConsoleApiAction {
certificateChecker.validateCertificate(newFailoverCert);
updatedRegistrarBuilder.setFailoverClientCertificate(
newFailoverCert, tm().getTransactionTime());
updates.add("FAILOVER_SSL_CERT_CHANGE");
}
}
} catch (InsecureCertificateException e) {
@@ -121,7 +129,9 @@ public class SecurityAction extends ConsoleApiAction {
finishAndPersistConsoleUpdateHistory(
new ConsoleUpdateHistory.Builder()
.setType(ConsoleUpdateHistory.Type.REGISTRAR_SECURITY_UPDATE)
.setDescription(registrarId));
.setDescription(
String.format(
"%s%s%s", registrarId, ConsoleUpdateHistory.DESCRIPTION_SEPARATOR, updates)));
sendExternalUpdatesIfNecessary(
EmailInfo.create(savedRegistrar, updatedRegistrar, ImmutableSet.of(), ImmutableSet.of()));

View File

@@ -27,7 +27,6 @@ import static google.registry.testing.DatabaseHelper.persistActiveHost;
import static google.registry.testing.DatabaseHelper.persistDeletedDomain;
import static google.registry.testing.DatabaseHelper.persistDomainAsDeleted;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.DatabaseHelper.persistSimpleResource;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static org.joda.time.DateTimeZone.UTC;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -287,7 +286,7 @@ class DeleteProberDataActionTest {
private static Set<ImmutableObject> persistDomainAndDescendants(String fqdn) {
Domain domain = persistDeletedDomain(fqdn, DELETION_TIME);
DomainHistory historyEntry =
persistSimpleResource(
persistResource(
new DomainHistory.Builder()
.setDomain(domain)
.setType(HistoryEntry.Type.DOMAIN_CREATE)
@@ -295,7 +294,7 @@ class DeleteProberDataActionTest {
.setModificationTime(DELETION_TIME.minusYears(3))
.build());
BillingEvent billingEvent =
persistSimpleResource(
persistResource(
new BillingEvent.Builder()
.setDomainHistory(historyEntry)
.setBillingTime(DELETION_TIME.plusYears(1))
@@ -307,7 +306,7 @@ class DeleteProberDataActionTest {
.setTargetId(fqdn)
.build());
PollMessage.OneTime pollMessage =
persistSimpleResource(
persistResource(
new PollMessage.OneTime.Builder()
.setHistoryEntry(historyEntry)
.setEventTime(DELETION_TIME)
@@ -315,7 +314,7 @@ class DeleteProberDataActionTest {
.setMsg("Domain registered")
.build());
GracePeriod gracePeriod =
persistSimpleResource(
persistResource(
GracePeriod.create(
ADD,
domain.getRepoId(),

View File

@@ -18,7 +18,7 @@ import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.JpaTransactionManagerExtension.makeRegistrar1;
import static google.registry.testing.DatabaseHelper.loadByEntity;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.DatabaseHelper.persistSimpleResources;
import static google.registry.testing.DatabaseHelper.persistResources;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.apache.http.HttpStatus.SC_OK;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -62,7 +62,8 @@ class SendExpiringCertificateNotificationEmailActionTest {
Kind update your account using the following steps:
1. Navigate to support and login using your %4$s@registry.example credentials.
2. Click Settings -> Privacy on the top left corner.
3. Click Edit and enter certificate string. 3. Click SaveRegards,Example Registry""";
3. Click Edit and enter certificate string. 3. Click SaveRegards,Example Registry\
""";
private static final String EXPIRATION_WARNING_EMAIL_SUBJECT_TEXT = "Expiration Warning Email";
@@ -223,7 +224,7 @@ class SendExpiringCertificateNotificationEmailActionTest {
.setVisibleInWhoisAsAdmin(true)
.setVisibleInWhoisAsTech(false)
.build());
persistSimpleResources(contacts);
persistResources(contacts);
RuntimeException thrown =
assertThrows(
RuntimeException.class,
@@ -548,7 +549,7 @@ class SendExpiringCertificateNotificationEmailActionTest {
.setTypes(ImmutableSet.of(RegistrarPoc.Type.ADMIN))
.setVisibleInWhoisAsTech(true)
.build());
persistSimpleResources(contacts);
persistResources(contacts);
assertThat(action.getEmailAddresses(registrar, Type.TECH))
.containsExactly(
new InternetAddress("will@example-registrar.tld"),
@@ -700,7 +701,7 @@ class SendExpiringCertificateNotificationEmailActionTest {
/** Returns persisted sample contacts with a customized contact email type. */
private static ImmutableList<RegistrarPoc> persistSampleContacts(
Registrar registrar, RegistrarPoc.Type emailType) {
return persistSimpleResources(
return persistResources(
ImmutableList.of(
new RegistrarPoc.Builder()
.setRegistrar(registrar)

View File

@@ -15,9 +15,10 @@
package google.registry.beam.common;
import static google.registry.persistence.transaction.JpaTransactionManagerExtension.makeRegistrar1;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.newContact;
import static google.registry.testing.DatabaseHelper.newTld;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.DatabaseHelper.persistResources;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
@@ -75,7 +76,7 @@ public class RegistryJpaReadTest {
@BeforeEach
void beforeEach() {
Registrar ofyRegistrar = JpaIntegrationTestExtension.makeRegistrar2();
insertInDb(ofyRegistrar);
persistResource(ofyRegistrar);
ImmutableList.Builder<Contact> builder = new ImmutableList.Builder<>();
@@ -84,7 +85,7 @@ public class RegistryJpaReadTest {
builder.add(contact);
}
contacts = builder.build();
insertInDb(contacts);
persistResources(contacts);
}
@Test
@@ -211,6 +212,6 @@ public class RegistryJpaReadTest {
null,
100L))
.build();
insertInDb(registry, registrar, contact, domain);
persistResources(registry, registrar, contact, domain);
}
}

View File

@@ -30,7 +30,6 @@ import static google.registry.rde.RdeResourceType.DOMAIN;
import static google.registry.rde.RdeResourceType.HOST;
import static google.registry.rde.RdeResourceType.REGISTRAR;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.insertSimpleResources;
import static google.registry.testing.DatabaseHelper.newDomain;
import static google.registry.testing.DatabaseHelper.persistActiveContact;
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
@@ -38,6 +37,7 @@ import static google.registry.testing.DatabaseHelper.persistActiveHost;
import static google.registry.testing.DatabaseHelper.persistEppResource;
import static google.registry.testing.DatabaseHelper.persistNewRegistrar;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.DatabaseHelper.persistResources;
import static google.registry.util.ResourceUtils.readResourceUtf8;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertThrows;
@@ -223,7 +223,7 @@ public class RdePipelineTest {
@BeforeEach
void beforeEach() throws Exception {
insertSimpleResources(ImmutableList.of(makeRegistrar1(), makeRegistrar2()));
persistResources(ImmutableList.of(makeRegistrar1(), makeRegistrar2()));
// Two real registrars have been created by loadInitialData(), named "New Registrar" and "The
// Registrar". Create one included registrar (external_monitoring) and two excluded ones.
@@ -403,7 +403,8 @@ public class RdePipelineTest {
<rdeDomain:crRr>TheRegistrar</rdeDomain:crRr>
<rdeDomain:crDate>1970-01-01T00:00:00Z</rdeDomain:crDate>
<rdeDomain:exDate>294247-01-10T04:00:54Z</rdeDomain:exDate>
</rdeDomain:domain>""");
</rdeDomain:domain>\
""");
}
if (kv.getKey().mode().equals(FULL)) {
// Contact fragments for hello.soy.
@@ -441,7 +442,8 @@ public class RdePipelineTest {
<rdeDomain:crRr>TheRegistrar</rdeDomain:crRr>
<rdeDomain:crDate>1970-01-01T00:00:00Z</rdeDomain:crDate>
<rdeDomain:exDate>294247-01-10T04:00:54Z</rdeDomain:exDate>
</rdeDomain:domain>""");
</rdeDomain:domain>\
""");
} else {
// Contact fragments for cat.fun.
assertThat(
@@ -484,7 +486,8 @@ public class RdePipelineTest {
<rdeDomain:crRr>TheRegistrar</rdeDomain:crRr>
<rdeDomain:crDate>1970-01-01T00:00:00Z</rdeDomain:crDate>
<rdeDomain:exDate>294247-01-10T04:00:54Z</rdeDomain:exDate>
</rdeDomain:domain>""");
</rdeDomain:domain>\
""");
}
});
return null;

View File

@@ -23,7 +23,7 @@ import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.loadByKey;
import static google.registry.testing.DatabaseHelper.persistNewRegistrar;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.DatabaseHelper.persistSimpleResources;
import static google.registry.testing.DatabaseHelper.persistResources;
import static org.joda.money.CurrencyUnit.JPY;
import static org.joda.money.CurrencyUnit.USD;
import static org.joda.time.DateTimeZone.UTC;
@@ -179,7 +179,7 @@ public class SyncRegistrarsSheetTest {
.build());
// Use registrar key for contacts' parent.
DateTime registrarCreationTime = persistResource(registrar).getCreationTime();
persistSimpleResources(contacts);
persistResources(contacts);
clock.advanceBy(standardMinutes(1));
newSyncRegistrarsSheet().run("foobar");

View File

@@ -15,6 +15,9 @@
package google.registry.flows.contact;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_PROHIBITED;
import static google.registry.model.common.FeatureFlag.FeatureStatus.ACTIVE;
import static google.registry.model.common.FeatureFlag.FeatureStatus.INACTIVE;
import static google.registry.testing.ContactSubject.assertAboutContacts;
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
import static google.registry.testing.DatabaseHelper.newContact;
@@ -22,15 +25,19 @@ import static google.registry.testing.DatabaseHelper.persistActiveContact;
import static google.registry.testing.DatabaseHelper.persistDeletedContact;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableSortedMap;
import google.registry.flows.EppException;
import google.registry.flows.FlowUtils.NotLoggedInException;
import google.registry.flows.ResourceFlowTestCase;
import google.registry.flows.contact.ContactFlowUtils.BadInternationalizedPostalInfoException;
import google.registry.flows.contact.ContactFlowUtils.DeclineContactDisclosureFieldDisallowedPolicyException;
import google.registry.flows.exceptions.ContactsProhibitedException;
import google.registry.flows.exceptions.ResourceAlreadyExistsForThisClientException;
import google.registry.flows.exceptions.ResourceCreateContentionException;
import google.registry.model.common.FeatureFlag;
import google.registry.model.contact.Contact;
import org.joda.time.DateTime;
import org.junit.jupiter.api.Test;
@@ -89,6 +96,18 @@ class ContactCreateFlowTest extends ResourceFlowTestCase<ContactCreateFlow, Cont
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testFailure_minimumDatasetPhase2_cannotCreateContacts() throws Exception {
persistResource(
new FeatureFlag.Builder()
.setFeatureName(MINIMUM_DATASET_CONTACTS_PROHIBITED)
.setStatusMap(
ImmutableSortedMap.of(START_OF_TIME, INACTIVE, clock.nowUtc().minusDays(5), ACTIVE))
.build());
EppException thrown = assertThrows(ContactsProhibitedException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testFailure_resourceContention() throws Exception {
String targetId = getUniqueIdFromCommand();

View File

@@ -15,6 +15,9 @@
package google.registry.flows.contact;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_PROHIBITED;
import static google.registry.model.common.FeatureFlag.FeatureStatus.ACTIVE;
import static google.registry.model.common.FeatureFlag.FeatureStatus.INACTIVE;
import static google.registry.testing.ContactSubject.assertAboutContacts;
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
import static google.registry.testing.DatabaseHelper.newContact;
@@ -22,10 +25,12 @@ import static google.registry.testing.DatabaseHelper.persistActiveContact;
import static google.registry.testing.DatabaseHelper.persistDeletedContact;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import google.registry.flows.EppException;
import google.registry.flows.FlowUtils.NotLoggedInException;
import google.registry.flows.ResourceFlowTestCase;
@@ -35,8 +40,10 @@ import google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException;
import google.registry.flows.ResourceFlowUtils.StatusNotClientSettableException;
import google.registry.flows.contact.ContactFlowUtils.BadInternationalizedPostalInfoException;
import google.registry.flows.contact.ContactFlowUtils.DeclineContactDisclosureFieldDisallowedPolicyException;
import google.registry.flows.exceptions.ContactsProhibitedException;
import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException;
import google.registry.flows.exceptions.ResourceStatusProhibitsOperationException;
import google.registry.model.common.FeatureFlag;
import google.registry.model.contact.Contact;
import google.registry.model.contact.ContactAddress;
import google.registry.model.contact.PostalInfo;
@@ -86,6 +93,18 @@ class ContactUpdateFlowTest extends ResourceFlowTestCase<ContactUpdateFlow, Cont
doSuccessfulTest();
}
@Test
void testFailure_minimumDatasetPhase2_cannotUpdateContacts() throws Exception {
persistResource(
new FeatureFlag.Builder()
.setFeatureName(MINIMUM_DATASET_CONTACTS_PROHIBITED)
.setStatusMap(
ImmutableSortedMap.of(START_OF_TIME, INACTIVE, clock.nowUtc().minusDays(5), ACTIVE))
.build());
EppException thrown = assertThrows(ContactsProhibitedException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testSuccess_updatingInternationalizedPostalInfoDeletesLocalized() throws Exception {
Contact contact =

View File

@@ -25,6 +25,7 @@ import static google.registry.model.billing.BillingBase.Flag.SUNRISE;
import static google.registry.model.billing.BillingBase.RenewalPriceBehavior.NONPREMIUM;
import static google.registry.model.billing.BillingBase.RenewalPriceBehavior.SPECIFIED;
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_OPTIONAL;
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_PROHIBITED;
import static google.registry.model.common.FeatureFlag.FeatureStatus.ACTIVE;
import static google.registry.model.common.FeatureFlag.FeatureStatus.INACTIVE;
import static google.registry.model.domain.fee.Fee.FEE_EXTENSION_URIS;
@@ -133,6 +134,7 @@ import google.registry.flows.domain.DomainFlowUtils.NameserversNotSpecifiedForTl
import google.registry.flows.domain.DomainFlowUtils.NotAuthorizedForTldException;
import google.registry.flows.domain.DomainFlowUtils.PremiumNameBlockedException;
import google.registry.flows.domain.DomainFlowUtils.RegistrantNotAllowedException;
import google.registry.flows.domain.DomainFlowUtils.RegistrantProhibitedException;
import google.registry.flows.domain.DomainFlowUtils.RegistrarMustBeActiveForThisOperationException;
import google.registry.flows.domain.DomainFlowUtils.TldDoesNotExistException;
import google.registry.flows.domain.DomainFlowUtils.TooManyDsRecordsException;
@@ -145,6 +147,7 @@ import google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTok
import google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForRegistrarException;
import google.registry.flows.domain.token.AllocationTokenFlowUtils.AlreadyRedeemedAllocationTokenException;
import google.registry.flows.domain.token.AllocationTokenFlowUtils.NonexistentAllocationTokenException;
import google.registry.flows.exceptions.ContactsProhibitedException;
import google.registry.flows.exceptions.OnlyToolCanPassMetadataException;
import google.registry.flows.exceptions.ResourceAlreadyExistsForThisClientException;
import google.registry.flows.exceptions.ResourceCreateContentionException;
@@ -247,12 +250,6 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
"badcrash,NAME_COLLISION"),
persistReservedList("global-list", "resdom,FULLY_BLOCKED"))
.build());
persistResource(
new FeatureFlag()
.asBuilder()
.setFeatureName(MINIMUM_DATASET_CONTACTS_OPTIONAL)
.setStatusMap(ImmutableSortedMap.of(START_OF_TIME, INACTIVE))
.build());
persistClaimsList(ImmutableMap.of("example-one", CLAIMS_KEY, "test-validate", CLAIMS_KEY));
}
@@ -2104,8 +2101,8 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
@Test
void testSuccess_minimumDatasetPhase1_missingRegistrant() throws Exception {
persistResource(
FeatureFlag.get(MINIMUM_DATASET_CONTACTS_OPTIONAL)
.asBuilder()
new FeatureFlag.Builder()
.setFeatureName(MINIMUM_DATASET_CONTACTS_OPTIONAL)
.setStatusMap(
ImmutableSortedMap.of(START_OF_TIME, INACTIVE, clock.nowUtc().minusDays(5), ACTIVE))
.build());
@@ -2115,6 +2112,20 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
loadFile("domain_create_response.xml", ImmutableMap.of("DOMAIN", "example.tld")));
}
@Test
void testFailure_minimumDatasetPhase2_noRegistrantButSomeOtherContactTypes() throws Exception {
persistResource(
new FeatureFlag.Builder()
.setFeatureName(MINIMUM_DATASET_CONTACTS_PROHIBITED)
.setStatusMap(
ImmutableSortedMap.of(START_OF_TIME, INACTIVE, clock.nowUtc().minusDays(5), ACTIVE))
.build());
setEppInput("domain_create_missing_registrant.xml");
persistContactsAndHosts();
EppException thrown = assertThrows(ContactsProhibitedException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testFailure_missingAdmin() {
setEppInput("domain_create_missing_admin.xml");
@@ -2126,8 +2137,8 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
@Test
void testSuccess_minimumDatasetPhase1_missingAdmin() throws Exception {
persistResource(
FeatureFlag.get(MINIMUM_DATASET_CONTACTS_OPTIONAL)
.asBuilder()
new FeatureFlag.Builder()
.setFeatureName(MINIMUM_DATASET_CONTACTS_OPTIONAL)
.setStatusMap(
ImmutableSortedMap.of(START_OF_TIME, INACTIVE, clock.nowUtc().minusDays(5), ACTIVE))
.build());
@@ -2137,6 +2148,20 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
loadFile("domain_create_response.xml", ImmutableMap.of("DOMAIN", "example.tld")));
}
@Test
void testFailure_minimumDatasetPhase2_registrantAndOtherContactsSent() throws Exception {
persistResource(
new FeatureFlag.Builder()
.setFeatureName(MINIMUM_DATASET_CONTACTS_PROHIBITED)
.setStatusMap(
ImmutableSortedMap.of(START_OF_TIME, INACTIVE, clock.nowUtc().minusDays(5), ACTIVE))
.build());
setEppInput("domain_create_missing_admin.xml");
persistContactsAndHosts();
EppException thrown = assertThrows(RegistrantProhibitedException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testFailure_missingTech() {
setEppInput("domain_create_missing_tech.xml");
@@ -2148,8 +2173,8 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
@Test
void testSuccess_minimumDatasetPhase1_missingTech() throws Exception {
persistResource(
FeatureFlag.get(MINIMUM_DATASET_CONTACTS_OPTIONAL)
.asBuilder()
new FeatureFlag.Builder()
.setFeatureName(MINIMUM_DATASET_CONTACTS_OPTIONAL)
.setStatusMap(
ImmutableSortedMap.of(START_OF_TIME, INACTIVE, clock.nowUtc().minusDays(5), ACTIVE))
.build());
@@ -2170,8 +2195,8 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
@Test
void testSuccess_minimumDatasetPhase1_missingNonRegistrantContacts() throws Exception {
persistResource(
FeatureFlag.get(MINIMUM_DATASET_CONTACTS_OPTIONAL)
.asBuilder()
new FeatureFlag.Builder()
.setFeatureName(MINIMUM_DATASET_CONTACTS_OPTIONAL)
.setStatusMap(
ImmutableSortedMap.of(START_OF_TIME, INACTIVE, clock.nowUtc().minusDays(5), ACTIVE))
.build());
@@ -2181,6 +2206,34 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
loadFile("domain_create_response.xml", ImmutableMap.of("DOMAIN", "example.tld")));
}
@Test
void testFailure_minimumDatasetPhase2_registrantNotPermitted() throws Exception {
persistResource(
new FeatureFlag.Builder()
.setFeatureName(MINIMUM_DATASET_CONTACTS_PROHIBITED)
.setStatusMap(
ImmutableSortedMap.of(START_OF_TIME, INACTIVE, clock.nowUtc().minusDays(5), ACTIVE))
.build());
setEppInput("domain_create_missing_non_registrant_contacts.xml");
persistContactsAndHosts();
EppException thrown = assertThrows(RegistrantProhibitedException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testSuccess_minimumDatasetPhase2_noContactsWhatsoever() throws Exception {
persistResource(
new FeatureFlag.Builder()
.setFeatureName(MINIMUM_DATASET_CONTACTS_PROHIBITED)
.setStatusMap(
ImmutableSortedMap.of(START_OF_TIME, INACTIVE, clock.nowUtc().minusDays(5), ACTIVE))
.build());
setEppInput("domain_create_no_contacts.xml");
persistContactsAndHosts();
runFlowAssertResponse(
loadFile("domain_create_response.xml", ImmutableMap.of("DOMAIN", "example.tld")));
}
@Test
void testFailure_badIdn() {
createTld("xn--q9jyb4c");

View File

@@ -150,53 +150,50 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
StatusValue... statusValues)
throws Exception {
Domain domain = DatabaseHelper.newDomain(getUniqueIdFromCommand());
tm().transact(
() -> {
try {
DomainHistory historyEntryDomainCreate =
new DomainHistory.Builder()
.setDomain(domain)
.setType(HistoryEntry.Type.DOMAIN_CREATE)
.setModificationTime(clock.nowUtc())
.setRegistrarId(domain.getCreationRegistrarId())
.build();
BillingRecurrence autorenewEvent =
new BillingRecurrence.Builder()
.setReason(Reason.RENEW)
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
.setTargetId(getUniqueIdFromCommand())
.setRegistrarId("TheRegistrar")
.setEventTime(expirationTime)
.setRecurrenceEndTime(END_OF_TIME)
.setDomainHistory(historyEntryDomainCreate)
.setRenewalPriceBehavior(renewalPriceBehavior)
.setRenewalPrice(renewalPrice)
.build();
PollMessage.Autorenew autorenewPollMessage =
new PollMessage.Autorenew.Builder()
.setTargetId(getUniqueIdFromCommand())
.setRegistrarId("TheRegistrar")
.setEventTime(expirationTime)
.setAutorenewEndTime(END_OF_TIME)
.setMsg("Domain was auto-renewed.")
.setHistoryEntry(historyEntryDomainCreate)
.build();
Domain newDomain =
domain
.asBuilder()
.setRegistrationExpirationTime(expirationTime)
.setStatusValues(ImmutableSet.copyOf(statusValues))
.setAutorenewBillingEvent(autorenewEvent.createVKey())
.setAutorenewPollMessage(autorenewPollMessage.createVKey())
.build();
persistResources(
ImmutableSet.of(
historyEntryDomainCreate, autorenewEvent,
autorenewPollMessage, newDomain));
} catch (Exception e) {
throw new RuntimeException("Error persisting domain", e);
}
});
try {
DomainHistory historyEntryDomainCreate =
new DomainHistory.Builder()
.setDomain(domain)
.setType(HistoryEntry.Type.DOMAIN_CREATE)
.setModificationTime(clock.nowUtc())
.setRegistrarId(domain.getCreationRegistrarId())
.build();
BillingRecurrence autorenewEvent =
new BillingRecurrence.Builder()
.setReason(Reason.RENEW)
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
.setTargetId(getUniqueIdFromCommand())
.setRegistrarId("TheRegistrar")
.setEventTime(expirationTime)
.setRecurrenceEndTime(END_OF_TIME)
.setDomainHistory(historyEntryDomainCreate)
.setRenewalPriceBehavior(renewalPriceBehavior)
.setRenewalPrice(renewalPrice)
.build();
PollMessage.Autorenew autorenewPollMessage =
new PollMessage.Autorenew.Builder()
.setTargetId(getUniqueIdFromCommand())
.setRegistrarId("TheRegistrar")
.setEventTime(expirationTime)
.setAutorenewEndTime(END_OF_TIME)
.setMsg("Domain was auto-renewed.")
.setHistoryEntry(historyEntryDomainCreate)
.build();
Domain newDomain =
domain
.asBuilder()
.setRegistrationExpirationTime(expirationTime)
.setStatusValues(ImmutableSet.copyOf(statusValues))
.setAutorenewBillingEvent(autorenewEvent.createVKey())
.setAutorenewPollMessage(autorenewPollMessage.createVKey())
.build();
persistResources(
ImmutableSet.of(
historyEntryDomainCreate, autorenewEvent,
autorenewPollMessage, newDomain));
} catch (Exception e) {
throw new RuntimeException("Error persisting domain", e);
}
clock.advanceOneMilli();
}

View File

@@ -20,6 +20,7 @@ import static com.google.common.io.BaseEncoding.base16;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_OPTIONAL;
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_PROHIBITED;
import static google.registry.model.common.FeatureFlag.FeatureStatus.ACTIVE;
import static google.registry.model.common.FeatureFlag.FeatureStatus.INACTIVE;
import static google.registry.model.eppcommon.StatusValue.CLIENT_DELETE_PROHIBITED;
@@ -89,10 +90,12 @@ import google.registry.flows.domain.DomainFlowUtils.NameserversNotAllowedForTldE
import google.registry.flows.domain.DomainFlowUtils.NameserversNotSpecifiedForTldWithNameserverAllowListException;
import google.registry.flows.domain.DomainFlowUtils.NotAuthorizedForTldException;
import google.registry.flows.domain.DomainFlowUtils.RegistrantNotAllowedException;
import google.registry.flows.domain.DomainFlowUtils.RegistrantProhibitedException;
import google.registry.flows.domain.DomainFlowUtils.SecDnsAllUsageException;
import google.registry.flows.domain.DomainFlowUtils.TooManyDsRecordsException;
import google.registry.flows.domain.DomainFlowUtils.TooManyNameserversException;
import google.registry.flows.domain.DomainFlowUtils.UrgentAttributeNotSupportedException;
import google.registry.flows.exceptions.ContactsProhibitedException;
import google.registry.flows.exceptions.OnlyToolCanPassMetadataException;
import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException;
import google.registry.flows.exceptions.ResourceStatusProhibitsOperationException;
@@ -146,12 +149,6 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
createTld("tld");
// Note that "domain_update.xml" tests adding and removing the same contact type.
setEppInput("domain_update.xml");
persistResource(
new FeatureFlag()
.asBuilder()
.setFeatureName(MINIMUM_DATASET_CONTACTS_OPTIONAL)
.setStatusMap(ImmutableSortedMap.of(START_OF_TIME, INACTIVE))
.build());
}
private void persistReferencedEntities() {
@@ -336,8 +333,8 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
@Test
void testSuccess_minimumDatasetPhase1_emptyRegistrant() throws Exception {
persistResource(
FeatureFlag.get(MINIMUM_DATASET_CONTACTS_OPTIONAL)
.asBuilder()
new FeatureFlag.Builder()
.setFeatureName(MINIMUM_DATASET_CONTACTS_OPTIONAL)
.setStatusMap(
ImmutableSortedMap.of(START_OF_TIME, INACTIVE, clock.nowUtc().minusDays(5), ACTIVE))
.build());
@@ -348,6 +345,24 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
assertThat(reloadResourceByForeignKey().getRegistrant()).isEmpty();
}
@Test
void testFailure_minimumDatasetPhase2_nonRegistrantContactsStillExist() throws Exception {
persistResource(
new FeatureFlag.Builder()
.setFeatureName(MINIMUM_DATASET_CONTACTS_PROHIBITED)
.setStatusMap(
ImmutableSortedMap.of(START_OF_TIME, INACTIVE, clock.nowUtc().minusDays(5), ACTIVE))
.build());
setEppInput("domain_update_empty_registrant.xml");
persistReferencedEntities();
persistDomain();
// Fails because after the update the domain would still have some contacts on it even though
// the registrant has been removed.
ContactsProhibitedException thrown =
assertThrows(ContactsProhibitedException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
private void modifyDomainToHave13Nameservers() throws Exception {
ImmutableSet.Builder<VKey<Host>> nameservers = new ImmutableSet.Builder<>();
for (int i = 1; i < 15; i++) {
@@ -1540,8 +1555,8 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
@Test
void testSuccess_minimumDatasetPhase1_removeAdmin() throws Exception {
persistResource(
FeatureFlag.get(MINIMUM_DATASET_CONTACTS_OPTIONAL)
.asBuilder()
new FeatureFlag.Builder()
.setFeatureName(MINIMUM_DATASET_CONTACTS_OPTIONAL)
.setStatusMap(
ImmutableSortedMap.of(START_OF_TIME, INACTIVE, clock.nowUtc().minusDays(5), ACTIVE))
.build());
@@ -1558,6 +1573,29 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
runFlowAssertResponse(loadFile("generic_success_response.xml"));
}
@Test
void testFailure_minimumDatasetPhase2_registrantStillExists() throws Exception {
persistResource(
new FeatureFlag.Builder()
.setFeatureName(MINIMUM_DATASET_CONTACTS_PROHIBITED)
.setStatusMap(
ImmutableSortedMap.of(START_OF_TIME, INACTIVE, clock.nowUtc().minusDays(5), ACTIVE))
.build());
setEppInput("domain_update_remove_admin.xml");
persistReferencedEntities();
persistResource(
DatabaseHelper.newDomain(getUniqueIdFromCommand())
.asBuilder()
.setContacts(
ImmutableSet.of(
DesignatedContact.create(Type.ADMIN, sh8013Contact.createVKey()),
DesignatedContact.create(Type.TECH, sh8013Contact.createVKey())))
.build());
RegistrantProhibitedException thrown =
assertThrows(RegistrantProhibitedException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testFailure_removeTech() throws Exception {
setEppInput("domain_update_remove_tech.xml");
@@ -1577,8 +1615,8 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
@Test
void testSuccess_minimumDatasetPhase1_removeTech() throws Exception {
persistResource(
FeatureFlag.get(MINIMUM_DATASET_CONTACTS_OPTIONAL)
.asBuilder()
new FeatureFlag.Builder()
.setFeatureName(MINIMUM_DATASET_CONTACTS_OPTIONAL)
.setStatusMap(
ImmutableSortedMap.of(START_OF_TIME, INACTIVE, clock.nowUtc().minusDays(5), ACTIVE))
.build());
@@ -1595,6 +1633,30 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
runFlowAssertResponse(loadFile("generic_success_response.xml"));
}
@Test
void testSuccess_minimumDatasetPhase2_removeAllContacts() throws Exception {
persistResource(
new FeatureFlag.Builder()
.setFeatureName(MINIMUM_DATASET_CONTACTS_PROHIBITED)
.setStatusMap(
ImmutableSortedMap.of(START_OF_TIME, INACTIVE, clock.nowUtc().minusDays(5), ACTIVE))
.build());
setEppInput("domain_update_remove_all_contacts.xml");
persistReferencedEntities();
persistResource(
DatabaseHelper.newDomain(getUniqueIdFromCommand())
.asBuilder()
.setContacts(
ImmutableSet.of(
DesignatedContact.create(Type.ADMIN, sh8013Contact.createVKey()),
DesignatedContact.create(Type.TECH, sh8013Contact.createVKey())))
.build());
runFlowAssertResponse(loadFile("generic_success_response.xml"));
Domain updatedDomain = reloadResourceByForeignKey();
assertThat(updatedDomain.getRegistrant()).isEmpty();
assertThat(updatedDomain.getContacts()).isEmpty();
}
@Test
void testFailure_addPendingDeleteContact() throws Exception {
persistReferencedEntities();

View File

@@ -24,7 +24,7 @@ import static google.registry.testing.CertificateSamples.SAMPLE_CERT_HASH;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.loadByKeyIfPresent;
import static google.registry.testing.DatabaseHelper.persistPremiumList;
import static google.registry.testing.DatabaseHelper.persistSimpleResource;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.joda.money.CurrencyUnit.USD;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -275,7 +275,7 @@ public final class OteAccountBuilderTest {
@Test
void testCreateOteEntities_registrarExists_failsWhenNotReplaceExisting() {
persistSimpleResource(makeRegistrar1().asBuilder().setRegistrarId("myclientid-1").build());
persistResource(makeRegistrar1().asBuilder().setRegistrarId("myclientid-1").build());
OteAccountBuilder oteSetupHelper = OteAccountBuilder.forRegistrarId("myclientid");
@@ -301,7 +301,7 @@ public final class OteAccountBuilderTest {
@Test
void testCreateOteEntities_entitiesExist_succeedsWhenReplaceExisting() {
persistSimpleResource(makeRegistrar1().asBuilder().setRegistrarId("myclientid-1").build());
persistResource(makeRegistrar1().asBuilder().setRegistrarId("myclientid-1").build());
// we intentionally create the -ga TLD with the wrong state, to make sure it's overwritten.
createTld("myclientid-ga", START_DATE_SUNRISE);

View File

@@ -16,7 +16,7 @@ package google.registry.model.common;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.loadAllOf;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -41,7 +41,7 @@ public class DnsRefreshRequestTest extends EntityTestCase {
void testPersistence() {
assertThat(request.getLastProcessTime()).isEqualTo(START_OF_TIME);
fakeClock.advanceOneMilli();
insertInDb(request);
tm().transact(() -> tm().insert(request));
fakeClock.advanceOneMilli();
ImmutableList<DnsRefreshRequest> requests = loadAllOf(DnsRefreshRequest.class);
assertThat(requests.size()).isEqualTo(1);

View File

@@ -56,6 +56,7 @@ public class FeatureFlagTest extends EntityTestCase {
persistResource(featureFlag);
FeatureFlag flagFromDb = loadByEntity(featureFlag);
assertThat(featureFlag).isEqualTo(flagFromDb);
assertThat(featureFlag.getFeatureName()).isEqualTo(TEST_FEATURE);
}
@Test
@@ -217,23 +218,12 @@ public class FeatureFlagTest extends EntityTestCase {
.build());
tm().transact(
() -> {
assertThat(FeatureFlag.isActiveNowOrElse(TEST_FEATURE, false)).isFalse();
assertThat(FeatureFlag.isActiveNowOrElse(TEST_FEATURE, true)).isFalse();
assertThat(FeatureFlag.isActiveNow(TEST_FEATURE)).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();
assertThat(FeatureFlag.isActiveNow(TEST_FEATURE)).isTrue();
});
}
}

View File

@@ -20,7 +20,6 @@ import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableO
import static google.registry.testing.ContactSubject.assertAboutContacts;
import static google.registry.testing.DatabaseHelper.cloneAndSetAutoTimestamps;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.loadByEntity;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.SqlHelper.assertThrowForeignKeyViolation;
@@ -132,7 +131,7 @@ public class ContactTest extends EntityTestCase {
void testCloudSqlPersistence_failWhenViolateForeignKeyConstraint() {
assertThrowForeignKeyViolation(
() ->
insertInDb(
persistResource(
originalContact
.asBuilder()
.setRepoId("2-FOOBAR")

View File

@@ -20,11 +20,10 @@ import static google.registry.model.domain.token.AllocationToken.TokenStatus.NOT
import static google.registry.model.domain.token.AllocationToken.TokenType.BULK_PRICING;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.loadByEntity;
import static google.registry.testing.DatabaseHelper.loadByKey;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.DatabaseHelper.updateInDb;
import static google.registry.testing.DatabaseHelper.persistResources;
import static google.registry.testing.SqlHelper.assertThrowForeignKeyViolation;
import static google.registry.testing.SqlHelper.saveRegistrar;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
@@ -169,13 +168,13 @@ public class DomainSqlTest {
@Test
void testHostForeignKeyConstraints() {
// Persist the domain without the associated host object.
assertThrowForeignKeyViolation(() -> insertInDb(contact, contact2, domain));
assertThrowForeignKeyViolation(() -> persistResources(contact, contact2, domain));
}
@Test
void testContactForeignKeyConstraints() {
// Persist the domain without the associated contact objects.
assertThrowForeignKeyViolation(() -> insertInDb(domain, host));
assertThrowForeignKeyViolation(() -> persistResources(domain, host));
}
@Test
@@ -358,7 +357,7 @@ public class DomainSqlTest {
@Test
void testSerializable() {
createTld("com");
insertInDb(contact, contact2, domain, host);
persistResources(contact, contact2, domain, host);
Domain persisted = tm().transact(() -> tm().loadByEntity(domain));
assertThat(SerializeUtils.serializeDeserialize(persisted)).isEqualTo(persisted);
}
@@ -366,9 +365,9 @@ public class DomainSqlTest {
@Test
void testUpdates() {
createTld("com");
insertInDb(contact, contact2, domain, host);
persistResources(contact, contact2, domain, host);
domain = domain.asBuilder().setNameservers(ImmutableSet.of()).build();
updateInDb(domain);
persistResource(domain);
assertAboutImmutableObjects()
.that(loadByEntity(domain))
.isEqualExceptFields(domain, "updateTimestamp", "creationTime");
@@ -385,7 +384,7 @@ public class DomainSqlTest {
private void persistDomain() {
createTld("com");
insertInDb(contact, contact2, domain, host);
persistResources(contact, contact2, domain, host);
}
private <T> VKey<T> createKey(Class<T> clazz, String key) {
@@ -414,24 +413,19 @@ public class DomainSqlTest {
Domain persisted = loadByKey(domain.createVKey());
DateTime originalUpdateTime = persisted.getUpdateTimestamp().getTimestamp();
fakeClock.advanceOneMilli();
DateTime transactionTime =
tm().transact(
() -> {
Host host2 =
new Host.Builder()
.setRepoId("host2")
.setHostName("ns2.example.com")
.setCreationRegistrarId("registrar1")
.setPersistedCurrentSponsorRegistrarId("registrar2")
.build();
insertInDb(host2);
Host host2 =
new Host.Builder()
.setRepoId("host2")
.setHostName("ns2.example.com")
.setCreationRegistrarId("registrar1")
.setPersistedCurrentSponsorRegistrarId("registrar2")
.build();
persistResource(host2);
domain = persisted.asBuilder().addNameserver(host2.createVKey()).build();
updateInDb(domain);
return tm().getTransactionTime();
});
persistResource(domain);
domain = loadByKey(domain.createVKey());
assertThat(domain.getUpdateTimestamp().getTimestamp()).isEqualTo(transactionTime);
assertThat(domain.getUpdateTimestamp().getTimestamp()).isNotEqualTo(originalUpdateTime);
assertThat(domain.getUpdateTimestamp().getTimestamp())
.isEqualTo(originalUpdateTime.plusMillis(1));
}
@Test
@@ -440,20 +434,14 @@ public class DomainSqlTest {
Domain persisted = loadByKey(domain.createVKey());
DateTime originalUpdateTime = persisted.getUpdateTimestamp().getTimestamp();
fakeClock.advanceOneMilli();
DateTime transactionTime =
tm().transact(
() -> {
domain =
persisted
.asBuilder()
.setDsData(
ImmutableSet.of(DomainDsData.create(1, 2, 3, new byte[] {0, 1, 2})))
.build();
updateInDb(domain);
return tm().getTransactionTime();
});
domain =
persisted
.asBuilder()
.setDsData(ImmutableSet.of(DomainDsData.create(1, 2, 3, new byte[] {0, 1, 2})))
.build();
persistResource(domain);
domain = loadByKey(domain.createVKey());
assertThat(domain.getUpdateTimestamp().getTimestamp()).isEqualTo(transactionTime);
assertThat(domain.getUpdateTimestamp().getTimestamp()).isNotEqualTo(originalUpdateTime);
assertThat(domain.getUpdateTimestamp().getTimestamp())
.isEqualTo(originalUpdateTime.plusMillis(1));
}
}

View File

@@ -23,12 +23,12 @@ import static google.registry.model.domain.token.AllocationToken.TokenType.BULK_
import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE;
import static google.registry.testing.DatabaseHelper.cloneAndSetAutoTimestamps;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.newHost;
import static google.registry.testing.DatabaseHelper.persistActiveContact;
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
import static google.registry.testing.DatabaseHelper.persistActiveHost;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.DatabaseHelper.persistResources;
import static google.registry.testing.DomainSubject.assertAboutDomains;
import static google.registry.testing.SqlHelper.saveRegistrar;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
@@ -162,7 +162,7 @@ public class DomainTest {
.setRecurrenceEndTime(END_OF_TIME)
.setDomainHistory(historyEntry)
.build();
insertInDb(historyEntry, billingEventBill, billingRecurrence);
persistResources(historyEntry, billingEventBill, billingRecurrence);
recurrenceBillKey = billingRecurrence.createVKey();
VKey<PollMessage.Autorenew> autorenewPollKey = VKey.create(PollMessage.Autorenew.class, 3L);
VKey<PollMessage.OneTime> onetimePollKey = VKey.create(PollMessage.OneTime.class, 1L);

View File

@@ -16,8 +16,8 @@ package google.registry.model.eppcommon;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.loadByEntity;
import static google.registry.testing.DatabaseHelper.persistResource;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableList;
@@ -50,25 +50,25 @@ class AddressTest {
private static final String ENTITY_XML =
"""
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<testEntity>
<address>
<street>123 W 14th St</street>
<street>8th Fl</street>
<street>Rm 8</street>
<city>New York</city>
<sp>NY</sp>
<pc>10011</pc>
<cc>US</cc>
</address>
</testEntity>
""";
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<testEntity>
<address>
<street>123 W 14th St</street>
<street>8th Fl</street>
<street>Rm 8</street>
<city>New York</city>
<sp>NY</sp>
<pc>10011</pc>
<cc>US</cc>
</address>
</testEntity>
""";
private TestAddress address = createAddress("123 W 14th St", "8th Fl", "Rm 8");
private TestEntity entity = new TestEntity(1L, address);
private static TestEntity saveAndLoad(TestEntity entity) {
insertInDb(entity);
persistResource(entity);
return loadByEntity(entity);
}

View File

@@ -17,9 +17,9 @@ package google.registry.model.history;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.loadByEntity;
import static google.registry.testing.DatabaseHelper.newContactWithRoid;
import static google.registry.testing.DatabaseHelper.persistResource;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ImmutableList;
@@ -45,10 +45,10 @@ public class ContactHistoryTest extends EntityTestCase {
@Test
void testPersistence() {
Contact contact = newContactWithRoid("contactId", "contact1");
insertInDb(contact);
persistResource(contact);
Contact contactFromDb = loadByEntity(contact);
ContactHistory contactHistory = createContactHistory(contactFromDb);
insertInDb(contactHistory);
persistResource(contactHistory);
tm().transact(
() -> {
ContactHistory fromDatabase = tm().loadByKey(contactHistory.createVKey());
@@ -60,10 +60,10 @@ public class ContactHistoryTest extends EntityTestCase {
@Test
void testSerializable() {
Contact contact = newContactWithRoid("contactId", "contact1");
insertInDb(contact);
persistResource(contact);
Contact contactFromDb = loadByEntity(contact);
ContactHistory contactHistory = createContactHistory(contactFromDb);
insertInDb(contactHistory);
persistResource(contactHistory);
ContactHistory fromDatabase = tm().transact(() -> tm().loadByKey(contactHistory.createVKey()));
assertThat(SerializeUtils.serializeDeserialize(fromDatabase)).isEqualTo(fromDatabase);
}

View File

@@ -18,10 +18,10 @@ import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.newContactWithRoid;
import static google.registry.testing.DatabaseHelper.newDomain;
import static google.registry.testing.DatabaseHelper.newHostWithRoid;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -60,7 +60,7 @@ public class DomainHistoryTest extends EntityTestCase {
void testPersistence() {
Domain domain = addGracePeriodForSql(createDomainWithContactsAndHosts());
DomainHistory domainHistory = createDomainHistory(domain);
insertInDb(domainHistory);
persistResource(domainHistory);
tm().transact(
() -> {
@@ -74,7 +74,7 @@ public class DomainHistoryTest extends EntityTestCase {
void testSerializable() {
Domain domain = addGracePeriodForSql(createDomainWithContactsAndHosts());
DomainHistory domainHistory = createDomainHistory(domain);
insertInDb(domainHistory);
persistResource(domainHistory);
DomainHistory fromDatabase = tm().transact(() -> tm().loadByKey(domainHistory.createVKey()));
assertThat(SerializeUtils.serializeDeserialize(fromDatabase)).isEqualTo(fromDatabase);
}
@@ -96,7 +96,7 @@ public class DomainHistoryTest extends EntityTestCase {
.setNameservers(host.createVKey())
.setDsData(ImmutableSet.of(DomainDsData.create(1, 2, 3, new byte[] {0, 1, 2})))
.build();
insertInDb(domain);
persistResource(domain);
return domain;
}

View File

@@ -17,9 +17,9 @@ package google.registry.model.history;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.loadByEntity;
import static google.registry.testing.DatabaseHelper.newHostWithRoid;
import static google.registry.testing.DatabaseHelper.persistResource;
import static java.nio.charset.StandardCharsets.UTF_8;
import google.registry.model.EntityTestCase;
@@ -41,10 +41,10 @@ public class HostHistoryTest extends EntityTestCase {
@Test
void testPersistence() {
Host host = newHostWithRoid("ns1.example.com", "host1");
insertInDb(host);
persistResource(host);
Host hostFromDb = loadByEntity(host);
HostHistory hostHistory = createHostHistory(hostFromDb);
insertInDb(hostHistory);
persistResource(hostHistory);
tm().transact(
() -> {
HostHistory fromDatabase = tm().loadByKey(hostHistory.createVKey());
@@ -56,10 +56,10 @@ public class HostHistoryTest extends EntityTestCase {
@Test
void testSerializable() {
Host host = newHostWithRoid("ns1.example.com", "host1");
insertInDb(host);
persistResource(host);
Host hostFromDb = loadByEntity(host);
HostHistory hostHistory = createHostHistory(hostFromDb);
insertInDb(hostHistory);
persistResource(hostHistory);
HostHistory fromDatabase = tm().transact(() -> tm().loadByKey(hostHistory.createVKey()));
assertThat(SerializeUtils.serializeDeserialize(fromDatabase)).isEqualTo(fromDatabase);
}

View File

@@ -17,7 +17,6 @@ package google.registry.model.poll;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.loadByKey;
import static google.registry.testing.DatabaseHelper.persistActiveContact;
import static google.registry.testing.DatabaseHelper.persistResource;
@@ -90,12 +89,12 @@ public class PollMessageTest extends EntityTestCase {
@Test
void testCloudSqlSupportForPolymorphicVKey() {
insertInDb(oneTime);
persistResource(oneTime);
PollMessage persistedOneTime = loadByKey(VKey.create(PollMessage.class, oneTime.getId()));
assertThat(persistedOneTime).isInstanceOf(PollMessage.OneTime.class);
assertThat(persistedOneTime).isEqualTo(oneTime);
insertInDb(autoRenew);
persistResource(autoRenew);
PollMessage persistedAutoRenew = loadByKey(VKey.create(PollMessage.class, autoRenew.getId()));
assertThat(persistedAutoRenew).isInstanceOf(PollMessage.Autorenew.class);
assertThat(persistedAutoRenew).isEqualTo(autoRenew);

View File

@@ -25,8 +25,7 @@ import static google.registry.testing.DatabaseHelper.cloneAndSetAutoTimestamps;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.newTld;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.DatabaseHelper.persistSimpleResource;
import static google.registry.testing.DatabaseHelper.persistSimpleResources;
import static google.registry.testing.DatabaseHelper.persistResources;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.joda.money.CurrencyUnit.JPY;
import static org.joda.money.CurrencyUnit.USD;
@@ -132,7 +131,7 @@ class RegistrarTest extends EntityTestCase {
.setFaxNumber("+1.2125551213")
.setTypes(ImmutableSet.of(RegistrarPoc.Type.ABUSE, RegistrarPoc.Type.ADMIN))
.build();
persistSimpleResources(
persistResources(
ImmutableList.of(
abuseAdminContact,
new RegistrarPoc.Builder()
@@ -301,7 +300,7 @@ class RegistrarTest extends EntityTestCase {
@Test
void testSuccess_emptyContactTypesAllowed() {
persistSimpleResource(
persistResource(
new RegistrarPoc.Builder()
.setRegistrar(registrar)
.setName("John Abussy")
@@ -318,7 +317,7 @@ class RegistrarTest extends EntityTestCase {
@Test
void testSuccess_getContactsByType() {
RegistrarPoc newTechContact =
persistSimpleResource(
persistResource(
new RegistrarPoc.Builder()
.setRegistrar(registrar)
.setName("Jake Tech")
@@ -330,7 +329,7 @@ class RegistrarTest extends EntityTestCase {
.setTypes(ImmutableSet.of(RegistrarPoc.Type.TECH))
.build());
RegistrarPoc newTechAbuseContact =
persistSimpleResource(
persistResource(
new RegistrarPoc.Builder()
.setRegistrar(registrar)
.setName("Jim Tech-Abuse")

View File

@@ -17,9 +17,10 @@ package google.registry.model.reporting;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.model.reporting.Spec11ThreatMatch.ThreatType.MALWARE;
import static google.registry.model.reporting.Spec11ThreatMatch.ThreatType.UNWANTED_SOFTWARE;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.loadByEntity;
import static google.registry.testing.DatabaseHelper.persistResources;
import static google.registry.testing.SqlHelper.assertThrowForeignKeyViolation;
import static google.registry.testing.SqlHelper.saveRegistrar;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -105,7 +106,7 @@ public final class Spec11ThreatMatchTest extends EntityTestCase {
void testPersistence() {
createTld("tld");
saveRegistrar(REGISTRAR_ID);
insertInDb(registrantContact, domain, host, threat);
tm().transact(() -> tm().insertAll(registrantContact, domain, host, threat));
assertAboutImmutableObjects().that(loadByEntity(threat)).isEqualExceptFields(threat, "id");
}
@@ -113,12 +114,12 @@ public final class Spec11ThreatMatchTest extends EntityTestCase {
@Disabled("We can't rely on foreign keys until we've migrated to SQL")
void testThreatForeignKeyConstraints() {
// Persist the threat without the associated registrar.
assertThrowForeignKeyViolation(() -> insertInDb(host, registrantContact, domain, threat));
assertThrowForeignKeyViolation(() -> persistResources(host, registrantContact, domain, threat));
saveRegistrar(REGISTRAR_ID);
// Persist the threat without the associated domain.
assertThrowForeignKeyViolation(() -> insertInDb(registrantContact, host, threat));
assertThrowForeignKeyViolation(() -> persistResources(registrantContact, host, threat));
}
@Test

View File

@@ -29,6 +29,7 @@ import google.registry.testing.TestDataHelper;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link RequestComponent}. */
@@ -49,6 +50,7 @@ public class RequestComponentTest {
}
@Test
@Disabled("To be removed with GAE components")
void testGaeToJettyRoutingCoverage() {
Set<Route> jettyRoutes = getRoutes(RequestComponent.class, "routing.txt");
Set<Route> gaeRoutes = new HashSet<>();

View File

@@ -18,7 +18,7 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.persistResource;
import com.google.common.collect.ImmutableSet;
import google.registry.model.ImmutableObject;
@@ -51,7 +51,7 @@ class EntityCallbacksListenerTest {
@Test
void verifyAllCallbacks_executedExpectedTimes() {
TestEntity testPersist = new TestEntity();
insertInDb(testPersist);
tm().transact(() -> tm().insert(testPersist));
checkAll(testPersist, 1, 0, 0, 0);
TestEntity testUpdate = new TestEntity();
@@ -99,7 +99,7 @@ class EntityCallbacksListenerTest {
@Test
void verifyCallbacksNotCalledOnCommit() {
insertInDb(new TestEntity());
persistResource(new TestEntity());
TestEntity testLoad = tm().transact(() -> tm().loadByKey(VKey.create(TestEntity.class, "id")));
assertThat(testLoad.entityPreUpdate).isEqualTo(0);

View File

@@ -19,7 +19,7 @@ import static google.registry.model.domain.token.AllocationToken.TokenStatus.END
import static google.registry.model.domain.token.AllocationToken.TokenStatus.NOT_STARTED;
import static google.registry.model.domain.token.AllocationToken.TokenStatus.VALID;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import com.google.common.collect.ImmutableSortedMap;
@@ -59,7 +59,7 @@ public class AllocationTokenStatusTransitionUserTypeTest {
TimedTransitionProperty.fromValueMap(values);
AllocationTokenStatusTransitionConverterTestEntity testEntity =
new AllocationTokenStatusTransitionConverterTestEntity(timedTransitionProperty);
insertInDb(testEntity);
persistResource(testEntity);
AllocationTokenStatusTransitionConverterTestEntity persisted =
tm().transact(
() ->

View File

@@ -18,7 +18,7 @@ import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE;
import static google.registry.model.domain.token.AllocationToken.TokenType.UNLIMITED_USE;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.persistResource;
import com.google.common.collect.ImmutableList;
import google.registry.model.ImmutableObject;
@@ -51,7 +51,7 @@ public class AllocationTokenVkeyListUserTypeTest {
List<VKey<AllocationToken>> tokens = ImmutableList.of(token1.createVKey(), token2.createVKey());
TestAllocationTokenVKeyList testAllocationTokenVKeyList =
new TestAllocationTokenVKeyList(tokens);
insertInDb(testAllocationTokenVKeyList);
persistResource(testAllocationTokenVKeyList);
TestAllocationTokenVKeyList persisted =
tm().transact(() -> tm().getEntityManager().find(TestAllocationTokenVKeyList.class, "id"));
assertThat(persisted.tokenList).isEqualTo(tokens);

View File

@@ -16,7 +16,7 @@ package google.registry.persistence.converter;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.joda.money.CurrencyUnit.USD;
@@ -52,7 +52,7 @@ public class BillingCostTransitionUserTypeTest {
TimedTransitionProperty<Money> timedTransitionProperty =
TimedTransitionProperty.fromValueMap(values);
TestEntity testEntity = new TestEntity(timedTransitionProperty);
insertInDb(testEntity);
persistResource(testEntity);
TestEntity persisted =
tm().transact(() -> tm().getEntityManager().find(TestEntity.class, "id"));
assertThat(persisted.timedTransitionProperty.toValueMap())

View File

@@ -16,7 +16,7 @@ package google.registry.persistence.converter;
import static com.google.common.hash.Funnels.stringFunnel;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.persistResource;
import static java.nio.charset.StandardCharsets.US_ASCII;
import com.google.common.collect.ImmutableSet;
@@ -41,7 +41,7 @@ class BloomFilterConverterTest {
BloomFilter<String> bloomFilter = BloomFilter.create(stringFunnel(US_ASCII), 3);
ImmutableSet.of("foo", "bar", "baz").forEach(bloomFilter::put);
TestEntity entity = new TestEntity(bloomFilter);
insertInDb(entity);
persistResource(entity);
TestEntity persisted =
tm().transact(() -> tm().getEntityManager().find(TestEntity.class, "id"));
assertThat(persisted.bloomFilter).isEqualTo(bloomFilter);

View File

@@ -16,7 +16,7 @@ package google.registry.persistence.converter;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.persistResource;
import com.google.common.collect.ImmutableList;
import google.registry.model.ImmutableObject;
@@ -47,7 +47,7 @@ public class CidrBlockListUserTypeTest {
CidrAddressBlock.create("8000::/1"),
CidrAddressBlock.create("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"));
TestEntity testEntity = new TestEntity(addresses);
insertInDb(testEntity);
persistResource(testEntity);
TestEntity persisted =
tm().transact(() -> tm().getEntityManager().find(TestEntity.class, "id"));
assertThat(persisted.addresses).isEqualTo(addresses);

View File

@@ -16,7 +16,7 @@ package google.registry.persistence.converter;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.persistResource;
import com.google.common.collect.ImmutableMap;
import google.registry.model.ImmutableObject;
@@ -44,7 +44,7 @@ public class CurrencyToStringMapUserTypeTest {
CurrencyUnit.of("USD"), "accountId1",
CurrencyUnit.of("CNY"), "accountId2");
TestEntity testEntity = new TestEntity(currencyToBilling);
insertInDb(testEntity);
persistResource(testEntity);
TestEntity persisted =
tm().transact(() -> tm().getEntityManager().find(TestEntity.class, "id"));
assertThat(persisted.currencyToBilling).containsExactlyEntriesIn(currencyToBilling);

View File

@@ -16,7 +16,7 @@ package google.registry.persistence.converter;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.persistResource;
import static org.junit.jupiter.api.Assertions.assertThrows;
import google.registry.model.ImmutableObject;
@@ -39,7 +39,7 @@ public class CurrencyUnitConverterTest {
@Test
void roundTripConversion() {
TestEntity entity = new TestEntity(CurrencyUnit.EUR);
insertInDb(entity);
persistResource(entity);
assertThat(
tm().transact(
() ->

View File

@@ -16,7 +16,7 @@ package google.registry.persistence.converter;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.persistResource;
import static java.time.ZoneOffset.UTC;
import google.registry.model.ImmutableObject;
@@ -75,7 +75,7 @@ public class DateTimeConverterTest {
void converter_generatesTimestampWithNormalizedZone() {
DateTime dt = parseDateTime("2019-09-01T01:01:01Z");
TestEntity entity = new TestEntity("normalized_utc_time", dt);
insertInDb(entity);
persistResource(entity);
TestEntity retrievedEntity =
tm().transact(() -> tm().getEntityManager().find(TestEntity.class, "normalized_utc_time"));
assertThat(retrievedEntity.dt.toString()).isEqualTo("2019-09-01T01:01:01.000Z");
@@ -86,7 +86,7 @@ public class DateTimeConverterTest {
DateTime dt = parseDateTime("2019-09-01T01:01:01-05:00");
TestEntity entity = new TestEntity("new_york_time", dt);
insertInDb(entity);
persistResource(entity);
TestEntity retrievedEntity =
tm().transact(() -> tm().getEntityManager().find(TestEntity.class, "new_york_time"));
assertThat(retrievedEntity.dt.toString()).isEqualTo("2019-09-01T06:01:01.000Z");

View File

@@ -16,7 +16,7 @@ package google.registry.persistence.converter;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.persistResource;
import google.registry.model.ImmutableObject;
import google.registry.persistence.transaction.JpaTestExtensions;
@@ -39,7 +39,7 @@ public class DurationUserTypeTest {
@Test
void testNulls() {
DurationTestEntity entity = new DurationTestEntity(null);
insertInDb(entity);
persistResource(entity);
DurationTestEntity persisted =
tm().transact(() -> tm().getEntityManager().find(DurationTestEntity.class, "id"));
assertThat(persisted.duration).isNull();
@@ -90,7 +90,7 @@ public class DurationUserTypeTest {
private void assertPersistedEntityHasSameDuration(Duration duration) {
DurationTestEntity entity = new DurationTestEntity(duration);
insertInDb(entity);
persistResource(entity);
DurationTestEntity persisted =
tm().transact(() -> tm().getEntityManager().find(DurationTestEntity.class, "id"));
assertThat(persisted.duration.getMillis()).isEqualTo(duration.getMillis());

View File

@@ -16,7 +16,7 @@ package google.registry.persistence.converter;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.persistResource;
import com.google.common.collect.ImmutableSet;
import com.google.common.net.InetAddresses;
@@ -64,7 +64,7 @@ public class InetAddressSetUserTypeTest {
private void verifySaveAndLoad(@Nullable Set<InetAddress> inetAddresses) {
InetAddressSetTestEntity testEntity = new InetAddressSetTestEntity(inetAddresses);
insertInDb(testEntity);
persistResource(testEntity);
InetAddressSetTestEntity persisted =
tm().transact(() -> tm().loadByKey(VKey.create(InetAddressSetTestEntity.class, "id")));
assertThat(persisted.addresses).isEqualTo(inetAddresses);

View File

@@ -15,7 +15,7 @@ package google.registry.persistence.converter;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.persistResource;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableMap;
@@ -57,7 +57,7 @@ public class JodaMoneyTypeTest {
Money money = Money.of(CurrencyUnit.USD, 100.12);
assertThat(money.getAmount().scale()).isEqualTo(2);
TestEntity entity = new TestEntity(money);
insertInDb(entity);
persistResource(entity);
List<?> result =
tm().transact(
() ->
@@ -81,7 +81,7 @@ public class JodaMoneyTypeTest {
Money money = Money.ofMajor(CurrencyUnit.JPY, 100);
assertThat(money.getAmount().scale()).isEqualTo(0); // JPY's amount has scale at 0.
TestEntity entity = new TestEntity(money);
insertInDb(entity);
persistResource(entity);
List<?> result =
tm().transact(
() ->
@@ -121,7 +121,7 @@ public class JodaMoneyTypeTest {
"dos", Money.ofMajor(CurrencyUnit.JPY, 2000),
"tres", Money.of(CurrencyUnit.GBP, 20));
ComplexTestEntity entity = new ComplexTestEntity(moneyMap, myMoney, yourMoney);
insertInDb(entity);
persistResource(entity);
List<?> result =
tm().transact(
() ->

View File

@@ -16,7 +16,7 @@ package google.registry.persistence.converter;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.persistResource;
import google.registry.model.ImmutableObject;
import google.registry.persistence.VKey;
@@ -54,7 +54,7 @@ public class LocalDateConverterTest {
private LocalDateConverterTestEntity persistAndLoadTestEntity(LocalDate date) {
LocalDateConverterTestEntity entity = new LocalDateConverterTestEntity(date);
insertInDb(entity);
persistResource(entity);
return tm().transact(
() -> tm().loadByKey(VKey.create(LocalDateConverterTestEntity.class, "id")));
}

View File

@@ -48,7 +48,7 @@ public class RegistrarToRoleMapUserTypeTest {
"FooRegistrar",
RegistrarRole.TECH_CONTACT);
TestEntity entity = new TestEntity(map);
DatabaseHelper.insertInDb(entity);
DatabaseHelper.persistResource(entity);
TestEntity persisted = Iterables.getOnlyElement(DatabaseHelper.loadAllOf(TestEntity.class));
assertThat(persisted.map).isEqualTo(map);
}

View File

@@ -17,7 +17,7 @@ package google.registry.persistence.converter;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.persistResource;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableList;
@@ -45,7 +45,7 @@ public class StringCollectionUserTypeTest {
List<ListElement> value =
ImmutableList.of(new ListElement("app"), new ListElement("dev"), new ListElement("com"));
TestEntity testEntity = new TestEntity(value);
insertInDb(testEntity);
persistResource(testEntity);
TestEntity persisted =
tm().transact(() -> tm().getEntityManager().find(TestEntity.class, "id"));
assertThat(persisted.value).containsExactlyElementsIn(value);
@@ -56,7 +56,7 @@ public class StringCollectionUserTypeTest {
List<ListElement> value =
ImmutableList.of(new ListElement("app"), new ListElement("dev"), new ListElement("com"));
TestEntity testEntity = new TestEntity(value);
insertInDb(testEntity);
persistResource(testEntity);
TestEntity persisted =
tm().transact(() -> tm().getEntityManager().find(TestEntity.class, "id"));
persisted.value = ImmutableList.of(new ListElement("app"), new ListElement("org"));
@@ -68,7 +68,7 @@ public class StringCollectionUserTypeTest {
@Test
void testNullValue_writesAndReadsNullSuccessfully() {
TestEntity testEntity = new TestEntity(null);
insertInDb(testEntity);
persistResource(testEntity);
TestEntity persisted =
tm().transact(() -> tm().getEntityManager().find(TestEntity.class, "id"));
assertThat(persisted.value).isNull();
@@ -77,7 +77,7 @@ public class StringCollectionUserTypeTest {
@Test
void testEmptyCollection_writesAndReadsEmptyCollectionSuccessfully() {
TestEntity testEntity = new TestEntity(ImmutableList.of());
insertInDb(testEntity);
persistResource(testEntity);
TestEntity persisted =
tm().transact(() -> tm().getEntityManager().find(TestEntity.class, "id"));
assertThat(persisted.value).isEmpty();

View File

@@ -16,7 +16,7 @@ package google.registry.persistence.converter;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.persistResource;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableList;
@@ -41,7 +41,7 @@ public class StringListConversionTest {
void roundTripConversion_returnsSameStringList() {
List<String> tlds = ImmutableList.of("app", "dev", "how");
TestEntity testEntity = new TestEntity(tlds);
insertInDb(testEntity);
persistResource(testEntity);
TestEntity persisted =
tm().transact(() -> tm().getEntityManager().find(TestEntity.class, "id"));
assertThat(persisted.tlds).containsExactly("app", "dev", "how");
@@ -51,7 +51,7 @@ public class StringListConversionTest {
void testMerge_succeeds() {
List<String> tlds = ImmutableList.of("app", "dev", "how");
TestEntity testEntity = new TestEntity(tlds);
insertInDb(testEntity);
persistResource(testEntity);
TestEntity persisted =
tm().transact(() -> tm().getEntityManager().find(TestEntity.class, "id"));
persisted.tlds = ImmutableList.of("com", "gov");
@@ -63,7 +63,7 @@ public class StringListConversionTest {
@Test
void testNullValue_writesAndReadsNullSuccessfully() {
TestEntity testEntity = new TestEntity(null);
insertInDb(testEntity);
persistResource(testEntity);
TestEntity persisted =
tm().transact(() -> tm().getEntityManager().find(TestEntity.class, "id"));
assertThat(persisted.tlds).isNull();
@@ -72,7 +72,7 @@ public class StringListConversionTest {
@Test
void testEmptyCollection_writesAndReadsEmptyCollectionSuccessfully() {
TestEntity testEntity = new TestEntity(ImmutableList.of());
insertInDb(testEntity);
persistResource(testEntity);
TestEntity persisted =
tm().transact(() -> tm().getEntityManager().find(TestEntity.class, "id"));
assertThat(persisted.tlds).isEmpty();

View File

@@ -16,7 +16,7 @@ package google.registry.persistence.converter;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.persistResource;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableMap;
@@ -47,7 +47,7 @@ public class StringMapUserTypeTest {
@Test
void roundTripConversion_returnsSameMap() {
TestEntity testEntity = new TestEntity(MAP);
insertInDb(testEntity);
persistResource(testEntity);
TestEntity persisted =
tm().transact(() -> tm().getEntityManager().find(TestEntity.class, "id"));
assertThat(persisted.map).containsExactlyEntriesIn(MAP);
@@ -56,7 +56,7 @@ public class StringMapUserTypeTest {
@Test
void testUpdateColumn_succeeds() {
TestEntity testEntity = new TestEntity(MAP);
insertInDb(testEntity);
persistResource(testEntity);
TestEntity persisted =
tm().transact(() -> tm().getEntityManager().find(TestEntity.class, "id"));
assertThat(persisted.map).containsExactlyEntriesIn(MAP);
@@ -69,7 +69,7 @@ public class StringMapUserTypeTest {
@Test
void testNullValue_writesAndReadsNullSuccessfully() {
TestEntity testEntity = new TestEntity(null);
insertInDb(testEntity);
persistResource(testEntity);
TestEntity persisted =
tm().transact(() -> tm().getEntityManager().find(TestEntity.class, "id"));
assertThat(persisted.map).isNull();
@@ -78,7 +78,7 @@ public class StringMapUserTypeTest {
@Test
void testEmptyMap_writesAndReadsEmptyCollectionSuccessfully() {
TestEntity testEntity = new TestEntity(ImmutableMap.of());
insertInDb(testEntity);
persistResource(testEntity);
TestEntity persisted =
tm().transact(() -> tm().getEntityManager().find(TestEntity.class, "id"));
assertThat(persisted.map).isEmpty();

View File

@@ -16,7 +16,7 @@ package google.registry.persistence.converter;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.persistResource;
import com.google.common.collect.ImmutableSet;
import google.registry.model.ImmutableObject;
@@ -39,7 +39,7 @@ public class StringSetConversionTest {
void roundTripConversion_returnsSameStringList() {
Set<String> tlds = ImmutableSet.of("app", "dev", "how");
TestEntity testEntity = new TestEntity(tlds);
insertInDb(testEntity);
persistResource(testEntity);
TestEntity persisted =
tm().transact(() -> tm().getEntityManager().find(TestEntity.class, "id"));
assertThat(persisted.tlds).containsExactly("app", "dev", "how");
@@ -48,7 +48,7 @@ public class StringSetConversionTest {
@Test
void testNullValue_writesAndReadsNullSuccessfully() {
TestEntity testEntity = new TestEntity(null);
insertInDb(testEntity);
persistResource(testEntity);
TestEntity persisted =
tm().transact(() -> tm().getEntityManager().find(TestEntity.class, "id"));
assertThat(persisted.tlds).isNull();
@@ -57,7 +57,7 @@ public class StringSetConversionTest {
@Test
void testEmptyCollection_writesAndReadsEmptyCollectionSuccessfully() {
TestEntity testEntity = new TestEntity(ImmutableSet.of());
insertInDb(testEntity);
persistResource(testEntity);
TestEntity persisted =
tm().transact(() -> tm().getEntityManager().find(TestEntity.class, "id"));
assertThat(persisted.tlds).isEmpty();

View File

@@ -16,7 +16,7 @@ package google.registry.persistence.converter;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.persistResource;
import google.registry.model.ImmutableObject;
import google.registry.model.registrar.Registrar.State;
@@ -39,7 +39,7 @@ public class StringValueEnumeratedTest {
@Test
void roundTripConversion_returnsSameEnum() {
TestEntity testEntity = new TestEntity(State.ACTIVE);
insertInDb(testEntity);
persistResource(testEntity);
TestEntity persisted =
tm().transact(() -> tm().getEntityManager().find(TestEntity.class, "id"));
assertThat(persisted.state).isEqualTo(State.ACTIVE);
@@ -48,7 +48,7 @@ public class StringValueEnumeratedTest {
@Test
void testNativeQuery_succeeds() {
TestEntity testEntity = new TestEntity(State.DISABLED);
insertInDb(testEntity);
persistResource(testEntity);
assertThat(
tm().transact(

View File

@@ -16,7 +16,7 @@ package google.registry.persistence.converter;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -55,7 +55,7 @@ class TimedTransitionBaseUserTypeTest {
@Test
void roundTripConversion_returnsSameTimedTransitionProperty() {
TestEntity testEntity = new TestEntity(TIMED_TRANSITION_PROPERTY);
insertInDb(testEntity);
persistResource(testEntity);
TestEntity persisted =
tm().transact(() -> tm().getEntityManager().find(TestEntity.class, "id"));
assertThat(persisted.property.toValueMap())
@@ -65,7 +65,7 @@ class TimedTransitionBaseUserTypeTest {
@Test
void testUpdateColumn_succeeds() {
TestEntity testEntity = new TestEntity(TIMED_TRANSITION_PROPERTY);
insertInDb(testEntity);
persistResource(testEntity);
TestEntity persisted =
tm().transact(() -> tm().getEntityManager().find(TestEntity.class, "id"));
assertThat(persisted.property.toValueMap())
@@ -80,7 +80,7 @@ class TimedTransitionBaseUserTypeTest {
@Test
void testNullValue_writesAndReadsNullSuccessfully() {
TestEntity testEntity = new TestEntity(null);
insertInDb(testEntity);
persistResource(testEntity);
TestEntity persisted =
tm().transact(() -> tm().getEntityManager().find(TestEntity.class, "id"));
assertThat(persisted.property).isNull();

View File

@@ -16,7 +16,7 @@ package google.registry.persistence.converter;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import com.google.common.collect.ImmutableSortedMap;
@@ -55,7 +55,7 @@ class TldStateTransitionUserTypeTest {
TimedTransitionProperty<TldState> timedTransitionProperty =
TimedTransitionProperty.fromValueMap(values);
TestEntity testEntity = new TestEntity(timedTransitionProperty);
insertInDb(testEntity);
persistResource(testEntity);
TestEntity persisted =
tm().transact(() -> tm().getEntityManager().find(TestEntity.class, "id"));
assertThat(persisted.timedTransitionProperty.toValueMap())

View File

@@ -16,7 +16,7 @@ package google.registry.persistence.converter;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.persistResources;
import google.registry.model.ImmutableObject;
import google.registry.persistence.VKey;
@@ -50,7 +50,7 @@ public class VKeyConverterTest {
TestLongEntity longEntity = new TestLongEntity(300L);
VKey<TestLongEntity> longKey = VKey.create(TestLongEntity.class, 300L);
TestEntity original = new TestEntity(1984L, stringKey, longKey);
insertInDb(stringEntity, longEntity, original);
persistResources(stringEntity, longEntity, original);
TestEntity retrieved =
tm().transact(() -> tm().getEntityManager().find(TestEntity.class, 1984L));

View File

@@ -16,7 +16,7 @@ package google.registry.persistence.transaction;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.persistResources;
import com.google.common.collect.ImmutableList;
import google.registry.model.ImmutableObject;
@@ -50,7 +50,7 @@ class CriteriaQueryBuilderTest {
@BeforeEach
void beforeEach() {
insertInDb(entity1, entity2, entity3);
persistResources(entity1, entity2, entity3);
}
@Test

Some files were not shown because too many files have changed in this diff Show More