1
0
mirror of https://github.com/google/nomulus synced 2026-03-02 00:34:54 +00:00

Compare commits

...

6 Commits

Author SHA1 Message Date
Pavlo Tkach
186dd80567 Enable password reset for registrars (#2971) 2026-02-27 20:02:51 +00:00
gbrodman
c52983fb61 Update some Nomulus documentation (#2970)
This doesn't update everything -- it leaves out some of the more
complicated changes (architecture, code-structure, configuration,
install, and proxy-setup). Those will require more complete rewrites, so
I'm punting them to a future PR.
2026-02-26 19:05:22 +00:00
Weimin Yu
8a3ab00e58 Apply Fee tag normalization in production (#2968)
Feature verified in Sandbox.
2026-02-25 20:02:37 +00:00
Pavlo Tkach
49df9c325a Update angular @21 (#2965) 2026-02-24 20:08:27 +00:00
gbrodman
929dccbfe3 Remove the concept of a TransferData abstract class (#2966)
The only type of thing that can be transferred now is a domain, so
there's no point in having this abstract class / redirection.

This does not include deletion of the contact-response-related XML
classes; that can come next.
2026-02-23 16:08:27 +00:00
gbrodman
ee8746c857 Remove Contact and ContactHistory Java objects and related code (#2964)
This doesn't remove everything -- there are still other contact-related
objects that we'll need to remove (e.g.
ContactPendingActionNotificationResponse) and simplifications we'll need to make
(e.g. only domains can be transferred now, so all transfer data can move
there instead of being generic)

But this removes the bulk of the remaining contact-related code. We'll
keep around the XML request objects, since it's still nice to route them
to the appropriate (exception-throwing but logging) flow class.
2026-02-20 16:22:29 +00:00
123 changed files with 5152 additions and 10433 deletions

View File

@@ -15,7 +15,7 @@
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"builder": "@angular/build:application",
"options": {
"outputPath": {
"base": "staged/dist/",
@@ -112,7 +112,7 @@
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"builder": "@angular/build:dev-server",
"configurations": {
"production": {
"buildTarget": "console-webapp:build:production"
@@ -136,16 +136,18 @@
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"builder": "@angular/build:extract-i18n",
"options": {
"buildTarget": "console-webapp:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"builder": "@angular/build:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"polyfills": [
"src/polyfills.ts"
],
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"inlineStyleLanguage": "scss",
@@ -183,5 +185,31 @@
"schematicCollections": [
"@angular-eslint/schematics"
]
},
"schematics": {
"@schematics/angular:component": {
"type": "component"
},
"@schematics/angular:directive": {
"type": "directive"
},
"@schematics/angular:service": {
"type": "service"
},
"@schematics/angular:guard": {
"typeSeparator": "."
},
"@schematics/angular:interceptor": {
"typeSeparator": "."
},
"@schematics/angular:module": {
"typeSeparator": "."
},
"@schematics/angular:pipe": {
"typeSeparator": "."
},
"@schematics/angular:resolver": {
"typeSeparator": "."
}
}
}

View File

@@ -1,7 +1,7 @@
{
"/console-api":
{
"target": "http://[::1]:8080",
"target": "http://localhost:8080",
"secure": false,
"logLevel": "debug",
"changeOrigin": true

View File

@@ -21,7 +21,7 @@ module.exports = function (config) {
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
jasmine: {

File diff suppressed because it is too large Load Diff

View File

@@ -16,29 +16,29 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^19.1.4",
"@angular/cdk": "^19.1.2",
"@angular/common": "^19.1.4",
"@angular/compiler": "^19.1.4",
"@angular/core": "^19.1.4",
"@angular/forms": "^19.1.4",
"@angular/material": "^19.1.2",
"@angular/platform-browser": "^19.1.4",
"@angular/platform-browser-dynamic": "^19.1.4",
"@angular/router": "^19.1.4",
"@angular/animations": "^21.1.5",
"@angular/cdk": "^21.1.5",
"@angular/common": "^21.1.5",
"@angular/compiler": "^21.1.5",
"@angular/core": "^21.1.5",
"@angular/forms": "^21.1.5",
"@angular/material": "^21.1.5",
"@angular/platform-browser": "^21.1.5",
"@angular/platform-browser-dynamic": "^21.1.5",
"@angular/router": "^21.1.5",
"rxjs": "~7.5.0",
"tslib": "^2.3.0",
"zone.js": "~0.15.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "^19.1.5",
"@angular-eslint/builder": "19.0.2",
"@angular-eslint/eslint-plugin": "19.0.2",
"@angular-eslint/eslint-plugin-template": "19.0.2",
"@angular-eslint/schematics": "19.0.2",
"@angular-eslint/template-parser": "19.0.2",
"@angular/cli": "~19.1.5",
"@angular/compiler-cli": "^19.1.4",
"@angular/build": "^21.1.4",
"@angular/cli": "~21.1.4",
"@angular/compiler-cli": "^21.1.5",
"@types/jasmine": "~4.0.0",
"@types/node": "^18.19.74",
"@typescript-eslint/eslint-plugin": "^7.2.0",
@@ -52,6 +52,6 @@
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.0.0",
"prettier": "2.8.7",
"typescript": "^5.7.3"
"typescript": "^5.9.3"
}
}
}

View File

@@ -1,10 +1,9 @@
<div class="console-app mat-typography">
<app-header (toggleNavOpen)="toggleSidenav()"></app-header>
<div class="console-app__global-spinner">
<mat-progress-bar
mode="indeterminate"
*ngIf="globalLoader.isLoading"
></mat-progress-bar>
@if (globalLoader.isLoading) {
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
}
</div>
<mat-sidenav-container class="console-app__container">
<mat-sidenav-content class="console-app__content-wrapper">

View File

@@ -143,13 +143,14 @@
<ng-container matColumnDef="domainName">
<mat-header-cell *matHeaderCellDef>Domain Name</mat-header-cell>
<mat-cell *matCellDef="let element">
@if (getOperationMessage(element.domainName)) {
<mat-icon
*ngIf="getOperationMessage(element.domainName)"
[matTooltip]="getOperationMessage(element.domainName)"
matTooltipPosition="above"
class="primary-text"
>info</mat-icon
>
}
<span>{{ element.domainName }}</span>
</mat-cell>
</ng-container>
@@ -209,9 +210,9 @@
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
<!-- Row shown when there is no matching data. -->
<mat-row *matNoDataRow>
<mat-cell colspan="6">No domains found</mat-cell>
</mat-row>
<tr class="mat-row" *matNoDataRow>
<td class="mat-cell" colspan="6">No domains found</td>
</tr>
</mat-table>
<mat-paginator
[length]="totalResults"

View File

@@ -33,6 +33,7 @@ import {
MatDialogRef,
} from '@angular/material/dialog';
import { RESTRICTED_ELEMENTS } from '../shared/directives/userLevelVisiblity.directive';
import { CdkColumnDef } from '@angular/cdk/table';
interface DomainResponse {
message: string;
@@ -114,6 +115,7 @@ export class ReasonDialogComponent {
templateUrl: './domainList.component.html',
styleUrls: ['./domainList.component.scss'],
standalone: false,
providers: [CdkColumnDef],
})
export class DomainListComponent {
public static PATH = 'domain-list';

View File

@@ -1,14 +1,15 @@
<p class="console-app__header">
<mat-toolbar>
@if (breakpointObserver.isMobileView()) {
<button
mat-icon-button
aria-label="Open navigation menu"
(click)="toggleNavPane()"
*ngIf="breakpointObserver.isMobileView()"
class="console-app__menu-btn"
>
<mat-icon>menu</mat-icon>
</button>
}
<a
[routerLink]="'/home'"
routerLinkActive="active"
@@ -65,7 +66,9 @@
</svg>
</a>
<span class="spacer"></span>
<app-registrar-selector *ngIf="!breakpointObserver.isMobileView()" />
@if (!breakpointObserver.isMobileView()) {
<app-registrar-selector />
}
<button
class="console-app__header-user-icon"
mat-mini-fab
@@ -79,5 +82,7 @@
<button mat-menu-item (click)="logOut()">Log out</button>
</mat-menu>
</mat-toolbar>
<app-registrar-selector *ngIf="breakpointObserver.isMobileView()" />
@if (breakpointObserver.isMobileView()) {
<app-registrar-selector />
}
</p>

View File

@@ -9,41 +9,36 @@
<mat-card>
<mat-card-content>
<mat-list role="list">
<ng-container *ngFor="let item of historyRecords; let last = last">
<mat-list-item class="history-list__item">
<mat-icon
[ngClass]="getIconClass(item.type)"
class="history-list__icon"
>
{{ getIconForType(item.type) }}
</mat-icon>
<div class="history-list__content">
<div class="history-list__description">
<span class="history-list__description--main">{{
item.type
}}</span>
<div>
<mat-chip
*ngIf="parseDescription(item.description).detail"
class="history-list__chip"
>
{{ parseDescription(item.description).detail }}
</mat-chip>
</div>
</div>
<div class="history-list__user">
<b>User - {{ item.actingUser.emailAddress }}</b>
@for (item of historyRecords; track item; let last = $last) {
<mat-list-item class="history-list__item">
<mat-icon
[ngClass]="getIconClass(item.type)"
class="history-list__icon"
>
{{ getIconForType(item.type) }}
</mat-icon>
<div class="history-list__content">
<div class="history-list__description">
<span class="history-list__description--main">{{ item.type }}</span>
<div>
@if (parseDescription(item.description).detail) {
<mat-chip class="history-list__chip">
{{ parseDescription(item.description).detail }}
</mat-chip>
}
</div>
</div>
<span class="history-list__timestamp">
{{ item.modificationTime | date : "MMM d, y, h:mm a" }}
</span>
</mat-list-item>
<mat-divider *ngIf="!last"></mat-divider>
</ng-container>
<div class="history-list__user">
<b>User - {{ item.actingUser.emailAddress }}</b>
</div>
</div>
<span class="history-list__timestamp">
{{ item.modificationTime | date : "MMM d, y, h:mm a" }}
</span>
</mat-list-item>
@if (!last) {
<mat-divider></mat-divider>
} }
</mat-list>
</mat-card-content>
</mat-card>

View File

@@ -17,6 +17,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HomeComponent } from './home.component';
import { MaterialModule } from '../material.module';
import { AppModule } from '../app.module';
import { BackendService } from '../shared/services/backend.service';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
describe('HomeComponent', () => {
let component: HomeComponent;
@@ -26,6 +29,11 @@ describe('HomeComponent', () => {
await TestBed.configureTestingModule({
imports: [MaterialModule, AppModule],
declarations: [HomeComponent],
providers: [
BackendService,
provideHttpClient(),
provideHttpClientTesting(),
],
}).compileComponents();
fixture = TestBed.createComponent(HomeComponent);

View File

@@ -12,9 +12,11 @@
[class.active]="router.url.includes(node.path)"
[elementId]="getElementId(node)"
>
<mat-icon class="console-app__nav-icon" *ngIf="node.iconName">
@if (node.iconName) {
<mat-icon class="console-app__nav-icon">
{{ node.iconName }}
</mat-icon>
}
{{ node.title }}
</mat-tree-node>
<mat-nested-tree-node
@@ -34,9 +36,11 @@
{{ treeControl.isExpanded(node) ? "expand_more" : "chevron_right" }}
</mat-icon>
</button>
<mat-icon class="console-app__nav-icon" *ngIf="node.iconName">
@if (node.iconName) {
<mat-icon class="console-app__nav-icon">
{{ node.iconName }}
</mat-icon>
}
{{ node.title }}
</div>
<div

View File

@@ -1,25 +1,28 @@
<h1 class="mat-headline-4">OT&E Status Check</h1>
@if(registrarId() === null) {
<h1>Missing registrarId param</h1>
} @else if(isOte()) {
<h1 *ngIf="oteStatusResponse().length">
} @else if(isOte()) { @if (oteStatusResponse().length) {
<h1>
Status:
<span>{{ oteStatusUnfinished().length ? "Unfinished" : "Completed" }}</span>
</h1>
}
<div class="console-app__ote-status">
@if(oteStatusCompleted().length) {
<div class="console-app__ote-status_completed">
<h1>Completed</h1>
<div *ngFor="let entry of oteStatusCompleted()">
<mat-icon>check_box</mat-icon>{{ entry.description }}
</div>
@for (entry of oteStatusCompleted(); track entry) {
<div><mat-icon>check_box</mat-icon>{{ entry.description }}</div>
}
</div>
} @if(oteStatusUnfinished().length) {
<div class="console-app__ote-status_unfinished">
<h1>Unfinished</h1>
<div *ngFor="let entry of oteStatusUnfinished()">
@for (entry of oteStatusUnfinished(); track entry) {
<div>
<mat-icon>check_box_outline_blank</mat-icon>{{ entry.description }}
</div>
}
</div>
}
</div>

View File

@@ -18,7 +18,7 @@ import { MatSnackBar } from '@angular/material/snack-bar';
import { RegistrarService } from '../registrar/registrar.service';
import { MaterialModule } from '../material.module';
import { SnackBarModule } from '../snackbar.module';
import { CommonModule } from '@angular/common';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { take } from 'rxjs';
@@ -31,7 +31,7 @@ export interface OteStatusResponse {
@Component({
selector: 'app-ote-status',
imports: [MaterialModule, SnackBarModule, CommonModule],
imports: [MaterialModule, SnackBarModule],
templateUrl: './oteStatus.component.html',
styleUrls: ['./oteStatus.component.scss'],
})

View File

@@ -11,9 +11,8 @@
<mat-icon>arrow_back</mat-icon>
</button>
<div class="spacer"></div>
@if(!inEdit && !registrarNotFound) {
@if(!inEdit && !registrarNotFound) { @if (oteButtonVisible) {
<button
*ngIf="oteButtonVisible"
mat-stroked-button
(click)="checkOteStatus()"
aria-label="Check OT&E account status"
@@ -21,6 +20,7 @@
>
Check OT&E Status
</button>
}
<button
mat-flat-button
color="primary"
@@ -39,10 +39,11 @@
<h1>Registrar not found</h1>
} @else {
<h1>{{ registrarInEdit.registrarId }}</h1>
<h2 *ngIf="registrarInEdit.registrarName !== registrarInEdit.registrarId">
@if (registrarInEdit.registrarName !== registrarInEdit.registrarId) {
<h2>
{{ registrarInEdit.registrarName }}
</h2>
@if(inEdit) {
} @if(inEdit) {
<form (ngSubmit)="saveAndClose()">
<div>
<mat-form-field appearance="outline">
@@ -60,15 +61,14 @@
<mat-form-field appearance="outline">
<mat-label>Onboarded TLDs: </mat-label>
<mat-chip-grid #chipGrid aria-label="Enter TLD">
<mat-chip-row
*ngFor="let tld of registrarInEdit.allowedTlds"
(removed)="removeTLD(tld)"
>
@for (tld of registrarInEdit.allowedTlds; track tld) {
<mat-chip-row (removed)="removeTLD(tld)">
{{ tld }}
<button matChipRemove aria-label="'remove ' + tld">
<mat-icon>cancel</mat-icon>
</button>
</mat-chip-row>
}
</mat-chip-grid>
<input
placeholder="New tld..."

View File

@@ -5,15 +5,16 @@
<div class="console-app__registrars-header">
<h1 class="mat-headline-4" forceFocus>Registrars</h1>
<div class="spacer"></div>
@if (oteButtonVisible) {
<button
mat-stroked-button
*ngIf="oteButtonVisible"
(click)="createOteAccount()"
aria-label="Generate OT&E accounts"
[elementId]="getElementIdForOteBlock()"
>
Create OT&E accounts
</button>
}
<button
class="console-app__registrars-new"
mat-flat-button
@@ -43,10 +44,8 @@
class="console-app__registrars-table"
matSort
>
<ng-container
*ngFor="let column of columns"
[matColumnDef]="column.columnDef"
>
@for (column of columns; track column) {
<ng-container [matColumnDef]="column.columnDef">
<mat-header-cell *matHeaderCellDef>
{{ column.header }}
</mat-header-cell>
@@ -55,6 +54,7 @@
[innerHTML]="column.cell(row)"
></mat-cell>
</ng-container>
}
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row
*matRowDef="let row; columns: displayedColumns"

View File

@@ -22,13 +22,12 @@
</div>
} @else {
<mat-table [dataSource]="dataSource" class="mat-elevation-z0">
<ng-container
*ngFor="let column of columns"
[matColumnDef]="column.columnDef"
>
@for (column of columns; track column) {
<ng-container [matColumnDef]="column.columnDef">
<mat-header-cell *matHeaderCellDef> {{ column.header }} </mat-header-cell>
<mat-cell *matCellDef="let row" [innerHTML]="column.cell(row)"></mat-cell>
</ng-container>
}
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row
*matRowDef="let row; columns: displayedColumns"

View File

@@ -1,9 +1,5 @@
<div
class="console-app__contact"
*ngIf="contactService.contactInEdit"
cdkTrapFocus
[cdkTrapFocusAutoCapture]="true"
>
@if (contactService.contactInEdit) {
<div class="console-app__contact" cdkTrapFocus [cdkTrapFocusAutoCapture]="true">
<div class="console-app__contact-controls">
<button
mat-icon-button
@@ -32,7 +28,6 @@
</button>
}
</div>
@if(isEditing || contactService.isContactNewView) {
<h1>Contact Details</h1>
<form (ngSubmit)="save($event)">
@@ -46,7 +41,6 @@
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Primary account email: </mat-label>
<input
@@ -64,7 +58,6 @@
"
/>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Phone: </mat-label>
<input
@@ -74,7 +67,6 @@
placeholder="+0.0000000000"
/>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Fax: </mat-label>
<input
@@ -84,7 +76,6 @@
/>
</mat-form-field>
</section>
<section>
<h1>Contact Type</h1>
<p class="console-app__contact-required">
@@ -92,21 +83,18 @@
(primary contact can't be updated)
</p>
<div class="">
<ng-container
*ngFor="let contactType of contactTypeToTextMap | keyvalue"
@for (contactType of contactTypeToTextMap | keyvalue; track contactType)
{ @if (shouldDisplayCheckbox(contactType.key)) {
<mat-checkbox
[checked]="checkboxIsChecked(contactType.key)"
(change)="checkboxOnChange($event, contactType.key)"
[disabled]="checkboxIsDisabled(contactType.key)"
>
<mat-checkbox
*ngIf="shouldDisplayCheckbox(contactType.key)"
[checked]="checkboxIsChecked(contactType.key)"
(change)="checkboxOnChange($event, contactType.key)"
[disabled]="checkboxIsDisabled(contactType.key)"
>
{{ contactType.value }}
</mat-checkbox>
</ng-container>
{{ contactType.value }}
</mat-checkbox>
} }
</div>
</section>
<section>
<h1>RDAP Preferences</h1>
<div>
@@ -116,7 +104,6 @@
>Show in Registrar RDAP record as admin contact</mat-checkbox
>
</div>
<div>
<mat-checkbox
[(ngModel)]="contactService.contactInEdit.visibleInRdapAsTech"
@@ -124,7 +111,6 @@
>Show in Registrar RDAP record as technical contact</mat-checkbox
>
</div>
<div>
<mat-checkbox
[(ngModel)]="contactService.contactInEdit.visibleInDomainRdapAsAbuse"
@@ -198,15 +184,13 @@
</mat-list-item>
} @if(contactService.contactInEdit.visibleInRdapAsTech) {
<mat-divider></mat-divider>
<mat-list-item
role="listitem"
*ngIf="contactService.contactInEdit.visibleInRdapAsTech"
>
@if (contactService.contactInEdit.visibleInRdapAsTech) {
<mat-list-item role="listitem">
<span class="console-app__list-value"
>Show in Registrar RDAP record as technical contact</span
>
</mat-list-item>
} @if(contactService.contactInEdit.visibleInDomainRdapAsAbuse) {
} } @if(contactService.contactInEdit.visibleInDomainRdapAsAbuse) {
<mat-divider></mat-divider>
<mat-list-item role="listitem">
<span class="console-app__list-value"
@@ -220,3 +204,4 @@
</mat-card>
}
</div>
}

View File

@@ -1,6 +1,6 @@
@if (registrarInEdit) {
<div
class="console-app__rdap-edit"
*ngIf="registrarInEdit"
cdkTrapFocus
[cdkTrapFocusAutoCapture]="true"
>
@@ -12,7 +12,6 @@
>
<mat-icon>arrow_back</mat-icon>
</button>
<div class="console-app__rdap-edit-controls">
<span>
General registrar information for your RDAP record. This information is
@@ -20,10 +19,8 @@
</span>
<div class="spacer"></div>
</div>
<div class="console-app__rdap-edit">
<h1>Personal info</h1>
<form (ngSubmit)="save($event)">
<mat-form-field appearance="outline">
<mat-label>Email: </mat-label>
@@ -34,7 +31,6 @@
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Phone: </mat-label>
<input
@@ -44,7 +40,6 @@
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Fax: </mat-label>
<input
@@ -54,7 +49,6 @@
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Street Address (line 1): </mat-label>
<input
@@ -64,7 +58,6 @@
[(ngModel)]="registrarInEdit.localizedAddress.street![0]"
/>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Street Address (line 2): </mat-label>
<input
@@ -74,7 +67,6 @@
[(ngModel)]="registrarInEdit.localizedAddress.street![1]"
/>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>City: </mat-label>
<input
@@ -84,7 +76,6 @@
[(ngModel)]="(registrarInEdit.localizedAddress || {}).city"
/>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>State or Province: </mat-label>
<input
@@ -94,7 +85,6 @@
[(ngModel)]="(registrarInEdit.localizedAddress || {}).state"
/>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Country: </mat-label>
<input
@@ -104,7 +94,6 @@
[(ngModel)]="(registrarInEdit.localizedAddress || {}).countryCode"
/>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Postal code: </mat-label>
<input
@@ -114,7 +103,6 @@
[(ngModel)]="(registrarInEdit.localizedAddress || {}).zip"
/>
</mat-form-field>
<button
mat-flat-button
color="primary"
@@ -126,3 +114,4 @@
</form>
</div>
</div>
}

View File

@@ -21,7 +21,6 @@
[formGroup]="passwordUpdateForm"
(submitResults)="save($event)"
/>
@if(userDataService.userData()?.isAdmin) {
<div class="settings-security__reset-password-field">
<h2>Need to reset your EPP password?</h2>
<button
@@ -33,5 +32,4 @@
Reset EPP password via email
</button>
</div>
}
</div>

View File

@@ -20,7 +20,7 @@ import { RegistrarService } from 'src/app/registrar/registrar.service';
import { SecurityService } from './security.service';
import { UserDataService } from 'src/app/shared/services/userData.service';
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { CommonModule } from '@angular/common';
import { MaterialModule } from 'src/app/material.module';
import { filter, switchMap, take } from 'rxjs';
import { BackendService } from 'src/app/shared/services/backend.service';
@@ -41,7 +41,7 @@ import {
<button mat-button color="warn" (click)="onSave()">Confirm</button>
</mat-dialog-actions>
`,
imports: [CommonModule, MaterialModule],
imports: [MaterialModule],
})
export class ResetEppPasswordComponent {
constructor(public dialogRef: MatDialogRef<ResetEppPasswordComponent>) {}

View File

@@ -68,7 +68,7 @@
>
</mat-list-item>
<mat-divider></mat-divider>
@for (item of dataSource.ipAddressAllowList; track item.value) {
@for (item of dataSource.ipAddressAllowList; track $index){
<mat-list-item role="listitem">
<span class="console-app__list-value">{{ item.value }}</span>
</mat-list-item>

View File

@@ -12,7 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import {
ComponentFixture,
fakeAsync,
TestBed,
tick,
waitForAsync,
} from '@angular/core/testing';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
@@ -30,43 +36,32 @@ import { MOCK_REGISTRAR_SERVICE } from 'src/testdata/registrar/registrar.service
describe('SecurityComponent', () => {
let component: SecurityComponent;
let fixture: ComponentFixture<SecurityComponent>;
let fetchSecurityDetailsSpy: Function;
let saveSpy: Function;
let securityServiceStub: Partial<SecurityService>;
beforeEach(async () => {
const securityServiceSpy = jasmine.createSpyObj(SecurityService, [
'fetchSecurityDetails',
'saveChanges',
]);
fetchSecurityDetailsSpy =
securityServiceSpy.fetchSecurityDetails.and.returnValue(of());
saveSpy = securityServiceSpy.saveChanges.and.returnValue(of());
securityServiceStub = {
isEditingSecurity: false,
isEditingPassword: false,
saveChanges: jasmine.createSpy('saveChanges').and.returnValue(of({})),
};
await TestBed.configureTestingModule({
declarations: [SecurityEditComponent, SecurityComponent],
imports: [MaterialModule, BrowserAnimationsModule, FormsModule],
providers: [
BackendService,
SecurityService,
{ provide: SecurityService, useValue: securityServiceStub },
{ provide: RegistrarService, useValue: MOCK_REGISTRAR_SERVICE },
provideHttpClient(),
provideHttpClientTesting(),
],
})
.overrideComponent(SecurityComponent, {
set: {
providers: [
{ provide: SecurityService, useValue: securityServiceSpy },
],
},
})
.compileComponents();
}).compileComponents();
saveSpy = securityServiceStub.saveChanges as jasmine.Spy;
fixture = TestBed.createComponent(SecurityComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
@@ -93,17 +88,36 @@ describe('SecurityComponent', () => {
});
}));
it('should remove ip', waitForAsync(() => {
component.dataSource.ipAddressAllowList =
component.dataSource.ipAddressAllowList?.splice(1);
fixture.whenStable().then(() => {
fixture.detectChanges();
let listElems: Array<HTMLElement> = Array.from(
fixture.nativeElement.querySelectorAll('span.console-app__list-value')
);
expect(listElems.map((e) => e.textContent)).toContain(
'No IP addresses on file.'
);
it('should remove ip', fakeAsync(() => {
fixture.detectChanges();
tick();
const editBtn = fixture.nativeElement.querySelector(
'button[aria-label="Edit security settings"]'
);
editBtn.click();
tick();
fixture.detectChanges();
const removeIpBtn = fixture.nativeElement.querySelector(
'.console-app__removeIp'
);
removeIpBtn.click();
tick();
fixture.detectChanges();
const saveBtn = fixture.nativeElement.querySelector(
'.settings-security__edit-save'
);
saveBtn.click();
tick();
fixture.detectChanges();
expect(saveSpy).toHaveBeenCalledWith({
ipAddressAllowList: [],
});
}));
@@ -119,21 +133,34 @@ describe('SecurityComponent', () => {
expect(component.securityService.isEditingPassword).toBeTrue();
});
it('should call save', waitForAsync(async () => {
component.editSecurity();
await fixture.whenStable();
it('should call save', fakeAsync(() => {
fixture.detectChanges();
tick();
const editBtn = fixture.nativeElement.querySelector(
'button[aria-label="Edit security settings"]'
);
editBtn.click();
tick();
fixture.detectChanges();
const el = fixture.nativeElement.querySelector(
'.console-app__clientCertificateValue'
);
el.value = 'test';
el.dispatchEvent(new Event('input'));
tick();
fixture.detectChanges();
await fixture.whenStable();
fixture.nativeElement
.querySelector('.settings-security__edit-save')
.click();
expect(saveSpy).toHaveBeenCalledOnceWith({
const saveBtn = fixture.nativeElement.querySelector(
'.settings-security__edit-save'
);
saveBtn.click();
tick();
expect(saveSpy).toHaveBeenCalledWith({
ipAddressAllowList: [{ value: '123.123.123.123' }],
clientCertificate: 'test',
});

View File

@@ -23,9 +23,11 @@
<button
matSuffix
mat-icon-button
class="console-app__removeIp"
[attr.aria-label]="'Remove IP entry ' + ip.value"
(click)="removeIpEntry(ip)"
[disabled]="isUpdating"
type="button"
>
<mat-icon>close</mat-icon>
</button>

View File

@@ -14,9 +14,9 @@
required
autocomplete="current-password"
/>
<mat-error *ngIf="hasError('oldPassword') as errorText">{{
errorText
}}</mat-error>
@if (hasError('oldPassword'); as errorText) {
<mat-error>{{ errorText }}</mat-error>
}
</mat-form-field>
</div>
}
@@ -30,9 +30,9 @@
required
autocomplete="new-password"
/>
<mat-error *ngIf="hasError('newPassword') as errorText">{{
errorText
}}</mat-error>
@if (hasError('newPassword'); as errorText) {
<mat-error>{{ errorText }}</mat-error>
}
</mat-form-field>
</div>
<div class="console-app__password-input-form-field">
@@ -45,9 +45,9 @@
required
autocomplete="new-password"
/>
<mat-error *ngIf="hasError('newPasswordRepeat') as errorText">{{
errorText
}}</mat-error>
@if (hasError('newPasswordRepeat'); as errorText) {
<mat-error>{{ errorText }}</mat-error>
}
</mat-form-field>
</div>
<button

View File

@@ -29,10 +29,9 @@ export const DISABLED_ELEMENTS_PER_ROLE = {
RESTRICTED_ELEMENTS.REGISTRAR_ELEMENT,
RESTRICTED_ELEMENTS.OTE,
RESTRICTED_ELEMENTS.SUSPEND,
RESTRICTED_ELEMENTS.ACTIVITY_PER_USER,
],
SUPPORT_LEAD: [],
SUPPORT_AGENT: [RESTRICTED_ELEMENTS.ACTIVITY_PER_USER],
SUPPORT_AGENT: [],
};
@Directive({

View File

@@ -50,15 +50,17 @@
<mat-icon>delete</mat-icon>
</button>
</div>
<div *ngIf="isNewUser" class="console-app__user-details-save-password">
@if (isNewUser) {
<div class="console-app__user-details-save-password">
<mat-icon>priority_high</mat-icon>
Please save the password. For your security, we do not store passwords in a
recoverable format.
</div>
<p *ngIf="isLoading">
} @if (isLoading) {
<p>
<mat-progress-bar mode="query"></mat-progress-bar>
</p>
}
<mat-card appearance="outlined">
<mat-card-content>

View File

@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { CommonModule } from '@angular/common';
import { Component, computed } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { SelectedRegistrarModule } from '../app.module';
@@ -31,7 +30,6 @@ import { UserEditFormComponent } from './userEditForm.component';
FormsModule,
MaterialModule,
SnackBarModule,
CommonModule,
SelectedRegistrarModule,
UserEditFormComponent,
],

View File

@@ -1,6 +1,7 @@
<div class="console-app__user-edit">
<form (ngSubmit)="saveEdit($event)" #form>
<p *ngIf="isNew()">
@if (isNew()) {
<p>
<mat-form-field appearance="outline">
<mat-label
>User name prefix:
@@ -19,6 +20,7 @@
/>
</mat-form-field>
</p>
}
<p>
<mat-form-field appearance="outline">
<mat-label
@@ -44,7 +46,6 @@
Save
</button>
</form>
@if(userDataService.userData()?.isAdmin) {
<button
mat-flat-button
color="primary"
@@ -53,5 +54,4 @@
>
Reset registry lock password
</button>
}
</div>

View File

@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { CommonModule } from '@angular/common';
import {
Component,
ElementRef,
@@ -50,7 +49,7 @@ import { HttpErrorResponse } from '@angular/common/http';
<button mat-button color="warn" (click)="onSave()">Confirm</button>
</mat-dialog-actions>
`,
imports: [CommonModule, MaterialModule],
imports: [MaterialModule],
})
export class ResetRegistryLockPasswordComponent {
constructor(
@@ -72,7 +71,7 @@ export class ResetRegistryLockPasswordComponent {
selector: 'app-user-edit-form',
templateUrl: './userEditForm.component.html',
styleUrls: ['./userEditForm.component.scss'],
imports: [FormsModule, MaterialModule, CommonModule],
imports: [FormsModule, MaterialModule],
providers: [],
})
export class UserEditFormComponent {

View File

@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { CommonModule } from '@angular/common';
import { HttpErrorResponse } from '@angular/common/http';
import { Component, effect } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
@@ -35,7 +34,6 @@ import { UserEditFormComponent } from './userEditForm.component';
FormsModule,
MaterialModule,
SnackBarModule,
CommonModule,
SelectedRegistrarModule,
UsersListComponent,
UserEditFormComponent,

View File

@@ -5,15 +5,14 @@
class="console-app__users-table"
matSort
>
<ng-container
*ngFor="let column of columns"
[matColumnDef]="column.columnDef"
>
@for (column of columns; track column) {
<ng-container [matColumnDef]="column.columnDef">
<mat-header-cell *matHeaderCellDef>
{{ column.header }}
</mat-header-cell>
<mat-cell *matCellDef="let row" [innerHTML]="column.cell(row)"></mat-cell>
</ng-container>
}
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row
*matRowDef="let row; columns: displayedColumns"

View File

@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { CommonModule } from '@angular/common';
import {
Component,
effect,
@@ -43,7 +42,7 @@ export const columns = [
selector: 'app-users-list',
templateUrl: './usersList.component.html',
styleUrls: ['./usersList.component.scss'],
imports: [MaterialModule, CommonModule],
imports: [MaterialModule],
providers: [],
})
export class UsersListComponent {

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { enableProdMode } from '@angular/core';
import { enableProdMode, provideZoneChangeDetection } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
@@ -23,5 +23,7 @@ if (environment.production || environment.sandbox) {
}
platformBrowserDynamic()
.bootstrapModule(AppModule)
.bootstrapModule(AppModule, {
applicationProviders: [provideZoneChangeDetection()],
})
.catch((err) => console.error(err));

View File

@@ -14,14 +14,10 @@
"sourceMap": true,
"declaration": false,
"experimentalDecorators": true,
"moduleResolution": "node",
"moduleResolution": "bundler",
"importHelpers": true,
"target": "ES2022",
"module": "es2020",
"lib": [
"es2020",
"dom"
],
"useDefineForClassFields": false
},
"angularCompilerOptions": {

View File

@@ -67,14 +67,11 @@ 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.contact.Contact;
import google.registry.model.domain.DesignatedContact;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainCommand.Create;
import google.registry.model.domain.DomainCommand.CreateOrUpdate;
@@ -421,33 +418,6 @@ public class DomainFlowUtils {
}
}
/** Enforces absence of contact data on creation as part of the Minimum Dataset requirements. */
static void enforceContactAbsencesOnCreate(Create create)
throws ParameterValuePolicyErrorException {
enforceContactAbsences(create.getRegistrant(), create.getContacts());
}
/** Enforces absence of contact data on update as part of the Minimum Dataset requirements. */
static void enforceContactAbsencesOnUpdate(Update update)
throws ParameterValuePolicyErrorException {
Set<DesignatedContact> allDesignatedContacts =
Sets.union(update.getInnerAdd().getContacts(), update.getInnerRemove().getContacts());
enforceContactAbsences(update.getInnerChange().getRegistrant(), allDesignatedContacts);
}
/** Enforces the absence of contact data as part of the Minimum Dataset requirements. */
static void enforceContactAbsences(
Optional<VKey<Contact>> registrant, Set<DesignatedContact> contacts)
throws ParameterValuePolicyErrorException {
if (registrant.isPresent()) {
throw new RegistrantProhibitedException();
}
if (!contacts.isEmpty()) {
throw new ContactsProhibitedException();
}
}
static void validateNameserversAllowedOnTld(String tld, Set<String> fullyQualifiedHostNames)
throws EppException {
ImmutableSet<String> allowedHostNames = Tld.get(tld).getAllowedFullyQualifiedHostNames();
@@ -971,7 +941,6 @@ public class DomainFlowUtils {
Create command, Tld tld, InternetDomainName domainName) throws EppException {
verifyNotInPendingDelete(command.getNameservers());
String tldStr = tld.getTldStr();
enforceContactAbsencesOnCreate(command);
ImmutableSet<String> hostNames = command.getNameserverHostNames();
validateNameserversCountForTld(tldStr, domainName, hostNames.size());
validateNameserversAllowedOnTld(tldStr, hostNames);

View File

@@ -76,7 +76,7 @@ import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import google.registry.model.tld.Tld;
import google.registry.model.transfer.DomainTransferData;
import google.registry.model.transfer.TransferData.TransferServerApproveEntity;
import google.registry.model.transfer.DomainTransferData.TransferServerApproveEntity;
import google.registry.model.transfer.TransferResponse.DomainTransferResponse;
import google.registry.model.transfer.TransferStatus;
import jakarta.inject.Inject;

View File

@@ -36,8 +36,7 @@ import google.registry.model.poll.PollMessage;
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
import google.registry.model.tld.Tld;
import google.registry.model.transfer.DomainTransferData;
import google.registry.model.transfer.TransferData;
import google.registry.model.transfer.TransferData.TransferServerApproveEntity;
import google.registry.model.transfer.DomainTransferData.TransferServerApproveEntity;
import google.registry.model.transfer.TransferResponse.DomainTransferResponse;
import google.registry.model.transfer.TransferStatus;
import google.registry.persistence.VKey;
@@ -51,7 +50,7 @@ import org.joda.time.DateTime;
*/
public final class DomainTransferUtils {
/** Sets up {@link TransferData} for a domain with links to entities for server approval. */
/** Sets up {@link DomainTransferData} for a domain with links to entities for server approval. */
public static DomainTransferData createPendingTransferData(
String domainRepoId,
Long historyId,
@@ -179,7 +178,7 @@ public final class DomainTransferUtils {
/** Create a poll message for the gaining client in a transfer. */
public static PollMessage createGainingTransferPollMessage(
String targetId,
TransferData transferData,
DomainTransferData transferData,
@Nullable DateTime extendedRegistrationExpirationTime,
DateTime now,
HistoryEntryId domainHistoryId) {
@@ -202,7 +201,7 @@ public final class DomainTransferUtils {
/** Create a poll message for the losing client in a transfer. */
public static PollMessage createLosingTransferPollMessage(
String targetId,
TransferData transferData,
DomainTransferData transferData,
@Nullable DateTime extendedRegistrationExpirationTime,
HistoryEntryId domainHistoryId) {
return new PollMessage.OneTime.Builder()
@@ -216,10 +215,10 @@ public final class DomainTransferUtils {
.build();
}
/** Create a {@link DomainTransferResponse} off of the info in a {@link TransferData}. */
/** Create a {@link DomainTransferResponse} off of the info in a {@link DomainTransferData}. */
static DomainTransferResponse createTransferResponse(
String targetId,
TransferData transferData,
DomainTransferData transferData,
@Nullable DateTime extendedRegistrationExpirationTime) {
return new DomainTransferResponse.Builder()
.setDomainName(targetId)

View File

@@ -29,7 +29,6 @@ import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfo;
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.enforceContactAbsencesOnUpdate;
import static google.registry.flows.domain.DomainFlowUtils.updateDsData;
import static google.registry.flows.domain.DomainFlowUtils.validateDsData;
import static google.registry.flows.domain.DomainFlowUtils.validateFeesAckedIfPresent;
@@ -249,8 +248,6 @@ public final class DomainUpdateFlow implements MutatingFlow {
ext.getRemove().map(Remove::getDsData).orElse(ImmutableSet.of()));
}
Change change = command.getInnerChange();
enforceContactAbsencesOnUpdate(command);
Domain.Builder domainBuilder =
domain
.asBuilder()

View File

@@ -34,7 +34,6 @@ import com.google.common.collect.ImmutableSet;
import com.google.gson.annotations.Expose;
import google.registry.config.RegistryConfig;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.transfer.TransferData;
import google.registry.persistence.VKey;
import google.registry.util.NonFinalForTesting;
import jakarta.persistence.Access;
@@ -207,27 +206,6 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
/** EppResources that are loaded via foreign keys should implement this marker interface. */
public interface ForeignKeyedEppResource {}
/** An interface for resources that have transfer data. */
public interface ResourceWithTransferData<T extends TransferData> {
T getTransferData();
/**
* The time that this resource was last transferred.
*
* <p>Can be null if the resource has never been transferred.
*/
DateTime getLastTransferTime();
}
/** An interface for builders of resources that have transfer data. */
public interface BuilderWithTransferData<
T extends TransferData, B extends BuilderWithTransferData<T, B>> {
B setTransferData(T transferData);
/** Set the time when this resource was transferred. */
B setLastTransferTime(DateTime lastTransferTime);
}
/** Abstract builder for {@link EppResource} types. */
public abstract static class Builder<T extends EppResource, B extends Builder<T, B>>
extends GenericBuilder<T, B> {

View File

@@ -29,7 +29,6 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import google.registry.config.RegistryConfig;
import google.registry.model.contact.Contact;
import google.registry.model.domain.Domain;
import google.registry.model.host.Host;
import google.registry.persistence.VKey;
@@ -60,7 +59,6 @@ public final class ForeignKeyUtils {
private static final ImmutableMap<Class<? extends EppResource>, String>
RESOURCE_TYPE_TO_FK_PROPERTY =
ImmutableMap.of(
Contact.class, "contactId",
Domain.class, "domainName",
Host.class, "hostName");

View File

@@ -29,7 +29,6 @@ import google.registry.model.poll.PendingActionNotificationResponse.DomainPendin
import google.registry.model.poll.PollMessage;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.transfer.DomainTransferData;
import google.registry.model.transfer.TransferData;
import google.registry.model.transfer.TransferResponse;
import google.registry.model.transfer.TransferResponse.DomainTransferResponse;
import google.registry.model.transfer.TransferStatus;
@@ -79,7 +78,7 @@ public final class ResourceTransferUtils {
if (!domain.getStatusValues().contains(StatusValue.PENDING_TRANSFER)) {
return;
}
TransferData oldTransferData = domain.getTransferData();
DomainTransferData oldTransferData = domain.getTransferData();
tm().delete(oldTransferData.getServerApproveEntities());
tm().put(
new PollMessage.OneTime.Builder()
@@ -99,8 +98,8 @@ public final class ResourceTransferUtils {
* Turn a domain into a builder with its pending transfer resolved.
*
* <p>This removes the {@link StatusValue#PENDING_TRANSFER} status, sets the {@link
* TransferStatus}, clears all the server-approve fields on the {@link TransferData}, and sets the
* expiration time of the last pending transfer to now.
* TransferStatus}, clears all the server-approve fields on the {@link DomainTransferData}, and
* sets the expiration time of the last pending transfer to now.
*/
private static Domain.Builder resolvePendingTransfer(
Domain domain, TransferStatus transferStatus, DateTime now) {
@@ -125,9 +124,9 @@ public final class ResourceTransferUtils {
* Resolve a pending transfer by awarding it to the gaining client.
*
* <p>This removes the {@link StatusValue#PENDING_TRANSFER} status, sets the {@link
* TransferStatus}, clears all the server-approve fields on the {@link TransferData}, sets the new
* client id, and sets the last transfer time and the expiration time of the last pending transfer
* to now.
* TransferStatus}, clears all the server-approve fields on the {@link DomainTransferData}, sets
* the new client id, and sets the last transfer time and the expiration time of the last pending
* transfer to now.
*/
public static Domain approvePendingTransfer(
Domain domain, TransferStatus transferStatus, DateTime now) {
@@ -143,9 +142,9 @@ public final class ResourceTransferUtils {
* Resolve a pending transfer by denying it.
*
* <p>This removes the {@link StatusValue#PENDING_TRANSFER} status, sets the {@link
* TransferStatus}, clears all the server-approve fields on the {@link TransferData}, sets the
* expiration time of the last pending transfer to now, sets the last EPP update time to now, and
* sets the last EPP update client id to the given client id.
* TransferStatus}, clears all the server-approve fields on the {@link DomainTransferData}, sets
* the expiration time of the last pending transfer to now, sets the last EPP update time to now,
* and sets the last EPP update client id to the given client id.
*/
public static Domain denyPendingTransfer(
Domain domain, TransferStatus transferStatus, DateTime now, String lastEppUpdateRegistrarId) {

View File

@@ -26,7 +26,7 @@ import google.registry.model.UnsafeSerializable;
import google.registry.model.annotations.IdAllocation;
import google.registry.model.domain.DomainHistory;
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
import google.registry.model.transfer.TransferData.TransferServerApproveEntity;
import google.registry.model.transfer.DomainTransferData.TransferServerApproveEntity;
import google.registry.persistence.VKey;
import jakarta.persistence.Column;
import jakarta.persistence.EnumType;

View File

@@ -1,96 +0,0 @@
// Copyright 2017 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.model.contact;
import google.registry.model.EppResource.ForeignKeyedEppResource;
import google.registry.model.annotations.ExternalMessagingName;
import google.registry.persistence.VKey;
import google.registry.persistence.WithVKey;
import jakarta.persistence.Access;
import jakarta.persistence.AccessType;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Index;
import jakarta.persistence.Table;
/**
* A persistable contact resource including mutable and non-mutable fields.
*
* @see <a href="https://tools.ietf.org/html/rfc5733">RFC 5733</a>
*/
@Entity
@Table(
name = "Contact",
indexes = {
@Index(columnList = "creationTime"),
@Index(columnList = "currentSponsorRegistrarId"),
@Index(columnList = "deletionTime"),
@Index(columnList = "contactId"),
@Index(columnList = "searchName")
})
@ExternalMessagingName("contact")
@WithVKey(String.class)
@Access(AccessType.FIELD)
public class Contact extends ContactBase implements ForeignKeyedEppResource {
@Override
public VKey<Contact> createVKey() {
return VKey.create(Contact.class, getRepoId());
}
@Override
@Id
@Access(AccessType.PROPERTY)
public String getRepoId() {
return super.getRepoId();
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
}
/** A builder for constructing {@link Contact}, since it is immutable. */
public static class Builder extends ContactBase.Builder<Contact, Builder> {
public Builder() {}
private Builder(Contact instance) {
super(instance);
}
public Builder copyFrom(ContactBase contactBase) {
return this.setAuthInfo(contactBase.getAuthInfo())
.setContactId(contactBase.getContactId())
.setCreationRegistrarId(contactBase.getCreationRegistrarId())
.setCreationTime(contactBase.getCreationTime())
.setDeletionTime(contactBase.getDeletionTime())
.setDisclose(contactBase.getDisclose())
.setEmailAddress(contactBase.getEmailAddress())
.setFaxNumber(contactBase.getFaxNumber())
.setInternationalizedPostalInfo(contactBase.getInternationalizedPostalInfo())
.setLastTransferTime(contactBase.getLastTransferTime())
.setLastEppUpdateRegistrarId(contactBase.getLastEppUpdateRegistrarId())
.setLastEppUpdateTime(contactBase.getLastEppUpdateTime())
.setLocalizedPostalInfo(contactBase.getLocalizedPostalInfo())
.setPersistedCurrentSponsorRegistrarId(
contactBase.getPersistedCurrentSponsorRegistrarId())
.setRepoId(contactBase.getRepoId())
.setStatusValues(contactBase.getStatusValues())
.setTransferData(contactBase.getTransferData())
.setVoiceNumber(contactBase.getVoiceNumber());
}
}
}

View File

@@ -1,388 +0,0 @@
// Copyright 2020 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.model.contact;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.common.collect.ImmutableList;
import google.registry.model.EppResource;
import google.registry.model.EppResource.ResourceWithTransferData;
import google.registry.model.transfer.ContactTransferData;
import google.registry.persistence.VKey;
import jakarta.persistence.Access;
import jakarta.persistence.AccessType;
import jakarta.persistence.AttributeOverride;
import jakarta.persistence.AttributeOverrides;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Embedded;
import jakarta.persistence.MappedSuperclass;
import jakarta.xml.bind.annotation.XmlElement;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
import org.joda.time.DateTime;
/**
* A persistable contact resource including mutable and non-mutable fields.
*
* <p>This class deliberately does not include an {@link jakarta.persistence.Id} so that any
* foreign-keyed fields can refer to the proper parent entity's ID, whether we're storing this in
* the DB itself or as part of another entity
*
* @see <a href="https://tools.ietf.org/html/rfc5733">RFC 5733</a>
*/
@MappedSuperclass
@Embeddable
@Access(AccessType.FIELD)
public class ContactBase extends EppResource
implements ResourceWithTransferData<ContactTransferData> {
/**
* Unique identifier for this contact.
*
* <p>This is only unique in the sense that for any given lifetime specified as the time range
* from (creationTime, deletionTime) there can only be one contact in the database with this id.
* However, there can be many contacts with the same id and non-overlapping lifetimes.
*/
String contactId;
/**
* Localized postal info for the contact. All contained values must be representable in the 7-bit
* US-ASCII character set. Personal info; cleared by {@link Contact.Builder#wipeOut}.
*/
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "name", column = @Column(name = "addr_local_name")),
@AttributeOverride(name = "org", column = @Column(name = "addr_local_org")),
@AttributeOverride(name = "type", column = @Column(name = "addr_local_type")),
@AttributeOverride(
name = "address.streetLine1",
column = @Column(name = "addr_local_street_line1")),
@AttributeOverride(
name = "address.streetLine2",
column = @Column(name = "addr_local_street_line2")),
@AttributeOverride(
name = "address.streetLine3",
column = @Column(name = "addr_local_street_line3")),
@AttributeOverride(name = "address.city", column = @Column(name = "addr_local_city")),
@AttributeOverride(name = "address.state", column = @Column(name = "addr_local_state")),
@AttributeOverride(name = "address.zip", column = @Column(name = "addr_local_zip")),
@AttributeOverride(
name = "address.countryCode",
column = @Column(name = "addr_local_country_code"))
})
PostalInfo localizedPostalInfo;
/**
* Internationalized postal info for the contact. Personal info; cleared by {@link
* Contact.Builder#wipeOut}.
*/
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "name", column = @Column(name = "addr_i18n_name")),
@AttributeOverride(name = "org", column = @Column(name = "addr_i18n_org")),
@AttributeOverride(name = "type", column = @Column(name = "addr_i18n_type")),
@AttributeOverride(
name = "address.streetLine1",
column = @Column(name = "addr_i18n_street_line1")),
@AttributeOverride(
name = "address.streetLine2",
column = @Column(name = "addr_i18n_street_line2")),
@AttributeOverride(
name = "address.streetLine3",
column = @Column(name = "addr_i18n_street_line3")),
@AttributeOverride(name = "address.city", column = @Column(name = "addr_i18n_city")),
@AttributeOverride(name = "address.state", column = @Column(name = "addr_i18n_state")),
@AttributeOverride(name = "address.zip", column = @Column(name = "addr_i18n_zip")),
@AttributeOverride(
name = "address.countryCode",
column = @Column(name = "addr_i18n_country_code"))
})
PostalInfo internationalizedPostalInfo;
/**
* Contact name used for name searches. This is set automatically to be the internationalized
* postal name, or if null, the localized postal name, or if that is null as well, null. Personal
* info; cleared by {@link Contact.Builder#wipeOut}.
*/
String searchName;
/** Contacts voice number. Personal info; cleared by {@link Contact.Builder#wipeOut}. */
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "phoneNumber", column = @Column(name = "voice_phone_number")),
@AttributeOverride(name = "extension", column = @Column(name = "voice_phone_extension")),
})
ContactPhoneNumber voice;
/** Contacts fax number. Personal info; cleared by {@link Contact.Builder#wipeOut}. */
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "phoneNumber", column = @Column(name = "fax_phone_number")),
@AttributeOverride(name = "extension", column = @Column(name = "fax_phone_extension")),
})
ContactPhoneNumber fax;
/** Contacts email address. Personal info; cleared by {@link Contact.Builder#wipeOut}. */
String email;
/** Authorization info (aka transfer secret) of the contact. */
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "pw.value", column = @Column(name = "auth_info_value")),
@AttributeOverride(name = "pw.repoId", column = @Column(name = "auth_info_repo_id")),
})
ContactAuthInfo authInfo;
/** Data about any pending or past transfers on this contact. */
ContactTransferData transferData;
/**
* The time that this resource was last transferred.
*
* <p>Can be null if the resource has never been transferred.
*/
DateTime lastTransferTime;
// If any new fields are added which contain personal information, make sure they are cleared by
// the wipeOut() function, so that data is not kept around for deleted contacts.
/** Disclosure policy. */
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "name", column = @Column(name = "disclose_types_name")),
@AttributeOverride(name = "org", column = @Column(name = "disclose_types_org")),
@AttributeOverride(name = "addr", column = @Column(name = "disclose_types_addr")),
@AttributeOverride(name = "flag", column = @Column(name = "disclose_mode_flag")),
@AttributeOverride(name = "voice.marked", column = @Column(name = "disclose_show_voice")),
@AttributeOverride(name = "fax.marked", column = @Column(name = "disclose_show_fax")),
@AttributeOverride(name = "email.marked", column = @Column(name = "disclose_show_email"))
})
Disclose disclose;
@Override
public VKey<? extends ContactBase> createVKey() {
throw new UnsupportedOperationException(
"ContactBase is not an actual persisted entity you can create a key to;"
+ " use Contact instead");
}
public String getContactId() {
return contactId;
}
public PostalInfo getLocalizedPostalInfo() {
return localizedPostalInfo;
}
public PostalInfo getInternationalizedPostalInfo() {
return internationalizedPostalInfo;
}
public String getSearchName() {
return searchName;
}
public ContactPhoneNumber getVoiceNumber() {
return voice;
}
public ContactPhoneNumber getFaxNumber() {
return fax;
}
public String getEmailAddress() {
return email;
}
public ContactAuthInfo getAuthInfo() {
return authInfo;
}
public Disclose getDisclose() {
return disclose;
}
public String getCurrentSponsorRegistrarId() {
return getPersistedCurrentSponsorRegistrarId();
}
@Override
public ContactTransferData getTransferData() {
return Optional.ofNullable(transferData).orElse(ContactTransferData.EMPTY);
}
@Override
public DateTime getLastTransferTime() {
return lastTransferTime;
}
@Override
public String getForeignKey() {
return contactId;
}
/**
* Postal info for the contact.
*
* <p>The XML marshalling expects the {@link PostalInfo} objects in a list, but we can't actually
* persist them directly due to legacy reasons (Objectify can't handle collections of embedded
* objects that themselves contain collections, and there's a list of streets inside). This method
* transforms the persisted format to the XML format for marshalling.
*/
@XmlElement(name = "postalInfo")
public ImmutableList<PostalInfo> getPostalInfosAsList() {
return Stream.of(localizedPostalInfo, internationalizedPostalInfo)
.filter(Objects::nonNull)
.collect(toImmutableList());
}
@Override
public ContactBase cloneProjectedAtTime(DateTime now) {
// Contacts no longer exist and thus do not need to be projected
return this;
}
@Override
public Builder<? extends ContactBase, ?> asBuilder() {
return new Builder<>(clone(this));
}
/** A builder for constructing {@link Contact}, since it is immutable. */
public static class Builder<T extends ContactBase, B extends Builder<T, B>>
extends EppResource.Builder<T, B> implements BuilderWithTransferData<ContactTransferData, B> {
public Builder() {}
protected Builder(T instance) {
super(instance);
}
public B setContactId(String contactId) {
getInstance().contactId = contactId;
return thisCastToDerived();
}
public B setLocalizedPostalInfo(PostalInfo localizedPostalInfo) {
checkArgument(
localizedPostalInfo == null
|| PostalInfo.Type.LOCALIZED.equals(localizedPostalInfo.getType()));
getInstance().localizedPostalInfo = localizedPostalInfo;
return thisCastToDerived();
}
public B setInternationalizedPostalInfo(PostalInfo internationalizedPostalInfo) {
checkArgument(
internationalizedPostalInfo == null
|| PostalInfo.Type.INTERNATIONALIZED.equals(internationalizedPostalInfo.getType()));
getInstance().internationalizedPostalInfo = internationalizedPostalInfo;
return thisCastToDerived();
}
public B overlayLocalizedPostalInfo(PostalInfo localizedPostalInfo) {
return setLocalizedPostalInfo(
getInstance().localizedPostalInfo == null
? localizedPostalInfo
: getInstance().localizedPostalInfo.overlay(localizedPostalInfo));
}
public B overlayInternationalizedPostalInfo(PostalInfo internationalizedPostalInfo) {
return setInternationalizedPostalInfo(
getInstance().internationalizedPostalInfo == null
? internationalizedPostalInfo
: getInstance().internationalizedPostalInfo.overlay(internationalizedPostalInfo));
}
public B setVoiceNumber(ContactPhoneNumber voiceNumber) {
if (voiceNumber != null && voiceNumber.hasNullFields()) {
voiceNumber = null;
}
getInstance().voice = voiceNumber;
return thisCastToDerived();
}
public B setFaxNumber(ContactPhoneNumber faxNumber) {
if (faxNumber != null && faxNumber.hasNullFields()) {
faxNumber = null;
}
getInstance().fax = faxNumber;
return thisCastToDerived();
}
public B setEmailAddress(String emailAddress) {
getInstance().email = emailAddress;
return thisCastToDerived();
}
public B setAuthInfo(ContactAuthInfo authInfo) {
getInstance().authInfo = authInfo;
return thisCastToDerived();
}
public B setDisclose(Disclose disclose) {
getInstance().disclose = disclose;
return thisCastToDerived();
}
@Override
public B setTransferData(ContactTransferData transferData) {
getInstance().transferData = transferData;
return thisCastToDerived();
}
@Override
public B setLastTransferTime(DateTime lastTransferTime) {
getInstance().lastTransferTime = lastTransferTime;
return thisCastToDerived();
}
/**
* Remove all personally identifying information about a contact.
*
* <p>This should be used when deleting a contact so that the soft-deleted entity doesn't
* contain information that the registrant requested to be deleted.
*/
public B wipeOut() {
setEmailAddress(null);
setFaxNumber(null);
setInternationalizedPostalInfo(null);
setLocalizedPostalInfo(null);
setVoiceNumber(null);
return thisCastToDerived();
}
@Override
public T build() {
T instance = getInstance();
// If TransferData is totally empty, set it to null.
if (ContactTransferData.EMPTY.equals(instance.transferData)) {
setTransferData(null);
}
// Set the searchName using the internationalized and localized postal info names.
if ((instance.internationalizedPostalInfo != null)
&& (instance.internationalizedPostalInfo.getName() != null)) {
instance.searchName = instance.internationalizedPostalInfo.getName();
} else if ((instance.localizedPostalInfo != null)
&& (instance.localizedPostalInfo.getName() != null)) {
instance.searchName = instance.localizedPostalInfo.getName();
} else {
instance.searchName = null;
}
return super.build();
}
}
}

View File

@@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkState;
import static google.registry.util.CollectionUtils.nullToEmpty;
import com.google.common.collect.Maps;
import google.registry.model.EppResource;
import google.registry.model.ImmutableObject;
import google.registry.model.contact.PostalInfo.Type;
import google.registry.model.eppinput.ResourceCommand.AbstractSingleResourceCommand;
@@ -34,13 +35,13 @@ import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import java.util.List;
import java.util.Map;
/** A collection of {@link Contact} commands. */
/** A collection of (vestigial) Contact commands. */
public class ContactCommand {
/** The fields on "chgType" from <a href="http://tools.ietf.org/html/rfc5733">RFC5733</a>. */
@XmlTransient
public static class ContactCreateOrChange extends ImmutableObject
implements ResourceCreateOrChange<Contact.Builder> {
implements ResourceCreateOrChange<EppResource.Builder<?, ?>> {
/** Postal info for the contact. */
List<PostalInfo> postalInfo;
@@ -111,13 +112,13 @@ public class ContactCommand {
}
/**
* A create command for a {@link Contact}, mapping "createType" from <a
* A create command for a (vestigial) Contact, mapping "createType" from <a
* href="http://tools.ietf.org/html/rfc5733">RFC5733</a>}.
*/
@XmlType(propOrder = {"contactId", "postalInfo", "voice", "fax", "email", "authInfo", "disclose"})
@XmlRootElement
public static class Create extends ContactCreateOrChange
implements SingleResourceCommand, ResourceCreateOrChange<Contact.Builder> {
implements SingleResourceCommand, ResourceCreateOrChange<EppResource.Builder<?, ?>> {
/**
* Unique identifier for this contact.
*
@@ -139,29 +140,29 @@ public class ContactCommand {
}
}
/** A delete command for a {@link Contact}. */
/** A delete command for a (vestigial) Contact. */
@XmlRootElement
public static class Delete extends AbstractSingleResourceCommand {}
/** An info request for a {@link Contact}. */
/** An info request for a (vestigial) Contact. */
@XmlRootElement
@XmlType(propOrder = {"targetId", "authInfo"})
public static class Info extends AbstractContactAuthCommand {}
/** A check request for {@link Contact}. */
/** A check request for (vestigial) Contact. */
@XmlRootElement
public static class Check extends ResourceCheck {}
/** A transfer operation for a {@link Contact}. */
/** A transfer operation for a (vestigial) Contact. */
@XmlRootElement
@XmlType(propOrder = {"targetId", "authInfo"})
public static class Transfer extends AbstractContactAuthCommand {}
/** An update to a {@link Contact}. */
/** An update to a (vestigial) Contact. */
@XmlRootElement
@XmlType(propOrder = {"targetId", "innerAdd", "innerRemove", "innerChange"})
public static class Update
extends ResourceUpdate<Update.AddRemove, Contact.Builder, Update.Change> {
extends ResourceUpdate<Update.AddRemove, EppResource.Builder<?, ?>, Update.Change> {
@XmlElement(name = "chg")
protected Change innerChange;

View File

@@ -1,102 +0,0 @@
// Copyright 2020 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.model.contact;
import google.registry.model.EppResource;
import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.VKey;
import jakarta.persistence.Access;
import jakarta.persistence.AccessType;
import jakarta.persistence.AttributeOverride;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Index;
import jakarta.persistence.Table;
import java.util.Optional;
import javax.annotation.Nullable;
/**
* A persisted history entry representing an EPP modification to a contact.
*
* <p>In addition to the general history fields (e.g. action time, registrar ID) we also persist a
* copy of the contact entity at this point in time. We persist a raw {@link ContactBase} so that
* the foreign-keyed fields in that class can refer to this object.
*/
@Entity
@Table(
indexes = {
@Index(columnList = "creationTime"),
@Index(columnList = "historyRegistrarId"),
@Index(columnList = "historyType"),
@Index(columnList = "historyModificationTime")
})
@AttributeOverride(name = "repoId", column = @Column(name = "contactRepoId"))
@Access(AccessType.FIELD)
public class ContactHistory extends HistoryEntry {
// Store ContactBase instead of Contact, so we don't pick up its @Id
// @Nullable for the sake of pre-Registry-3.0 history objects
@Nullable ContactBase resource;
@Override
protected ContactBase getResource() {
return resource;
}
/**
* The values of all the fields on the {@link ContactBase} object after the action represented by
* this history object was executed.
*
* <p>Will be absent for objects created prior to the Registry 3.0 SQL migration.
*/
public Optional<ContactBase> getContactBase() {
return Optional.ofNullable(resource);
}
/** Creates a {@link VKey} instance for this entity. */
@Override
public VKey<ContactHistory> createVKey() {
return VKey.create(ContactHistory.class, getHistoryEntryId());
}
@Override
public Optional<? extends EppResource> getResourceAtPointInTime() {
return getContactBase().map(contactBase -> new Contact.Builder().copyFrom(contactBase).build());
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
}
public static class Builder extends HistoryEntry.Builder<ContactHistory, ContactHistory.Builder> {
public Builder() {}
public Builder(ContactHistory instance) {
super(instance);
}
public Builder setContact(ContactBase contactBase) {
getInstance().resource = contactBase;
return setRepoId(contactBase);
}
public Builder wipeOutPii() {
getInstance().resource = getInstance().resource.asBuilder().wipeOut().build();
return this;
}
}
}

View File

@@ -20,11 +20,9 @@ import jakarta.persistence.Embeddable;
/**
* EPP Contact Phone Number
*
* <p>This class is embedded inside a {@link Contact} hold the phone number of an EPP contact. The
* fields are all defined in the parent class {@link PhoneNumber}, but the subclass is still
* necessary to pick up the contact namespace.
*
* @see Contact
* <p>This class is embedded inside a (vestigial) Contact to hold the phone number of an EPP
* contact. The fields are all defined in the parent class {@link PhoneNumber}, but the subclass is
* still necessary to pick up the contact namespace.
*/
@Embeddable
public class ContactPhoneNumber extends PhoneNumber {

View File

@@ -1,80 +0,0 @@
// Copyright 2017 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.model.domain;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import google.registry.model.contact.Contact;
import google.registry.persistence.VKey;
import jakarta.persistence.Embeddable;
import jakarta.xml.bind.annotation.XmlEnumValue;
/**
* Persisted type for storing a domain's contact associations.
*
* <p>A contact association on a domain consists of the contact key and the contact "type", which is
* the designated role of this contact with respect to this domain. When converting to and from EPP
* XML, we use {@link ForeignKeyedDesignatedContact} to replace the contact's primary key with its
* foreign key, since that is what EPP exposes.
*
* <p>Note one could in principle store contact foreign keys here in addition to keys, unlike the
* situation with hosts where client-side renames would make that data stale. However, we sometimes
* rename contacts internally ourselves, and it's easier to use the same model for both cases.
*
* <p>This entity type is not persisted in Cloud SQL. The different roles are represented as
* separate fields in the Domain table.
*
* @see <a href="http://tools.ietf.org/html/rfc5731#section-2.2">RFC 5731 - EPP Domain Name Mapping
* - Contact and Client Identifiers</a>
*/
@Embeddable
public class DesignatedContact extends ImmutableObject implements UnsafeSerializable {
/**
* XML type for contact types. This can be either: {@code "admin"}, {@code "billing"}, or
* {@code "tech"} and corresponds to {@code contactAttrType} in {@code domain-1.0.xsd}.
*/
public enum Type {
@XmlEnumValue("admin")
ADMIN,
@XmlEnumValue("billing")
BILLING,
@XmlEnumValue("tech")
TECH,
/** The registrant type is not reflected in XML and exists only for internal use. */
REGISTRANT
}
public static DesignatedContact create(Type type, VKey<Contact> contact) {
DesignatedContact instance = new DesignatedContact();
instance.type = type;
instance.contactVKey = checkArgumentNotNull(contact, "Must specify contact key");
return instance;
}
Type type;
VKey<Contact> contactVKey;
public Type getType() {
return type;
}
public VKey<Contact> getContactKey() {
return contactVKey;
}
}

View File

@@ -178,7 +178,6 @@ public class Domain extends DomainBase implements ForeignKeyedEppResource {
.setAutorenewPollMessage(domainBase.getAutorenewPollMessage())
.setAutorenewBillingEvent(domainBase.getAutorenewBillingEvent())
.setAutorenewEndTime(domainBase.getAutorenewEndTime())
.setContacts(domainBase.getContacts())
.setCreationRegistrarId(domainBase.getCreationRegistrarId())
.setCreationTime(domainBase.getCreationTime())
.setDomainName(domainBase.getDomainName())
@@ -193,7 +192,6 @@ public class Domain extends DomainBase implements ForeignKeyedEppResource {
.setLastEppUpdateTime(domainBase.getLastEppUpdateTime())
.setNameservers(domainBase.getNameservers())
.setPersistedCurrentSponsorRegistrarId(domainBase.getPersistedCurrentSponsorRegistrarId())
.setRegistrant(domainBase.getRegistrant())
.setRegistrationExpirationTime(domainBase.getRegistrationExpirationTime())
.setRepoId(domainBase.getRepoId())
.setSmdId(domainBase.getSmdId())

View File

@@ -43,10 +43,7 @@ import com.google.common.collect.Sets;
import com.google.gson.annotations.Expose;
import google.registry.flows.ResourceFlowUtils;
import google.registry.model.EppResource;
import google.registry.model.EppResource.ResourceWithTransferData;
import google.registry.model.billing.BillingRecurrence;
import google.registry.model.contact.Contact;
import google.registry.model.domain.DesignatedContact.Type;
import google.registry.model.domain.launch.LaunchNotice;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.domain.secdns.DomainDsData;
@@ -79,10 +76,8 @@ import jakarta.persistence.Id;
import jakarta.persistence.MappedSuperclass;
import jakarta.persistence.Transient;
import java.util.HashSet;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import javax.annotation.Nullable;
import org.hibernate.collection.spi.PersistentSet;
import org.joda.time.DateTime;
@@ -100,8 +95,7 @@ import org.joda.time.Interval;
@MappedSuperclass
@Embeddable
@Access(AccessType.FIELD)
public class DomainBase extends EppResource
implements ResourceWithTransferData<DomainTransferData> {
public class DomainBase extends EppResource {
/** The max number of years that a domain can be registered for, as set by ICANN policy. */
public static final int MAX_REGISTRATION_YEARS = 10;
@@ -131,12 +125,12 @@ public class DomainBase extends EppResource
/** References to hosts that are the nameservers for the domain. */
@Expose @Transient Set<VKey<Host>> nsHosts;
/** Contacts. */
@Expose @Nullable VKey<Contact> adminContact;
/** Contacts keys are kept around for vestigial purposes for now. */
@Expose @Nullable String adminContact;
@Expose @Nullable VKey<Contact> billingContact;
@Expose @Nullable VKey<Contact> techContact;
@Expose @Nullable VKey<Contact> registrantContact;
@Expose @Nullable String billingContact;
@Expose @Nullable String techContact;
@Expose @Nullable String registrantContact;
/** Authorization info (aka transfer secret) of the domain. */
@Embedded
@@ -323,12 +317,10 @@ public class DomainBase extends EppResource
return Optional.ofNullable(autorenewEndTime.equals(END_OF_TIME) ? null : autorenewEndTime);
}
@Override
public DomainTransferData getTransferData() {
return Optional.ofNullable(transferData).orElse(DomainTransferData.EMPTY);
}
@Override
public DateTime getLastTransferTime() {
return lastTransferTime;
}
@@ -586,120 +578,21 @@ public class DomainBase extends EppResource
.collect(toImmutableSortedSet(Ordering.natural())));
}
/** A key to the registrant who registered this domain. */
public Optional<VKey<Contact>> getRegistrant() {
return Optional.ofNullable(registrantContact);
}
public Optional<VKey<Contact>> getAdminContact() {
return Optional.ofNullable(adminContact);
}
public Optional<VKey<Contact>> getBillingContact() {
return Optional.ofNullable(billingContact);
}
public Optional<VKey<Contact>> getTechContact() {
return Optional.ofNullable(techContact);
}
/**
* Associated contacts for the domain (other than registrant).
*
* <p>Note: This can be an empty set if no contacts are present for the domain.
*/
public ImmutableSet<DesignatedContact> getContacts() {
return getAllContacts(false);
}
/**
* Gets all associated contacts for the domain, including the registrant.
*
* <p>Note: This can be an empty set if no contacts are present for the domain.
*/
public ImmutableSet<DesignatedContact> getAllContacts() {
return getAllContacts(true);
}
@Nullable
public DomainAuthInfo getAuthInfo() {
return authInfo;
}
/**
* Returns all referenced contacts from this domain.
*
* <p>Note: This can be an empty set if no contacts are present for the domain.
*/
public ImmutableSet<VKey<Contact>> getReferencedContacts() {
return nullToEmptyImmutableCopy(getAllContacts(true)).stream()
.map(DesignatedContact::getContactKey)
.filter(Objects::nonNull)
.collect(toImmutableSet());
}
private ImmutableSet<DesignatedContact> getAllContacts(boolean includeRegistrant) {
ImmutableSet.Builder<DesignatedContact> builder = new ImmutableSet.Builder<>();
if (includeRegistrant) {
getRegistrant().ifPresent(c -> builder.add(DesignatedContact.create(Type.REGISTRANT, c)));
}
getAdminContact().ifPresent(c -> builder.add(DesignatedContact.create(Type.ADMIN, c)));
getBillingContact().ifPresent(c -> builder.add(DesignatedContact.create(Type.BILLING, c)));
getTechContact().ifPresent(c -> builder.add(DesignatedContact.create(Type.TECH, c)));
return builder.build();
}
public String getTld() {
return tld;
}
/**
* Sets the individual contact fields from {@code contacts}.
*
* <p>The registrant field is only set if {@code includeRegistrant} is true, as this field needs
* to be set in some circumstances but not in others.
*/
void setContactFields(Set<DesignatedContact> contacts, boolean includeRegistrant) {
// Set the individual contact fields.
billingContact = null;
techContact = null;
adminContact = null;
if (includeRegistrant) {
registrantContact = null;
}
HashSet<Type> contactsDiscovered = new HashSet<>();
for (DesignatedContact contact : contacts) {
checkArgument(
!contactsDiscovered.contains(contact.getType()),
"Duplicate contact type %s in designated contact set.",
contact.getType());
contactsDiscovered.add(contact.getType());
switch (contact.getType()) {
case BILLING -> billingContact = contact.getContactKey();
case TECH -> techContact = contact.getContactKey();
case ADMIN -> adminContact = contact.getContactKey();
case REGISTRANT -> {
if (includeRegistrant) {
registrantContact = contact.getContactKey();
}
}
default ->
throw new IllegalArgumentException(
"Unknown contact resource type: " + contact.getType());
}
}
}
@Override
public VKey<Domain> createVKey() {
throw new UnsupportedOperationException(
"DomainBase is not an actual persisted entity you can create a key to; use Domain instead");
}
/** Predicate to determine if a given {@link DesignatedContact} is the registrant. */
static final Predicate<DesignatedContact> IS_REGISTRANT =
(DesignatedContact contact) -> Type.REGISTRANT.equals(contact.type);
/** An override of {@link EppResource#asBuilder} with tighter typing. */
@Override
public Builder<? extends DomainBase, ?> asBuilder() {
@@ -708,7 +601,7 @@ public class DomainBase extends EppResource
/** A builder for constructing {@link Domain}, since it is immutable. */
public static class Builder<T extends DomainBase, B extends Builder<T, B>>
extends EppResource.Builder<T, B> implements BuilderWithTransferData<DomainTransferData, B> {
extends EppResource.Builder<T, B> {
public Builder() {}
@@ -764,12 +657,6 @@ public class DomainBase extends EppResource
return thisCastToDerived();
}
public B setRegistrant(Optional<VKey<Contact>> registrant) {
// Set the registrant field specifically.
getInstance().registrantContact = registrant.orElse(null);
return thisCastToDerived();
}
public B setAuthInfo(DomainAuthInfo authInfo) {
getInstance().authInfo = authInfo;
return thisCastToDerived();
@@ -805,26 +692,6 @@ public class DomainBase extends EppResource
ImmutableSet.copyOf(difference(getInstance().getNameservers(), nameservers)));
}
public B setContacts(DesignatedContact contact) {
return setContacts(ImmutableSet.of(contact));
}
public B setContacts(ImmutableSet<DesignatedContact> contacts) {
checkArgument(contacts.stream().noneMatch(IS_REGISTRANT), "Registrant cannot be a contact");
// Set the individual fields.
getInstance().setContactFields(contacts, false);
return thisCastToDerived();
}
public B addContacts(ImmutableSet<DesignatedContact> contacts) {
return setContacts(ImmutableSet.copyOf(Sets.union(getInstance().getContacts(), contacts)));
}
public B removeContacts(ImmutableSet<DesignatedContact> contacts) {
return setContacts(ImmutableSet.copyOf(difference(getInstance().getContacts(), contacts)));
}
public B setLaunchNotice(LaunchNotice launchNotice) {
getInstance().launchNotice = launchNotice;
return thisCastToDerived();
@@ -912,13 +779,11 @@ public class DomainBase extends EppResource
return thisCastToDerived();
}
@Override
public B setTransferData(DomainTransferData transferData) {
getInstance().transferData = transferData;
return thisCastToDerived();
}
@Override
public B setLastTransferTime(DateTime lastTransferTime) {
getInstance().lastTransferTime = lastTransferTime;
return thisCastToDerived();

View File

@@ -30,7 +30,6 @@ import google.registry.flows.domain.DomainFlowUtils.RegistrantProhibitedExceptio
import google.registry.flows.exceptions.ContactsProhibitedException;
import google.registry.model.ForeignKeyUtils;
import google.registry.model.ImmutableObject;
import google.registry.model.contact.Contact;
import google.registry.model.eppinput.ResourceCommand.AbstractSingleResourceCommand;
import google.registry.model.eppinput.ResourceCommand.ResourceCheck;
import google.registry.model.eppinput.ResourceCommand.ResourceCreateOrChange;
@@ -79,9 +78,6 @@ public class DomainCommand {
@Nullable
String registrantContactId;
/** A resolved key to the registrant who registered this domain. */
@Nullable @XmlTransient VKey<Contact> registrant;
/** Authorization info (aka transfer secret) of the domain. */
DomainAuthInfo authInfo;
@@ -89,10 +85,6 @@ public class DomainCommand {
return Optional.ofNullable(registrantContactId);
}
public Optional<VKey<Contact>> getRegistrant() {
return Optional.ofNullable(registrant);
}
public DomainAuthInfo getAuthInfo() {
return authInfo;
}
@@ -131,10 +123,6 @@ public class DomainCommand {
@XmlElement(name = "contact")
Set<ForeignKeyedDesignatedContact> foreignKeyedDesignatedContacts;
/** Resolved keys to associated contacts for the domain (other than registrant). */
@XmlTransient
Set<DesignatedContact> contacts;
/** The period that this domain's state was set to last for (e.g. 1-10 years). */
Period period;
@@ -159,10 +147,6 @@ public class DomainCommand {
return nullToEmptyImmutableCopy(nameservers);
}
public ImmutableSet<DesignatedContact> getContacts() {
return nullToEmptyImmutableCopy(contacts);
}
@Override
public DomainAuthInfo getAuthInfo() {
return authInfo;
@@ -340,10 +324,6 @@ public class DomainCommand {
@XmlElement(name = "contact")
Set<ForeignKeyedDesignatedContact> foreignKeyedDesignatedContacts;
/** Resolved keys to associated contacts for the domain (other than registrant). */
@XmlTransient
Set<DesignatedContact> contacts;
public ImmutableSet<String> getNameserverHostNames() {
return nullSafeImmutableCopy(nameserverHostNames);
}
@@ -352,10 +332,6 @@ public class DomainCommand {
return nullToEmptyImmutableCopy(nameservers);
}
public ImmutableSet<DesignatedContact> getContacts() {
return nullToEmptyImmutableCopy(contacts);
}
/** Creates a copy of this {@link AddRemove} with hard links to hosts and contacts. */
private AddRemove cloneAndLinkReferences(DateTime now)
throws InvalidReferencesException, ContactsProhibitedException {

View File

@@ -16,27 +16,42 @@ package google.registry.model.domain;
import google.registry.model.ImmutableObject;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlEnumValue;
import jakarta.xml.bind.annotation.XmlValue;
/**
* EPP-XML-serializable equivalent of {@link DesignatedContact}.
* Vestigial EPP-XML-serializable equivalent of a contact.
*
* <p>This type is used on the wire for EPP XML, where only the contact ID (foreign key) is exposed.
* This is converted to and from the persisted type, {@link DesignatedContact}, which stores the
* primary key instead of the foreign key.
* <p>This type was used on the wire for EPP XML, where only the contact ID (foreign key) was
* exposed.
*
* @see <a href="http://tools.ietf.org/html/rfc5731#section-2.2">RFC 5731 - EPP Domain Name Mapping
* - Contact and Client Identifiers</a>
*/
public class ForeignKeyedDesignatedContact extends ImmutableObject {
/**
* XML type for contact types. This can be either: {@code "admin"}, {@code "billing"}, or {@code
* "tech"} and corresponds to {@code contactAttrType} in {@code domain-1.0.xsd}.
*/
public enum Type {
@XmlEnumValue("admin")
ADMIN,
@XmlEnumValue("billing")
BILLING,
@XmlEnumValue("tech")
TECH,
/** The registrant type is not reflected in XML and exists only for internal use. */
REGISTRANT
}
@XmlAttribute(required = true)
DesignatedContact.Type type;
Type type;
@XmlValue
String contactId;
public static ForeignKeyedDesignatedContact create(
DesignatedContact.Type type, String contactId) {
public static ForeignKeyedDesignatedContact create(Type type, String contactId) {
ForeignKeyedDesignatedContact instance = new ForeignKeyedDesignatedContact();
instance.type = type;
instance.contactId = contactId;

View File

@@ -123,8 +123,7 @@ public class EppXmlTransformer {
public static byte[] marshal(EppOutput root, ValidationMode validation) throws XmlException {
byte[] bytes = marshal(OUTPUT_TRANSFORMER, root, validation);
if (!RegistryEnvironment.PRODUCTION.equals(RegistryEnvironment.get())
&& hasFeeExtension(root)) {
if (hasFeeExtension(root)) {
return FeeExtensionXmlTagNormalizer.normalize(new String(bytes, UTF_8)).getBytes(UTF_8);
}
return bytes;

View File

@@ -22,8 +22,6 @@ import com.google.common.collect.ImmutableSet;
import google.registry.model.EppResource;
import google.registry.model.adapters.EnumToAttributeAdapter.EppEnum;
import google.registry.model.adapters.StatusValueAdapter;
import google.registry.model.contact.Contact;
import google.registry.model.contact.ContactBase;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainBase;
import google.registry.model.host.Host;
@@ -130,8 +128,6 @@ public enum StatusValue implements EppEnum {
/** Enum to help clearly list which resource types a status value is allowed to be present on. */
private enum AllowedOn {
ALL(
Contact.class,
ContactBase.class,
Domain.class,
DomainBase.class,
Host.class,

View File

@@ -24,7 +24,6 @@ import static google.registry.util.DomainNameUtils.canonicalizeHostname;
import com.google.common.collect.ImmutableSet;
import google.registry.model.EppResource;
import google.registry.model.domain.Domain;
import google.registry.model.transfer.TransferData;
import google.registry.persistence.VKey;
import google.registry.persistence.converter.InetAddressSetUserType;
import jakarta.persistence.Access;
@@ -41,8 +40,8 @@ import org.joda.time.DateTime;
/**
* A persistable Host resource including mutable and non-mutable fields.
*
* <p>A host's {@link TransferData} is stored on the superordinate domain. Non-subordinate hosts
* don't carry a full set of TransferData; all they have is lastTransferTime.
* <p>A host's full transfer data is stored on the superordinate domain. Non-subordinate hosts don't
* carry a full set of TransferData; all they have is lastTransferTime.
*
* <p>This class deliberately does not include an {@link jakarta.persistence.Id} so that any
* foreign-keyed fields can refer to the proper parent entity's ID, whether we're storing this in

View File

@@ -32,14 +32,12 @@ import google.registry.model.domain.DomainRenewData;
import google.registry.model.eppoutput.EppResponse.ResponseData;
import google.registry.model.host.Host;
import google.registry.model.host.HostHistory;
import google.registry.model.poll.PendingActionNotificationResponse.ContactPendingActionNotificationResponse;
import google.registry.model.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse;
import google.registry.model.poll.PendingActionNotificationResponse.HostPendingActionNotificationResponse;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
import google.registry.model.transfer.TransferData.TransferServerApproveEntity;
import google.registry.model.transfer.DomainTransferData.TransferServerApproveEntity;
import google.registry.model.transfer.TransferResponse;
import google.registry.model.transfer.TransferResponse.ContactTransferResponse;
import google.registry.model.transfer.TransferResponse.DomainTransferResponse;
import google.registry.persistence.VKey;
import google.registry.persistence.WithVKey;
@@ -406,12 +404,8 @@ public abstract class PollMessage extends ImmutableObject
if (pendingActionNotificationResponse != null) {
// Promote the pending action notification response to its specialized type.
if (contactId != null) {
pendingActionNotificationResponse =
ContactPendingActionNotificationResponse.create(
pendingActionNotificationResponse.nameOrId.value,
pendingActionNotificationResponse.getActionResult(),
pendingActionNotificationResponse.getTrid(),
pendingActionNotificationResponse.processedDate);
// Contacts are no longer supported
pendingActionNotificationResponse = null;
} else if (domainName != null) {
pendingActionNotificationResponse =
DomainPendingActionNotificationResponse.create(
@@ -432,16 +426,8 @@ public abstract class PollMessage extends ImmutableObject
// The transferResponse is currently an unspecialized TransferResponse instance, create the
// appropriate subclass so that the value is consistently specialized
if (contactId != null) {
transferResponse =
new ContactTransferResponse.Builder()
.setContactId(contactId)
.setGainingRegistrarId(transferResponse.getGainingRegistrarId())
.setLosingRegistrarId(transferResponse.getLosingRegistrarId())
.setTransferStatus(transferResponse.getTransferStatus())
.setTransferRequestTime(transferResponse.getTransferRequestTime())
.setPendingTransferExpirationTime(
transferResponse.getPendingTransferExpirationTime())
.build();
// Contacts are no longer supported
transferResponse = null;
} else if (domainName != null) {
transferResponse =
new DomainTransferResponse.Builder()
@@ -488,9 +474,6 @@ public abstract class PollMessage extends ImmutableObject
// Set identifier fields based on the type of the notification response.
if (instance.pendingActionNotificationResponse
instanceof ContactPendingActionNotificationResponse) {
instance.contactId = instance.pendingActionNotificationResponse.nameOrId.value;
} else if (instance.pendingActionNotificationResponse
instanceof DomainPendingActionNotificationResponse) {
instance.domainName = instance.pendingActionNotificationResponse.nameOrId.value;
} else if (instance.pendingActionNotificationResponse
@@ -507,9 +490,7 @@ public abstract class PollMessage extends ImmutableObject
.orElse(null);
// Set the identifier according to the TransferResponse type.
if (instance.transferResponse instanceof ContactTransferResponse) {
instance.contactId = ((ContactTransferResponse) instance.transferResponse).getContactId();
} else if (instance.transferResponse instanceof DomainTransferResponse response) {
if (instance.transferResponse instanceof DomainTransferResponse response) {
instance.domainName = response.getDomainName();
instance.extendedRegistrationExpirationTime =
response.getExtendedRegistrationExpirationTime();

View File

@@ -25,7 +25,7 @@ import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlTransient;
import org.joda.time.DateTime;
/** Fields common to {@link TransferData} and {@link TransferResponse}. */
/** Fields common to {@link DomainTransferData} and {@link TransferResponse}. */
@XmlTransient
@MappedSuperclass
public abstract class BaseTransferObject extends ImmutableObject implements UnsafeSerializable {

View File

@@ -1,48 +0,0 @@
// Copyright 2020 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.model.transfer;
import jakarta.persistence.Embeddable;
/** Transfer data for contact. */
@Embeddable
public class ContactTransferData extends TransferData {
public static final ContactTransferData EMPTY = new ContactTransferData();
@Override
public boolean isEmpty() {
return EMPTY.equals(this);
}
@Override
protected Builder createEmptyBuilder() {
return new Builder();
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
}
public static class Builder extends TransferData.Builder<ContactTransferData, Builder> {
/** Create a {@link Builder} wrapping a new instance. */
public Builder() {}
/** Create a {@link Builder} wrapping the given instance. */
private Builder(ContactTransferData instance) {
super(instance);
}
}
}

View File

@@ -14,14 +14,20 @@
package google.registry.model.transfer;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.util.CollectionUtils.isNullOrEmpty;
import static google.registry.util.CollectionUtils.nullToEmpty;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import google.registry.model.Buildable;
import google.registry.model.billing.BillingCancellation;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingRecurrence;
import google.registry.model.domain.Period;
import google.registry.model.domain.Period.Unit;
import google.registry.model.eppcommon.Trid;
import google.registry.model.poll.PollMessage;
import google.registry.persistence.VKey;
import google.registry.util.NullIgnoringCollectionBuilder;
@@ -36,9 +42,41 @@ import org.joda.time.DateTime;
/** Transfer data for domain. */
@Embeddable
public class DomainTransferData extends TransferData {
public class DomainTransferData extends BaseTransferObject implements Buildable {
public static final DomainTransferData EMPTY = new DomainTransferData();
/** The transaction id of the most recent transfer request (or null if there never was one). */
@Embedded
@AttributeOverrides({
@AttributeOverride(
name = "serverTransactionId",
column = @Column(name = "transfer_server_txn_id")),
@AttributeOverride(
name = "clientTransactionId",
column = @Column(name = "transfer_client_txn_id"))
})
Trid transferRequestTrid;
@Column(name = "transfer_repo_id")
String repoId;
@Column(name = "transfer_history_entry_id")
Long historyEntryId;
// The pollMessageId1 and pollMessageId2 are used to store the IDs for gaining and losing poll
// messages in Cloud SQL.
//
// In addition, there may be a third poll message for the autorenew poll message on domain
// transfer if applicable.
@Column(name = "transfer_poll_message_id_1")
Long pollMessageId1;
@Column(name = "transfer_poll_message_id_2")
Long pollMessageId2;
@Column(name = "transfer_poll_message_id_3")
Long pollMessageId3;
/**
* The period to extend the registration upon completion of the transfer.
*
@@ -107,15 +145,19 @@ public class DomainTransferData extends TransferData {
@Column(name = "transfer_autorenew_poll_message_history_id")
Long serverApproveAutorenewPollMessageHistoryId;
@Override
public Builder copyConstantFieldsToBuilder() {
return ((Builder) super.copyConstantFieldsToBuilder()).setTransferPeriod(transferPeriod);
}
public Period getTransferPeriod() {
return transferPeriod;
}
public Long getHistoryEntryId() {
return historyEntryId;
}
@Nullable
public Trid getTransferRequestTrid() {
return transferRequestTrid;
}
@Nullable
public DateTime getTransferredRegistrationExpirationTime() {
return transferredRegistrationExpirationTime;
@@ -141,12 +183,13 @@ public class DomainTransferData extends TransferData {
return serverApproveAutorenewPollMessageHistoryId;
}
@Override
public ImmutableSet<VKey<? extends TransferServerApproveEntity>> getServerApproveEntities() {
ImmutableSet.Builder<VKey<? extends TransferServerApproveEntity>> builder =
new ImmutableSet.Builder<>();
builder.addAll(super.getServerApproveEntities());
return NullIgnoringCollectionBuilder.create(builder)
.add(pollMessageId1 != null ? VKey.create(PollMessage.class, pollMessageId1) : null)
.add(pollMessageId2 != null ? VKey.create(PollMessage.class, pollMessageId2) : null)
.add(pollMessageId3 != null ? VKey.create(PollMessage.class, pollMessageId3) : null)
.add(serverApproveBillingEvent)
.add(serverApproveAutorenewEvent)
.add(billingCancellationId)
@@ -154,16 +197,10 @@ public class DomainTransferData extends TransferData {
.build();
}
@Override
public boolean isEmpty() {
return EMPTY.equals(this);
}
@Override
protected Builder createEmptyBuilder() {
return new Builder();
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
@@ -186,7 +223,72 @@ public class DomainTransferData extends TransferData {
}
}
public static class Builder extends TransferData.Builder<DomainTransferData, Builder> {
/**
* Returns a fresh Builder populated only with the constant fields of this TransferData, i.e.
* those that are fixed and unchanging throughout the transfer process.
*
* <p>These fields are:
*
* <ul>
* <li>transferRequestTrid
* <li>transferRequestTime
* <li>gainingClientId
* <li>losingClientId
* <li>transferPeriod
* </ul>
*/
public Builder copyConstantFieldsToBuilder() {
return new Builder()
.setTransferPeriod(transferPeriod)
.setTransferRequestTrid(transferRequestTrid)
.setTransferRequestTime(transferRequestTime)
.setGainingRegistrarId(gainingClientId)
.setLosingRegistrarId(losingClientId);
}
/** Maps serverApproveEntities set to the individual fields. */
static void mapServerApproveEntitiesToFields(
Set<VKey<? extends TransferServerApproveEntity>> serverApproveEntities,
DomainTransferData transferData) {
if (isNullOrEmpty(serverApproveEntities)) {
transferData.pollMessageId1 = null;
transferData.pollMessageId2 = null;
transferData.pollMessageId3 = null;
return;
}
ImmutableList<Long> sortedPollMessageIds = getSortedPollMessageIds(serverApproveEntities);
if (!sortedPollMessageIds.isEmpty()) {
transferData.pollMessageId1 = sortedPollMessageIds.get(0);
}
if (sortedPollMessageIds.size() >= 2) {
transferData.pollMessageId2 = sortedPollMessageIds.get(1);
}
if (sortedPollMessageIds.size() >= 3) {
transferData.pollMessageId3 = sortedPollMessageIds.get(2);
}
}
/**
* Gets poll message IDs from the given serverApproveEntities and sorts the IDs in natural order.
*/
private static ImmutableList<Long> getSortedPollMessageIds(
Set<VKey<? extends TransferServerApproveEntity>> serverApproveEntities) {
return nullToEmpty(serverApproveEntities).stream()
.filter(vKey -> PollMessage.class.isAssignableFrom(vKey.getKind()))
.map(vKey -> (long) vKey.getKey())
.sorted()
.collect(toImmutableList());
}
/**
* Marker interface for objects that are written in anticipation of a server approval, and
* therefore need to be deleted under any other outcome.
*/
public interface TransferServerApproveEntity {
VKey<? extends TransferServerApproveEntity> createVKey();
}
public static class Builder extends BaseTransferObject.Builder<DomainTransferData, Builder> {
/** Create a {@link Builder} wrapping a new instance. */
public Builder() {}
@@ -195,6 +297,20 @@ public class DomainTransferData extends TransferData {
super(instance);
}
@Override
public DomainTransferData build() {
if (getInstance().pollMessageId1 != null) {
checkState(getInstance().repoId != null, "Repo id undefined");
checkState(getInstance().historyEntryId != null, "History entry undefined");
}
return super.build();
}
public Builder setTransferRequestTrid(Trid transferRequestTrid) {
getInstance().transferRequestTrid = transferRequestTrid;
return this;
}
public Builder setTransferPeriod(Period transferPeriod) {
getInstance().transferPeriod = transferPeriod;
return this;
@@ -223,12 +339,13 @@ public class DomainTransferData extends TransferData {
return this;
}
@Override
public Builder setServerApproveEntities(
String repoId,
Long historyId,
ImmutableSet<VKey<? extends TransferServerApproveEntity>> serverApproveEntities) {
super.setServerApproveEntities(repoId, historyId, serverApproveEntities);
getInstance().repoId = repoId;
getInstance().historyEntryId = historyId;
mapServerApproveEntitiesToFields(serverApproveEntities, getInstance());
mapBillingCancellationEntityToField(serverApproveEntities, getInstance());
return this;
}

View File

@@ -1,205 +0,0 @@
// Copyright 2017 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.model.transfer;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.util.CollectionUtils.isNullOrEmpty;
import static google.registry.util.CollectionUtils.nullToEmpty;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import google.registry.model.Buildable;
import google.registry.model.EppResource;
import google.registry.model.eppcommon.Trid;
import google.registry.model.poll.PollMessage;
import google.registry.persistence.VKey;
import google.registry.util.NullIgnoringCollectionBuilder;
import jakarta.persistence.AttributeOverride;
import jakarta.persistence.AttributeOverrides;
import jakarta.persistence.Column;
import jakarta.persistence.Embedded;
import jakarta.persistence.MappedSuperclass;
import java.util.Set;
import javax.annotation.Nullable;
/**
* Common transfer data for {@link EppResource} types. Only applies to domains and contacts; hosts
* are implicitly transferred with their superordinate domain.
*/
@MappedSuperclass
public abstract class TransferData extends BaseTransferObject implements Buildable {
/** The transaction id of the most recent transfer request (or null if there never was one). */
@Embedded
@AttributeOverrides({
@AttributeOverride(
name = "serverTransactionId",
column = @Column(name = "transfer_server_txn_id")),
@AttributeOverride(
name = "clientTransactionId",
column = @Column(name = "transfer_client_txn_id"))
})
Trid transferRequestTrid;
@Column(name = "transfer_repo_id")
String repoId;
@Column(name = "transfer_history_entry_id")
Long historyEntryId;
// The pollMessageId1 and pollMessageId2 are used to store the IDs for gaining and losing poll
// messages in Cloud SQL.
//
// In addition, there may be a third poll message for the autorenew poll message on domain
// transfer if applicable.
@Column(name = "transfer_poll_message_id_1")
Long pollMessageId1;
@Column(name = "transfer_poll_message_id_2")
Long pollMessageId2;
@Column(name = "transfer_poll_message_id_3")
Long pollMessageId3;
public abstract boolean isEmpty();
public Long getHistoryEntryId() {
return historyEntryId;
}
@Nullable
public Trid getTransferRequestTrid() {
return transferRequestTrid;
}
public ImmutableSet<VKey<? extends TransferServerApproveEntity>> getServerApproveEntities() {
return NullIgnoringCollectionBuilder.create(
new ImmutableSet.Builder<VKey<? extends TransferServerApproveEntity>>())
.add(pollMessageId1 != null ? VKey.create(PollMessage.class, pollMessageId1) : null)
.add(pollMessageId2 != null ? VKey.create(PollMessage.class, pollMessageId2) : null)
.add(pollMessageId3 != null ? VKey.create(PollMessage.class, pollMessageId3) : null)
.getBuilder()
.build();
}
@Override
public abstract Builder<?, ?> asBuilder();
protected abstract Builder<?, ?> createEmptyBuilder();
/**
* Returns a fresh Builder populated only with the constant fields of this TransferData, i.e.
* those that are fixed and unchanging throughout the transfer process.
*
* <p>These fields are:
*
* <ul>
* <li>transferRequestTrid
* <li>transferRequestTime
* <li>gainingClientId
* <li>losingClientId
* <li>transferPeriod
* </ul>
*/
public Builder<?, ?> copyConstantFieldsToBuilder() {
Builder<?, ?> newBuilder = createEmptyBuilder();
newBuilder
.setTransferRequestTrid(transferRequestTrid)
.setTransferRequestTime(transferRequestTime)
.setGainingRegistrarId(gainingClientId)
.setLosingRegistrarId(losingClientId);
return newBuilder;
}
/** Maps serverApproveEntities set to the individual fields. */
static void mapServerApproveEntitiesToFields(
Set<VKey<? extends TransferServerApproveEntity>> serverApproveEntities,
TransferData transferData) {
if (isNullOrEmpty(serverApproveEntities)) {
transferData.pollMessageId1 = null;
transferData.pollMessageId2 = null;
transferData.pollMessageId3 = null;
return;
}
ImmutableList<Long> sortedPollMessageIds = getSortedPollMessageIds(serverApproveEntities);
if (sortedPollMessageIds.size() >= 1) {
transferData.pollMessageId1 = sortedPollMessageIds.get(0);
}
if (sortedPollMessageIds.size() >= 2) {
transferData.pollMessageId2 = sortedPollMessageIds.get(1);
}
if (sortedPollMessageIds.size() >= 3) {
transferData.pollMessageId3 = sortedPollMessageIds.get(2);
}
}
/**
* Gets poll message IDs from the given serverApproveEntities and sorted the IDs in natural order.
*/
private static ImmutableList<Long> getSortedPollMessageIds(
Set<VKey<? extends TransferServerApproveEntity>> serverApproveEntities) {
return nullToEmpty(serverApproveEntities).stream()
.filter(vKey -> PollMessage.class.isAssignableFrom(vKey.getKind()))
.map(vKey -> (long) vKey.getKey())
.sorted()
.collect(toImmutableList());
}
/** Builder for {@link TransferData} because it is immutable. */
public abstract static class Builder<T extends TransferData, B extends Builder<T, B>>
extends BaseTransferObject.Builder<T, B> {
/** Create a {@link Builder} wrapping a new instance. */
protected Builder() {}
/** Create a {@link Builder} wrapping the given instance. */
protected Builder(T instance) {
super(instance);
}
public B setTransferRequestTrid(Trid transferRequestTrid) {
getInstance().transferRequestTrid = transferRequestTrid;
return thisCastToDerived();
}
public B setServerApproveEntities(
String repoId,
Long historyId,
ImmutableSet<VKey<? extends TransferServerApproveEntity>> serverApproveEntities) {
getInstance().repoId = repoId;
getInstance().historyEntryId = historyId;
mapServerApproveEntitiesToFields(serverApproveEntities, getInstance());
return thisCastToDerived();
}
@Override
public T build() {
if (getInstance().pollMessageId1 != null) {
checkState(getInstance().repoId != null, "Repo id undefined");
checkState(getInstance().historyEntryId != null, "History entry undefined");
}
return super.build();
}
}
/**
* Marker interface for objects that are written in anticipation of a server approval, and
* therefore need to be deleted under any other outcome.
*/
public interface TransferServerApproveEntity {
VKey<? extends TransferServerApproveEntity> createVKey();
}
}

View File

@@ -27,7 +27,6 @@ import com.google.common.collect.ImmutableMap;
import com.google.gson.annotations.Expose;
import google.registry.model.EppResource;
import google.registry.model.ImmutableObject;
import google.registry.model.contact.Contact;
import google.registry.model.domain.Domain;
import google.registry.model.host.Host;
import google.registry.util.SerializeUtils;
@@ -51,7 +50,7 @@ public class VKey<T> extends ImmutableObject implements Serializable {
private static final String DELIMITER = "@";
private static final ImmutableMap<String, Class<? extends EppResource>> EPP_RESOURCE_CLASS_MAP =
ImmutableList.of(Domain.class, Host.class, Contact.class).stream()
ImmutableList.of(Domain.class, Host.class).stream()
.collect(toImmutableMap(Class::getSimpleName, identity()));
// The primary key for the referenced entity.

View File

@@ -23,7 +23,6 @@ import google.registry.model.domain.secdns.DomainDsData;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.rde.RdeMode;
import google.registry.model.transfer.DomainTransferData;
import google.registry.model.transfer.TransferData;
import google.registry.util.Idn;
import google.registry.xjc.domain.XjcDomainNsType;
import google.registry.xjc.domain.XjcDomainStatusType;
@@ -221,7 +220,7 @@ final class DomainToXjcConverter {
&& !Strings.isNullOrEmpty(model.getTransferData().getLosingRegistrarId());
}
/** Converts {@link TransferData} to {@link XjcRdeDomainTransferDataType}. */
/** Converts {@link DomainTransferData} to {@link XjcRdeDomainTransferDataType}. */
private static XjcRdeDomainTransferDataType convertTransferData(DomainTransferData model) {
XjcRdeDomainTransferDataType bean = new XjcRdeDomainTransferDataType();
bean.setTrStatus(

View File

@@ -44,7 +44,6 @@ import google.registry.gcs.GcsUtils;
import google.registry.keyring.api.KeyModule.Key;
import google.registry.model.common.Cursor;
import google.registry.model.common.Cursor.CursorType;
import google.registry.model.contact.Contact;
import google.registry.model.domain.Domain;
import google.registry.model.host.Host;
import google.registry.model.rde.RdeMode;
@@ -80,8 +79,8 @@ import org.joda.time.Duration;
* type and loads the embedded resource from it, which is then projected to watermark time to
* account for things like pending transfer.
*
* <p>Only {@link Contact}s and {@link Host}s that are referenced by an included {@link Domain} will
* be included in the corresponding pending deposit.
* <p>Only {@link Host}s that are referenced by an included {@link Domain} will be included in the
* corresponding pending deposit.
*
* <p>{@link Registrar} entities, both active and inactive, are included in all deposits. They are
* not rewound point-in-time.

View File

@@ -19,7 +19,6 @@ import static com.google.common.base.Preconditions.checkState;
import com.google.common.base.Ascii;
import google.registry.model.EppResource;
import google.registry.model.ForeignKeyUtils;
import google.registry.model.contact.Contact;
import google.registry.model.domain.Domain;
import google.registry.model.host.Host;
import google.registry.persistence.VKey;
@@ -30,7 +29,6 @@ class CommandUtilities {
/** A useful parameter enum for commands that operate on {@link EppResource} objects. */
public enum ResourceType {
CONTACT(Contact.class),
HOST(Host.class),
DOMAIN(Domain.class);

View File

@@ -61,10 +61,6 @@ public class PasswordResetRequestAction extends ConsoleApiAction {
@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);
}

View File

@@ -23,6 +23,7 @@ import static google.registry.ui.server.console.PasswordResetRequestAction.check
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import google.registry.model.console.ConsolePermission;
import google.registry.model.console.ConsoleUpdateHistory;
import google.registry.model.console.PasswordResetRequest;
import google.registry.model.console.User;
import google.registry.model.registrar.Registrar;
@@ -59,11 +60,6 @@ public class PasswordResetVerifyAction extends ConsoleApiAction {
@Override
protected void getHandler(User user) {
// Temporary flag when testing email sending etc
if (!user.getUserRoles().isAdmin()) {
setFailedResponse("", HttpServletResponse.SC_FORBIDDEN);
return;
}
PasswordResetRequest request = tm().transact(() -> loadAndValidateResetRequest(user));
ImmutableMap<String, ?> result =
ImmutableMap.of("type", request.getType(), "registrarId", request.getRegistrarId());
@@ -73,11 +69,6 @@ public class PasswordResetVerifyAction extends ConsoleApiAction {
@Override
protected void postHandler(User user) {
// Temporary flag when testing email sending etc
if (!user.getUserRoles().isAdmin()) {
setFailedResponse("", HttpServletResponse.SC_FORBIDDEN);
return;
}
checkArgument(!Strings.isNullOrEmpty(newPassword.orElse(null)), "Password must be provided");
tm().transact(
() -> {
@@ -87,6 +78,16 @@ public class PasswordResetVerifyAction extends ConsoleApiAction {
case REGISTRY_LOCK -> handleRegistryLockPasswordReset(request);
}
tm().put(request.asBuilder().setFulfillmentTime(tm().getTransactionTime()).build());
finishAndPersistConsoleUpdateHistory(
new ConsoleUpdateHistory.Builder()
.setType(ConsoleUpdateHistory.Type.EPP_PASSWORD_UPDATE)
.setDescription(
String.format(
"%s%s%s",
request.getRegistrarId(),
ConsoleUpdateHistory.DESCRIPTION_SEPARATOR,
"Password reset fulfilled via verification code")));
});
consoleApiParams.response().setStatus(HttpServletResponse.SC_OK);
}
@@ -110,6 +111,11 @@ public class PasswordResetVerifyAction extends ConsoleApiAction {
PasswordResetRequest request =
tm().loadByKeyIfPresent(VKey.create(PasswordResetRequest.class, verificationCode))
.orElseThrow(this::createVerificationCodeException);
if (request.getFulfillmentTime().isPresent()) {
throw new IllegalArgumentException("This reset request has already been used.");
}
ConsolePermission requiredVerifyPermission =
switch (request.getType()) {
case EPP -> ConsolePermission.MANAGE_USERS;

View File

@@ -51,8 +51,6 @@
<class>google.registry.model.console.ConsoleUpdateHistory</class>
<class>google.registry.model.console.PasswordResetRequest</class>
<class>google.registry.model.console.User</class>
<class>google.registry.model.contact.ContactHistory</class>
<class>google.registry.model.contact.Contact</class>
<class>google.registry.model.domain.Domain</class>
<class>google.registry.model.domain.DomainHistory</class>
<class>google.registry.model.domain.GracePeriod</class>
@@ -94,7 +92,6 @@
<class>google.registry.model.billing.VKeyConverter_BillingCancellation</class>
<class>google.registry.model.billing.VKeyConverter_BillingEvent</class>
<class>google.registry.model.billing.VKeyConverter_BillingRecurrence</class>
<class>google.registry.model.contact.VKeyConverter_Contact</class>
<class>google.registry.model.domain.VKeyConverter_Domain</class>
<class>google.registry.model.domain.token.VKeyConverter_AllocationToken</class>
<class>google.registry.model.host.VKeyConverter_Host</class>

View File

@@ -185,7 +185,6 @@ public class RegistryJpaReadTest {
StatusValue.SERVER_UPDATE_PROHIBITED,
StatusValue.SERVER_RENEW_PROHIBITED,
StatusValue.SERVER_HOLD))
.setContacts(ImmutableSet.of())
.setSubordinateHosts(ImmutableSet.of("ns1.example.com"))
.setPersistedCurrentSponsorRegistrarId(registrar.getRegistrarId())
.setRegistrationExpirationTime(fakeClock.nowUtc().plusYears(1))

View File

@@ -81,7 +81,6 @@ import google.registry.testing.CloudTasksHelper.TaskMatcher;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeKeyringModule;
import java.io.IOException;
import java.util.Optional;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -236,12 +235,7 @@ public class RdePipelineTest {
persistHostHistory(host1);
Domain helloDomain =
persistEppResource(
newDomain("hello.soy")
.asBuilder()
.addNameserver(host1.createVKey())
.setRegistrant(Optional.empty())
.setContacts(ImmutableSet.of())
.build());
newDomain("hello.soy").asBuilder().addNameserver(host1.createVKey()).build());
persistDomainHistory(helloDomain);
persistHostHistory(persistActiveHost("not-used-subordinate.hello.soy"));
Host host2 = persistActiveHost("ns1.hello.soy");
@@ -253,8 +247,6 @@ public class RdePipelineTest {
newDomain("kitty.fun")
.asBuilder()
.addNameservers(ImmutableSet.of(host1.createVKey(), host2.createVKey()))
.setRegistrant(Optional.empty())
.setContacts(ImmutableSet.of())
.build());
persistDomainHistory(kittyDomain);
// Should not appear because the TLD is not included in a pending deposit.

View File

@@ -50,7 +50,6 @@ import static google.registry.testing.DatabaseHelper.getHistoryEntries;
import static google.registry.testing.DatabaseHelper.loadAllOf;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
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.persistReservedList;
@@ -1869,7 +1868,6 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
@Test
void testFailure_minimumDataset_noRegistrantButSomeOtherContactTypes() throws Exception {
persistActiveContact("sh8013");
setEppInput("domain_create_other_contact_types.xml");
persistHosts();
EppException thrown = assertThrows(ContactsProhibitedException.class, this::runFlow);
@@ -1878,7 +1876,6 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
@Test
void testFailure_minimumDataset_registrantNotPermitted() throws Exception {
persistActiveContact("jd1234");
setEppInput("domain_create_has_registrant_contact.xml");
persistHosts();
EppException thrown = assertThrows(RegistrantProhibitedException.class, this::runFlow);

View File

@@ -73,7 +73,6 @@ import google.registry.persistence.VKey;
import google.registry.persistence.transaction.JpaTransactionManagerExtension;
import google.registry.testing.DatabaseHelper;
import google.registry.xml.ValidationMode;
import java.util.Optional;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import org.joda.money.Money;
@@ -214,23 +213,9 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, Domain> {
doSuccessfulTest("domain_info_response.xml");
}
@Test
void testSuccess_noRegistrant() throws Exception {
persistTestEntities(false);
domain = persistResource(domain.asBuilder().setRegistrant(Optional.empty()).build());
doSuccessfulTest("domain_info_response_no_registrant.xml", false);
}
@Test
void testSuccess_noContacts() throws Exception {
persistTestEntities(false);
domain =
persistResource(
domain
.asBuilder()
.setRegistrant(Optional.empty())
.setContacts(ImmutableSet.of())
.build());
doSuccessfulTest("domain_info_response_no_contacts.xml", false);
}

View File

@@ -41,7 +41,7 @@ import google.registry.model.eppcommon.StatusValue;
import google.registry.model.host.Host;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.tld.Tld;
import google.registry.model.transfer.TransferData;
import google.registry.model.transfer.DomainTransferData;
import google.registry.model.transfer.TransferStatus;
import google.registry.persistence.transaction.JpaTransactionManagerExtension;
import org.joda.time.DateTime;
@@ -161,7 +161,8 @@ abstract class DomainTransferFlowTestCase<F extends Flow, R extends EppResource>
.build();
}
void assertTransferFailed(Domain domain, TransferStatus status, TransferData oldTransferData) {
void assertTransferFailed(
Domain domain, TransferStatus status, DomainTransferData oldTransferData) {
assertAboutDomains()
.that(domain)
.doesNotHaveStatusValue(StatusValue.PENDING_TRANSFER)

View File

@@ -54,7 +54,7 @@ import google.registry.model.poll.PollMessage;
import google.registry.model.reporting.DomainTransactionRecord;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.tld.Tld;
import google.registry.model.transfer.TransferData;
import google.registry.model.transfer.DomainTransferData;
import google.registry.model.transfer.TransferResponse;
import google.registry.model.transfer.TransferStatus;
import org.joda.time.DateTime;
@@ -90,7 +90,7 @@ class DomainTransferRejectFlowTest
assertMutatingFlow(true);
DateTime originalExpirationTime = domain.getRegistrationExpirationTime();
ImmutableSet<GracePeriod> originalGracePeriods = domain.getGracePeriods();
TransferData originalTransferData = domain.getTransferData();
DomainTransferData originalTransferData = domain.getTransferData();
runFlowAssertResponse(loadFile(expectedXmlFilename));
// Transfer should have been rejected. Verify correct fields were set.
domain = reloadResourceByForeignKey();

View File

@@ -42,7 +42,6 @@ import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.getOnlyHistoryEntryOfType;
import static google.registry.testing.DatabaseHelper.getPollMessages;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
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.persistActiveSubordinateHost;
@@ -272,7 +271,6 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
@Test
void testFailure_minimumDataset_whenAddingNewContacts() throws Exception {
persistActiveContact("mak21");
// This EPP adds a new technical contact mak21 that wasn't already present.
setEppInput("domain_update_empty_registrant.xml");
persistReferencedEntities();
@@ -1375,13 +1373,8 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
@Test
void testFailure_minimumDataset_addingNewRegistrantFails() throws Exception {
persistActiveContact("sh8013");
persistReferencedEntities();
persistResource(
DatabaseHelper.newDomain(getUniqueIdFromCommand())
.asBuilder()
.setRegistrant(Optional.empty())
.build());
persistResource(DatabaseHelper.newDomain(getUniqueIdFromCommand()));
// This EPP sets the registrant to sh8013, whereas in our test setup it is absent.
setEppInput("domain_update_registrant.xml");
RegistrantProhibitedException thrown =

View File

@@ -1,227 +0,0 @@
// Copyright 2017 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.model.contact;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.testing.DatabaseHelper.cloneAndSetAutoTimestamps;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.loadByEntity;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.SqlHelper.assertThrowForeignKeyViolation;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import google.registry.model.EntityTestCase;
import google.registry.model.ForeignKeyUtils;
import google.registry.model.contact.Disclose.PostalInfoChoice;
import google.registry.model.contact.PostalInfo.Type;
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
import google.registry.model.eppcommon.PresenceMarker;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.eppcommon.Trid;
import google.registry.model.transfer.ContactTransferData;
import google.registry.model.transfer.TransferStatus;
import google.registry.util.SerializeUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link Contact}. */
public class ContactTest extends EntityTestCase {
private Contact originalContact;
private Contact contact;
ContactTest() {
super(JpaEntityCoverageCheck.ENABLED);
}
@BeforeEach
void setUp() {
createTld("foobar");
originalContact =
new Contact.Builder()
.setContactId("contact_id")
.setRepoId("1-FOOBAR")
.setCreationRegistrarId("TheRegistrar")
.setLastEppUpdateTime(fakeClock.nowUtc())
.setLastEppUpdateRegistrarId("NewRegistrar")
.setLastTransferTime(fakeClock.nowUtc())
.setPersistedCurrentSponsorRegistrarId("NewRegistrar")
.setLocalizedPostalInfo(
new PostalInfo.Builder()
.setType(Type.LOCALIZED)
.setAddress(
new ContactAddress.Builder()
.setStreet(ImmutableList.of("111 8th Ave", "4th Floor"))
.setCity("New York")
.setState("NY")
.setZip("10011")
.setCountryCode("US")
.build())
.build())
.setInternationalizedPostalInfo(
new PostalInfo.Builder()
.setType(Type.INTERNATIONALIZED)
.setAddress(
new ContactAddress.Builder()
.setStreet(ImmutableList.of("111 8th Ave", "4th Floor"))
.setCity("New York")
.setState("NY")
.setZip("10011")
.setCountryCode("US")
.build())
.build())
.setVoiceNumber(new ContactPhoneNumber.Builder().setPhoneNumber("867-5309").build())
.setFaxNumber(
new ContactPhoneNumber.Builder()
.setPhoneNumber("867-5309")
.setExtension("1000")
.build())
.setEmailAddress("jenny@example.com")
.setAuthInfo(ContactAuthInfo.create(PasswordAuth.create("passw0rd")))
.setDisclose(
new Disclose.Builder()
.setVoice(new PresenceMarker())
.setEmail(new PresenceMarker())
.setFax(new PresenceMarker())
.setFlag(true)
.setAddrs(ImmutableList.of(PostalInfoChoice.create(Type.INTERNATIONALIZED)))
.setNames(ImmutableList.of(PostalInfoChoice.create(Type.INTERNATIONALIZED)))
.setOrgs(ImmutableList.of(PostalInfoChoice.create(Type.INTERNATIONALIZED)))
.build())
.setStatusValues(ImmutableSet.of(StatusValue.OK))
.setTransferData(
new ContactTransferData.Builder()
.setGainingRegistrarId("TheRegistrar")
.setLosingRegistrarId("NewRegistrar")
.setPendingTransferExpirationTime(fakeClock.nowUtc())
.setTransferRequestTime(fakeClock.nowUtc())
.setTransferStatus(TransferStatus.SERVER_APPROVED)
.setTransferRequestTrid(Trid.create("client-trid", "server-trid"))
.build())
.build();
// Set up a new persisted Contact entity.
contact = persistResource(cloneAndSetAutoTimestamps(originalContact));
}
@Test
void testContactBaseToContact() {
assertAboutImmutableObjects()
.that(new Contact.Builder().copyFrom(contact).build())
.isEqualExceptFields(contact, "updateTimestamp", "revisions");
}
@Test
void testCloudSqlPersistence_failWhenViolateForeignKeyConstraint() {
assertThrowForeignKeyViolation(
() ->
persistResource(
originalContact
.asBuilder()
.setRepoId("2-FOOBAR")
.setCreationRegistrarId("nonexistent-registrar")
.build()));
}
@Test
void testCloudSqlPersistence_succeed() {
Contact persisted = loadByEntity(originalContact);
Contact fixed =
originalContact
.asBuilder()
.setCreationTime(persisted.getCreationTime())
.setTransferData(
originalContact
.getTransferData()
.asBuilder()
.setServerApproveEntities(null, null, null)
.build())
.build();
assertAboutImmutableObjects().that(persisted).isEqualExceptFields(fixed, "updateTimestamp");
}
@Test
void testPersistence() {
assertThat(
ForeignKeyUtils.loadResource(
Contact.class, contact.getForeignKey(), fakeClock.nowUtc()))
.hasValue(contact);
}
@Test
void testSerializable() {
Contact persisted =
ForeignKeyUtils.loadResource(Contact.class, contact.getForeignKey(), fakeClock.nowUtc())
.get();
assertThat(SerializeUtils.serializeDeserialize(persisted)).isEqualTo(persisted);
}
@Test
void testEmptyStringsBecomeNull() {
assertThat(new Contact.Builder().setContactId(null).build().getContactId()).isNull();
assertThat(new Contact.Builder().setContactId("").build().getContactId()).isNull();
assertThat(new Contact.Builder().setContactId(" ").build().getContactId()).isNotNull();
// Nested ImmutableObjects should also be fixed
assertThat(
new Contact.Builder()
.setInternationalizedPostalInfo(
new PostalInfo.Builder().setType(Type.INTERNATIONALIZED).setName(null).build())
.build()
.getInternationalizedPostalInfo()
.getName())
.isNull();
assertThat(
new Contact.Builder()
.setInternationalizedPostalInfo(
new PostalInfo.Builder().setType(Type.INTERNATIONALIZED).setName("").build())
.build()
.getInternationalizedPostalInfo()
.getName())
.isNull();
assertThat(
new Contact.Builder()
.setInternationalizedPostalInfo(
new PostalInfo.Builder().setType(Type.INTERNATIONALIZED).setName(" ").build())
.build()
.getInternationalizedPostalInfo()
.getName())
.isNotNull();
}
@Test
void testEmptyTransferDataBecomesNull() {
Contact withNull = new Contact.Builder().setTransferData(null).build();
Contact withEmpty = withNull.asBuilder().setTransferData(ContactTransferData.EMPTY).build();
assertThat(withNull).isEqualTo(withEmpty);
assertThat(withEmpty.transferData).isNull();
}
@Test
void testSetCreationTime_cantBeCalledTwice() {
IllegalStateException thrown =
assertThrows(
IllegalStateException.class, () -> contact.asBuilder().setCreationTime(END_OF_TIME));
assertThat(thrown).hasMessageThat().contains("creationTime can only be set once");
}
@Test
void testToHydratedString_notCircular() {
// If there are circular references, this will overflow the stack.
contact.toHydratedString();
}
}

View File

@@ -236,17 +236,6 @@ public class DomainTest {
.hasValue(domain);
}
@Test
void testRegistrantNotRequired() {
persistResource(domain.asBuilder().setRegistrant(Optional.empty()).build());
String foreignKey = domain.getForeignKey();
assertThat(
ForeignKeyUtils.loadResource(Domain.class, foreignKey, fakeClock.nowUtc())
.get()
.getRegistrant())
.isEmpty();
}
@Test
void testEmptyStringsBecomeNull() {
assertThat(

View File

@@ -19,7 +19,6 @@ import static google.registry.model.eppcommon.EppXmlTransformer.unmarshal;
import static google.registry.testing.TestDataHelper.loadBytes;
import static org.junit.jupiter.api.Assertions.assertThrows;
import google.registry.model.contact.ContactTest;
import google.registry.model.domain.DomainTest;
import google.registry.model.eppinput.EppInput.InnerCommand;
import google.registry.model.eppinput.EppInput.Login;
@@ -29,17 +28,6 @@ import org.junit.jupiter.api.Test;
/** Unit tests for {@link EppInput}. */
class EppInputTest {
@Test
void testUnmarshalling_contactInfo() throws Exception {
EppInput input =
unmarshal(EppInput.class, loadBytes(ContactTest.class, "contact_info.xml").read());
assertThat(input.getCommandWrapper().getClTrid()).hasValue("ABC-12345");
assertThat(input.getCommandType()).isEqualTo("info");
assertThat(input.getResourceType()).hasValue("contact");
assertThat(input.getSingleTargetId()).hasValue("sh8013");
assertThat(input.getTargetIds()).containsExactly("sh8013");
}
@Test
void testUnmarshalling_domainCheck() throws Exception {
EppInput input =

View File

@@ -1,145 +0,0 @@
// Copyright 2020 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.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.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;
import google.registry.model.EntityTestCase;
import google.registry.model.contact.Contact;
import google.registry.model.contact.ContactAddress;
import google.registry.model.contact.ContactBase;
import google.registry.model.contact.ContactHistory;
import google.registry.model.contact.ContactPhoneNumber;
import google.registry.model.contact.PostalInfo;
import google.registry.model.eppcommon.Trid;
import google.registry.model.reporting.HistoryEntry;
import google.registry.util.SerializeUtils;
import org.junit.jupiter.api.Test;
/** Tests for {@link ContactHistory}. */
public class ContactHistoryTest extends EntityTestCase {
ContactHistoryTest() {
super(JpaEntityCoverageCheck.ENABLED);
}
@Test
void testPersistence() {
Contact contact = newContactWithRoid("contactId", "contact1");
persistResource(contact);
Contact contactFromDb = loadByEntity(contact);
ContactHistory contactHistory = createContactHistory(contactFromDb);
persistResource(contactHistory);
tm().transact(
() -> {
ContactHistory fromDatabase = tm().loadByKey(contactHistory.createVKey());
assertContactHistoriesEqual(fromDatabase, contactHistory);
assertThat(fromDatabase.getRepoId()).isEqualTo(contactHistory.getRepoId());
});
}
@Test
void testSerializable() {
Contact contact = newContactWithRoid("contactId", "contact1");
persistResource(contact);
Contact contactFromDb = loadByEntity(contact);
ContactHistory contactHistory = createContactHistory(contactFromDb);
persistResource(contactHistory);
ContactHistory fromDatabase = tm().transact(() -> tm().loadByKey(contactHistory.createVKey()));
assertThat(SerializeUtils.serializeDeserialize(fromDatabase)).isEqualTo(fromDatabase);
}
@Test
void testWipeOutPii_assertsAllPiiFieldsAreNull() {
ContactHistory originalEntity =
createContactHistory(
new Contact.Builder()
.setRepoId("1-FOOBAR")
.setLocalizedPostalInfo(
new PostalInfo.Builder()
.setType(PostalInfo.Type.LOCALIZED)
.setAddress(
new ContactAddress.Builder()
.setStreet(ImmutableList.of("111 8th Ave", "4th Floor"))
.setCity("New York")
.setState("NY")
.setZip("10011")
.setCountryCode("US")
.build())
.build())
.setInternationalizedPostalInfo(
new PostalInfo.Builder()
.setType(PostalInfo.Type.INTERNATIONALIZED)
.setAddress(
new ContactAddress.Builder()
.setStreet(ImmutableList.of("111 8th Ave", "4th Floor"))
.setCity("New York")
.setState("NY")
.setZip("10011")
.setCountryCode("US")
.build())
.build())
.setVoiceNumber(new ContactPhoneNumber.Builder().setPhoneNumber("867-5309").build())
.setFaxNumber(
new ContactPhoneNumber.Builder()
.setPhoneNumber("867-5309")
.setExtension("1000")
.build())
.setEmailAddress("test@example.com")
.build());
assertThat(originalEntity.getContactBase().get().getEmailAddress()).isNotNull();
assertThat(originalEntity.getContactBase().get().getLocalizedPostalInfo()).isNotNull();
assertThat(originalEntity.getContactBase().get().getInternationalizedPostalInfo()).isNotNull();
assertThat(originalEntity.getContactBase().get().getVoiceNumber()).isNotNull();
assertThat(originalEntity.getContactBase().get().getFaxNumber()).isNotNull();
ContactHistory wipedEntity = originalEntity.asBuilder().wipeOutPii().build();
assertThat(wipedEntity.getContactBase().get().getEmailAddress()).isNull();
assertThat(wipedEntity.getContactBase().get().getLocalizedPostalInfo()).isNull();
assertThat(wipedEntity.getContactBase().get().getInternationalizedPostalInfo()).isNull();
assertThat(wipedEntity.getContactBase().get().getVoiceNumber()).isNull();
assertThat(wipedEntity.getContactBase().get().getFaxNumber()).isNull();
}
private ContactHistory createContactHistory(ContactBase contact) {
return new ContactHistory.Builder()
.setType(HistoryEntry.Type.HOST_CREATE)
.setXmlBytes("<xml></xml>".getBytes(UTF_8))
.setModificationTime(fakeClock.nowUtc())
.setRegistrarId("TheRegistrar")
.setTrid(Trid.create("ABC-123", "server-trid"))
.setBySuperuser(false)
.setReason("reason")
.setRequestedByRegistrar(true)
.setContact(contact)
.build();
}
static void assertContactHistoriesEqual(ContactHistory one, ContactHistory two) {
assertAboutImmutableObjects().that(one).isEqualExceptFields(two, "resource");
assertAboutImmutableObjects()
.that(one.getContactBase().get())
.isEqualExceptFields(two.getContactBase().get());
}
}

View File

@@ -29,8 +29,8 @@ import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link TransferData}. */
public class TransferDataTest {
/** Unit tests for {@link DomainTransferData}. */
public class DomainTransferDataTest {
private final DateTime now = DateTime.now(UTC);

View File

@@ -14,11 +14,9 @@
package google.registry.rde;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.io.BaseEncoding.base16;
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.createTld;
import static google.registry.testing.DatabaseHelper.persistEppResource;
import static google.registry.testing.DatabaseHelper.persistResource;
@@ -34,7 +32,6 @@ import google.registry.model.billing.BillingBase.Flag;
import google.registry.model.billing.BillingBase.Reason;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingRecurrence;
import google.registry.model.domain.DesignatedContact;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainAuthInfo;
import google.registry.model.domain.DomainHistory;
@@ -198,12 +195,6 @@ public class DomainToXjcConverterTest {
@Test
void testConvertAbsentContacts() throws XmlException {
Domain domain = makeDomain(clock);
tm().transact(
() ->
tm().delete(
domain.getAllContacts().stream()
.map(DesignatedContact::getContactKey)
.collect(toImmutableSet())));
XjcRdeDomain bean = DomainToXjcConverter.convertDomain(domain, RdeMode.FULL);
assertThat(bean.getRegistrant()).isNull();
assertThat(bean.getContacts()).isEmpty();

View File

@@ -27,11 +27,9 @@ import google.registry.model.common.FeatureFlagTest;
import google.registry.model.console.ConsoleUpdateHistoryTest;
import google.registry.model.console.PasswordResetRequestTest;
import google.registry.model.console.UserTest;
import google.registry.model.contact.ContactTest;
import google.registry.model.domain.DomainSqlTest;
import google.registry.model.domain.token.AllocationTokenTest;
import google.registry.model.domain.token.BulkPricingPackageTest;
import google.registry.model.history.ContactHistoryTest;
import google.registry.model.history.DomainHistoryTest;
import google.registry.model.history.HostHistoryTest;
import google.registry.model.poll.PollMessageTest;
@@ -96,8 +94,6 @@ import org.junit.runner.RunWith;
BulkPricingPackageTest.class,
ClaimsListDaoTest.class,
ConsoleUpdateHistoryTest.class,
ContactHistoryTest.class,
ContactTest.class,
CursorTest.class,
DnsRefreshRequestTest.class,
DomainSqlTest.class,

View File

@@ -73,8 +73,6 @@ import google.registry.model.common.DnsRefreshRequest;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.User;
import google.registry.model.console.UserRoles;
import google.registry.model.contact.Contact;
import google.registry.model.contact.ContactAuthInfo;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainAuthInfo;
import google.registry.model.domain.DomainBase;
@@ -182,17 +180,6 @@ public final class DatabaseHelper {
.build();
}
public static Contact newContactWithRoid(String contactId, String repoId) {
return new Contact.Builder()
.setRepoId(repoId)
.setContactId(contactId)
.setCreationRegistrarId("TheRegistrar")
.setPersistedCurrentSponsorRegistrarId("TheRegistrar")
.setAuthInfo(ContactAuthInfo.create(PasswordAuth.create("2fooBAR")))
.setCreationTimeForTest(START_OF_TIME)
.build();
}
public static Tld newTld(String tld, String roidSuffix) {
return newTld(tld, roidSuffix, ImmutableSortedMap.of(START_OF_TIME, GENERAL_AVAILABILITY));
}
@@ -232,10 +219,6 @@ public final class DatabaseHelper {
.build();
}
public static Contact persistActiveContact(String contactId) {
return persistResource(newContactWithRoid(contactId, generateNewHostRoid()));
}
public static Host persistActiveHost(String hostName) {
return persistResource(newHost(hostName));
}
@@ -1019,11 +1002,7 @@ public final class DatabaseHelper {
}
private static HistoryEntry.Type getHistoryEntryType(EppResource resource) {
if (resource instanceof Contact) {
return resource.getRepoId() != null
? HistoryEntry.Type.CONTACT_CREATE
: HistoryEntry.Type.CONTACT_UPDATE;
} else if (resource instanceof Host) {
if (resource instanceof Host) {
return resource.getRepoId() != null
? HistoryEntry.Type.HOST_CREATE
: HistoryEntry.Type.HOST_UPDATE;

View File

@@ -34,7 +34,6 @@ import java.util.Optional;
import javax.annotation.Nullable;
import org.joda.time.Duration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
/** Tests for {@link PasswordResetVerifyAction}. */
@@ -111,28 +110,24 @@ public class PasswordResetVerifyActionTest extends ConsoleActionBaseTestCase {
}
@Test
@Disabled("Enable when testing is done in sandbox and isAdmin check is removed")
void testFailure_get_epp_badPermission() throws Exception {
createAction(createTechUser(), "GET", verificationCode, null).run();
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN);
}
@Test
@Disabled("Enable when testing is done in sandbox and isAdmin check is removed")
void testFailure_get_lock_badPermission() throws Exception {
createAction(createAccountManager(), "GET", verificationCode, null).run();
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN);
}
@Test
@Disabled("Enable when testing is done in sandbox and isAdmin check is removed")
void testFailure_post_epp_badPermission() throws Exception {
createAction(createTechUser(), "POST", verificationCode, "newPassword").run();
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN);
}
@Test
@Disabled("Enable when testing is done in sandbox and isAdmin check is removed")
void testFailure_post_lock_badPermission() throws Exception {
createAction(createAccountManager(), "POST", verificationCode, "newPassword").run();
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

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