mirror of
https://github.com/google/nomulus
synced 2026-08-01 21:06:09 +00:00
Compare commits
26
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
083a9dc8c9 | ||
|
|
0153c6284a | ||
|
|
ca240adfb6 | ||
|
|
b17125ae9a | ||
|
|
dfef733360 | ||
|
|
04a0659197 | ||
|
|
70010886b1 | ||
|
|
3cd50dc929 | ||
|
|
03872b508f | ||
|
|
1096f201cd | ||
|
|
9dc3215624 | ||
|
|
af321fb65e | ||
|
|
c5132c04be | ||
|
|
a64dc21f96 | ||
|
|
0381533a35 | ||
|
|
4999a72d96 | ||
|
|
2d072c3844 | ||
|
|
c15dec4419 | ||
|
|
8340125bf4 | ||
|
|
98ba80d94e | ||
|
|
967d04efce | ||
|
|
20fd944e83 | ||
|
|
daa56e6d85 | ||
|
|
ed33c7424d | ||
|
|
04b30f5c04 | ||
|
|
11702bc940 |
@@ -90,7 +90,6 @@ explodeWar.doLast {
|
||||
|
||||
appengineDeployAll.mustRunAfter ':console-webapp:deploy'
|
||||
appengineDeployAll.finalizedBy ':deployCloudSchedulerAndQueue'
|
||||
rootProject.deploy.dependsOn appengineDeployAll
|
||||
|
||||
rootProject.stage.dependsOn appengineStage
|
||||
tasks['war'].dependsOn ':core:processResources'
|
||||
|
||||
@@ -25,7 +25,11 @@ import textwrap
|
||||
import re
|
||||
|
||||
# We should never analyze any generated files
|
||||
UNIVERSALLY_SKIPPED_PATTERNS = {"/build/", "cloudbuild-caches", "/out/", ".git/", ".gradle/", "/dist/", "/console-alpha/", "/console-crash/", "/console-qa", "/console-sandbox", "/console-production", "karma.conf.js", "polyfills.ts", "test.ts", "/docs/console-endpoints/"}
|
||||
UNIVERSALLY_SKIPPED_PATTERNS = {"/build/", "cloudbuild-caches", "/out/", ".git/",
|
||||
".gradle/", "/dist/", "/console-alpha/", "/console-crash/", "/console-qa",
|
||||
"/console-sandbox", "/console-production", "karma.conf.js", "polyfills.ts",
|
||||
"test.ts", "/docs/console-endpoints/", "/bin/generated-sources/",
|
||||
"/bin/generated-test-sources/", "src/main/generated", "src/test/generated"}
|
||||
# We can't rely on CI to have the Enum package installed so we do this instead.
|
||||
FORBIDDEN = 1
|
||||
REQUIRED = 2
|
||||
@@ -87,11 +91,9 @@ PRESUBMITS = {
|
||||
PresubmitCheck(
|
||||
r".*Copyright 20\d{2} The Nomulus Authors\. All Rights Reserved\.",
|
||||
("java", "js", "soy", "sql", "py", "sh", "gradle", "ts"), {
|
||||
".git", "/build/", "/bin/generated-sources/", "/bin/generated-test-sources/",
|
||||
"node_modules/", "LoggerConfig.java", "registrar_bin.",
|
||||
".git", "/build/", "node_modules/", "LoggerConfig.java", "registrar_bin.",
|
||||
"registrar_dbg.", "google-java-format-diff.py",
|
||||
"nomulus.golden.sql", "soyutils_usegoog.js", "javascript/checks.js",
|
||||
"/src/main/generated", "/src/test/generated"
|
||||
"nomulus.golden.sql", "soyutils_usegoog.js", "javascript/checks.js"
|
||||
}, REQUIRED):
|
||||
"File did not include the license header.",
|
||||
|
||||
|
||||
@@ -101,16 +101,9 @@ task checkFormatting(type: Exec) {
|
||||
args 'run', 'prettify:check'
|
||||
}
|
||||
|
||||
task deploy(type: Exec) {
|
||||
workingDir "${consoleDir}/staged"
|
||||
executable 'gcloud'
|
||||
args 'app', 'deploy', "${projectParam}", '--quiet'
|
||||
}
|
||||
|
||||
tasks.buildConsoleWebapp.dependsOn(tasks.npmInstallDeps)
|
||||
tasks.runConsoleWebappUnitTests.dependsOn(tasks.npmInstallDeps)
|
||||
tasks.applyFormatting.dependsOn(tasks.npmInstallDeps)
|
||||
tasks.checkFormatting.dependsOn(tasks.npmInstallDeps)
|
||||
tasks.build.dependsOn(tasks.checkFormatting)
|
||||
tasks.build.dependsOn(tasks.runConsoleWebappUnitTests)
|
||||
tasks.deploy.dependsOn(tasks.buildConsoleWebapp)
|
||||
|
||||
@@ -24,8 +24,8 @@ import { ResourcesComponent } from './resources/resources.component';
|
||||
import ContactComponent from './settings/contact/contact.component';
|
||||
import SecurityComponent from './settings/security/security.component';
|
||||
import { SettingsComponent } from './settings/settings.component';
|
||||
import WhoisComponent from './settings/whois/whois.component';
|
||||
import { SupportComponent } from './support/support.component';
|
||||
import RdapComponent from './settings/rdap/rdap.component';
|
||||
|
||||
export interface RouteWithIcon extends Route {
|
||||
iconName?: string;
|
||||
@@ -83,9 +83,9 @@ export const routes: RouteWithIcon[] = [
|
||||
title: 'Contacts',
|
||||
},
|
||||
{
|
||||
path: WhoisComponent.PATH,
|
||||
component: WhoisComponent,
|
||||
title: 'WHOIS Info',
|
||||
path: RdapComponent.PATH,
|
||||
component: RdapComponent,
|
||||
title: 'RDAP Info',
|
||||
},
|
||||
{
|
||||
path: SecurityComponent.PATH,
|
||||
|
||||
@@ -47,8 +47,6 @@ import EppPasswordEditComponent from './settings/security/eppPasswordEdit.compon
|
||||
import SecurityComponent from './settings/security/security.component';
|
||||
import SecurityEditComponent from './settings/security/securityEdit.component';
|
||||
import { SettingsComponent } from './settings/settings.component';
|
||||
import WhoisComponent from './settings/whois/whois.component';
|
||||
import WhoisEditComponent from './settings/whois/whoisEdit.component';
|
||||
import { NotificationsComponent } from './shared/components/notifications/notifications.component';
|
||||
import { SelectedRegistrarWrapper } from './shared/components/selectedRegistrarWrapper/selectedRegistrarWrapper.component';
|
||||
import { LocationBackDirective } from './shared/directives/locationBack.directive';
|
||||
@@ -60,6 +58,8 @@ import { SnackBarModule } from './snackbar.module';
|
||||
import { SupportComponent } from './support/support.component';
|
||||
import { TldsComponent } from './tlds/tlds.component';
|
||||
import { ForceFocusDirective } from './shared/directives/forceFocus.directive';
|
||||
import RdapComponent from './settings/rdap/rdap.component';
|
||||
import RdapEditComponent from './settings/rdap/rdapEdit.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [SelectedRegistrarWrapper],
|
||||
@@ -76,30 +76,30 @@ export class SelectedRegistrarModule {}
|
||||
ContactDetailsComponent,
|
||||
DomainListComponent,
|
||||
EppPasswordEditComponent,
|
||||
ForceFocusDirective,
|
||||
HeaderComponent,
|
||||
HomeComponent,
|
||||
LocationBackDirective,
|
||||
ForceFocusDirective,
|
||||
UserLevelVisibility,
|
||||
NavigationComponent,
|
||||
NewRegistrarComponent,
|
||||
NotificationsComponent,
|
||||
RdapComponent,
|
||||
RdapEditComponent,
|
||||
ReasonDialogComponent,
|
||||
RegistrarComponent,
|
||||
RegistrarDetailsComponent,
|
||||
RegistryLockComponent,
|
||||
RegistrarSelectorComponent,
|
||||
RegistryLockComponent,
|
||||
RegistryLockVerifyComponent,
|
||||
ResourcesComponent,
|
||||
ResponseDialogComponent,
|
||||
SecurityComponent,
|
||||
SecurityEditComponent,
|
||||
SettingsComponent,
|
||||
SettingsContactComponent,
|
||||
SupportComponent,
|
||||
TldsComponent,
|
||||
WhoisComponent,
|
||||
WhoisEditComponent,
|
||||
ReasonDialogComponent,
|
||||
ResponseDialogComponent,
|
||||
UserLevelVisibility,
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
imports: [
|
||||
@@ -108,8 +108,8 @@ export class SelectedRegistrarModule {}
|
||||
BrowserModule,
|
||||
FormsModule,
|
||||
MaterialModule,
|
||||
SnackBarModule,
|
||||
SelectedRegistrarModule,
|
||||
SnackBarModule,
|
||||
],
|
||||
providers: [
|
||||
BackendService,
|
||||
|
||||
@@ -48,7 +48,6 @@ export default class NewRegistrarComponent {
|
||||
this.newRegistrar = {
|
||||
registrarId: '',
|
||||
url: '',
|
||||
whoisServer: '',
|
||||
registrarName: '',
|
||||
icannReferralEmail: '',
|
||||
localizedAddress: {
|
||||
|
||||
@@ -50,17 +50,16 @@ export interface SecuritySettings
|
||||
ipAddressAllowList?: Array<IpAllowListItem>;
|
||||
}
|
||||
|
||||
export interface WhoisRegistrarFields {
|
||||
export interface RdapRegistrarFields {
|
||||
ianaIdentifier?: number;
|
||||
icannReferralEmail: string;
|
||||
localizedAddress: Address;
|
||||
registrarId: string;
|
||||
url: string;
|
||||
whoisServer: string;
|
||||
}
|
||||
|
||||
export interface Registrar
|
||||
extends WhoisRegistrarFields,
|
||||
extends RdapRegistrarFields,
|
||||
SecuritySettingsBackendModel {
|
||||
allowedTlds?: string[];
|
||||
billingAccountMap?: object;
|
||||
|
||||
@@ -24,7 +24,7 @@ export type contactType =
|
||||
| 'LEGAL'
|
||||
| 'MARKETING'
|
||||
| 'TECH'
|
||||
| 'WHOIS';
|
||||
| 'RDAP';
|
||||
|
||||
type contactTypesToUserFriendlyTypes = { [type in contactType]: string };
|
||||
|
||||
@@ -35,7 +35,7 @@ export const contactTypeToTextMap: contactTypesToUserFriendlyTypes = {
|
||||
LEGAL: 'Legal contact',
|
||||
MARKETING: 'Marketing contact',
|
||||
TECH: 'Technical contact',
|
||||
WHOIS: 'WHOIS-Inquiry contact',
|
||||
RDAP: 'RDAP-Inquiry contact',
|
||||
};
|
||||
|
||||
type UserFriendlyType = (typeof contactTypeToTextMap)[contactType];
|
||||
|
||||
@@ -97,12 +97,12 @@
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h1>WHOIS Preferences</h1>
|
||||
<h1>RDAP Preferences</h1>
|
||||
<div>
|
||||
<mat-checkbox
|
||||
[(ngModel)]="contactService.contactInEdit.visibleInWhoisAsAdmin"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
>Show in Registrar WHOIS record as admin contact</mat-checkbox
|
||||
>Show in Registrar RDAP record as admin contact</mat-checkbox
|
||||
>
|
||||
</div>
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
<mat-checkbox
|
||||
[(ngModel)]="contactService.contactInEdit.visibleInWhoisAsTech"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
>Show in Registrar WHOIS record as technical contact</mat-checkbox
|
||||
>Show in Registrar RDAP record as technical contact</mat-checkbox
|
||||
>
|
||||
</div>
|
||||
|
||||
@@ -118,8 +118,8 @@
|
||||
<mat-checkbox
|
||||
[(ngModel)]="contactService.contactInEdit.visibleInDomainWhoisAsAbuse"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
>Show Phone and Email in Domain WHOIS Record as registrar abuse
|
||||
contact (per CL&D requirements)</mat-checkbox
|
||||
>Show Phone and Email in Domain RDAP Record as registrar abuse contact
|
||||
(per CL&D requirements)</mat-checkbox
|
||||
>
|
||||
</div>
|
||||
</section>
|
||||
@@ -176,13 +176,13 @@
|
||||
<mat-card-content>
|
||||
<mat-list role="list">
|
||||
<mat-list-item role="listitem">
|
||||
<h2>WHOIS Preferences</h2>
|
||||
<h2>RDAP Preferences</h2>
|
||||
</mat-list-item>
|
||||
@if(contactService.contactInEdit.visibleInWhoisAsAdmin) {
|
||||
<mat-divider></mat-divider>
|
||||
<mat-list-item role="listitem">
|
||||
<span class="console-app__list-value"
|
||||
>Show in Registrar WHOIS record as admin contact</span
|
||||
>Show in Registrar RDAP record as admin contact</span
|
||||
>
|
||||
</mat-list-item>
|
||||
} @if(contactService.contactInEdit.visibleInWhoisAsTech) {
|
||||
@@ -192,14 +192,14 @@
|
||||
*ngIf="contactService.contactInEdit.visibleInWhoisAsTech"
|
||||
>
|
||||
<span class="console-app__list-value"
|
||||
>Show in Registrar WHOIS record as technical contact</span
|
||||
>Show in Registrar RDAP record as technical contact</span
|
||||
>
|
||||
</mat-list-item>
|
||||
} @if(contactService.contactInEdit.visibleInDomainWhoisAsAbuse) {
|
||||
<mat-divider></mat-divider>
|
||||
<mat-list-item role="listitem">
|
||||
<span class="console-app__list-value"
|
||||
>Show Phone and Email in Domain WHOIS Record as registrar abuse
|
||||
>Show Phone and Email in Domain RDAP Record as registrar abuse
|
||||
contact (per CL&D requirements)</span
|
||||
>
|
||||
</mat-list-item>
|
||||
|
||||
+8
-48
@@ -1,18 +1,18 @@
|
||||
@if(whoisService.editing) {
|
||||
<app-whois-edit></app-whois-edit>
|
||||
@if(rdapService.editing) {
|
||||
<app-rdap-edit></app-rdap-edit>
|
||||
} @else {
|
||||
<div class="console-app__whois">
|
||||
<div class="console-app__whois-controls">
|
||||
<div class="console-app__rdap">
|
||||
<div class="console-app__rdap-controls">
|
||||
<span>
|
||||
General registrar information for your WHOIS record. This information is
|
||||
always visible in WHOIS.
|
||||
General registrar information for your RDAP record. This information is
|
||||
always visible in RDAP.
|
||||
</span>
|
||||
<div class="spacer"></div>
|
||||
<button
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
aria-label="Edit WHOIS record"
|
||||
(click)="whoisService.editing = true"
|
||||
aria-label="Edit RDAP record"
|
||||
(click)="rdapService.editing = true"
|
||||
>
|
||||
<mat-icon>edit</mat-icon>
|
||||
Edit
|
||||
@@ -61,45 +61,5 @@
|
||||
</mat-list>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
<mat-card appearance="outlined">
|
||||
<mat-card-content>
|
||||
<mat-list role="list">
|
||||
<mat-list-item role="listitem">
|
||||
<h2>Technical Info</h2>
|
||||
</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
<mat-list-item role="listitem">
|
||||
<span class="console-app__list-key">IANA Identifier</span>
|
||||
<span class="console-app__list-value">{{
|
||||
registrarService.registrar()?.ianaIdentifier
|
||||
}}</span>
|
||||
</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
<mat-list-item role="listitem">
|
||||
<div>
|
||||
<span class="console-app__list-key">ICANN Referral Email</span>
|
||||
<span class="console-app__list-value">{{
|
||||
registrarService.registrar()?.icannReferralEmail
|
||||
}}</span>
|
||||
</div>
|
||||
</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
<mat-list-item role="listitem">
|
||||
<span class="console-app__list-key">WHOIS server</span>
|
||||
<span class="console-app__list-value">{{
|
||||
registrarService.registrar()?.whoisServer
|
||||
}}</span>
|
||||
</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
<mat-list-item role="listitem">
|
||||
<span class="console-app__list-key">Referral URL</span>
|
||||
<span class="console-app__list-value">{{
|
||||
registrarService.registrar()?.url
|
||||
}}</span>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
.console-app__whois {
|
||||
.console-app__rdap {
|
||||
max-width: 616px;
|
||||
|
||||
&-controls {
|
||||
+6
-6
@@ -20,15 +20,15 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { MaterialModule } from 'src/app/material.module';
|
||||
import { RegistrarService } from 'src/app/registrar/registrar.service';
|
||||
import { BackendService } from 'src/app/shared/services/backend.service';
|
||||
import WhoisComponent from './whois.component';
|
||||
import RdapComponent from './rdap.component';
|
||||
|
||||
describe('WhoisComponent', () => {
|
||||
let component: WhoisComponent;
|
||||
let fixture: ComponentFixture<WhoisComponent>;
|
||||
describe('RdapComponent', () => {
|
||||
let component: RdapComponent;
|
||||
let fixture: ComponentFixture<RdapComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [WhoisComponent],
|
||||
declarations: [RdapComponent],
|
||||
imports: [MaterialModule, BrowserAnimationsModule],
|
||||
providers: [
|
||||
BackendService,
|
||||
@@ -45,7 +45,7 @@ describe('WhoisComponent', () => {
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(WhoisComponent);
|
||||
fixture = TestBed.createComponent(RdapComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
+7
-8
@@ -14,17 +14,16 @@
|
||||
|
||||
import { Component, computed } from '@angular/core';
|
||||
import { RegistrarService } from 'src/app/registrar/registrar.service';
|
||||
|
||||
import { WhoisService } from './whois.service';
|
||||
import { RdapService } from './rdap.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-whois',
|
||||
templateUrl: './whois.component.html',
|
||||
styleUrls: ['./whois.component.scss'],
|
||||
selector: 'app-rdap',
|
||||
templateUrl: './rdap.component.html',
|
||||
styleUrls: ['./rdap.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export default class WhoisComponent {
|
||||
public static PATH = 'whois';
|
||||
export default class RdapComponent {
|
||||
public static PATH = 'rdap';
|
||||
formattedAddress = computed(() => {
|
||||
let result = '';
|
||||
const registrar = this.registrarService.registrar();
|
||||
@@ -47,7 +46,7 @@ export default class WhoisComponent {
|
||||
});
|
||||
|
||||
constructor(
|
||||
public whoisService: WhoisService,
|
||||
public rdapService: RdapService,
|
||||
public registrarService: RegistrarService
|
||||
) {}
|
||||
}
|
||||
+4
-4
@@ -16,14 +16,14 @@ import { Injectable } from '@angular/core';
|
||||
import { switchMap } from 'rxjs';
|
||||
import {
|
||||
RegistrarService,
|
||||
WhoisRegistrarFields,
|
||||
RdapRegistrarFields,
|
||||
} from 'src/app/registrar/registrar.service';
|
||||
import { BackendService } from 'src/app/shared/services/backend.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class WhoisService {
|
||||
export class RdapService {
|
||||
editing: boolean = false;
|
||||
|
||||
constructor(
|
||||
@@ -31,8 +31,8 @@ export class WhoisService {
|
||||
private registrarService: RegistrarService
|
||||
) {}
|
||||
|
||||
saveChanges(newWhoisRegistrarFields: WhoisRegistrarFields) {
|
||||
return this.backend.postWhoisRegistrarFields(newWhoisRegistrarFields).pipe(
|
||||
saveChanges(newRdapRegistrarFields: RdapRegistrarFields) {
|
||||
return this.backend.postRdapRegistrarFields(newRdapRegistrarFields).pipe(
|
||||
switchMap(() => {
|
||||
return this.registrarService.loadRegistrars();
|
||||
})
|
||||
+9
-43
@@ -1,27 +1,27 @@
|
||||
<div
|
||||
class="console-app__whois-edit"
|
||||
class="console-app__rdap-edit"
|
||||
*ngIf="registrarInEdit"
|
||||
cdkTrapFocus
|
||||
[cdkTrapFocusAutoCapture]="true"
|
||||
>
|
||||
<button
|
||||
mat-icon-button
|
||||
class="console-app__whois-edit-back"
|
||||
aria-label="Back to whois view"
|
||||
(click)="whoisService.editing = false"
|
||||
class="console-app__rdap-edit-back"
|
||||
aria-label="Back to rdap view"
|
||||
(click)="rdapService.editing = false"
|
||||
>
|
||||
<mat-icon>arrow_back</mat-icon>
|
||||
</button>
|
||||
|
||||
<div class="console-app__whois-edit-controls">
|
||||
<div class="console-app__rdap-edit-controls">
|
||||
<span>
|
||||
General registrar information for your WHOIS record. This information is
|
||||
always visible in WHOIS.
|
||||
General registrar information for your RDAP record. This information is
|
||||
always visible in RDAP.
|
||||
</span>
|
||||
<div class="spacer"></div>
|
||||
</div>
|
||||
|
||||
<div class="console-app__whois-edit">
|
||||
<div class="console-app__rdap-edit">
|
||||
<h1>Personal info</h1>
|
||||
|
||||
<form (ngSubmit)="save($event)">
|
||||
@@ -115,45 +115,11 @@
|
||||
/>
|
||||
</mat-form-field>
|
||||
|
||||
<h1>Technical info</h1>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>WHOIS server: </mat-label>
|
||||
<input
|
||||
matInput
|
||||
type="text"
|
||||
[(ngModel)]="registrarInEdit.whoisServer"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
/>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Referral URL: </mat-label>
|
||||
<input
|
||||
matInput
|
||||
type="text"
|
||||
[(ngModel)]="registrarInEdit.url"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
/>
|
||||
</mat-form-field>
|
||||
|
||||
@if((userDataService.userData()?.globalRole || 'NONE') !== "NONE") {
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>ICANN Referral Email: </mat-label>
|
||||
<input
|
||||
matInput
|
||||
type="text"
|
||||
[(ngModel)]="registrarInEdit.icannReferralEmail"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
/>
|
||||
</mat-form-field>
|
||||
}
|
||||
|
||||
<button
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
type="submit"
|
||||
aria-label="Save WHOIS settings"
|
||||
aria-label="Save RDAO settings"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
.console-app__whois-edit {
|
||||
.console-app__rdap-edit {
|
||||
max-width: 616px;
|
||||
|
||||
&-controls {
|
||||
+8
-8
@@ -20,20 +20,20 @@ import {
|
||||
RegistrarService,
|
||||
} from 'src/app/registrar/registrar.service';
|
||||
import { UserDataService } from 'src/app/shared/services/userData.service';
|
||||
import { WhoisService } from './whois.service';
|
||||
import { RdapService } from './rdap.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-whois-edit',
|
||||
templateUrl: './whoisEdit.component.html',
|
||||
styleUrls: ['./whoisEdit.component.scss'],
|
||||
selector: 'app-rdap-edit',
|
||||
templateUrl: './rdapEdit.component.html',
|
||||
styleUrls: ['./rdapEdit.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export default class WhoisEditComponent {
|
||||
export default class RdapEditComponent {
|
||||
registrarInEdit: Registrar | undefined;
|
||||
|
||||
constructor(
|
||||
public userDataService: UserDataService,
|
||||
public whoisService: WhoisService,
|
||||
public rdapService: RdapService,
|
||||
public registrarService: RegistrarService,
|
||||
private _snackBar: MatSnackBar
|
||||
) {
|
||||
@@ -49,9 +49,9 @@ export default class WhoisEditComponent {
|
||||
e.preventDefault();
|
||||
if (!this.registrarInEdit) return;
|
||||
|
||||
this.whoisService.saveChanges(this.registrarInEdit).subscribe({
|
||||
this.rdapService.saveChanges(this.registrarInEdit).subscribe({
|
||||
complete: () => {
|
||||
this.whoisService.editing = false;
|
||||
this.rdapService.editing = false;
|
||||
},
|
||||
error: (err: HttpErrorResponse) => {
|
||||
this._snackBar.open(err.error);
|
||||
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { switchMap, timeout } from 'rxjs';
|
||||
import { switchMap } from 'rxjs';
|
||||
import {
|
||||
IpAllowListItem,
|
||||
RegistrarService,
|
||||
@@ -69,7 +69,6 @@ export class SecurityService {
|
||||
uiToApiConverter(newSecuritySettings)
|
||||
)
|
||||
.pipe(
|
||||
timeout(2000),
|
||||
switchMap(() => {
|
||||
return this.registrarService.loadRegistrars();
|
||||
})
|
||||
|
||||
@@ -19,13 +19,13 @@
|
||||
>
|
||||
<a
|
||||
mat-tab-link
|
||||
routerLink="whois"
|
||||
routerLink="rdap"
|
||||
routerLinkActive
|
||||
queryParamsHandling="merge"
|
||||
#rla2="routerLinkActive"
|
||||
[active]="rla2.isActive"
|
||||
aria-label="Access whois settings"
|
||||
>WHOIS Info</a
|
||||
aria-label="Access rdap settings"
|
||||
>RDAP Info</a
|
||||
>
|
||||
<a
|
||||
mat-tab-link
|
||||
|
||||
@@ -25,7 +25,7 @@ import { User } from 'src/app/users/users.service';
|
||||
import {
|
||||
Registrar,
|
||||
SecuritySettingsBackendModel,
|
||||
WhoisRegistrarFields,
|
||||
RdapRegistrarFields,
|
||||
} from '../../registrar/registrar.service';
|
||||
import { Contact } from '../../settings/contact/contact.service';
|
||||
import { EppPasswordBackendModel } from '../../settings/security/security.service';
|
||||
@@ -209,12 +209,12 @@ export class BackendService {
|
||||
.pipe(catchError((err) => this.errorCatcher<UserData>(err)));
|
||||
}
|
||||
|
||||
postWhoisRegistrarFields(
|
||||
whoisRegistrarFields: WhoisRegistrarFields
|
||||
): Observable<WhoisRegistrarFields> {
|
||||
return this.http.post<WhoisRegistrarFields>(
|
||||
'/console-api/settings/whois-fields',
|
||||
whoisRegistrarFields
|
||||
postRdapRegistrarFields(
|
||||
rdapRegistrarFields: RdapRegistrarFields
|
||||
): Observable<RdapRegistrarFields> {
|
||||
return this.http.post<RdapRegistrarFields>(
|
||||
'/console-api/settings/rdap-fields',
|
||||
rdapRegistrarFields
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ import google.registry.flows.certs.CertificateChecker;
|
||||
import google.registry.groups.GmailClient;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
import google.registry.model.registrar.RegistrarPocBase.Type;
|
||||
import google.registry.model.registrar.RegistrarPoc.Type;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Response;
|
||||
|
||||
@@ -58,7 +58,7 @@ import google.registry.model.host.Host;
|
||||
import google.registry.model.host.HostHistory;
|
||||
import google.registry.model.rde.RdeMode;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarBase.Type;
|
||||
import google.registry.model.registrar.Registrar.Type;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
|
||||
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
|
||||
|
||||
@@ -144,7 +144,6 @@ public abstract class CredentialModule {
|
||||
Duration tokenRefreshDelay,
|
||||
Clock clock) {
|
||||
GoogleCredentials signer = credentialsBundle.getGoogleCredentials();
|
||||
|
||||
checkArgument(
|
||||
signer instanceof ServiceAccountSigner,
|
||||
"Expecting a ServiceAccountSigner, found %s.",
|
||||
|
||||
@@ -1111,12 +1111,6 @@ public final class RegistryConfig {
|
||||
return config.registryPolicy.whoisCommandFactoryClass;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("allocationTokenCustomLogicClass")
|
||||
public static String provideAllocationTokenCustomLogicClass(RegistryConfigSettings config) {
|
||||
return config.registryPolicy.allocationTokenCustomLogicClass;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("dnsCountQueryCoordinatorClass")
|
||||
public static String dnsCountQueryCoordinatorClass(RegistryConfigSettings config) {
|
||||
|
||||
@@ -91,7 +91,6 @@ public class RegistryConfigSettings {
|
||||
public String productName;
|
||||
public String customLogicFactoryClass;
|
||||
public String whoisCommandFactoryClass;
|
||||
public String allocationTokenCustomLogicClass;
|
||||
public String dnsCountQueryCoordinatorClass;
|
||||
public int contactAutomaticTransferDays;
|
||||
public String greetingServerId;
|
||||
|
||||
@@ -69,10 +69,6 @@ registryPolicy:
|
||||
# See whois/WhoisCommandFactory.java
|
||||
whoisCommandFactoryClass: google.registry.whois.WhoisCommandFactory
|
||||
|
||||
# Custom logic class for handling allocation tokens.
|
||||
# See flows/domain/token/AllocationTokenCustomLogic.java
|
||||
allocationTokenCustomLogicClass: google.registry.flows.domain.token.AllocationTokenCustomLogic
|
||||
|
||||
# Custom logic class for handling DNS query count reporting for ICANN.
|
||||
# See reporting/icann/DnsCountQueryCoordinator.java
|
||||
dnsCountQueryCoordinatorClass: google.registry.reporting.icann.DummyDnsCountQueryCoordinator
|
||||
@@ -247,7 +243,7 @@ hibernate:
|
||||
# that BEAM pipelines are not subject to the maximumPoolSize value defined
|
||||
# here. See PersistenceModule.java for more information.
|
||||
hikariMinimumIdle: 1
|
||||
hikariMaximumPoolSize: 10
|
||||
hikariMaximumPoolSize: 20
|
||||
hikariIdleTimeout: 300000
|
||||
# The batch size is basically the number of insertions / updates in a single
|
||||
# transaction that will be batched together into one INSERT/UPDATE statement.
|
||||
|
||||
@@ -50,7 +50,6 @@ import google.registry.model.domain.Domain;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
import google.registry.model.registrar.RegistrarPocBase;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
@@ -296,7 +295,7 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
|
||||
|
||||
ImmutableList<InternetAddress> recipients =
|
||||
registrar.get().getContacts().stream()
|
||||
.filter(c -> c.getTypes().contains(RegistrarPocBase.Type.ADMIN))
|
||||
.filter(c -> c.getTypes().contains(RegistrarPoc.Type.ADMIN))
|
||||
.map(RegistrarPoc::getEmailAddress)
|
||||
.map(PublishDnsUpdatesAction::emailToInternetAddress)
|
||||
.collect(toImmutableList());
|
||||
|
||||
@@ -78,7 +78,7 @@ public class ExportDomainListsAction implements Runnable {
|
||||
ORDER BY d.domain_name""";
|
||||
|
||||
// This may be a CSV, but it is uses a .txt file extension for back-compatibility
|
||||
static final String REGISTERED_DOMAINS_FILENAME = "registered_domains.txt";
|
||||
static final String REGISTERED_DOMAINS_FILENAME_FORMAT = "registered_domains_%s.txt";
|
||||
|
||||
@Inject Clock clock;
|
||||
@Inject DriveConnection driveConnection;
|
||||
@@ -146,7 +146,7 @@ public class ExportDomainListsAction implements Runnable {
|
||||
} else {
|
||||
String resultMsg =
|
||||
driveConnection.createOrUpdateFile(
|
||||
REGISTERED_DOMAINS_FILENAME,
|
||||
String.format(REGISTERED_DOMAINS_FILENAME_FORMAT, tldStr),
|
||||
MediaType.PLAIN_TEXT_UTF_8,
|
||||
tld.getDriveFolderId(),
|
||||
domains.getBytes(UTF_8));
|
||||
|
||||
@@ -43,7 +43,7 @@ public class ExportReservedTermsAction implements Runnable {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
static final MediaType EXPORT_MIME_TYPE = MediaType.PLAIN_TEXT_UTF_8;
|
||||
static final String RESERVED_TERMS_FILENAME = "reserved_terms.txt";
|
||||
static final String RESERVED_TERMS_FILENAME_FORMAT = "reserved_terms_%s.txt";
|
||||
|
||||
@Inject DriveConnection driveConnection;
|
||||
@Inject ExportUtils exportUtils;
|
||||
@@ -79,7 +79,7 @@ public class ExportReservedTermsAction implements Runnable {
|
||||
} else {
|
||||
resultMsg =
|
||||
driveConnection.createOrUpdateFile(
|
||||
RESERVED_TERMS_FILENAME,
|
||||
String.format(RESERVED_TERMS_FILENAME_FORMAT, tldStr),
|
||||
EXPORT_MIME_TYPE,
|
||||
tld.getDriveFolderId(),
|
||||
exportUtils.exportReservedTerms(tld).getBytes(UTF_8));
|
||||
|
||||
@@ -33,7 +33,6 @@ import google.registry.groups.GroupsConnection;
|
||||
import google.registry.groups.GroupsConnection.Role;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
import google.registry.model.registrar.RegistrarPocBase;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Response;
|
||||
@@ -101,7 +100,7 @@ public final class SyncGroupMembersAction implements Runnable {
|
||||
* Returns the Google Groups email address for the given registrar ID and RegistrarContact.Type.
|
||||
*/
|
||||
public static String getGroupEmailAddressForContactType(
|
||||
String registrarId, RegistrarPocBase.Type type, String gSuiteDomainName) {
|
||||
String registrarId, RegistrarPoc.Type type, String gSuiteDomainName) {
|
||||
// Take the registrar's ID, make it lowercase, and remove all characters that aren't
|
||||
// alphanumeric, hyphens, or underscores.
|
||||
return String.format(
|
||||
@@ -176,7 +175,7 @@ public final class SyncGroupMembersAction implements Runnable {
|
||||
Set<RegistrarPoc> registrarPocs = registrar.getContacts();
|
||||
long totalAdded = 0;
|
||||
long totalRemoved = 0;
|
||||
for (final RegistrarPocBase.Type type : RegistrarPocBase.Type.values()) {
|
||||
for (final RegistrarPoc.Type type : RegistrarPoc.Type.values()) {
|
||||
groupKey =
|
||||
getGroupEmailAddressForContactType(registrar.getRegistrarId(), type, gSuiteDomainName);
|
||||
Set<String> currentMembers = groupsConnection.getMembersOfGroup(groupKey);
|
||||
|
||||
@@ -17,13 +17,13 @@ package google.registry.export.sheet;
|
||||
import static com.google.common.base.MoreObjects.firstNonNull;
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static google.registry.model.common.Cursor.CursorType.SYNC_REGISTRAR_SHEET;
|
||||
import static google.registry.model.registrar.RegistrarPocBase.Type.ABUSE;
|
||||
import static google.registry.model.registrar.RegistrarPocBase.Type.ADMIN;
|
||||
import static google.registry.model.registrar.RegistrarPocBase.Type.BILLING;
|
||||
import static google.registry.model.registrar.RegistrarPocBase.Type.LEGAL;
|
||||
import static google.registry.model.registrar.RegistrarPocBase.Type.MARKETING;
|
||||
import static google.registry.model.registrar.RegistrarPocBase.Type.TECH;
|
||||
import static google.registry.model.registrar.RegistrarPocBase.Type.WHOIS;
|
||||
import static google.registry.model.registrar.RegistrarPoc.Type.ABUSE;
|
||||
import static google.registry.model.registrar.RegistrarPoc.Type.ADMIN;
|
||||
import static google.registry.model.registrar.RegistrarPoc.Type.BILLING;
|
||||
import static google.registry.model.registrar.RegistrarPoc.Type.LEGAL;
|
||||
import static google.registry.model.registrar.RegistrarPoc.Type.MARKETING;
|
||||
import static google.registry.model.registrar.RegistrarPoc.Type.TECH;
|
||||
import static google.registry.model.registrar.RegistrarPoc.Type.WHOIS;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
|
||||
@@ -36,7 +36,6 @@ import google.registry.model.common.Cursor;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarAddress;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
import google.registry.model.registrar.RegistrarPocBase;
|
||||
import google.registry.util.Clock;
|
||||
import google.registry.util.DateTimeUtils;
|
||||
import jakarta.inject.Inject;
|
||||
@@ -174,7 +173,7 @@ class SyncRegistrarsSheet {
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private static Predicate<RegistrarPoc> byType(final RegistrarPocBase.Type type) {
|
||||
private static Predicate<RegistrarPoc> byType(final RegistrarPoc.Type type) {
|
||||
return contact -> contact.getTypes().contains(type);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.flows;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.io.BaseEncoding;
|
||||
import google.registry.request.Response;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* A metadata class that saves the data directly in cookies.
|
||||
*
|
||||
* <p>Unlike {@link HttpSessionMetadata}, this class does not rely on a session manager to translate
|
||||
* an opaque session cookie into the metadata. This means that the locality of the session manager
|
||||
* is irrelevant and as long as the client (the proxy) respects the {@code Set-Cookie} headers and
|
||||
* sets the respective cookies in subsequent requests in a session, the metadata will be available
|
||||
* to all servers, not just the one that created the session.
|
||||
*
|
||||
* <p>The string representation of the metadata is saved in Base64 URL-safe format in a cookie named
|
||||
* {@code SESSION_INFO}.
|
||||
*/
|
||||
public class CookieSessionMetadata extends SessionMetadata {
|
||||
|
||||
protected static final String COOKIE_NAME = "SESSION_INFO";
|
||||
protected static final String REGISTRAR_ID = "clientId";
|
||||
protected static final String SERVICE_EXTENSIONS = "serviceExtensionUris";
|
||||
protected static final String FAILED_LOGIN_ATTEMPTS = "failedLoginAttempts";
|
||||
|
||||
private static final Pattern COOKIE_PATTERN = Pattern.compile("SESSION_INFO=([^;\\s]+)?");
|
||||
private static final Pattern REGISTRAR_ID_PATTERN = Pattern.compile("clientId=([^,\\s]+)?");
|
||||
private static final Pattern SERVICE_EXTENSIONS_PATTERN =
|
||||
Pattern.compile("serviceExtensionUris=([^,\\s}]+)?");
|
||||
private static final Pattern FAILED_LOGIN_ATTEMPTS_PATTERN =
|
||||
Pattern.compile("failedLoginAttempts=([^,\\s]+)?");
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
private final Map<String, String> data = new HashMap<>();
|
||||
|
||||
public CookieSessionMetadata(HttpServletRequest request) {
|
||||
Optional.ofNullable(request.getHeader("Cookie"))
|
||||
.ifPresent(
|
||||
cookie -> {
|
||||
Matcher matcher = COOKIE_PATTERN.matcher(cookie);
|
||||
if (matcher.find()) {
|
||||
String sessionInfo = decode(matcher.group(1));
|
||||
logger.atInfo().log("SESSION INFO: %s", sessionInfo);
|
||||
matcher = REGISTRAR_ID_PATTERN.matcher(sessionInfo);
|
||||
if (matcher.find()) {
|
||||
String registrarId = matcher.group(1);
|
||||
if (!registrarId.equals("null")) {
|
||||
data.put(REGISTRAR_ID, registrarId);
|
||||
}
|
||||
}
|
||||
matcher = SERVICE_EXTENSIONS_PATTERN.matcher(sessionInfo);
|
||||
if (matcher.find()) {
|
||||
String serviceExtensions = matcher.group(1);
|
||||
if (serviceExtensions != null) {
|
||||
data.put(SERVICE_EXTENSIONS, serviceExtensions);
|
||||
}
|
||||
}
|
||||
matcher = FAILED_LOGIN_ATTEMPTS_PATTERN.matcher(sessionInfo);
|
||||
if (matcher.find()) {
|
||||
String failedLoginAttempts = matcher.group(1);
|
||||
data.put(FAILED_LOGIN_ATTEMPTS, failedLoginAttempts);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidate() {
|
||||
data.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRegistrarId() {
|
||||
return data.getOrDefault(REGISTRAR_ID, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getServiceExtensionUris() {
|
||||
return Optional.ofNullable(data.getOrDefault(SERVICE_EXTENSIONS, null))
|
||||
.map(s -> Splitter.on(URI_SEPARATOR).splitToList(s))
|
||||
.map(ImmutableSet::copyOf)
|
||||
.orElse(ImmutableSet.of());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFailedLoginAttempts() {
|
||||
return Optional.ofNullable(data.getOrDefault(FAILED_LOGIN_ATTEMPTS, null))
|
||||
.map(Integer::parseInt)
|
||||
.orElse(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegistrarId(String registrarId) {
|
||||
data.put(REGISTRAR_ID, registrarId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setServiceExtensionUris(Set<String> serviceExtensionUris) {
|
||||
if (serviceExtensionUris == null || serviceExtensionUris.isEmpty()) {
|
||||
data.remove(SERVICE_EXTENSIONS);
|
||||
} else {
|
||||
data.put(SERVICE_EXTENSIONS, Joiner.on(URI_SEPARATOR).join(serviceExtensionUris));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incrementFailedLoginAttempts() {
|
||||
data.put(FAILED_LOGIN_ATTEMPTS, String.valueOf(getFailedLoginAttempts() + 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetFailedLoginAttempts() {
|
||||
data.remove(FAILED_LOGIN_ATTEMPTS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(Response response) {
|
||||
String value = encode(toString());
|
||||
response.setHeader("Set-Cookie", COOKIE_NAME + "=" + value);
|
||||
}
|
||||
|
||||
protected static String encode(String plainText) {
|
||||
return BaseEncoding.base64Url().encode(plainText.getBytes(US_ASCII));
|
||||
}
|
||||
|
||||
protected static String decode(String cipherText) {
|
||||
return new String(BaseEncoding.base64Url().decode(cipherText), US_ASCII);
|
||||
}
|
||||
}
|
||||
@@ -78,6 +78,8 @@ public class EppRequestHandler {
|
||||
} catch (Exception e) {
|
||||
logger.atWarning().withCause(e).log("handleEppCommand general exception.");
|
||||
response.setStatus(SC_BAD_REQUEST);
|
||||
} finally {
|
||||
sessionMetadata.save(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import google.registry.request.Action.Method;
|
||||
import google.registry.request.Payload;
|
||||
import google.registry.request.auth.Auth;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* Establishes a transport for EPP+TLS over HTTP. All commands and responses are EPP XML according
|
||||
@@ -35,18 +35,18 @@ public class EppTlsAction implements Runnable {
|
||||
|
||||
@Inject @Payload byte[] inputXmlBytes;
|
||||
@Inject TlsCredentials tlsCredentials;
|
||||
@Inject HttpSession session;
|
||||
@Inject HttpServletRequest request;
|
||||
@Inject EppRequestHandler eppRequestHandler;
|
||||
@Inject EppTlsAction() {}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
eppRequestHandler.executeEpp(
|
||||
new HttpSessionMetadata(session),
|
||||
new CookieSessionMetadata(request),
|
||||
tlsCredentials,
|
||||
EppRequestSource.TLS,
|
||||
false, // This endpoint is never a dry run.
|
||||
false, // This endpoint is never a superuser.
|
||||
false, // This endpoint is never a dry run.
|
||||
false, // This endpoint is never a superuser.
|
||||
inputXmlBytes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,6 @@ import google.registry.flows.domain.DomainTransferQueryFlow;
|
||||
import google.registry.flows.domain.DomainTransferRejectFlow;
|
||||
import google.registry.flows.domain.DomainTransferRequestFlow;
|
||||
import google.registry.flows.domain.DomainUpdateFlow;
|
||||
import google.registry.flows.domain.token.AllocationTokenModule;
|
||||
import google.registry.flows.host.HostCheckFlow;
|
||||
import google.registry.flows.host.HostCreateFlow;
|
||||
import google.registry.flows.host.HostDeleteFlow;
|
||||
@@ -59,7 +58,6 @@ import google.registry.model.eppcommon.Trid;
|
||||
/** Dagger component for flow classes. */
|
||||
@FlowScope
|
||||
@Subcomponent(modules = {
|
||||
AllocationTokenModule.class,
|
||||
BatchModule.class,
|
||||
CustomLogicModule.class,
|
||||
DnsModule.class,
|
||||
|
||||
@@ -14,16 +14,14 @@
|
||||
|
||||
package google.registry.flows;
|
||||
|
||||
import static com.google.common.base.MoreObjects.toStringHelper;
|
||||
import static google.registry.util.CollectionUtils.nullToEmpty;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/** A metadata class that is a wrapper around {@link HttpSession}. */
|
||||
public class HttpSessionMetadata implements SessionMetadata {
|
||||
public class HttpSessionMetadata extends SessionMetadata {
|
||||
|
||||
private static final String REGISTRAR_ID = "REGISTRAR_ID";
|
||||
private static final String SERVICE_EXTENSIONS = "SERVICE_EXTENSIONS";
|
||||
@@ -75,13 +73,4 @@ public class HttpSessionMetadata implements SessionMetadata {
|
||||
public void resetFailedLoginAttempts() {
|
||||
session.removeAttribute(FAILED_LOGIN_ATTEMPTS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toStringHelper(getClass())
|
||||
.add("clientId", getRegistrarId())
|
||||
.add("failedLoginAttempts", getFailedLoginAttempts())
|
||||
.add("serviceExtensionUris", Joiner.on('.').join(nullToEmpty(getServiceExtensionUris())))
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,29 +14,49 @@
|
||||
|
||||
package google.registry.flows;
|
||||
|
||||
import static com.google.common.base.MoreObjects.toStringHelper;
|
||||
import static google.registry.util.CollectionUtils.nullToEmpty;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import google.registry.request.Response;
|
||||
import java.util.Set;
|
||||
|
||||
/** Object to allow setting and retrieving session information in flows. */
|
||||
public interface SessionMetadata {
|
||||
public abstract class SessionMetadata {
|
||||
|
||||
protected static final char URI_SEPARATOR = '|';
|
||||
|
||||
/**
|
||||
* Invalidates the session. A new instance must be created after this for future sessions.
|
||||
* Attempts to invoke methods of this class after this method has been called will throw
|
||||
* {@code IllegalStateException}.
|
||||
* Attempts to invoke methods of this class after this method has been called will throw {@code
|
||||
* IllegalStateException}.
|
||||
*/
|
||||
void invalidate();
|
||||
public abstract void invalidate();
|
||||
|
||||
String getRegistrarId();
|
||||
public abstract String getRegistrarId();
|
||||
|
||||
Set<String> getServiceExtensionUris();
|
||||
public abstract Set<String> getServiceExtensionUris();
|
||||
|
||||
int getFailedLoginAttempts();
|
||||
public abstract int getFailedLoginAttempts();
|
||||
|
||||
void setRegistrarId(String registrarId);
|
||||
public abstract void setRegistrarId(String registrarId);
|
||||
|
||||
void setServiceExtensionUris(Set<String> serviceExtensionUris);
|
||||
public abstract void setServiceExtensionUris(Set<String> serviceExtensionUris);
|
||||
|
||||
void incrementFailedLoginAttempts();
|
||||
public abstract void incrementFailedLoginAttempts();
|
||||
|
||||
void resetFailedLoginAttempts();
|
||||
public abstract void resetFailedLoginAttempts();
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toStringHelper(getClass())
|
||||
.add("clientId", getRegistrarId())
|
||||
.add("failedLoginAttempts", getFailedLoginAttempts())
|
||||
.add(
|
||||
"serviceExtensionUris",
|
||||
Joiner.on(URI_SEPARATOR).join(nullToEmpty(getServiceExtensionUris())))
|
||||
.toString();
|
||||
}
|
||||
|
||||
public void save(Response response) {}
|
||||
}
|
||||
|
||||
@@ -14,16 +14,13 @@
|
||||
|
||||
package google.registry.flows;
|
||||
|
||||
import static com.google.common.base.MoreObjects.toStringHelper;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static google.registry.util.CollectionUtils.nullToEmpty;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import java.util.Set;
|
||||
|
||||
/** A read-only {@link SessionMetadata} that doesn't support login/logout. */
|
||||
public class StatelessRequestSessionMetadata implements SessionMetadata {
|
||||
public class StatelessRequestSessionMetadata extends SessionMetadata {
|
||||
|
||||
private final String registrarId;
|
||||
private final ImmutableSet<String> serviceExtensionUris;
|
||||
@@ -74,13 +71,6 @@ public class StatelessRequestSessionMetadata implements SessionMetadata {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toStringHelper(getClass())
|
||||
.add("clientId", getRegistrarId())
|
||||
.add("failedLoginAttempts", getFailedLoginAttempts())
|
||||
.add("serviceExtensionUris", Joiner.on('.').join(nullToEmpty(getServiceExtensionUris())))
|
||||
.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ import google.registry.model.eppoutput.EppResponse.ResponseExtension;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.poll.PollMessage.Autorenew;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarBase.State;
|
||||
import google.registry.model.registrar.Registrar.State;
|
||||
import google.registry.model.reporting.DomainTransactionRecord;
|
||||
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
|
||||
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
|
||||
|
||||
@@ -1,64 +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.flows.domain.token;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainCommand;
|
||||
import google.registry.model.domain.token.AllocationToken;
|
||||
import google.registry.model.tld.Tld;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* A no-op base class for allocation token custom logic.
|
||||
*
|
||||
* <p>Extend this class and override the hook(s) to perform custom logic.
|
||||
*/
|
||||
public class AllocationTokenCustomLogic {
|
||||
|
||||
/** Performs additional custom logic for validating a token on a domain create. */
|
||||
public AllocationToken validateToken(
|
||||
DomainCommand.Create command,
|
||||
AllocationToken token,
|
||||
Tld tld,
|
||||
String registrarId,
|
||||
DateTime now)
|
||||
throws EppException {
|
||||
// Do nothing.
|
||||
return token;
|
||||
}
|
||||
|
||||
/** Performs additional custom logic for validating a token on an existing domain. */
|
||||
public AllocationToken validateToken(
|
||||
Domain domain, AllocationToken token, Tld tld, String registrarId, DateTime now)
|
||||
throws EppException {
|
||||
// Do nothing.
|
||||
return token;
|
||||
}
|
||||
|
||||
/** Performs additional custom logic for performing domain checks using a token. */
|
||||
public ImmutableMap<InternetDomainName, String> checkDomainsWithToken(
|
||||
ImmutableList<InternetDomainName> domainNames,
|
||||
AllocationToken token,
|
||||
String registrarId,
|
||||
DateTime now) {
|
||||
// Do nothing.
|
||||
return Maps.toMap(domainNames, k -> "");
|
||||
}
|
||||
}
|
||||
+4
-16
@@ -19,7 +19,6 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
|
||||
import static google.registry.pricing.PricingEngineProxy.isDomainPremium;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
@@ -48,12 +47,8 @@ import org.joda.time.DateTime;
|
||||
/** Utility functions for dealing with {@link AllocationToken}s in domain flows. */
|
||||
public class AllocationTokenFlowUtils {
|
||||
|
||||
private final AllocationTokenCustomLogic tokenCustomLogic;
|
||||
|
||||
@Inject
|
||||
AllocationTokenFlowUtils(AllocationTokenCustomLogic tokenCustomLogic) {
|
||||
this.tokenCustomLogic = tokenCustomLogic;
|
||||
}
|
||||
public AllocationTokenFlowUtils() {}
|
||||
|
||||
/**
|
||||
* Checks if the allocation token applies to the given domain names, used for domain checks.
|
||||
@@ -75,7 +70,6 @@ public class AllocationTokenFlowUtils {
|
||||
|
||||
// If the token is only invalid for some domain names (e.g. an invalid TLD), include those error
|
||||
// results for only those domain names
|
||||
ImmutableList.Builder<InternetDomainName> validDomainNames = new ImmutableList.Builder<>();
|
||||
ImmutableMap.Builder<InternetDomainName, String> resultsBuilder = new ImmutableMap.Builder<>();
|
||||
for (InternetDomainName domainName : domainNames) {
|
||||
try {
|
||||
@@ -86,16 +80,11 @@ public class AllocationTokenFlowUtils {
|
||||
registrarId,
|
||||
isDomainPremium(domainName.toString(), now),
|
||||
now);
|
||||
validDomainNames.add(domainName);
|
||||
resultsBuilder.put(domainName, "");
|
||||
} catch (EppException e) {
|
||||
resultsBuilder.put(domainName, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// For all valid domain names, run the custom logic and include the results
|
||||
resultsBuilder.putAll(
|
||||
tokenCustomLogic.checkDomainsWithToken(
|
||||
validDomainNames.build(), tokenEntity, registrarId, now));
|
||||
return new AllocationTokenDomainCheckResults(Optional.of(tokenEntity), resultsBuilder.build());
|
||||
}
|
||||
|
||||
@@ -209,7 +198,7 @@ public class AllocationTokenFlowUtils {
|
||||
registrarId,
|
||||
isDomainPremium(command.getDomainName(), now),
|
||||
now);
|
||||
return Optional.of(tokenCustomLogic.validateToken(command, tokenEntity, tld, registrarId, now));
|
||||
return Optional.of(tokenEntity);
|
||||
}
|
||||
|
||||
/** Verifies and returns the allocation token if one is specified, otherwise does nothing. */
|
||||
@@ -232,8 +221,7 @@ public class AllocationTokenFlowUtils {
|
||||
registrarId,
|
||||
isDomainPremium(existingDomain.getDomainName(), now),
|
||||
now);
|
||||
return Optional.of(
|
||||
tokenCustomLogic.validateToken(existingDomain, tokenEntity, tld, registrarId, now));
|
||||
return Optional.of(tokenEntity);
|
||||
}
|
||||
|
||||
public static void verifyTokenAllowedOnDomain(
|
||||
|
||||
@@ -1,33 +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.flows.domain.token;
|
||||
|
||||
import static google.registry.util.TypeUtils.getClassFromString;
|
||||
import static google.registry.util.TypeUtils.instantiate;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
|
||||
/** Dagger module for allocation token classes. */
|
||||
@Module
|
||||
public class AllocationTokenModule {
|
||||
|
||||
@Provides
|
||||
static AllocationTokenCustomLogic provideAllocationTokenCustomLogic(
|
||||
@Config("allocationTokenCustomLogicClass") String customClass) {
|
||||
return instantiate(getClassFromString(customClass, AllocationTokenCustomLogic.class));
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.model.console;
|
||||
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
|
||||
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;
|
||||
|
||||
/**
|
||||
* A persisted history object representing an EPP action via the console.
|
||||
*
|
||||
* <p>In addition to the generic history fields (time, URL, etc.) we also persist a reference to the
|
||||
* history entry so that we can refer to it if necessary.
|
||||
*/
|
||||
@Access(AccessType.FIELD)
|
||||
@Entity
|
||||
@Table(
|
||||
indexes = {
|
||||
@Index(columnList = "historyActingUser"),
|
||||
@Index(columnList = "repoId"),
|
||||
@Index(columnList = "revisionId")
|
||||
})
|
||||
public class ConsoleEppActionHistory extends ConsoleUpdateHistory {
|
||||
|
||||
@AttributeOverride(name = "repoId", column = @Column(nullable = false))
|
||||
HistoryEntryId historyEntryId;
|
||||
|
||||
@Column(nullable = false)
|
||||
Class<? extends HistoryEntry> historyEntryClass;
|
||||
|
||||
public HistoryEntryId getHistoryEntryId() {
|
||||
return historyEntryId;
|
||||
}
|
||||
|
||||
public Class<? extends HistoryEntry> getHistoryEntryClass() {
|
||||
return historyEntryClass;
|
||||
}
|
||||
|
||||
/** Creates a {@link VKey} instance for this entity. */
|
||||
@Override
|
||||
public VKey<ConsoleEppActionHistory> createVKey() {
|
||||
return VKey.create(ConsoleEppActionHistory.class, getRevisionId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder asBuilder() {
|
||||
return new Builder(clone(this));
|
||||
}
|
||||
|
||||
/** Builder for the immutable UserUpdateHistory. */
|
||||
public static class Builder
|
||||
extends ConsoleUpdateHistory.Builder<ConsoleEppActionHistory, Builder> {
|
||||
|
||||
public Builder() {}
|
||||
|
||||
public Builder(ConsoleEppActionHistory instance) {
|
||||
super(instance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConsoleEppActionHistory build() {
|
||||
checkArgumentNotNull(getInstance().historyEntryId, "History entry ID must be specified");
|
||||
checkArgumentNotNull(
|
||||
getInstance().historyEntryClass, "History entry class must be specified");
|
||||
return super.build();
|
||||
}
|
||||
|
||||
public Builder setHistoryEntryId(HistoryEntryId historyEntryId) {
|
||||
getInstance().historyEntryId = historyEntryId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setHistoryEntryClass(Class<? extends HistoryEntry> historyEntryClass) {
|
||||
getInstance().historyEntryClass = historyEntryClass;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
|
||||
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -19,150 +19,146 @@ import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.annotations.IdAllocation;
|
||||
import jakarta.persistence.Access;
|
||||
import jakarta.persistence.AccessType;
|
||||
import google.registry.persistence.WithVKey;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Index;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.MappedSuperclass;
|
||||
import jakarta.persistence.Table;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* A record of a resource that was updated through the console.
|
||||
*
|
||||
* <p>This abstract class has several subclasses that (mostly) include the modified resource itself
|
||||
* so that the entire object history is persisted to SQL.
|
||||
*/
|
||||
@Access(AccessType.FIELD)
|
||||
@MappedSuperclass
|
||||
public abstract class ConsoleUpdateHistory extends ImmutableObject implements Buildable {
|
||||
@Entity
|
||||
@WithVKey(Long.class)
|
||||
@Table(
|
||||
indexes = {
|
||||
@Index(columnList = "actingUser", name = "idx_console_update_history_acting_user"),
|
||||
@Index(columnList = "type", name = "idx_console_update_history_type"),
|
||||
@Index(columnList = "modificationTime", name = "idx_console_update_history_modification_time")
|
||||
})
|
||||
public class ConsoleUpdateHistory extends ImmutableObject implements Buildable {
|
||||
|
||||
public enum Type {
|
||||
EPP_ACTION,
|
||||
POC_CREATE,
|
||||
POC_UPDATE,
|
||||
POC_DELETE,
|
||||
REGISTRAR_UPDATE,
|
||||
USER_CREATE,
|
||||
USER_DELETE,
|
||||
USER_UPDATE
|
||||
}
|
||||
@Id @IdAllocation @Column Long revisionId;
|
||||
|
||||
/** Autogenerated ID of this event. */
|
||||
@Id
|
||||
@IdAllocation
|
||||
@Column(nullable = false, name = "historyRevisionId")
|
||||
protected Long revisionId;
|
||||
|
||||
/** The user that performed the modification. */
|
||||
@JoinColumn(name = "historyActingUser", referencedColumnName = "emailAddress", nullable = false)
|
||||
@ManyToOne
|
||||
User actingUser;
|
||||
|
||||
/** The URL of the action that was used to make the modification. */
|
||||
@Column(nullable = false, name = "historyUrl")
|
||||
String url;
|
||||
|
||||
/** The HTTP method (e.g. POST, PUT) used to make this modification. */
|
||||
@Column(nullable = false, name = "historyMethod")
|
||||
String method;
|
||||
|
||||
/** The raw body of the request that was used to make this modification. */
|
||||
@Column(name = "historyRequestBody")
|
||||
String requestBody;
|
||||
|
||||
/** The time at which the modification was mode. */
|
||||
@Column(nullable = false, name = "historyModificationTime")
|
||||
@Column(nullable = false)
|
||||
DateTime modificationTime;
|
||||
|
||||
/** The HTTP method (e.g. POST, PUT) used to make this modification. */
|
||||
@Column(nullable = false)
|
||||
String method;
|
||||
|
||||
/** The type of modification. */
|
||||
@Column(nullable = false, name = "historyType")
|
||||
@Column(nullable = false)
|
||||
@Enumerated(EnumType.STRING)
|
||||
Type type;
|
||||
|
||||
public long getRevisionId() {
|
||||
/** The URL of the action that was used to make the modification. */
|
||||
@Column(nullable = false)
|
||||
String url;
|
||||
|
||||
/** An optional further description of the action. */
|
||||
String description;
|
||||
|
||||
/** The user that performed the modification. */
|
||||
@JoinColumn(name = "actingUser", referencedColumnName = "emailAddress", nullable = false)
|
||||
@ManyToOne
|
||||
User actingUser;
|
||||
|
||||
public Long getRevisionId() {
|
||||
return revisionId;
|
||||
}
|
||||
|
||||
public User getActingUser() {
|
||||
return actingUser;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public String getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public String getRequestBody() {
|
||||
return requestBody;
|
||||
}
|
||||
|
||||
public DateTime getModificationTime() {
|
||||
return modificationTime;
|
||||
}
|
||||
|
||||
public Optional<String> getDescription() {
|
||||
return Optional.ofNullable(description);
|
||||
}
|
||||
|
||||
public String getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public User getActingUser() {
|
||||
return actingUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract Builder<? extends ConsoleUpdateHistory, ?> asBuilder();
|
||||
public Builder asBuilder() {
|
||||
return new Builder(clone(this));
|
||||
}
|
||||
|
||||
/** Builder for the immutable ConsoleUpdateHistory. */
|
||||
public abstract static class Builder<
|
||||
T extends ConsoleUpdateHistory, B extends ConsoleUpdateHistory.Builder<?, ?>>
|
||||
extends GenericBuilder<T, B> {
|
||||
public enum Type {
|
||||
DOMAIN_DELETE,
|
||||
DOMAIN_SUSPEND,
|
||||
DOMAIN_UNSUSPEND,
|
||||
EPP_PASSWORD_UPDATE,
|
||||
REGISTRAR_CREATE,
|
||||
REGISTRAR_SECURITY_UPDATE,
|
||||
REGISTRAR_UPDATE,
|
||||
USER_CREATE,
|
||||
USER_DELETE,
|
||||
USER_UPDATE
|
||||
}
|
||||
|
||||
protected Builder() {}
|
||||
public static class Builder extends Buildable.Builder<ConsoleUpdateHistory> {
|
||||
public Builder() {}
|
||||
|
||||
protected Builder(T instance) {
|
||||
private Builder(ConsoleUpdateHistory instance) {
|
||||
super(instance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T build() {
|
||||
public ConsoleUpdateHistory build() {
|
||||
checkArgumentNotNull(getInstance().modificationTime, "Modification time must be specified");
|
||||
checkArgumentNotNull(getInstance().actingUser, "Acting user must be specified");
|
||||
checkArgumentNotNull(getInstance().url, "URL must be specified");
|
||||
checkArgumentNotNull(getInstance().method, "HTTP method must be specified");
|
||||
checkArgumentNotNull(getInstance().modificationTime, "modificationTime must be specified");
|
||||
checkArgumentNotNull(getInstance().type, "Console History type must be specified");
|
||||
checkArgumentNotNull(getInstance().type, "ConsoleUpdateHistory type must be specified");
|
||||
return super.build();
|
||||
}
|
||||
|
||||
public B setActingUser(User actingUser) {
|
||||
getInstance().actingUser = actingUser;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setUrl(String url) {
|
||||
getInstance().url = url;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setMethod(String method) {
|
||||
getInstance().method = method;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setRequestBody(String requestBody) {
|
||||
getInstance().requestBody = requestBody;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setModificationTime(DateTime modificationTime) {
|
||||
public Builder setModificationTime(DateTime modificationTime) {
|
||||
getInstance().modificationTime = modificationTime;
|
||||
return thisCastToDerived();
|
||||
return this;
|
||||
}
|
||||
|
||||
public B setType(Type type) {
|
||||
public Builder setActingUser(User actingUser) {
|
||||
getInstance().actingUser = actingUser;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setUrl(String url) {
|
||||
getInstance().url = url;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setMethod(String method) {
|
||||
getInstance().method = method;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setDescription(String description) {
|
||||
getInstance().description = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setType(Type type) {
|
||||
getInstance().type = type;
|
||||
return thisCastToDerived();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.model.console;
|
||||
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
import google.registry.model.registrar.RegistrarPocBase;
|
||||
import google.registry.persistence.VKey;
|
||||
import jakarta.persistence.Access;
|
||||
import jakarta.persistence.AccessType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Index;
|
||||
import jakarta.persistence.PostLoad;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
/**
|
||||
* A persisted history object representing an update to a RegistrarPoc.
|
||||
*
|
||||
* <p>In addition to the generic history fields (time, URL, etc.) we also persist a copy of the
|
||||
* modified RegistrarPoc object at this point in time.
|
||||
*/
|
||||
@Access(AccessType.FIELD)
|
||||
@Entity
|
||||
@Table(
|
||||
indexes = {
|
||||
@Index(columnList = "historyActingUser"),
|
||||
@Index(columnList = "emailAddress"),
|
||||
@Index(columnList = "registrarId")
|
||||
})
|
||||
public class RegistrarPocUpdateHistory extends ConsoleUpdateHistory {
|
||||
|
||||
RegistrarPocBase registrarPoc;
|
||||
|
||||
// These fields exist so that they can be populated in the SQL table
|
||||
@Column(nullable = false)
|
||||
String emailAddress;
|
||||
|
||||
@Column(nullable = false)
|
||||
String registrarId;
|
||||
|
||||
public RegistrarPocBase getRegistrarPoc() {
|
||||
return registrarPoc;
|
||||
}
|
||||
|
||||
@PostLoad
|
||||
void postLoad() {
|
||||
registrarPoc.setEmailAddress(emailAddress);
|
||||
registrarPoc.setRegistrarId(registrarId);
|
||||
}
|
||||
|
||||
/** Creates a {@link VKey} instance for this entity. */
|
||||
@Override
|
||||
public VKey<RegistrarPocUpdateHistory> createVKey() {
|
||||
return VKey.create(RegistrarPocUpdateHistory.class, getRevisionId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder asBuilder() {
|
||||
return new Builder(clone(this));
|
||||
}
|
||||
|
||||
/** Builder for the immutable UserUpdateHistory. */
|
||||
public static class Builder
|
||||
extends ConsoleUpdateHistory.Builder<RegistrarPocUpdateHistory, Builder> {
|
||||
|
||||
public Builder() {}
|
||||
|
||||
public Builder(RegistrarPocUpdateHistory instance) {
|
||||
super(instance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegistrarPocUpdateHistory build() {
|
||||
checkArgumentNotNull(getInstance().registrarPoc, "Registrar POC must be specified");
|
||||
return super.build();
|
||||
}
|
||||
|
||||
public Builder setRegistrarPoc(RegistrarPoc registrarPoc) {
|
||||
getInstance().registrarPoc = registrarPoc;
|
||||
getInstance().registrarId = registrarPoc.getRegistrarId();
|
||||
getInstance().emailAddress = registrarPoc.getEmailAddress();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.model.console;
|
||||
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import google.registry.model.registrar.RegistrarBase;
|
||||
import google.registry.persistence.VKey;
|
||||
import jakarta.persistence.Access;
|
||||
import jakarta.persistence.AccessType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Index;
|
||||
import jakarta.persistence.PostLoad;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
/**
|
||||
* A persisted history object representing an update to a Registrar.
|
||||
*
|
||||
* <p>In addition to the generic history fields (time, URL, etc.) we also persist a copy of the
|
||||
* modified Registrar object at this point in time.
|
||||
*/
|
||||
@Access(AccessType.FIELD)
|
||||
@Entity
|
||||
@Table(indexes = {@Index(columnList = "historyActingUser"), @Index(columnList = "registrarId")})
|
||||
public class RegistrarUpdateHistory extends ConsoleUpdateHistory {
|
||||
|
||||
RegistrarBase registrar;
|
||||
|
||||
// This field exists so that it exists in the SQL table
|
||||
@Column(nullable = false)
|
||||
@SuppressWarnings("unused")
|
||||
private String registrarId;
|
||||
|
||||
public RegistrarBase getRegistrar() {
|
||||
return registrar;
|
||||
}
|
||||
|
||||
@PostLoad
|
||||
void postLoad() {
|
||||
registrar.setRegistrarId(registrarId);
|
||||
}
|
||||
|
||||
/** Creates a {@link VKey} instance for this entity. */
|
||||
@Override
|
||||
public VKey<RegistrarUpdateHistory> createVKey() {
|
||||
return VKey.create(RegistrarUpdateHistory.class, getRevisionId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder asBuilder() {
|
||||
return new RegistrarUpdateHistory.Builder(clone(this));
|
||||
}
|
||||
|
||||
/** Builder for the immutable UserUpdateHistory. */
|
||||
public static class Builder
|
||||
extends ConsoleUpdateHistory.Builder<RegistrarUpdateHistory, Builder> {
|
||||
|
||||
public Builder() {}
|
||||
|
||||
public Builder(RegistrarUpdateHistory instance) {
|
||||
super(instance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegistrarUpdateHistory build() {
|
||||
checkArgumentNotNull(getInstance().registrar, "Registrar must be specified");
|
||||
return super.build();
|
||||
}
|
||||
|
||||
public Builder setRegistrar(RegistrarBase registrar) {
|
||||
getInstance().registrar = registrar;
|
||||
getInstance().registrarId = registrar.getRegistrarId();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
|
||||
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -15,23 +15,30 @@
|
||||
package google.registry.model.console;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
import static com.google.common.io.BaseEncoding.base64;
|
||||
import static google.registry.model.registrar.Registrar.checkValidEmail;
|
||||
import static google.registry.tools.server.UpdateUserGroupAction.GROUP_UPDATE_QUEUE;
|
||||
import static google.registry.util.PasswordUtils.SALT_SUPPLIER;
|
||||
import static google.registry.util.PasswordUtils.hashPassword;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import com.google.cloud.tasks.v2.Task;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.net.MediaType;
|
||||
import com.google.gson.annotations.Expose;
|
||||
import google.registry.batch.CloudTasksUtils;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.UpdateAutoTimestampEntity;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.tools.IamClient;
|
||||
import google.registry.tools.ServiceConnection;
|
||||
import google.registry.tools.server.UpdateUserGroupAction;
|
||||
import google.registry.tools.server.UpdateUserGroupAction.Mode;
|
||||
import google.registry.util.PasswordUtils;
|
||||
import google.registry.util.RegistryEnvironment;
|
||||
import jakarta.persistence.Access;
|
||||
import jakarta.persistence.AccessType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Embeddable;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
@@ -44,11 +51,33 @@ import javax.annotation.Nullable;
|
||||
@Embeddable
|
||||
@Entity
|
||||
@Table
|
||||
public class User extends UserBase {
|
||||
public class User extends UpdateAutoTimestampEntity implements Buildable {
|
||||
|
||||
public static final String IAP_SECURED_WEB_APP_USER_ROLE = "roles/iap.httpsResourceAccessor";
|
||||
|
||||
private static final long serialVersionUID = 6936728603828566721L;
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
/** Email address of the user in question. */
|
||||
@Id @Expose String emailAddress;
|
||||
|
||||
/** Optional external email address to use for registry lock confirmation emails. */
|
||||
@Column String registryLockEmailAddress;
|
||||
|
||||
/** Roles (which grant permissions) associated with this user. */
|
||||
@Expose
|
||||
@Column(nullable = false)
|
||||
UserRoles userRoles;
|
||||
|
||||
/**
|
||||
* A hashed password that exists iff this contact is registry-lock-enabled. The hash is a base64
|
||||
* encoded SHA256 string.
|
||||
*/
|
||||
String registryLockPasswordHash;
|
||||
|
||||
/** Randomly generated hash salt. */
|
||||
String registryLockPasswordSalt;
|
||||
|
||||
/**
|
||||
* Grants the user permission to pass IAP.
|
||||
*
|
||||
@@ -113,9 +142,13 @@ public class User extends UserBase {
|
||||
logger.atInfo().log("Removing %s from group %s", emailAddress, groupEmailAddress.get());
|
||||
if (cloudTasksUtils != null) {
|
||||
modifyGroupMembershipAsync(
|
||||
emailAddress, groupEmailAddress.get(), cloudTasksUtils, Mode.REMOVE);
|
||||
emailAddress,
|
||||
groupEmailAddress.get(),
|
||||
cloudTasksUtils,
|
||||
UpdateUserGroupAction.Mode.REMOVE);
|
||||
} else {
|
||||
modifyGroupMembershipSync(emailAddress, groupEmailAddress.get(), connection, Mode.REMOVE);
|
||||
modifyGroupMembershipSync(
|
||||
emailAddress, groupEmailAddress.get(), connection, UpdateUserGroupAction.Mode.REMOVE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -124,7 +157,7 @@ public class User extends UserBase {
|
||||
String userEmailAddress,
|
||||
String groupEmailAddress,
|
||||
CloudTasksUtils cloudTasksUtils,
|
||||
Mode mode) {
|
||||
UpdateUserGroupAction.Mode mode) {
|
||||
Task task =
|
||||
cloudTasksUtils.createTask(
|
||||
UpdateUserGroupAction.class,
|
||||
@@ -140,7 +173,10 @@ public class User extends UserBase {
|
||||
}
|
||||
|
||||
private static void modifyGroupMembershipSync(
|
||||
String userEmailAddress, String groupEmailAddress, ServiceConnection connection, Mode mode) {
|
||||
String userEmailAddress,
|
||||
String groupEmailAddress,
|
||||
ServiceConnection connection,
|
||||
UpdateUserGroupAction.Mode mode) {
|
||||
try {
|
||||
connection.sendPostRequest(
|
||||
UpdateUserGroupAction.PATH,
|
||||
@@ -158,30 +194,117 @@ public class User extends UserBase {
|
||||
}
|
||||
}
|
||||
|
||||
@Id
|
||||
@Override
|
||||
@Access(AccessType.PROPERTY)
|
||||
/**
|
||||
* Sets the user email address.
|
||||
*
|
||||
* <p>This should only be used for restoring an object being loaded in a PostLoad method
|
||||
* (effectively, when it is still under construction by Hibernate). In all other cases, the object
|
||||
* should be regarded as immutable and changes should go through a Builder.
|
||||
*
|
||||
* <p>In addition to this special case use, this method must exist to satisfy Hibernate.
|
||||
*/
|
||||
void setEmailAddress(String emailAddress) {
|
||||
this.emailAddress = emailAddress;
|
||||
}
|
||||
|
||||
public String getEmailAddress() {
|
||||
return super.getEmailAddress();
|
||||
return emailAddress;
|
||||
}
|
||||
|
||||
public Optional<String> getRegistryLockEmailAddress() {
|
||||
return Optional.ofNullable(registryLockEmailAddress);
|
||||
}
|
||||
|
||||
public UserRoles getUserRoles() {
|
||||
return userRoles;
|
||||
}
|
||||
|
||||
public boolean hasRegistryLockPassword() {
|
||||
return !isNullOrEmpty(registryLockPasswordHash) && !isNullOrEmpty(registryLockPasswordSalt);
|
||||
}
|
||||
|
||||
public boolean verifyRegistryLockPassword(String registryLockPassword) {
|
||||
if (isNullOrEmpty(registryLockPassword)
|
||||
|| isNullOrEmpty(registryLockPasswordSalt)
|
||||
|| isNullOrEmpty(registryLockPasswordHash)) {
|
||||
return false;
|
||||
}
|
||||
return PasswordUtils.verifyPassword(
|
||||
registryLockPassword, registryLockPasswordHash, registryLockPasswordSalt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the user has the registry lock permission on any registrar or globally.
|
||||
*
|
||||
* <p>If so, they should be allowed to (re)set their registry lock password.
|
||||
*/
|
||||
public boolean hasAnyRegistryLockPermission() {
|
||||
if (userRoles == null) {
|
||||
return false;
|
||||
}
|
||||
if (userRoles.isAdmin() || userRoles.hasGlobalPermission(ConsolePermission.REGISTRY_LOCK)) {
|
||||
return true;
|
||||
}
|
||||
return userRoles.getRegistrarRoles().values().stream()
|
||||
.anyMatch(role -> role.hasPermission(ConsolePermission.REGISTRY_LOCK));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder asBuilder() {
|
||||
return new Builder(clone(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public VKey<User> createVKey() {
|
||||
return VKey.create(User.class, getEmailAddress());
|
||||
public Builder<? extends User, ?> asBuilder() {
|
||||
return new Builder<>(clone(this));
|
||||
}
|
||||
|
||||
/** Builder for constructing immutable {@link User} objects. */
|
||||
public static class Builder extends UserBase.Builder<User, Builder> {
|
||||
public static class Builder<T extends User, B extends Builder<T, B>>
|
||||
extends GenericBuilder<T, B> {
|
||||
|
||||
public Builder() {}
|
||||
|
||||
public Builder(User user) {
|
||||
super(user);
|
||||
public Builder(T abstractUser) {
|
||||
super(abstractUser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T build() {
|
||||
checkArgumentNotNull(getInstance().emailAddress, "Email address cannot be null");
|
||||
checkArgumentNotNull(getInstance().userRoles, "User roles cannot be null");
|
||||
return super.build();
|
||||
}
|
||||
|
||||
public B setEmailAddress(String emailAddress) {
|
||||
getInstance().emailAddress = checkValidEmail(emailAddress);
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setRegistryLockEmailAddress(@Nullable String registryLockEmailAddress) {
|
||||
getInstance().registryLockEmailAddress =
|
||||
registryLockEmailAddress == null ? null : checkValidEmail(registryLockEmailAddress);
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setUserRoles(UserRoles userRoles) {
|
||||
checkArgumentNotNull(userRoles, "User roles cannot be null");
|
||||
getInstance().userRoles = userRoles;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B removeRegistryLockPassword() {
|
||||
getInstance().registryLockPasswordHash = null;
|
||||
getInstance().registryLockPasswordSalt = null;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setRegistryLockPassword(String registryLockPassword) {
|
||||
checkArgument(
|
||||
getInstance().hasAnyRegistryLockPermission(), "User has no registry lock permission");
|
||||
checkArgument(
|
||||
!getInstance().hasRegistryLockPassword(), "User already has a password, remove it first");
|
||||
checkArgument(
|
||||
!isNullOrEmpty(registryLockPassword), "Registry lock password was null or empty");
|
||||
byte[] salt = SALT_SUPPLIER.get();
|
||||
getInstance().registryLockPasswordSalt = base64().encode(salt);
|
||||
getInstance().registryLockPasswordHash = hashPassword(registryLockPassword, salt);
|
||||
return thisCastToDerived();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,186 +0,0 @@
|
||||
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.model.console;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
import static com.google.common.io.BaseEncoding.base64;
|
||||
import static google.registry.model.registrar.Registrar.checkValidEmail;
|
||||
import static google.registry.util.PasswordUtils.SALT_SUPPLIER;
|
||||
import static google.registry.util.PasswordUtils.hashPassword;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import com.google.gson.annotations.Expose;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.UpdateAutoTimestampEntity;
|
||||
import google.registry.util.PasswordUtils;
|
||||
import jakarta.persistence.Access;
|
||||
import jakarta.persistence.AccessType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Embeddable;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.MappedSuperclass;
|
||||
import jakarta.persistence.Transient;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A console user, either a registry employee or a registrar partner.
|
||||
*
|
||||
* <p>This class deliberately does not include an {@link 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.
|
||||
*/
|
||||
@Access(AccessType.FIELD)
|
||||
@Embeddable
|
||||
@MappedSuperclass
|
||||
public class UserBase extends UpdateAutoTimestampEntity implements Buildable {
|
||||
|
||||
private static final long serialVersionUID = 6936728603828566721L;
|
||||
|
||||
/** Email address of the user in question. */
|
||||
@Transient @Expose String emailAddress;
|
||||
|
||||
/** Optional external email address to use for registry lock confirmation emails. */
|
||||
@Column String registryLockEmailAddress;
|
||||
|
||||
/** Roles (which grant permissions) associated with this user. */
|
||||
@Expose
|
||||
@Column(nullable = false)
|
||||
UserRoles userRoles;
|
||||
|
||||
/**
|
||||
* A hashed password that exists iff this contact is registry-lock-enabled. The hash is a base64
|
||||
* encoded SHA256 string.
|
||||
*/
|
||||
String registryLockPasswordHash;
|
||||
|
||||
/** Randomly generated hash salt. */
|
||||
String registryLockPasswordSalt;
|
||||
|
||||
/**
|
||||
* Sets the user email address.
|
||||
*
|
||||
* <p>This should only be used for restoring an object being loaded in a PostLoad method
|
||||
* (effectively, when it is still under construction by Hibernate). In all other cases, the object
|
||||
* should be regarded as immutable and changes should go through a Builder.
|
||||
*
|
||||
* <p>In addition to this special case use, this method must exist to satisfy Hibernate.
|
||||
*/
|
||||
void setEmailAddress(String emailAddress) {
|
||||
this.emailAddress = emailAddress;
|
||||
}
|
||||
|
||||
public String getEmailAddress() {
|
||||
return emailAddress;
|
||||
}
|
||||
|
||||
public Optional<String> getRegistryLockEmailAddress() {
|
||||
return Optional.ofNullable(registryLockEmailAddress);
|
||||
}
|
||||
|
||||
public UserRoles getUserRoles() {
|
||||
return userRoles;
|
||||
}
|
||||
|
||||
public boolean hasRegistryLockPassword() {
|
||||
return !isNullOrEmpty(registryLockPasswordHash) && !isNullOrEmpty(registryLockPasswordSalt);
|
||||
}
|
||||
|
||||
public boolean verifyRegistryLockPassword(String registryLockPassword) {
|
||||
if (isNullOrEmpty(registryLockPassword)
|
||||
|| isNullOrEmpty(registryLockPasswordSalt)
|
||||
|| isNullOrEmpty(registryLockPasswordHash)) {
|
||||
return false;
|
||||
}
|
||||
return PasswordUtils.verifyPassword(
|
||||
registryLockPassword, registryLockPasswordHash, registryLockPasswordSalt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the user has the registry lock permission on any registrar or globally.
|
||||
*
|
||||
* <p>If so, they should be allowed to (re)set their registry lock password.
|
||||
*/
|
||||
public boolean hasAnyRegistryLockPermission() {
|
||||
if (userRoles == null) {
|
||||
return false;
|
||||
}
|
||||
if (userRoles.isAdmin() || userRoles.hasGlobalPermission(ConsolePermission.REGISTRY_LOCK)) {
|
||||
return true;
|
||||
}
|
||||
return userRoles.getRegistrarRoles().values().stream()
|
||||
.anyMatch(role -> role.hasPermission(ConsolePermission.REGISTRY_LOCK));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder<? extends UserBase, ?> asBuilder() {
|
||||
return new Builder<>(clone(this));
|
||||
}
|
||||
|
||||
/** Builder for constructing immutable {@link UserBase} objects. */
|
||||
public static class Builder<T extends UserBase, B extends Builder<T, B>>
|
||||
extends GenericBuilder<T, B> {
|
||||
|
||||
public Builder() {}
|
||||
|
||||
public Builder(T abstractUser) {
|
||||
super(abstractUser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T build() {
|
||||
checkArgumentNotNull(getInstance().emailAddress, "Email address cannot be null");
|
||||
checkArgumentNotNull(getInstance().userRoles, "User roles cannot be null");
|
||||
return super.build();
|
||||
}
|
||||
|
||||
public B setEmailAddress(String emailAddress) {
|
||||
getInstance().emailAddress = checkValidEmail(emailAddress);
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setRegistryLockEmailAddress(@Nullable String registryLockEmailAddress) {
|
||||
getInstance().registryLockEmailAddress =
|
||||
registryLockEmailAddress == null ? null : checkValidEmail(registryLockEmailAddress);
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setUserRoles(UserRoles userRoles) {
|
||||
checkArgumentNotNull(userRoles, "User roles cannot be null");
|
||||
getInstance().userRoles = userRoles;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B removeRegistryLockPassword() {
|
||||
getInstance().registryLockPasswordHash = null;
|
||||
getInstance().registryLockPasswordSalt = null;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setRegistryLockPassword(String registryLockPassword) {
|
||||
checkArgument(
|
||||
getInstance().hasAnyRegistryLockPermission(), "User has no registry lock permission");
|
||||
checkArgument(
|
||||
!getInstance().hasRegistryLockPassword(), "User already has a password, remove it first");
|
||||
checkArgument(
|
||||
!isNullOrEmpty(registryLockPassword), "Registry lock password was null or empty");
|
||||
byte[] salt = SALT_SUPPLIER.get();
|
||||
getInstance().registryLockPasswordSalt = base64().encode(salt);
|
||||
getInstance().registryLockPasswordHash = hashPassword(registryLockPassword, salt);
|
||||
return thisCastToDerived();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.model.console;
|
||||
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import google.registry.persistence.VKey;
|
||||
import jakarta.persistence.Access;
|
||||
import jakarta.persistence.AccessType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Index;
|
||||
import jakarta.persistence.PostLoad;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
/**
|
||||
* A persisted history object representing an update to a User.
|
||||
*
|
||||
* <p>In addition to the generic history fields (time, URL, etc.) we also persist a copy of the
|
||||
* modified User object at this point in time.
|
||||
*/
|
||||
@Access(AccessType.FIELD)
|
||||
@Entity
|
||||
@Table(indexes = {@Index(columnList = "historyActingUser"), @Index(columnList = "emailAddress")})
|
||||
public class UserUpdateHistory extends ConsoleUpdateHistory {
|
||||
|
||||
UserBase user;
|
||||
|
||||
@Column(nullable = false, name = "emailAddress")
|
||||
String emailAddress;
|
||||
|
||||
public UserBase getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
@PostLoad
|
||||
void postLoad() {
|
||||
user.setEmailAddress(emailAddress);
|
||||
}
|
||||
|
||||
/** Creates a {@link VKey} instance for this entity. */
|
||||
@Override
|
||||
public VKey<UserUpdateHistory> createVKey() {
|
||||
return VKey.create(UserUpdateHistory.class, getRevisionId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder asBuilder() {
|
||||
return new Builder(clone(this));
|
||||
}
|
||||
|
||||
/** Builder for the immutable UserUpdateHistory. */
|
||||
public static class Builder extends ConsoleUpdateHistory.Builder<UserUpdateHistory, Builder> {
|
||||
|
||||
public Builder() {}
|
||||
|
||||
public Builder(UserUpdateHistory instance) {
|
||||
super(instance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserUpdateHistory build() {
|
||||
checkArgumentNotNull(getInstance().user, "User must be specified");
|
||||
return super.build();
|
||||
}
|
||||
|
||||
public Builder setUser(User user) {
|
||||
getInstance().user = user;
|
||||
getInstance().emailAddress = user.getEmailAddress();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -14,17 +14,40 @@
|
||||
|
||||
package google.registry.model.registrar;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static com.google.common.io.BaseEncoding.base64;
|
||||
import static google.registry.model.registrar.Registrar.checkValidEmail;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.CollectionUtils.nullToEmptyImmutableSortedCopy;
|
||||
import static google.registry.util.PasswordUtils.SALT_SUPPLIER;
|
||||
import static google.registry.util.PasswordUtils.hashPassword;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.gson.annotations.Expose;
|
||||
import google.registry.model.Buildable.GenericBuilder;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.registrar.RegistrarPoc.RegistrarPocId;
|
||||
import google.registry.model.JsonMapBuilder;
|
||||
import google.registry.model.Jsonifiable;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import google.registry.persistence.VKey;
|
||||
import jakarta.persistence.Access;
|
||||
import jakarta.persistence.AccessType;
|
||||
import google.registry.util.PasswordUtils;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.IdClass;
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A contact for a Registrar. Note, equality, hashCode and comparable have been overridden to only
|
||||
@@ -35,21 +58,244 @@ import java.io.Serializable;
|
||||
* set to true.
|
||||
*/
|
||||
@Entity
|
||||
@IdClass(RegistrarPocId.class)
|
||||
@Access(AccessType.FIELD)
|
||||
public class RegistrarPoc extends RegistrarPocBase {
|
||||
@IdClass(RegistrarPoc.RegistrarPocId.class)
|
||||
public class RegistrarPoc extends ImmutableObject implements Jsonifiable, UnsafeSerializable {
|
||||
/**
|
||||
* Registrar contacts types for partner communication tracking.
|
||||
*
|
||||
* <p><b>Note:</b> These types only matter to the registry. They are not meant to be used for
|
||||
* WHOIS or RDAP results.
|
||||
*/
|
||||
public enum Type {
|
||||
ABUSE("abuse", true),
|
||||
ADMIN("primary", true),
|
||||
BILLING("billing", true),
|
||||
LEGAL("legal", true),
|
||||
MARKETING("marketing", false),
|
||||
TECH("technical", true),
|
||||
WHOIS("whois-inquiry", true);
|
||||
|
||||
private final String displayName;
|
||||
|
||||
private final boolean required;
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public boolean isRequired() {
|
||||
return required;
|
||||
}
|
||||
|
||||
Type(String display, boolean required) {
|
||||
displayName = display;
|
||||
this.required = required;
|
||||
}
|
||||
}
|
||||
|
||||
/** The name of the contact. */
|
||||
@Expose String name;
|
||||
|
||||
/** The contact email address of the contact. */
|
||||
@Id @Expose String emailAddress;
|
||||
|
||||
@Id @Expose public String registrarId;
|
||||
|
||||
/** External email address of this contact used for registry lock confirmations. */
|
||||
String registryLockEmailAddress;
|
||||
|
||||
/** The voice number of the contact. */
|
||||
@Expose String phoneNumber;
|
||||
|
||||
/** The fax number of the contact. */
|
||||
@Expose String faxNumber;
|
||||
|
||||
/**
|
||||
* Multiple types are used to associate the registrar contact with various mailing groups. This
|
||||
* data is internal to the registry.
|
||||
*/
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Expose
|
||||
Set<Type> types;
|
||||
|
||||
/**
|
||||
* Whether this contact is publicly visible in WHOIS registrar query results as an Admin contact.
|
||||
*/
|
||||
@Column(nullable = false)
|
||||
@Expose
|
||||
boolean visibleInWhoisAsAdmin = false;
|
||||
|
||||
/**
|
||||
* Whether this contact is publicly visible in WHOIS registrar query results as a Technical
|
||||
* contact.
|
||||
*/
|
||||
@Column(nullable = false)
|
||||
@Expose
|
||||
boolean visibleInWhoisAsTech = false;
|
||||
|
||||
/**
|
||||
* Whether this contact's phone number and email address is publicly visible in WHOIS domain query
|
||||
* results as registrar abuse contact info.
|
||||
*/
|
||||
@Column(nullable = false)
|
||||
@Expose
|
||||
boolean visibleInDomainWhoisAsAbuse = false;
|
||||
|
||||
/**
|
||||
* Whether the contact is allowed to set their registry lock password through the registrar
|
||||
* console. This will be set to false on contact creation and when the user sets a password.
|
||||
*/
|
||||
@Column(nullable = false)
|
||||
boolean allowedToSetRegistryLockPassword = false;
|
||||
|
||||
/**
|
||||
* A hashed password that exists iff this contact is registry-lock-enabled. The hash is a base64
|
||||
* encoded SHA256 string.
|
||||
*/
|
||||
String registryLockPasswordHash;
|
||||
|
||||
/** Randomly generated hash salt. */
|
||||
String registryLockPasswordSalt;
|
||||
|
||||
/**
|
||||
* Helper to update the contacts associated with a Registrar. This requires querying for the
|
||||
* existing contacts, deleting existing contacts that are not part of the given {@code contacts}
|
||||
* set, and then saving the given {@code contacts}.
|
||||
*
|
||||
* <p>IMPORTANT NOTE: If you call this method then it is your responsibility to also persist the
|
||||
* relevant Registrar entity with the {@link Registrar#contactsRequireSyncing} field set to true.
|
||||
*/
|
||||
public static void updateContacts(
|
||||
final Registrar registrar, final ImmutableSet<RegistrarPoc> contacts) {
|
||||
ImmutableSet<String> emailAddressesToKeep =
|
||||
contacts.stream().map(RegistrarPoc::getEmailAddress).collect(toImmutableSet());
|
||||
tm().query(
|
||||
"DELETE FROM RegistrarPoc WHERE registrarId = :registrarId AND "
|
||||
+ "emailAddress NOT IN :emailAddressesToKeep")
|
||||
.setParameter("registrarId", registrar.getRegistrarId())
|
||||
.setParameter("emailAddressesToKeep", emailAddressesToKeep)
|
||||
.executeUpdate();
|
||||
|
||||
tm().putAll(contacts);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Id
|
||||
@Access(AccessType.PROPERTY)
|
||||
@Override
|
||||
public String getEmailAddress() {
|
||||
return emailAddress;
|
||||
}
|
||||
|
||||
@Id
|
||||
@Access(AccessType.PROPERTY)
|
||||
public String getRegistrarId() {
|
||||
return registrarId;
|
||||
public Optional<String> getRegistryLockEmailAddress() {
|
||||
return Optional.ofNullable(registryLockEmailAddress);
|
||||
}
|
||||
|
||||
public String getPhoneNumber() {
|
||||
return phoneNumber;
|
||||
}
|
||||
|
||||
public String getFaxNumber() {
|
||||
return faxNumber;
|
||||
}
|
||||
|
||||
public ImmutableSortedSet<Type> getTypes() {
|
||||
return nullToEmptyImmutableSortedCopy(types);
|
||||
}
|
||||
|
||||
public boolean getVisibleInWhoisAsAdmin() {
|
||||
return visibleInWhoisAsAdmin;
|
||||
}
|
||||
|
||||
public boolean getVisibleInWhoisAsTech() {
|
||||
return visibleInWhoisAsTech;
|
||||
}
|
||||
|
||||
public boolean getVisibleInDomainWhoisAsAbuse() {
|
||||
return visibleInDomainWhoisAsAbuse;
|
||||
}
|
||||
|
||||
public Builder<? extends RegistrarPoc, ?> asBuilder() {
|
||||
return new Builder<>(clone(this));
|
||||
}
|
||||
|
||||
public boolean isAllowedToSetRegistryLockPassword() {
|
||||
return allowedToSetRegistryLockPassword;
|
||||
}
|
||||
|
||||
public boolean isRegistryLockAllowed() {
|
||||
return !isNullOrEmpty(registryLockPasswordHash) && !isNullOrEmpty(registryLockPasswordSalt);
|
||||
}
|
||||
|
||||
public boolean verifyRegistryLockPassword(String registryLockPassword) {
|
||||
if (isNullOrEmpty(registryLockPassword)
|
||||
|| isNullOrEmpty(registryLockPasswordSalt)
|
||||
|| isNullOrEmpty(registryLockPasswordHash)) {
|
||||
return false;
|
||||
}
|
||||
return PasswordUtils.verifyPassword(
|
||||
registryLockPassword, registryLockPasswordHash, registryLockPasswordSalt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation that's human friendly.
|
||||
*
|
||||
* <p>The output will look something like this:
|
||||
*
|
||||
* <pre>{@code
|
||||
* Some Person
|
||||
* person@example.com
|
||||
* Tel: +1.2125650666
|
||||
* Types: [ADMIN, WHOIS]
|
||||
* Visible in WHOIS as Admin contact: Yes
|
||||
* Visible in WHOIS as Technical contact: No
|
||||
* Registrar-Console access: Yes
|
||||
* Login Email Address: person@registry.example
|
||||
* }</pre>
|
||||
*/
|
||||
public String toStringMultilinePlainText() {
|
||||
StringBuilder result = new StringBuilder(256);
|
||||
result.append(getName()).append('\n');
|
||||
result.append(getEmailAddress()).append('\n');
|
||||
if (phoneNumber != null) {
|
||||
result.append("Tel: ").append(getPhoneNumber()).append('\n');
|
||||
}
|
||||
if (faxNumber != null) {
|
||||
result.append("Fax: ").append(getFaxNumber()).append('\n');
|
||||
}
|
||||
result.append("Types: ").append(getTypes()).append('\n');
|
||||
result
|
||||
.append("Visible in registrar WHOIS query as Admin contact: ")
|
||||
.append(getVisibleInWhoisAsAdmin() ? "Yes" : "No")
|
||||
.append('\n');
|
||||
result
|
||||
.append("Visible in registrar WHOIS query as Technical contact: ")
|
||||
.append(getVisibleInWhoisAsTech() ? "Yes" : "No")
|
||||
.append('\n');
|
||||
result
|
||||
.append(
|
||||
"Phone number and email visible in domain WHOIS query as "
|
||||
+ "Registrar Abuse contact info: ")
|
||||
.append(getVisibleInDomainWhoisAsAbuse() ? "Yes" : "No")
|
||||
.append('\n');
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> toJsonMap() {
|
||||
return new JsonMapBuilder()
|
||||
.put("name", name)
|
||||
.put("emailAddress", emailAddress)
|
||||
.put("registryLockEmailAddress", registryLockEmailAddress)
|
||||
.put("phoneNumber", phoneNumber)
|
||||
.put("faxNumber", faxNumber)
|
||||
.put("types", getTypes().stream().map(Object::toString).collect(joining(",")))
|
||||
.put("visibleInWhoisAsAdmin", visibleInWhoisAsAdmin)
|
||||
.put("visibleInWhoisAsTech", visibleInWhoisAsTech)
|
||||
.put("visibleInDomainWhoisAsAbuse", visibleInDomainWhoisAsAbuse)
|
||||
.put("allowedToSetRegistryLockPassword", allowedToSetRegistryLockPassword)
|
||||
.put("registryLockAllowed", isRegistryLockAllowed())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -57,9 +303,124 @@ public class RegistrarPoc extends RegistrarPocBase {
|
||||
return VKey.create(RegistrarPoc.class, new RegistrarPocId(emailAddress, registrarId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder asBuilder() {
|
||||
return new Builder(clone(this));
|
||||
/**
|
||||
* These methods set the email address and registrar ID
|
||||
*
|
||||
* <p>This should only be used for restoring the fields of an object being loaded in a PostLoad
|
||||
* method (effectively, when it is still under construction by Hibernate). In all other cases, the
|
||||
* object should be regarded as immutable and changes should go through a Builder.
|
||||
*
|
||||
* <p>In addition to this special case use, this method must exist to satisfy Hibernate.
|
||||
*/
|
||||
public void setEmailAddress(String emailAddress) {
|
||||
this.emailAddress = emailAddress;
|
||||
}
|
||||
|
||||
public void setRegistrarId(String registrarId) {
|
||||
this.registrarId = registrarId;
|
||||
}
|
||||
|
||||
/** A builder for constructing a {@link RegistrarPoc}, since it is immutable. */
|
||||
public static class Builder<T extends RegistrarPoc, B extends Builder<T, B>>
|
||||
extends GenericBuilder<T, B> {
|
||||
public Builder() {}
|
||||
|
||||
protected Builder(T instance) {
|
||||
super(instance);
|
||||
}
|
||||
|
||||
/** Build the registrar, nullifying empty fields. */
|
||||
@Override
|
||||
public T build() {
|
||||
checkNotNull(getInstance().registrarId, "Registrar ID cannot be null");
|
||||
checkValidEmail(getInstance().emailAddress);
|
||||
// Check allowedToSetRegistryLockPassword here because if we want to allow the user to set
|
||||
// a registry lock password, we must also set up the correct registry lock email concurrently
|
||||
// or beforehand.
|
||||
if (getInstance().allowedToSetRegistryLockPassword) {
|
||||
checkArgument(
|
||||
!isNullOrEmpty(getInstance().registryLockEmailAddress),
|
||||
"Registry lock email must not be null if allowing registry lock access");
|
||||
}
|
||||
return cloneEmptyToNull(super.build());
|
||||
}
|
||||
|
||||
public B setName(String name) {
|
||||
getInstance().name = name;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setEmailAddress(String emailAddress) {
|
||||
getInstance().emailAddress = emailAddress;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setRegistryLockEmailAddress(@Nullable String registryLockEmailAddress) {
|
||||
getInstance().registryLockEmailAddress = registryLockEmailAddress;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setPhoneNumber(String phoneNumber) {
|
||||
getInstance().phoneNumber = phoneNumber;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setRegistrarId(String registrarId) {
|
||||
getInstance().registrarId = registrarId;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setRegistrar(Registrar registrar) {
|
||||
getInstance().registrarId = registrar.getRegistrarId();
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setFaxNumber(String faxNumber) {
|
||||
getInstance().faxNumber = faxNumber;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setTypes(Iterable<Type> types) {
|
||||
getInstance().types = ImmutableSet.copyOf(types);
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setVisibleInWhoisAsAdmin(boolean visible) {
|
||||
getInstance().visibleInWhoisAsAdmin = visible;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setVisibleInWhoisAsTech(boolean visible) {
|
||||
getInstance().visibleInWhoisAsTech = visible;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setVisibleInDomainWhoisAsAbuse(boolean visible) {
|
||||
getInstance().visibleInDomainWhoisAsAbuse = visible;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setAllowedToSetRegistryLockPassword(boolean allowedToSetRegistryLockPassword) {
|
||||
if (allowedToSetRegistryLockPassword) {
|
||||
getInstance().registryLockPasswordSalt = null;
|
||||
getInstance().registryLockPasswordHash = null;
|
||||
}
|
||||
getInstance().allowedToSetRegistryLockPassword = allowedToSetRegistryLockPassword;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setRegistryLockPassword(String registryLockPassword) {
|
||||
checkArgument(
|
||||
getInstance().allowedToSetRegistryLockPassword,
|
||||
"Not allowed to set registry lock password for this contact");
|
||||
checkArgument(
|
||||
!isNullOrEmpty(registryLockPassword), "Registry lock password was null or empty");
|
||||
byte[] salt = SALT_SUPPLIER.get();
|
||||
getInstance().registryLockPasswordSalt = base64().encode(salt);
|
||||
getInstance().registryLockPasswordHash = hashPassword(registryLockPassword, salt);
|
||||
getInstance().allowedToSetRegistryLockPassword = false;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
}
|
||||
|
||||
/** Class to represent the composite primary key for {@link RegistrarPoc} entity. */
|
||||
@@ -90,13 +451,4 @@ public class RegistrarPoc extends RegistrarPocBase {
|
||||
return registrarId;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Builder extends RegistrarPocBase.Builder<RegistrarPoc, Builder> {
|
||||
|
||||
public Builder() {}
|
||||
|
||||
public Builder(RegistrarPoc registrarPoc) {
|
||||
super(registrarPoc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,425 +0,0 @@
|
||||
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.model.registrar;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static com.google.common.io.BaseEncoding.base64;
|
||||
import static google.registry.model.registrar.RegistrarBase.checkValidEmail;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.CollectionUtils.nullToEmptyImmutableSortedCopy;
|
||||
import static google.registry.util.PasswordUtils.SALT_SUPPLIER;
|
||||
import static google.registry.util.PasswordUtils.hashPassword;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.gson.annotations.Expose;
|
||||
import google.registry.model.Buildable.GenericBuilder;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.JsonMapBuilder;
|
||||
import google.registry.model.Jsonifiable;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import google.registry.util.PasswordUtils;
|
||||
import jakarta.persistence.Access;
|
||||
import jakarta.persistence.AccessType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Embeddable;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.MappedSuperclass;
|
||||
import jakarta.persistence.Transient;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A contact for a Registrar. Note, equality, hashCode and comparable have been overridden to only
|
||||
* enable key equality.
|
||||
*
|
||||
* <p>IMPORTANT NOTE: Any time that you change, update, or delete RegistrarContact entities, you
|
||||
* *MUST* also modify the persisted Registrar entity with {@link Registrar#contactsRequireSyncing}
|
||||
* set to true.
|
||||
*
|
||||
* <p>This class deliberately does not include an {@link 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.
|
||||
*/
|
||||
@Access(AccessType.FIELD)
|
||||
@Embeddable
|
||||
@MappedSuperclass
|
||||
public class RegistrarPocBase extends ImmutableObject implements Jsonifiable, UnsafeSerializable {
|
||||
/**
|
||||
* Registrar contacts types for partner communication tracking.
|
||||
*
|
||||
* <p><b>Note:</b> These types only matter to the registry. They are not meant to be used for
|
||||
* WHOIS or RDAP results.
|
||||
*/
|
||||
public enum Type {
|
||||
ABUSE("abuse", true),
|
||||
ADMIN("primary", true),
|
||||
BILLING("billing", true),
|
||||
LEGAL("legal", true),
|
||||
MARKETING("marketing", false),
|
||||
TECH("technical", true),
|
||||
WHOIS("whois-inquiry", true);
|
||||
|
||||
private final String displayName;
|
||||
|
||||
private final boolean required;
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public boolean isRequired() {
|
||||
return required;
|
||||
}
|
||||
|
||||
Type(String display, boolean required) {
|
||||
displayName = display;
|
||||
this.required = required;
|
||||
}
|
||||
}
|
||||
|
||||
/** The name of the contact. */
|
||||
@Expose String name;
|
||||
|
||||
/** The contact email address of the contact. */
|
||||
@Expose @Transient String emailAddress;
|
||||
|
||||
@Expose @Transient public String registrarId;
|
||||
|
||||
/** External email address of this contact used for registry lock confirmations. */
|
||||
String registryLockEmailAddress;
|
||||
|
||||
/** The voice number of the contact. */
|
||||
@Expose String phoneNumber;
|
||||
|
||||
/** The fax number of the contact. */
|
||||
@Expose String faxNumber;
|
||||
|
||||
/**
|
||||
* Multiple types are used to associate the registrar contact with various mailing groups. This
|
||||
* data is internal to the registry.
|
||||
*/
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Expose
|
||||
Set<Type> types;
|
||||
|
||||
/**
|
||||
* Whether this contact is publicly visible in WHOIS registrar query results as an Admin contact.
|
||||
*/
|
||||
@Column(nullable = false)
|
||||
@Expose
|
||||
boolean visibleInWhoisAsAdmin = false;
|
||||
|
||||
/**
|
||||
* Whether this contact is publicly visible in WHOIS registrar query results as a Technical
|
||||
* contact.
|
||||
*/
|
||||
@Column(nullable = false)
|
||||
@Expose
|
||||
boolean visibleInWhoisAsTech = false;
|
||||
|
||||
/**
|
||||
* Whether this contact's phone number and email address is publicly visible in WHOIS domain query
|
||||
* results as registrar abuse contact info.
|
||||
*/
|
||||
@Column(nullable = false)
|
||||
@Expose
|
||||
boolean visibleInDomainWhoisAsAbuse = false;
|
||||
|
||||
/**
|
||||
* Whether the contact is allowed to set their registry lock password through the registrar
|
||||
* console. This will be set to false on contact creation and when the user sets a password.
|
||||
*/
|
||||
@Column(nullable = false)
|
||||
boolean allowedToSetRegistryLockPassword = false;
|
||||
|
||||
/**
|
||||
* A hashed password that exists iff this contact is registry-lock-enabled. The hash is a base64
|
||||
* encoded SHA256 string.
|
||||
*/
|
||||
String registryLockPasswordHash;
|
||||
|
||||
/** Randomly generated hash salt. */
|
||||
String registryLockPasswordSalt;
|
||||
|
||||
/**
|
||||
* Helper to update the contacts associated with a Registrar. This requires querying for the
|
||||
* existing contacts, deleting existing contacts that are not part of the given {@code contacts}
|
||||
* set, and then saving the given {@code contacts}.
|
||||
*
|
||||
* <p>IMPORTANT NOTE: If you call this method then it is your responsibility to also persist the
|
||||
* relevant Registrar entity with the {@link Registrar#contactsRequireSyncing} field set to true.
|
||||
*/
|
||||
public static void updateContacts(
|
||||
final Registrar registrar, final ImmutableSet<RegistrarPoc> contacts) {
|
||||
ImmutableSet<String> emailAddressesToKeep =
|
||||
contacts.stream().map(RegistrarPoc::getEmailAddress).collect(toImmutableSet());
|
||||
tm().query(
|
||||
"DELETE FROM RegistrarPoc WHERE registrarId = :registrarId AND "
|
||||
+ "emailAddress NOT IN :emailAddressesToKeep")
|
||||
.setParameter("registrarId", registrar.getRegistrarId())
|
||||
.setParameter("emailAddressesToKeep", emailAddressesToKeep)
|
||||
.executeUpdate();
|
||||
|
||||
tm().putAll(contacts);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getEmailAddress() {
|
||||
return emailAddress;
|
||||
}
|
||||
|
||||
public Optional<String> getRegistryLockEmailAddress() {
|
||||
return Optional.ofNullable(registryLockEmailAddress);
|
||||
}
|
||||
|
||||
public String getPhoneNumber() {
|
||||
return phoneNumber;
|
||||
}
|
||||
|
||||
public String getFaxNumber() {
|
||||
return faxNumber;
|
||||
}
|
||||
|
||||
public ImmutableSortedSet<Type> getTypes() {
|
||||
return nullToEmptyImmutableSortedCopy(types);
|
||||
}
|
||||
|
||||
public boolean getVisibleInWhoisAsAdmin() {
|
||||
return visibleInWhoisAsAdmin;
|
||||
}
|
||||
|
||||
public boolean getVisibleInWhoisAsTech() {
|
||||
return visibleInWhoisAsTech;
|
||||
}
|
||||
|
||||
public boolean getVisibleInDomainWhoisAsAbuse() {
|
||||
return visibleInDomainWhoisAsAbuse;
|
||||
}
|
||||
|
||||
public Builder<? extends RegistrarPocBase, ?> asBuilder() {
|
||||
return new Builder<>(clone(this));
|
||||
}
|
||||
|
||||
public boolean isAllowedToSetRegistryLockPassword() {
|
||||
return allowedToSetRegistryLockPassword;
|
||||
}
|
||||
|
||||
public boolean isRegistryLockAllowed() {
|
||||
return !isNullOrEmpty(registryLockPasswordHash) && !isNullOrEmpty(registryLockPasswordSalt);
|
||||
}
|
||||
|
||||
public boolean verifyRegistryLockPassword(String registryLockPassword) {
|
||||
if (isNullOrEmpty(registryLockPassword)
|
||||
|| isNullOrEmpty(registryLockPasswordSalt)
|
||||
|| isNullOrEmpty(registryLockPasswordHash)) {
|
||||
return false;
|
||||
}
|
||||
return PasswordUtils.verifyPassword(
|
||||
registryLockPassword, registryLockPasswordHash, registryLockPasswordSalt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation that's human friendly.
|
||||
*
|
||||
* <p>The output will look something like this:
|
||||
*
|
||||
* <pre>{@code
|
||||
* Some Person
|
||||
* person@example.com
|
||||
* Tel: +1.2125650666
|
||||
* Types: [ADMIN, WHOIS]
|
||||
* Visible in WHOIS as Admin contact: Yes
|
||||
* Visible in WHOIS as Technical contact: No
|
||||
* Registrar-Console access: Yes
|
||||
* Login Email Address: person@registry.example
|
||||
* }</pre>
|
||||
*/
|
||||
public String toStringMultilinePlainText() {
|
||||
StringBuilder result = new StringBuilder(256);
|
||||
result.append(getName()).append('\n');
|
||||
result.append(getEmailAddress()).append('\n');
|
||||
if (phoneNumber != null) {
|
||||
result.append("Tel: ").append(getPhoneNumber()).append('\n');
|
||||
}
|
||||
if (faxNumber != null) {
|
||||
result.append("Fax: ").append(getFaxNumber()).append('\n');
|
||||
}
|
||||
result.append("Types: ").append(getTypes()).append('\n');
|
||||
result
|
||||
.append("Visible in registrar WHOIS query as Admin contact: ")
|
||||
.append(getVisibleInWhoisAsAdmin() ? "Yes" : "No")
|
||||
.append('\n');
|
||||
result
|
||||
.append("Visible in registrar WHOIS query as Technical contact: ")
|
||||
.append(getVisibleInWhoisAsTech() ? "Yes" : "No")
|
||||
.append('\n');
|
||||
result
|
||||
.append(
|
||||
"Phone number and email visible in domain WHOIS query as "
|
||||
+ "Registrar Abuse contact info: ")
|
||||
.append(getVisibleInDomainWhoisAsAbuse() ? "Yes" : "No")
|
||||
.append('\n');
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> toJsonMap() {
|
||||
return new JsonMapBuilder()
|
||||
.put("name", name)
|
||||
.put("emailAddress", emailAddress)
|
||||
.put("registryLockEmailAddress", registryLockEmailAddress)
|
||||
.put("phoneNumber", phoneNumber)
|
||||
.put("faxNumber", faxNumber)
|
||||
.put("types", getTypes().stream().map(Object::toString).collect(joining(",")))
|
||||
.put("visibleInWhoisAsAdmin", visibleInWhoisAsAdmin)
|
||||
.put("visibleInWhoisAsTech", visibleInWhoisAsTech)
|
||||
.put("visibleInDomainWhoisAsAbuse", visibleInDomainWhoisAsAbuse)
|
||||
.put("allowedToSetRegistryLockPassword", allowedToSetRegistryLockPassword)
|
||||
.put("registryLockAllowed", isRegistryLockAllowed())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* These methods set the email address and registrar ID
|
||||
*
|
||||
* <p>This should only be used for restoring the fields of an object being loaded in a PostLoad
|
||||
* method (effectively, when it is still under construction by Hibernate). In all other cases, the
|
||||
* object should be regarded as immutable and changes should go through a Builder.
|
||||
*
|
||||
* <p>In addition to this special case use, this method must exist to satisfy Hibernate.
|
||||
*/
|
||||
public void setEmailAddress(String emailAddress) {
|
||||
this.emailAddress = emailAddress;
|
||||
}
|
||||
|
||||
public void setRegistrarId(String registrarId) {
|
||||
this.registrarId = registrarId;
|
||||
}
|
||||
|
||||
/** A builder for constructing a {@link RegistrarPoc}, since it is immutable. */
|
||||
public static class Builder<T extends RegistrarPocBase, B extends Builder<T, B>>
|
||||
extends GenericBuilder<T, B> {
|
||||
public Builder() {}
|
||||
|
||||
protected Builder(T instance) {
|
||||
super(instance);
|
||||
}
|
||||
|
||||
/** Build the registrar, nullifying empty fields. */
|
||||
@Override
|
||||
public T build() {
|
||||
checkNotNull(getInstance().registrarId, "Registrar ID cannot be null");
|
||||
checkValidEmail(getInstance().emailAddress);
|
||||
// Check allowedToSetRegistryLockPassword here because if we want to allow the user to set
|
||||
// a registry lock password, we must also set up the correct registry lock email concurrently
|
||||
// or beforehand.
|
||||
if (getInstance().allowedToSetRegistryLockPassword) {
|
||||
checkArgument(
|
||||
!isNullOrEmpty(getInstance().registryLockEmailAddress),
|
||||
"Registry lock email must not be null if allowing registry lock access");
|
||||
}
|
||||
return cloneEmptyToNull(super.build());
|
||||
}
|
||||
|
||||
public B setName(String name) {
|
||||
getInstance().name = name;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setEmailAddress(String emailAddress) {
|
||||
getInstance().emailAddress = emailAddress;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setRegistryLockEmailAddress(@Nullable String registryLockEmailAddress) {
|
||||
getInstance().registryLockEmailAddress = registryLockEmailAddress;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setPhoneNumber(String phoneNumber) {
|
||||
getInstance().phoneNumber = phoneNumber;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setRegistrarId(String registrarId) {
|
||||
getInstance().registrarId = registrarId;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setRegistrar(Registrar registrar) {
|
||||
getInstance().registrarId = registrar.getRegistrarId();
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setFaxNumber(String faxNumber) {
|
||||
getInstance().faxNumber = faxNumber;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setTypes(Iterable<Type> types) {
|
||||
getInstance().types = ImmutableSet.copyOf(types);
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setVisibleInWhoisAsAdmin(boolean visible) {
|
||||
getInstance().visibleInWhoisAsAdmin = visible;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setVisibleInWhoisAsTech(boolean visible) {
|
||||
getInstance().visibleInWhoisAsTech = visible;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setVisibleInDomainWhoisAsAbuse(boolean visible) {
|
||||
getInstance().visibleInDomainWhoisAsAbuse = visible;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setAllowedToSetRegistryLockPassword(boolean allowedToSetRegistryLockPassword) {
|
||||
if (allowedToSetRegistryLockPassword) {
|
||||
getInstance().registryLockPasswordSalt = null;
|
||||
getInstance().registryLockPasswordHash = null;
|
||||
}
|
||||
getInstance().allowedToSetRegistryLockPassword = allowedToSetRegistryLockPassword;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setRegistryLockPassword(String registryLockPassword) {
|
||||
checkArgument(
|
||||
getInstance().allowedToSetRegistryLockPassword,
|
||||
"Not allowed to set registry lock password for this contact");
|
||||
checkArgument(
|
||||
!isNullOrEmpty(registryLockPassword), "Registry lock password was null or empty");
|
||||
byte[] salt = SALT_SUPPLIER.get();
|
||||
getInstance().registryLockPasswordSalt = base64().encode(salt);
|
||||
getInstance().registryLockPasswordHash = hashPassword(registryLockPassword, salt);
|
||||
getInstance().allowedToSetRegistryLockPassword = false;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.module;
|
||||
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Action.GkeService;
|
||||
import google.registry.request.auth.Auth;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
public class ReadinessProbeAction implements Runnable {
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
private final HttpServletResponse rsp;
|
||||
|
||||
public ReadinessProbeAction(HttpServletResponse rsp) {
|
||||
this.rsp = rsp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the readiness check.
|
||||
*
|
||||
* <p>Performs a simple database query and sets the HTTP response status to OK (200) upon
|
||||
* successful completion. Throws a runtime exception if the database query fails.
|
||||
*/
|
||||
@Override
|
||||
public final void run() {
|
||||
logger.atInfo().log("Performing readiness check database query...");
|
||||
try {
|
||||
tm().transact(() -> tm().query("SELECT version()", Void.class));
|
||||
rsp.setStatus(SC_OK);
|
||||
logger.atInfo().log("Readiness check successful.");
|
||||
} catch (Exception e) {
|
||||
logger.atWarning().withCause(e).log("Readiness check failed:");
|
||||
throw new RuntimeException("Readiness check failed during database query", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Action(
|
||||
service = GaeService.DEFAULT,
|
||||
gkeService = GkeService.CONSOLE,
|
||||
path = ReadinessProbeConsoleAction.PATH,
|
||||
auth = Auth.AUTH_PUBLIC)
|
||||
public static class ReadinessProbeConsoleAction extends ReadinessProbeAction {
|
||||
public static final String PATH = "/ready/console";
|
||||
|
||||
@Inject
|
||||
public ReadinessProbeConsoleAction(HttpServletResponse rsp) {
|
||||
super(rsp);
|
||||
}
|
||||
}
|
||||
|
||||
@Action(
|
||||
service = GaeService.PUBAPI,
|
||||
gkeService = GkeService.PUBAPI,
|
||||
path = ReadinessProbeActionPubApi.PATH,
|
||||
auth = Auth.AUTH_PUBLIC)
|
||||
public static class ReadinessProbeActionPubApi extends ReadinessProbeAction {
|
||||
public static final String PATH = "/ready/pubapi";
|
||||
|
||||
@Inject
|
||||
public ReadinessProbeActionPubApi(HttpServletResponse rsp) {
|
||||
super(rsp);
|
||||
}
|
||||
}
|
||||
|
||||
@Action(
|
||||
service = GaeService.DEFAULT,
|
||||
gkeService = GkeService.FRONTEND,
|
||||
path = ReadinessProbeActionFrontend.PATH,
|
||||
auth = Auth.AUTH_PUBLIC)
|
||||
public static final class ReadinessProbeActionFrontend extends ReadinessProbeAction {
|
||||
public static final String PATH = "/ready/frontend";
|
||||
|
||||
@Inject
|
||||
public ReadinessProbeActionFrontend(HttpServletResponse rsp) {
|
||||
super(rsp);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,10 +58,14 @@ import google.registry.flows.TlsCredentials.EppTlsModule;
|
||||
import google.registry.flows.custom.CustomLogicModule;
|
||||
import google.registry.loadtest.LoadTestAction;
|
||||
import google.registry.loadtest.LoadTestModule;
|
||||
import google.registry.module.ReadinessProbeAction.ReadinessProbeActionFrontend;
|
||||
import google.registry.module.ReadinessProbeAction.ReadinessProbeActionPubApi;
|
||||
import google.registry.module.ReadinessProbeAction.ReadinessProbeConsoleAction;
|
||||
import google.registry.monitoring.whitebox.WhiteboxModule;
|
||||
import google.registry.rdap.RdapAutnumAction;
|
||||
import google.registry.rdap.RdapDomainAction;
|
||||
import google.registry.rdap.RdapDomainSearchAction;
|
||||
import google.registry.rdap.RdapEmptyAction;
|
||||
import google.registry.rdap.RdapEntityAction;
|
||||
import google.registry.rdap.RdapEntitySearchAction;
|
||||
import google.registry.rdap.RdapHelpAction;
|
||||
@@ -123,8 +127,8 @@ import google.registry.ui.server.console.ConsoleUsersAction;
|
||||
import google.registry.ui.server.console.RegistrarsAction;
|
||||
import google.registry.ui.server.console.domains.ConsoleBulkDomainAction;
|
||||
import google.registry.ui.server.console.settings.ContactAction;
|
||||
import google.registry.ui.server.console.settings.RdapRegistrarFieldsAction;
|
||||
import google.registry.ui.server.console.settings.SecurityAction;
|
||||
import google.registry.ui.server.console.settings.WhoisRegistrarFieldsAction;
|
||||
import google.registry.whois.WhoisAction;
|
||||
import google.registry.whois.WhoisHttpAction;
|
||||
import google.registry.whois.WhoisModule;
|
||||
@@ -255,12 +259,20 @@ interface RequestComponent {
|
||||
|
||||
PublishSpec11ReportAction publishSpec11ReportAction();
|
||||
|
||||
ReadinessProbeConsoleAction readinessProbeConsoleAction();
|
||||
|
||||
ReadinessProbeActionPubApi readinessProbeActionPubApi();
|
||||
|
||||
ReadinessProbeActionFrontend readinessProbeActionFrontend();
|
||||
|
||||
RdapAutnumAction rdapAutnumAction();
|
||||
|
||||
RdapDomainAction rdapDomainAction();
|
||||
|
||||
RdapDomainSearchAction rdapDomainSearchAction();
|
||||
|
||||
RdapEmptyAction rdapEmptyAction();
|
||||
|
||||
RdapEntityAction rdapEntityAction();
|
||||
|
||||
RdapEntitySearchAction rdapEntitySearchAction();
|
||||
@@ -325,7 +337,7 @@ interface RequestComponent {
|
||||
|
||||
WhoisHttpAction whoisHttpAction();
|
||||
|
||||
WhoisRegistrarFieldsAction whoisRegistrarFieldsAction();
|
||||
RdapRegistrarFieldsAction rdapRegistrarFieldsAction();
|
||||
|
||||
WipeOutContactHistoryPiiAction wipeOutContactHistoryPiiAction();
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ import google.registry.dns.DnsModule;
|
||||
import google.registry.flows.EppTlsAction;
|
||||
import google.registry.flows.FlowComponent;
|
||||
import google.registry.flows.TlsCredentials.EppTlsModule;
|
||||
import google.registry.module.ReadinessProbeAction.ReadinessProbeActionFrontend;
|
||||
import google.registry.module.ReadinessProbeAction.ReadinessProbeConsoleAction;
|
||||
import google.registry.monitoring.whitebox.WhiteboxModule;
|
||||
import google.registry.request.RequestComponentBuilder;
|
||||
import google.registry.request.RequestModule;
|
||||
@@ -39,8 +41,8 @@ import google.registry.ui.server.console.ConsoleUsersAction;
|
||||
import google.registry.ui.server.console.RegistrarsAction;
|
||||
import google.registry.ui.server.console.domains.ConsoleBulkDomainAction;
|
||||
import google.registry.ui.server.console.settings.ContactAction;
|
||||
import google.registry.ui.server.console.settings.RdapRegistrarFieldsAction;
|
||||
import google.registry.ui.server.console.settings.SecurityAction;
|
||||
import google.registry.ui.server.console.settings.WhoisRegistrarFieldsAction;
|
||||
|
||||
/** Dagger component with per-request lifetime for "default" App Engine module. */
|
||||
@RequestScope
|
||||
@@ -82,11 +84,15 @@ public interface FrontendRequestComponent {
|
||||
|
||||
FlowComponent.Builder flowComponentBuilder();
|
||||
|
||||
ReadinessProbeActionFrontend readinessProbeActionFrontend();
|
||||
|
||||
ReadinessProbeConsoleAction readinessProbeConsoleAction();
|
||||
|
||||
RegistrarsAction registrarsAction();
|
||||
|
||||
SecurityAction securityAction();
|
||||
|
||||
WhoisRegistrarFieldsAction whoisRegistrarFieldsAction();
|
||||
RdapRegistrarFieldsAction rdapRegistrarFieldsAction();
|
||||
|
||||
@Subcomponent.Builder
|
||||
abstract class Builder implements RequestComponentBuilder<FrontendRequestComponent> {
|
||||
|
||||
@@ -20,10 +20,12 @@ import google.registry.dns.DnsModule;
|
||||
import google.registry.flows.CheckApiAction;
|
||||
import google.registry.flows.CheckApiAction.CheckApiModule;
|
||||
import google.registry.flows.TlsCredentials.EppTlsModule;
|
||||
import google.registry.module.ReadinessProbeAction.ReadinessProbeActionPubApi;
|
||||
import google.registry.monitoring.whitebox.WhiteboxModule;
|
||||
import google.registry.rdap.RdapAutnumAction;
|
||||
import google.registry.rdap.RdapDomainAction;
|
||||
import google.registry.rdap.RdapDomainSearchAction;
|
||||
import google.registry.rdap.RdapEmptyAction;
|
||||
import google.registry.rdap.RdapEntityAction;
|
||||
import google.registry.rdap.RdapEntitySearchAction;
|
||||
import google.registry.rdap.RdapHelpAction;
|
||||
@@ -55,12 +57,16 @@ public interface PubApiRequestComponent {
|
||||
RdapAutnumAction rdapAutnumAction();
|
||||
RdapDomainAction rdapDomainAction();
|
||||
RdapDomainSearchAction rdapDomainSearchAction();
|
||||
RdapEmptyAction rdapEmptyAction();
|
||||
RdapEntityAction rdapEntityAction();
|
||||
RdapEntitySearchAction rdapEntitySearchAction();
|
||||
RdapHelpAction rdapHelpAction();
|
||||
RdapIpAction rdapDefaultAction();
|
||||
RdapNameserverAction rdapNameserverAction();
|
||||
RdapNameserverSearchAction rdapNameserverSearchAction();
|
||||
|
||||
ReadinessProbeActionPubApi readinessProbeActionPubApi();
|
||||
|
||||
WhoisHttpAction whoisHttpAction();
|
||||
WhoisAction whoisAction();
|
||||
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@ import org.joda.time.DateTime;
|
||||
public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
private static final Retrier retrier = new Retrier(new SystemSleeper(), 3);
|
||||
private static final Retrier retrier = new Retrier(new SystemSleeper(), 6);
|
||||
private static final String NESTED_TRANSACTION_MESSAGE =
|
||||
"Nested transaction detected. Try refactoring to avoid nested transactions. If unachievable,"
|
||||
+ " use reTransact() in nested transactions";
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.rdap;
|
||||
|
||||
import static google.registry.request.Action.Method.GET;
|
||||
import static google.registry.request.Action.Method.HEAD;
|
||||
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
import jakarta.inject.Inject;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* RDAP action that serves the empty string, redirecting to the help page.
|
||||
*
|
||||
* <p>This isn't technically required, but if someone requests the base url it seems nice to give
|
||||
* them the help response.
|
||||
*/
|
||||
@Action(
|
||||
service = Action.GaeService.PUBAPI,
|
||||
path = "/rdap/",
|
||||
method = {GET, HEAD},
|
||||
auth = Auth.AUTH_PUBLIC)
|
||||
public class RdapEmptyAction implements Runnable {
|
||||
|
||||
private final Response response;
|
||||
|
||||
@Inject
|
||||
public RdapEmptyAction(Response response) {
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
response.sendRedirect(RdapHelpAction.PATH);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,12 +31,14 @@ import java.util.Optional;
|
||||
/** RDAP (new WHOIS) action for help requests. */
|
||||
@Action(
|
||||
service = GaeService.PUBAPI,
|
||||
path = "/rdap/help",
|
||||
path = RdapHelpAction.PATH,
|
||||
method = {GET, HEAD},
|
||||
isPrefix = true,
|
||||
auth = Auth.AUTH_PUBLIC)
|
||||
public class RdapHelpAction extends RdapActionBase {
|
||||
|
||||
public static final String PATH = "/rdap/help";
|
||||
|
||||
/** The help path for the RDAP terms of service. */
|
||||
public static final String TOS_PATH = "/tos";
|
||||
|
||||
|
||||
@@ -19,9 +19,8 @@ import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.Registrar.State;
|
||||
import google.registry.model.registrar.RegistrarAddress;
|
||||
import google.registry.model.registrar.RegistrarBase;
|
||||
import google.registry.model.registrar.RegistrarBase.State;
|
||||
import google.registry.xjc.contact.XjcContactE164Type;
|
||||
import google.registry.xjc.rderegistrar.XjcRdeRegistrar;
|
||||
import google.registry.xjc.rderegistrar.XjcRdeRegistrarAddrType;
|
||||
@@ -41,7 +40,7 @@ final class RegistrarToXjcConverter {
|
||||
private static final String UNKNOWN_CC = "US";
|
||||
|
||||
/** A conversion map between internal Registrar states and external RDE states. */
|
||||
private static final ImmutableMap<RegistrarBase.State, XjcRdeRegistrarStatusType>
|
||||
private static final ImmutableMap<Registrar.State, XjcRdeRegistrarStatusType>
|
||||
REGISTRAR_STATUS_CONVERSIONS =
|
||||
ImmutableMap.of(
|
||||
State.ACTIVE, XjcRdeRegistrarStatusType.OK,
|
||||
|
||||
@@ -85,9 +85,6 @@ public final class ActivityReportingQueryBuilder implements QueryBuilder {
|
||||
String monthlyLogsQuery =
|
||||
SqlTemplate.create(getQueryFromFile(MONTHLY_LOGS + ".sql"))
|
||||
.put("PROJECT_ID", projectId)
|
||||
.put("APPENGINE_LOGS_DATA_SET", "appengine_logs")
|
||||
.put("GKE_LOGS_DATA_SET", "gke_logs")
|
||||
.put("REQUEST_TABLE", "appengine_googleapis_com_request_log_")
|
||||
.put("FIRST_DAY_OF_MONTH", logTableFormatter.print(firstDayOfMonth))
|
||||
.put("LAST_DAY_OF_MONTH", logTableFormatter.print(lastDayOfMonth))
|
||||
.build();
|
||||
@@ -96,9 +93,6 @@ public final class ActivityReportingQueryBuilder implements QueryBuilder {
|
||||
String eppQuery =
|
||||
SqlTemplate.create(getQueryFromFile(EPP_METRICS + ".sql"))
|
||||
.put("PROJECT_ID", projectId)
|
||||
.put("APPENGINE_LOGS_DATA_SET", "appengine_logs")
|
||||
.put("GKE_LOGS_DATA_SET", "gke_logs")
|
||||
.put("APP_LOGS_TABLE", "_var_log_app_")
|
||||
.put("FIRST_DAY_OF_MONTH", logTableFormatter.print(firstDayOfMonth))
|
||||
.put("LAST_DAY_OF_MONTH", logTableFormatter.print(lastDayOfMonth))
|
||||
.build();
|
||||
|
||||
-3
@@ -112,9 +112,6 @@ public final class TransactionsReportingQueryBuilder implements QueryBuilder {
|
||||
String attemptedAddsQuery =
|
||||
SqlTemplate.create(getQueryFromFile(ATTEMPTED_ADDS + ".sql"))
|
||||
.put("PROJECT_ID", projectId)
|
||||
.put("APPENGINE_LOGS_DATA_SET", "appengine_logs")
|
||||
.put("GKE_LOGS_DATA_SET", "gke_logs")
|
||||
.put("APP_LOGS_TABLE", "_var_log_app_")
|
||||
.put("FIRST_DAY_OF_MONTH", logTableFormatter.print(earliestReportTime))
|
||||
.put("LAST_DAY_OF_MONTH", logTableFormatter.print(latestReportTime))
|
||||
.build();
|
||||
|
||||
@@ -143,8 +143,11 @@ public class RequestHandler<C> {
|
||||
GkeService service = Action.ServiceGetter.get(route.get().action());
|
||||
String expectedDomain = RegistryConfig.getServiceUrl(service).getHost();
|
||||
String actualDomain = req.getServerName();
|
||||
// If the hostname is "localhost", it must have come from the sidecar proxy.
|
||||
if (!Objects.equals("localhost", actualDomain)
|
||||
// If the request doesn't come from GKE readiness prober
|
||||
String maybeUserAgent = Optional.ofNullable(req.getHeader("User-Agent")).orElse("");
|
||||
if (!maybeUserAgent.startsWith("kube-probe")
|
||||
// If the hostname is "localhost", it must have come from the sidecar proxy.
|
||||
&& !Objects.equals("localhost", actualDomain)
|
||||
&& !Objects.equals(actualDomain, expectedDomain)) {
|
||||
logger.atWarning().log(
|
||||
"Actual domain %s does not match expected domain %s", actualDomain, expectedDomain);
|
||||
|
||||
@@ -27,7 +27,7 @@ import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.groups.GroupsConnection;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarBase.State;
|
||||
import google.registry.model.registrar.Registrar.State;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@@ -30,7 +30,6 @@ import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.flows.certs.CertificateChecker;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarAddress;
|
||||
import google.registry.model.registrar.RegistrarBase;
|
||||
import google.registry.tools.params.KeyValueMapParameter.CurrencyUnitToStringMap;
|
||||
import google.registry.tools.params.OptionalLongParameter;
|
||||
import google.registry.tools.params.OptionalPhoneNumberParameter;
|
||||
@@ -61,11 +60,11 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
|
||||
List<String> mainParameters;
|
||||
|
||||
@Parameter(names = "--registrar_type", description = "Type of the registrar")
|
||||
RegistrarBase.Type registrarType;
|
||||
Registrar.Type registrarType;
|
||||
|
||||
@Nullable
|
||||
@Parameter(names = "--registrar_state", description = "Initial state of the registrar")
|
||||
RegistrarBase.State registrarState;
|
||||
Registrar.State registrarState;
|
||||
|
||||
@Parameter(
|
||||
names = "--allowed_tlds",
|
||||
|
||||
@@ -18,7 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.common.base.Strings.emptyToNull;
|
||||
import static com.google.common.collect.Iterables.getOnlyElement;
|
||||
import static google.registry.model.registrar.RegistrarBase.State.ACTIVE;
|
||||
import static google.registry.model.registrar.Registrar.State.ACTIVE;
|
||||
import static google.registry.tools.RegistryToolEnvironment.PRODUCTION;
|
||||
import static google.registry.tools.RegistryToolEnvironment.SANDBOX;
|
||||
import static google.registry.tools.RegistryToolEnvironment.UNITTEST;
|
||||
|
||||
@@ -29,7 +29,6 @@ import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
import google.registry.model.registrar.RegistrarPocBase;
|
||||
import google.registry.tools.params.OptionalPhoneNumberParameter;
|
||||
import google.registry.tools.params.PathParameter;
|
||||
import google.registry.tools.params.StringListParameter;
|
||||
@@ -153,7 +152,7 @@ final class RegistrarPocCommand extends MutatingCommand {
|
||||
private static final ImmutableSet<Mode> MODES_REQUIRING_CONTACT_SYNC =
|
||||
ImmutableSet.of(Mode.CREATE, Mode.UPDATE, Mode.DELETE);
|
||||
|
||||
@Nullable private ImmutableSet<RegistrarPocBase.Type> contactTypes;
|
||||
@Nullable private ImmutableSet<RegistrarPoc.Type> contactTypes;
|
||||
|
||||
@Override
|
||||
protected void init() throws Exception {
|
||||
@@ -169,7 +168,7 @@ final class RegistrarPocCommand extends MutatingCommand {
|
||||
} else {
|
||||
contactTypes =
|
||||
contactTypeNames.stream()
|
||||
.map(Enums.stringConverter(RegistrarPocBase.Type.class))
|
||||
.map(Enums.stringConverter(RegistrarPoc.Type.class))
|
||||
.collect(toImmutableSet());
|
||||
}
|
||||
ImmutableSet<RegistrarPoc> contacts = registrar.getContacts();
|
||||
|
||||
@@ -24,7 +24,7 @@ import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.groups.GroupsConnection;
|
||||
import google.registry.groups.GroupsConnection.Role;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarPocBase;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.HttpException.BadRequestException;
|
||||
@@ -65,7 +65,7 @@ public class CreateGroupsAction implements Runnable {
|
||||
if (registrar == null) {
|
||||
return;
|
||||
}
|
||||
List<RegistrarPocBase.Type> types = asList(RegistrarPocBase.Type.values());
|
||||
List<RegistrarPoc.Type> types = asList(RegistrarPoc.Type.values());
|
||||
// Concurrently create the groups for each RegistrarContact.Type, collecting the results from
|
||||
// each call (which are either an Exception if it failed, or absent() if it succeeded).
|
||||
List<Optional<Exception>> results =
|
||||
|
||||
@@ -29,7 +29,6 @@ import com.google.re2j.Pattern;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarAddress;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
import google.registry.model.registrar.RegistrarPocBase;
|
||||
import google.registry.ui.forms.FormException;
|
||||
import google.registry.ui.forms.FormField;
|
||||
import google.registry.ui.forms.FormFieldException;
|
||||
@@ -201,10 +200,10 @@ public final class RegistrarFormFields {
|
||||
public static final FormField<String, String> CONTACT_REGISTRY_LOCK_PASSWORD_FIELD =
|
||||
FormFields.NAME.asBuilderNamed("registryLockPassword").build();
|
||||
|
||||
public static final FormField<String, Set<RegistrarPocBase.Type>> CONTACT_TYPES =
|
||||
public static final FormField<String, Set<RegistrarPoc.Type>> CONTACT_TYPES =
|
||||
FormField.named("types")
|
||||
.uppercased()
|
||||
.asEnum(RegistrarPocBase.Type.class)
|
||||
.asEnum(RegistrarPoc.Type.class)
|
||||
.asSet(Splitter.on(',').omitEmptyStrings().trimResults())
|
||||
.build();
|
||||
|
||||
|
||||
@@ -41,7 +41,6 @@ import google.registry.model.console.ConsoleUpdateHistory;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
import google.registry.model.registrar.RegistrarPocBase;
|
||||
import google.registry.request.HttpException;
|
||||
import google.registry.security.XsrfTokenManager;
|
||||
import google.registry.util.DiffUtils;
|
||||
@@ -218,7 +217,7 @@ public abstract class ConsoleApiAction implements Runnable {
|
||||
consoleApiParams.authResult().userIdForLogging(),
|
||||
DiffUtils.prettyPrintDiffedMap(diffs, null)),
|
||||
contacts.stream()
|
||||
.filter(c -> c.getTypes().contains(RegistrarPocBase.Type.ADMIN))
|
||||
.filter(c -> c.getTypes().contains(RegistrarPoc.Type.ADMIN))
|
||||
.map(RegistrarPoc::getEmailAddress)
|
||||
.collect(toImmutableList()));
|
||||
}
|
||||
@@ -262,7 +261,7 @@ public abstract class ConsoleApiAction implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
protected void finishAndPersistConsoleUpdateHistory(ConsoleUpdateHistory.Builder<?, ?> builder) {
|
||||
protected void finishAndPersistConsoleUpdateHistory(ConsoleUpdateHistory.Builder builder) {
|
||||
builder.setActingUser(consoleApiParams.authResult().user().get());
|
||||
builder.setUrl(consoleApiParams.request().getRequestURI());
|
||||
builder.setMethod(consoleApiParams.request().getMethod());
|
||||
|
||||
@@ -28,7 +28,6 @@ import com.google.gson.annotations.Expose;
|
||||
import google.registry.flows.EppException.AuthenticationErrorException;
|
||||
import google.registry.flows.PasswordOnlyTransportCredentials;
|
||||
import google.registry.model.console.ConsoleUpdateHistory;
|
||||
import google.registry.model.console.RegistrarUpdateHistory;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.request.Action;
|
||||
@@ -107,14 +106,10 @@ public class ConsoleEppPasswordAction extends ConsoleApiAction {
|
||||
Registrar updatedRegistrar =
|
||||
registrar.asBuilder().setPassword(eppRequestBody.newPassword()).build();
|
||||
tm().put(updatedRegistrar);
|
||||
EppPasswordData sanitizedData =
|
||||
new EppPasswordData(
|
||||
eppRequestBody.registrarId, "********", "••••••••", "••••••••");
|
||||
finishAndPersistConsoleUpdateHistory(
|
||||
new RegistrarUpdateHistory.Builder()
|
||||
.setType(ConsoleUpdateHistory.Type.REGISTRAR_UPDATE)
|
||||
.setRegistrar(updatedRegistrar)
|
||||
.setRequestBody(consoleApiParams.gson().toJson(sanitizedData)));
|
||||
new ConsoleUpdateHistory.Builder()
|
||||
.setType(ConsoleUpdateHistory.Type.EPP_PASSWORD_UPDATE)
|
||||
.setDescription(registrar.getRegistrarId()));
|
||||
sendExternalUpdates(
|
||||
ImmutableMap.of("password", new DiffUtils.DiffPair("********", "••••••••")),
|
||||
registrar,
|
||||
|
||||
@@ -35,7 +35,6 @@ import google.registry.model.OteStats.StatType;
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarBase;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GkeService;
|
||||
import google.registry.request.Parameter;
|
||||
@@ -140,7 +139,7 @@ public class ConsoleOteAction extends ConsoleApiAction {
|
||||
SC_BAD_REQUEST);
|
||||
return;
|
||||
}
|
||||
if (!RegistrarBase.Type.OTE.equals(registrar.get().getType())) {
|
||||
if (!Registrar.Type.OTE.equals(registrar.get().getType())) {
|
||||
setFailedResponse(
|
||||
String.format("Registrar with ID %s is not an OT&E registrar", registrarId),
|
||||
SC_BAD_REQUEST);
|
||||
|
||||
+2
-4
@@ -24,7 +24,6 @@ import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
import google.registry.model.console.ConsoleUpdateHistory;
|
||||
import google.registry.model.console.RegistrarUpdateHistory;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.request.Action;
|
||||
@@ -102,10 +101,9 @@ public class ConsoleUpdateRegistrarAction extends ConsoleApiAction {
|
||||
|
||||
tm().put(updatedRegistrar);
|
||||
finishAndPersistConsoleUpdateHistory(
|
||||
new RegistrarUpdateHistory.Builder()
|
||||
new ConsoleUpdateHistory.Builder()
|
||||
.setType(ConsoleUpdateHistory.Type.REGISTRAR_UPDATE)
|
||||
.setRegistrar(updatedRegistrar)
|
||||
.setRequestBody(consoleApiParams.gson().toJson(registrarParam)));
|
||||
.setDescription(updatedRegistrar.getRegistrarId()));
|
||||
sendExternalUpdatesIfNecessary(
|
||||
EmailInfo.create(
|
||||
existingRegistrar.get(),
|
||||
|
||||
@@ -165,7 +165,7 @@ public class ConsoleUsersAction extends ConsoleApiAction {
|
||||
User updatedUser = updateUserRegistrarRoles(email, registrarId, null);
|
||||
|
||||
// User has no registrars assigned
|
||||
if (updatedUser.getUserRoles().getRegistrarRoles().size() == 0) {
|
||||
if (updatedUser.getUserRoles().getRegistrarRoles().isEmpty()) {
|
||||
try {
|
||||
directory.users().delete(email).execute();
|
||||
} catch (IOException e) {
|
||||
|
||||
@@ -28,11 +28,9 @@ import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Streams;
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
import google.registry.model.console.ConsoleUpdateHistory;
|
||||
import google.registry.model.console.RegistrarUpdateHistory;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarBase;
|
||||
import google.registry.model.registrar.RegistrarBase.State;
|
||||
import google.registry.model.registrar.Registrar.State;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
@@ -55,8 +53,8 @@ import java.util.Optional;
|
||||
public class RegistrarsAction extends ConsoleApiAction {
|
||||
private static final int PASSWORD_LENGTH = 16;
|
||||
private static final int PASSCODE_LENGTH = 5;
|
||||
private static final ImmutableList<RegistrarBase.Type> allowedRegistrarTypes =
|
||||
ImmutableList.of(Registrar.Type.REAL, RegistrarBase.Type.OTE);
|
||||
private static final ImmutableList<Registrar.Type> allowedRegistrarTypes =
|
||||
ImmutableList.of(Registrar.Type.REAL, Registrar.Type.OTE);
|
||||
private static final String SQL_TEMPLATE =
|
||||
"""
|
||||
SELECT * FROM "Registrar"
|
||||
@@ -174,10 +172,9 @@ public class RegistrarsAction extends ConsoleApiAction {
|
||||
registrar.getRegistrarId());
|
||||
tm().putAll(registrar, contact);
|
||||
finishAndPersistConsoleUpdateHistory(
|
||||
new RegistrarUpdateHistory.Builder()
|
||||
.setType(ConsoleUpdateHistory.Type.REGISTRAR_UPDATE)
|
||||
.setRegistrar(registrar)
|
||||
.setRequestBody(consoleApiParams.gson().toJson(registrar)));
|
||||
new ConsoleUpdateHistory.Builder()
|
||||
.setType(ConsoleUpdateHistory.Type.REGISTRAR_CREATE)
|
||||
.setDescription(registrar.getRegistrarId()));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+24
@@ -15,6 +15,7 @@
|
||||
package google.registry.ui.server.console.domains;
|
||||
|
||||
import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
@@ -25,6 +26,7 @@ import google.registry.flows.EppController;
|
||||
import google.registry.flows.EppRequestSource;
|
||||
import google.registry.flows.PasswordOnlyTransportCredentials;
|
||||
import google.registry.flows.StatelessRequestSessionMetadata;
|
||||
import google.registry.model.console.ConsoleUpdateHistory;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.eppcommon.ProtocolDefinition;
|
||||
import google.registry.model.eppoutput.EppOutput;
|
||||
@@ -93,11 +95,29 @@ public class ConsoleBulkDomainAction extends ConsoleApiAction {
|
||||
domainList.domainList.stream()
|
||||
.collect(
|
||||
toImmutableMap(d -> d, d -> executeEpp(actionType.getXmlContentsToRun(d), user)));
|
||||
handleHistoryAdditions(result, actionType);
|
||||
// Front end should parse situations where only some commands worked
|
||||
consoleApiParams.response().setPayload(consoleApiParams.gson().toJson(result));
|
||||
consoleApiParams.response().setStatus(SC_OK);
|
||||
}
|
||||
|
||||
private void handleHistoryAdditions(
|
||||
ImmutableMap<String, ConsoleEppOutput> result, ConsoleDomainActionType actionType) {
|
||||
if (result.values().stream().noneMatch(ConsoleEppOutput::isSuccess)) {
|
||||
return;
|
||||
}
|
||||
tm().transact(
|
||||
() ->
|
||||
result.entrySet().stream()
|
||||
.filter(e -> e.getValue().isSuccess())
|
||||
.forEach(
|
||||
e ->
|
||||
finishAndPersistConsoleUpdateHistory(
|
||||
new ConsoleUpdateHistory.Builder()
|
||||
.setDescription(e.getKey())
|
||||
.setType(actionType.getConsoleUpdateHistoryType()))));
|
||||
}
|
||||
|
||||
private ConsoleEppOutput executeEpp(String xml, User user) {
|
||||
return ConsoleEppOutput.fromEppOutput(
|
||||
eppController.handleEppCommand(
|
||||
@@ -115,5 +135,9 @@ public class ConsoleBulkDomainAction extends ConsoleApiAction {
|
||||
Result result = eppOutput.getResponse().getResult();
|
||||
return new ConsoleEppOutput(result.getMsg(), result.getCode().code);
|
||||
}
|
||||
|
||||
boolean isSuccess() {
|
||||
return responseCode < 2000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -16,6 +16,7 @@ package google.registry.ui.server.console.domains;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
import google.registry.model.console.ConsoleUpdateHistory;
|
||||
|
||||
/** An action that will run a delete EPP command on the given domain. */
|
||||
public class ConsoleBulkDomainDeleteActionType extends ConsoleDomainActionType {
|
||||
@@ -54,4 +55,9 @@ public class ConsoleBulkDomainDeleteActionType extends ConsoleDomainActionType {
|
||||
public ConsolePermission getNecessaryPermission() {
|
||||
return ConsolePermission.EXECUTE_EPP_COMMANDS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConsoleUpdateHistory.Type getConsoleUpdateHistoryType() {
|
||||
return ConsoleUpdateHistory.Type.DOMAIN_DELETE;
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -16,6 +16,7 @@ package google.registry.ui.server.console.domains;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
import google.registry.model.console.ConsoleUpdateHistory;
|
||||
|
||||
/** An action that will suspend the given domain, assigning all 5 server*Prohibited statuses. */
|
||||
public class ConsoleBulkDomainSuspendActionType extends ConsoleDomainActionType {
|
||||
@@ -64,4 +65,9 @@ public class ConsoleBulkDomainSuspendActionType extends ConsoleDomainActionType
|
||||
public ConsolePermission getNecessaryPermission() {
|
||||
return ConsolePermission.SUSPEND_DOMAIN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConsoleUpdateHistory.Type getConsoleUpdateHistoryType() {
|
||||
return ConsoleUpdateHistory.Type.DOMAIN_SUSPEND;
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -16,6 +16,7 @@ package google.registry.ui.server.console.domains;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
import google.registry.model.console.ConsoleUpdateHistory;
|
||||
|
||||
/** An action that will unsuspend the given domain, removing all 5 server*Prohibited statuses. */
|
||||
public class ConsoleBulkDomainUnsuspendActionType extends ConsoleDomainActionType {
|
||||
@@ -64,4 +65,9 @@ public class ConsoleBulkDomainUnsuspendActionType extends ConsoleDomainActionTyp
|
||||
public ConsolePermission getNecessaryPermission() {
|
||||
return ConsolePermission.SUSPEND_DOMAIN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConsoleUpdateHistory.Type getConsoleUpdateHistoryType() {
|
||||
return ConsoleUpdateHistory.Type.DOMAIN_UNSUSPEND;
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -18,6 +18,7 @@ import com.google.common.escape.Escaper;
|
||||
import com.google.common.xml.XmlEscapers;
|
||||
import com.google.gson.JsonElement;
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
import google.registry.model.console.ConsoleUpdateHistory;
|
||||
|
||||
/**
|
||||
* A type of EPP action to perform on domain(s), run by the {@link ConsoleBulkDomainAction}.
|
||||
@@ -68,6 +69,9 @@ public abstract class ConsoleDomainActionType {
|
||||
/** Returns the permission necessary to successfully perform this action. */
|
||||
public abstract ConsolePermission getNecessaryPermission();
|
||||
|
||||
/** Returns the type of history / audit logging object to save. */
|
||||
public abstract ConsoleUpdateHistory.Type getConsoleUpdateHistoryType();
|
||||
|
||||
/** Returns the XML template contents for this action. */
|
||||
protected abstract String getXmlTemplate();
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ import google.registry.model.console.ConsolePermission;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
import google.registry.model.registrar.RegistrarPocBase.Type;
|
||||
import google.registry.model.registrar.RegistrarPoc.Type;
|
||||
import google.registry.persistence.transaction.QueryComposer.Comparator;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
|
||||
+8
-24
@@ -17,13 +17,11 @@ package google.registry.ui.server.console.settings;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.request.Action.Method.POST;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
import google.registry.model.console.ConsoleUpdateHistory;
|
||||
import google.registry.model.console.RegistrarUpdateHistory;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.request.Action;
|
||||
@@ -36,7 +34,6 @@ import google.registry.request.auth.AuthenticatedRegistrarAccessor.RegistrarAcce
|
||||
import google.registry.ui.server.console.ConsoleApiAction;
|
||||
import google.registry.ui.server.console.ConsoleApiParams;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
@@ -48,17 +45,17 @@ import java.util.Optional;
|
||||
@Action(
|
||||
service = GaeService.DEFAULT,
|
||||
gkeService = GkeService.CONSOLE,
|
||||
path = WhoisRegistrarFieldsAction.PATH,
|
||||
path = RdapRegistrarFieldsAction.PATH,
|
||||
method = {POST},
|
||||
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
|
||||
public class WhoisRegistrarFieldsAction extends ConsoleApiAction {
|
||||
public class RdapRegistrarFieldsAction extends ConsoleApiAction {
|
||||
|
||||
static final String PATH = "/console-api/settings/whois-fields";
|
||||
static final String PATH = "/console-api/settings/rdap-fields";
|
||||
private final AuthenticatedRegistrarAccessor registrarAccessor;
|
||||
private final Optional<Registrar> registrar;
|
||||
|
||||
@Inject
|
||||
public WhoisRegistrarFieldsAction(
|
||||
public RdapRegistrarFieldsAction(
|
||||
ConsoleApiParams consoleApiParams,
|
||||
AuthenticatedRegistrarAccessor registrarAccessor,
|
||||
@Parameter("registrar") Optional<Registrar> registrar) {
|
||||
@@ -72,10 +69,10 @@ public class WhoisRegistrarFieldsAction extends ConsoleApiAction {
|
||||
checkArgument(registrar.isPresent(), "'registrar' parameter is not present");
|
||||
checkPermission(
|
||||
user, registrar.get().getRegistrarId(), ConsolePermission.EDIT_REGISTRAR_DETAILS);
|
||||
tm().transact(() -> loadAndModifyRegistrar(registrar.get(), user));
|
||||
tm().transact(() -> loadAndModifyRegistrar(registrar.get()));
|
||||
}
|
||||
|
||||
private void loadAndModifyRegistrar(Registrar providedRegistrar, User user) {
|
||||
private void loadAndModifyRegistrar(Registrar providedRegistrar) {
|
||||
Registrar savedRegistrar;
|
||||
try {
|
||||
// reload to make sure the object has all the correct fields
|
||||
@@ -85,32 +82,19 @@ public class WhoisRegistrarFieldsAction extends ConsoleApiAction {
|
||||
return;
|
||||
}
|
||||
|
||||
// icannReferralEmail can't be updated by partners, only by global users with
|
||||
// EDIT_REGISTRAR_DETAILS permission
|
||||
if (!Objects.equals(
|
||||
savedRegistrar.getIcannReferralEmail(), providedRegistrar.getIcannReferralEmail())
|
||||
&& !user.getUserRoles().hasGlobalPermission(ConsolePermission.EDIT_REGISTRAR_DETAILS)) {
|
||||
setFailedResponse(
|
||||
"Icann Referral Email update is not permitted for this user", SC_BAD_REQUEST);
|
||||
}
|
||||
|
||||
Registrar newRegistrar =
|
||||
savedRegistrar
|
||||
.asBuilder()
|
||||
.setWhoisServer(providedRegistrar.getWhoisServer())
|
||||
.setUrl(providedRegistrar.getUrl())
|
||||
.setLocalizedAddress(providedRegistrar.getLocalizedAddress())
|
||||
.setPhoneNumber(providedRegistrar.getPhoneNumber())
|
||||
.setFaxNumber(providedRegistrar.getFaxNumber())
|
||||
.setIcannReferralEmail(providedRegistrar.getIcannReferralEmail())
|
||||
.setEmailAddress(providedRegistrar.getEmailAddress())
|
||||
.build();
|
||||
tm().put(newRegistrar);
|
||||
finishAndPersistConsoleUpdateHistory(
|
||||
new RegistrarUpdateHistory.Builder()
|
||||
new ConsoleUpdateHistory.Builder()
|
||||
.setType(ConsoleUpdateHistory.Type.REGISTRAR_UPDATE)
|
||||
.setRegistrar(newRegistrar)
|
||||
.setRequestBody(consoleApiParams.gson().toJson(registrar.get())));
|
||||
.setDescription(newRegistrar.getRegistrarId()));
|
||||
sendExternalUpdatesIfNecessary(
|
||||
EmailInfo.create(
|
||||
savedRegistrar,
|
||||
@@ -26,7 +26,6 @@ import google.registry.flows.certs.CertificateChecker;
|
||||
import google.registry.flows.certs.CertificateChecker.InsecureCertificateException;
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
import google.registry.model.console.ConsoleUpdateHistory;
|
||||
import google.registry.model.console.RegistrarUpdateHistory;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.request.Action;
|
||||
@@ -120,10 +119,9 @@ public class SecurityAction extends ConsoleApiAction {
|
||||
Registrar updatedRegistrar = updatedRegistrarBuilder.build();
|
||||
tm().put(updatedRegistrar);
|
||||
finishAndPersistConsoleUpdateHistory(
|
||||
new RegistrarUpdateHistory.Builder()
|
||||
.setType(ConsoleUpdateHistory.Type.REGISTRAR_UPDATE)
|
||||
.setRegistrar(updatedRegistrar)
|
||||
.setRequestBody(consoleApiParams.gson().toJson(registrar.get())));
|
||||
new ConsoleUpdateHistory.Builder()
|
||||
.setType(ConsoleUpdateHistory.Type.REGISTRAR_SECURITY_UPDATE)
|
||||
.setDescription(registrarId));
|
||||
|
||||
sendExternalUpdatesIfNecessary(
|
||||
EmailInfo.create(savedRegistrar, updatedRegistrar, ImmutableSet.of(), ImmutableSet.of()));
|
||||
|
||||
@@ -47,11 +47,8 @@
|
||||
<class>google.registry.model.billing.BillingRecurrence</class>
|
||||
<class>google.registry.model.common.Cursor</class>
|
||||
<class>google.registry.model.common.DnsRefreshRequest</class>
|
||||
<class>google.registry.model.console.ConsoleEppActionHistory</class>
|
||||
<class>google.registry.model.console.RegistrarPocUpdateHistory</class>
|
||||
<class>google.registry.model.console.RegistrarUpdateHistory</class>
|
||||
<class>google.registry.model.console.ConsoleUpdateHistory</class>
|
||||
<class>google.registry.model.console.User</class>
|
||||
<class>google.registry.model.console.UserUpdateHistory</class>
|
||||
<class>google.registry.model.contact.ContactHistory</class>
|
||||
<class>google.registry.model.contact.Contact</class>
|
||||
<class>google.registry.model.domain.Domain</class>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
-- Determine the number of attempted adds each registrar made.
|
||||
|
||||
-- Since the specification requests all 'attempted' adds, we regex the
|
||||
-- monthly App Engine logs, searching for all create commands and associating
|
||||
-- monthly GKE logs, searching for all create commands and associating
|
||||
-- them with their corresponding registrars.
|
||||
|
||||
-- Example log generated by FlowReporter in App Engine and GKE logs:
|
||||
@@ -27,7 +27,7 @@
|
||||
-- ,"targetId":"","targetIds":[],"tld":"",
|
||||
-- "tlds":[],"icannActivityReportField":""}
|
||||
|
||||
-- This outer select just converts the registrar's clientId to their name.
|
||||
-- This outer select just converts the registrar's ID to their name.
|
||||
SELECT
|
||||
tld,
|
||||
registrar_table.registrar_name AS registrar_name,
|
||||
@@ -38,34 +38,20 @@ FROM (
|
||||
JSON_EXTRACT_SCALAR(json, '$.tld') AS tld,
|
||||
JSON_EXTRACT_SCALAR(json, '$.clientId') AS clientId,
|
||||
COUNT(json) AS count
|
||||
FROM ((
|
||||
-- Extract JSON metadata package from monthly logs
|
||||
FROM (
|
||||
-- Extract JSON metadata package from monthly logs
|
||||
SELECT
|
||||
REGEXP_EXTRACT(textPayload, r'FLOW-LOG-SIGNATURE-METADATA: (.*)\n?$') AS json
|
||||
REGEXP_EXTRACT(jsonPayload.message, r'FLOW-LOG-SIGNATURE-METADATA: (.*)\n?$') AS json
|
||||
FROM
|
||||
`%PROJECT_ID%.%APPENGINE_LOGS_DATA_SET%.%APP_LOGS_TABLE%*`
|
||||
`%PROJECT_ID%.gke_logs.stderr_*`
|
||||
WHERE
|
||||
_TABLE_SUFFIX BETWEEN '%FIRST_DAY_OF_MONTH%'
|
||||
AND '%LAST_DAY_OF_MONTH%'
|
||||
AND STARTS_WITH(textPayload, "FLOW-LOG-SIGNATURE-METADATA")
|
||||
AND STARTS_WITH(jsonPayload.message, "FLOW-LOG-SIGNATURE-METADATA")
|
||||
-- Look for domain creates
|
||||
AND REGEXP_CONTAINS(textPayload, r'"commandType":"create","resourceType":"domain"')
|
||||
AND REGEXP_CONTAINS(jsonPayload.message, r'"commandType":"create","resourceType":"domain"')
|
||||
-- Filter prober data
|
||||
AND NOT REGEXP_CONTAINS(textPayload, r'"prober-[a-z]{2}-((any)|(canary))"'))
|
||||
UNION ALL (
|
||||
-- Extract JSON metadata package from monthly logs
|
||||
SELECT
|
||||
REGEXP_EXTRACT(jsonPayload.message, r'FLOW-LOG-SIGNATURE-METADATA: (.*)\n?$') AS json
|
||||
FROM
|
||||
`%PROJECT_ID%.%GKE_LOGS_DATA_SET%.stderr_*`
|
||||
WHERE
|
||||
_TABLE_SUFFIX BETWEEN '%FIRST_DAY_OF_MONTH%'
|
||||
AND '%LAST_DAY_OF_MONTH%'
|
||||
AND STARTS_WITH(jsonPayload.message, "FLOW-LOG-SIGNATURE-METADATA")
|
||||
-- Look for domain creates
|
||||
AND REGEXP_CONTAINS(jsonPayload.message, r'"commandType":"create","resourceType":"domain"')
|
||||
-- Filter prober data
|
||||
AND NOT REGEXP_CONTAINS(jsonPayload.message, r'"prober-[a-z]{2}-((any)|(canary))"')))
|
||||
AND NOT REGEXP_CONTAINS(jsonPayload.message, r'"prober-[a-z]{2}-((any)|(canary))"'))
|
||||
GROUP BY
|
||||
tld,
|
||||
clientId ) AS logs_table
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
-- Query FlowReporter JSON log messages and calculate SRS metrics.
|
||||
|
||||
-- We use ugly regex's over the monthly appengine logs to determine how many
|
||||
-- We use ugly regexes over the monthly GKE logs to determine how many
|
||||
-- EPP requests we received for each command. For example:
|
||||
-- {"commandType":"check"...,"targetIds":["ais.a.how"],
|
||||
-- "tld":"","tlds":["a.how"],"icannActivityReportField":"srs-dom-check"}
|
||||
@@ -35,30 +35,15 @@ FROM (
|
||||
JSON_EXTRACT_SCALAR(json,
|
||||
'$.icannActivityReportField') AS activityReportField
|
||||
FROM (
|
||||
-- For reasons that I don't understand, if I directly select the three columns
|
||||
-- from the union, BigQuery complains about column number mismatch, so I have to
|
||||
-- make a temporary union table and select on it.
|
||||
SELECT
|
||||
*
|
||||
FROM (
|
||||
SELECT
|
||||
-- Extract the logged JSON payload.
|
||||
REGEXP_EXTRACT(textPayload, r'FLOW-LOG-SIGNATURE-METADATA: (.*)\n?$')
|
||||
AS json
|
||||
FROM `%PROJECT_ID%.%APPENGINE_LOGS_DATA_SET%.%APP_LOGS_TABLE%*`
|
||||
WHERE
|
||||
STARTS_WITH(textPayload, "FLOW-LOG-SIGNATURE-METADATA")
|
||||
AND _TABLE_SUFFIX BETWEEN '%FIRST_DAY_OF_MONTH%' AND '%LAST_DAY_OF_MONTH%')
|
||||
UNION ALL (
|
||||
SELECT
|
||||
-- Extract the logged JSON payload.
|
||||
REGEXP_EXTRACT(jsonPayload.message, r'FLOW-LOG-SIGNATURE-METADATA: (.*)\n?$')
|
||||
AS json
|
||||
FROM `%PROJECT_ID%.%GKE_LOGS_DATA_SET%.stderr_*`
|
||||
WHERE
|
||||
STARTS_WITH(jsonPayload.message, "FLOW-LOG-SIGNATURE-METADATA")
|
||||
AND _TABLE_SUFFIX BETWEEN '%FIRST_DAY_OF_MONTH%' AND '%LAST_DAY_OF_MONTH%')
|
||||
)) AS regexes
|
||||
-- Extract the logged JSON payload.
|
||||
REGEXP_EXTRACT(jsonPayload.message, r'FLOW-LOG-SIGNATURE-METADATA: (.*)\n?$')
|
||||
AS json
|
||||
FROM `%PROJECT_ID%.gke_logs.stderr_*`
|
||||
WHERE
|
||||
STARTS_WITH(jsonPayload.message, "FLOW-LOG-SIGNATURE-METADATA")
|
||||
AND _TABLE_SUFFIX BETWEEN '%FIRST_DAY_OF_MONTH%' AND '%LAST_DAY_OF_MONTH%')
|
||||
) AS regexes
|
||||
JOIN
|
||||
-- Unnest the JSON-parsed tlds.
|
||||
UNNEST(regexes.tlds) AS tld
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
-- See the License for the specific language governing permissions and
|
||||
-- limitations under the License.
|
||||
|
||||
-- Query to fetch AppEngine and GKE request logs for the report month.
|
||||
-- Query to fetch GKE request logs for the report month.
|
||||
|
||||
-- START_OF_MONTH and END_OF_MONTH should be in YYYYMM01 format.
|
||||
|
||||
@@ -23,13 +23,6 @@ FROM (
|
||||
SELECT
|
||||
jsonPayload.httrequest.requesturl AS requestPath
|
||||
FROM
|
||||
`%PROJECT_ID%.%GKE_LOGS_DATA_SET%.stderr_*`
|
||||
WHERE
|
||||
_TABLE_SUFFIX BETWEEN '%FIRST_DAY_OF_MONTH%' AND '%LAST_DAY_OF_MONTH%')
|
||||
UNION ALL (
|
||||
SELECT
|
||||
protoPayload.resource AS requestPath
|
||||
FROM
|
||||
`%PROJECT_ID%.%APPENGINE_LOGS_DATA_SET%.%REQUEST_TABLE%*`
|
||||
`%PROJECT_ID%.gke_logs.stderr_*`
|
||||
WHERE
|
||||
_TABLE_SUFFIX BETWEEN '%FIRST_DAY_OF_MONTH%' AND '%LAST_DAY_OF_MONTH%')
|
||||
|
||||
+11
-11
@@ -32,7 +32,16 @@ FROM (
|
||||
WHEN field = 'TRANSFER_NACKED' THEN 'TRANSFER_GAINING_NACKED'
|
||||
ELSE field
|
||||
END AS metricName,
|
||||
SUM(amount) AS metricValue
|
||||
-- See b/290228682, there are edge cases in which the net_renew would be negative when
|
||||
-- a domain is cancelled by superusers during renew grace period. The correct thing
|
||||
-- to do is attribute the cancellation to the owning registrar, but that would require
|
||||
-- changing the owing registrar of the the corresponding cancellation DomainHistory,
|
||||
-- which has cascading effects that we don't want to deal with. As such we simply
|
||||
-- floor the number here to zero to prevent any negative value from appearing, which
|
||||
-- should have negligible impact as the edge cage happens very rarely, more specifically
|
||||
-- when a cancellation happens during grace period by a registrar other than the the
|
||||
-- owning one. All the numbers here should be non-negative to pass ICANN validation.
|
||||
GREATEST(SUM(amount), 0) AS metricValue
|
||||
FROM (
|
||||
SELECT
|
||||
CASE
|
||||
@@ -47,16 +56,7 @@ FROM (
|
||||
END AS clientId,
|
||||
tld,
|
||||
report_field AS field,
|
||||
-- See b/290228682, there are edge cases in which the net_renew would be negative when
|
||||
-- a domain is cancelled by superusers during renew grace period. The correct thing
|
||||
-- to do is attribute the cancellation to the owning registrar, but that would require
|
||||
-- changing the owing registrar of the the corresponding cancellation DomainHistory,
|
||||
-- which has cascading effects that we don't want to deal with. As such we simply
|
||||
-- floor the number here to zero to prevent any negative value from appearing, which
|
||||
-- should have negligible impact as the edge cage happens very rarely, more specifically
|
||||
-- when a cancellation happens during grace period by a registrar other than the the
|
||||
-- owning one. All the numbers here should be positive to pass ICANN validation.
|
||||
GREATEST(report_amount, 0) AS amount,
|
||||
report_amount AS amount,
|
||||
reporting_time AS reportingTime
|
||||
FROM EXTERNAL_QUERY("projects/%PROJECT_ID%/locations/us/connections/%PROJECT_ID%-sql",
|
||||
''' SELECT history_type, history_other_registrar_id, history_registrar_id, domain_repo_id, history_revision_id FROM "DomainHistory";''') AS dh
|
||||
|
||||
+14
-15
@@ -36,8 +36,7 @@ import google.registry.groups.GmailClient;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarAddress;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
import google.registry.model.registrar.RegistrarPocBase;
|
||||
import google.registry.model.registrar.RegistrarPocBase.Type;
|
||||
import google.registry.model.registrar.RegistrarPoc.Type;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
|
||||
import google.registry.testing.FakeClock;
|
||||
@@ -57,13 +56,13 @@ class SendExpiringCertificateNotificationEmailActionTest {
|
||||
|
||||
private static final String EXPIRATION_WARNING_EMAIL_BODY_TEXT =
|
||||
"""
|
||||
Dear %1$s,
|
||||
Dear %1$s,
|
||||
|
||||
We would like to inform you that your %2$s certificate will expire at %3$s.
|
||||
Kind update your account using the following steps:
|
||||
1. Navigate to support and login using your %4$s@registry.example credentials.
|
||||
2. Click Settings -> Privacy on the top left corner.
|
||||
3. Click Edit and enter certificate string. 3. Click SaveRegards,Example Registry""";
|
||||
We would like to inform you that your %2$s certificate will expire at %3$s.
|
||||
Kind update your account using the following steps:
|
||||
1. Navigate to support and login using your %4$s@registry.example credentials.
|
||||
2. Click Settings -> Privacy on the top left corner.
|
||||
3. Click Edit and enter certificate string. 3. Click SaveRegards,Example Registry""";
|
||||
|
||||
private static final String EXPIRATION_WARNING_EMAIL_SUBJECT_TEXT = "Expiration Warning Email";
|
||||
|
||||
@@ -220,7 +219,7 @@ class SendExpiringCertificateNotificationEmailActionTest {
|
||||
.setEmailAddress("will@example-registrar.tld")
|
||||
.setPhoneNumber("+1.3105551213")
|
||||
.setFaxNumber("+1.3105551213")
|
||||
.setTypes(ImmutableSet.of(RegistrarPocBase.Type.TECH))
|
||||
.setTypes(ImmutableSet.of(RegistrarPoc.Type.TECH))
|
||||
.setVisibleInWhoisAsAdmin(true)
|
||||
.setVisibleInWhoisAsTech(false)
|
||||
.build());
|
||||
@@ -510,7 +509,7 @@ class SendExpiringCertificateNotificationEmailActionTest {
|
||||
.setEmailAddress("jd@example-registrar.tld")
|
||||
.setPhoneNumber("+1.3105551213")
|
||||
.setFaxNumber("+1.3105551213")
|
||||
.setTypes(ImmutableSet.of(RegistrarPocBase.Type.TECH))
|
||||
.setTypes(ImmutableSet.of(RegistrarPoc.Type.TECH))
|
||||
.setVisibleInWhoisAsAdmin(true)
|
||||
.setVisibleInWhoisAsTech(false)
|
||||
.build(),
|
||||
@@ -520,7 +519,7 @@ class SendExpiringCertificateNotificationEmailActionTest {
|
||||
.setEmailAddress("js@example-registrar.tld")
|
||||
.setPhoneNumber("+1.1111111111")
|
||||
.setFaxNumber("+1.1111111111")
|
||||
.setTypes(ImmutableSet.of(RegistrarPocBase.Type.TECH))
|
||||
.setTypes(ImmutableSet.of(RegistrarPoc.Type.TECH))
|
||||
.build(),
|
||||
new RegistrarPoc.Builder()
|
||||
.setRegistrar(registrar)
|
||||
@@ -528,7 +527,7 @@ class SendExpiringCertificateNotificationEmailActionTest {
|
||||
.setEmailAddress("will@example-registrar.tld")
|
||||
.setPhoneNumber("+1.3105551213")
|
||||
.setFaxNumber("+1.3105551213")
|
||||
.setTypes(ImmutableSet.of(RegistrarPocBase.Type.TECH))
|
||||
.setTypes(ImmutableSet.of(RegistrarPoc.Type.TECH))
|
||||
.setVisibleInWhoisAsAdmin(true)
|
||||
.setVisibleInWhoisAsTech(false)
|
||||
.build(),
|
||||
@@ -538,7 +537,7 @@ class SendExpiringCertificateNotificationEmailActionTest {
|
||||
.setEmailAddress("mike@example-registrar.tld")
|
||||
.setPhoneNumber("+1.1111111111")
|
||||
.setFaxNumber("+1.1111111111")
|
||||
.setTypes(ImmutableSet.of(RegistrarPocBase.Type.ADMIN))
|
||||
.setTypes(ImmutableSet.of(RegistrarPoc.Type.ADMIN))
|
||||
.build(),
|
||||
new RegistrarPoc.Builder()
|
||||
.setRegistrar(registrar)
|
||||
@@ -546,7 +545,7 @@ class SendExpiringCertificateNotificationEmailActionTest {
|
||||
.setEmailAddress("john@example-registrar.tld")
|
||||
.setPhoneNumber("+1.3105551215")
|
||||
.setFaxNumber("+1.3105551216")
|
||||
.setTypes(ImmutableSet.of(RegistrarPocBase.Type.ADMIN))
|
||||
.setTypes(ImmutableSet.of(RegistrarPoc.Type.ADMIN))
|
||||
.setVisibleInWhoisAsTech(true)
|
||||
.build());
|
||||
persistSimpleResources(contacts);
|
||||
@@ -700,7 +699,7 @@ class SendExpiringCertificateNotificationEmailActionTest {
|
||||
|
||||
/** Returns persisted sample contacts with a customized contact email type. */
|
||||
private static ImmutableList<RegistrarPoc> persistSampleContacts(
|
||||
Registrar registrar, RegistrarPocBase.Type emailType) {
|
||||
Registrar registrar, RegistrarPoc.Type emailType) {
|
||||
return persistSimpleResources(
|
||||
ImmutableList.of(
|
||||
new RegistrarPoc.Builder()
|
||||
|
||||
@@ -70,7 +70,7 @@ import google.registry.model.rde.RdeMode;
|
||||
import google.registry.model.rde.RdeRevision;
|
||||
import google.registry.model.rde.RdeRevision.RdeRevisionId;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarBase.State;
|
||||
import google.registry.model.registrar.Registrar.State;
|
||||
import google.registry.model.reporting.DomainTransactionRecord;
|
||||
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
@@ -388,22 +388,22 @@ public class RdePipelineTest {
|
||||
assertThat(domainFrags.stream().findFirst().get().xml().strip())
|
||||
.isEqualTo(
|
||||
"""
|
||||
<rdeDomain:domain>
|
||||
<rdeDomain:name>cat.fun</rdeDomain:name>
|
||||
<rdeDomain:roid>15-FUN</rdeDomain:roid>
|
||||
<rdeDomain:uName>cat.fun</rdeDomain:uName>
|
||||
<rdeDomain:status s="ok"/>
|
||||
<rdeDomain:contact type="admin">contact456</rdeDomain:contact>
|
||||
<rdeDomain:contact type="tech">contact456</rdeDomain:contact>
|
||||
<rdeDomain:ns>
|
||||
<domain:hostObj>ns1.external.tld</domain:hostObj>
|
||||
<domain:hostObj>ns1.hello.soy</domain:hostObj>
|
||||
</rdeDomain:ns>
|
||||
<rdeDomain:clID>TheRegistrar</rdeDomain:clID>
|
||||
<rdeDomain:crRr>TheRegistrar</rdeDomain:crRr>
|
||||
<rdeDomain:crDate>1970-01-01T00:00:00Z</rdeDomain:crDate>
|
||||
<rdeDomain:exDate>294247-01-10T04:00:54Z</rdeDomain:exDate>
|
||||
</rdeDomain:domain>""");
|
||||
<rdeDomain:domain>
|
||||
<rdeDomain:name>cat.fun</rdeDomain:name>
|
||||
<rdeDomain:roid>15-FUN</rdeDomain:roid>
|
||||
<rdeDomain:uName>cat.fun</rdeDomain:uName>
|
||||
<rdeDomain:status s="ok"/>
|
||||
<rdeDomain:contact type="admin">contact456</rdeDomain:contact>
|
||||
<rdeDomain:contact type="tech">contact456</rdeDomain:contact>
|
||||
<rdeDomain:ns>
|
||||
<domain:hostObj>ns1.external.tld</domain:hostObj>
|
||||
<domain:hostObj>ns1.hello.soy</domain:hostObj>
|
||||
</rdeDomain:ns>
|
||||
<rdeDomain:clID>TheRegistrar</rdeDomain:clID>
|
||||
<rdeDomain:crRr>TheRegistrar</rdeDomain:crRr>
|
||||
<rdeDomain:crDate>1970-01-01T00:00:00Z</rdeDomain:crDate>
|
||||
<rdeDomain:exDate>294247-01-10T04:00:54Z</rdeDomain:exDate>
|
||||
</rdeDomain:domain>""");
|
||||
}
|
||||
if (kv.getKey().mode().equals(FULL)) {
|
||||
// Contact fragments for hello.soy.
|
||||
@@ -425,23 +425,23 @@ public class RdePipelineTest {
|
||||
assertThat(domainFrags.stream().findFirst().get().xml().strip())
|
||||
.isEqualTo(
|
||||
"""
|
||||
<rdeDomain:domain>
|
||||
<rdeDomain:name>hello.soy</rdeDomain:name>
|
||||
<rdeDomain:roid>E-SOY</rdeDomain:roid>
|
||||
<rdeDomain:uName>hello.soy</rdeDomain:uName>
|
||||
<rdeDomain:status s="ok"/>
|
||||
<rdeDomain:registrant>contact1234</rdeDomain:registrant>
|
||||
<rdeDomain:contact type="admin">contact789</rdeDomain:contact>
|
||||
<rdeDomain:contact type="tech">contact1234</rdeDomain:contact>
|
||||
<rdeDomain:ns>
|
||||
<domain:hostObj>ns1.external.tld</domain:hostObj>
|
||||
<domain:hostObj>ns1.lol.cat</domain:hostObj>
|
||||
</rdeDomain:ns>
|
||||
<rdeDomain:clID>TheRegistrar</rdeDomain:clID>
|
||||
<rdeDomain:crRr>TheRegistrar</rdeDomain:crRr>
|
||||
<rdeDomain:crDate>1970-01-01T00:00:00Z</rdeDomain:crDate>
|
||||
<rdeDomain:exDate>294247-01-10T04:00:54Z</rdeDomain:exDate>
|
||||
</rdeDomain:domain>""");
|
||||
<rdeDomain:domain>
|
||||
<rdeDomain:name>hello.soy</rdeDomain:name>
|
||||
<rdeDomain:roid>E-SOY</rdeDomain:roid>
|
||||
<rdeDomain:uName>hello.soy</rdeDomain:uName>
|
||||
<rdeDomain:status s="ok"/>
|
||||
<rdeDomain:registrant>contact1234</rdeDomain:registrant>
|
||||
<rdeDomain:contact type="admin">contact789</rdeDomain:contact>
|
||||
<rdeDomain:contact type="tech">contact1234</rdeDomain:contact>
|
||||
<rdeDomain:ns>
|
||||
<domain:hostObj>ns1.external.tld</domain:hostObj>
|
||||
<domain:hostObj>ns1.lol.cat</domain:hostObj>
|
||||
</rdeDomain:ns>
|
||||
<rdeDomain:clID>TheRegistrar</rdeDomain:clID>
|
||||
<rdeDomain:crRr>TheRegistrar</rdeDomain:crRr>
|
||||
<rdeDomain:crDate>1970-01-01T00:00:00Z</rdeDomain:crDate>
|
||||
<rdeDomain:exDate>294247-01-10T04:00:54Z</rdeDomain:exDate>
|
||||
</rdeDomain:domain>""");
|
||||
} else {
|
||||
// Contact fragments for cat.fun.
|
||||
assertThat(
|
||||
@@ -471,20 +471,20 @@ public class RdePipelineTest {
|
||||
assertThat(domainFrags.stream().findFirst().get().xml().strip())
|
||||
.isEqualTo(
|
||||
"""
|
||||
<rdeDomain:domain>
|
||||
<rdeDomain:name>hello.soy</rdeDomain:name>
|
||||
<rdeDomain:roid>E-SOY</rdeDomain:roid>
|
||||
<rdeDomain:uName>hello.soy</rdeDomain:uName>
|
||||
<rdeDomain:status s="ok"/>
|
||||
<rdeDomain:ns>
|
||||
<domain:hostObj>ns1.external.tld</domain:hostObj>
|
||||
<domain:hostObj>ns1.lol.cat</domain:hostObj>
|
||||
</rdeDomain:ns>
|
||||
<rdeDomain:clID>TheRegistrar</rdeDomain:clID>
|
||||
<rdeDomain:crRr>TheRegistrar</rdeDomain:crRr>
|
||||
<rdeDomain:crDate>1970-01-01T00:00:00Z</rdeDomain:crDate>
|
||||
<rdeDomain:exDate>294247-01-10T04:00:54Z</rdeDomain:exDate>
|
||||
</rdeDomain:domain>""");
|
||||
<rdeDomain:domain>
|
||||
<rdeDomain:name>hello.soy</rdeDomain:name>
|
||||
<rdeDomain:roid>E-SOY</rdeDomain:roid>
|
||||
<rdeDomain:uName>hello.soy</rdeDomain:uName>
|
||||
<rdeDomain:status s="ok"/>
|
||||
<rdeDomain:ns>
|
||||
<domain:hostObj>ns1.external.tld</domain:hostObj>
|
||||
<domain:hostObj>ns1.lol.cat</domain:hostObj>
|
||||
</rdeDomain:ns>
|
||||
<rdeDomain:clID>TheRegistrar</rdeDomain:clID>
|
||||
<rdeDomain:crRr>TheRegistrar</rdeDomain:crRr>
|
||||
<rdeDomain:crDate>1970-01-01T00:00:00Z</rdeDomain:crDate>
|
||||
<rdeDomain:exDate>294247-01-10T04:00:54Z</rdeDomain:exDate>
|
||||
</rdeDomain:domain>""");
|
||||
}
|
||||
});
|
||||
return null;
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
package google.registry.export;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.export.ExportDomainListsAction.REGISTERED_DOMAINS_FILENAME;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureName.INCLUDE_PENDING_DELETE_DATE_FOR_DOMAINS;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureStatus.ACTIVE;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureStatus.INACTIVE;
|
||||
@@ -83,10 +82,11 @@ class ExportDomainListsActionTest {
|
||||
persistFeatureFlag(INACTIVE);
|
||||
}
|
||||
|
||||
private void verifyExportedToDrive(String folderId, String domains) throws Exception {
|
||||
private void verifyExportedToDrive(String folderId, String filename, String domains)
|
||||
throws Exception {
|
||||
verify(driveConnection)
|
||||
.createOrUpdateFile(
|
||||
eq(REGISTERED_DOMAINS_FILENAME),
|
||||
eq(filename),
|
||||
eq(MediaType.PLAIN_TEXT_UTF_8),
|
||||
eq(folderId),
|
||||
bytesExportedToDrive.capture());
|
||||
@@ -103,7 +103,7 @@ class ExportDomainListsActionTest {
|
||||
String tlds = new String(gcsUtils.readBytesFrom(existingFile), UTF_8);
|
||||
// Check that it only contains the active domains, not the dead one.
|
||||
assertThat(tlds).isEqualTo("onetwo.tld\nrudnitzky.tld");
|
||||
verifyExportedToDrive("brouhaha", "onetwo.tld\nrudnitzky.tld");
|
||||
verifyExportedToDrive("brouhaha", "registered_domains_tld.txt", "onetwo.tld\nrudnitzky.tld");
|
||||
verifyNoMoreInteractions(driveConnection);
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ class ExportDomainListsActionTest {
|
||||
String tlds = new String(gcsUtils.readBytesFrom(existingFile), UTF_8);
|
||||
// Check that it only contains the active domains, not the dead one.
|
||||
assertThat(tlds).isEqualTo("onetwo.tld,\nrudnitzky.tld,");
|
||||
verifyExportedToDrive("brouhaha", "onetwo.tld,\nrudnitzky.tld,");
|
||||
verifyExportedToDrive("brouhaha", "registered_domains_tld.txt", "onetwo.tld,\nrudnitzky.tld,");
|
||||
verifyNoMoreInteractions(driveConnection);
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ class ExportDomainListsActionTest {
|
||||
assertThrows(StorageException.class, () -> gcsUtils.readBytesFrom(nonexistentFile));
|
||||
ImmutableList<String> ls = gcsUtils.listFolderObjects("outputbucket", "");
|
||||
assertThat(ls).containsExactly("tld.txt");
|
||||
verifyExportedToDrive("brouhaha", "onetwo.tld\nrudnitzky.tld");
|
||||
verifyExportedToDrive("brouhaha", "registered_domains_tld.txt", "onetwo.tld\nrudnitzky.tld");
|
||||
verifyNoMoreInteractions(driveConnection);
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ class ExportDomainListsActionTest {
|
||||
assertThrows(StorageException.class, () -> gcsUtils.readBytesFrom(nonexistentFile));
|
||||
ImmutableList<String> ls = gcsUtils.listFolderObjects("outputbucket", "");
|
||||
assertThat(ls).containsExactly("tld.txt");
|
||||
verifyExportedToDrive("brouhaha", "onetwo.tld,\nrudnitzky.tld,");
|
||||
verifyExportedToDrive("brouhaha", "registered_domains_tld.txt", "onetwo.tld,\nrudnitzky.tld,");
|
||||
verifyNoMoreInteractions(driveConnection);
|
||||
}
|
||||
|
||||
@@ -189,7 +189,9 @@ class ExportDomainListsActionTest {
|
||||
action.run();
|
||||
|
||||
verifyExportedToDrive(
|
||||
"brouhaha", "active.tld,\npendingdelete.tld,2020-02-05T02:02:02.000Z\nredemption.tld,");
|
||||
"brouhaha",
|
||||
"registered_domains_tld.txt",
|
||||
"active.tld,\npendingdelete.tld,2020-02-05T02:02:02.000Z\nredemption.tld,");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -215,8 +217,9 @@ class ExportDomainListsActionTest {
|
||||
BlobId thirdTldFile = BlobId.of("outputbucket", "tldthree.txt");
|
||||
String evenMoreTlds = new String(gcsUtils.readBytesFrom(thirdTldFile), UTF_8).trim();
|
||||
assertThat(evenMoreTlds).isEqualTo("cupid.tldthree");
|
||||
verifyExportedToDrive("brouhaha", "dasher.tld\nprancer.tld");
|
||||
verifyExportedToDrive("hooray", "buddy.tldtwo\nrudolph.tldtwo\nsanta.tldtwo");
|
||||
verifyExportedToDrive("brouhaha", "registered_domains_tld.txt", "dasher.tld\nprancer.tld");
|
||||
verifyExportedToDrive(
|
||||
"hooray", "registered_domains_tldtwo.txt", "buddy.tldtwo\nrudolph.tldtwo\nsanta.tldtwo");
|
||||
// tldthree does not have a drive id, so no export to drive is performed.
|
||||
verifyNoMoreInteractions(driveConnection);
|
||||
}
|
||||
@@ -245,8 +248,9 @@ class ExportDomainListsActionTest {
|
||||
BlobId thirdTldFile = BlobId.of("outputbucket", "tldthree.txt");
|
||||
String evenMoreTlds = new String(gcsUtils.readBytesFrom(thirdTldFile), UTF_8).trim();
|
||||
assertThat(evenMoreTlds).isEqualTo("cupid.tldthree,");
|
||||
verifyExportedToDrive("brouhaha", "dasher.tld,\nprancer.tld,");
|
||||
verifyExportedToDrive("hooray", "buddy.tldtwo,\nrudolph.tldtwo,\nsanta.tldtwo,");
|
||||
verifyExportedToDrive("brouhaha", "registered_domains_tld.txt", "dasher.tld,\nprancer.tld,");
|
||||
verifyExportedToDrive(
|
||||
"hooray", "registered_domains_tldtwo.txt", "buddy.tldtwo,\nrudolph.tldtwo,\nsanta.tldtwo,");
|
||||
// tldthree does not have a drive id, so no export to drive is performed.
|
||||
verifyNoMoreInteractions(driveConnection);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ package google.registry.export;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.export.ExportReservedTermsAction.EXPORT_MIME_TYPE;
|
||||
import static google.registry.export.ExportReservedTermsAction.RESERVED_TERMS_FILENAME;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.persistReservedList;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
@@ -77,7 +76,7 @@ public class ExportReservedTermsActionTest {
|
||||
runAction("tld");
|
||||
byte[] expected = "# This is a disclaimer.\ncat\nlol\n".getBytes(UTF_8);
|
||||
verify(driveConnection)
|
||||
.createOrUpdateFile(RESERVED_TERMS_FILENAME, EXPORT_MIME_TYPE, "brouhaha", expected);
|
||||
.createOrUpdateFile("reserved_terms_tld.txt", EXPORT_MIME_TYPE, "brouhaha", expected);
|
||||
assertThat(response.getStatus()).isEqualTo(SC_OK);
|
||||
assertThat(response.getPayload()).isEqualTo("1001");
|
||||
}
|
||||
|
||||
@@ -16,9 +16,9 @@ package google.registry.export;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.export.SyncGroupMembersAction.getGroupEmailAddressForContactType;
|
||||
import static google.registry.model.registrar.RegistrarPocBase.Type.ADMIN;
|
||||
import static google.registry.model.registrar.RegistrarPocBase.Type.MARKETING;
|
||||
import static google.registry.model.registrar.RegistrarPocBase.Type.TECH;
|
||||
import static google.registry.model.registrar.RegistrarPoc.Type.ADMIN;
|
||||
import static google.registry.model.registrar.RegistrarPoc.Type.MARKETING;
|
||||
import static google.registry.model.registrar.RegistrarPoc.Type.TECH;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.testing.DatabaseHelper.loadRegistrar;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
|
||||
@@ -38,7 +38,6 @@ import google.registry.model.common.Cursor;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarAddress;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
import google.registry.model.registrar.RegistrarPocBase;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
@@ -158,8 +157,7 @@ public class SyncRegistrarsSheetTest {
|
||||
.setName("Jane Doe")
|
||||
.setEmailAddress("contact@example.com")
|
||||
.setPhoneNumber("+1.1234567890")
|
||||
.setTypes(
|
||||
ImmutableSet.of(RegistrarPocBase.Type.ADMIN, RegistrarPocBase.Type.BILLING))
|
||||
.setTypes(ImmutableSet.of(RegistrarPoc.Type.ADMIN, RegistrarPoc.Type.BILLING))
|
||||
.build(),
|
||||
new RegistrarPoc.Builder()
|
||||
.setRegistrar(registrar)
|
||||
@@ -167,7 +165,7 @@ public class SyncRegistrarsSheetTest {
|
||||
.setEmailAddress("john.doe@example.tld")
|
||||
.setPhoneNumber("+1.1234567890")
|
||||
.setFaxNumber("+1.1234567891")
|
||||
.setTypes(ImmutableSet.of(RegistrarPocBase.Type.ADMIN))
|
||||
.setTypes(ImmutableSet.of(RegistrarPoc.Type.ADMIN))
|
||||
// Purposely flip the internal/external admin/tech
|
||||
// distinction to make sure we're not relying on it. Sigh.
|
||||
.setVisibleInWhoisAsAdmin(false)
|
||||
@@ -177,7 +175,7 @@ public class SyncRegistrarsSheetTest {
|
||||
.setRegistrar(registrar)
|
||||
.setName("Jane Smith")
|
||||
.setEmailAddress("pride@example.net")
|
||||
.setTypes(ImmutableSet.of(RegistrarPocBase.Type.TECH))
|
||||
.setTypes(ImmutableSet.of(RegistrarPoc.Type.TECH))
|
||||
.build());
|
||||
// Use registrar key for contacts' parent.
|
||||
DateTime registrarCreationTime = persistResource(registrar).getCreationTime();
|
||||
@@ -199,37 +197,37 @@ public class SyncRegistrarsSheetTest {
|
||||
.containsEntry(
|
||||
"primaryContacts",
|
||||
"""
|
||||
Jane Doe
|
||||
contact@example.com
|
||||
Tel: +1.1234567890
|
||||
Types: [ADMIN, BILLING]
|
||||
Visible in registrar WHOIS query as Admin contact: No
|
||||
Visible in registrar WHOIS query as Technical contact: No
|
||||
Phone number and email visible in domain WHOIS query as Registrar Abuse contact\
|
||||
info: No
|
||||
Jane Doe
|
||||
contact@example.com
|
||||
Tel: +1.1234567890
|
||||
Types: [ADMIN, BILLING]
|
||||
Visible in registrar WHOIS query as Admin contact: No
|
||||
Visible in registrar WHOIS query as Technical contact: No
|
||||
Phone number and email visible in domain WHOIS query as Registrar Abuse contact\
|
||||
info: No
|
||||
|
||||
John Doe
|
||||
john.doe@example.tld
|
||||
Tel: +1.1234567890
|
||||
Fax: +1.1234567891
|
||||
Types: [ADMIN]
|
||||
Visible in registrar WHOIS query as Admin contact: No
|
||||
Visible in registrar WHOIS query as Technical contact: Yes
|
||||
Phone number and email visible in domain WHOIS query as Registrar Abuse contact\
|
||||
info: No
|
||||
""");
|
||||
John Doe
|
||||
john.doe@example.tld
|
||||
Tel: +1.1234567890
|
||||
Fax: +1.1234567891
|
||||
Types: [ADMIN]
|
||||
Visible in registrar WHOIS query as Admin contact: No
|
||||
Visible in registrar WHOIS query as Technical contact: Yes
|
||||
Phone number and email visible in domain WHOIS query as Registrar Abuse contact\
|
||||
info: No
|
||||
""");
|
||||
assertThat(row)
|
||||
.containsEntry(
|
||||
"techContacts",
|
||||
"""
|
||||
Jane Smith
|
||||
pride@example.net
|
||||
Types: [TECH]
|
||||
Visible in registrar WHOIS query as Admin contact: No
|
||||
Visible in registrar WHOIS query as Technical contact: No
|
||||
Phone number and email visible in domain WHOIS query as Registrar Abuse contact\
|
||||
info: No
|
||||
""");
|
||||
Jane Smith
|
||||
pride@example.net
|
||||
Types: [TECH]
|
||||
Visible in registrar WHOIS query as Admin contact: No
|
||||
Visible in registrar WHOIS query as Technical contact: No
|
||||
Phone number and email visible in domain WHOIS query as Registrar Abuse contact\
|
||||
info: No
|
||||
""");
|
||||
assertThat(row).containsEntry("marketingContacts", "");
|
||||
assertThat(row).containsEntry("abuseContacts", "");
|
||||
assertThat(row).containsEntry("whoisInquiryContacts", "");
|
||||
@@ -238,30 +236,30 @@ public class SyncRegistrarsSheetTest {
|
||||
.containsEntry(
|
||||
"billingContacts",
|
||||
"""
|
||||
Jane Doe
|
||||
contact@example.com
|
||||
Tel: +1.1234567890
|
||||
Types: [ADMIN, BILLING]
|
||||
Visible in registrar WHOIS query as Admin contact: No
|
||||
Visible in registrar WHOIS query as Technical contact: No
|
||||
Phone number and email visible in domain WHOIS query as Registrar Abuse contact\
|
||||
info: No
|
||||
""");
|
||||
Jane Doe
|
||||
contact@example.com
|
||||
Tel: +1.1234567890
|
||||
Types: [ADMIN, BILLING]
|
||||
Visible in registrar WHOIS query as Admin contact: No
|
||||
Visible in registrar WHOIS query as Technical contact: No
|
||||
Phone number and email visible in domain WHOIS query as Registrar Abuse contact\
|
||||
info: No
|
||||
""");
|
||||
assertThat(row).containsEntry("contactsMarkedAsWhoisAdmin", "");
|
||||
assertThat(row)
|
||||
.containsEntry(
|
||||
"contactsMarkedAsWhoisTech",
|
||||
"""
|
||||
John Doe
|
||||
john.doe@example.tld
|
||||
Tel: +1.1234567890
|
||||
Fax: +1.1234567891
|
||||
Types: [ADMIN]
|
||||
Visible in registrar WHOIS query as Admin contact: No
|
||||
Visible in registrar WHOIS query as Technical contact: Yes
|
||||
Phone number and email visible in domain WHOIS query as Registrar Abuse contact\
|
||||
info: No
|
||||
""");
|
||||
John Doe
|
||||
john.doe@example.tld
|
||||
Tel: +1.1234567890
|
||||
Fax: +1.1234567891
|
||||
Types: [ADMIN]
|
||||
Visible in registrar WHOIS query as Admin contact: No
|
||||
Visible in registrar WHOIS query as Technical contact: Yes
|
||||
Phone number and email visible in domain WHOIS query as Registrar Abuse contact\
|
||||
info: No
|
||||
""");
|
||||
assertThat(row).containsEntry("emailAddress", "nowhere@example.org");
|
||||
assertThat(row).containsEntry(
|
||||
"address.street", "I get fallen back upon since there's no l10n addr");
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.flows;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.flows.CookieSessionMetadata.COOKIE_NAME;
|
||||
import static google.registry.flows.CookieSessionMetadata.decode;
|
||||
import static google.registry.flows.CookieSessionMetadata.encode;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.testing.FakeResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Unit tests for {@link CookieSessionMetadata}. */
|
||||
public class CookieSessionMetadataTest {
|
||||
|
||||
private HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
private FakeResponse response = new FakeResponse();
|
||||
private CookieSessionMetadata cookieSessionMetadata = new CookieSessionMetadata(request);
|
||||
|
||||
@Test
|
||||
void testNoCookie() {
|
||||
assertThat(cookieSessionMetadata.getRegistrarId()).isNull();
|
||||
assertThat(cookieSessionMetadata.getFailedLoginAttempts()).isEqualTo(0);
|
||||
assertThat(cookieSessionMetadata.getServiceExtensionUris()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCookieWithAllFields() {
|
||||
when(request.getHeader("Cookie"))
|
||||
.thenReturn(
|
||||
"THIS_COOKIE=foo; SESSION_INFO="
|
||||
+ encode(
|
||||
"CookieSessionMetadata{clientId=test_registrar, failedLoginAttempts=5, "
|
||||
+ " serviceExtensionUris=A|B|C}")
|
||||
+ "; THAT_COOKIE=bar");
|
||||
cookieSessionMetadata = new CookieSessionMetadata(request);
|
||||
assertThat(cookieSessionMetadata.getRegistrarId()).isEqualTo("test_registrar");
|
||||
assertThat(cookieSessionMetadata.getFailedLoginAttempts()).isEqualTo(5);
|
||||
assertThat(cookieSessionMetadata.getServiceExtensionUris()).containsExactly("A", "B", "C");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCookieWithNullRegistrar() {
|
||||
when(request.getHeader("Cookie"))
|
||||
.thenReturn(
|
||||
"SESSION_INFO="
|
||||
+ encode(
|
||||
"CookieSessionMetadata{clientId=null, failedLoginAttempts=5, "
|
||||
+ " serviceExtensionUris=A|B|C}"));
|
||||
cookieSessionMetadata = new CookieSessionMetadata(request);
|
||||
assertThat(cookieSessionMetadata.getRegistrarId()).isNull();
|
||||
assertThat(cookieSessionMetadata.getFailedLoginAttempts()).isEqualTo(5);
|
||||
assertThat(cookieSessionMetadata.getServiceExtensionUris()).containsExactly("A", "B", "C");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCookieWithEmptyExtension() {
|
||||
when(request.getHeader("Cookie"))
|
||||
.thenReturn(
|
||||
"SESSION_INFO="
|
||||
+ encode(
|
||||
"CookieSessionMetadata{clientId=test_registrar, failedLoginAttempts=5, "
|
||||
+ " serviceExtensionUris=}"));
|
||||
cookieSessionMetadata = new CookieSessionMetadata(request);
|
||||
assertThat(cookieSessionMetadata.getRegistrarId()).isEqualTo("test_registrar");
|
||||
assertThat(cookieSessionMetadata.getFailedLoginAttempts()).isEqualTo(5);
|
||||
assertThat(cookieSessionMetadata.getServiceExtensionUris()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCookieWithSingleExtension() {
|
||||
when(request.getHeader("Cookie"))
|
||||
.thenReturn(
|
||||
"SESSION_INFO="
|
||||
+ encode(
|
||||
"CookieSessionMetadata{clientId=test_registrar, failedLoginAttempts=5, "
|
||||
+ " serviceExtensionUris=Foo}"));
|
||||
cookieSessionMetadata = new CookieSessionMetadata(request);
|
||||
assertThat(cookieSessionMetadata.getRegistrarId()).isEqualTo("test_registrar");
|
||||
assertThat(cookieSessionMetadata.getFailedLoginAttempts()).isEqualTo(5);
|
||||
assertThat(cookieSessionMetadata.getServiceExtensionUris()).containsExactly("Foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIncrementFailedLoginAttempts() {
|
||||
when(request.getHeader("Cookie"))
|
||||
.thenReturn(
|
||||
"SESSION_INFO="
|
||||
+ encode(
|
||||
"CookieSessionMetadata{clientId=test_registrar, failedLoginAttempts=5, "
|
||||
+ " serviceExtensionUris=Foo}"));
|
||||
cookieSessionMetadata = new CookieSessionMetadata(request);
|
||||
cookieSessionMetadata.incrementFailedLoginAttempts();
|
||||
assertThat(cookieSessionMetadata.getRegistrarId()).isEqualTo("test_registrar");
|
||||
assertThat(cookieSessionMetadata.getFailedLoginAttempts()).isEqualTo(6);
|
||||
assertThat(cookieSessionMetadata.getServiceExtensionUris()).containsExactly("Foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testResetFailedLoginAttempts() {
|
||||
when(request.getHeader("Cookie"))
|
||||
.thenReturn(
|
||||
"SESSION_INFO="
|
||||
+ encode(
|
||||
"CookieSessionMetadata{clientId=test_registrar, failedLoginAttempts=5, "
|
||||
+ " serviceExtensionUris=Foo}"));
|
||||
cookieSessionMetadata = new CookieSessionMetadata(request);
|
||||
cookieSessionMetadata.resetFailedLoginAttempts();
|
||||
assertThat(cookieSessionMetadata.getRegistrarId()).isEqualTo("test_registrar");
|
||||
assertThat(cookieSessionMetadata.getFailedLoginAttempts()).isEqualTo(0);
|
||||
assertThat(cookieSessionMetadata.getServiceExtensionUris()).containsExactly("Foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetRegistrarId() {
|
||||
when(request.getHeader("Cookie"))
|
||||
.thenReturn(
|
||||
"SESSION_INFO="
|
||||
+ encode(
|
||||
"CookieSessionMetadata{clientId=test_registrar, failedLoginAttempts=5, "
|
||||
+ " serviceExtensionUris=Foo}"));
|
||||
cookieSessionMetadata = new CookieSessionMetadata(request);
|
||||
cookieSessionMetadata.setRegistrarId("new_registrar");
|
||||
assertThat(cookieSessionMetadata.getRegistrarId()).isEqualTo("new_registrar");
|
||||
assertThat(cookieSessionMetadata.getFailedLoginAttempts()).isEqualTo(5);
|
||||
assertThat(cookieSessionMetadata.getServiceExtensionUris()).containsExactly("Foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetExtensions() {
|
||||
when(request.getHeader("Cookie"))
|
||||
.thenReturn(
|
||||
"SESSION_INFO="
|
||||
+ encode(
|
||||
"CookieSessionMetadata{clientId=test_registrar, failedLoginAttempts=5, "
|
||||
+ " serviceExtensionUris=Foo}"));
|
||||
cookieSessionMetadata = new CookieSessionMetadata(request);
|
||||
cookieSessionMetadata.setServiceExtensionUris(ImmutableSet.of("Bar", "Baz", "foo:bar:baz-1.3"));
|
||||
assertThat(cookieSessionMetadata.getRegistrarId()).isEqualTo("test_registrar");
|
||||
assertThat(cookieSessionMetadata.getFailedLoginAttempts()).isEqualTo(5);
|
||||
assertThat(cookieSessionMetadata.getServiceExtensionUris())
|
||||
.containsExactly("Bar", "Baz", "foo:bar:baz-1.3");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetEmptyExtensions() {
|
||||
when(request.getHeader("Cookie"))
|
||||
.thenReturn(
|
||||
"SESSION_INFO="
|
||||
+ encode(
|
||||
"CookieSessionMetadata{clientId=test_registrar, failedLoginAttempts=5, "
|
||||
+ " serviceExtensionUris=Foo}"));
|
||||
cookieSessionMetadata = new CookieSessionMetadata(request);
|
||||
cookieSessionMetadata.setServiceExtensionUris(ImmutableSet.of());
|
||||
assertThat(cookieSessionMetadata.getRegistrarId()).isEqualTo("test_registrar");
|
||||
assertThat(cookieSessionMetadata.getFailedLoginAttempts()).isEqualTo(5);
|
||||
assertThat(cookieSessionMetadata.getServiceExtensionUris()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInvalidate() {
|
||||
when(request.getHeader("Cookie"))
|
||||
.thenReturn(
|
||||
"SESSION_INFO="
|
||||
+ encode(
|
||||
"CookieSessionMetadata{clientId=test_registrar, failedLoginAttempts=5, "
|
||||
+ " serviceExtensionUris=Foo}"));
|
||||
cookieSessionMetadata = new CookieSessionMetadata(request);
|
||||
cookieSessionMetadata.invalidate();
|
||||
assertThat(cookieSessionMetadata.getRegistrarId()).isNull();
|
||||
assertThat(cookieSessionMetadata.getFailedLoginAttempts()).isEqualTo(0);
|
||||
assertThat(cookieSessionMetadata.getServiceExtensionUris()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSave() {
|
||||
cookieSessionMetadata.save(response);
|
||||
String value =
|
||||
decode(
|
||||
response.getHeaders().get("Set-Cookie").toString().substring(COOKIE_NAME.length() + 1));
|
||||
assertThat(value)
|
||||
.isEqualTo(
|
||||
"CookieSessionMetadata{clientId=null, failedLoginAttempts=0, serviceExtensionUris=}");
|
||||
cookieSessionMetadata.setRegistrarId("new_registrar");
|
||||
cookieSessionMetadata.setServiceExtensionUris(ImmutableSet.of("Bar", "Baz"));
|
||||
cookieSessionMetadata.incrementFailedLoginAttempts();
|
||||
cookieSessionMetadata.save(response);
|
||||
value =
|
||||
decode(
|
||||
response.getHeaders().get("Set-Cookie").toString().substring(COOKIE_NAME.length() + 1));
|
||||
assertThat(value)
|
||||
.isEqualTo(
|
||||
"CookieSessionMetadata{clientId=new_registrar, failedLoginAttempts=1,"
|
||||
+ " serviceExtensionUris=Bar|Baz}");
|
||||
}
|
||||
}
|
||||
@@ -12,17 +12,19 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
package google.registry.flows;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.same;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import google.registry.testing.FakeHttpSession;
|
||||
import com.google.common.io.BaseEncoding;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
@@ -36,18 +38,22 @@ class EppTlsActionTest {
|
||||
EppTlsAction action = new EppTlsAction();
|
||||
action.inputXmlBytes = INPUT_XML_BYTES;
|
||||
action.tlsCredentials = mock(TlsCredentials.class);
|
||||
action.session = new FakeHttpSession();
|
||||
action.session.setAttribute("REGISTRAR_ID", "ClientIdentifier");
|
||||
action.request = mock(HttpServletRequest.class);
|
||||
when(action.request.getHeader("Cookie"))
|
||||
.thenReturn(
|
||||
"SESSION_INFO="
|
||||
+ BaseEncoding.base64Url().encode("clientId=ClientIdentifier".getBytes(US_ASCII)));
|
||||
action.eppRequestHandler = mock(EppRequestHandler.class);
|
||||
action.run();
|
||||
ArgumentCaptor<SessionMetadata> captor = ArgumentCaptor.forClass(SessionMetadata.class);
|
||||
verify(action.eppRequestHandler).executeEpp(
|
||||
captor.capture(),
|
||||
same(action.tlsCredentials),
|
||||
eq(EppRequestSource.TLS),
|
||||
eq(false),
|
||||
eq(false),
|
||||
eq(INPUT_XML_BYTES));
|
||||
verify(action.eppRequestHandler)
|
||||
.executeEpp(
|
||||
captor.capture(),
|
||||
same(action.tlsCredentials),
|
||||
eq(EppRequestSource.TLS),
|
||||
eq(false),
|
||||
eq(false),
|
||||
eq(INPUT_XML_BYTES));
|
||||
assertThat(captor.getValue().getRegistrarId()).isEqualTo("ClientIdentifier");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarBase.State;
|
||||
import google.registry.model.registrar.Registrar.State;
|
||||
import google.registry.model.reporting.DomainTransactionRecord;
|
||||
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
|
||||
@@ -93,7 +93,7 @@ import google.registry.model.domain.token.AllocationToken.TokenStatus;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarBase.State;
|
||||
import google.registry.model.registrar.Registrar.State;
|
||||
import google.registry.model.reporting.DomainTransactionRecord;
|
||||
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
|
||||
@@ -69,7 +69,7 @@ import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarBase.State;
|
||||
import google.registry.model.registrar.Registrar.State;
|
||||
import google.registry.model.reporting.DomainTransactionRecord;
|
||||
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user