mirror of
https://github.com/google/nomulus
synced 2026-02-26 14:54:49 +00:00
Compare commits
13 Commits
proxy-2026
...
nomulus-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49df9c325a | ||
|
|
929dccbfe3 | ||
|
|
ee8746c857 | ||
|
|
c7f2db177b | ||
|
|
6747cc894d | ||
|
|
e4c4149033 | ||
|
|
e24c90fea6 | ||
|
|
8ff4d7dc8a | ||
|
|
88906f1bd9 | ||
|
|
bca05f3982 | ||
|
|
763630bca5 | ||
|
|
140b19e919 | ||
|
|
a787660b27 |
@@ -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": "."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"/console-api":
|
||||
{
|
||||
"target": "http://[::1]:8080",
|
||||
"target": "http://localhost:8080",
|
||||
"secure": false,
|
||||
"logLevel": "debug",
|
||||
"changeOrigin": true
|
||||
|
||||
@@ -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: {
|
||||
|
||||
11439
console-webapp/package-lock.json
generated
11439
console-webapp/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'],
|
||||
})
|
||||
|
||||
@@ -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..."
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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>) {}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -29,7 +29,6 @@ import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.flows.poll.PollFlowUtils;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.EppResourceUtils;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
@@ -94,7 +93,6 @@ public class DeleteLoadTestDataAction implements Runnable {
|
||||
TRANSACTION_REPEATABLE_READ,
|
||||
() -> {
|
||||
LOAD_TEST_REGISTRARS.forEach(this::deletePollMessages);
|
||||
tm().loadAllOfStream(Contact.class).forEach(this::deleteContact);
|
||||
tm().loadAllOfStream(Host.class).forEach(this::deleteHost);
|
||||
});
|
||||
}
|
||||
@@ -110,21 +108,6 @@ public class DeleteLoadTestDataAction implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteContact(Contact contact) {
|
||||
if (!LOAD_TEST_REGISTRARS.contains(contact.getPersistedCurrentSponsorRegistrarId())) {
|
||||
return;
|
||||
}
|
||||
// We cannot remove contacts from domains in the general case, so we cannot delete contacts
|
||||
// that are linked to domains (since it would break the foreign keys)
|
||||
if (EppResourceUtils.isLinked(contact.createVKey(), clock.nowUtc())) {
|
||||
logger.atWarning().log(
|
||||
"Cannot delete contact with repo ID %s since it is referenced from a domain.",
|
||||
contact.getRepoId());
|
||||
return;
|
||||
}
|
||||
deleteResource(contact);
|
||||
}
|
||||
|
||||
private void deleteHost(Host host) {
|
||||
if (!LOAD_TEST_REGISTRARS.contains(host.getPersistedCurrentSponsorRegistrarId())) {
|
||||
return;
|
||||
|
||||
@@ -25,7 +25,6 @@ import com.google.common.collect.Streams;
|
||||
import google.registry.beam.common.RegistryJpaIO;
|
||||
import google.registry.beam.common.RegistryJpaIO.Read;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.host.Host;
|
||||
@@ -56,7 +55,7 @@ import org.joda.time.DateTime;
|
||||
public class ResaveAllEppResourcesPipeline implements Serializable {
|
||||
|
||||
private static final ImmutableSet<Class<? extends EppResource>> EPP_RESOURCE_CLASSES =
|
||||
ImmutableSet.of(Contact.class, Domain.class, Host.class);
|
||||
ImmutableSet.of(Domain.class, Host.class);
|
||||
|
||||
/**
|
||||
* There exist three possible situations where we know we'll want to project domains to the
|
||||
@@ -92,25 +91,12 @@ public class ResaveAllEppResourcesPipeline implements Serializable {
|
||||
|
||||
void setupPipeline(Pipeline pipeline) {
|
||||
if (options.getFast()) {
|
||||
fastResaveContacts(pipeline);
|
||||
fastResaveDomains(pipeline);
|
||||
} else {
|
||||
EPP_RESOURCE_CLASSES.forEach(clazz -> forceResaveAllResources(pipeline, clazz));
|
||||
}
|
||||
}
|
||||
|
||||
/** Projects to the current time and saves any contacts with expired transfers. */
|
||||
private void fastResaveContacts(Pipeline pipeline) {
|
||||
Read<String, String> repoIdRead =
|
||||
RegistryJpaIO.read(
|
||||
"SELECT repoId FROM Contact WHERE transferData.transferStatus = 'PENDING' AND"
|
||||
+ " transferData.pendingTransferExpirationTime < current_timestamp()",
|
||||
String.class,
|
||||
r -> r)
|
||||
.withCoder(StringUtf8Coder.of());
|
||||
projectAndResaveResources(pipeline, Contact.class, repoIdRead);
|
||||
}
|
||||
|
||||
/**
|
||||
* Projects to the current time and saves any domains with expired pending actions (e.g.
|
||||
* transfers, grace periods).
|
||||
|
||||
@@ -143,15 +143,15 @@ public final class RegistryConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the roid suffix to be used for the roids of all contacts and hosts. E.g. a value of
|
||||
* "ROID" would end up creating roids that look like "ABC123-ROID".
|
||||
* Returns the roid suffix to be used for the roids of all hosts. E.g. a value of "ROID" would
|
||||
* end up creating roids that look like "ABC123-ROID".
|
||||
*
|
||||
* @see <a href="http://www.iana.org/assignments/epp-repository-ids/epp-repository-ids.xhtml">
|
||||
* Extensible Provisioning Protocol (EPP) Repository Identifiers</a>
|
||||
*/
|
||||
@Provides
|
||||
@Config("contactAndHostRoidSuffix")
|
||||
public static String provideContactAndHostRoidSuffix(RegistryConfigSettings config) {
|
||||
@Config("hostRoidSuffix")
|
||||
public static String provideHostRoidSuffix(RegistryConfigSettings config) {
|
||||
return config.registryPolicy.contactAndHostRoidSuffix;
|
||||
}
|
||||
|
||||
@@ -1024,18 +1024,6 @@ public final class RegistryConfig {
|
||||
return Duration.standardSeconds(config.monitoring.writeIntervalSeconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* The global automatic transfer length for contacts. After this amount of time has elapsed, the
|
||||
* transfer is automatically approved.
|
||||
*
|
||||
* @see google.registry.flows.contact.ContactTransferRequestFlow
|
||||
*/
|
||||
@Provides
|
||||
@Config("contactAutomaticTransferLength")
|
||||
public static Duration provideContactAutomaticTransferLength(RegistryConfigSettings config) {
|
||||
return Duration.standardDays(config.registryPolicy.contactAutomaticTransferDays);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum number of entities that can be checked at one time in an EPP check flow.
|
||||
*/
|
||||
@@ -1623,15 +1611,11 @@ public final class RegistryConfig {
|
||||
return CONFIG_SETTINGS.get().hibernate.jdbcFetchSize;
|
||||
}
|
||||
|
||||
/** Returns the roid suffix to be used for the roids of all contacts and hosts. */
|
||||
public static String getContactAndHostRoidSuffix() {
|
||||
/** Returns the roid suffix to be used for the roids of all hosts. */
|
||||
public static String getHostRoidSuffix() {
|
||||
return CONFIG_SETTINGS.get().registryPolicy.contactAndHostRoidSuffix;
|
||||
}
|
||||
|
||||
/** Returns the global automatic transfer length for contacts. */
|
||||
public static Duration getContactAutomaticTransferLength() {
|
||||
return Duration.standardDays(CONFIG_SETTINGS.get().registryPolicy.contactAutomaticTransferDays);
|
||||
}
|
||||
|
||||
/** A discount for all sunrise domain creates, between 0.0 (no discount) and 1.0 (free). */
|
||||
public static double getSunriseDomainCreateDiscount() {
|
||||
|
||||
@@ -86,7 +86,6 @@ public class RegistryConfigSettings {
|
||||
public String productName;
|
||||
public String customLogicFactoryClass;
|
||||
public String dnsCountQueryCoordinatorClass;
|
||||
public int contactAutomaticTransferDays;
|
||||
public String greetingServerId;
|
||||
public List<String> registrarChangesNotificationEmailAddresses;
|
||||
public String defaultRegistrarWhoisServer;
|
||||
|
||||
@@ -63,9 +63,6 @@ registryPolicy:
|
||||
# See reporting/icann/DnsCountQueryCoordinator.java
|
||||
dnsCountQueryCoordinatorClass: google.registry.reporting.icann.DummyDnsCountQueryCoordinator
|
||||
|
||||
# Length of time after which contact transfers automatically conclude.
|
||||
contactAutomaticTransferDays: 5
|
||||
|
||||
# Server ID used in the 'svID' element of an EPP 'greeting'.
|
||||
greetingServerId: Nomulus Registry
|
||||
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
// Copyright 2026 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.package google.registry.flows;
|
||||
|
||||
package google.registry.flows;
|
||||
|
||||
import static google.registry.model.eppcommon.ProtocolDefinition.ServiceExtension.FEE_0_11;
|
||||
import static google.registry.model.eppcommon.ProtocolDefinition.ServiceExtension.FEE_0_12;
|
||||
import static google.registry.model.eppcommon.ProtocolDefinition.ServiceExtension.FEE_0_6;
|
||||
import static google.registry.model.eppcommon.ProtocolDefinition.ServiceExtension.FEE_1_00;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.model.eppcommon.EppXmlTransformer;
|
||||
import google.registry.model.eppcommon.ProtocolDefinition.ServiceExtension;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Normalizes Fee extension namespace tags in EPP XML response messages.
|
||||
*
|
||||
* <p>Nomulus currently supports multiple versions of the Fee extension. With the current XML
|
||||
* tooling, the namespace of every version is included in each EPP response, and as a result must
|
||||
* use a unique XML tag. E.g., fee for extension v0.6, and fee12 for extension v0.12.
|
||||
*
|
||||
* <p>Some registrars are not XML namespace-aware and rely on the XML tags being specific literals.
|
||||
* This makes it difficult to perform seamless rollout of new versions: if Nomulus reassigns a tag
|
||||
* literal to a different version, it effectively forces all these registrars to upgrade at the time
|
||||
* of the deployment.
|
||||
*
|
||||
* <p>This class can be used to normalize the namespace tag in EPP responses. Since every response
|
||||
* message may use at most one version of the Fee extension, we can remove declared but unused
|
||||
* versions from the message, thus freeing up the canonical tag ('fee') for the active version.
|
||||
*/
|
||||
public class FeeExtensionXmlTagNormalizer {
|
||||
|
||||
// So far we only have Fee extensions to process
|
||||
private static final String CANONICAL_FEE_TAG = "fee";
|
||||
|
||||
private static final ImmutableSet<ServiceExtension> FEE_EXTENSIONS =
|
||||
ImmutableSet.of(FEE_0_6, FEE_0_11, FEE_0_12, FEE_1_00);
|
||||
|
||||
private static final Pattern FEE_EXTENSION_IN_USE_PATTERN =
|
||||
Pattern.compile(feeExtensionInUseRegex());
|
||||
|
||||
@VisibleForTesting
|
||||
static String feeExtensionInUseRegex() {
|
||||
return FEE_EXTENSIONS.stream()
|
||||
.map(ServiceExtension::getXmlTag)
|
||||
.map(tag -> String.format("\\b(%s):", tag))
|
||||
.collect(Collectors.joining("|"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a EPP response that uses the canonical tag ({@code fee}) for the fee extension.
|
||||
*
|
||||
* <p>This method replaces any versioned tag, e.g., {@code fee12} with the canonical tag. It also
|
||||
* removes unused namespace declarations and update the tag in the remaining declaration.
|
||||
*
|
||||
* <p>The input {@code xml} must be an EPP response message generated by the {@link
|
||||
* EppXmlTransformer}. With this assumption, we can use regular expressions which is 10X faster
|
||||
* than XML stream parsers.
|
||||
*/
|
||||
public static String normalize(String xml) {
|
||||
Optional<String> maybeFeeTagInUse = findFeeExtensionInUse(xml);
|
||||
if (maybeFeeTagInUse.isEmpty()) {
|
||||
return xml;
|
||||
}
|
||||
String feeTagInUse = maybeFeeTagInUse.get();
|
||||
String normalized = xml;
|
||||
for (ServiceExtension serviceExtension : FEE_EXTENSIONS) {
|
||||
if (serviceExtension.getXmlTag().equals(feeTagInUse)) {
|
||||
normalized = normalizeExtensionInUse(feeTagInUse, serviceExtension.getUri(), normalized);
|
||||
} else {
|
||||
normalized =
|
||||
removeUnusedExtension(
|
||||
serviceExtension.getXmlTag(), serviceExtension.getUri(), normalized);
|
||||
}
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
static String removeUnusedExtension(String tag, String uri, String xml) {
|
||||
String declaration = String.format("xmlns:%s=\"%s\"", tag, uri);
|
||||
// There must be a leading whitespace, and it can be safely removed with the declaration.
|
||||
return xml.replaceAll(String.format("\\s%s", declaration), "");
|
||||
}
|
||||
|
||||
static String normalizeExtensionInUse(String tagInUse, String uriInUse, String xml) {
|
||||
if (tagInUse.equals(CANONICAL_FEE_TAG)) {
|
||||
return xml;
|
||||
}
|
||||
// Change the tag in the namespace declaration:
|
||||
String currentDeclaration = String.format("xmlns:%s=\"%s\"", tagInUse, uriInUse);
|
||||
String desiredDeclaraion = String.format("xmlns:fee=\"%s\"", uriInUse);
|
||||
// The new tag at each site of use, with trailing colon:
|
||||
String newTagWithColon = CANONICAL_FEE_TAG + ":";
|
||||
return xml.replaceAll(String.format("\\b%s:", tagInUse), newTagWithColon)
|
||||
.replaceAll(currentDeclaration, desiredDeclaraion);
|
||||
}
|
||||
|
||||
static Optional<String> findFeeExtensionInUse(String xml) {
|
||||
Matcher matcher = FEE_EXTENSION_IN_USE_PATTERN.matcher(xml);
|
||||
|
||||
if (!matcher.find()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
// We know only one extension is in use, so we can return on the first match
|
||||
for (int i = 1; i <= matcher.groupCount(); i++) {
|
||||
if (matcher.group(i) != null) {
|
||||
return Optional.of(matcher.group(i));
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Should not reach here. Bad FEE_EXTENSION_IN_USE_PATTERN?");
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,6 @@ import google.registry.flows.exceptions.ResourceToDeleteIsReferencedException;
|
||||
import google.registry.flows.exceptions.TooManyResourceChecksException;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.EppResource.ForeignKeyedEppResource;
|
||||
import google.registry.model.EppResource.ResourceWithTransferData;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
@@ -42,6 +41,7 @@ import google.registry.model.domain.Period;
|
||||
import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.util.List;
|
||||
@@ -63,30 +63,26 @@ public final class ResourceFlowUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether if there are domains linked to the resource to be deleted. Throws an exception if
|
||||
* so.
|
||||
*/
|
||||
public static <R extends EppResource> void checkLinkedDomains(
|
||||
final String targetId, final DateTime now, final Class<R> resourceClass) throws EppException {
|
||||
VKey<R> key =
|
||||
ForeignKeyUtils.loadKey(resourceClass, targetId, now)
|
||||
.orElseThrow(() -> new ResourceDoesNotExistException(resourceClass, targetId));
|
||||
/** Check if there are domains linked to the host to be deleted. Throws an exception if so. */
|
||||
public static void checkLinkedDomains(final String targetId, final DateTime now)
|
||||
throws EppException {
|
||||
VKey<Host> key =
|
||||
ForeignKeyUtils.loadKey(Host.class, targetId, now)
|
||||
.orElseThrow(() -> new ResourceDoesNotExistException(Host.class, targetId));
|
||||
if (isLinked(key, now)) {
|
||||
throw new ResourceToDeleteIsReferencedException();
|
||||
}
|
||||
}
|
||||
|
||||
public static <R extends EppResource & ResourceWithTransferData> void verifyHasPendingTransfer(
|
||||
R resource) throws NotPendingTransferException {
|
||||
if (resource.getTransferData().getTransferStatus() != TransferStatus.PENDING) {
|
||||
throw new NotPendingTransferException(resource.getForeignKey());
|
||||
public static void verifyHasPendingTransfer(Domain domain) throws NotPendingTransferException {
|
||||
if (domain.getTransferData().getTransferStatus() != TransferStatus.PENDING) {
|
||||
throw new NotPendingTransferException(domain.getForeignKey());
|
||||
}
|
||||
}
|
||||
|
||||
public static <R extends EppResource & ResourceWithTransferData> void verifyTransferInitiator(
|
||||
String registrarId, R resource) throws NotTransferInitiatorException {
|
||||
if (!resource.getTransferData().getGainingRegistrarId().equals(registrarId)) {
|
||||
public static void verifyTransferInitiator(String registrarId, Domain domain)
|
||||
throws NotTransferInitiatorException {
|
||||
if (!domain.getTransferData().getGainingRegistrarId().equals(registrarId)) {
|
||||
throw new NotTransferInitiatorException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -1252,7 +1221,7 @@ public class DomainFlowUtils {
|
||||
}
|
||||
|
||||
/** Having a registrant is prohibited by registry policy. */
|
||||
static class RegistrantProhibitedException extends ParameterValuePolicyErrorException {
|
||||
public static class RegistrantProhibitedException extends ParameterValuePolicyErrorException {
|
||||
public RegistrantProhibitedException() {
|
||||
super("Having a registrant is prohibited by registry policy");
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -87,7 +87,7 @@ public final class HostCreateFlow implements MutatingFlow {
|
||||
@Inject EppResponse.Builder responseBuilder;
|
||||
|
||||
@Inject
|
||||
@Config("contactAndHostRoidSuffix")
|
||||
@Config("hostRoidSuffix")
|
||||
String roidSuffix;
|
||||
|
||||
@Inject
|
||||
|
||||
@@ -84,7 +84,7 @@ public final class HostDeleteFlow implements MutatingFlow {
|
||||
extensionManager.validate();
|
||||
DateTime now = tm().getTransactionTime();
|
||||
validateHostName(targetId);
|
||||
checkLinkedDomains(targetId, now, Host.class);
|
||||
checkLinkedDomains(targetId, now);
|
||||
Host existingHost = loadAndVerifyExistence(Host.class, targetId, now);
|
||||
verifyNoDisallowedStatuses(existingHost, ImmutableSet.of(StatusValue.PENDING_DELETE));
|
||||
if (!isSuperuser) {
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -23,17 +23,14 @@ import static google.registry.util.DateTimeUtils.isBeforeOrAt;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.model.EppResource.BuilderWithTransferData;
|
||||
import google.registry.model.EppResource.ResourceWithTransferData;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.reporting.HistoryEntryDao;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.model.transfer.DomainTransferData;
|
||||
import google.registry.model.transfer.TransferData;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import google.registry.persistence.VKey;
|
||||
import jakarta.persistence.Query;
|
||||
@@ -48,14 +45,6 @@ public final class EppResourceUtils {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
private static final String CONTACT_LINKED_DOMAIN_QUERY =
|
||||
"SELECT repoId FROM Domain "
|
||||
+ "WHERE (adminContact = :fkRepoId "
|
||||
+ "OR billingContact = :fkRepoId "
|
||||
+ "OR techContact = :fkRepoId "
|
||||
+ "OR registrantContact = :fkRepoId) "
|
||||
+ "AND deletionTime > :now";
|
||||
|
||||
// We have to use the native SQL query here because DomainHost table doesn't have its entity
|
||||
// class, so we cannot reference its property like domainHost.hostRepoId in a JPQL query.
|
||||
private static final String HOST_LINKED_DOMAIN_QUERY =
|
||||
@@ -105,41 +94,34 @@ public final class EppResourceUtils {
|
||||
return !isActive(resource, time);
|
||||
}
|
||||
|
||||
/** Process an automatic transfer on a resource. */
|
||||
public static <
|
||||
T extends TransferData,
|
||||
B extends EppResource.Builder<?, B> & BuilderWithTransferData<T, B>>
|
||||
void setAutomaticTransferSuccessProperties(B builder, TransferData transferData) {
|
||||
/** Process an automatic transfer on a domain. */
|
||||
public static void setAutomaticTransferSuccessProperties(
|
||||
DomainBase.Builder<?, ?> builder, DomainTransferData transferData) {
|
||||
checkArgument(TransferStatus.PENDING.equals(transferData.getTransferStatus()));
|
||||
TransferData.Builder transferDataBuilder = transferData.asBuilder();
|
||||
DomainTransferData.Builder transferDataBuilder = transferData.asBuilder();
|
||||
transferDataBuilder.setTransferStatus(TransferStatus.SERVER_APPROVED);
|
||||
transferDataBuilder.setServerApproveEntities(null, null, null);
|
||||
if (transferData instanceof DomainTransferData) {
|
||||
((DomainTransferData.Builder) transferDataBuilder)
|
||||
.setServerApproveBillingEvent(null)
|
||||
.setServerApproveAutorenewEvent(null)
|
||||
.setServerApproveAutorenewPollMessage(null);
|
||||
}
|
||||
transferDataBuilder
|
||||
.setServerApproveEntities(null, null, null)
|
||||
.setServerApproveBillingEvent(null)
|
||||
.setServerApproveAutorenewEvent(null)
|
||||
.setServerApproveAutorenewPollMessage(null);
|
||||
builder
|
||||
.removeStatusValue(StatusValue.PENDING_TRANSFER)
|
||||
.setTransferData((T) transferDataBuilder.build())
|
||||
.setTransferData(transferDataBuilder.build())
|
||||
.setLastTransferTime(transferData.getPendingTransferExpirationTime())
|
||||
.setPersistedCurrentSponsorRegistrarId(transferData.getGainingRegistrarId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform common operations for projecting an {@link EppResource} at a given time:
|
||||
* Perform common operations for projecting a {@link Domain} at a given time:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Process an automatic transfer.
|
||||
* </ul>
|
||||
*/
|
||||
public static <
|
||||
T extends TransferData,
|
||||
E extends EppResource & ResourceWithTransferData<T>,
|
||||
B extends EppResource.Builder<?, B> & BuilderWithTransferData<T, B>>
|
||||
void projectResourceOntoBuilderAtTime(E resource, B builder, DateTime now) {
|
||||
T transferData = resource.getTransferData();
|
||||
public static void projectResourceOntoBuilderAtTime(
|
||||
DomainBase domain, DomainBase.Builder<?, ?> builder, DateTime now) {
|
||||
DomainTransferData transferData = domain.getTransferData();
|
||||
// If there's a pending transfer that has expired, process it.
|
||||
DateTime expirationTime = transferData.getPendingTransferExpirationTime();
|
||||
if (TransferStatus.PENDING.equals(transferData.getTransferStatus())
|
||||
@@ -207,36 +189,21 @@ public final class EppResourceUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a set of {@link VKey} for domains that reference a specified contact or host.
|
||||
*
|
||||
* <p>This is an eventually consistent query if used for the database.
|
||||
* Returns a set of {@link VKey} for domains that reference a specified host.
|
||||
*
|
||||
* @param key the referent key
|
||||
* @param now the logical time of the check
|
||||
* @param limit the maximum number of returned keys, unlimited if null
|
||||
*/
|
||||
public static ImmutableSet<VKey<Domain>> getLinkedDomainKeys(
|
||||
VKey<? extends EppResource> key, DateTime now, @Nullable Integer limit) {
|
||||
checkArgument(
|
||||
key.getKind().equals(Contact.class) || key.getKind().equals(Host.class),
|
||||
"key must be either VKey<Contact> or VKey<Host>, but it is %s",
|
||||
key);
|
||||
boolean isContactKey = key.getKind().equals(Contact.class);
|
||||
VKey<Host> key, DateTime now, @Nullable Integer limit) {
|
||||
return tm().reTransact(
|
||||
() -> {
|
||||
Query query;
|
||||
if (isContactKey) {
|
||||
query =
|
||||
tm().query(CONTACT_LINKED_DOMAIN_QUERY, String.class)
|
||||
.setParameter("fkRepoId", key)
|
||||
.setParameter("now", now);
|
||||
} else {
|
||||
query =
|
||||
tm().getEntityManager()
|
||||
.createNativeQuery(HOST_LINKED_DOMAIN_QUERY)
|
||||
.setParameter("fkRepoId", key.getKey())
|
||||
.setParameter("now", now.toDate());
|
||||
}
|
||||
Query query =
|
||||
tm().getEntityManager()
|
||||
.createNativeQuery(HOST_LINKED_DOMAIN_QUERY)
|
||||
.setParameter("fkRepoId", key.getKey())
|
||||
.setParameter("now", now.toDate());
|
||||
if (limit != null) {
|
||||
query.setMaxResults(limit);
|
||||
}
|
||||
@@ -252,12 +219,12 @@ public final class EppResourceUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given contact or host is linked to (that is, referenced by) a domain.
|
||||
* Returns whether the given host is linked to (that is, referenced by) a domain.
|
||||
*
|
||||
* @param key the referent key
|
||||
* @param now the logical time of the check
|
||||
*/
|
||||
public static boolean isLinked(VKey<? extends EppResource> key, DateTime now) {
|
||||
public static boolean isLinked(VKey<Host> key, DateTime now) {
|
||||
return !getLinkedDomainKeys(key, now, 1).isEmpty();
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -16,32 +16,25 @@ package google.registry.model;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Sets;
|
||||
import google.registry.model.EppResource.BuilderWithTransferData;
|
||||
import google.registry.model.EppResource.ResourceWithTransferData;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
import google.registry.model.poll.PendingActionNotificationResponse;
|
||||
import google.registry.model.poll.PendingActionNotificationResponse.ContactPendingActionNotificationResponse;
|
||||
import google.registry.model.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse;
|
||||
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.ContactTransferResponse;
|
||||
import google.registry.model.transfer.TransferResponse.DomainTransferResponse;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Static utility functions for resource transfers. */
|
||||
/** Static utility functions for domain transfers. */
|
||||
public final class ResourceTransferUtils {
|
||||
|
||||
private ResourceTransferUtils() {}
|
||||
@@ -50,128 +43,98 @@ public final class ResourceTransferUtils {
|
||||
private static final ImmutableSet<TransferStatus> ADD_EXDATE_STATUSES = Sets.immutableEnumSet(
|
||||
TransferStatus.PENDING, TransferStatus.CLIENT_APPROVED, TransferStatus.SERVER_APPROVED);
|
||||
|
||||
/**
|
||||
* Create a transfer response using the id and type of this resource and the specified {@link
|
||||
* TransferData}.
|
||||
*/
|
||||
/** Create a transfer response using the domain and the specified {@link DomainTransferData}. */
|
||||
public static TransferResponse createTransferResponse(
|
||||
EppResource eppResource, TransferData transferData) {
|
||||
assertIsContactOrDomain(eppResource);
|
||||
@SuppressWarnings("NonCanonicalType")
|
||||
TransferResponse.Builder<? extends TransferResponse, ?> builder;
|
||||
if (eppResource instanceof Contact) {
|
||||
builder = new ContactTransferResponse.Builder().setContactId(eppResource.getForeignKey());
|
||||
} else {
|
||||
DomainTransferData domainTransferData = (DomainTransferData) transferData;
|
||||
builder =
|
||||
new DomainTransferResponse.Builder()
|
||||
.setDomainName(eppResource.getForeignKey())
|
||||
.setExtendedRegistrationExpirationTime(
|
||||
ADD_EXDATE_STATUSES.contains(domainTransferData.getTransferStatus())
|
||||
? domainTransferData.getTransferredRegistrationExpirationTime()
|
||||
: null);
|
||||
}
|
||||
builder
|
||||
Domain domain, DomainTransferData transferData) {
|
||||
return new DomainTransferResponse.Builder()
|
||||
.setDomainName(domain.getForeignKey())
|
||||
.setExtendedRegistrationExpirationTime(
|
||||
ADD_EXDATE_STATUSES.contains(transferData.getTransferStatus())
|
||||
? transferData.getTransferredRegistrationExpirationTime()
|
||||
: null)
|
||||
.setGainingRegistrarId(transferData.getGainingRegistrarId())
|
||||
.setLosingRegistrarId(transferData.getLosingRegistrarId())
|
||||
.setPendingTransferExpirationTime(transferData.getPendingTransferExpirationTime())
|
||||
.setTransferRequestTime(transferData.getTransferRequestTime())
|
||||
.setTransferStatus(transferData.getTransferStatus());
|
||||
return builder.build();
|
||||
.setTransferStatus(transferData.getTransferStatus())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a pending action notification response indicating the resolution of a transfer.
|
||||
*
|
||||
* <p>The returned object will use the id and type of this resource, the trid of the resource's
|
||||
* last transfer request, and the specified status and date.
|
||||
* <p>The returned object will use the trid of the domain's last transfer request, and the
|
||||
* specified status and date.
|
||||
*/
|
||||
public static PendingActionNotificationResponse createPendingTransferNotificationResponse(
|
||||
EppResource eppResource,
|
||||
Trid transferRequestTrid,
|
||||
boolean actionResult,
|
||||
DateTime processedDate) {
|
||||
assertIsContactOrDomain(eppResource);
|
||||
return eppResource instanceof Contact
|
||||
? ContactPendingActionNotificationResponse.create(
|
||||
eppResource.getForeignKey(), actionResult, transferRequestTrid, processedDate)
|
||||
: DomainPendingActionNotificationResponse.create(
|
||||
eppResource.getForeignKey(), actionResult, transferRequestTrid, processedDate);
|
||||
}
|
||||
|
||||
private static void assertIsContactOrDomain(EppResource eppResource) {
|
||||
checkState(eppResource instanceof Contact || eppResource instanceof Domain);
|
||||
Domain domain, Trid transferRequestTrid, boolean actionResult, DateTime processedDate) {
|
||||
return DomainPendingActionNotificationResponse.create(
|
||||
domain.getDomainName(), actionResult, transferRequestTrid, processedDate);
|
||||
}
|
||||
|
||||
/** If there is a transfer out, delete the server-approve entities and enqueue a poll message. */
|
||||
public static <R extends EppResource & ResourceWithTransferData>
|
||||
void handlePendingTransferOnDelete(
|
||||
R resource, R newResource, DateTime now, HistoryEntry historyEntry) {
|
||||
if (resource.getStatusValues().contains(StatusValue.PENDING_TRANSFER)) {
|
||||
TransferData oldTransferData = resource.getTransferData();
|
||||
tm().delete(oldTransferData.getServerApproveEntities());
|
||||
tm().put(
|
||||
new PollMessage.OneTime.Builder()
|
||||
.setRegistrarId(oldTransferData.getGainingRegistrarId())
|
||||
.setEventTime(now)
|
||||
.setMsg(TransferStatus.SERVER_CANCELLED.getMessage())
|
||||
.setResponseData(
|
||||
ImmutableList.of(
|
||||
createTransferResponse(newResource, newResource.getTransferData()),
|
||||
createPendingTransferNotificationResponse(
|
||||
resource, oldTransferData.getTransferRequestTrid(), false, now)))
|
||||
.setHistoryEntry(historyEntry)
|
||||
.build());
|
||||
public static void handlePendingTransferOnDelete(
|
||||
Domain domain, Domain newDomain, DateTime now, HistoryEntry historyEntry) {
|
||||
if (!domain.getStatusValues().contains(StatusValue.PENDING_TRANSFER)) {
|
||||
return;
|
||||
}
|
||||
DomainTransferData oldTransferData = domain.getTransferData();
|
||||
tm().delete(oldTransferData.getServerApproveEntities());
|
||||
tm().put(
|
||||
new PollMessage.OneTime.Builder()
|
||||
.setRegistrarId(oldTransferData.getGainingRegistrarId())
|
||||
.setEventTime(now)
|
||||
.setMsg(TransferStatus.SERVER_CANCELLED.getMessage())
|
||||
.setResponseData(
|
||||
ImmutableList.of(
|
||||
createTransferResponse(newDomain, newDomain.getTransferData()),
|
||||
createPendingTransferNotificationResponse(
|
||||
domain, oldTransferData.getTransferRequestTrid(), false, now)))
|
||||
.setHistoryEntry(historyEntry)
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn a resource into a builder with its pending transfer resolved.
|
||||
* 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 <
|
||||
R extends EppResource & ResourceWithTransferData,
|
||||
B extends EppResource.Builder<R, B> & BuilderWithTransferData<TransferData, B>>
|
||||
B resolvePendingTransfer(R resource, TransferStatus transferStatus, DateTime now) {
|
||||
private static Domain.Builder resolvePendingTransfer(
|
||||
Domain domain, TransferStatus transferStatus, DateTime now) {
|
||||
checkArgument(
|
||||
resource.getStatusValues().contains(StatusValue.PENDING_TRANSFER),
|
||||
"Resource is not in pending transfer status.");
|
||||
checkArgument(!resource.getTransferData().isEmpty(), "No old transfer data to resolve.");
|
||||
@SuppressWarnings("unchecked")
|
||||
B builder = (B) resource.asBuilder();
|
||||
domain.getStatusValues().contains(StatusValue.PENDING_TRANSFER),
|
||||
"Domain is not in pending transfer status.");
|
||||
checkArgument(!domain.getTransferData().isEmpty(), "No old transfer data to resolve.");
|
||||
|
||||
return builder
|
||||
return domain
|
||||
.asBuilder()
|
||||
.removeStatusValue(StatusValue.PENDING_TRANSFER)
|
||||
.setTransferData(
|
||||
(TransferData)
|
||||
resource
|
||||
.getTransferData()
|
||||
.copyConstantFieldsToBuilder()
|
||||
.setTransferStatus(transferStatus)
|
||||
.setPendingTransferExpirationTime(checkNotNull(now))
|
||||
.build());
|
||||
domain
|
||||
.getTransferData()
|
||||
.copyConstantFieldsToBuilder()
|
||||
.setTransferStatus(transferStatus)
|
||||
.setPendingTransferExpirationTime(checkNotNull(now))
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 <
|
||||
R extends EppResource & ResourceWithTransferData,
|
||||
B extends EppResource.Builder<R, B> & BuilderWithTransferData<TransferData, B>>
|
||||
R approvePendingTransfer(R resource, TransferStatus transferStatus, DateTime now) {
|
||||
public static Domain approvePendingTransfer(
|
||||
Domain domain, TransferStatus transferStatus, DateTime now) {
|
||||
checkArgument(transferStatus.isApproved(), "Not an approval transfer status");
|
||||
B builder = resolvePendingTransfer(resource, transferStatus, now);
|
||||
Domain.Builder builder = resolvePendingTransfer(domain, transferStatus, now);
|
||||
return builder
|
||||
.setLastTransferTime(now)
|
||||
.setPersistedCurrentSponsorRegistrarId(resource.getTransferData().getGainingRegistrarId())
|
||||
.setPersistedCurrentSponsorRegistrarId(domain.getTransferData().getGainingRegistrarId())
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -179,14 +142,14 @@ 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 <R extends EppResource & ResourceWithTransferData> R denyPendingTransfer(
|
||||
R resource, TransferStatus transferStatus, DateTime now, String lastEppUpdateRegistrarId) {
|
||||
public static Domain denyPendingTransfer(
|
||||
Domain domain, TransferStatus transferStatus, DateTime now, String lastEppUpdateRegistrarId) {
|
||||
checkArgument(transferStatus.isDenied(), "Not a denial transfer status");
|
||||
return resolvePendingTransfer(resource, transferStatus, now)
|
||||
return resolvePendingTransfer(domain, transferStatus, now)
|
||||
.setLastEppUpdateTime(now)
|
||||
.setLastEppUpdateRegistrarId(lastEppUpdateRegistrarId)
|
||||
.build();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,102 +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;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* 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 Contact cloneProjectedAtTime(DateTime now) {
|
||||
return ContactBase.cloneContactProjectedAtTime(this, now);
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,398 +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 static google.registry.model.EppResourceUtils.projectResourceOntoBuilderAtTime;
|
||||
|
||||
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;
|
||||
|
||||
/** Contact’s 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;
|
||||
|
||||
/** Contact’s 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;
|
||||
|
||||
/** Contact’s 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) {
|
||||
return cloneContactProjectedAtTime(this, now);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones the contact (or subclass). A separate static method so that we can pass in and return a
|
||||
* T without the compiler complaining.
|
||||
*/
|
||||
protected static <T extends ContactBase> T cloneContactProjectedAtTime(T contact, DateTime now) {
|
||||
Builder builder = contact.asBuilder();
|
||||
projectResourceOntoBuilderAtTime(contact, builder, now);
|
||||
return (T) builder.build();
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -16,23 +16,20 @@ package google.registry.model.domain;
|
||||
|
||||
import static com.google.common.base.MoreObjects.firstNonNull;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.collect.Iterables.getOnlyElement;
|
||||
import static com.google.common.collect.Sets.difference;
|
||||
import static google.registry.util.CollectionUtils.difference;
|
||||
import static google.registry.util.CollectionUtils.forceEmptyToNull;
|
||||
import static google.registry.util.CollectionUtils.isNullOrEmpty;
|
||||
import static google.registry.util.CollectionUtils.nullSafeImmutableCopy;
|
||||
import static google.registry.util.CollectionUtils.nullToEmpty;
|
||||
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
|
||||
import static google.registry.util.CollectionUtils.union;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.flows.EppException.ParameterValuePolicyErrorException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.RegistrantProhibitedException;
|
||||
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;
|
||||
@@ -67,7 +64,8 @@ public class DomainCommand {
|
||||
*/
|
||||
public interface CreateOrUpdate<T extends CreateOrUpdate<T>> extends SingleResourceCommand {
|
||||
/** Creates a copy of this command with hard links to hosts and contacts. */
|
||||
T cloneAndLinkReferences(DateTime now) throws InvalidReferencesException;
|
||||
T cloneAndLinkReferences(DateTime now)
|
||||
throws InvalidReferencesException, ParameterValuePolicyErrorException;
|
||||
}
|
||||
|
||||
/** The fields on "chgType" from <a href="http://tools.ietf.org/html/rfc5731">RFC5731</a>. */
|
||||
@@ -80,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;
|
||||
|
||||
@@ -90,10 +85,6 @@ public class DomainCommand {
|
||||
return Optional.ofNullable(registrantContactId);
|
||||
}
|
||||
|
||||
public Optional<VKey<Contact>> getRegistrant() {
|
||||
return Optional.ofNullable(registrant);
|
||||
}
|
||||
|
||||
public DomainAuthInfo getAuthInfo() {
|
||||
return authInfo;
|
||||
}
|
||||
@@ -132,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;
|
||||
|
||||
@@ -160,10 +147,6 @@ public class DomainCommand {
|
||||
return nullToEmptyImmutableCopy(nameservers);
|
||||
}
|
||||
|
||||
public ImmutableSet<DesignatedContact> getContacts() {
|
||||
return nullToEmptyImmutableCopy(contacts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DomainAuthInfo getAuthInfo() {
|
||||
return authInfo;
|
||||
@@ -171,26 +154,15 @@ public class DomainCommand {
|
||||
|
||||
/** Creates a copy of this {@link Create} with hard links to hosts and contacts. */
|
||||
@Override
|
||||
public Create cloneAndLinkReferences(DateTime now) throws InvalidReferencesException {
|
||||
public Create cloneAndLinkReferences(DateTime now)
|
||||
throws InvalidReferencesException, ParameterValuePolicyErrorException {
|
||||
Create clone = clone(this);
|
||||
clone.nameservers = linkHosts(clone.nameserverHostNames, now);
|
||||
if (registrantContactId == null) {
|
||||
clone.contacts = linkContacts(clone.foreignKeyedDesignatedContacts, now);
|
||||
} else {
|
||||
// Load the registrant and contacts in one shot.
|
||||
ForeignKeyedDesignatedContact registrantPlaceholder = new ForeignKeyedDesignatedContact();
|
||||
registrantPlaceholder.contactId = clone.registrantContactId;
|
||||
registrantPlaceholder.type = DesignatedContact.Type.REGISTRANT;
|
||||
Set<DesignatedContact> contacts = linkContacts(
|
||||
union(nullToEmpty(clone.foreignKeyedDesignatedContacts), registrantPlaceholder),
|
||||
now);
|
||||
for (DesignatedContact contact : contacts) {
|
||||
if (DesignatedContact.Type.REGISTRANT.equals(contact.getType())) {
|
||||
clone.registrant = contact.getContactKey();
|
||||
clone.contacts = forceEmptyToNull(difference(contacts, contact));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (registrantContactId != null) {
|
||||
throw new RegistrantProhibitedException();
|
||||
}
|
||||
if (!isNullOrEmpty(foreignKeyedDesignatedContacts)) {
|
||||
throw new ContactsProhibitedException();
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
@@ -352,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);
|
||||
}
|
||||
@@ -364,15 +332,14 @@ 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 {
|
||||
private AddRemove cloneAndLinkReferences(DateTime now)
|
||||
throws InvalidReferencesException, ContactsProhibitedException {
|
||||
AddRemove clone = clone(this);
|
||||
clone.nameservers = linkHosts(clone.nameserverHostNames, now);
|
||||
clone.contacts = linkContacts(clone.foreignKeyedDesignatedContacts, now);
|
||||
if (!isNullOrEmpty(foreignKeyedDesignatedContacts)) {
|
||||
throw new ContactsProhibitedException();
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
@@ -380,16 +347,11 @@ public class DomainCommand {
|
||||
/** The inner change type on a domain update command. */
|
||||
@XmlType(propOrder = {"registrantContactId", "authInfo"})
|
||||
public static class Change extends DomainCreateOrChange<Domain.Builder> {
|
||||
/** Creates a copy of this {@link Change} with hard links to hosts and contacts. */
|
||||
Change cloneAndLinkReferences(DateTime now) throws InvalidReferencesException {
|
||||
Change cloneAndLinkReferences() throws RegistrantProhibitedException {
|
||||
Change clone = clone(this);
|
||||
clone.registrant =
|
||||
Strings.isNullOrEmpty(clone.registrantContactId)
|
||||
? null
|
||||
: getOnlyElement(
|
||||
loadByForeignKeysCached(
|
||||
ImmutableSet.of(clone.registrantContactId), Contact.class, now)
|
||||
.values());
|
||||
if (clone.registrantContactId != null) {
|
||||
throw new RegistrantProhibitedException();
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
@@ -401,11 +363,12 @@ public class DomainCommand {
|
||||
* of those classes, which is harmless because the getters do that anyways.
|
||||
*/
|
||||
@Override
|
||||
public Update cloneAndLinkReferences(DateTime now) throws InvalidReferencesException {
|
||||
public Update cloneAndLinkReferences(DateTime now)
|
||||
throws InvalidReferencesException, ParameterValuePolicyErrorException {
|
||||
Update clone = clone(this);
|
||||
clone.innerAdd = clone.getInnerAdd().cloneAndLinkReferences(now);
|
||||
clone.innerRemove = clone.getInnerRemove().cloneAndLinkReferences(now);
|
||||
clone.innerChange = clone.getInnerChange().cloneAndLinkReferences(now);
|
||||
clone.innerChange = clone.getInnerChange().cloneAndLinkReferences();
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
@@ -415,37 +378,17 @@ public class DomainCommand {
|
||||
if (hostNames == null) {
|
||||
return null;
|
||||
}
|
||||
return ImmutableSet.copyOf(loadByForeignKeysCached(hostNames, Host.class, now).values());
|
||||
return ImmutableSet.copyOf(loadByForeignKeysCached(hostNames, now).values());
|
||||
}
|
||||
|
||||
private static Set<DesignatedContact> linkContacts(
|
||||
Set<ForeignKeyedDesignatedContact> contacts, DateTime now) throws InvalidReferencesException {
|
||||
if (contacts == null) {
|
||||
return null;
|
||||
}
|
||||
ImmutableSet.Builder<String> foreignKeys = new ImmutableSet.Builder<>();
|
||||
for (ForeignKeyedDesignatedContact contact : contacts) {
|
||||
foreignKeys.add(contact.contactId);
|
||||
}
|
||||
ImmutableMap<String, VKey<Contact>> loadedContacts =
|
||||
loadByForeignKeysCached(foreignKeys.build(), Contact.class, now);
|
||||
ImmutableSet.Builder<DesignatedContact> linkedContacts = new ImmutableSet.Builder<>();
|
||||
for (ForeignKeyedDesignatedContact contact : contacts) {
|
||||
linkedContacts.add(
|
||||
DesignatedContact.create(contact.type, loadedContacts.get(contact.contactId)));
|
||||
}
|
||||
return linkedContacts.build();
|
||||
}
|
||||
|
||||
/** Loads keys to cached EPP resources by their foreign keys. */
|
||||
private static <T extends EppResource> ImmutableMap<String, VKey<T>> loadByForeignKeysCached(
|
||||
final Set<String> foreignKeys, final Class<T> clazz, final DateTime now)
|
||||
throws InvalidReferencesException {
|
||||
ImmutableMap<String, VKey<T>> fks =
|
||||
ForeignKeyUtils.loadKeysByCacheIfEnabled(clazz, foreignKeys, now);
|
||||
/** Loads host keys to cached EPP resources by their foreign keys. */
|
||||
private static ImmutableMap<String, VKey<Host>> loadByForeignKeysCached(
|
||||
final Set<String> foreignKeys, final DateTime now) throws InvalidReferencesException {
|
||||
ImmutableMap<String, VKey<Host>> fks =
|
||||
ForeignKeyUtils.loadKeysByCacheIfEnabled(Host.class, foreignKeys, now);
|
||||
if (!fks.keySet().equals(foreignKeys)) {
|
||||
throw new InvalidReferencesException(
|
||||
clazz, ImmutableSet.copyOf(difference(foreignKeys, fks.keySet())));
|
||||
Host.class, ImmutableSet.copyOf(difference(foreignKeys, fks.keySet())));
|
||||
}
|
||||
return fks;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -20,9 +20,14 @@ import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.flows.FeeExtensionXmlTagNormalizer;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.domain.fee.FeeCheckResponseExtension;
|
||||
import google.registry.model.domain.fee.FeeTransformResponseExtension;
|
||||
import google.registry.model.domain.fee06.FeeInfoResponseExtensionV06;
|
||||
import google.registry.model.eppinput.EppInput;
|
||||
import google.registry.model.eppoutput.EppOutput;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.util.RegistryEnvironment;
|
||||
import google.registry.xml.ValidationMode;
|
||||
import google.registry.xml.XmlException;
|
||||
@@ -98,8 +103,31 @@ public class EppXmlTransformer {
|
||||
return byteArrayOutputStream.toByteArray();
|
||||
}
|
||||
|
||||
private static boolean hasFeeExtension(EppOutput eppOutput) {
|
||||
if (!eppOutput.isResponse()) {
|
||||
return false;
|
||||
}
|
||||
return eppOutput.getResponse().getExtensions().stream()
|
||||
.map(EppResponse.ResponseExtension::getClass)
|
||||
.filter(EppXmlTransformer::isFeeExtension)
|
||||
.findAny()
|
||||
.isPresent();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static boolean isFeeExtension(Class<?> clazz) {
|
||||
return FeeCheckResponseExtension.class.isAssignableFrom(clazz)
|
||||
|| FeeTransformResponseExtension.class.isAssignableFrom(clazz)
|
||||
|| FeeInfoResponseExtensionV06.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
public static byte[] marshal(EppOutput root, ValidationMode validation) throws XmlException {
|
||||
return marshal(OUTPUT_TRANSFORMER, root, validation);
|
||||
byte[] bytes = marshal(OUTPUT_TRANSFORMER, root, validation);
|
||||
if (!RegistryEnvironment.PRODUCTION.equals(RegistryEnvironment.get())
|
||||
&& hasFeeExtension(root)) {
|
||||
return FeeExtensionXmlTagNormalizer.normalize(new String(bytes, UTF_8)).getBytes(UTF_8);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
|
||||
@@ -18,6 +18,7 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static com.google.common.collect.Maps.uniqueIndex;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.VerifyException;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.model.domain.fee06.FeeCheckCommandExtensionV06;
|
||||
@@ -87,6 +88,7 @@ public class ProtocolDefinition {
|
||||
private final Class<? extends CommandExtension> commandExtensionClass;
|
||||
private final Class<? extends ResponseExtension> responseExtensionClass;
|
||||
private final String uri;
|
||||
private final String xmlTag;
|
||||
private final ServiceExtensionVisibility visibility;
|
||||
|
||||
ServiceExtension(
|
||||
@@ -96,6 +98,7 @@ public class ProtocolDefinition {
|
||||
this.commandExtensionClass = commandExtensionClass;
|
||||
this.responseExtensionClass = responseExtensionClass;
|
||||
this.uri = getCommandExtensionUri(commandExtensionClass);
|
||||
this.xmlTag = getCommandExtensionXmlTag(commandExtensionClass);
|
||||
this.visibility = visibility;
|
||||
}
|
||||
|
||||
@@ -111,11 +114,27 @@ public class ProtocolDefinition {
|
||||
return uri;
|
||||
}
|
||||
|
||||
public String getXmlTag() {
|
||||
return xmlTag;
|
||||
}
|
||||
|
||||
/** Returns the namespace URI of the command extension class. */
|
||||
public static String getCommandExtensionUri(Class<? extends CommandExtension> clazz) {
|
||||
return clazz.getPackage().getAnnotation(XmlSchema.class).namespace();
|
||||
}
|
||||
|
||||
/** Returns the XML tag for this extension in the response message. */
|
||||
public static String getCommandExtensionXmlTag(Class<? extends CommandExtension> clazz) {
|
||||
var xmlSchema = clazz.getPackage().getAnnotation(XmlSchema.class);
|
||||
var xmlns = xmlSchema.xmlns();
|
||||
if (xmlns == null || xmlns.length != 1) {
|
||||
throw new VerifyException(
|
||||
String.format(
|
||||
"Expecting exactly one NS declaration in %s", clazz.getPackage().getName()));
|
||||
}
|
||||
return xmlns[0].prefix();
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
return switch (visibility) {
|
||||
case ALL -> true;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -164,7 +164,7 @@ public class EppInput extends ImmutableObject {
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
/** A tag that goes inside of an EPP {@literal <command>}. */
|
||||
/** A tag that goes inside an EPP {@literal <command>}. */
|
||||
public static class InnerCommand extends ImmutableObject {}
|
||||
|
||||
/** A command that has an extension inside of it. */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -26,21 +26,18 @@ import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import google.registry.model.annotations.ExternalMessagingName;
|
||||
import google.registry.model.annotations.IdAllocation;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
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;
|
||||
@@ -98,7 +95,7 @@ public abstract class PollMessage extends ImmutableObject
|
||||
/** Indicates the type of entity the poll message is for. */
|
||||
public enum Type {
|
||||
DOMAIN(1L, Domain.class),
|
||||
CONTACT(2L, Contact.class),
|
||||
// Contacts would be 2L but have since been removed. Host is kept at 3 for consistency.
|
||||
HOST(3L, Host.class);
|
||||
|
||||
private final long id;
|
||||
@@ -179,16 +176,6 @@ public abstract class PollMessage extends ImmutableObject
|
||||
return domainRepoId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the contact repo id.
|
||||
*
|
||||
* <p>This may only be used on a {@link Contact} poll event.
|
||||
*/
|
||||
public String getContactRepoId() {
|
||||
checkArgument(getType() == Type.CONTACT);
|
||||
return contactRepoId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the host repo id.
|
||||
*
|
||||
@@ -215,7 +202,7 @@ public abstract class PollMessage extends ImmutableObject
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return domainRepoId != null ? Type.DOMAIN : contactRepoId != null ? Type.CONTACT : Type.HOST;
|
||||
return domainRepoId != null ? Type.DOMAIN : Type.HOST;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -417,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(
|
||||
@@ -443,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()
|
||||
@@ -499,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
|
||||
@@ -518,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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -14,28 +14,12 @@
|
||||
|
||||
package google.registry.privileges.secretmanager;
|
||||
|
||||
import com.google.cloud.secretmanager.v1.SecretVersionName;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.privileges.secretmanager.SecretManagerClient.NoSuchSecretResourceException;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Storage of SQL users' login credentials, backed by Cloud Secret Manager.
|
||||
*
|
||||
* <p>A user's credential is stored with one level of indirection using two secret IDs: Each version
|
||||
* of the <em>credential data</em> is stored as follows: its secret ID is determined by {@link
|
||||
* #getCredentialDataSecretId(SqlUser, String dbInstance)}, and the value of each version is a
|
||||
* {@link SqlCredential}, serialized using {@link SqlCredential#toFormattedString}. The 'live'
|
||||
* version of the credential is saved under the 'live pointer' secret explained below.
|
||||
*
|
||||
* <p>The pointer to the 'live' version of the credential data is stored as follows: its secret ID
|
||||
* is determined by {@link #getLiveLabelSecretId(SqlUser, String dbInstance)}; and the value of each
|
||||
* version is a {@link SecretVersionName} in String form, pointing to a version of the credential
|
||||
* data. Only the 'latest' version of this secret should be used. It is guaranteed to be valid.
|
||||
*
|
||||
* <p>The indirection in credential storage makes it easy to handle failures in the credential
|
||||
* change process.
|
||||
*/
|
||||
public class SqlCredentialStore {
|
||||
private final SecretManagerClient csmClient;
|
||||
@@ -49,61 +33,19 @@ public class SqlCredentialStore {
|
||||
}
|
||||
|
||||
public SqlCredential getCredential(SqlUser user) {
|
||||
SecretVersionName credentialName = getLiveCredentialSecretVersion(user);
|
||||
return SqlCredential.fromFormattedString(
|
||||
csmClient.getSecretData(
|
||||
credentialName.getSecret(), Optional.of(credentialName.getSecretVersion())));
|
||||
var secretId = getSecretIdForUserPassword(user);
|
||||
var secretData = csmClient.getSecretData(secretId, Optional.empty());
|
||||
return SqlCredential.fromFormattedString(secretData);
|
||||
}
|
||||
|
||||
public void createOrUpdateCredential(SqlUser user, String password) {
|
||||
SecretVersionName dataName = saveCredentialData(user, password);
|
||||
saveLiveLabel(user, dataName);
|
||||
var secretId = getSecretIdForUserPassword(user);
|
||||
csmClient.createSecretIfAbsent(secretId);
|
||||
csmClient.addSecretVersion(
|
||||
secretId, SqlCredential.create(user.geUserName(), password).toFormattedString());
|
||||
}
|
||||
|
||||
public void deleteCredential(SqlUser user) {
|
||||
try {
|
||||
csmClient.deleteSecret(getCredentialDataSecretId(user, dbInstance));
|
||||
} catch (NoSuchSecretResourceException e) {
|
||||
// ok
|
||||
}
|
||||
try {
|
||||
csmClient.deleteSecret(getLiveLabelSecretId(user, dbInstance));
|
||||
} catch (NoSuchSecretResourceException e) {
|
||||
// ok.
|
||||
}
|
||||
}
|
||||
|
||||
private SecretVersionName saveCredentialData(SqlUser user, String password) {
|
||||
String credentialDataSecretId = getCredentialDataSecretId(user, dbInstance);
|
||||
csmClient.createSecretIfAbsent(credentialDataSecretId);
|
||||
String credentialVersion =
|
||||
csmClient.addSecretVersion(
|
||||
credentialDataSecretId,
|
||||
SqlCredential.create(createDatabaseLoginName(user), password).toFormattedString());
|
||||
return SecretVersionName.of(csmClient.getProject(), credentialDataSecretId, credentialVersion);
|
||||
}
|
||||
|
||||
private void saveLiveLabel(SqlUser user, SecretVersionName dataVersionName) {
|
||||
String liveLabelSecretId = getLiveLabelSecretId(user, dbInstance);
|
||||
csmClient.createSecretIfAbsent(liveLabelSecretId);
|
||||
csmClient.addSecretVersion(liveLabelSecretId, dataVersionName.toString());
|
||||
}
|
||||
|
||||
private SecretVersionName getLiveCredentialSecretVersion(SqlUser user) {
|
||||
return SecretVersionName.parse(
|
||||
csmClient.getSecretData(getLiveLabelSecretId(user, dbInstance), Optional.empty()));
|
||||
}
|
||||
|
||||
private static String getLiveLabelSecretId(SqlUser user, String dbInstance) {
|
||||
return String.format("sql-cred-live-label-%s-%s", user.geUserName(), dbInstance);
|
||||
}
|
||||
|
||||
private static String getCredentialDataSecretId(SqlUser user, String dbInstance) {
|
||||
return String.format("sql-cred-data-%s-%s", user.geUserName(), dbInstance);
|
||||
}
|
||||
|
||||
// WIP: when b/170230882 is complete, login will be versioned.
|
||||
private static String createDatabaseLoginName(SqlUser user) {
|
||||
return user.geUserName();
|
||||
private String getSecretIdForUserPassword(SqlUser user) {
|
||||
return String.format("sql-password-for-%s-on-%s", user.geUserName(), this.dbInstance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,192 +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.rde;
|
||||
|
||||
import static google.registry.util.XmlEnumUtils.enumToXml;
|
||||
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactAddress;
|
||||
import google.registry.model.contact.ContactPhoneNumber;
|
||||
import google.registry.model.contact.Disclose;
|
||||
import google.registry.model.contact.Disclose.PostalInfoChoice;
|
||||
import google.registry.model.contact.PostalInfo;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.transfer.TransferData;
|
||||
import google.registry.xjc.contact.XjcContactAddrType;
|
||||
import google.registry.xjc.contact.XjcContactDiscloseType;
|
||||
import google.registry.xjc.contact.XjcContactE164Type;
|
||||
import google.registry.xjc.contact.XjcContactIntLocType;
|
||||
import google.registry.xjc.contact.XjcContactPostalInfoEnumType;
|
||||
import google.registry.xjc.contact.XjcContactPostalInfoType;
|
||||
import google.registry.xjc.contact.XjcContactStatusType;
|
||||
import google.registry.xjc.contact.XjcContactStatusValueType;
|
||||
import google.registry.xjc.eppcom.XjcEppcomTrStatusType;
|
||||
import google.registry.xjc.rdecontact.XjcRdeContact;
|
||||
import google.registry.xjc.rdecontact.XjcRdeContactElement;
|
||||
import google.registry.xjc.rdecontact.XjcRdeContactTransferDataType;
|
||||
import javax.annotation.CheckForNull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/** Utility class that turns {@link Contact} as {@link XjcRdeContactElement}. */
|
||||
final class ContactToXjcConverter {
|
||||
|
||||
/** Converts {@link Contact} to {@link XjcRdeContactElement}. */
|
||||
static XjcRdeContactElement convert(Contact host) {
|
||||
return new XjcRdeContactElement(convertContact(host));
|
||||
}
|
||||
|
||||
/** Converts {@link Contact} to {@link XjcRdeContact}. */
|
||||
static XjcRdeContact convertContact(Contact model) {
|
||||
XjcRdeContact bean = new XjcRdeContact();
|
||||
bean.setRoid(model.getRepoId());
|
||||
for (StatusValue status : model.getStatusValues()) {
|
||||
bean.getStatuses().add(convertStatusValue(status));
|
||||
}
|
||||
PostalInfo localizedPostalInfo = model.getLocalizedPostalInfo();
|
||||
if (localizedPostalInfo != null) {
|
||||
bean.getPostalInfos().add(convertPostalInfo(localizedPostalInfo));
|
||||
}
|
||||
PostalInfo internationalizedPostalInfo = model.getInternationalizedPostalInfo();
|
||||
if (internationalizedPostalInfo != null) {
|
||||
bean.getPostalInfos().add(convertPostalInfo(internationalizedPostalInfo));
|
||||
}
|
||||
bean.setId(model.getContactId());
|
||||
bean.setClID(model.getCurrentSponsorRegistrarId());
|
||||
bean.setCrRr(RdeAdapter.convertRr(model.getCreationRegistrarId(), null));
|
||||
bean.setUpRr(RdeAdapter.convertRr(model.getLastEppUpdateRegistrarId(), null));
|
||||
bean.setCrDate(model.getCreationTime());
|
||||
bean.setUpDate(model.getLastEppUpdateTime());
|
||||
bean.setTrDate(model.getLastTransferTime());
|
||||
bean.setVoice(convertPhoneNumber(model.getVoiceNumber()));
|
||||
bean.setFax(convertPhoneNumber(model.getFaxNumber()));
|
||||
bean.setEmail(model.getEmailAddress());
|
||||
bean.setDisclose(convertDisclose(model.getDisclose()));
|
||||
|
||||
// o An OPTIONAL <trnData> element that contains the following child
|
||||
// elements related to the last transfer request of the contact
|
||||
// object:
|
||||
//
|
||||
// * A <trStatus> element that contains the state of the most recent
|
||||
// transfer request.
|
||||
//
|
||||
// * A <reRr> element that contains the identifier of the registrar
|
||||
// that requested the domain name object transfer. An OPTIONAL
|
||||
// client attribute is used to specify the client that performed
|
||||
// the operation.
|
||||
//
|
||||
// * An <acRr> element that contains the identifier of the registrar
|
||||
// that SHOULD act upon a PENDING transfer request. For all other
|
||||
// status types, the value identifies the registrar that took the
|
||||
// indicated action. An OPTIONAL client attribute is used to
|
||||
// specify the client that performed the operation.
|
||||
//
|
||||
// * A <reDate> element that contains the date and time that the
|
||||
// transfer was requested.
|
||||
//
|
||||
// * An <acDate> element that contains the date and time of a
|
||||
// required or completed response. For a PENDING request, the
|
||||
// value identifies the date and time by which a response is
|
||||
// required before an automated response action will be taken by
|
||||
// the registry. For all other status types, the value identifies
|
||||
// the date and time when the request was completed.
|
||||
if (!model.getTransferData().isEmpty()) {
|
||||
bean.setTrnData(convertTransferData(model.getTransferData()));
|
||||
}
|
||||
|
||||
return bean;
|
||||
}
|
||||
|
||||
/** Converts {@link TransferData} to {@link XjcRdeContactTransferDataType}. */
|
||||
private static XjcRdeContactTransferDataType convertTransferData(TransferData model) {
|
||||
XjcRdeContactTransferDataType bean = new XjcRdeContactTransferDataType();
|
||||
bean.setTrStatus(XjcEppcomTrStatusType.fromValue(model.getTransferStatus().getXmlName()));
|
||||
bean.setReRr(RdeUtils.makeXjcRdeRrType(model.getGainingRegistrarId()));
|
||||
bean.setAcRr(RdeUtils.makeXjcRdeRrType(model.getLosingRegistrarId()));
|
||||
bean.setReDate(model.getTransferRequestTime());
|
||||
bean.setAcDate(model.getPendingTransferExpirationTime());
|
||||
return bean;
|
||||
}
|
||||
|
||||
/** Converts {@link ContactAddress} to {@link XjcContactAddrType}. */
|
||||
private static XjcContactAddrType convertAddress(ContactAddress model) {
|
||||
XjcContactAddrType bean = new XjcContactAddrType();
|
||||
bean.getStreets().addAll(model.getStreet());
|
||||
bean.setCity(model.getCity());
|
||||
bean.setSp(model.getState());
|
||||
bean.setPc(model.getZip());
|
||||
bean.setCc(model.getCountryCode());
|
||||
return bean;
|
||||
}
|
||||
|
||||
/** Converts {@link Disclose} to {@link XjcContactDiscloseType}. */
|
||||
@Nullable
|
||||
@CheckForNull
|
||||
static XjcContactDiscloseType convertDisclose(@Nullable Disclose model) {
|
||||
if (model == null) {
|
||||
return null;
|
||||
}
|
||||
XjcContactDiscloseType bean = new XjcContactDiscloseType();
|
||||
bean.setFlag(model.getFlag());
|
||||
for (PostalInfoChoice loc : model.getNames()) {
|
||||
bean.getNames().add(convertPostalInfoChoice(loc));
|
||||
}
|
||||
for (PostalInfoChoice loc : model.getOrgs()) {
|
||||
bean.getOrgs().add(convertPostalInfoChoice(loc));
|
||||
}
|
||||
for (PostalInfoChoice loc : model.getAddrs()) {
|
||||
bean.getAddrs().add(convertPostalInfoChoice(loc));
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
/** Converts {@link ContactPhoneNumber} to {@link XjcContactE164Type}. */
|
||||
@Nullable
|
||||
@CheckForNull
|
||||
private static XjcContactE164Type convertPhoneNumber(@Nullable ContactPhoneNumber model) {
|
||||
if (model == null) {
|
||||
return null;
|
||||
}
|
||||
XjcContactE164Type bean = new XjcContactE164Type();
|
||||
bean.setValue(model.getPhoneNumber());
|
||||
bean.setX(model.getExtension());
|
||||
return bean;
|
||||
}
|
||||
|
||||
/** Converts {@link PostalInfoChoice} to {@link XjcContactIntLocType}. */
|
||||
private static XjcContactIntLocType convertPostalInfoChoice(PostalInfoChoice model) {
|
||||
XjcContactIntLocType bean = new XjcContactIntLocType();
|
||||
bean.setType(XjcContactPostalInfoEnumType.fromValue(enumToXml(model.getType())));
|
||||
return bean;
|
||||
}
|
||||
|
||||
/** Converts {@link PostalInfo} to {@link XjcContactPostalInfoType}. */
|
||||
private static XjcContactPostalInfoType convertPostalInfo(PostalInfo model) {
|
||||
XjcContactPostalInfoType bean = new XjcContactPostalInfoType();
|
||||
bean.setName(model.getName());
|
||||
bean.setOrg(model.getOrg());
|
||||
bean.setAddr(convertAddress(model.getAddress()));
|
||||
bean.setType(XjcContactPostalInfoEnumType.fromValue(enumToXml(model.getType())));
|
||||
return bean;
|
||||
}
|
||||
|
||||
/** Converts {@link StatusValue} to {@link XjcContactStatusType}. */
|
||||
private static XjcContactStatusType convertStatusValue(StatusValue model) {
|
||||
XjcContactStatusType bean = new XjcContactStatusType();
|
||||
bean.setS(XjcContactStatusValueType.fromValue(model.getXmlName()));
|
||||
return bean;
|
||||
}
|
||||
|
||||
private ContactToXjcConverter() {}
|
||||
}
|
||||
@@ -14,25 +14,16 @@
|
||||
|
||||
package google.registry.rde;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.DesignatedContact;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||
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.persistence.VKey;
|
||||
import google.registry.util.Idn;
|
||||
import google.registry.xjc.domain.XjcDomainContactAttrType;
|
||||
import google.registry.xjc.domain.XjcDomainContactType;
|
||||
import google.registry.xjc.domain.XjcDomainNsType;
|
||||
import google.registry.xjc.domain.XjcDomainStatusType;
|
||||
import google.registry.xjc.domain.XjcDomainStatusValueType;
|
||||
@@ -44,7 +35,6 @@ import google.registry.xjc.rgp.XjcRgpStatusType;
|
||||
import google.registry.xjc.rgp.XjcRgpStatusValueType;
|
||||
import google.registry.xjc.secdns.XjcSecdnsDsDataType;
|
||||
import google.registry.xjc.secdns.XjcSecdnsDsOrKeyType;
|
||||
import java.util.Optional;
|
||||
|
||||
/** Utility class that turns {@link Domain} as {@link XjcRdeDomainElement}. */
|
||||
final class DomainToXjcConverter {
|
||||
@@ -152,8 +142,6 @@ final class DomainToXjcConverter {
|
||||
|
||||
switch (mode) {
|
||||
case FULL:
|
||||
String domainName = model.getDomainName();
|
||||
|
||||
// o Zero or more OPTIONAL <rgpStatus> element to represent
|
||||
// "pendingDelete" sub-statuses, including "redemptionPeriod",
|
||||
// "pendingRestore", and "pendingDelete", that a domain name can be
|
||||
@@ -163,25 +151,6 @@ final class DomainToXjcConverter {
|
||||
bean.getRgpStatuses().add(convertGracePeriodStatus(status));
|
||||
}
|
||||
|
||||
// o An OPTIONAL <registrant> element that contain the identifier for
|
||||
// the human or organizational social information object associated
|
||||
// as the holder of the domain name object.
|
||||
Optional<VKey<Contact>> registrant = model.getRegistrant();
|
||||
if (registrant.isPresent()) {
|
||||
Optional<Contact> registrantContact =
|
||||
tm().transact(() -> tm().loadByKeyIfPresent(registrant.get()));
|
||||
registrantContact.ifPresent(c -> bean.setRegistrant(c.getContactId()));
|
||||
}
|
||||
|
||||
// o Zero or more OPTIONAL <contact> elements that contain identifiers
|
||||
// for the human or organizational social information objects
|
||||
// associated with the domain name object.
|
||||
for (DesignatedContact contact : model.getContacts()) {
|
||||
Optional<XjcDomainContactType> contactType =
|
||||
convertDesignatedContact(contact, domainName);
|
||||
contactType.ifPresent(c -> bean.getContacts().add(c));
|
||||
}
|
||||
|
||||
// o An OPTIONAL <secDNS> element that contains the public key
|
||||
// information associated with Domain Name System security (DNSSEC)
|
||||
// extensions for the domain name as specified in [RFC5910].
|
||||
@@ -251,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(
|
||||
@@ -289,23 +258,5 @@ final class DomainToXjcConverter {
|
||||
return bean;
|
||||
}
|
||||
|
||||
/** Converts {@link DesignatedContact} to {@link XjcDomainContactType}. */
|
||||
private static Optional<XjcDomainContactType> convertDesignatedContact(
|
||||
DesignatedContact model, String domainName) {
|
||||
XjcDomainContactType bean = new XjcDomainContactType();
|
||||
checkState(
|
||||
model.getContactKey() != null,
|
||||
"Contact key for type %s is null on domain %s",
|
||||
model.getType(),
|
||||
domainName);
|
||||
Optional<Contact> contact = tm().transact(() -> tm().loadByKeyIfPresent(model.getContactKey()));
|
||||
if (contact.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
bean.setType(XjcDomainContactAttrType.fromValue(Ascii.toLowerCase(model.getType().toString())));
|
||||
bean.setValue(contact.get().getContactId());
|
||||
return Optional.of(bean);
|
||||
}
|
||||
|
||||
private DomainToXjcConverter() {}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -1,135 +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.tools;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import com.google.template.soy.data.SoyMapData;
|
||||
import google.registry.tools.params.PhoneNumberParameter;
|
||||
import google.registry.tools.soy.ContactCreateSoyInfo;
|
||||
import google.registry.util.StringGenerator;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
import java.util.List;
|
||||
|
||||
/** A command to create a new contact via EPP. */
|
||||
@Parameters(separators = " =", commandDescription = "Create a new contact via EPP.")
|
||||
final class CreateContactCommand extends MutatingEppToolCommand {
|
||||
|
||||
// TODO(b/19016175): Expand to allow full suite of contact flows.
|
||||
@Parameter(
|
||||
names = {"-c", "--client"},
|
||||
description = "Client identifier of the registrar to execute the command as",
|
||||
required = true)
|
||||
String clientId;
|
||||
|
||||
@Parameter(
|
||||
names = {"-i", "--id"},
|
||||
description = "Contact ID.")
|
||||
private String id;
|
||||
|
||||
@Parameter(
|
||||
names = {"-n", "--name"},
|
||||
description = "Contact name.")
|
||||
private String name;
|
||||
|
||||
@Parameter(
|
||||
names = {"-o", "--org"},
|
||||
description = "Organization")
|
||||
private String org;
|
||||
|
||||
@Parameter(
|
||||
names = "--street",
|
||||
description = "Street lines of address. Can take up to 3 lines.",
|
||||
variableArity = true)
|
||||
private List<String> street;
|
||||
|
||||
@Parameter(
|
||||
names = "--city",
|
||||
description = "City of address.")
|
||||
private String city;
|
||||
|
||||
@Parameter(
|
||||
names = "--state",
|
||||
description = "State of address.")
|
||||
private String state;
|
||||
|
||||
@Parameter(
|
||||
names = {"-z", "--zip"},
|
||||
description = "Postal code of address.")
|
||||
private String zip;
|
||||
|
||||
@Parameter(
|
||||
names = "--cc",
|
||||
description = "Country code of address.")
|
||||
private String cc;
|
||||
|
||||
@Parameter(
|
||||
names = "--phone",
|
||||
description = "E.164 phone number, e.g. +1.2125650666",
|
||||
converter = PhoneNumberParameter.class,
|
||||
validateWith = PhoneNumberParameter.class)
|
||||
String phone;
|
||||
|
||||
@Parameter(
|
||||
names = "--fax",
|
||||
description = "E.164 fax number, e.g. +1.2125650666",
|
||||
converter = PhoneNumberParameter.class,
|
||||
validateWith = PhoneNumberParameter.class)
|
||||
String fax;
|
||||
|
||||
@Parameter(
|
||||
names = {"-e", "--email"},
|
||||
description = "Email address.")
|
||||
private String email;
|
||||
|
||||
@Parameter(
|
||||
names = {"-p", "--password"},
|
||||
description = "Password. Optional, randomly generated if not provided.")
|
||||
private String password;
|
||||
|
||||
@Inject
|
||||
@Named("base64StringGenerator")
|
||||
StringGenerator passwordGenerator;
|
||||
|
||||
private static final int PASSWORD_LENGTH = 16;
|
||||
|
||||
@Override
|
||||
protected void initMutatingEppToolCommand() {
|
||||
if (isNullOrEmpty(password)) {
|
||||
password = passwordGenerator.createString(PASSWORD_LENGTH);
|
||||
}
|
||||
checkArgument(street == null || street.size() <= 3,
|
||||
"Addresses must contain at most 3 street lines.");
|
||||
|
||||
setSoyTemplate(ContactCreateSoyInfo.getInstance(), ContactCreateSoyInfo.CONTACTCREATE);
|
||||
addSoyRecord(clientId, new SoyMapData(
|
||||
"id", id,
|
||||
"name", name,
|
||||
"org", org,
|
||||
"street", street,
|
||||
"city", city,
|
||||
"state", state,
|
||||
"zip", zip,
|
||||
"cc", cc,
|
||||
"phone", phone,
|
||||
"fax", fax,
|
||||
"email", email,
|
||||
"password", password));
|
||||
}
|
||||
}
|
||||
@@ -85,9 +85,6 @@ final class CreateDomainCommand extends CreateOrUpdateDomainCommand {
|
||||
"domain", domain,
|
||||
"period", period,
|
||||
"nameservers", nameservers,
|
||||
"registrant", registrant,
|
||||
"admins", admins,
|
||||
"techs", techs,
|
||||
"password", password,
|
||||
"currency", currency,
|
||||
"price", cost,
|
||||
|
||||
@@ -21,7 +21,6 @@ import com.beust.jcommander.Parameter;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.tools.params.NameserversParameter;
|
||||
import google.registry.tools.params.StringListParameter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -47,24 +46,6 @@ abstract class CreateOrUpdateDomainCommand extends MutatingEppToolCommand {
|
||||
validateWith = NameserversParameter.class)
|
||||
Set<String> nameservers = new HashSet<>();
|
||||
|
||||
@Parameter(
|
||||
names = {"-r", "--registrant"},
|
||||
description = "Domain registrant."
|
||||
)
|
||||
String registrant;
|
||||
|
||||
@Parameter(
|
||||
names = {"-a", "--admins"},
|
||||
description = "Comma-separated list of admin contacts.",
|
||||
listConverter = StringListParameter.class)
|
||||
List<String> admins = new ArrayList<>();
|
||||
|
||||
@Parameter(
|
||||
names = {"-t", "--techs"},
|
||||
description = "Comma-separated list of technical contacts.",
|
||||
listConverter = StringListParameter.class)
|
||||
List<String> techs = new ArrayList<>();
|
||||
|
||||
@Parameter(
|
||||
names = {"-p", "--password"},
|
||||
description = "Password."
|
||||
|
||||
@@ -400,7 +400,10 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
|
||||
if (registrarName != null && !registrarName.equals(oldRegistrarName)) {
|
||||
String normalizedName = normalizeRegistrarName(registrarName);
|
||||
for (Registrar registrar : Registrar.loadAll()) {
|
||||
if (registrar.getRegistrarName() != null) {
|
||||
// Only check against other registrars (i.e. not the existing version of this one), and
|
||||
// which also have a display name set.
|
||||
if (!registrar.getRegistrarId().equals(clientId)
|
||||
&& registrar.getRegistrarName() != null) {
|
||||
checkArgument(
|
||||
!normalizedName.equals(normalizeRegistrarName(registrar.getRegistrarName())),
|
||||
"The registrar name %s normalizes identically to existing registrar name %s",
|
||||
|
||||
@@ -1,41 +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.tools;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.contact.Contact;
|
||||
import java.util.List;
|
||||
|
||||
/** Command to show one or more contacts. */
|
||||
@Parameters(separators = " =", commandDescription = "Show contact resource(s)")
|
||||
final class GetContactCommand extends GetEppResourceCommand {
|
||||
|
||||
@Parameter(
|
||||
description = "Contact id(s)",
|
||||
required = true)
|
||||
private List<String> mainParameters;
|
||||
|
||||
@Override
|
||||
public void runAndPrint() {
|
||||
for (String contactId : mainParameters) {
|
||||
printResource(
|
||||
"Contact",
|
||||
contactId,
|
||||
ForeignKeyUtils.loadResource(Contact.class, contactId, readTimestamp));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,6 @@ public final class RegistryTool {
|
||||
"create_cancellations_for_billing_events",
|
||||
CreateCancellationsForBillingEventsCommand.class)
|
||||
.put("create_cdns_tld", CreateCdnsTld.class)
|
||||
.put("create_contact", CreateContactCommand.class)
|
||||
.put("create_domain", CreateDomainCommand.class)
|
||||
.put("create_host", CreateHostCommand.class)
|
||||
.put("create_premium_list", CreatePremiumListCommand.class)
|
||||
@@ -72,7 +71,6 @@ public final class RegistryTool {
|
||||
.put("get_allocation_token", GetAllocationTokenCommand.class)
|
||||
.put("get_bulk_pricing_package", GetBulkPricingPackageCommand.class)
|
||||
.put("get_claims_list", GetClaimsListCommand.class)
|
||||
.put("get_contact", GetContactCommand.class)
|
||||
.put("get_domain", GetDomainCommand.class)
|
||||
.put("get_feature_flag", GetFeatureFlagCommand.class)
|
||||
.put("get_history_entries", GetHistoryEntriesCommand.class)
|
||||
|
||||
@@ -83,8 +83,6 @@ interface RegistryToolComponent {
|
||||
|
||||
void inject(CreateCdnsTld command);
|
||||
|
||||
void inject(CreateContactCommand command);
|
||||
|
||||
void inject(CreateDomainCommand command);
|
||||
|
||||
void inject(CreateRegistrarCommand command);
|
||||
@@ -107,8 +105,6 @@ interface RegistryToolComponent {
|
||||
|
||||
void inject(GetBulkPricingPackageCommand command);
|
||||
|
||||
void inject(GetContactCommand command);
|
||||
|
||||
void inject(GetDomainCommand command);
|
||||
|
||||
void inject(GetFeatureFlagCommand command);
|
||||
|
||||
@@ -15,11 +15,9 @@
|
||||
package google.registry.tools;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static google.registry.model.domain.rgp.GracePeriodStatus.AUTO_RENEW;
|
||||
import static google.registry.model.eppcommon.StatusValue.PENDING_DELETE;
|
||||
import static google.registry.model.eppcommon.StatusValue.SERVER_UPDATE_PROHIBITED;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static java.util.function.Predicate.isEqual;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
@@ -30,7 +28,6 @@ import com.google.common.collect.Sets;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.template.soy.data.SoyMapData;
|
||||
import google.registry.flows.ResourceFlowUtils;
|
||||
import google.registry.model.domain.DesignatedContact;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.GracePeriodBase;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
@@ -66,15 +63,6 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
|
||||
validateWith = NameserversParameter.class)
|
||||
private Set<String> addNameservers = new HashSet<>();
|
||||
|
||||
// TODO(b/184067241): enforce only one of each type of contact
|
||||
@Parameter(
|
||||
names = "--add_admins",
|
||||
description = "Admins to add. Cannot be set if --admins is set.")
|
||||
private List<String> addAdmins = new ArrayList<>();
|
||||
|
||||
@Parameter(names = "--add_techs", description = "Techs to add. Cannot be set if --techs is set.")
|
||||
private List<String> addTechs = new ArrayList<>();
|
||||
|
||||
@Parameter(
|
||||
names = "--add_statuses",
|
||||
description = "Statuses to add. Cannot be set if --statuses is set."
|
||||
@@ -97,18 +85,6 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
|
||||
validateWith = NameserversParameter.class)
|
||||
private Set<String> removeNameservers = new HashSet<>();
|
||||
|
||||
@Parameter(
|
||||
names = "--remove_admins",
|
||||
description = "Admins to remove. Cannot be set if --admins is set."
|
||||
)
|
||||
private List<String> removeAdmins = new ArrayList<>();
|
||||
|
||||
@Parameter(
|
||||
names = "--remove_techs",
|
||||
description = "Techs to remove. Cannot be set if --techs is set."
|
||||
)
|
||||
private List<String> removeTechs = new ArrayList<>();
|
||||
|
||||
@Parameter(
|
||||
names = "--remove_statuses",
|
||||
description = "Statuses to remove. Cannot be set if --statuses is set."
|
||||
@@ -154,16 +130,6 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
|
||||
} else {
|
||||
checkArgument(addNameservers.size() <= 13, "You can add at most 13 nameservers.");
|
||||
}
|
||||
if (!admins.isEmpty()) {
|
||||
checkArgument(
|
||||
addAdmins.isEmpty() && removeAdmins.isEmpty(),
|
||||
"If you provide the admins flag, you cannot use the add_admins and remove_admins flags.");
|
||||
}
|
||||
if (!techs.isEmpty()) {
|
||||
checkArgument(
|
||||
addTechs.isEmpty() && removeTechs.isEmpty(),
|
||||
"If you provide the techs flag, you cannot use the add_techs and remove_techs flags.");
|
||||
}
|
||||
if (!statuses.isEmpty()) {
|
||||
checkArgument(
|
||||
addStatuses.isEmpty() && removeStatuses.isEmpty(),
|
||||
@@ -197,16 +163,12 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
|
||||
domainName);
|
||||
|
||||
// Use TreeSets so that the results are always in the same order (this makes testing easier).
|
||||
Set<String> addAdminsThisDomain = new TreeSet<>(addAdmins);
|
||||
Set<String> removeAdminsThisDomain = new TreeSet<>(removeAdmins);
|
||||
Set<String> addTechsThisDomain = new TreeSet<>(addTechs);
|
||||
Set<String> removeTechsThisDomain = new TreeSet<>(removeTechs);
|
||||
Set<String> addNameserversThisDomain = new TreeSet<>(addNameservers);
|
||||
Set<String> removeNameserversThisDomain = new TreeSet<>(removeNameservers);
|
||||
Set<String> addStatusesThisDomain = new TreeSet<>(addStatuses);
|
||||
Set<String> removeStatusesThisDomain = new TreeSet<>(removeStatuses);
|
||||
|
||||
if (!nameservers.isEmpty() || !admins.isEmpty() || !techs.isEmpty() || !statuses.isEmpty()) {
|
||||
if (!nameservers.isEmpty() || !statuses.isEmpty()) {
|
||||
if (!nameservers.isEmpty()) {
|
||||
ImmutableSortedSet<String> existingNameservers = domain.loadNameserverHostNames();
|
||||
populateAddRemoveLists(
|
||||
@@ -223,27 +185,7 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
|
||||
"The resulting nameservers count for domain %s would be more than 13",
|
||||
domainName);
|
||||
}
|
||||
if (!admins.isEmpty() || !techs.isEmpty()) {
|
||||
ImmutableSet<String> existingAdmins =
|
||||
getContactsOfType(domain, DesignatedContact.Type.ADMIN);
|
||||
ImmutableSet<String> existingTechs =
|
||||
getContactsOfType(domain, DesignatedContact.Type.TECH);
|
||||
|
||||
if (!admins.isEmpty()) {
|
||||
populateAddRemoveLists(
|
||||
ImmutableSet.copyOf(admins),
|
||||
existingAdmins,
|
||||
addAdminsThisDomain,
|
||||
removeAdminsThisDomain);
|
||||
}
|
||||
if (!techs.isEmpty()) {
|
||||
populateAddRemoveLists(
|
||||
ImmutableSet.copyOf(techs),
|
||||
existingTechs,
|
||||
addTechsThisDomain,
|
||||
removeTechsThisDomain);
|
||||
}
|
||||
}
|
||||
if (!statuses.isEmpty()) {
|
||||
Set<String> currentStatusValues = new HashSet<>();
|
||||
for (StatusValue statusValue : domain.getStatusValues()) {
|
||||
@@ -259,17 +201,13 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
|
||||
|
||||
boolean add =
|
||||
(!addNameserversThisDomain.isEmpty()
|
||||
|| !addAdminsThisDomain.isEmpty()
|
||||
|| !addTechsThisDomain.isEmpty()
|
||||
|| !addStatusesThisDomain.isEmpty());
|
||||
|
||||
boolean remove =
|
||||
(!removeNameserversThisDomain.isEmpty()
|
||||
|| !removeAdminsThisDomain.isEmpty()
|
||||
|| !removeTechsThisDomain.isEmpty()
|
||||
|| !removeStatusesThisDomain.isEmpty());
|
||||
|
||||
boolean change = (registrant != null || password != null);
|
||||
boolean change = password != null;
|
||||
boolean secDns =
|
||||
(!addDsRecords.isEmpty()
|
||||
|| !removeDsRecords.isEmpty()
|
||||
@@ -297,16 +235,11 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
|
||||
"domain", domainName,
|
||||
"add", add,
|
||||
"addNameservers", addNameserversThisDomain,
|
||||
"addAdmins", addAdminsThisDomain,
|
||||
"addTechs", addTechsThisDomain,
|
||||
"addStatuses", addStatusesThisDomain,
|
||||
"remove", remove,
|
||||
"removeNameservers", removeNameserversThisDomain,
|
||||
"removeAdmins", removeAdminsThisDomain,
|
||||
"removeTechs", removeTechsThisDomain,
|
||||
"removeStatuses", removeStatusesThisDomain,
|
||||
"change", change,
|
||||
"registrant", registrant,
|
||||
"password", password,
|
||||
"secdns", secDns,
|
||||
"addDsRecords", DsRecord.convertToSoy(addDsRecords),
|
||||
@@ -337,13 +270,4 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
|
||||
addSet.addAll(Sets.difference(targetSet, oldSet));
|
||||
removeSet.addAll(Sets.difference(oldSet, targetSet));
|
||||
}
|
||||
|
||||
ImmutableSet<String> getContactsOfType(Domain domain, final DesignatedContact.Type contactType) {
|
||||
return tm().transact(
|
||||
() ->
|
||||
domain.getContacts().stream()
|
||||
.filter(contact -> contact.getType().equals(contactType))
|
||||
.map(contact -> tm().loadByKey(contact.getContactKey()).getContactId())
|
||||
.collect(toImmutableSet()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +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.tools.params;
|
||||
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.host.Host;
|
||||
|
||||
/** Enum to make it easy for a command to accept a flag that specifies an EppResource subclass. */
|
||||
public enum EppResourceTypeParameter {
|
||||
CONTACT(Contact.class),
|
||||
DOMAIN(Domain.class),
|
||||
HOST(Host.class);
|
||||
|
||||
private final Class<? extends EppResource> type;
|
||||
|
||||
EppResourceTypeParameter(Class<? extends EppResource> type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Class<? extends EppResource> getType() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -20,16 +20,11 @@
|
||||
{@param domain: string}
|
||||
{@param add: bool}
|
||||
{@param addNameservers: list<string>}
|
||||
{@param addAdmins: list<string>}
|
||||
{@param addTechs: list<string>}
|
||||
{@param addStatuses: list<string>}
|
||||
{@param remove: bool}
|
||||
{@param removeNameservers: list<string>}
|
||||
{@param removeAdmins: list<string>}
|
||||
{@param removeTechs: list<string>}
|
||||
{@param removeStatuses: list<string>}
|
||||
{@param change: bool}
|
||||
{@param? registrant: string|null}
|
||||
{@param? password: string|null}
|
||||
{@param secdns: bool}
|
||||
{@param addDsRecords: list<[keyTag:int, alg:int, digestType:int, digest:string]>}
|
||||
@@ -54,12 +49,6 @@
|
||||
{/for}
|
||||
</domain:ns>
|
||||
{/if}
|
||||
{for $admin in $addAdmins}
|
||||
<domain:contact type="admin">{$admin}</domain:contact>
|
||||
{/for}
|
||||
{for $tech in $addTechs}
|
||||
<domain:contact type="tech">{$tech}</domain:contact>
|
||||
{/for}
|
||||
{for $status in $addStatuses}
|
||||
<domain:status s="{$status}"/>
|
||||
{/for}
|
||||
@@ -74,12 +63,6 @@
|
||||
{/for}
|
||||
</domain:ns>
|
||||
{/if}
|
||||
{for $admin in $removeAdmins}
|
||||
<domain:contact type="admin">{$admin}</domain:contact>
|
||||
{/for}
|
||||
{for $tech in $removeTechs}
|
||||
<domain:contact type="tech">{$tech}</domain:contact>
|
||||
{/for}
|
||||
{for $status in $removeStatuses}
|
||||
<domain:status s="{$status}"/>
|
||||
{/for}
|
||||
@@ -87,9 +70,6 @@
|
||||
{/if}
|
||||
{if $change}
|
||||
<domain:chg>
|
||||
{if $registrant}
|
||||
<domain:registrant>{$registrant}</domain:registrant>
|
||||
{/if}
|
||||
{if $password}
|
||||
<domain:authInfo>
|
||||
<domain:pw>{$password}</domain:pw>
|
||||
|
||||
@@ -18,12 +18,12 @@ import static google.registry.batch.AsyncTaskEnqueuer.PARAM_REQUESTED_TIME;
|
||||
import static google.registry.batch.AsyncTaskEnqueuer.PARAM_RESAVE_TIMES;
|
||||
import static google.registry.batch.AsyncTaskEnqueuer.PARAM_RESOURCE_KEY;
|
||||
import static google.registry.batch.AsyncTaskEnqueuer.QUEUE_ASYNC_ACTIONS;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveContact;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveHost;
|
||||
import static google.registry.testing.TestLogHandlerUtils.assertLogMessage;
|
||||
|
||||
import com.google.cloud.tasks.v2.HttpMethod;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
|
||||
import google.registry.testing.CloudTasksHelper;
|
||||
@@ -67,9 +67,9 @@ public class AsyncTaskEnqueuerTest {
|
||||
|
||||
@Test
|
||||
void test_enqueueAsyncResave_success() {
|
||||
Contact contact = persistActiveContact("jd23456");
|
||||
Host host = persistActiveHost("ns1.example.tld");
|
||||
asyncTaskEnqueuer.enqueueAsyncResave(
|
||||
contact.createVKey(), clock.nowUtc(), ImmutableSortedSet.of(clock.nowUtc().plusDays(5)));
|
||||
host.createVKey(), clock.nowUtc(), ImmutableSortedSet.of(clock.nowUtc().plusDays(5)));
|
||||
cloudTasksHelper.assertTasksEnqueued(
|
||||
QUEUE_ASYNC_ACTIONS,
|
||||
new CloudTasksHelper.TaskMatcher()
|
||||
@@ -77,17 +77,17 @@ public class AsyncTaskEnqueuerTest {
|
||||
.method(HttpMethod.POST)
|
||||
.service("backend")
|
||||
.header("content-type", "application/x-www-form-urlencoded")
|
||||
.param(PARAM_RESOURCE_KEY, contact.createVKey().stringify())
|
||||
.param(PARAM_RESOURCE_KEY, host.createVKey().stringify())
|
||||
.param(PARAM_REQUESTED_TIME, clock.nowUtc().toString())
|
||||
.scheduleTime(clock.nowUtc().plus(Duration.standardDays(5))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_enqueueAsyncResave_multipleResaves() {
|
||||
Contact contact = persistActiveContact("jd23456");
|
||||
Host host = persistActiveHost("ns1.example.tld");
|
||||
DateTime now = clock.nowUtc();
|
||||
asyncTaskEnqueuer.enqueueAsyncResave(
|
||||
contact.createVKey(),
|
||||
host.createVKey(),
|
||||
now,
|
||||
ImmutableSortedSet.of(now.plusHours(24), now.plusHours(50), now.plusHours(75)));
|
||||
cloudTasksHelper.assertTasksEnqueued(
|
||||
@@ -97,7 +97,7 @@ public class AsyncTaskEnqueuerTest {
|
||||
.method(HttpMethod.POST)
|
||||
.service("backend")
|
||||
.header("content-type", "application/x-www-form-urlencoded")
|
||||
.param(PARAM_RESOURCE_KEY, contact.createVKey().stringify())
|
||||
.param(PARAM_RESOURCE_KEY, host.createVKey().stringify())
|
||||
.param(PARAM_REQUESTED_TIME, now.toString())
|
||||
.param(PARAM_RESAVE_TIMES, "2015-05-20T14:34:56.000Z,2015-05-21T15:34:56.000Z")
|
||||
.scheduleTime(clock.nowUtc().plus(Duration.standardHours(24))));
|
||||
@@ -106,9 +106,9 @@ public class AsyncTaskEnqueuerTest {
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
@Test
|
||||
void test_enqueueAsyncResave_ignoresTasksTooFarIntoFuture() {
|
||||
Contact contact = persistActiveContact("jd23456");
|
||||
Host host = persistActiveHost("ns1.example.tld");
|
||||
asyncTaskEnqueuer.enqueueAsyncResave(
|
||||
contact.createVKey(), clock.nowUtc(), ImmutableSortedSet.of(clock.nowUtc().plusDays(31)));
|
||||
host.createVKey(), clock.nowUtc(), ImmutableSortedSet.of(clock.nowUtc().plusDays(31)));
|
||||
cloudTasksHelper.assertNoTasksEnqueued(QUEUE_ASYNC_ACTIONS);
|
||||
assertLogMessage(logHandler, Level.INFO, "Ignoring async re-save");
|
||||
}
|
||||
|
||||
@@ -65,23 +65,18 @@ public class BulkDomainTransferActionTest {
|
||||
// The default registrar is TheRegistrar, which will be the losing registrar
|
||||
activeDomain =
|
||||
persistDomainWithDependentResources(
|
||||
"active", "tld", null, now, now.minusDays(1), DateTimeUtils.END_OF_TIME);
|
||||
"active", "tld", now, now.minusDays(1), DateTimeUtils.END_OF_TIME);
|
||||
alreadyTransferredDomain =
|
||||
persistResource(
|
||||
persistDomainWithDependentResources(
|
||||
"alreadytransferred",
|
||||
"tld",
|
||||
null,
|
||||
now,
|
||||
now.minusDays(1),
|
||||
DateTimeUtils.END_OF_TIME)
|
||||
"alreadytransferred", "tld", now, now.minusDays(1), DateTimeUtils.END_OF_TIME)
|
||||
.asBuilder()
|
||||
.setPersistedCurrentSponsorRegistrarId("NewRegistrar")
|
||||
.build());
|
||||
pendingDeleteDomain =
|
||||
persistResource(
|
||||
persistDomainWithDependentResources(
|
||||
"pendingdelete", "tld", null, now, now.minusDays(1), now.plusMonths(1))
|
||||
"pendingdelete", "tld", now, now.minusDays(1), now.plusMonths(1))
|
||||
.asBuilder()
|
||||
.setStatusValues(ImmutableSet.of(StatusValue.PENDING_DELETE))
|
||||
.build());
|
||||
|
||||
@@ -16,7 +16,6 @@ package google.registry.batch;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveContact;
|
||||
import static google.registry.testing.DatabaseHelper.persistEppResource;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.LogsSubject.assertAboutLogs;
|
||||
@@ -32,7 +31,6 @@ import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.testing.TestLogHandler;
|
||||
import google.registry.groups.GmailClient;
|
||||
import google.registry.model.billing.BillingBase.RenewalPriceBehavior;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.fee.FeeQueryCommandExtensionItem.CommandName;
|
||||
import google.registry.model.domain.token.AllocationToken;
|
||||
import google.registry.model.domain.token.AllocationToken.TokenType;
|
||||
@@ -79,7 +77,6 @@ public class CheckBulkComplianceActionTest {
|
||||
private final Logger loggerToIntercept =
|
||||
Logger.getLogger(CheckBulkComplianceAction.class.getCanonicalName());
|
||||
private final GmailClient gmailClient = mock(GmailClient.class);
|
||||
private Contact contact;
|
||||
private BulkPricingPackage bulkPricingPackage;
|
||||
private SendEmailUtils sendEmailUtils;
|
||||
private ArgumentCaptor<EmailMessage> emailCaptor = ArgumentCaptor.forClass(EmailMessage.class);
|
||||
@@ -125,8 +122,6 @@ public class CheckBulkComplianceActionTest {
|
||||
.setNextBillingDate(DateTime.parse("2012-11-12T05:00:00Z"))
|
||||
.setLastNotificationSent(DateTime.parse("2010-11-12T05:00:00Z"))
|
||||
.build();
|
||||
|
||||
contact = persistActiveContact("contact1234");
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
@@ -138,7 +133,7 @@ public class CheckBulkComplianceActionTest {
|
||||
void testSuccess_noBulkPackageOverCreateLimit() {
|
||||
tm().transact(() -> tm().put(bulkPricingPackage));
|
||||
persistEppResource(
|
||||
DatabaseHelper.newDomain("foo.tld", contact)
|
||||
DatabaseHelper.newDomain("foo.tld")
|
||||
.asBuilder()
|
||||
.setCurrentBulkToken(token.createVKey())
|
||||
.build());
|
||||
@@ -156,12 +151,12 @@ public class CheckBulkComplianceActionTest {
|
||||
tm().transact(() -> tm().put(bulkPricingPackage));
|
||||
// Create limit is 1, creating 2 domains to go over the limit
|
||||
persistEppResource(
|
||||
DatabaseHelper.newDomain("foo.tld", contact)
|
||||
DatabaseHelper.newDomain("foo.tld")
|
||||
.asBuilder()
|
||||
.setCurrentBulkToken(token.createVKey())
|
||||
.build());
|
||||
persistEppResource(
|
||||
DatabaseHelper.newDomain("buzz.tld", contact)
|
||||
DatabaseHelper.newDomain("buzz.tld")
|
||||
.asBuilder()
|
||||
.setCurrentBulkToken(token.createVKey())
|
||||
.build());
|
||||
@@ -189,12 +184,12 @@ public class CheckBulkComplianceActionTest {
|
||||
tm().transact(() -> tm().put(bulkPricingPackage));
|
||||
// Create limit is 1, creating 2 domains to go over the limit
|
||||
persistEppResource(
|
||||
DatabaseHelper.newDomain("foo.tld", contact)
|
||||
DatabaseHelper.newDomain("foo.tld")
|
||||
.asBuilder()
|
||||
.setCurrentBulkToken(token.createVKey())
|
||||
.build());
|
||||
persistEppResource(
|
||||
DatabaseHelper.newDomain("buzz.tld", contact)
|
||||
DatabaseHelper.newDomain("buzz.tld")
|
||||
.asBuilder()
|
||||
.setCurrentBulkToken(token.createVKey())
|
||||
.build());
|
||||
@@ -223,12 +218,12 @@ public class CheckBulkComplianceActionTest {
|
||||
tm().transact(() -> tm().put(bulkPricingPackage2));
|
||||
|
||||
persistEppResource(
|
||||
DatabaseHelper.newDomain("foo2.tld", contact)
|
||||
DatabaseHelper.newDomain("foo2.tld")
|
||||
.asBuilder()
|
||||
.setCurrentBulkToken(token2.createVKey())
|
||||
.build());
|
||||
persistEppResource(
|
||||
DatabaseHelper.newDomain("buzz2.tld", contact)
|
||||
DatabaseHelper.newDomain("buzz2.tld")
|
||||
.asBuilder()
|
||||
.setCurrentBulkToken(token2.createVKey())
|
||||
.build());
|
||||
@@ -282,12 +277,12 @@ public class CheckBulkComplianceActionTest {
|
||||
|
||||
// Create limit is 1, creating 2 domains to go over the limit
|
||||
persistEppResource(
|
||||
DatabaseHelper.newDomain("foo.tld", contact)
|
||||
DatabaseHelper.newDomain("foo.tld")
|
||||
.asBuilder()
|
||||
.setCurrentBulkToken(token2.createVKey())
|
||||
.build());
|
||||
persistEppResource(
|
||||
DatabaseHelper.newDomain("buzz.tld", contact)
|
||||
DatabaseHelper.newDomain("buzz.tld")
|
||||
.asBuilder()
|
||||
.setCurrentBulkToken(token2.createVKey())
|
||||
.build());
|
||||
@@ -304,7 +299,7 @@ public class CheckBulkComplianceActionTest {
|
||||
void testSuccess_noBulkPricingPackageOverActiveDomainsLimit() {
|
||||
tm().transact(() -> tm().put(bulkPricingPackage));
|
||||
persistEppResource(
|
||||
DatabaseHelper.newDomain("foo.tld", contact)
|
||||
DatabaseHelper.newDomain("foo.tld")
|
||||
.asBuilder()
|
||||
.setCurrentBulkToken(token.createVKey())
|
||||
.build());
|
||||
@@ -323,12 +318,12 @@ public class CheckBulkComplianceActionTest {
|
||||
tm().transact(() -> tm().put(bulkPricingPackage));
|
||||
// Domains limit is 1, creating 2 domains to go over the limit
|
||||
persistEppResource(
|
||||
DatabaseHelper.newDomain("foo.tld", contact)
|
||||
DatabaseHelper.newDomain("foo.tld")
|
||||
.asBuilder()
|
||||
.setCurrentBulkToken(token.createVKey())
|
||||
.build());
|
||||
persistEppResource(
|
||||
DatabaseHelper.newDomain("buzz.tld", contact)
|
||||
DatabaseHelper.newDomain("buzz.tld")
|
||||
.asBuilder()
|
||||
.setCurrentBulkToken(token.createVKey())
|
||||
.build());
|
||||
@@ -356,7 +351,7 @@ public class CheckBulkComplianceActionTest {
|
||||
.build();
|
||||
tm().transact(() -> tm().put(bulkPricingPackage2));
|
||||
persistEppResource(
|
||||
DatabaseHelper.newDomain("foo2.tld", contact)
|
||||
DatabaseHelper.newDomain("foo2.tld")
|
||||
.asBuilder()
|
||||
.setCurrentBulkToken(token2.createVKey())
|
||||
.build());
|
||||
@@ -390,12 +385,12 @@ public class CheckBulkComplianceActionTest {
|
||||
tm().put(bulkPricingPackage.asBuilder().setMaxDomains(1).setMaxCreates(4).build()));
|
||||
// Domains limit is 1, creating 2 domains to go over the limit
|
||||
persistEppResource(
|
||||
DatabaseHelper.newDomain("foo.tld", contact)
|
||||
DatabaseHelper.newDomain("foo.tld")
|
||||
.asBuilder()
|
||||
.setCurrentBulkToken(token.createVKey())
|
||||
.build());
|
||||
persistEppResource(
|
||||
DatabaseHelper.newDomain("buzz.tld", contact)
|
||||
DatabaseHelper.newDomain("buzz.tld")
|
||||
.asBuilder()
|
||||
.setCurrentBulkToken(token.createVKey())
|
||||
.build());
|
||||
@@ -424,12 +419,12 @@ public class CheckBulkComplianceActionTest {
|
||||
tm().transact(() -> tm().put(bulkPricingPackage2));
|
||||
|
||||
persistEppResource(
|
||||
DatabaseHelper.newDomain("foo2.tld", contact)
|
||||
DatabaseHelper.newDomain("foo2.tld")
|
||||
.asBuilder()
|
||||
.setCurrentBulkToken(token2.createVKey())
|
||||
.build());
|
||||
persistEppResource(
|
||||
DatabaseHelper.newDomain("buzz2.tld", contact)
|
||||
DatabaseHelper.newDomain("buzz2.tld")
|
||||
.asBuilder()
|
||||
.setCurrentBulkToken(token2.createVKey())
|
||||
.build());
|
||||
@@ -467,12 +462,12 @@ public class CheckBulkComplianceActionTest {
|
||||
tm().transact(() -> tm().put(bulkPricingPackage));
|
||||
// Domains limit is 1, creating 2 domains to go over the limit
|
||||
persistEppResource(
|
||||
DatabaseHelper.newDomain("foo.tld", contact)
|
||||
DatabaseHelper.newDomain("foo.tld")
|
||||
.asBuilder()
|
||||
.setCurrentBulkToken(token.createVKey())
|
||||
.build());
|
||||
persistEppResource(
|
||||
DatabaseHelper.newDomain("buzz.tld", contact)
|
||||
DatabaseHelper.newDomain("buzz.tld")
|
||||
.asBuilder()
|
||||
.setCurrentBulkToken(token.createVKey())
|
||||
.build());
|
||||
@@ -508,12 +503,12 @@ public class CheckBulkComplianceActionTest {
|
||||
tm().transact(() -> tm().put(bulkPricingPackage));
|
||||
// Domains limit is 1, creating 2 domains to go over the limit
|
||||
persistEppResource(
|
||||
DatabaseHelper.newDomain("foo.tld", contact)
|
||||
DatabaseHelper.newDomain("foo.tld")
|
||||
.asBuilder()
|
||||
.setCurrentBulkToken(token.createVKey())
|
||||
.build());
|
||||
persistEppResource(
|
||||
DatabaseHelper.newDomain("buzz.tld", contact)
|
||||
DatabaseHelper.newDomain("buzz.tld")
|
||||
.asBuilder()
|
||||
.setCurrentBulkToken(token.createVKey())
|
||||
.build());
|
||||
@@ -553,12 +548,12 @@ public class CheckBulkComplianceActionTest {
|
||||
tm().transact(() -> tm().put(bulkPricingPackage));
|
||||
// Domains limit is 1, creating 2 domains to go over the limit
|
||||
persistEppResource(
|
||||
DatabaseHelper.newDomain("foo.tld", contact)
|
||||
DatabaseHelper.newDomain("foo.tld")
|
||||
.asBuilder()
|
||||
.setCurrentBulkToken(token.createVKey())
|
||||
.build());
|
||||
persistEppResource(
|
||||
DatabaseHelper.newDomain("buzz.tld", contact)
|
||||
DatabaseHelper.newDomain("buzz.tld")
|
||||
.asBuilder()
|
||||
.setCurrentBulkToken(token.createVKey())
|
||||
.build());
|
||||
|
||||
@@ -21,7 +21,6 @@ import static google.registry.batch.AsyncTaskEnqueuer.QUEUE_ASYNC_ACTIONS;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.loadByEntity;
|
||||
import static google.registry.testing.DatabaseHelper.newDomain;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveContact;
|
||||
import static google.registry.testing.DatabaseHelper.persistDomainWithDependentResources;
|
||||
import static google.registry.testing.DatabaseHelper.persistDomainWithPendingTransfer;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
@@ -87,7 +86,6 @@ public class ResaveEntityActionTest {
|
||||
persistDomainWithDependentResources(
|
||||
"domain",
|
||||
"tld",
|
||||
persistActiveContact("jd1234"),
|
||||
DateTime.parse("2016-02-06T10:00:00Z"),
|
||||
DateTime.parse("2016-02-06T10:00:00Z"),
|
||||
DateTime.parse("2017-01-02T10:11:00Z")),
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
package google.registry.beam.common;
|
||||
|
||||
import static google.registry.persistence.transaction.JpaTransactionManagerExtension.makeRegistrar1;
|
||||
import static google.registry.testing.DatabaseHelper.newContact;
|
||||
import static google.registry.testing.DatabaseHelper.newHost;
|
||||
import static google.registry.testing.DatabaseHelper.newTld;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.DatabaseHelper.persistResources;
|
||||
@@ -27,8 +27,6 @@ import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.beam.TestPipelineExtension;
|
||||
import google.registry.beam.common.RegistryJpaIO.Read;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactBase;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainAuthInfo;
|
||||
import google.registry.model.domain.GracePeriod;
|
||||
@@ -37,14 +35,14 @@ import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import google.registry.model.domain.secdns.DomainDsData;
|
||||
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.host.HostBase;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.model.transfer.ContactTransferData;
|
||||
import google.registry.persistence.transaction.CriteriaQueryBuilder;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
|
||||
import google.registry.testing.FakeClock;
|
||||
import java.util.Optional;
|
||||
import org.apache.beam.sdk.coders.StringUtf8Coder;
|
||||
import org.apache.beam.sdk.testing.PAssert;
|
||||
import org.apache.beam.sdk.values.PCollection;
|
||||
@@ -71,32 +69,33 @@ public class RegistryJpaReadTest {
|
||||
final transient TestPipelineExtension testPipeline =
|
||||
TestPipelineExtension.create().enableAbandonedNodeEnforcement(true);
|
||||
|
||||
private transient ImmutableList<Contact> contacts;
|
||||
private transient ImmutableList<Host> hosts;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
Registrar ofyRegistrar = JpaIntegrationTestExtension.makeRegistrar2();
|
||||
persistResource(ofyRegistrar);
|
||||
|
||||
ImmutableList.Builder<Contact> builder = new ImmutableList.Builder<>();
|
||||
ImmutableList.Builder<Host> builder = new ImmutableList.Builder<>();
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
Contact contact = newContact("contact_" + i);
|
||||
builder.add(contact);
|
||||
Host host = newHost(String.format("ns%d.example.tld", i));
|
||||
builder.add(host);
|
||||
}
|
||||
contacts = builder.build();
|
||||
persistResources(contacts);
|
||||
hosts = builder.build();
|
||||
persistResources(hosts);
|
||||
}
|
||||
|
||||
@Test
|
||||
void readWithCriteriaQuery() {
|
||||
Read<Contact, String> read =
|
||||
Read<Host, String> read =
|
||||
RegistryJpaIO.read(
|
||||
() -> CriteriaQueryBuilder.create(Contact.class).build(), ContactBase::getContactId)
|
||||
() -> CriteriaQueryBuilder.create(Host.class).build(), HostBase::getHostName)
|
||||
.withCoder(StringUtf8Coder.of());
|
||||
PCollection<String> repoIds = testPipeline.apply(read);
|
||||
|
||||
PAssert.that(repoIds).containsInAnyOrder("contact_0", "contact_1", "contact_2");
|
||||
PAssert.that(repoIds)
|
||||
.containsInAnyOrder("ns0.example.tld", "ns1.example.tld", "ns2.example.tld");
|
||||
testPipeline.run();
|
||||
}
|
||||
|
||||
@@ -170,13 +169,6 @@ public class RegistryJpaReadTest {
|
||||
.setRegistrarId("registrar1")
|
||||
.setEmailAddress("me@google.com")
|
||||
.build();
|
||||
Contact contact =
|
||||
new Contact.Builder()
|
||||
.setRepoId("contactid_1")
|
||||
.setCreationRegistrarId(registrar.getRegistrarId())
|
||||
.setTransferData(new ContactTransferData.Builder().build())
|
||||
.setPersistedCurrentSponsorRegistrarId(registrar.getRegistrarId())
|
||||
.build();
|
||||
Domain domain =
|
||||
new Domain.Builder()
|
||||
.setDomainName("example.com")
|
||||
@@ -193,8 +185,6 @@ public class RegistryJpaReadTest {
|
||||
StatusValue.SERVER_UPDATE_PROHIBITED,
|
||||
StatusValue.SERVER_RENEW_PROHIBITED,
|
||||
StatusValue.SERVER_HOLD))
|
||||
.setRegistrant(Optional.of(contact.createVKey()))
|
||||
.setContacts(ImmutableSet.of())
|
||||
.setSubordinateHosts(ImmutableSet.of("ns1.example.com"))
|
||||
.setPersistedCurrentSponsorRegistrarId(registrar.getRegistrarId())
|
||||
.setRegistrationExpirationTime(fakeClock.nowUtc().plusYears(1))
|
||||
@@ -212,6 +202,6 @@ public class RegistryJpaReadTest {
|
||||
null,
|
||||
100L))
|
||||
.build();
|
||||
persistResources(registry, registrar, contact, domain);
|
||||
persistResources(registry, registrar, domain);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,22 +18,18 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.ImmutableObjectSubject.immutableObjectCorrespondence;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.testing.DatabaseHelper.loadAllOf;
|
||||
import static google.registry.testing.DatabaseHelper.newContact;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static google.registry.testing.DatabaseHelper.newHost;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
import google.registry.beam.TestPipelineExtension;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
|
||||
import google.registry.persistence.transaction.JpaTransactionManagerExtension;
|
||||
import google.registry.testing.FakeClock;
|
||||
import java.io.Serializable;
|
||||
import org.apache.beam.sdk.Pipeline.PipelineExecutionException;
|
||||
import org.apache.beam.sdk.transforms.Create;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
@@ -53,42 +49,18 @@ class RegistryJpaWriteTest implements Serializable {
|
||||
@Test
|
||||
void writeToSql_twoWriters() {
|
||||
tm().transact(() -> tm().put(JpaTransactionManagerExtension.makeRegistrar2()));
|
||||
ImmutableList.Builder<Contact> contactsBuilder = new ImmutableList.Builder<>();
|
||||
ImmutableList.Builder<Host> hostsBuilder = new ImmutableList.Builder<>();
|
||||
for (int i = 0; i < 3; i++) {
|
||||
contactsBuilder.add(newContact("contact_" + i));
|
||||
hostsBuilder.add(newHost(String.format("ns%d.example.tld", i)));
|
||||
}
|
||||
ImmutableList<Contact> contacts = contactsBuilder.build();
|
||||
ImmutableList<Host> hosts = hostsBuilder.build();
|
||||
testPipeline
|
||||
.apply(Create.of(contacts))
|
||||
.apply(RegistryJpaIO.<Contact>write().withName("Contact").withBatchSize(4));
|
||||
.apply(Create.of(hosts))
|
||||
.apply(RegistryJpaIO.<Host>write().withName("Host").withBatchSize(4));
|
||||
testPipeline.run().waitUntilFinish();
|
||||
|
||||
assertThat(loadAllOf(Contact.class))
|
||||
assertThat(loadAllOf(Host.class))
|
||||
.comparingElementsUsing(immutableObjectCorrespondence("revisions", "updateTimestamp"))
|
||||
.containsExactlyElementsIn(contacts);
|
||||
}
|
||||
|
||||
@Disabled("b/263502442")
|
||||
@Test
|
||||
void testFailure_writeExistingEntity() {
|
||||
// RegistryJpaIO.Write actions should not write existing objects to the database because the
|
||||
// object could have been mutated in between creation and when the Write actually occurs,
|
||||
// causing a race condition
|
||||
tm().transact(
|
||||
() -> {
|
||||
tm().put(JpaTransactionManagerExtension.makeRegistrar2());
|
||||
tm().put(newContact("contact"));
|
||||
});
|
||||
Contact contact = Iterables.getOnlyElement(loadAllOf(Contact.class));
|
||||
testPipeline
|
||||
.apply(Create.of(contact))
|
||||
.apply(RegistryJpaIO.<Contact>write().withName("Contact"));
|
||||
// PipelineExecutionException caused by a RuntimeException caused by an IllegalArgumentException
|
||||
assertThat(
|
||||
assertThrows(
|
||||
PipelineExecutionException.class, () -> testPipeline.run().waitUntilFinish()))
|
||||
.hasCauseThat()
|
||||
.hasCauseThat()
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
.containsExactlyElementsIn(hosts);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -239,8 +238,6 @@ public class RdePipelineTest {
|
||||
newDomain("hello.soy")
|
||||
.asBuilder()
|
||||
.addNameserver(host1.createVKey())
|
||||
.setRegistrant(Optional.empty())
|
||||
.setContacts(ImmutableSet.of())
|
||||
.build());
|
||||
persistDomainHistory(helloDomain);
|
||||
persistHostHistory(persistActiveHost("not-used-subordinate.hello.soy"));
|
||||
@@ -253,8 +250,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.
|
||||
@@ -348,7 +343,7 @@ public class RdePipelineTest {
|
||||
"""
|
||||
<rdeDomain:domain>
|
||||
<rdeDomain:name>cat.fun</rdeDomain:name>
|
||||
<rdeDomain:roid>10-FUN</rdeDomain:roid>
|
||||
<rdeDomain:roid>F-FUN</rdeDomain:roid>
|
||||
<rdeDomain:uName>cat.fun</rdeDomain:uName>
|
||||
<rdeDomain:status s="ok"/>
|
||||
<rdeDomain:ns>
|
||||
|
||||
@@ -20,8 +20,8 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.loadAllOf;
|
||||
import static google.registry.testing.DatabaseHelper.loadByEntity;
|
||||
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.persistDomainWithDependentResources;
|
||||
import static google.registry.testing.DatabaseHelper.persistDomainWithPendingTransfer;
|
||||
import static google.registry.testing.DatabaseHelper.persistNewRegistrars;
|
||||
@@ -31,10 +31,10 @@ import static org.mockito.Mockito.verify;
|
||||
|
||||
import google.registry.beam.TestPipelineExtension;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.GracePeriod;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
|
||||
@@ -79,13 +79,13 @@ public class ResaveAllEppResourcesPipelineTest {
|
||||
|
||||
@Test
|
||||
void testPipeline_unchangedEntity() {
|
||||
Contact contact = persistActiveContact("test123");
|
||||
DateTime creationTime = contact.getUpdateTimestamp().getTimestamp();
|
||||
Host host = persistActiveHost("ns1.example.tld");
|
||||
DateTime creationTime = host.getUpdateTimestamp().getTimestamp();
|
||||
fakeClock.advanceOneMilli();
|
||||
assertThat(loadByEntity(contact).getUpdateTimestamp().getTimestamp()).isEqualTo(creationTime);
|
||||
assertThat(loadByEntity(host).getUpdateTimestamp().getTimestamp()).isEqualTo(creationTime);
|
||||
fakeClock.advanceOneMilli();
|
||||
runPipeline();
|
||||
assertThat(loadByEntity(contact)).isEqualTo(contact);
|
||||
assertThat(loadByEntity(host)).isEqualTo(host);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -95,12 +95,7 @@ public class ResaveAllEppResourcesPipelineTest {
|
||||
Domain domain =
|
||||
persistDomainWithPendingTransfer(
|
||||
persistDomainWithDependentResources(
|
||||
"domain",
|
||||
"tld",
|
||||
persistActiveContact("jd1234"),
|
||||
now.minusDays(5),
|
||||
now.minusDays(5),
|
||||
now.plusYears(2)),
|
||||
"domain", "tld", now.minusDays(5), now.minusDays(5), now.plusYears(2)),
|
||||
now.minusDays(4),
|
||||
now.minusDays(1),
|
||||
now.plusYears(2));
|
||||
@@ -117,8 +112,7 @@ public class ResaveAllEppResourcesPipelineTest {
|
||||
void testPipeline_autorenewedDomain() {
|
||||
DateTime now = fakeClock.nowUtc();
|
||||
Domain domain =
|
||||
persistDomainWithDependentResources(
|
||||
"domain", "tld", persistActiveContact("jd1234"), now, now, now.plusYears(1));
|
||||
persistDomainWithDependentResources("domain", "tld", now, now, now.plusYears(1));
|
||||
assertThat(domain.getRegistrationExpirationTime()).isEqualTo(now.plusYears(1));
|
||||
fakeClock.advanceBy(Duration.standardDays(500));
|
||||
runPipeline();
|
||||
@@ -129,8 +123,7 @@ public class ResaveAllEppResourcesPipelineTest {
|
||||
@Test
|
||||
void testPipeline_expiredGracePeriod() {
|
||||
DateTime now = fakeClock.nowUtc();
|
||||
persistDomainWithDependentResources(
|
||||
"domain", "tld", persistActiveContact("jd1234"), now, now, now.plusYears(1));
|
||||
persistDomainWithDependentResources("domain", "tld", now, now, now.plusYears(1));
|
||||
assertThat(loadAllOf(GracePeriod.class)).hasSize(1);
|
||||
fakeClock.advanceBy(Duration.standardDays(500));
|
||||
runPipeline();
|
||||
@@ -140,8 +133,7 @@ public class ResaveAllEppResourcesPipelineTest {
|
||||
@Test
|
||||
void testPipeline_fastOnlySavesChanged() {
|
||||
DateTime now = fakeClock.nowUtc();
|
||||
Contact contact = persistActiveContact("jd1234");
|
||||
persistDomainWithDependentResources("renewed", "tld", contact, now, now, now.plusYears(1));
|
||||
persistDomainWithDependentResources("renewed", "tld", now, now, now.plusYears(1));
|
||||
persistActiveDomain("nonrenewed.tld", now, now.plusYears(20));
|
||||
// Spy the transaction manager so we can be sure we're only saving the renewed domain
|
||||
JpaTransactionManager spy = spy(tm());
|
||||
@@ -157,24 +149,22 @@ public class ResaveAllEppResourcesPipelineTest {
|
||||
void testPipeline_notFastResavesAll() {
|
||||
options.setFast(false);
|
||||
DateTime now = fakeClock.nowUtc();
|
||||
Contact contact = persistActiveContact("jd1234");
|
||||
Domain renewed =
|
||||
persistDomainWithDependentResources("renewed", "tld", contact, now, now, now.plusYears(1));
|
||||
persistDomainWithDependentResources("renewed", "tld", now, now, now.plusYears(1));
|
||||
Domain nonRenewed =
|
||||
persistDomainWithDependentResources(
|
||||
"nonrenewed", "tld", contact, now, now, now.plusYears(20));
|
||||
persistDomainWithDependentResources("nonrenewed", "tld", now, now, now.plusYears(20));
|
||||
// Spy the transaction manager so we can be sure we're attempting to save everything
|
||||
JpaTransactionManager spy = spy(tm());
|
||||
TransactionManagerFactory.setJpaTm(() -> spy);
|
||||
ArgumentCaptor<EppResource> eppResourcePutCaptor = ArgumentCaptor.forClass(EppResource.class);
|
||||
runPipeline();
|
||||
// We should be attempting to put both domains (and the contact) in, even the unchanged ones
|
||||
verify(spy, times(3)).put(eppResourcePutCaptor.capture());
|
||||
// We should be attempting to put both domains in, even the unchanged one
|
||||
verify(spy, times(2)).put(eppResourcePutCaptor.capture());
|
||||
assertThat(
|
||||
eppResourcePutCaptor.getAllValues().stream()
|
||||
.map(EppResource::getRepoId)
|
||||
.collect(toImmutableSet()))
|
||||
.containsExactly(contact.getRepoId(), renewed.getRepoId(), nonRenewed.getRepoId());
|
||||
.containsExactly(renewed.getRepoId(), nonRenewed.getRepoId());
|
||||
}
|
||||
|
||||
private void runPipeline() {
|
||||
|
||||
@@ -20,7 +20,6 @@ import static google.registry.model.ImmutableObjectSubject.immutableObjectCorres
|
||||
import static google.registry.persistence.transaction.JpaTransactionManagerExtension.makeRegistrar1;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveContact;
|
||||
import static google.registry.testing.DatabaseHelper.persistNewRegistrar;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
@@ -37,7 +36,6 @@ import com.google.common.truth.Correspondence.BinaryPredicate;
|
||||
import google.registry.beam.TestPipelineExtension;
|
||||
import google.registry.beam.spec11.SafeBrowsingTransforms.EvaluateSafeBrowsingFn;
|
||||
import google.registry.beam.spec11.SafeBrowsingTransformsTest.HttpResponder;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainAuthInfo;
|
||||
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
|
||||
@@ -54,7 +52,6 @@ import google.registry.util.Retrier;
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import org.apache.beam.sdk.coders.KvCoder;
|
||||
import org.apache.beam.sdk.coders.SerializableCoder;
|
||||
import org.apache.beam.sdk.options.PipelineOptionsFactory;
|
||||
@@ -252,16 +249,11 @@ class Spec11PipelineTest {
|
||||
createTld("bank");
|
||||
createTld("dev");
|
||||
|
||||
Contact contact1 = persistActiveContact(registrar1.getRegistrarId());
|
||||
Contact contact2 = persistActiveContact(registrar2.getRegistrarId());
|
||||
Contact contact3 = persistActiveContact(registrar3.getRegistrarId());
|
||||
|
||||
persistResource(createDomain("111.com", "123456789-COM", registrar1, contact1));
|
||||
persistResource(createDomain("party-night.net", "2244AABBC-NET", registrar2, contact2));
|
||||
persistResource(createDomain("bitcoin.bank", "1C3D5E7F9-BANK", registrar1, contact1));
|
||||
persistResource(createDomain("no-email.com", "2A4BA9BBC-COM", registrar2, contact2));
|
||||
persistResource(
|
||||
createDomain("anti-anti-anti-virus.dev", "555666888-DEV", registrar3, contact3));
|
||||
persistResource(createDomain("111.com", "123456789-COM", registrar1));
|
||||
persistResource(createDomain("party-night.net", "2244AABBC-NET", registrar2));
|
||||
persistResource(createDomain("bitcoin.bank", "1C3D5E7F9-BANK", registrar1));
|
||||
persistResource(createDomain("no-email.com", "2A4BA9BBC-COM", registrar2));
|
||||
persistResource(createDomain("anti-anti-anti-virus.dev", "555666888-DEV", registrar3));
|
||||
}
|
||||
|
||||
private void verifySaveToGcs() throws Exception {
|
||||
@@ -289,8 +281,7 @@ class Spec11PipelineTest {
|
||||
});
|
||||
}
|
||||
|
||||
private Domain createDomain(
|
||||
String domainName, String repoId, Registrar registrar, Contact contact) {
|
||||
private Domain createDomain(String domainName, String repoId, Registrar registrar) {
|
||||
return new Domain.Builder()
|
||||
.setDomainName(domainName)
|
||||
.setRepoId(repoId)
|
||||
@@ -298,7 +289,6 @@ class Spec11PipelineTest {
|
||||
.setLastEppUpdateTime(fakeClock.nowUtc())
|
||||
.setLastEppUpdateRegistrarId(registrar.getRegistrarId())
|
||||
.setLastTransferTime(fakeClock.nowUtc())
|
||||
.setRegistrant(Optional.of(contact.createVKey()))
|
||||
.setPersistedCurrentSponsorRegistrarId(registrar.getRegistrarId())
|
||||
.setRegistrationExpirationTime(fakeClock.nowUtc().plusYears(1))
|
||||
.setAuthInfo(DomainAuthInfo.create(PasswordAuth.create("password")))
|
||||
|
||||
@@ -20,7 +20,6 @@ import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableO
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.loadAllOf;
|
||||
import static google.registry.testing.DatabaseHelper.loadByEntity;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveContact;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveHost;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.joda.time.DateTimeZone.UTC;
|
||||
@@ -86,8 +85,6 @@ class EppPointInTimeTest {
|
||||
|
||||
persistActiveHost("ns1.example.net");
|
||||
persistActiveHost("ns2.example.net");
|
||||
persistActiveContact("jd1234");
|
||||
persistActiveContact("sh8013");
|
||||
|
||||
clock.advanceBy(standardDays(1));
|
||||
DateTime timeAtCreate = clock.nowUtc();
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright 2026 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.package google.registry.flows;
|
||||
|
||||
package google.registry.flows;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.flows.FeeExtensionXmlTagNormalizer.feeExtensionInUseRegex;
|
||||
import static google.registry.flows.FeeExtensionXmlTagNormalizer.normalize;
|
||||
import static google.registry.flows.FlowTestCase.verifyFeeTagNormalized;
|
||||
import static google.registry.model.eppcommon.EppXmlTransformer.validateOutput;
|
||||
import static google.registry.testing.TestDataHelper.loadFile;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
class FeeExtensionXmlTagNormalizerTest {
|
||||
|
||||
@Test
|
||||
void feeExtensionInUseRegex_correct() {
|
||||
assertThat(feeExtensionInUseRegex())
|
||||
.isEqualTo("\\b(fee):|\\b(fee11):|\\b(fee12):|\\b(fee_1_00):");
|
||||
}
|
||||
|
||||
@Test
|
||||
void normalize_noFeeExtensions() throws Exception {
|
||||
String xml = loadFile(getClass(), "domain_create.xml");
|
||||
String normalized = normalize(xml);
|
||||
assertThat(normalized).isEqualTo(xml);
|
||||
}
|
||||
|
||||
@Test
|
||||
void normalize_greetingUnchanged() throws Exception {
|
||||
String xml = loadFile(getClass(), "greeting.xml");
|
||||
String normalized = normalize(xml);
|
||||
assertThat(normalized).isEqualTo(xml);
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "normalize_withFeeExtension-{0}")
|
||||
@MethodSource("provideTestCombinations")
|
||||
@SuppressWarnings("unused") // Parameter 'name' is part of test case name
|
||||
void normalize_withFeeExtension(String name, String inputXmlFilename, String expectedXmlFilename)
|
||||
throws Exception {
|
||||
String original = loadFile(getClass(), inputXmlFilename);
|
||||
String normalized = normalize(original);
|
||||
String expected = loadFile(getClass(), expectedXmlFilename);
|
||||
// Verify that expected xml is syntatically correct.
|
||||
validateOutput(expected);
|
||||
|
||||
assertThat(normalized).isEqualTo(expected);
|
||||
}
|
||||
|
||||
// Piggyback tests for FlowTestCase.verifyFeeTagNormalized here.
|
||||
@ParameterizedTest(name = "verifyFeeTagNormalized-{0}")
|
||||
@MethodSource("provideTestCombinations")
|
||||
@SuppressWarnings("unused") // Parameter 'name' is part of test case name
|
||||
void verifyFeeTagNormalized_success(
|
||||
String name, String inputXmlFilename, String expectedXmlFilename) throws Exception {
|
||||
String original = loadFile(getClass(), inputXmlFilename);
|
||||
String expected = loadFile(getClass(), expectedXmlFilename);
|
||||
|
||||
if (name.equals("v06")) {
|
||||
// Fee-06 already uses 'fee'. Non-normalized tags only appear in header.
|
||||
verifyFeeTagNormalized(original);
|
||||
} else {
|
||||
assertThrows(
|
||||
AssertionError.class,
|
||||
() -> {
|
||||
verifyFeeTagNormalized(original);
|
||||
});
|
||||
}
|
||||
verifyFeeTagNormalized(expected);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static Stream<Arguments> provideTestCombinations() {
|
||||
return Stream.of(
|
||||
Arguments.of(
|
||||
"v06",
|
||||
"domain_check_fee_response_raw_v06.xml",
|
||||
"domain_check_fee_response_normalized_v06.xml"),
|
||||
Arguments.of(
|
||||
"v11",
|
||||
"domain_check_fee_response_raw_v11.xml",
|
||||
"domain_check_fee_response_normalized_v11.xml"),
|
||||
Arguments.of(
|
||||
"v12",
|
||||
"domain_check_fee_response_raw_v12.xml",
|
||||
"domain_check_fee_response_normalized_v12.xml"),
|
||||
Arguments.of(
|
||||
"stdv1",
|
||||
"domain_check_fee_response_raw_stdv1.xml",
|
||||
"domain_check_fee_response_normalized_stdv1.xml"));
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.collect.Sets.difference;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.common.truth.Truth.assertWithMessage;
|
||||
import static google.registry.flows.FlowUtils.marshalWithLenientRetry;
|
||||
import static google.registry.model.eppcommon.EppXmlTransformer.marshal;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.testing.DatabaseHelper.stripBillingEventId;
|
||||
@@ -55,6 +56,7 @@ import google.registry.xml.ValidationMode;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.annotation.Nullable;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -284,6 +286,7 @@ public abstract class FlowTestCase<F extends Flow> {
|
||||
Arrays.toString(marshal(output, ValidationMode.LENIENT))),
|
||||
e);
|
||||
}
|
||||
verifyFeeTagNormalized(new String(marshalWithLenientRetry(output), UTF_8));
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -298,4 +301,14 @@ public abstract class FlowTestCase<F extends Flow> {
|
||||
public EppOutput runFlowAssertResponse(String xml, String... ignoredPaths) throws Exception {
|
||||
return runFlowAssertResponse(CommitMode.LIVE, UserPrivileges.NORMAL, xml, ignoredPaths);
|
||||
}
|
||||
|
||||
// Pattern for non-normalized tags in use. Occurrences in namespace declarations ignored.
|
||||
private static final Pattern NON_NORMALIZED_FEE_TAGS =
|
||||
Pattern.compile("\\bfee11:|\\bfee12:|\\bfee_1_00:");
|
||||
|
||||
static void verifyFeeTagNormalized(String xml) {
|
||||
assertWithMessage("Unexpected un-normalized Fee tags found in message.")
|
||||
.that(NON_NORMALIZED_FEE_TAGS.matcher(xml).find())
|
||||
.isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user