1
0
mirror of https://github.com/google/nomulus synced 2026-07-06 00:04:50 +00:00

Compare commits

...

8 Commits

Author SHA1 Message Date
Pavlo Tkach ab5f6cc229 Add environment support to the console build (#2539) 2024-08-30 18:31:28 +00:00
gbrodman 1765f4f0b4 Allow skip of emailing/uploading for activity reports (#2538)
This will help us if/when we need to run the report generation multiple
times, or for past dates and we don't want to send extra emails or
upload any extra reports to ICANN.
2024-08-26 20:25:31 +00:00
gbrodman e88c6e1550 Update activity/txn reporting to use new GAE log format (#2535)
Instead of having to parse the protoPayload.line from the request logs,
we just want to inspect the textPayload from the app logs (stored in a
separate table). This applies to the EPP metrics from the activity
reporting and the attempted-adds column for the transaction reporting.
2024-08-26 19:41:40 +00:00
Pavlo Tkach 1739c6d74f Update node.js to v22 (#2537) 2024-08-26 18:15:39 +00:00
Pavlo Tkach 66513a114e Add OT&E UI to the new console (#2536) 2024-08-23 20:53:45 +00:00
Pavlo Tkach 0e808a4c01 Add OT&E create and status to the new console (#2534) 2024-08-22 20:03:56 +00:00
Lai Jiang 4e013603be Make GKE networking work more properly (#2531) 2024-08-22 13:10:56 +00:00
gbrodman 730585cd14 Fix front-end unit tests (#2529)
This doesn't really add any tests, and we'll require many more additions
if we actually want to have full unit testing, but this at least makes
the tests pass when running `npm test`.
2024-08-21 16:39:29 +00:00
117 changed files with 1940 additions and 605 deletions
+2 -1
View File
@@ -60,7 +60,7 @@ dependencyLocking {
node {
download = false
version = "20.10.0"
version = "22.7.0"
}
wrapper {
@@ -119,6 +119,7 @@ if (environment == '') {
rootProject.ext.environment = environment
rootProject.ext.gcpProject = gcpProject
rootProject.ext.baseDomain = baseDomains[environment]
rootProject.ext.prodOrSandboxEnv = environment in ['production', 'sandbox']
// Function to verify that the deployment parameters have been set.
+1 -1
View File
@@ -13,7 +13,7 @@ Webapp is deployed with the nomulus default service war to Google App Engine.
During nomulus default service war build task, gradle script triggers the
following:
1) Console webapp build script `buildConsoleWebappProd`, which installs
1) Console webapp build script `buildConsoleWebapp`, which installs
dependencies, assembles a compiled ts -> js, minified, optimized static
artifact (html, css, js)
2) Artifact assembled in step 1 then gets copied to core project web artifact
+42
View File
@@ -63,6 +63,39 @@
],
"outputHashing": "all"
},
"sandbox": {
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.sandbox.ts"
}
],
"outputHashing": "all"
},
"crash": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
},
"alpha": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
},
"development": {
"optimization": false,
"extractLicenses": false,
@@ -78,6 +111,15 @@
"production": {
"buildTarget": "console-webapp:build:production"
},
"alpha": {
"buildTarget": "console-webapp:build:alpha"
},
"crash": {
"buildTarget": "console-webapp:build:crash"
},
"sandbox": {
"buildTarget": "console-webapp:build:sandbox"
},
"development": {
"buildTarget": "console-webapp:build:development"
}
+12 -11
View File
@@ -37,17 +37,16 @@ task runConsoleWebappUnitTests(type: Exec) {
args 'run', 'test'
}
task buildConsoleWebappNonProd(type: Exec) {
task buildConsoleWebapp(type: Exec) {
workingDir "${consoleDir}/"
executable 'npm'
args 'run', 'build'
}
// Keeping the same as non prod for now before we figure out optimization we want to include
task buildConsoleWebappProd(type: Exec) {
workingDir "${consoleDir}/"
executable 'npm'
args 'run', 'build'
def configuration = project.hasProperty('configuration') ?
project.getProperty('configuration') :
'production'
args 'run', "build", "--configuration=${configuration}"
doFirst {
println "Building console for environment: ${configuration}"
}
}
task applyFormatting(type: Exec) {
@@ -68,8 +67,10 @@ task deploy(type: Exec) {
args 'app', 'deploy', "${projectParam}", '--quiet'
}
tasks.buildConsoleWebappProd.dependsOn(tasks.npmInstallDeps)
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.deploy.dependsOn(tasks.buildConsoleWebappProd)
tasks.build.dependsOn(tasks.runConsoleWebappUnitTests)
tasks.deploy.dependsOn(tasks.buildConsoleWebapp)
+11
View File
@@ -3,6 +3,17 @@
module.exports = function (config) {
config.set({
customLaunchers: {
ChromeHeadless: {
base: 'Chrome',
flags: [
'--no-sandbox',
'--disable-gpu',
'--headless',
'--remote-debugging-port=9222'
]
}
},
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
+3 -3
View File
@@ -4,9 +4,9 @@
"scripts": {
"ng": "ng",
"start": "ng serve --proxy-config dev-proxy.config.json",
"build": "ng build --base-href=/console/",
"build": "ng build --base-href=/console/ --configuration=$npm_config_configuration",
"build:local": "ng build --base-href=/default/console/",
"watch": "ng build --watch --configuration development",
"watch": "ng build --watch --configuration=development",
"test": "ng test --browsers=ChromeHeadless --watch=false",
"run:dev": "",
"prettify": "npx prettier --write ./src/",
@@ -54,4 +54,4 @@
"prettier": "2.8.7",
"typescript": "~5.4.5"
}
}
}
+17 -1
View File
@@ -17,6 +17,9 @@ import { Route, RouterModule } from '@angular/router';
import { BillingInfoComponent } from './billingInfo/billingInfo.component';
import { DomainListComponent } from './domains/domainList.component';
import { HomeComponent } from './home/home.component';
import { RegistryLockVerifyComponent } from './lock/registryLockVerify.component';
import { NewOteComponent } from './ote/newOte.component';
import { OteStatusComponent } from './ote/oteStatus.component';
import { RegistrarDetailsComponent } from './registrar/registrarDetails.component';
import { RegistrarComponent } from './registrar/registrarsTable.component';
import { ResourcesComponent } from './resources/resources.component';
@@ -26,18 +29,31 @@ import { SettingsComponent } from './settings/settings.component';
import UsersComponent from './settings/users/users.component';
import WhoisComponent from './settings/whois/whois.component';
import { SupportComponent } from './support/support.component';
import { RegistryLockVerifyComponent } from './lock/registryLockVerify.component';
export interface RouteWithIcon extends Route {
iconName?: string;
}
export const PATHS = {
NewOteComponent: 'new-ote',
OteStatusComponent: 'ote-status/:registrarId',
};
export const routes: RouteWithIcon[] = [
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{
path: RegistryLockVerifyComponent.PATH,
component: RegistryLockVerifyComponent,
},
{
path: PATHS.NewOteComponent,
loadComponent: () =>
import('./ote/newOte.component').then((mod) => mod.NewOteComponent),
},
{
path: PATHS.OteStatusComponent,
loadComponent: () =>
import('./ote/oteStatus.component').then((mod) => mod.OteStatusComponent),
},
{ path: 'registrars', component: RegistrarComponent },
{
path: 'home',
+2 -2
View File
@@ -16,16 +16,16 @@ import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
import { MaterialModule } from './material.module';
import { BackendService } from './shared/services/backend.service';
import { AppRoutingModule } from './app-routing.module';
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [AppComponent],
imports: [RouterTestingModule, MaterialModule, BrowserAnimationsModule],
imports: [MaterialModule, BrowserAnimationsModule, AppRoutingModule],
providers: [
BackendService,
provideHttpClient(),
+4 -1
View File
@@ -30,7 +30,10 @@ import { DomainListComponent } from './domains/domainList.component';
import { RegistryLockComponent } from './domains/registryLock.component';
import { HeaderComponent } from './header/header.component';
import { HomeComponent } from './home/home.component';
import { RegistryLockVerifyComponent } from './lock/registryLockVerify.component';
import { NavigationComponent } from './navigation/navigation.component';
import { NewOteComponent } from './ote/newOte.component';
import { OteStatusComponent } from './ote/oteStatus.component';
import NewRegistrarComponent from './registrar/newRegistrar.component';
import { RegistrarDetailsComponent } from './registrar/registrarDetails.component';
import { RegistrarSelectorComponent } from './registrar/registrarSelector.component';
@@ -54,7 +57,6 @@ import { UserDataService } from './shared/services/userData.service';
import { SnackBarModule } from './snackbar.module';
import { SupportComponent } from './support/support.component';
import { TldsComponent } from './tlds/tlds.component';
import { RegistryLockVerifyComponent } from './lock/registryLockVerify.component';
@NgModule({
declarations: [
@@ -95,6 +97,7 @@ import { RegistryLockVerifyComponent } from './lock/registryLockVerify.component
MaterialModule,
SnackBarModule,
],
exports: [SelectedRegistrarWrapper],
providers: [
BackendService,
BreakPointObserverService,
@@ -20,6 +20,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MaterialModule } from '../material.module';
import { BackendService } from '../shared/services/backend.service';
import { DomainListComponent } from './domainList.component';
import { FormsModule } from '@angular/forms';
describe('DomainListComponent', () => {
let component: DomainListComponent;
@@ -28,7 +29,7 @@ describe('DomainListComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [DomainListComponent],
imports: [MaterialModule, BrowserAnimationsModule],
imports: [MaterialModule, BrowserAnimationsModule, FormsModule],
providers: [
BackendService,
provideHttpClient(),
@@ -0,0 +1,36 @@
<h1 class="mat-headline-4">Generate OT&E Accounts</h1>
<div class="console-app__new-ote">
@if (oteCreateResponseFormatted()) {
<h1>Generated Successfully</h1>
<mat-card appearance="outlined">
<mat-card-header>
<mat-card-title>Epp Credentials</mat-card-title>
<mat-card-subtitle
>Copy and paste this into an email to the registrars</mat-card-subtitle
>
</mat-card-header>
<mat-card-content>
<p>{{ oteCreateResponseFormatted() }}</p>
</mat-card-content>
</mat-card>
} @else {
<form (ngSubmit)="onSubmit()" [formGroup]="createOte">
<p>
<mat-form-field name="registrarId" appearance="outline">
<mat-label>Base Registrar Id: </mat-label>
<input matInput type="text" formControlName="registrarId" required />
</mat-form-field>
</p>
<p>
<mat-form-field name="registrarEmail" appearance="outline">
<mat-label>Contact Email: </mat-label>
<input matInput type="text" formControlName="registrarEmail" required />
<mat-hint
>Will be granted web-console access to the OTE registrars.</mat-hint
>
</mat-form-field>
</p>
<button mat-flat-button color="primary" type="submit">Save</button>
</form>
}
</div>
@@ -0,0 +1,11 @@
.console-app__new-ote {
max-width: 720px;
mat-card-content {
white-space: break-spaces;
padding: 20px;
}
mat-form-field {
width: 100%;
max-width: 350px;
}
}
@@ -0,0 +1,83 @@
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { HttpErrorResponse } from '@angular/common/http';
import { Component, computed, signal } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { MatSnackBar } from '@angular/material/snack-bar';
import { RegistrarService } from '../registrar/registrar.service';
import { MaterialModule } from '../material.module';
import { SnackBarModule } from '../snackbar.module';
export interface OteCreateResponse extends Map<string, string> {
password: string;
}
@Component({
selector: 'app-ote',
standalone: true,
imports: [MaterialModule, SnackBarModule],
templateUrl: './newOte.component.html',
styleUrls: ['./newOte.component.scss'],
})
export class NewOteComponent {
oteCreateResponse = signal<OteCreateResponse | undefined>(undefined);
readonly oteCreateResponseFormatted = computed(() => {
const oteCreateResponse = this.oteCreateResponse();
if (oteCreateResponse) {
const { password } = oteCreateResponse;
return Object.entries(oteCreateResponse)
.filter((entry) => entry[0] !== 'password')
.map(
([login, tld]) =>
`Login: ${login}\t\tPassword: ${password}\t\tTLD: ${tld}`
)
.join('\n');
}
return undefined;
});
createOte = new FormGroup({
registrarId: new FormControl('', [Validators.required]),
registrarEmail: new FormControl('', [Validators.required]),
});
constructor(
protected registrarService: RegistrarService,
private _snackBar: MatSnackBar
) {}
onSubmit() {
if (this.createOte.valid) {
const { registrarId, registrarEmail } = this.createOte.value;
this.registrarService
.generateOte(
{
registrarId,
registrarEmail,
},
registrarId || ''
)
.subscribe({
next: (oteCreateResponse: OteCreateResponse) => {
this.oteCreateResponse.set(oteCreateResponse);
},
error: (err: HttpErrorResponse) => {
this._snackBar.open(err.error || err.message);
},
});
}
}
}
@@ -0,0 +1,28 @@
<h1 class="mat-headline-4">OT&E Status Check</h1>
@if(registrarId() === null) {
<h1>Missing registrarId param</h1>
} @else if(isOte()) {
<h1 *ngIf="oteStatusResponse().length">
Status:
<span>{{ oteStatusUnfinished().length ? "Unfinished" : "Completed" }}</span>
</h1>
<div class="console-app__ote-status">
@if(oteStatusCompleted().length) {
<div class="console-app__ote-status_completed">
<h1>Completed</h1>
<div *ngFor="let entry of oteStatusCompleted()">
<mat-icon>check_box</mat-icon>{{ entry.description }}
</div>
</div>
} @if(oteStatusUnfinished().length) {
<div class="console-app__ote-status_unfinished">
<h1>Unfinished</h1>
<div *ngFor="let entry of oteStatusUnfinished()">
<mat-icon>check_box_outline_blank</mat-icon>{{ entry.description }}
</div>
</div>
}
</div>
} @else {
<h1>Registrar {{ registrarId() }} is not an OT&E registrar</h1>
}
@@ -0,0 +1,28 @@
.console-app__ote-status {
max-width: 730px;
display: flex;
flex-wrap: wrap;
&_completed,
&_unfinished {
border: 1px solid #ddd;
padding: 20px;
border-radius: 10px;
margin: 0 20px 30px 0;
div {
display: flex;
min-width: 300px;
align-items: flex-start;
max-width: 300px;
margin-bottom: 10px;
padding-bottom: 5px;
border-bottom: 1px solid #ddd;
&:last-child {
border: none;
}
}
mat-icon {
min-width: 30px;
}
}
}
@@ -0,0 +1,79 @@
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { HttpErrorResponse } from '@angular/common/http';
import { Component, computed, OnInit, signal } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { RegistrarService } from '../registrar/registrar.service';
import { MaterialModule } from '../material.module';
import { SnackBarModule } from '../snackbar.module';
import { CommonModule } from '@angular/common';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { take } from 'rxjs';
export interface OteStatusResponse {
description: string;
requirement: number;
timesPerformed: number;
completed: boolean;
}
@Component({
selector: 'app-ote-status',
standalone: true,
imports: [MaterialModule, SnackBarModule, CommonModule],
templateUrl: './oteStatus.component.html',
styleUrls: ['./oteStatus.component.scss'],
})
export class OteStatusComponent implements OnInit {
registrarId = signal<string | null>(null);
oteStatusResponse = signal<OteStatusResponse[]>([]);
oteStatusCompleted = computed(() =>
this.oteStatusResponse().filter((v) => v.completed)
);
oteStatusUnfinished = computed(() =>
this.oteStatusResponse().filter((v) => !v.completed)
);
isOte = computed(
() =>
this.registrarService
.registrars()
.find((r) => r.registrarId === this.registrarId())
?.type?.toLowerCase() === 'ote'
);
constructor(
private route: ActivatedRoute,
protected registrarService: RegistrarService,
private _snackBar: MatSnackBar
) {}
ngOnInit(): void {
this.route.paramMap.pipe(take(1)).subscribe((params: ParamMap) => {
this.registrarId.set(params.get('registrarId'));
const registrarId = this.registrarId();
if (!registrarId) throw 'Missing registrarId param';
this.registrarService.oteStatus(registrarId).subscribe({
next: (oteStatusResponse: OteStatusResponse[]) => {
this.oteStatusResponse.set(oteStatusResponse);
},
error: (err: HttpErrorResponse) => {
this._snackBar.open(err.error || err.message);
},
});
});
}
}
@@ -17,6 +17,8 @@ import { Observable, switchMap, tap } from 'rxjs';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Router } from '@angular/router';
import { OteCreateResponse } from '../ote/newOte.component';
import { OteStatusResponse } from '../ote/oteStatus.component';
import { BackendService } from '../shared/services/backend.service';
import {
GlobalLoader,
@@ -69,6 +71,7 @@ export interface Registrar
registrarId: string;
registrarName: string;
registryLockAllowed?: boolean;
type?: string;
}
@Injectable({
@@ -149,4 +152,17 @@ export class RegistrarService implements GlobalLoader {
loadingTimeout() {
this._snackBar.open('Timeout loading registrars');
}
generateOte(
oteForm: Object,
registrarId: string
): Observable<OteCreateResponse> {
return this.backend
.generateOte(oteForm, registrarId)
.pipe(tap((_) => this.loadRegistrars()));
}
oteStatus(registrarId: string): Observable<OteStatusResponse[]> {
return this.backend.getOteStatus(registrarId);
}
}
@@ -8,6 +8,15 @@
</button>
<div class="spacer"></div>
@if(!inEdit && !registrarNotFound) {
<button
*ngIf="oteButtonVisible"
mat-stroked-button
(click)="checkOteStatus()"
aria-label="Check OT&E account"
[elementId]="getElementIdForOteBlock()"
>
Check OT&E Status
</button>
<button
mat-flat-button
color="primary"
@@ -18,8 +18,10 @@ import { MatChipInputEvent } from '@angular/material/chips';
import { MatSnackBar } from '@angular/material/snack-bar';
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
import { Subscription } from 'rxjs';
import { RESTRICTED_ELEMENTS } from '../shared/directives/userLevelVisiblity.directive';
import { Registrar, RegistrarService } from './registrar.service';
import { RegistrarComponent, columns } from './registrarsTable.component';
import { environment } from '../../environments/environment';
@Component({
selector: 'app-registrar-details',
@@ -29,8 +31,9 @@ import { RegistrarComponent, columns } from './registrarsTable.component';
export class RegistrarDetailsComponent implements OnInit {
public static PATH = 'registrars/:id';
inEdit: boolean = false;
oteButtonVisible = environment.sandbox;
registrarInEdit!: Registrar;
registrarNotFound: boolean = false;
registrarNotFound: boolean = true;
columns = columns.filter((c) => !c.hiddenOnDetailsCard);
private subscription!: Subscription;
@@ -70,6 +73,16 @@ export class RegistrarDetailsComponent implements OnInit {
];
}
checkOteStatus() {
this.router.navigate(['ote-status/', this.registrarInEdit.registrarId], {
queryParamsHandling: 'merge',
});
}
getElementIdForOteBlock() {
return RESTRICTED_ELEMENTS.OTE;
}
removeTLD(tld: string) {
this.registrarInEdit.allowedTlds = this.registrarInEdit.allowedTlds?.filter(
(v) => v != tld
@@ -91,6 +104,6 @@ export class RegistrarDetailsComponent implements OnInit {
}
ngOnDestroy() {
this.subscription.unsubscribe();
this.subscription && this.subscription.unsubscribe();
}
}
@@ -20,6 +20,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MaterialModule } from '../material.module';
import { BackendService } from '../shared/services/backend.service';
import { RegistrarSelectorComponent } from './registrarSelector.component';
import { FormsModule } from '@angular/forms';
describe('RegistrarSelectorComponent', () => {
let component: RegistrarSelectorComponent;
@@ -28,7 +29,7 @@ describe('RegistrarSelectorComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [RegistrarSelectorComponent],
imports: [MaterialModule, BrowserAnimationsModule],
imports: [MaterialModule, BrowserAnimationsModule, FormsModule],
providers: [
BackendService,
provideHttpClient(),
@@ -4,7 +4,18 @@
<div class="console-app__registrars">
<div class="console-app__registrars-header">
<h1 class="mat-headline-4">Registrars</h1>
<div class="spacer"></div>
<button
mat-stroked-button
*ngIf="oteButtonVisible"
(click)="createOteAccount()"
aria-label="Generate OT&E accounts"
[elementId]="getElementIdForOteBlock()"
>
Create OT&E accounts
</button>
<button
class="console-app__registrars-new"
mat-flat-button
color="primary"
(click)="openNewRegistrar()"
@@ -10,6 +10,10 @@
min-width: $min-width !important;
}
&__registrars-new {
margin-left: 20px;
}
&__registrars-header {
display: flex;
justify-content: space-between;
@@ -17,7 +17,10 @@ import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { Router } from '@angular/router';
import { RESTRICTED_ELEMENTS } from '../shared/directives/userLevelVisiblity.directive';
import { Registrar, RegistrarService } from './registrar.service';
import { PATHS } from '../app-routing.module';
import { environment } from '../../environments/environment';
export const columns = [
{
@@ -80,7 +83,7 @@ export class RegistrarComponent {
public static PATH = 'registrars';
dataSource: MatTableDataSource<Registrar>;
columns = columns;
oteButtonVisible = environment.sandbox;
displayedColumns = this.columns.map((c) => c.columnDef);
@ViewChild(MatPaginator) paginator!: MatPaginator;
@@ -103,6 +106,14 @@ export class RegistrarComponent {
this.dataSource.sort = this.sort;
}
createOteAccount() {
this.router.navigate([PATHS.NewOteComponent]);
}
getElementIdForOteBlock() {
return RESTRICTED_ELEMENTS.OTE;
}
openDetails(registrarId: string) {
this.router.navigate(['registrars/', registrarId], {
queryParamsHandling: 'merge',
@@ -20,20 +20,18 @@ import { FormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { of } from 'rxjs';
import { MaterialModule } from 'src/app/material.module';
import {
Registrar,
RegistrarService,
} from 'src/app/registrar/registrar.service';
import { RegistrarService } from 'src/app/registrar/registrar.service';
import { BackendService } from 'src/app/shared/services/backend.service';
import SecurityComponent from './security.component';
import { SecurityService, apiToUiConverter } from './security.service';
import { SecurityService } from './security.service';
import SecurityEditComponent from './securityEdit.component';
import { MOCK_REGISTRAR_SERVICE } from 'src/testdata/registrar/registrar.service.mock';
describe('SecurityComponent', () => {
let component: SecurityComponent;
let fixture: ComponentFixture<SecurityComponent>;
let fetchSecurityDetailsSpy: Function;
let saveSpy: Function;
let dummyRegistrarService: RegistrarService;
beforeEach(async () => {
const securityServiceSpy = jasmine.createSpyObj(SecurityService, [
@@ -46,16 +44,13 @@ describe('SecurityComponent', () => {
saveSpy = securityServiceSpy.saveChanges;
dummyRegistrarService = {
registrar: { ipAddressAllowList: ['123.123.123.123'] },
} as RegistrarService;
await TestBed.configureTestingModule({
declarations: [SecurityComponent],
declarations: [SecurityEditComponent, SecurityComponent],
imports: [MaterialModule, BrowserAnimationsModule, FormsModule],
providers: [
BackendService,
{ provide: RegistrarService, useValue: dummyRegistrarService },
SecurityService,
{ provide: RegistrarService, useValue: MOCK_REGISTRAR_SERVICE },
provideHttpClient(),
provideHttpClientTesting(),
],
@@ -78,78 +73,65 @@ describe('SecurityComponent', () => {
expect(component).toBeTruthy();
});
it('should render ip allow list', waitForAsync(() => {
component.enableEdit();
it('should render security elements', waitForAsync(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(
Array.from(
fixture.nativeElement.querySelectorAll(
'.settings-security__ip-allowlist'
)
)
).toHaveSize(1);
expect(
fixture.nativeElement.querySelector('.settings-security__ip-allowlist')
.value
).toBe('123.123.123.123');
let listElems: Array<HTMLElement> = Array.from(
fixture.nativeElement.querySelectorAll('span.console-app__list-value')
);
expect(listElems).toHaveSize(8);
expect(listElems.map((e) => e.textContent)).toEqual([
'Change the password used for EPP logins',
'••••••••••••••',
'Restrict access to EPP production servers to the following IP/IPv6 addresses, or ranges like 1.1.1.0/24',
'123.123.123.123',
'X.509 PEM certificate for EPP production access',
'No client certificate on file.',
'X.509 PEM backup certificate for EPP production access',
'No failover certificate on file.',
]);
});
}));
it('should remove ip', waitForAsync(() => {
expect(
Array.from(
fixture.nativeElement.querySelectorAll(
'.settings-security__ip-allowlist'
)
)
).toHaveSize(1);
component.removeIpEntry(0);
component.dataSource.ipAddressAllowList =
component.dataSource.ipAddressAllowList?.splice(1);
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(
Array.from(
fixture.nativeElement.querySelectorAll(
'.settings-security__ip-allowlist'
)
)
).toHaveSize(0);
let listElems: Array<HTMLElement> = Array.from(
fixture.nativeElement.querySelectorAll('span.console-app__list-value')
);
expect(listElems.map((e) => e.textContent)).toContain(
'No IP addresses on file.'
);
});
}));
it('should toggle inEdit', () => {
expect(component.inEdit).toBeFalse();
component.enableEdit();
expect(component.inEdit).toBeTrue();
it('should toggle isEditingSecurity', () => {
expect(component.securityService.isEditingSecurity).toBeFalse();
component.editSecurity();
expect(component.securityService.isEditingSecurity).toBeTrue();
});
it('should create temporary data structure', () => {
expect(component.dataSource).toEqual(
apiToUiConverter(dummyRegistrarService.registrar)
);
component.removeIpEntry(0);
expect(component.dataSource).toEqual({ ipAddressAllowList: [] });
expect(dummyRegistrarService.registrar).toEqual({
ipAddressAllowList: ['123.123.123.123'],
} as Registrar);
component.cancel();
expect(component.dataSource).toEqual(
apiToUiConverter(dummyRegistrarService.registrar)
);
it('should toggle isEditingPassword', () => {
expect(component.securityService.isEditingPassword).toBeFalse();
component.editEppPassword();
expect(component.securityService.isEditingPassword).toBeTrue();
});
it('should call save', waitForAsync(async () => {
component.enableEdit();
fixture.detectChanges();
component.editSecurity();
await fixture.whenStable();
fixture.detectChanges();
const el = fixture.nativeElement.querySelector(
'.settings-security__clientCertificate'
'.console-app__clientCertificateValue'
);
el.value = 'test';
el.dispatchEvent(new Event('input'));
fixture.detectChanges();
await fixture.whenStable();
fixture.nativeElement
.querySelector('.settings-security__actions-save')
.querySelector('.settings-security__edit-save')
.click();
expect(saveSpy).toHaveBeenCalledOnceWith({
ipAddressAllowList: [{ value: '123.123.123.123' }],
@@ -35,6 +35,7 @@ export default class SecurityComponent {
if (this.registrarService.registrar()) {
this.dataSource = apiToUiConverter(this.registrarService.registrar());
this.securityService.isEditingSecurity = false;
this.securityService.isEditingPassword = false;
}
});
}
@@ -21,11 +21,13 @@ import { BackendService } from 'src/app/shared/services/backend.service';
import SecurityComponent from './security.component';
import {
SecurityService,
SecuritySettings,
SecuritySettingsBackendModel,
apiToUiConverter,
uiToApiConverter,
} from './security.service';
import {
SecuritySettings,
SecuritySettingsBackendModel,
} from 'src/app/registrar/registrar.service';
describe('SecurityService', () => {
const uiMockData: SecuritySettings = {
@@ -33,6 +33,7 @@
<p>X.509 PEM certificate for EPP production access.</p>
<mat-form-field appearance="outline">
<textarea
class="console-app__clientCertificateValue"
matInput
[(ngModel)]="dataSource.clientCertificate"
[ngModelOptions]="{ standalone: true }"
@@ -32,7 +32,14 @@ describe('WhoisComponent', () => {
imports: [MaterialModule, BrowserAnimationsModule],
providers: [
BackendService,
{ provide: RegistrarService, useValue: { registrar: {} } },
{
provide: RegistrarService,
useValue: {
registrar: function () {
return {};
},
},
},
provideHttpClient(),
provideHttpClientTesting(),
],
@@ -17,10 +17,11 @@ import { UserDataService } from '../services/userData.service';
export enum RESTRICTED_ELEMENTS {
REGISTRAR_ELEMENT,
OTE,
}
export const DISABLED_ELEMENTS_PER_ROLE = {
NONE: [RESTRICTED_ELEMENTS.REGISTRAR_ELEMENT],
NONE: [RESTRICTED_ELEMENTS.REGISTRAR_ELEMENT, RESTRICTED_ELEMENTS.OTE],
};
@Directive({
@@ -19,6 +19,8 @@ import { Observable, catchError, of, throwError } from 'rxjs';
import { DomainListResult } from 'src/app/domains/domainList.service';
import { DomainLocksResult } from 'src/app/domains/registryLock.service';
import { RegistryLockVerificationResponse } from 'src/app/lock/registryLockVerify.service';
import { OteCreateResponse } from 'src/app/ote/newOte.component';
import { OteStatusResponse } from 'src/app/ote/oteStatus.component';
import {
Registrar,
SecuritySettingsBackendModel,
@@ -198,6 +200,22 @@ export class BackendService {
.pipe(catchError((err) => this.errorCatcher<DomainLocksResult[]>(err)));
}
generateOte(
oteForm: Object,
registrarId: string
): Observable<OteCreateResponse> {
return this.http.post<OteCreateResponse>(
`/console-api/ote?registrarId=${registrarId}`,
oteForm
);
}
getOteStatus(registrarId: string) {
return this.http
.get<OteStatusResponse[]>(`/console-api/ote?registrarId=${registrarId}`)
.pipe(catchError((err) => this.errorCatcher<OteStatusResponse[]>(err)));
}
verifyRegistryLockRequest(
lockVerificationCode: string
): Observable<RegistryLockVerificationResponse> {
@@ -14,4 +14,5 @@
export const environment = {
production: true,
sandbox: false,
};
@@ -0,0 +1,18 @@
// 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.
export const environment = {
production: false,
sandbox: true,
};
@@ -18,6 +18,7 @@
export const environment = {
production: false,
sandbox: false,
};
/*
+1 -1
View File
@@ -18,7 +18,7 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
if (environment.production || environment.sandbox) {
enableProdMode();
}
@@ -0,0 +1,21 @@
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { RegistrarService } from 'src/app/registrar/registrar.service';
export const MOCK_REGISTRAR_SERVICE = {
registrar: function () {
return { ipAddressAllowList: ['123.123.123.123'] };
},
} as RegistrarService;
+2 -1
View File
@@ -29,5 +29,6 @@
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
},
"exclude": ["./src/testdata/*.*/*.ts"]
}
@@ -119,6 +119,18 @@ public final class RegistryConfig {
return config.gcpProject.projectIdNumber;
}
@Provides
@Config("backendServiceIds")
public static Map<String, Long> provideBackendServiceIds(RegistryConfigSettings config) {
return config.gcpProject.backendServiceIds;
}
@Provides
@Config("baseDomain")
public static String provideBaseDomain(RegistryConfigSettings config) {
return config.gcpProject.baseDomain;
}
@Provides
@Config("locationId")
public static String provideLocationId(RegistryConfigSettings config) {
@@ -1259,12 +1271,6 @@ public final class RegistryConfig {
return config.auth.oauthClientId;
}
@Provides
@Config("fallbackOauthClientId")
public static String provideFallbackOauthClientId(RegistryConfigSettings config) {
return config.auth.fallbackOauthClientId;
}
/**
* Provides the OAuth scopes required for accessing Google APIs using the default credential.
*/
@@ -56,13 +56,14 @@ public class RegistryConfigSettings {
public String bsaServiceUrl;
public String toolsServiceUrl;
public String pubapiServiceUrl;
public Map<String, Long> backendServiceIds;
public String baseDomain;
}
/** Configuration options for authenticating users. */
public static class Auth {
public List<String> allowedServiceAccountEmails;
public String oauthClientId;
public String fallbackOauthClientId;
}
/** Configuration options for accessing Google APIs. */
@@ -1,5 +1,5 @@
# This is the default configuration file for Nomulus. Do not make changes to it
# unless you are writing new features that requires you to. To customize an
# unless you are writing new features that require you to. To customize an
# individual deployment or environment, create a nomulus-config.yaml file in the
# WEB-INF/ directory overriding only the values you wish to change. You may need
# to override some of these values to configure and enable some services used in
@@ -24,6 +24,17 @@ gcpProject:
toolsServiceUrl: https://tools.example.com
pubapiServiceUrl: https://pubapi.example.com
# The backend service IDs created when setting up GKE routes. They will be included in the
# audience field in the JWT that IAP creates.
# See: https://cloud.google.com/iap/docs/signed-headers-howto#verifying_the_jwt_payload
backendServiceIds:
frontend: 12345
backend: 12345
pubapi: 12345
console: 12345
# The base domain name of the registry service. Services are reachable at [service].baseDomain.
baseDomain: registry.test
gSuite:
# Publicly accessible domain name of the running G Suite instance.
@@ -328,25 +339,21 @@ caching:
# Note: Only allowedServiceAccountEmails and oauthClientId should be configured.
# Other fields are related to OAuth-based authentication and will be removed.
auth:
# Service accounts (e.g. default service account, account used by Cloud
# Service accounts (e.g., default service account, account used by Cloud
# Scheduler) allowed to send authenticated requests.
allowedServiceAccountEmails:
- default-service-account-email@email.com
- cloud-scheduler-email@email.com
# OAuth 2.0 client ID that will be used as the audience in OIDC ID tokens sent
# from clients (e.g. proxy, nomulus tool, cloud tasks) for authentication. The
# from clients (e.g., proxy, nomulus tool, cloud tasks) for authentication. The
# same ID is the only one accepted by the regular OIDC or IAP authentication
# mechanisms. In most cases we should use the client ID created for IAP here,
# mechanisms. In most cases, we should use the client ID created for IAP here,
# as it allows requests bearing a token with this audience to be accepted by
# both IAP or regular OIDC. The clientId value in proxy config file should be
# the same as this one.
oauthClientId: iap-oauth-clientid
# Same as above, but serve as a fallback, so we can switch the client ID of
# the proxy without downtime.
fallbackOauthClientId: fallback-oauth-clientid
credentialOAuth:
# OAuth scopes required for accessing Google APIs using the default
# credential.
@@ -36,7 +36,7 @@ import javax.inject.Inject;
/** Action that manually triggers refresh of DNS information. */
@Action(
service = Action.Service.BACKEND,
path = "/_dr/dnsRefresh",
path = "/_dr/task/dnsRefresh",
automaticallyPrintOk = true,
auth = Auth.AUTH_ADMIN)
public final class RefreshDnsAction implements Runnable {
@@ -165,7 +165,7 @@
<!-- Manually refreshes DNS information. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/dnsRefresh</url-pattern>
<url-pattern>/_dr/task/dnsRefresh</url-pattern>
</servlet-mapping>
<!-- Fans out a cron task over an adjustable range of TLDs. -->
@@ -233,6 +233,7 @@ public class RegistrarBase extends UpdateAutoTimestampEntity implements Buildabl
String registrarName;
/** The type of this registrar. */
@Expose
@Column(nullable = false)
@Enumerated(EnumType.STRING)
Type type;
@@ -102,9 +102,11 @@ interface RegistryComponent {
class RegistryModule {
@Provides
static RequestHandler<RequestComponent> provideRequestHandler(
@Config("baseDomain") String baseDomain,
Provider<RequestComponent.Builder> componentProvider,
RequestAuthenticator requestAuthenticator) {
return RequestHandler.create(RequestComponent.class, componentProvider, requestAuthenticator);
return RequestHandler.create(
RequestComponent.class, baseDomain, componentProvider, requestAuthenticator);
}
}
}
@@ -113,6 +113,8 @@ import google.registry.ui.server.console.ConsoleDomainGetAction;
import google.registry.ui.server.console.ConsoleDomainListAction;
import google.registry.ui.server.console.ConsoleDumDownloadAction;
import google.registry.ui.server.console.ConsoleEppPasswordAction;
import google.registry.ui.server.console.ConsoleModule;
import google.registry.ui.server.console.ConsoleOteAction;
import google.registry.ui.server.console.ConsoleRegistryLockAction;
import google.registry.ui.server.console.ConsoleRegistryLockVerifyAction;
import google.registry.ui.server.console.ConsoleUpdateRegistrarAction;
@@ -121,15 +123,6 @@ import google.registry.ui.server.console.RegistrarsAction;
import google.registry.ui.server.console.settings.ContactAction;
import google.registry.ui.server.console.settings.SecurityAction;
import google.registry.ui.server.console.settings.WhoisRegistrarFieldsAction;
import google.registry.ui.server.registrar.ConsoleOteSetupAction;
import google.registry.ui.server.registrar.ConsoleRegistrarCreatorAction;
import google.registry.ui.server.registrar.ConsoleUiAction;
import google.registry.ui.server.registrar.OteStatusAction;
import google.registry.ui.server.registrar.RegistrarConsoleModule;
import google.registry.ui.server.registrar.RegistrarSettingsAction;
import google.registry.ui.server.registrar.RegistryLockGetAction;
import google.registry.ui.server.registrar.RegistryLockPostAction;
import google.registry.ui.server.registrar.RegistryLockVerifyAction;
import google.registry.whois.WhoisAction;
import google.registry.whois.WhoisHttpAction;
import google.registry.whois.WhoisModule;
@@ -142,6 +135,7 @@ import google.registry.whois.WhoisModule;
BillingModule.class,
CheckApiModule.class,
CloudDnsWriterModule.class,
ConsoleModule.class,
CronModule.class,
CustomLogicModule.class,
DnsCountQueryCoordinatorModule.class,
@@ -154,7 +148,6 @@ import google.registry.whois.WhoisModule;
LoadTestModule.class,
RdapModule.class,
RdeModule.class,
RegistrarConsoleModule.class,
ReportingModule.class,
RequestModule.class,
SheetModule.class,
@@ -186,16 +179,12 @@ interface RequestComponent {
ConsoleEppPasswordAction consoleEppPasswordAction();
ConsoleOteSetupAction consoleOteSetupAction();
ConsoleRegistrarCreatorAction consoleRegistrarCreatorAction();
ConsoleOteAction consoleOteAction();
ConsoleRegistryLockAction consoleRegistryLockAction();
ConsoleRegistryLockVerifyAction consoleRegistryLockVerifyAction();
ConsoleUiAction consoleUiAction();
ConsoleUpdateRegistrarAction consoleUpdateRegistrarAction();
ConsoleUserDataAction consoleUserDataAction();
@@ -254,8 +243,6 @@ interface RequestComponent {
NordnVerifyAction nordnVerifyAction();
OteStatusAction oteStatusAction();
PublishDnsUpdatesAction publishDnsUpdatesAction();
PublishInvoicesAction uploadInvoicesAction();
@@ -296,16 +283,8 @@ interface RequestComponent {
RefreshDnsOnHostRenameAction refreshDnsOnHostRenameAction();
RegistrarSettingsAction registrarSettingsAction();
RegistrarsAction registrarsAction();
RegistryLockGetAction registryLockGetAction();
RegistryLockPostAction registryLockPostAction();
RegistryLockVerifyAction registryLockVerifyAction();
RelockDomainAction relockDomainAction();
ResaveAllEppResourcesPipelineAction resaveAllEppResourcesPipelineAction();
@@ -29,6 +29,8 @@ import google.registry.ui.server.console.ConsoleDomainGetAction;
import google.registry.ui.server.console.ConsoleDomainListAction;
import google.registry.ui.server.console.ConsoleDumDownloadAction;
import google.registry.ui.server.console.ConsoleEppPasswordAction;
import google.registry.ui.server.console.ConsoleModule;
import google.registry.ui.server.console.ConsoleOteAction;
import google.registry.ui.server.console.ConsoleRegistryLockAction;
import google.registry.ui.server.console.ConsoleRegistryLockVerifyAction;
import google.registry.ui.server.console.ConsoleUpdateRegistrarAction;
@@ -41,7 +43,6 @@ import google.registry.ui.server.registrar.ConsoleOteSetupAction;
import google.registry.ui.server.registrar.ConsoleRegistrarCreatorAction;
import google.registry.ui.server.registrar.ConsoleUiAction;
import google.registry.ui.server.registrar.OteStatusAction;
import google.registry.ui.server.registrar.RegistrarConsoleModule;
import google.registry.ui.server.registrar.RegistrarSettingsAction;
import google.registry.ui.server.registrar.RegistryLockGetAction;
import google.registry.ui.server.registrar.RegistryLockPostAction;
@@ -54,7 +55,7 @@ import google.registry.ui.server.registrar.RegistryLockVerifyAction;
BatchModule.class,
DnsModule.class,
EppTlsModule.class,
RegistrarConsoleModule.class,
ConsoleModule.class,
RequestModule.class,
WhiteboxModule.class,
})
@@ -65,6 +66,8 @@ public interface FrontendRequestComponent {
ConsoleEppPasswordAction consoleEppPasswordAction();
ConsoleOteAction consoleOteAction();
ConsoleOteSetupAction consoleOteSetupAction();
ConsoleRegistrarCreatorAction consoleRegistrarCreatorAction();
@@ -82,7 +82,6 @@ public final class ActivityReportingQueryBuilder implements QueryBuilder {
// Convert reportingMonth into YYYYMMDD format for Bigquery table partition pattern-matching.
DateTimeFormatter logTableFormatter = DateTimeFormat.forPattern("yyyyMMdd");
// The monthly logs are a shared dependency for epp counts and whois metrics
String monthlyLogsQuery =
SqlTemplate.create(getQueryFromFile("monthly_logs.sql"))
.put("PROJECT_ID", projectId)
@@ -96,12 +95,10 @@ public final class ActivityReportingQueryBuilder implements QueryBuilder {
String eppQuery =
SqlTemplate.create(getQueryFromFile("epp_metrics.sql"))
.put("PROJECT_ID", projectId)
.put("ICANN_REPORTING_DATA_SET", icannReportingDataSet)
.put("MONTHLY_LOGS_TABLE", getTableName(MONTHLY_LOGS, yearMonth))
// All metadata logs for reporting come from google.registry.flows.FlowReporter.
.put(
"METADATA_LOG_PREFIX",
"google.registry.flows.FlowReporter recordToLogs: FLOW-LOG-SIGNATURE-METADATA")
.put("APPENGINE_LOGS_DATA_SET", "appengine_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();
queriesBuilder.put(getTableName(EPP_METRICS, yearMonth), eppQuery);
@@ -14,6 +14,7 @@
package google.registry.reporting.icann;
import static google.registry.request.RequestParameters.extractOptionalBooleanParameter;
import static google.registry.request.RequestParameters.extractOptionalParameter;
import static google.registry.request.RequestParameters.extractRequiredParameter;
import static google.registry.request.RequestParameters.extractSetOfEnumParameters;
@@ -42,6 +43,7 @@ public final class IcannReportingModule {
static final String PARAM_SUBDIR = "subdir";
static final String PARAM_REPORT_TYPES = "reportTypes";
static final String PARAM_SHOULD_UPLOAD = "shouldUpload";
static final String ICANN_REPORTING_DATA_SET = "icannReportingDataSet";
static final String MANIFEST_FILE_NAME = "MANIFEST.txt";
@@ -76,6 +78,12 @@ public final class IcannReportingModule {
return reportTypes.isEmpty() ? ImmutableSet.copyOf(ReportType.values()) : reportTypes;
}
@Provides
@Parameter(PARAM_SHOULD_UPLOAD)
static boolean provideShouldUpload(HttpServletRequest req) {
return extractOptionalBooleanParameter(req, PARAM_SHOULD_UPLOAD).orElse(true);
}
/**
* Constructs a BigqueryConnection with default settings.
*
@@ -16,6 +16,7 @@ package google.registry.reporting.icann;
import static com.google.common.base.Throwables.getRootCause;
import static google.registry.reporting.icann.IcannReportingModule.PARAM_REPORT_TYPES;
import static google.registry.reporting.icann.IcannReportingModule.PARAM_SHOULD_UPLOAD;
import static google.registry.reporting.icann.IcannReportingModule.PARAM_SUBDIR;
import static google.registry.request.Action.Method.POST;
import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
@@ -30,6 +31,7 @@ import google.registry.batch.CloudTasksUtils;
import google.registry.bigquery.BigqueryJobFailureException;
import google.registry.config.RegistryConfig.Config;
import google.registry.groups.GmailClient;
import google.registry.reporting.ReportingModule;
import google.registry.reporting.icann.IcannReportingModule.ReportType;
import google.registry.request.Action;
import google.registry.request.Action.Service;
@@ -79,6 +81,15 @@ public final class IcannReportingStagingAction implements Runnable {
@Inject YearMonth yearMonth;
@Inject @Parameter(PARAM_SUBDIR) Optional<String> overrideSubdir;
@Inject @Parameter(PARAM_REPORT_TYPES) ImmutableSet<ReportType> reportTypes;
@Inject
@Parameter(ReportingModule.SEND_EMAIL)
boolean sendEmail;
@Inject
@Parameter(PARAM_SHOULD_UPLOAD)
boolean shouldUpload;
@Inject IcannReportingStager stager;
@Inject Retrier retrier;
@Inject Response response;
@@ -106,28 +117,35 @@ public final class IcannReportingStagingAction implements Runnable {
stager.createAndUploadManifest(subdir, manifestedFiles);
logger.atInfo().log("Completed staging %d report files.", manifestedFiles.size());
gmailClient.sendEmail(
EmailMessage.newBuilder()
.setSubject("ICANN Monthly report staging summary [SUCCESS]")
.setBody(
String.format(
"Completed staging the following %d ICANN reports:\n%s",
manifestedFiles.size(), Joiner.on('\n').join(manifestedFiles)))
.addRecipient(recipient)
.build());
if (sendEmail) {
gmailClient.sendEmail(
EmailMessage.newBuilder()
.setSubject("ICANN Monthly report staging summary [SUCCESS]")
.setBody(
String.format(
"Completed staging the following %d ICANN reports:\n%s",
manifestedFiles.size(), Joiner.on('\n').join(manifestedFiles)))
.addRecipient(recipient)
.build());
} else {
logger.atInfo().log("Would have sent staging report summary to %s", recipient);
}
response.setStatus(SC_OK);
response.setContentType(MediaType.PLAIN_TEXT_UTF_8);
response.setPayload("Completed staging action.");
logger.atInfo().log("Enqueueing report upload.");
cloudTasksUtils.enqueue(
CRON_QUEUE,
cloudTasksUtils.createPostTaskWithDelay(
IcannReportingUploadAction.PATH,
Service.BACKEND,
null,
Duration.standardMinutes(2)));
if (shouldUpload) {
logger.atInfo().log("Enqueueing report upload.");
cloudTasksUtils.enqueue(
CRON_QUEUE,
cloudTasksUtils.createPostTaskWithDelay(
IcannReportingUploadAction.PATH,
Service.BACKEND,
null,
Duration.standardMinutes(2)));
} else {
logger.atInfo().log("Would have enqueued report upload");
}
return null;
},
BigqueryJobFailureException.class);
@@ -113,13 +113,9 @@ public final class TransactionsReportingQueryBuilder implements QueryBuilder {
SqlTemplate.create(getQueryFromFile("cloud_sql_attempted_adds.sql"))
.put("PROJECT_ID", projectId)
.put("APPENGINE_LOGS_DATA_SET", "appengine_logs")
.put("REQUEST_TABLE", "appengine_googleapis_com_request_log_")
.put("APP_LOGS_TABLE", "_var_log_app_")
.put("FIRST_DAY_OF_MONTH", logTableFormatter.print(earliestReportTime))
.put("LAST_DAY_OF_MONTH", logTableFormatter.print(latestReportTime))
// All metadata logs for reporting come from google.registry.flows.FlowReporter.
.put(
"METADATA_LOG_PREFIX",
"google.registry.flows.FlowReporter recordToLogs: FLOW-LOG-SIGNATURE-METADATA")
.build();
queriesBuilder.put(getTableName(ATTEMPTED_ADDS, yearMonth), attemptedAddsQuery);
@@ -14,6 +14,8 @@
package google.registry.request;
import static com.google.common.base.Preconditions.checkState;
import google.registry.request.auth.Auth;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -36,7 +38,6 @@ public @interface Action {
BACKEND("backend"),
PUBAPI("pubapi");
private final String serviceId;
Service(String serviceId) {
@@ -49,9 +50,33 @@ public @interface Action {
}
}
enum GkeService {
// This designation means that it defers to the GAE service, so we don't have to annotate EVERY
// action during the GKE migration.
SAME_AS_GAE("same_as_gae"),
FRONTEND("frontend"),
BACKEND("backend"),
PUBAPI("pubapi"),
CONSOLE("console");
private final String serviceId;
GkeService(String serviceId) {
this.serviceId = serviceId;
}
public String getServiceId() {
checkState(this != SAME_AS_GAE, "Cannot get service Id for SAME_AS_GAE");
return serviceId;
}
}
/** Which App Engine service this action lives on. */
Service service();
/** Which GKE service this action lives on. */
GkeService gkeService() default GkeService.SAME_AS_GAE;
/** HTTP path to serve the action from. The path components must be percent-escaped. */
String path();
@@ -72,4 +97,22 @@ public @interface Action {
/** Authentication settings. */
Auth auth();
// TODO(jianglai): Use Action.gkeService() directly once we are off GAE.
class ServiceGetter {
public static GkeService get(Action action) {
GkeService service = action.gkeService();
if (service != GkeService.SAME_AS_GAE) {
return service;
}
Service gaeService = action.service();
return switch (gaeService) {
case DEFAULT -> GkeService.FRONTEND;
case BACKEND -> GkeService.BACKEND;
case TOOLS -> GkeService.BACKEND;
case BSA -> GkeService.BACKEND;
case PUBAPI -> GkeService.PUBAPI;
};
}
}
}
@@ -22,14 +22,17 @@ import static jakarta.servlet.http.HttpServletResponse.SC_METHOD_NOT_ALLOWED;
import static jakarta.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import com.google.common.flogger.FluentLogger;
import google.registry.request.Action.GkeService;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.RequestAuthenticator;
import google.registry.util.NonFinalForTesting;
import google.registry.util.RegistryEnvironment;
import google.registry.util.SystemClock;
import google.registry.util.TypeUtils.TypeInstantiator;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.inject.Provider;
@@ -69,6 +72,7 @@ public class RequestHandler<C> {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final Router router;
@Nullable private final String baseDomain;
private final Provider<? extends RequestComponentBuilder<C>> requestComponentBuilderProvider;
private final RequestAuthenticator requestAuthenticator;
private final SystemClock clock = new SystemClock();
@@ -91,22 +95,22 @@ public class RequestHandler<C> {
protected RequestHandler(
Provider<? extends RequestComponentBuilder<C>> requestComponentBuilderProvider,
RequestAuthenticator requestAuthenticator) {
this(null, requestComponentBuilderProvider, requestAuthenticator);
this(null, null, requestComponentBuilderProvider, requestAuthenticator);
}
/** Creates a new RequestHandler with an explicit component class for test purposes. */
public static <C> RequestHandler<C> create(
Class<C> component,
@Nullable String baseDomain,
Provider<? extends RequestComponentBuilder<C>> requestComponentBuilderProvider,
RequestAuthenticator requestAuthenticator) {
return new RequestHandler<>(
checkNotNull(component),
requestComponentBuilderProvider,
requestAuthenticator);
checkNotNull(component), baseDomain, requestComponentBuilderProvider, requestAuthenticator);
}
private RequestHandler(
@Nullable Class<C> component,
@Nullable String baseDomain,
Provider<? extends RequestComponentBuilder<C>> requestComponentBuilderProvider,
RequestAuthenticator requestAuthenticator) {
// If the component class isn't explicitly provided, infer it from the class's own typing.
@@ -114,6 +118,7 @@ public class RequestHandler<C> {
// preserved at runtime, so only expose that option via the protected constructor.
this.router = Router.create(
component != null ? component : new TypeInstantiator<C>(getClass()){}.getExactType());
this.baseDomain = baseDomain;
this.requestComponentBuilderProvider = checkNotNull(requestComponentBuilderProvider);
this.requestAuthenticator = checkNotNull(requestAuthenticator);
}
@@ -137,6 +142,17 @@ public class RequestHandler<C> {
rsp.sendError(SC_NOT_FOUND);
return;
}
if (RegistryEnvironment.isOnJetty()) {
GkeService service = Action.ServiceGetter.get(route.get().action());
String expectedDomain = String.format("%s.%s", service.getServiceId(), baseDomain);
String actualDomain = req.getServerName();
if (!Objects.equals(actualDomain, expectedDomain)) {
logger.atWarning().log(
"Actual domain %s does not match expected domain %s", actualDomain, expectedDomain);
rsp.sendError(SC_NOT_FOUND);
return;
}
}
if (!route.get().isMethodAllowed(method)) {
logger.atWarning().log("Method %s not allowed for: %s", method, path);
rsp.sendError(SC_METHOD_NOT_ALLOWED);
@@ -21,6 +21,7 @@ import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Streams;
import java.util.Comparator;
import java.util.Map;
/**
@@ -37,6 +38,7 @@ import java.util.Map;
* the content to be displayed. The columns are:
*
* <ol>
* <li>the GKE service this action lives on
* <li>the URL path which maps to this action (with a "(*)" after it if the prefix flag is set)
* <li>the simple name of the action class
* <li>the allowable HTTP methods
@@ -49,12 +51,13 @@ import java.util.Map;
*/
public class RouterDisplayHelper {
private static final String SERVICE = "service";
private static final String PATH = "path";
private static final String CLASS = "class";
private static final String METHODS = "methods";
private static final String MINIMUM_LEVEL = "minLevel";
private static final String FORMAT = "%%-%ds %%-%ds %%-%ds %%-2s %%-%ds %%s";
private static final String FORMAT = "%%-%ds %%-%ds %%-%ds %%-%ds %%-2s %%-%ds %%s";
/** Returns a string representation of the routing map in the specified component. */
public static String extractHumanReadableRoutesFromComponent(Class<?> componentClass) {
@@ -76,6 +79,7 @@ public class RouterDisplayHelper {
private static String getFormatString(Map<String, Integer> columnWidths) {
return String.format(
FORMAT,
columnWidths.get(SERVICE),
columnWidths.get(PATH),
columnWidths.get(CLASS),
columnWidths.get(METHODS),
@@ -84,18 +88,13 @@ public class RouterDisplayHelper {
private static String headerToString(String formatString) {
return String.format(
formatString,
"PATH",
"CLASS",
"METHODS",
"OK",
"MIN",
"USER_POLICY");
formatString, "SERVICE", "PATH", "CLASS", "METHODS", "OK", "MIN", "USER_POLICY");
}
private static String routeToString(Route route, String formatString) {
return String.format(
formatString,
Action.ServiceGetter.get(route.action()).name(),
route.action().isPrefix() ? (route.action().path() + "(*)") : route.action().path(),
route.actionClass().getSimpleName(),
Joiner.on(",").join(route.action().method()),
@@ -107,12 +106,17 @@ public class RouterDisplayHelper {
private static String formatRoutes(Iterable<Route> routes) {
// Use the column header length as a minimum.
int serviceWidth = 7;
int pathWidth = 4;
int classWidth = 5;
int methodsWidth = 7;
int minLevelWidth = 3;
for (Route route : routes) {
int len =
int len = Action.ServiceGetter.get(route.action()).name().length();
if (len > serviceWidth) {
serviceWidth = len;
}
len =
route.action().isPrefix()
? (route.action().path().length() + 3)
: route.action().path().length();
@@ -135,6 +139,7 @@ public class RouterDisplayHelper {
final String formatString =
getFormatString(
new ImmutableMap.Builder<String, Integer>()
.put(SERVICE, serviceWidth)
.put(PATH, pathWidth)
.put(CLASS, classWidth)
.put(METHODS, methodsWidth)
@@ -143,6 +148,9 @@ public class RouterDisplayHelper {
return headerToString(formatString)
+ String.format("%n")
+ Streams.stream(routes)
.sorted(
Comparator.comparing(
(Route route) -> Action.ServiceGetter.get(route.action()).ordinal()))
.map(route -> routeToString(route, formatString))
.collect(joining(String.format("%n")));
}
@@ -16,7 +16,6 @@ package google.registry.request.auth;
import static com.google.common.net.HttpHeaders.AUTHORIZATION;
import com.google.auth.oauth2.TokenVerifier;
import com.google.common.collect.ImmutableList;
import dagger.Module;
import dagger.Provides;
@@ -24,6 +23,10 @@ import google.registry.config.RegistryConfig.Config;
import google.registry.request.auth.OidcTokenAuthenticationMechanism.IapOidcAuthenticationMechanism;
import google.registry.request.auth.OidcTokenAuthenticationMechanism.RegularOidcAuthenticationMechanism;
import google.registry.request.auth.OidcTokenAuthenticationMechanism.TokenExtractor;
import google.registry.request.auth.OidcTokenAuthenticationMechanism.TokenVerifier;
import google.registry.util.RegistryEnvironment;
import java.util.Map;
import javax.annotation.Nullable;
import javax.inject.Qualifier;
import javax.inject.Singleton;
@@ -35,9 +38,10 @@ public class AuthModule {
// See https://cloud.google.com/iap/docs/signed-headers-howto#securing_iap_headers.
public static final String IAP_HEADER_NAME = "X-Goog-IAP-JWT-Assertion";
public static final String BEARER_PREFIX = "Bearer ";
// TODO: Change the IAP audience format once we are on GKE.
// TODO (jianglai): Only use GKE audience once we are fully migrated to GKE.
// See: https://cloud.google.com/iap/docs/signed-headers-howto#verifying_the_jwt_payload
private static final String IAP_AUDIENCE_FORMAT = "/projects/%d/apps/%s";
private static final String IAP_GAE_AUDIENCE_FORMAT = "/projects/%d/apps/%s";
private static final String IAP_GKE_AUDIENCE_FORMAT = "/projects/%d/global/backendServices/%d";
private static final String IAP_ISSUER_URL = "https://cloud.google.com/iap";
private static final String REGULAR_ISSUER_URL = "https://accounts.google.com";
@@ -62,24 +66,35 @@ public class AuthModule {
@IapOidc
@Singleton
TokenVerifier provideIapTokenVerifier(
@Config("projectId") String projectId, @Config("projectIdNumber") long projectIdNumber) {
String audience = String.format(IAP_AUDIENCE_FORMAT, projectIdNumber, projectId);
return TokenVerifier.newBuilder().setAudience(audience).setIssuer(IAP_ISSUER_URL).build();
@Config("projectId") String projectId,
@Config("projectIdNumber") long projectIdNumber,
@Config("backendServiceIds") Map<String, Long> backendServiceIds) {
com.google.auth.oauth2.TokenVerifier.Builder tokenVerifierBuilder =
com.google.auth.oauth2.TokenVerifier.newBuilder().setIssuer(IAP_ISSUER_URL);
return (String service, String token) -> {
String audience;
if (RegistryEnvironment.isOnJetty()) {
long backendServiceId = backendServiceIds.get(service);
audience = String.format(IAP_GKE_AUDIENCE_FORMAT, projectIdNumber, backendServiceId);
} else {
audience = String.format(IAP_GAE_AUDIENCE_FORMAT, projectIdNumber, projectId);
}
return tokenVerifierBuilder.setAudience(audience).build().verify(token);
};
}
@Provides
@RegularOidc
@Singleton
TokenVerifier provideRegularTokenVerifier(@Config("oauthClientId") String clientId) {
return TokenVerifier.newBuilder().setAudience(clientId).setIssuer(REGULAR_ISSUER_URL).build();
}
@Provides
@RegularOidcFallback
@Singleton
TokenVerifier provideFallbackRegularTokenVerifier(
@Config("fallbackOauthClientId") String clientId) {
return TokenVerifier.newBuilder().setAudience(clientId).setIssuer(REGULAR_ISSUER_URL).build();
com.google.auth.oauth2.TokenVerifier tokenVerifier =
com.google.auth.oauth2.TokenVerifier.newBuilder()
.setAudience(clientId)
.setIssuer(REGULAR_ISSUER_URL)
.build();
return (@Nullable String service, String token) -> {
return tokenVerifier.verify(token);
};
}
@Provides
@@ -18,8 +18,9 @@ import static com.google.common.base.Preconditions.checkState;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.api.client.json.webtoken.JsonWebSignature;
import com.google.auth.oauth2.TokenVerifier;
import com.google.auth.oauth2.TokenVerifier.VerificationException;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import google.registry.config.RegistryConfig.Config;
@@ -27,7 +28,6 @@ import google.registry.model.console.User;
import google.registry.persistence.VKey;
import google.registry.request.auth.AuthModule.IapOidc;
import google.registry.request.auth.AuthModule.RegularOidc;
import google.registry.request.auth.AuthModule.RegularOidcFallback;
import google.registry.request.auth.AuthSettings.AuthLevel;
import google.registry.util.RegistryEnvironment;
import jakarta.servlet.http.HttpServletRequest;
@@ -51,27 +51,23 @@ public abstract class OidcTokenAuthenticationMechanism implements Authentication
public static final FluentLogger logger = FluentLogger.forEnclosingClass();
// A workaround that allows "use" of the OIDC authenticator when running local testing, i.e.
// A workaround that allows "use" of the OIDC authenticator when running local testing, i.e.,
// the RegistryTestServer
private static AuthResult authResultForTesting = null;
protected final TokenVerifier tokenVerifier;
protected final Optional<TokenVerifier> fallbackTokenVerifier;
protected final TokenExtractor tokenExtractor;
protected final TokenVerifier tokenVerifier;
private final ImmutableSet<String> serviceAccountEmails;
protected OidcTokenAuthenticationMechanism(
ImmutableSet<String> serviceAccountEmails,
TokenVerifier tokenVerifier,
@Nullable TokenVerifier fallbackTokenVerifier,
TokenExtractor tokenExtractor) {
TokenExtractor tokenExtractor,
TokenVerifier tokenVerifier) {
this.serviceAccountEmails = serviceAccountEmails;
this.tokenVerifier = tokenVerifier;
this.fallbackTokenVerifier = Optional.ofNullable(fallbackTokenVerifier);
this.tokenExtractor = tokenExtractor;
this.tokenVerifier = tokenVerifier;
}
@Override
@@ -87,7 +83,12 @@ public abstract class OidcTokenAuthenticationMechanism implements Authentication
}
JsonWebSignature token = null;
try {
token = tokenVerifier.verify(rawIdToken);
String service = null;
if (RegistryEnvironment.isOnJetty()) {
String hostname = request.getServerName();
service = Splitter.on('.').split(hostname).iterator().next();
}
token = tokenVerifier.verify(service, rawIdToken);
} catch (Exception e) {
logger.atInfo().withCause(e).log(
"Failed OIDC verification attempt:\n%s",
@@ -97,20 +98,7 @@ public abstract class OidcTokenAuthenticationMechanism implements Authentication
}
if (token == null) {
if (fallbackTokenVerifier.isPresent()) {
try {
token = fallbackTokenVerifier.get().verify(rawIdToken);
} catch (Exception e) {
logger.atInfo().withCause(e).log(
"Failed OIDC fallback verification attempt:\n%s",
RegistryEnvironment.get().equals(RegistryEnvironment.PRODUCTION)
? "Raw token redacted in prod"
: rawIdToken);
return AuthResult.NOT_AUTHENTICATED;
}
} else {
return AuthResult.NOT_AUTHENTICATED;
}
return AuthResult.NOT_AUTHENTICATED;
}
String email = (String) token.getPayload().get("email");
@@ -155,6 +143,12 @@ public abstract class OidcTokenAuthenticationMechanism implements Authentication
String extract(HttpServletRequest request);
}
@FunctionalInterface
protected interface TokenVerifier {
@Nullable
JsonWebSignature verify(@Nullable String service, String rawToken) throws VerificationException;
}
/**
* A mechanism to authenticate HTTP requests that have gone through the GCP Identity-Aware Proxy.
*
@@ -171,9 +165,9 @@ public abstract class OidcTokenAuthenticationMechanism implements Authentication
@Inject
protected IapOidcAuthenticationMechanism(
@Config("allowedServiceAccountEmails") ImmutableSet<String> serviceAccountEmails,
@IapOidc TokenVerifier tokenVerifier,
@IapOidc TokenExtractor tokenExtractor) {
super(serviceAccountEmails, tokenVerifier, null, tokenExtractor);
@IapOidc TokenExtractor tokenExtractor,
@IapOidc TokenVerifier tokenVerifier) {
super(serviceAccountEmails, tokenExtractor, tokenVerifier);
}
}
@@ -192,10 +186,9 @@ public abstract class OidcTokenAuthenticationMechanism implements Authentication
@Inject
protected RegularOidcAuthenticationMechanism(
@Config("allowedServiceAccountEmails") ImmutableSet<String> serviceAccountEmails,
@RegularOidc TokenVerifier tokenVerifier,
@RegularOidcFallback TokenVerifier fallbackTokenVerifier,
@RegularOidc TokenExtractor tokenExtractor) {
super(serviceAccountEmails, tokenVerifier, fallbackTokenVerifier, tokenExtractor);
@RegularOidc TokenExtractor tokenExtractor,
@RegularOidc TokenVerifier tokenVerifier) {
super(serviceAccountEmails, tokenExtractor, tokenVerifier);
}
}
}
@@ -44,7 +44,6 @@ import google.registry.model.registrar.RegistrarPocBase;
import google.registry.request.Action.Service;
import google.registry.request.HttpException;
import google.registry.security.XsrfTokenManager;
import google.registry.ui.server.registrar.ConsoleApiParams;
import google.registry.ui.server.registrar.ConsoleUiAction;
import google.registry.util.DiffUtils;
import google.registry.util.RegistryEnvironment;
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.ui.server.registrar;
package google.registry.ui.server.console;
import google.registry.request.Response;
import google.registry.request.auth.AuthResult;
@@ -24,15 +24,16 @@ import google.registry.model.console.ConsolePermission;
import google.registry.model.console.User;
import google.registry.model.domain.Domain;
import google.registry.request.Action;
import google.registry.request.Action.GkeService;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.ui.server.registrar.ConsoleApiParams;
import java.util.Optional;
import javax.inject.Inject;
/** Returns a JSON representation of a domain to the registrar console. */
@Action(
service = Action.Service.DEFAULT,
gkeService = GkeService.CONSOLE,
path = ConsoleDomainGetAction.PATH,
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
public class ConsoleDomainGetAction extends ConsoleApiAction {
@@ -27,9 +27,9 @@ import google.registry.model.CreateAutoTimestamp;
import google.registry.model.console.User;
import google.registry.model.domain.Domain;
import google.registry.request.Action;
import google.registry.request.Action.GkeService;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.ui.server.registrar.ConsoleApiParams;
import java.util.List;
import java.util.Optional;
import javax.inject.Inject;
@@ -39,6 +39,7 @@ import org.joda.time.DateTime;
/** Returns a (paginated) list of domains for a particular registrar. */
@Action(
service = Action.Service.DEFAULT,
gkeService = GkeService.CONSOLE,
path = ConsoleDomainListAction.PATH,
method = Action.Method.GET,
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
@@ -26,9 +26,9 @@ import google.registry.config.RegistryConfig.Config;
import google.registry.model.console.ConsolePermission;
import google.registry.model.console.User;
import google.registry.request.Action;
import google.registry.request.Action.GkeService;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.ui.server.registrar.ConsoleApiParams;
import google.registry.util.Clock;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@@ -40,6 +40,7 @@ import org.joda.time.DateTime;
@Action(
service = Action.Service.DEFAULT,
gkeService = GkeService.CONSOLE,
path = ConsoleDumDownloadAction.PATH,
method = {GET},
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
@@ -30,17 +30,18 @@ import google.registry.flows.PasswordOnlyTransportCredentials;
import google.registry.model.console.User;
import google.registry.model.registrar.Registrar;
import google.registry.request.Action;
import google.registry.request.Action.GkeService;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.request.auth.AuthenticatedRegistrarAccessor.RegistrarAccessDeniedException;
import google.registry.ui.server.registrar.ConsoleApiParams;
import google.registry.util.DiffUtils;
import java.util.Optional;
import javax.inject.Inject;
@Action(
service = Action.Service.DEFAULT,
gkeService = GkeService.CONSOLE,
path = ConsoleEppPasswordAction.PATH,
method = {POST},
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.ui.server.registrar;
package google.registry.ui.server.console;
import static google.registry.request.RequestParameters.extractBooleanParameter;
import static google.registry.request.RequestParameters.extractOptionalIntParameter;
@@ -34,6 +34,7 @@ import google.registry.request.auth.AuthResult;
import google.registry.security.XsrfTokenManager;
import google.registry.ui.server.SendEmailUtils;
import google.registry.ui.server.console.ConsoleEppPasswordAction.EppPasswordData;
import google.registry.ui.server.console.ConsoleOteAction.OteCreateData;
import google.registry.ui.server.console.ConsoleRegistryLockAction.ConsoleRegistryLockPostInput;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Optional;
@@ -41,8 +42,8 @@ import org.joda.time.DateTime;
/** Dagger module for the Registrar Console parameters. */
@Module
public final class RegistrarConsoleModule {
static final String PARAM_CLIENT_ID = "clientId";
public final class ConsoleModule {
public static final String PARAM_CLIENT_ID = "clientId";
@Provides
@RequestScope
@@ -244,6 +245,13 @@ public final class RegistrarConsoleModule {
return payload.map(s -> gson.fromJson(s, EppPasswordData.class));
}
@Provides
@Parameter("oteCreateData")
public static Optional<OteCreateData> provideOteCreateData(
Gson gson, @OptionalJsonPayload Optional<JsonElement> payload) {
return payload.map(s -> gson.fromJson(s, OteCreateData.class));
}
@Provides
@Parameter("consoleRegistryLockPostInput")
public static Optional<ConsoleRegistryLockPostInput> provideRegistryLockPostInput(
@@ -0,0 +1,174 @@
// 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.ui.server.console;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.POST;
import static google.registry.util.RegistryEnvironment.PRODUCTION;
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 com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.annotations.Expose;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.OteAccountBuilder;
import google.registry.model.OteStats;
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;
import google.registry.request.auth.Auth;
import google.registry.tools.IamClient;
import google.registry.util.RegistryEnvironment;
import google.registry.util.StringGenerator;
import java.util.Map;
import java.util.Optional;
import javax.inject.Inject;
import javax.inject.Named;
@Action(
service = Action.Service.DEFAULT,
gkeService = GkeService.CONSOLE,
path = ConsoleOteAction.PATH,
method = {GET, POST},
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
public class ConsoleOteAction extends ConsoleApiAction {
static final String PATH = "/console-api/ote";
private static final int PASSWORD_LENGTH = 16;
private static final String COMPLETED_PARAM = "completed";
private static final String STAT_TYPE_DESCRIPTION_PARAM = "description";
private static final String STAT_TYPE_REQUIREMENT_PARAM = "requirement";
private static final String STAT_TYPE_TIMES_PERFORMED_PARAM = "timesPerformed";
private final Gson gson;
private final StringGenerator passwordGenerator;
private final Optional<OteCreateData> oteCreateData;
private final Optional<String> maybeGroupEmailAddress;
private final IamClient iamClient;
private final String registrarId;
@Inject
public ConsoleOteAction(
ConsoleApiParams consoleApiParams,
Gson gson,
IamClient iamClient,
@Parameter("registrarId") String registrarId, // Get request param
@Config("gSuiteConsoleUserGroupEmailAddress") Optional<String> maybeGroupEmailAddress,
@Named("base58StringGenerator") StringGenerator passwordGenerator,
@Parameter("oteCreateData") Optional<OteCreateData> oteCreateData) {
super(consoleApiParams);
this.gson = gson;
this.passwordGenerator = passwordGenerator;
this.oteCreateData = oteCreateData;
this.maybeGroupEmailAddress = maybeGroupEmailAddress;
this.iamClient = iamClient;
this.registrarId = registrarId;
}
@Override
protected void postHandler(User user) {
checkState(!RegistryEnvironment.get().equals(PRODUCTION), "Can't create OT&E in prod");
if (!user.getUserRoles().hasGlobalPermission(ConsolePermission.EDIT_REGISTRAR_DETAILS)) {
setFailedResponse("User doesn't have a permission to create OT&E accounts", SC_FORBIDDEN);
return;
}
boolean isBodyValid =
this.oteCreateData.isPresent()
&& !this.oteCreateData.get().registrarId.isEmpty()
&& !this.oteCreateData.get().registrarEmail.isEmpty();
checkArgument(isBodyValid, "OT&E create body is invalid");
String password = passwordGenerator.createString(PASSWORD_LENGTH);
OteAccountBuilder oteAccountBuilder =
OteAccountBuilder.forRegistrarId(this.oteCreateData.get().registrarId)
.addUser(this.oteCreateData.get().registrarEmail)
.setPassword(password);
ImmutableMap<String, String> registrarIdToTld = oteAccountBuilder.buildAndPersist();
oteAccountBuilder.grantIapPermission(maybeGroupEmailAddress, cloudTasksUtils, iamClient);
consoleApiParams.response().setStatus(SC_OK);
consoleApiParams
.response()
.setPayload(
gson.toJson(
ImmutableMap.builder().putAll(registrarIdToTld).put("password", password).build()));
}
@Override
protected void getHandler(User user) {
checkArgument(!Strings.isNullOrEmpty(registrarId), "Missing registrarId parameter");
if (!user.getUserRoles().hasGlobalPermission(ConsolePermission.EDIT_REGISTRAR_DETAILS)) {
setFailedResponse("User doesn't have a permission to check OT&E status", SC_BAD_REQUEST);
return;
}
String baseRegistrarId = OteAccountBuilder.getBaseRegistrarId(registrarId);
tm().transact(
() -> {
Optional<Registrar> registrar = Registrar.loadByRegistrarId(registrarId);
if (registrar.isEmpty()) {
setFailedResponse(
String.format("Registrar with ID %s is not present", registrarId),
SC_BAD_REQUEST);
return;
}
if (!RegistrarBase.Type.OTE.equals(registrar.get().getType())) {
setFailedResponse(
String.format("Registrar with ID %s is not an OT&E registrar", registrarId),
SC_BAD_REQUEST);
return;
}
OteStats oteStats = OteStats.getFromRegistrar(baseRegistrarId);
var stats =
StatType.REQUIRED_STAT_TYPES.stream()
.map(
statType ->
convertSingleRequirement(statType, oteStats.getCount(statType)))
.collect(toImmutableList());
consoleApiParams.response().setStatus(SC_OK);
consoleApiParams.response().setPayload(gson.toJson(stats));
});
}
private Map<String, Object> convertSingleRequirement(StatType statType, int count) {
int requirement = statType.getRequirement();
return ImmutableMap.of(
STAT_TYPE_DESCRIPTION_PARAM,
statType.getDescription(),
STAT_TYPE_REQUIREMENT_PARAM,
requirement,
STAT_TYPE_TIMES_PERFORMED_PARAM,
count,
COMPLETED_PARAM,
count >= requirement);
}
public record OteCreateData(@Expose String registrarId, @Expose String registrarEmail) {}
}
@@ -35,11 +35,11 @@ import google.registry.model.domain.RegistryLock;
import google.registry.model.registrar.Registrar;
import google.registry.model.tld.RegistryLockDao;
import google.registry.request.Action;
import google.registry.request.Action.GkeService;
import google.registry.request.Parameter;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.tools.DomainLockUtils;
import google.registry.ui.server.registrar.ConsoleApiParams;
import google.registry.util.EmailMessage;
import jakarta.mail.internet.AddressException;
import jakarta.mail.internet.InternetAddress;
@@ -56,6 +56,7 @@ import org.joda.time.Duration;
*/
@Action(
service = Action.Service.DEFAULT,
gkeService = GkeService.CONSOLE,
path = ConsoleRegistryLockAction.PATH,
method = {GET, POST},
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
@@ -22,16 +22,17 @@ import com.google.gson.annotations.Expose;
import google.registry.model.console.User;
import google.registry.model.domain.RegistryLock;
import google.registry.request.Action;
import google.registry.request.Action.GkeService;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.tools.DomainLockUtils;
import google.registry.ui.server.registrar.ConsoleApiParams;
import jakarta.servlet.http.HttpServletResponse;
import javax.inject.Inject;
/** Handler for verifying registry lock requests, a form of 2FA. */
@Action(
service = Action.Service.DEFAULT,
gkeService = GkeService.CONSOLE,
path = ConsoleRegistryLockVerifyAction.PATH,
method = {GET},
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
@@ -26,10 +26,10 @@ import google.registry.model.console.ConsolePermission;
import google.registry.model.console.User;
import google.registry.model.registrar.Registrar;
import google.registry.request.Action;
import google.registry.request.Action.GkeService;
import google.registry.request.HttpException.BadRequestException;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.ui.server.registrar.ConsoleApiParams;
import google.registry.util.DomainNameUtils;
import google.registry.util.RegistryEnvironment;
import java.util.Optional;
@@ -38,6 +38,7 @@ import javax.inject.Inject;
@Action(
service = Action.Service.DEFAULT,
gkeService = GkeService.CONSOLE,
path = ConsoleUpdateRegistrarAction.PATH,
method = {POST},
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
@@ -21,15 +21,16 @@ import com.google.common.collect.ImmutableMap;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.console.User;
import google.registry.request.Action;
import google.registry.request.Action.GkeService;
import google.registry.request.auth.Auth;
import google.registry.security.XsrfTokenManager;
import google.registry.ui.server.registrar.ConsoleApiParams;
import jakarta.servlet.http.Cookie;
import javax.inject.Inject;
import org.json.JSONObject;
@Action(
service = Action.Service.DEFAULT,
gkeService = GkeService.CONSOLE,
path = ConsoleUserDataAction.PATH,
method = {GET},
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
@@ -34,9 +34,9 @@ import google.registry.model.registrar.RegistrarBase;
import google.registry.model.registrar.RegistrarBase.State;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.request.Action;
import google.registry.request.Action.GkeService;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.ui.server.registrar.ConsoleApiParams;
import google.registry.util.StringGenerator;
import java.util.List;
import java.util.Map;
@@ -46,6 +46,7 @@ import javax.inject.Named;
@Action(
service = Action.Service.DEFAULT,
gkeService = GkeService.CONSOLE,
path = RegistrarsAction.PATH,
method = {GET, POST},
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
@@ -31,11 +31,12 @@ import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.persistence.transaction.QueryComposer.Comparator;
import google.registry.request.Action;
import google.registry.request.Action.GkeService;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.ui.forms.FormException;
import google.registry.ui.server.console.ConsoleApiAction;
import google.registry.ui.server.registrar.ConsoleApiParams;
import google.registry.ui.server.console.ConsoleApiParams;
import google.registry.ui.server.registrar.RegistrarSettingsAction;
import java.util.Collections;
import java.util.Optional;
@@ -43,6 +44,7 @@ import javax.inject.Inject;
@Action(
service = Action.Service.DEFAULT,
gkeService = GkeService.CONSOLE,
path = ContactAction.PATH,
method = {GET, POST},
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
@@ -28,17 +28,19 @@ import google.registry.model.console.ConsolePermission;
import google.registry.model.console.User;
import google.registry.model.registrar.Registrar;
import google.registry.request.Action;
import google.registry.request.Action.GkeService;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.request.auth.AuthenticatedRegistrarAccessor.RegistrarAccessDeniedException;
import google.registry.ui.server.console.ConsoleApiAction;
import google.registry.ui.server.registrar.ConsoleApiParams;
import google.registry.ui.server.console.ConsoleApiParams;
import java.util.Optional;
import javax.inject.Inject;
@Action(
service = Action.Service.DEFAULT,
gkeService = GkeService.CONSOLE,
path = SecurityAction.PATH,
method = {POST},
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
@@ -25,12 +25,13 @@ import google.registry.model.console.ConsolePermission;
import google.registry.model.console.User;
import google.registry.model.registrar.Registrar;
import google.registry.request.Action;
import google.registry.request.Action.GkeService;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.request.auth.AuthenticatedRegistrarAccessor.RegistrarAccessDeniedException;
import google.registry.ui.server.console.ConsoleApiAction;
import google.registry.ui.server.registrar.ConsoleApiParams;
import google.registry.ui.server.console.ConsoleApiParams;
import java.util.Objects;
import java.util.Optional;
import javax.inject.Inject;
@@ -43,6 +44,7 @@ import javax.inject.Inject;
*/
@Action(
service = Action.Service.DEFAULT,
gkeService = GkeService.CONSOLE,
path = WhoisRegistrarFieldsAction.PATH,
method = {POST},
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
@@ -18,7 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.security.JsonResponseHelper.Status.SUCCESS;
import static google.registry.ui.server.registrar.RegistrarConsoleModule.PARAM_CLIENT_ID;
import static google.registry.ui.server.console.ConsoleModule.PARAM_CLIENT_ID;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
@@ -41,29 +41,23 @@ FROM (
FROM (
-- Extract JSON metadata package from monthly logs
SELECT
REGEXP_EXTRACT(logMessages, r'FLOW-LOG-SIGNATURE-METADATA: (.*)\n?$')
REGEXP_EXTRACT(textPayload, r'FLOW-LOG-SIGNATURE-METADATA: (.*)\n?$')
AS json
FROM (
SELECT
protoPayload.resource AS requestPath,
ARRAY(
SELECT logMessage
FROM UNNEST(protoPayload.line)) AS logMessage
textPayload
FROM
`%PROJECT_ID%.%APPENGINE_LOGS_DATA_SET%.%REQUEST_TABLE%*`
`%PROJECT_ID%.%APPENGINE_LOGS_DATA_SET%.%APP_LOGS_TABLE%*`
WHERE _TABLE_SUFFIX
BETWEEN '%FIRST_DAY_OF_MONTH%'
AND '%LAST_DAY_OF_MONTH%')
JOIN UNNEST(logMessage) AS logMessages
-- Look for metadata logs from epp and registrar console requests
WHERE requestPath IN ('/_dr/epp', '/_dr/epptool', '/registrar-xhr')
AND STARTS_WITH(logMessages, "%METADATA_LOG_PREFIX%")
WHERE STARTS_WITH(textPayload, "FLOW-LOG-SIGNATURE-METADATA")
-- Look for domain creates
AND REGEXP_CONTAINS(
logMessages, r'"commandType":"create","resourceType":"domain"')
textPayload, r'"commandType":"create","resourceType":"domain"')
-- Filter prober data
AND NOT REGEXP_CONTAINS(
logMessages, r'"prober-[a-z]{2}-((any)|(canary))"') )
textPayload, r'"prober-[a-z]{2}-((any)|(canary))"') )
GROUP BY tld, clientId ) AS logs_table
JOIN
EXTERNAL_QUERY("projects/%PROJECT_ID%/locations/us/connections/%PROJECT_ID%-sql",
@@ -37,13 +37,12 @@ FROM (
FROM (
SELECT
-- Extract the logged JSON payload.
REGEXP_EXTRACT(logMessage, r'FLOW-LOG-SIGNATURE-METADATA: (.*)\n?$')
REGEXP_EXTRACT(textPayload, r'FLOW-LOG-SIGNATURE-METADATA: (.*)\n?$')
AS json
FROM `%PROJECT_ID%.%ICANN_REPORTING_DATA_SET%.%MONTHLY_LOGS_TABLE%` AS logs
JOIN
UNNEST(logs.logMessage) AS logMessage
FROM `%PROJECT_ID%.%APPENGINE_LOGS_DATA_SET%.%APP_LOGS_TABLE%*`
WHERE
STARTS_WITH(logMessage, "%METADATA_LOG_PREFIX%"))) AS regexes
STARTS_WITH(textPayload, "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
@@ -19,11 +19,6 @@
SELECT
protoPayload.resource AS requestPath,
ARRAY(
SELECT
logMessage
FROM
UNNEST(protoPayload.line)) AS logMessage
FROM
`%PROJECT_ID%.%APPENGINE_LOGS_DATA_SET%.%REQUEST_TABLE%*`
WHERE
@@ -19,6 +19,8 @@ import static com.google.common.truth.Truth.assertThat;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import google.registry.module.backend.BackendRequestComponent;
import google.registry.module.bsa.BsaRequestComponent;
import google.registry.module.frontend.FrontendRequestComponent;
@@ -26,8 +28,9 @@ import google.registry.module.pubapi.PubApiRequestComponent;
import google.registry.module.tools.ToolsRequestComponent;
import google.registry.testing.GoldenFileTestHelper;
import google.registry.testing.TestDataHelper;
import java.util.ArrayList;
import java.util.List;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link RequestComponent}. */
@@ -40,6 +43,18 @@ public class RequestComponentTest {
PubApiRequestComponent.class, "pubapi",
BsaRequestComponent.class, "bsa");
// Paths that do not route to Jetty (all for the legacy console).
private static final ImmutableSet<String> ignoredPaths =
ImmutableSet.of(
"/registrar",
"/registrar-create",
"/registrar-ote-setup",
"/registrar-ote-status",
"/registrar-settings",
"/registry-lock-get",
"/registry-lock-post",
"/registry-lock-verify");
@Test
void testRoutingMap() {
GoldenFileTestHelper.assertThatRoutesFromComponent(RequestComponent.class)
@@ -49,32 +64,49 @@ public class RequestComponentTest {
@Test
void testGaeToJettyRoutingCoverage() {
List<Route> jettyRoutes = getRoutes(RequestComponent.class, "routing.txt");
List<Route> gaeRoutes = new ArrayList<>();
Set<Route> jettyRoutes = getRoutes(RequestComponent.class, "routing.txt");
Set<Route> gaeRoutes = new HashSet<>();
for (var component : GaeComponents.entrySet()) {
gaeRoutes.addAll(getRoutes(component.getKey(), component.getValue() + "_routing.txt"));
}
assertThat(jettyRoutes).containsExactlyElementsIn(gaeRoutes);
assertThat(Sets.difference(jettyRoutes, gaeRoutes)).isEmpty();
assertThat(
Sets.difference(gaeRoutes, jettyRoutes).stream()
.map(Route::path)
.collect(Collectors.toSet()))
.containsExactlyElementsIn(ignoredPaths);
}
private List<Route> getRoutes(Class<?> context, String filename) {
private Set<Route> getRoutes(Class<?> context, String filename) {
return TestDataHelper.loadFile(context, filename)
.trim()
.lines()
.skip(1) // Skip the headers
.map(Route::create)
.toList();
.collect(Collectors.toSet());
}
private record Route(
String path, String clazz, String methods, String ok, String min, String userPolicy) {
String service,
String path,
String clazz,
String methods,
String ok,
String min,
String userPolicy) {
private static final Splitter splitter = Splitter.on(' ').omitEmptyStrings().trimResults();
static Route create(String line) {
ImmutableList<String> parts = ImmutableList.copyOf(splitter.split(line));
assertThat(parts.size()).isEqualTo(6);
assertThat(parts.size()).isEqualTo(7);
return new Route(
parts.get(0), parts.get(1), parts.get(2), parts.get(3), parts.get(4), parts.get(5));
parts.get(0),
parts.get(1),
parts.get(2),
parts.get(3),
parts.get(4),
parts.get(5),
parts.get(6));
}
}
}
@@ -19,6 +19,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import com.google.cloud.tasks.v2.HttpMethod;
@@ -60,6 +61,8 @@ class IcannReportingStagingActionTest {
action.yearMonth = yearMonth;
action.overrideSubdir = Optional.of(subdir);
action.reportTypes = ImmutableSet.of(ReportType.ACTIVITY, ReportType.TRANSACTIONS);
action.sendEmail = true;
action.shouldUpload = true;
action.response = response;
action.stager = stager;
action.retrier = new Retrier(new FakeSleeper(new FakeClock()), 3);
@@ -154,6 +157,33 @@ class IcannReportingStagingActionTest {
cloudTasksHelper.assertNoTasksEnqueued("retryable-cron-tasks");
}
@Test
void testSkipsEmail() throws Exception {
action.sendEmail = false;
action.run();
verify(stager).stageReports(yearMonth, subdir, ReportType.ACTIVITY);
verify(stager).stageReports(yearMonth, subdir, ReportType.TRANSACTIONS);
verify(stager).createAndUploadManifest(subdir, ImmutableList.of("a", "b", "c", "d"));
verifyNoInteractions(action.gmailClient);
assertUploadTaskEnqueued();
}
@Test
void testSkipsUpload() throws Exception {
action.shouldUpload = false;
action.run();
verify(stager).stageReports(yearMonth, subdir, ReportType.ACTIVITY);
verify(stager).stageReports(yearMonth, subdir, ReportType.TRANSACTIONS);
verify(stager).createAndUploadManifest(subdir, ImmutableList.of("a", "b", "c", "d"));
verify(action.gmailClient)
.sendEmail(
EmailMessage.create(
"ICANN Monthly report staging summary [SUCCESS]",
"Completed staging the following 4 ICANN reports:\na\nb\nc\nd",
new InternetAddress("recipient@example.com")));
cloudTasksHelper.assertNoTasksEnqueued("retryable-cron-tasks");
}
@Test
void testEmptySubDir_returnsDefaultSubdir() {
action.overrideSubdir = Optional.empty();
@@ -204,6 +204,7 @@ public final class RequestHandlerTest {
handler =
RequestHandler.create(
Component.class,
"registry.test",
() ->
new Builder() {
@Override
@@ -26,8 +26,8 @@ import static org.mockito.Mockito.when;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload;
import com.google.api.client.json.webtoken.JsonWebSignature;
import com.google.api.client.json.webtoken.JsonWebSignature.Header;
import com.google.auth.oauth2.TokenVerifier;
import com.google.auth.oauth2.TokenVerifier.VerificationException;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import dagger.Component;
import dagger.Module;
@@ -41,6 +41,7 @@ import google.registry.request.auth.AuthSettings.AuthLevel;
import google.registry.request.auth.OidcTokenAuthenticationMechanism.IapOidcAuthenticationMechanism;
import google.registry.request.auth.OidcTokenAuthenticationMechanism.RegularOidcAuthenticationMechanism;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Map;
import javax.inject.Singleton;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@@ -59,13 +60,13 @@ public class OidcTokenAuthenticationMechanismTest {
private final Payload payload = new Payload();
private final JsonWebSignature jwt =
new JsonWebSignature(new Header(), payload, new byte[0], new byte[0]);
private final TokenVerifier tokenVerifier = mock(TokenVerifier.class);
private final HttpServletRequest request = mock(HttpServletRequest.class);
private User user;
private AuthResult authResult;
private OidcTokenAuthenticationMechanism authenticationMechanism =
new OidcTokenAuthenticationMechanism(serviceAccounts, tokenVerifier, null, e -> rawToken) {};
new OidcTokenAuthenticationMechanism(
serviceAccounts, request -> rawToken, (service, token) -> jwt) {};
@RegisterExtension
public final JpaTestExtensions.JpaUnitTestExtension jpaExtension =
@@ -73,7 +74,6 @@ public class OidcTokenAuthenticationMechanismTest {
@BeforeEach
void beforeEach() throws Exception {
when(tokenVerifier.verify(rawToken)).thenReturn(jwt);
payload.setEmail(email);
payload.setSubject(gaiaId);
user = createAdminUser(email);
@@ -93,28 +93,23 @@ public class OidcTokenAuthenticationMechanismTest {
@Test
void testAuthenticate_noTokenFromRequest() {
authenticationMechanism =
new OidcTokenAuthenticationMechanism(serviceAccounts, tokenVerifier, null, e -> null) {};
new OidcTokenAuthenticationMechanism(
serviceAccounts, e -> null, (service, token) -> jwt) {};
authResult = authenticationMechanism.authenticate(request);
assertThat(authResult).isEqualTo(AuthResult.NOT_AUTHENTICATED);
}
@Test
void testAuthenticate_invalidToken() throws Exception {
when(tokenVerifier.verify(rawToken)).thenThrow(new VerificationException("Bad token"));
authResult = authenticationMechanism.authenticate(request);
assertThat(authResult).isEqualTo(AuthResult.NOT_AUTHENTICATED);
}
@Test
void testAuthenticate_fallbackVerifier() throws Exception {
TokenVerifier fallbackVerifier = mock(TokenVerifier.class);
when(tokenVerifier.verify(rawToken)).thenThrow(new VerificationException("Bad token"));
when(fallbackVerifier.verify(rawToken)).thenReturn(jwt);
authenticationMechanism =
new OidcTokenAuthenticationMechanism(
serviceAccounts, tokenVerifier, fallbackVerifier, e -> rawToken) {};
serviceAccounts,
e -> null,
(service, token) -> {
throw new VerificationException("Bad token");
}) {};
authResult = authenticationMechanism.authenticate(request);
assertThat(authResult.isAuthenticated()).isEqualTo(true);
assertThat(authResult).isEqualTo(AuthResult.NOT_AUTHENTICATED);
}
@Test
@@ -233,9 +228,9 @@ public class OidcTokenAuthenticationMechanismTest {
@Provides
@Singleton
@Config("fallbackOauthClientId")
String provideFallbackOauthClientId() {
return "fallback-client-id";
@Config("backendServiceIds")
Map<String, Long> provideBackendServiceIds() {
return ImmutableMap.of();
}
}
}
@@ -23,7 +23,7 @@ import google.registry.model.console.User;
import google.registry.request.auth.AuthResult;
import google.registry.security.XsrfTokenManager;
import google.registry.ui.server.SendEmailUtils;
import google.registry.ui.server.registrar.ConsoleApiParams;
import google.registry.ui.server.console.ConsoleApiParams;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import org.joda.time.DateTime;
@@ -33,7 +33,6 @@ import google.registry.request.auth.AuthResult;
import google.registry.testing.ConsoleApiParamsUtils;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.FakeResponse;
import google.registry.ui.server.registrar.ConsoleApiParams;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
@@ -36,7 +36,6 @@ import google.registry.testing.FakeClock;
import google.registry.testing.FakeResponse;
import google.registry.tools.GsonUtils;
import google.registry.ui.server.console.ConsoleDomainListAction.DomainListResult;
import google.registry.ui.server.registrar.ConsoleApiParams;
import java.util.Optional;
import javax.annotation.Nullable;
import org.joda.time.DateTime;
@@ -33,7 +33,6 @@ import google.registry.testing.DatabaseHelper;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeResponse;
import google.registry.tools.GsonUtils;
import google.registry.ui.server.registrar.ConsoleApiParams;
import java.io.IOException;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
@@ -44,8 +44,6 @@ import google.registry.testing.ConsoleApiParamsUtils;
import google.registry.testing.FakeResponse;
import google.registry.tools.GsonUtils;
import google.registry.ui.server.console.ConsoleEppPasswordAction.EppPasswordData;
import google.registry.ui.server.registrar.ConsoleApiParams;
import google.registry.ui.server.registrar.RegistrarConsoleModule;
import google.registry.util.EmailMessage;
import jakarta.mail.internet.AddressException;
import jakarta.mail.internet.InternetAddress;
@@ -167,7 +165,7 @@ class ConsoleEppPasswordActionTest {
.when(consoleApiParams.request())
.getReader();
Optional<EppPasswordData> maybePasswordChangeRequest =
RegistrarConsoleModule.provideEppPasswordChangeRequest(
ConsoleModule.provideEppPasswordChangeRequest(
GSON, RequestModule.provideJsonBody(consoleApiParams.request(), GSON));
return new ConsoleEppPasswordAction(
@@ -0,0 +1,255 @@
// 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.ui.server.console;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.OteAccountBuilderTest.verifyIapPermission;
import static google.registry.testing.DatabaseHelper.persistPremiumList;
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 static org.joda.money.CurrencyUnit.USD;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import google.registry.model.OteStatsTestHelper;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.User;
import google.registry.model.console.UserRoles;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.request.Action;
import google.registry.request.auth.AuthResult;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.ConsoleApiParamsUtils;
import google.registry.testing.DeterministicStringGenerator;
import google.registry.testing.FakeResponse;
import google.registry.tools.GsonUtils;
import google.registry.tools.IamClient;
import google.registry.ui.server.console.ConsoleOteAction.OteCreateData;
import google.registry.util.StringGenerator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.json.simple.JSONArray;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
class ConsoleOteActionTest {
@RegisterExtension
final JpaTestExtensions.JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
private static final Gson GSON = GsonUtils.provideGson();
private final IamClient iamClient = mock(IamClient.class);
private final CloudTasksHelper cloudTasksHelper = new CloudTasksHelper();
private FakeResponse response;
private ConsoleApiParams consoleApiParams;
private StringGenerator passwordGenerator =
new DeterministicStringGenerator("abcdefghijklmnopqrstuvwxyz");
private User user =
new User.Builder()
.setEmailAddress("marla.singer@example.com")
.setUserRoles(new UserRoles())
.build();
@BeforeEach
void beforeEach() throws Exception {
persistPremiumList("default_sandbox_list", USD, "sandbox,USD 1000");
}
@Test
void testFailure_missingGlobalPermission() {
AuthResult authResult = AuthResult.createUser(user);
consoleApiParams = ConsoleApiParamsUtils.createFake(authResult);
ConsoleOteAction action =
createAction(
Action.Method.POST,
authResult,
"testRegistrarId",
Optional.of("someRandomString"),
Optional.of(new OteCreateData("testRegistrarId", "tescontact@registry.example")));
action.run();
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_FORBIDDEN);
}
@Test
void testFailure_invalidParamsNoRegistrarId() {
user =
user.asBuilder()
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build())
.build();
AuthResult authResult = AuthResult.createUser(user);
consoleApiParams = ConsoleApiParamsUtils.createFake(authResult);
ConsoleOteAction action =
createAction(
Action.Method.POST,
authResult,
"testRegistrarId",
Optional.of("someRandomString"),
Optional.of(new OteCreateData("", "test@email.com")));
action.run();
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_BAD_REQUEST);
assertThat(((FakeResponse) consoleApiParams.response()).getPayload())
.isEqualTo("OT&E create body is invalid");
}
@Test
void testFailure_invalidParamsNoEmail() {
user =
user.asBuilder()
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build())
.build();
AuthResult authResult = AuthResult.createUser(user);
consoleApiParams = ConsoleApiParamsUtils.createFake(authResult);
ConsoleOteAction action =
createAction(
Action.Method.POST,
authResult,
"testRegistrarId",
Optional.of("someRandomString"),
Optional.of(new OteCreateData("testRegistrarId", "")));
action.run();
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_BAD_REQUEST);
assertThat(((FakeResponse) consoleApiParams.response()).getPayload())
.isEqualTo("OT&E create body is invalid");
}
@Test
void testSuccess_oteCreated() {
user =
user.asBuilder()
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build())
.build();
AuthResult authResult = AuthResult.createUser(user);
consoleApiParams = ConsoleApiParamsUtils.createFake(authResult);
ConsoleOteAction action =
createAction(
Action.Method.POST,
authResult,
"theregistrar",
Optional.of("someRandomString@email.test"),
Optional.of(new OteCreateData("theregistrar", "contact@registry.example")));
action.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
action.run();
String response = ((FakeResponse) consoleApiParams.response()).getPayload();
var obsResponse = GSON.fromJson(response, Map.class);
assertThat(
ImmutableMap.of(
"theregistrar-1", "theregistrar-sunrise",
"theregistrar-3", "theregistrar-ga",
"theregistrar-4", "theregistrar-ga",
"theregistrar-5", "theregistrar-eap",
"password", "abcdefghijklmnop"))
.containsExactlyEntriesIn(obsResponse);
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_OK);
verifyIapPermission(
"contact@registry.example",
Optional.of("someRandomString@email.test"),
cloudTasksHelper,
iamClient);
}
@Test
void testFail_statusMissingParam() {
AuthResult authResult = AuthResult.createUser(user);
consoleApiParams = ConsoleApiParamsUtils.createFake(authResult);
ConsoleOteAction action =
createAction(
Action.Method.GET,
authResult,
"",
Optional.of("someRandomString@email.test"),
Optional.of(new OteCreateData("theregistrar", "contact@registry.example")));
action.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
action.run();
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_BAD_REQUEST);
assertThat(((FakeResponse) consoleApiParams.response()).getPayload())
.isEqualTo("Missing registrarId parameter");
}
@Test
void testSuccess_finishedOte() throws Exception {
OteStatsTestHelper.setupCompleteOte("theregistrar");
user =
user.asBuilder()
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build())
.build();
AuthResult authResult = AuthResult.createUser(user);
consoleApiParams = ConsoleApiParamsUtils.createFake(authResult);
ConsoleOteAction action =
createAction(
Action.Method.GET, authResult, "theregistrar-1", Optional.empty(), Optional.empty());
action.run();
List<Map<String, ?>> response =
GSON.fromJson(((FakeResponse) consoleApiParams.response()).getPayload(), JSONArray.class);
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_OK);
assertTrue(response.stream().allMatch(status -> Boolean.TRUE.equals(status.get("completed"))));
}
@Test
void testSuccess_unfinishedOte() throws Exception {
OteStatsTestHelper.setupIncompleteOte("theregistrar");
user =
user.asBuilder()
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build())
.build();
AuthResult authResult = AuthResult.createUser(user);
consoleApiParams = ConsoleApiParamsUtils.createFake(authResult);
ConsoleOteAction action =
createAction(
Action.Method.GET, authResult, "theregistrar-1", Optional.empty(), Optional.empty());
action.run();
List<Map<String, ?>> response =
GSON.fromJson(((FakeResponse) consoleApiParams.response()).getPayload(), JSONArray.class);
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_OK);
assertThat(
response.stream()
.filter(status -> Boolean.FALSE.equals(status.get("completed")))
.map(status -> status.get("description"))
.collect(Collectors.toList()))
.containsExactlyElementsIn(
ImmutableList.of("domain creates idn", "domain restores", "host deletes"));
}
private ConsoleOteAction createAction(
Action.Method method,
AuthResult authResult,
String registrarId,
Optional<String> maybeGroupEmailAddress,
Optional<OteCreateData> oteCreateData) {
response = new FakeResponse();
consoleApiParams = ConsoleApiParamsUtils.createFake(authResult);
when(consoleApiParams.request().getMethod()).thenReturn(method.toString());
return new ConsoleOteAction(
consoleApiParams,
GSON,
iamClient,
registrarId,
maybeGroupEmailAddress,
passwordGenerator,
oteCreateData);
}
}
@@ -52,7 +52,6 @@ import google.registry.testing.DeterministicStringGenerator;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeResponse;
import google.registry.tools.DomainLockUtils;
import google.registry.ui.server.registrar.ConsoleApiParams;
import google.registry.util.EmailMessage;
import google.registry.util.StringGenerator;
import jakarta.mail.internet.InternetAddress;
@@ -40,7 +40,6 @@ import google.registry.testing.DeterministicStringGenerator;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeResponse;
import google.registry.tools.DomainLockUtils;
import google.registry.ui.server.registrar.ConsoleApiParams;
import google.registry.util.StringGenerator;
import jakarta.servlet.http.HttpServletResponse;
import org.joda.time.Duration;
@@ -42,8 +42,6 @@ import google.registry.testing.ConsoleApiParamsUtils;
import google.registry.testing.FakeResponse;
import google.registry.testing.SystemPropertyExtension;
import google.registry.tools.GsonUtils;
import google.registry.ui.server.registrar.ConsoleApiParams;
import google.registry.ui.server.registrar.RegistrarConsoleModule;
import google.registry.util.EmailMessage;
import google.registry.util.RegistryEnvironment;
import jakarta.mail.internet.AddressException;
@@ -172,7 +170,7 @@ class ConsoleUpdateRegistrarActionTest {
.when(consoleApiParams.request())
.getReader();
Optional<Registrar> maybeRegistrarUpdateData =
RegistrarConsoleModule.provideRegistrar(
ConsoleModule.provideRegistrar(
GSON, RequestModule.provideJsonBody(consoleApiParams.request(), GSON));
return new ConsoleUpdateRegistrarAction(consoleApiParams, maybeRegistrarUpdateData);
}
@@ -28,7 +28,6 @@ import google.registry.request.auth.AuthResult;
import google.registry.testing.ConsoleApiParamsUtils;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.FakeResponse;
import google.registry.ui.server.registrar.ConsoleApiParams;
import jakarta.servlet.http.Cookie;
import java.io.IOException;
import java.util.List;
@@ -41,8 +41,6 @@ import google.registry.request.auth.AuthResult;
import google.registry.testing.ConsoleApiParamsUtils;
import google.registry.testing.DeterministicStringGenerator;
import google.registry.testing.FakeResponse;
import google.registry.ui.server.registrar.ConsoleApiParams;
import google.registry.ui.server.registrar.RegistrarConsoleModule;
import google.registry.util.StringGenerator;
import java.io.BufferedReader;
import java.io.IOException;
@@ -253,7 +251,7 @@ class RegistrarsActionTest {
passcodeGenerator);
}
Optional<Registrar> maybeRegistrar =
RegistrarConsoleModule.provideRegistrar(
ConsoleModule.provideRegistrar(
GSON, RequestModule.provideJsonBody(consoleApiParams.request(), GSON));
return new RegistrarsAction(
consoleApiParams, GSON, maybeRegistrar, passwordGenerator, passcodeGenerator);
@@ -45,8 +45,8 @@ import google.registry.request.RequestModule;
import google.registry.request.auth.AuthResult;
import google.registry.testing.ConsoleApiParamsUtils;
import google.registry.testing.FakeResponse;
import google.registry.ui.server.registrar.ConsoleApiParams;
import google.registry.ui.server.registrar.RegistrarConsoleModule;
import google.registry.ui.server.console.ConsoleApiParams;
import google.registry.ui.server.console.ConsoleModule;
import google.registry.util.EmailMessage;
import jakarta.mail.internet.AddressException;
import jakarta.mail.internet.InternetAddress;
@@ -296,7 +296,7 @@ class ContactActionTest {
.when(consoleApiParams.request())
.getReader();
Optional<ImmutableSet<RegistrarPoc>> maybeContacts =
RegistrarConsoleModule.provideContacts(
ConsoleModule.provideContacts(
GSON, RequestModule.provideJsonBody(consoleApiParams.request(), GSON));
return new ContactAction(consoleApiParams, GSON, registrarId, maybeContacts);
}
@@ -38,8 +38,8 @@ import google.registry.testing.ConsoleApiParamsUtils;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeResponse;
import google.registry.ui.server.registrar.ConsoleApiParams;
import google.registry.ui.server.registrar.RegistrarConsoleModule;
import google.registry.ui.server.console.ConsoleApiParams;
import google.registry.ui.server.console.ConsoleModule;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
@@ -108,7 +108,7 @@ class SecurityActionTest {
.when(consoleApiParams.request())
.getReader();
Optional<Registrar> maybeRegistrar =
RegistrarConsoleModule.provideRegistrar(
ConsoleModule.provideRegistrar(
GSON, RequestModule.provideJsonBody(consoleApiParams.request(), GSON));
return new SecurityAction(
consoleApiParams, certificateChecker, registrarAccessor, registrarId, maybeRegistrar);
@@ -40,8 +40,8 @@ import google.registry.testing.ConsoleApiParamsUtils;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeResponse;
import google.registry.ui.server.registrar.ConsoleApiParams;
import google.registry.ui.server.registrar.RegistrarConsoleModule;
import google.registry.ui.server.console.ConsoleApiParams;
import google.registry.ui.server.console.ConsoleModule;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
@@ -169,7 +169,7 @@ public class WhoisRegistrarFieldsActionTest {
return new WhoisRegistrarFieldsAction(
consoleApiParams,
registrarAccessor,
RegistrarConsoleModule.provideRegistrar(
ConsoleModule.provideRegistrar(
GSON, RequestModule.provideJsonBody(consoleApiParams.request(), GSON)));
}
}
@@ -1,38 +1,38 @@
PATH CLASS METHODS OK MIN USER_POLICY
/_dr/cron/fanout TldFanoutAction GET y APP ADMIN
/_dr/dnsRefresh RefreshDnsAction GET y APP ADMIN
/_dr/task/brdaCopy BrdaCopyAction POST y APP ADMIN
/_dr/task/copyDetailReports CopyDetailReportsAction POST n APP ADMIN
/_dr/task/deleteExpiredDomains DeleteExpiredDomainsAction GET n APP ADMIN
/_dr/task/deleteLoadTestData DeleteLoadTestDataAction POST n APP ADMIN
/_dr/task/deleteProberData DeleteProberDataAction POST n APP ADMIN
/_dr/task/executeCannedScript CannedScriptExecutionAction POST,GET y APP ADMIN
/_dr/task/expandBillingRecurrences ExpandBillingRecurrencesAction GET n APP ADMIN
/_dr/task/exportDomainLists ExportDomainListsAction POST n APP ADMIN
/_dr/task/exportPremiumTerms ExportPremiumTermsAction POST n APP ADMIN
/_dr/task/exportReservedTerms ExportReservedTermsAction POST n APP ADMIN
/_dr/task/generateInvoices GenerateInvoicesAction POST n APP ADMIN
/_dr/task/generateSpec11 GenerateSpec11ReportAction POST n APP ADMIN
/_dr/task/icannReportingStaging IcannReportingStagingAction POST n APP ADMIN
/_dr/task/icannReportingUpload IcannReportingUploadAction POST n APP ADMIN
/_dr/task/nordnUpload NordnUploadAction POST y APP ADMIN
/_dr/task/nordnVerify NordnVerifyAction POST y APP ADMIN
/_dr/task/publishDnsUpdates PublishDnsUpdatesAction POST y APP ADMIN
/_dr/task/publishInvoices PublishInvoicesAction POST n APP ADMIN
/_dr/task/publishSpec11 PublishSpec11ReportAction POST n APP ADMIN
/_dr/task/rdeReport RdeReportAction POST n APP ADMIN
/_dr/task/rdeStaging RdeStagingAction GET,POST n APP ADMIN
/_dr/task/rdeUpload RdeUploadAction POST n APP ADMIN
/_dr/task/readDnsRefreshRequests ReadDnsRefreshRequestsAction POST y APP ADMIN
/_dr/task/refreshDnsOnHostRename RefreshDnsOnHostRenameAction POST n APP ADMIN
/_dr/task/relockDomain RelockDomainAction POST y APP ADMIN
/_dr/task/resaveAllEppResourcesPipeline ResaveAllEppResourcesPipelineAction GET n APP ADMIN
/_dr/task/resaveEntity ResaveEntityAction POST n APP ADMIN
/_dr/task/sendExpiringCertificateNotificationEmail SendExpiringCertificateNotificationEmailAction GET n APP ADMIN
/_dr/task/syncGroupMembers SyncGroupMembersAction POST n APP ADMIN
/_dr/task/syncRegistrarsSheet SyncRegistrarsSheetAction POST n APP ADMIN
/_dr/task/tmchCrl TmchCrlAction POST y APP ADMIN
/_dr/task/tmchDnl TmchDnlAction POST y APP ADMIN
/_dr/task/tmchSmdrl TmchSmdrlAction POST y APP ADMIN
/_dr/task/updateRegistrarRdapBaseUrls UpdateRegistrarRdapBaseUrlsAction GET y APP ADMIN
/_dr/task/wipeOutContactHistoryPii WipeOutContactHistoryPiiAction GET n APP ADMIN
SERVICE PATH CLASS METHODS OK MIN USER_POLICY
BACKEND /_dr/cron/fanout TldFanoutAction GET y APP ADMIN
BACKEND /_dr/task/brdaCopy BrdaCopyAction POST y APP ADMIN
BACKEND /_dr/task/copyDetailReports CopyDetailReportsAction POST n APP ADMIN
BACKEND /_dr/task/deleteExpiredDomains DeleteExpiredDomainsAction GET n APP ADMIN
BACKEND /_dr/task/deleteLoadTestData DeleteLoadTestDataAction POST n APP ADMIN
BACKEND /_dr/task/deleteProberData DeleteProberDataAction POST n APP ADMIN
BACKEND /_dr/task/dnsRefresh RefreshDnsAction GET y APP ADMIN
BACKEND /_dr/task/executeCannedScript CannedScriptExecutionAction POST,GET y APP ADMIN
BACKEND /_dr/task/expandBillingRecurrences ExpandBillingRecurrencesAction GET n APP ADMIN
BACKEND /_dr/task/exportDomainLists ExportDomainListsAction POST n APP ADMIN
BACKEND /_dr/task/exportPremiumTerms ExportPremiumTermsAction POST n APP ADMIN
BACKEND /_dr/task/exportReservedTerms ExportReservedTermsAction POST n APP ADMIN
BACKEND /_dr/task/generateInvoices GenerateInvoicesAction POST n APP ADMIN
BACKEND /_dr/task/generateSpec11 GenerateSpec11ReportAction POST n APP ADMIN
BACKEND /_dr/task/icannReportingStaging IcannReportingStagingAction POST n APP ADMIN
BACKEND /_dr/task/icannReportingUpload IcannReportingUploadAction POST n APP ADMIN
BACKEND /_dr/task/nordnUpload NordnUploadAction POST y APP ADMIN
BACKEND /_dr/task/nordnVerify NordnVerifyAction POST y APP ADMIN
BACKEND /_dr/task/publishDnsUpdates PublishDnsUpdatesAction POST y APP ADMIN
BACKEND /_dr/task/publishInvoices PublishInvoicesAction POST n APP ADMIN
BACKEND /_dr/task/publishSpec11 PublishSpec11ReportAction POST n APP ADMIN
BACKEND /_dr/task/rdeReport RdeReportAction POST n APP ADMIN
BACKEND /_dr/task/rdeStaging RdeStagingAction GET,POST n APP ADMIN
BACKEND /_dr/task/rdeUpload RdeUploadAction POST n APP ADMIN
BACKEND /_dr/task/readDnsRefreshRequests ReadDnsRefreshRequestsAction POST y APP ADMIN
BACKEND /_dr/task/refreshDnsOnHostRename RefreshDnsOnHostRenameAction POST n APP ADMIN
BACKEND /_dr/task/relockDomain RelockDomainAction POST y APP ADMIN
BACKEND /_dr/task/resaveAllEppResourcesPipeline ResaveAllEppResourcesPipelineAction GET n APP ADMIN
BACKEND /_dr/task/resaveEntity ResaveEntityAction POST n APP ADMIN
BACKEND /_dr/task/sendExpiringCertificateNotificationEmail SendExpiringCertificateNotificationEmailAction GET n APP ADMIN
BACKEND /_dr/task/syncGroupMembers SyncGroupMembersAction POST n APP ADMIN
BACKEND /_dr/task/syncRegistrarsSheet SyncRegistrarsSheetAction POST n APP ADMIN
BACKEND /_dr/task/tmchCrl TmchCrlAction POST y APP ADMIN
BACKEND /_dr/task/tmchDnl TmchDnlAction POST y APP ADMIN
BACKEND /_dr/task/tmchSmdrl TmchSmdrlAction POST y APP ADMIN
BACKEND /_dr/task/updateRegistrarRdapBaseUrls UpdateRegistrarRdapBaseUrlsAction GET y APP ADMIN
BACKEND /_dr/task/wipeOutContactHistoryPii WipeOutContactHistoryPiiAction GET n APP ADMIN
@@ -1,5 +1,5 @@
PATH CLASS METHODS OK MIN USER_POLICY
/_dr/task/bsaDownload BsaDownloadAction GET,POST n APP ADMIN
/_dr/task/bsaRefresh BsaRefreshAction GET,POST n APP ADMIN
/_dr/task/bsaValidate BsaValidateAction GET,POST n APP ADMIN
/_dr/task/uploadBsaUnavailableNames UploadBsaUnavailableDomainsAction GET,POST n APP ADMIN
SERVICE PATH CLASS METHODS OK MIN USER_POLICY
BACKEND /_dr/task/bsaDownload BsaDownloadAction GET,POST n APP ADMIN
BACKEND /_dr/task/bsaRefresh BsaRefreshAction GET,POST n APP ADMIN
BACKEND /_dr/task/bsaValidate BsaValidateAction GET,POST n APP ADMIN
BACKEND /_dr/task/uploadBsaUnavailableNames UploadBsaUnavailableDomainsAction GET,POST n APP ADMIN
@@ -1,22 +1,23 @@
PATH CLASS METHODS OK MIN USER_POLICY
/_dr/epp EppTlsAction POST n APP ADMIN
/console-api/domain ConsoleDomainGetAction GET n USER PUBLIC
/console-api/domain-list ConsoleDomainListAction GET n USER PUBLIC
/console-api/dum-download ConsoleDumDownloadAction GET n USER PUBLIC
/console-api/eppPassword ConsoleEppPasswordAction POST n USER PUBLIC
/console-api/registrar ConsoleUpdateRegistrarAction POST n USER PUBLIC
/console-api/registrars RegistrarsAction GET,POST n USER PUBLIC
/console-api/registry-lock ConsoleRegistryLockAction GET,POST n USER PUBLIC
/console-api/registry-lock-verify ConsoleRegistryLockVerifyAction GET n USER PUBLIC
/console-api/settings/contacts ContactAction GET,POST n USER PUBLIC
/console-api/settings/security SecurityAction POST n USER PUBLIC
/console-api/settings/whois-fields WhoisRegistrarFieldsAction POST n USER PUBLIC
/console-api/userdata ConsoleUserDataAction GET n USER PUBLIC
/registrar ConsoleUiAction GET n USER PUBLIC
/registrar-create ConsoleRegistrarCreatorAction POST,GET n USER PUBLIC
/registrar-ote-setup ConsoleOteSetupAction POST,GET n USER PUBLIC
/registrar-ote-status OteStatusAction POST n USER PUBLIC
/registrar-settings RegistrarSettingsAction POST n USER PUBLIC
/registry-lock-get RegistryLockGetAction GET n USER PUBLIC
/registry-lock-post RegistryLockPostAction POST n USER PUBLIC
/registry-lock-verify RegistryLockVerifyAction GET n USER PUBLIC
SERVICE PATH CLASS METHODS OK MIN USER_POLICY
FRONTEND /_dr/epp EppTlsAction POST n APP ADMIN
FRONTEND /registrar ConsoleUiAction GET n USER PUBLIC
FRONTEND /registrar-create ConsoleRegistrarCreatorAction POST,GET n USER PUBLIC
FRONTEND /registrar-ote-setup ConsoleOteSetupAction POST,GET n USER PUBLIC
FRONTEND /registrar-ote-status OteStatusAction POST n USER PUBLIC
FRONTEND /registrar-settings RegistrarSettingsAction POST n USER PUBLIC
FRONTEND /registry-lock-get RegistryLockGetAction GET n USER PUBLIC
FRONTEND /registry-lock-post RegistryLockPostAction POST n USER PUBLIC
FRONTEND /registry-lock-verify RegistryLockVerifyAction GET n USER PUBLIC
CONSOLE /console-api/domain ConsoleDomainGetAction GET n USER PUBLIC
CONSOLE /console-api/domain-list ConsoleDomainListAction GET n USER PUBLIC
CONSOLE /console-api/dum-download ConsoleDumDownloadAction GET n USER PUBLIC
CONSOLE /console-api/eppPassword ConsoleEppPasswordAction POST n USER PUBLIC
CONSOLE /console-api/ote ConsoleOteAction GET,POST n USER PUBLIC
CONSOLE /console-api/registrar ConsoleUpdateRegistrarAction POST n USER PUBLIC
CONSOLE /console-api/registrars RegistrarsAction GET,POST n USER PUBLIC
CONSOLE /console-api/registry-lock ConsoleRegistryLockAction GET,POST n USER PUBLIC
CONSOLE /console-api/registry-lock-verify ConsoleRegistryLockVerifyAction GET n USER PUBLIC
CONSOLE /console-api/settings/contacts ContactAction GET,POST n USER PUBLIC
CONSOLE /console-api/settings/security SecurityAction POST n USER PUBLIC
CONSOLE /console-api/settings/whois-fields WhoisRegistrarFieldsAction POST n USER PUBLIC
CONSOLE /console-api/userdata ConsoleUserDataAction GET n USER PUBLIC
@@ -1,13 +1,13 @@
PATH CLASS METHODS OK MIN USER_POLICY
/_dr/whois WhoisAction POST n APP ADMIN
/check CheckApiAction GET n NONE PUBLIC
/rdap/autnum/(*) RdapAutnumAction GET,HEAD n NONE PUBLIC
/rdap/domain/(*) RdapDomainAction GET,HEAD n NONE PUBLIC
/rdap/domains RdapDomainSearchAction GET,HEAD n NONE PUBLIC
/rdap/entities RdapEntitySearchAction GET,HEAD n NONE PUBLIC
/rdap/entity/(*) RdapEntityAction GET,HEAD n NONE PUBLIC
/rdap/help(*) RdapHelpAction GET,HEAD n NONE PUBLIC
/rdap/ip/(*) RdapIpAction GET,HEAD n NONE PUBLIC
/rdap/nameserver/(*) RdapNameserverAction GET,HEAD n NONE PUBLIC
/rdap/nameservers RdapNameserverSearchAction GET,HEAD n NONE PUBLIC
/whois/(*) WhoisHttpAction GET n NONE PUBLIC
SERVICE PATH CLASS METHODS OK MIN USER_POLICY
PUBAPI /_dr/whois WhoisAction POST n APP ADMIN
PUBAPI /check CheckApiAction GET n NONE PUBLIC
PUBAPI /rdap/autnum/(*) RdapAutnumAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/domain/(*) RdapDomainAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/domains RdapDomainSearchAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/entities RdapEntitySearchAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/entity/(*) RdapEntityAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/help(*) RdapHelpAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/ip/(*) RdapIpAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/nameserver/(*) RdapNameserverAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/nameservers RdapNameserverSearchAction GET,HEAD n NONE PUBLIC
PUBAPI /whois/(*) WhoisHttpAction GET n NONE PUBLIC
@@ -1,88 +1,81 @@
PATH CLASS METHODS OK MIN USER_POLICY
/_dr/admin/createGroups CreateGroupsAction POST n APP ADMIN
/_dr/admin/list/domains ListDomainsAction GET,POST n APP ADMIN
/_dr/admin/list/hosts ListHostsAction GET,POST n APP ADMIN
/_dr/admin/list/premiumLists ListPremiumListsAction GET,POST n APP ADMIN
/_dr/admin/list/registrars ListRegistrarsAction GET,POST n APP ADMIN
/_dr/admin/list/reservedLists ListReservedListsAction GET,POST n APP ADMIN
/_dr/admin/list/tlds ListTldsAction GET,POST n APP ADMIN
/_dr/admin/updateUserGroup UpdateUserGroupAction POST n APP ADMIN
/_dr/admin/verifyOte VerifyOteAction POST n APP ADMIN
/_dr/cron/fanout TldFanoutAction GET y APP ADMIN
/_dr/dnsRefresh RefreshDnsAction GET y APP ADMIN
/_dr/epp EppTlsAction POST n APP ADMIN
/_dr/epptool EppToolAction POST n APP ADMIN
/_dr/loadtest LoadTestAction POST y APP ADMIN
/_dr/task/brdaCopy BrdaCopyAction POST y APP ADMIN
/_dr/task/bsaDownload BsaDownloadAction GET,POST n APP ADMIN
/_dr/task/bsaRefresh BsaRefreshAction GET,POST n APP ADMIN
/_dr/task/bsaValidate BsaValidateAction GET,POST n APP ADMIN
/_dr/task/copyDetailReports CopyDetailReportsAction POST n APP ADMIN
/_dr/task/deleteExpiredDomains DeleteExpiredDomainsAction GET n APP ADMIN
/_dr/task/deleteLoadTestData DeleteLoadTestDataAction POST n APP ADMIN
/_dr/task/deleteProberData DeleteProberDataAction POST n APP ADMIN
/_dr/task/executeCannedScript CannedScriptExecutionAction POST,GET y APP ADMIN
/_dr/task/expandBillingRecurrences ExpandBillingRecurrencesAction GET n APP ADMIN
/_dr/task/exportDomainLists ExportDomainListsAction POST n APP ADMIN
/_dr/task/exportPremiumTerms ExportPremiumTermsAction POST n APP ADMIN
/_dr/task/exportReservedTerms ExportReservedTermsAction POST n APP ADMIN
/_dr/task/generateInvoices GenerateInvoicesAction POST n APP ADMIN
/_dr/task/generateSpec11 GenerateSpec11ReportAction POST n APP ADMIN
/_dr/task/generateZoneFiles GenerateZoneFilesAction POST n APP ADMIN
/_dr/task/icannReportingStaging IcannReportingStagingAction POST n APP ADMIN
/_dr/task/icannReportingUpload IcannReportingUploadAction POST n APP ADMIN
/_dr/task/nordnUpload NordnUploadAction POST y APP ADMIN
/_dr/task/nordnVerify NordnVerifyAction POST y APP ADMIN
/_dr/task/publishDnsUpdates PublishDnsUpdatesAction POST y APP ADMIN
/_dr/task/publishInvoices PublishInvoicesAction POST n APP ADMIN
/_dr/task/publishSpec11 PublishSpec11ReportAction POST n APP ADMIN
/_dr/task/rdeReport RdeReportAction POST n APP ADMIN
/_dr/task/rdeStaging RdeStagingAction GET,POST n APP ADMIN
/_dr/task/rdeUpload RdeUploadAction POST n APP ADMIN
/_dr/task/readDnsRefreshRequests ReadDnsRefreshRequestsAction POST y APP ADMIN
/_dr/task/refreshDnsForAllDomains RefreshDnsForAllDomainsAction GET n APP ADMIN
/_dr/task/refreshDnsOnHostRename RefreshDnsOnHostRenameAction POST n APP ADMIN
/_dr/task/relockDomain RelockDomainAction POST y APP ADMIN
/_dr/task/resaveAllEppResourcesPipeline ResaveAllEppResourcesPipelineAction GET n APP ADMIN
/_dr/task/resaveEntity ResaveEntityAction POST n APP ADMIN
/_dr/task/sendExpiringCertificateNotificationEmail SendExpiringCertificateNotificationEmailAction GET n APP ADMIN
/_dr/task/syncGroupMembers SyncGroupMembersAction POST n APP ADMIN
/_dr/task/syncRegistrarsSheet SyncRegistrarsSheetAction POST n APP ADMIN
/_dr/task/tmchCrl TmchCrlAction POST y APP ADMIN
/_dr/task/tmchDnl TmchDnlAction POST y APP ADMIN
/_dr/task/tmchSmdrl TmchSmdrlAction POST y APP ADMIN
/_dr/task/updateRegistrarRdapBaseUrls UpdateRegistrarRdapBaseUrlsAction GET y APP ADMIN
/_dr/task/uploadBsaUnavailableNames UploadBsaUnavailableDomainsAction GET,POST n APP ADMIN
/_dr/task/wipeOutContactHistoryPii WipeOutContactHistoryPiiAction GET n APP ADMIN
/_dr/whois WhoisAction POST n APP ADMIN
/check CheckApiAction GET n NONE PUBLIC
/console-api/domain ConsoleDomainGetAction GET n USER PUBLIC
/console-api/domain-list ConsoleDomainListAction GET n USER PUBLIC
/console-api/dum-download ConsoleDumDownloadAction GET n USER PUBLIC
/console-api/eppPassword ConsoleEppPasswordAction POST n USER PUBLIC
/console-api/registrar ConsoleUpdateRegistrarAction POST n USER PUBLIC
/console-api/registrars RegistrarsAction GET,POST n USER PUBLIC
/console-api/registry-lock ConsoleRegistryLockAction GET,POST n USER PUBLIC
/console-api/registry-lock-verify ConsoleRegistryLockVerifyAction GET n USER PUBLIC
/console-api/settings/contacts ContactAction GET,POST n USER PUBLIC
/console-api/settings/security SecurityAction POST n USER PUBLIC
/console-api/settings/whois-fields WhoisRegistrarFieldsAction POST n USER PUBLIC
/console-api/userdata ConsoleUserDataAction GET n USER PUBLIC
/rdap/autnum/(*) RdapAutnumAction GET,HEAD n NONE PUBLIC
/rdap/domain/(*) RdapDomainAction GET,HEAD n NONE PUBLIC
/rdap/domains RdapDomainSearchAction GET,HEAD n NONE PUBLIC
/rdap/entities RdapEntitySearchAction GET,HEAD n NONE PUBLIC
/rdap/entity/(*) RdapEntityAction GET,HEAD n NONE PUBLIC
/rdap/help(*) RdapHelpAction GET,HEAD n NONE PUBLIC
/rdap/ip/(*) RdapIpAction GET,HEAD n NONE PUBLIC
/rdap/nameserver/(*) RdapNameserverAction GET,HEAD n NONE PUBLIC
/rdap/nameservers RdapNameserverSearchAction GET,HEAD n NONE PUBLIC
/registrar ConsoleUiAction GET n USER PUBLIC
/registrar-create ConsoleRegistrarCreatorAction POST,GET n USER PUBLIC
/registrar-ote-setup ConsoleOteSetupAction POST,GET n USER PUBLIC
/registrar-ote-status OteStatusAction POST n USER PUBLIC
/registrar-settings RegistrarSettingsAction POST n USER PUBLIC
/registry-lock-get RegistryLockGetAction GET n USER PUBLIC
/registry-lock-post RegistryLockPostAction POST n USER PUBLIC
/registry-lock-verify RegistryLockVerifyAction GET n USER PUBLIC
/whois/(*) WhoisHttpAction GET n NONE PUBLIC
SERVICE PATH CLASS METHODS OK MIN USER_POLICY
FRONTEND /_dr/epp EppTlsAction POST n APP ADMIN
BACKEND /_dr/admin/createGroups CreateGroupsAction POST n APP ADMIN
BACKEND /_dr/admin/list/domains ListDomainsAction GET,POST n APP ADMIN
BACKEND /_dr/admin/list/hosts ListHostsAction GET,POST n APP ADMIN
BACKEND /_dr/admin/list/premiumLists ListPremiumListsAction GET,POST n APP ADMIN
BACKEND /_dr/admin/list/registrars ListRegistrarsAction GET,POST n APP ADMIN
BACKEND /_dr/admin/list/reservedLists ListReservedListsAction GET,POST n APP ADMIN
BACKEND /_dr/admin/list/tlds ListTldsAction GET,POST n APP ADMIN
BACKEND /_dr/admin/updateUserGroup UpdateUserGroupAction POST n APP ADMIN
BACKEND /_dr/admin/verifyOte VerifyOteAction POST n APP ADMIN
BACKEND /_dr/cron/fanout TldFanoutAction GET y APP ADMIN
BACKEND /_dr/epptool EppToolAction POST n APP ADMIN
BACKEND /_dr/loadtest LoadTestAction POST y APP ADMIN
BACKEND /_dr/task/brdaCopy BrdaCopyAction POST y APP ADMIN
BACKEND /_dr/task/bsaDownload BsaDownloadAction GET,POST n APP ADMIN
BACKEND /_dr/task/bsaRefresh BsaRefreshAction GET,POST n APP ADMIN
BACKEND /_dr/task/bsaValidate BsaValidateAction GET,POST n APP ADMIN
BACKEND /_dr/task/copyDetailReports CopyDetailReportsAction POST n APP ADMIN
BACKEND /_dr/task/deleteExpiredDomains DeleteExpiredDomainsAction GET n APP ADMIN
BACKEND /_dr/task/deleteLoadTestData DeleteLoadTestDataAction POST n APP ADMIN
BACKEND /_dr/task/deleteProberData DeleteProberDataAction POST n APP ADMIN
BACKEND /_dr/task/dnsRefresh RefreshDnsAction GET y APP ADMIN
BACKEND /_dr/task/executeCannedScript CannedScriptExecutionAction POST,GET y APP ADMIN
BACKEND /_dr/task/expandBillingRecurrences ExpandBillingRecurrencesAction GET n APP ADMIN
BACKEND /_dr/task/exportDomainLists ExportDomainListsAction POST n APP ADMIN
BACKEND /_dr/task/exportPremiumTerms ExportPremiumTermsAction POST n APP ADMIN
BACKEND /_dr/task/exportReservedTerms ExportReservedTermsAction POST n APP ADMIN
BACKEND /_dr/task/generateInvoices GenerateInvoicesAction POST n APP ADMIN
BACKEND /_dr/task/generateSpec11 GenerateSpec11ReportAction POST n APP ADMIN
BACKEND /_dr/task/generateZoneFiles GenerateZoneFilesAction POST n APP ADMIN
BACKEND /_dr/task/icannReportingStaging IcannReportingStagingAction POST n APP ADMIN
BACKEND /_dr/task/icannReportingUpload IcannReportingUploadAction POST n APP ADMIN
BACKEND /_dr/task/nordnUpload NordnUploadAction POST y APP ADMIN
BACKEND /_dr/task/nordnVerify NordnVerifyAction POST y APP ADMIN
BACKEND /_dr/task/publishDnsUpdates PublishDnsUpdatesAction POST y APP ADMIN
BACKEND /_dr/task/publishInvoices PublishInvoicesAction POST n APP ADMIN
BACKEND /_dr/task/publishSpec11 PublishSpec11ReportAction POST n APP ADMIN
BACKEND /_dr/task/rdeReport RdeReportAction POST n APP ADMIN
BACKEND /_dr/task/rdeStaging RdeStagingAction GET,POST n APP ADMIN
BACKEND /_dr/task/rdeUpload RdeUploadAction POST n APP ADMIN
BACKEND /_dr/task/readDnsRefreshRequests ReadDnsRefreshRequestsAction POST y APP ADMIN
BACKEND /_dr/task/refreshDnsForAllDomains RefreshDnsForAllDomainsAction GET n APP ADMIN
BACKEND /_dr/task/refreshDnsOnHostRename RefreshDnsOnHostRenameAction POST n APP ADMIN
BACKEND /_dr/task/relockDomain RelockDomainAction POST y APP ADMIN
BACKEND /_dr/task/resaveAllEppResourcesPipeline ResaveAllEppResourcesPipelineAction GET n APP ADMIN
BACKEND /_dr/task/resaveEntity ResaveEntityAction POST n APP ADMIN
BACKEND /_dr/task/sendExpiringCertificateNotificationEmail SendExpiringCertificateNotificationEmailAction GET n APP ADMIN
BACKEND /_dr/task/syncGroupMembers SyncGroupMembersAction POST n APP ADMIN
BACKEND /_dr/task/syncRegistrarsSheet SyncRegistrarsSheetAction POST n APP ADMIN
BACKEND /_dr/task/tmchCrl TmchCrlAction POST y APP ADMIN
BACKEND /_dr/task/tmchDnl TmchDnlAction POST y APP ADMIN
BACKEND /_dr/task/tmchSmdrl TmchSmdrlAction POST y APP ADMIN
BACKEND /_dr/task/updateRegistrarRdapBaseUrls UpdateRegistrarRdapBaseUrlsAction GET y APP ADMIN
BACKEND /_dr/task/uploadBsaUnavailableNames UploadBsaUnavailableDomainsAction GET,POST n APP ADMIN
BACKEND /_dr/task/wipeOutContactHistoryPii WipeOutContactHistoryPiiAction GET n APP ADMIN
PUBAPI /_dr/whois WhoisAction POST n APP ADMIN
PUBAPI /check CheckApiAction GET n NONE PUBLIC
PUBAPI /rdap/autnum/(*) RdapAutnumAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/domain/(*) RdapDomainAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/domains RdapDomainSearchAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/entities RdapEntitySearchAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/entity/(*) RdapEntityAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/help(*) RdapHelpAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/ip/(*) RdapIpAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/nameserver/(*) RdapNameserverAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/nameservers RdapNameserverSearchAction GET,HEAD n NONE PUBLIC
PUBAPI /whois/(*) WhoisHttpAction GET n NONE PUBLIC
CONSOLE /console-api/domain ConsoleDomainGetAction GET n USER PUBLIC
CONSOLE /console-api/domain-list ConsoleDomainListAction GET n USER PUBLIC
CONSOLE /console-api/dum-download ConsoleDumDownloadAction GET n USER PUBLIC
CONSOLE /console-api/eppPassword ConsoleEppPasswordAction POST n USER PUBLIC
CONSOLE /console-api/ote ConsoleOteAction GET,POST n USER PUBLIC
CONSOLE /console-api/registrar ConsoleUpdateRegistrarAction POST n USER PUBLIC
CONSOLE /console-api/registrars RegistrarsAction GET,POST n USER PUBLIC
CONSOLE /console-api/registry-lock ConsoleRegistryLockAction GET,POST n USER PUBLIC
CONSOLE /console-api/registry-lock-verify ConsoleRegistryLockVerifyAction GET n USER PUBLIC
CONSOLE /console-api/settings/contacts ContactAction GET,POST n USER PUBLIC
CONSOLE /console-api/settings/security SecurityAction POST n USER PUBLIC
CONSOLE /console-api/settings/whois-fields WhoisRegistrarFieldsAction POST n USER PUBLIC
CONSOLE /console-api/userdata ConsoleUserDataAction GET n USER PUBLIC
@@ -1,14 +1,14 @@
PATH CLASS METHODS OK MIN USER_POLICY
/_dr/admin/createGroups CreateGroupsAction POST n APP ADMIN
/_dr/admin/list/domains ListDomainsAction GET,POST n APP ADMIN
/_dr/admin/list/hosts ListHostsAction GET,POST n APP ADMIN
/_dr/admin/list/premiumLists ListPremiumListsAction GET,POST n APP ADMIN
/_dr/admin/list/registrars ListRegistrarsAction GET,POST n APP ADMIN
/_dr/admin/list/reservedLists ListReservedListsAction GET,POST n APP ADMIN
/_dr/admin/list/tlds ListTldsAction GET,POST n APP ADMIN
/_dr/admin/updateUserGroup UpdateUserGroupAction POST n APP ADMIN
/_dr/admin/verifyOte VerifyOteAction POST n APP ADMIN
/_dr/epptool EppToolAction POST n APP ADMIN
/_dr/loadtest LoadTestAction POST y APP ADMIN
/_dr/task/generateZoneFiles GenerateZoneFilesAction POST n APP ADMIN
/_dr/task/refreshDnsForAllDomains RefreshDnsForAllDomainsAction GET n APP ADMIN
SERVICE PATH CLASS METHODS OK MIN USER_POLICY
BACKEND /_dr/admin/createGroups CreateGroupsAction POST n APP ADMIN
BACKEND /_dr/admin/list/domains ListDomainsAction GET,POST n APP ADMIN
BACKEND /_dr/admin/list/hosts ListHostsAction GET,POST n APP ADMIN
BACKEND /_dr/admin/list/premiumLists ListPremiumListsAction GET,POST n APP ADMIN
BACKEND /_dr/admin/list/registrars ListRegistrarsAction GET,POST n APP ADMIN
BACKEND /_dr/admin/list/reservedLists ListReservedListsAction GET,POST n APP ADMIN
BACKEND /_dr/admin/list/tlds ListTldsAction GET,POST n APP ADMIN
BACKEND /_dr/admin/updateUserGroup UpdateUserGroupAction POST n APP ADMIN
BACKEND /_dr/admin/verifyOte VerifyOteAction POST n APP ADMIN
BACKEND /_dr/epptool EppToolAction POST n APP ADMIN
BACKEND /_dr/loadtest LoadTestAction POST y APP ADMIN
BACKEND /_dr/task/generateZoneFiles GenerateZoneFilesAction POST n APP ADMIN
BACKEND /_dr/task/refreshDnsForAllDomains RefreshDnsForAllDomainsAction GET n APP ADMIN
@@ -41,29 +41,23 @@ FROM (
FROM (
-- Extract JSON metadata package from monthly logs
SELECT
REGEXP_EXTRACT(logMessages, r'FLOW-LOG-SIGNATURE-METADATA: (.*)\n?$')
REGEXP_EXTRACT(textPayload, r'FLOW-LOG-SIGNATURE-METADATA: (.*)\n?$')
AS json
FROM (
SELECT
protoPayload.resource AS requestPath,
ARRAY(
SELECT logMessage
FROM UNNEST(protoPayload.line)) AS logMessage
textPayload
FROM
`domain-registry-alpha.appengine_logs.appengine_googleapis_com_request_log_*`
`domain-registry-alpha.appengine_logs._var_log_app_*`
WHERE _TABLE_SUFFIX
BETWEEN '20170901'
AND '20170930')
JOIN UNNEST(logMessage) AS logMessages
-- Look for metadata logs from epp and registrar console requests
WHERE requestPath IN ('/_dr/epp', '/_dr/epptool', '/registrar-xhr')
AND STARTS_WITH(logMessages, "google.registry.flows.FlowReporter recordToLogs: FLOW-LOG-SIGNATURE-METADATA")
WHERE STARTS_WITH(textPayload, "FLOW-LOG-SIGNATURE-METADATA")
-- Look for domain creates
AND REGEXP_CONTAINS(
logMessages, r'"commandType":"create","resourceType":"domain"')
textPayload, r'"commandType":"create","resourceType":"domain"')
-- Filter prober data
AND NOT REGEXP_CONTAINS(
logMessages, r'"prober-[a-z]{2}-((any)|(canary))"') )
textPayload, r'"prober-[a-z]{2}-((any)|(canary))"') )
GROUP BY tld, clientId ) AS logs_table
JOIN
EXTERNAL_QUERY("projects/domain-registry-alpha/locations/us/connections/domain-registry-alpha-sql",
@@ -37,13 +37,12 @@ FROM (
FROM (
SELECT
-- Extract the logged JSON payload.
REGEXP_EXTRACT(logMessage, r'FLOW-LOG-SIGNATURE-METADATA: (.*)\n?$')
REGEXP_EXTRACT(textPayload, r'FLOW-LOG-SIGNATURE-METADATA: (.*)\n?$')
AS json
FROM `domain-registry-alpha.cloud_sql_icann_reporting.monthly_logs_201709` AS logs
JOIN
UNNEST(logs.logMessage) AS logMessage
FROM `domain-registry-alpha.appengine_logs._var_log_app_*`
WHERE
STARTS_WITH(logMessage, "google.registry.flows.FlowReporter recordToLogs: FLOW-LOG-SIGNATURE-METADATA"))) AS regexes
STARTS_WITH(textPayload, "FLOW-LOG-SIGNATURE-METADATA")
AND _TABLE_SUFFIX BETWEEN '20170901' AND '20170930')) AS regexes
JOIN
-- Unnest the JSON-parsed tlds.
UNNEST(regexes.tlds) AS tld

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