1
0
mirror of https://github.com/google/nomulus synced 2026-05-21 23:31:51 +00:00

Compare commits

...

4 Commits

Author SHA1 Message Date
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
95 changed files with 1724 additions and 495 deletions

View File

@@ -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.

View File

@@ -69,7 +69,9 @@ task deploy(type: Exec) {
}
tasks.buildConsoleWebappProd.dependsOn(tasks.npmInstallDeps)
tasks.runConsoleWebappUnitTests.dependsOn(tasks.npmInstallDeps)
tasks.applyFormatting.dependsOn(tasks.npmInstallDeps)
tasks.checkFormatting.dependsOn(tasks.npmInstallDeps)
tasks.build.dependsOn(tasks.checkFormatting)
tasks.build.dependsOn(tasks.runConsoleWebappUnitTests)
tasks.deploy.dependsOn(tasks.buildConsoleWebappProd)

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: [

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,7 +29,6 @@ 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;
@@ -38,6 +40,14 @@ export const routes: RouteWithIcon[] = [
path: RegistryLockVerifyComponent.PATH,
component: RegistryLockVerifyComponent,
},
{
path: NewOteComponent.PATH,
component: NewOteComponent,
},
{
path: OteStatusComponent.PATH,
component: OteStatusComponent,
},
{ path: 'registrars', component: RegistrarComponent },
{
path: 'home',

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: [
@@ -66,6 +68,8 @@ import { RegistryLockVerifyComponent } from './lock/registryLockVerify.component
HeaderComponent,
HomeComponent,
LocationBackDirective,
NewOteComponent,
OteStatusComponent,
UserLevelVisibility,
NavigationComponent,
NewRegistrarComponent,

View File

@@ -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(),

View File

@@ -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>

View File

@@ -0,0 +1,7 @@
.console-app__new-ote {
max-width: 720px;
mat-card-content {
white-space: break-spaces;
padding: 20px;
}
}

View File

@@ -0,0 +1,81 @@
// 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';
export interface OteCreateResponse extends Map<string, string> {
password: string;
}
@Component({
selector: 'app-ote',
templateUrl: './newOte.component.html',
styleUrls: ['./newOte.component.scss'],
})
export class NewOteComponent {
public static PATH = 'new-ote';
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);
},
});
}
}
}

View File

@@ -0,0 +1,31 @@
<app-selected-registrar-wrapper>
<h1 class="mat-headline-4">OT&E Status Check</h1>
@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 {{ registrarService.registrar()?.registrarId }} is not an OT&E
registrar
</h1>
}
</app-selected-registrar-wrapper>

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,62 @@
// 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 { MatSnackBar } from '@angular/material/snack-bar';
import { RegistrarService } from '../registrar/registrar.service';
export interface OteStatusResponse {
description: string;
requirement: number;
timesPerformed: number;
completed: boolean;
}
@Component({
selector: 'app-ote-status',
templateUrl: './oteStatus.component.html',
styleUrls: ['./oteStatus.component.scss'],
})
export class OteStatusComponent {
public static PATH = 'ote-status';
oteStatusResponse = signal<OteStatusResponse[]>([]);
oteStatusCompleted = computed(() =>
this.oteStatusResponse().filter((v) => v.completed)
);
oteStatusUnfinished = computed(() =>
this.oteStatusResponse().filter((v) => !v.completed)
);
isOte = computed(
() => this.registrarService.registrar()?.type?.toLowerCase() === 'ote'
);
constructor(
protected registrarService: RegistrarService,
private _snackBar: MatSnackBar
) {
this.registrarService
.oteStatus(this.registrarService.registrarId())
.subscribe({
next: (oteStatusResponse: OteStatusResponse[]) => {
this.oteStatusResponse.set(oteStatusResponse);
},
error: (err: HttpErrorResponse) => {
this._snackBar.open(err.error || err.message);
},
});
}
}

View File

@@ -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);
}
}

View File

@@ -8,6 +8,14 @@
</button>
<div class="spacer"></div>
@if(!inEdit && !registrarNotFound) {
<button
mat-stroked-button
(click)="checkOteStatus()"
aria-label="Check OT&E account"
[elementId]="getElementIdForOteBlock()"
>
Check OT&E Status
</button>
<button
mat-flat-button
color="primary"

View File

@@ -18,6 +18,8 @@ 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 { OteStatusComponent } from '../ote/oteStatus.component';
import { RESTRICTED_ELEMENTS } from '../shared/directives/userLevelVisiblity.directive';
import { Registrar, RegistrarService } from './registrar.service';
import { RegistrarComponent, columns } from './registrarsTable.component';
@@ -70,6 +72,14 @@ export class RegistrarDetailsComponent implements OnInit {
];
}
checkOteStatus() {
this.router.navigate([OteStatusComponent.PATH]);
}
getElementIdForOteBlock() {
return RESTRICTED_ELEMENTS.OTE;
}
removeTLD(tld: string) {
this.registrarInEdit.allowedTlds = this.registrarInEdit.allowedTlds?.filter(
(v) => v != tld
@@ -91,6 +101,6 @@ export class RegistrarDetailsComponent implements OnInit {
}
ngOnDestroy() {
this.subscription.unsubscribe();
this.subscription && this.subscription.unsubscribe();
}
}

View File

@@ -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(),

View File

@@ -4,7 +4,17 @@
<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
(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()"

View File

@@ -10,6 +10,10 @@
min-width: $min-width !important;
}
&__registrars-new {
margin-left: 20px;
}
&__registrars-header {
display: flex;
justify-content: space-between;

View File

@@ -17,6 +17,8 @@ import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { Router } from '@angular/router';
import { NewOteComponent } from '../ote/newOte.component';
import { RESTRICTED_ELEMENTS } from '../shared/directives/userLevelVisiblity.directive';
import { Registrar, RegistrarService } from './registrar.service';
export const columns = [
@@ -103,6 +105,14 @@ export class RegistrarComponent {
this.dataSource.sort = this.sort;
}
createOteAccount() {
this.router.navigate([NewOteComponent.PATH]);
}
getElementIdForOteBlock() {
return RESTRICTED_ELEMENTS.OTE;
}
openDetails(registrarId: string) {
this.router.navigate(['registrars/', registrarId], {
queryParamsHandling: 'merge',

View File

@@ -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' }],

View File

@@ -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;
}
});
}

View File

@@ -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 = {

View File

@@ -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 }"

View File

@@ -32,7 +32,14 @@ describe('WhoisComponent', () => {
imports: [MaterialModule, BrowserAnimationsModule],
providers: [
BackendService,
{ provide: RegistrarService, useValue: { registrar: {} } },
{
provide: RegistrarService,
useValue: {
registrar: function () {
return {};
},
},
},
provideHttpClient(),
provideHttpClientTesting(),
],

View File

@@ -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({

View File

@@ -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> {

View File

@@ -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;

View File

@@ -29,5 +29,6 @@
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
},
"exclude": ["./src/testdata/*.*/*.ts"]
}

View File

@@ -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.
*/

View File

@@ -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. */

View File

@@ -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.

View File

@@ -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 {

View File

@@ -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. -->

View File

@@ -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;

View File

@@ -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);
}
}
}

View File

@@ -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();

View File

@@ -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();

View File

@@ -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;
};
}
}
}

View File

@@ -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);

View File

@@ -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")));
}

View File

@@ -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

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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(

View File

@@ -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) {}
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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;

View File

@@ -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));
}
}
}

View File

@@ -204,6 +204,7 @@ public final class RequestHandlerTest {
handler =
RequestHandler.create(
Component.class,
"registry.test",
() ->
new Builder() {
@Override

View File

@@ -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();
}
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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(

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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)));
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -85,7 +85,7 @@ tasks.register('run', JavaExec) {
tasks.register('deployNomulus', Exec) {
dependsOn('pushNomulusImage', ':proxy:pushProxyImage')
configure verifyDeploymentConfig
commandLine './deploy-nomulus-for-env.sh', "${rootProject.environment}"
commandLine './deploy-nomulus-for-env.sh', "${rootProject.environment}", "${rootProject.baseDomain}"
}
project.build.dependsOn(tasks.named('buildNomulusImage'))

View File

@@ -17,12 +17,13 @@
# kills all running pods to force k8s to create new pods using the just-pushed
# manifest.
if [[ $# -ne 1 ]]; then
echo "Usage: $0 alpha|crash|qa"
if [[ $# -ne 2 ]]; then
echo "Usage: $0 alpha|crash|qa [base_domain]}"
exit 1
fi
environment=${1}
base_domain=${2}
project="domain-registry-"${environment}
current_context=$(kubectl config current-context)
while read line
@@ -31,16 +32,25 @@ do
echo "Updating cluster ${parts[0]} in location ${parts[1]}..."
gcloud container clusters get-credentials "${parts[0]}" \
--project "${project}" --location "${parts[1]}"
sed s/GCP_PROJECT/"${project}"/g "./kubernetes/nomulus-deployment.yaml" | \
sed s/ENVIRONMENT/"${environment}"/g | \
kubectl apply -f -
kubectl apply -f "./kubernetes/nomulus-service.yaml"
for service in frontend backend pubapi console
do
sed s/GCP_PROJECT/"${project}"/g "./kubernetes/nomulus-${service}.yaml" | \
sed s/ENVIRONMENT/"${environment}"/g | \
kubectl apply -f -
done
# Kills all running pods, new pods created will be pulling the new image.
kubectl delete pods --all
# The multi-cluster gateway is only deployed to one cluster (the one in the US).
if [[ "${parts[1]}" == us-* ]]
then
kubectl apply -f "./kubernetes/nomulus-gateway.yaml"
kubectl apply -f "./kubernetes/gateway/nomulus-gateway.yaml"
for service in frontend backend pubapi console
do
sed s/BASE_DOMAIN/"${base_domain}"/g "./kubernetes/gateway/nomulus-route-${service}.yaml" | \
kubectl apply -f -
sed s/SERVICE/"${service}"/g "./kubernetes/gateway/nomulus-iap-${environment}.yaml" | \
kubectl apply -f -
done
fi
done < <(gcloud container clusters list --project "${project}" | grep nomulus)
kubectl config use-context "$current_context"

View File

@@ -0,0 +1,17 @@
apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
name: nomulus
spec:
gatewayClassName: gke-l7-global-external-managed-mc
listeners:
- name: https
protocol: HTTPS
port: 443
tls:
mode: Terminate
options:
networking.gke.io/pre-shared-certs: nomulus
allowedRoutes:
kinds:
- kind: HTTPRoute

View File

@@ -0,0 +1,47 @@
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: backend
spec:
parentRefs:
- kind: Gateway
name: nomulus
hostnames:
- "backend.BASE_DOMAIN"
rules:
- matches:
- path:
type: PathPrefix
value: /_dr/task
- path:
type: PathPrefix
value: /_dr/cron
- path:
type: PathPrefix
value: /_dr/admin
- path:
type: PathPrefix
value: /_dr/epptool
- path:
type: PathPrefix
value: /loadtest
backendRefs:
- group: net.gke.io
kind: ServiceImport
name: backend
port: 80
---
apiVersion: networking.gke.io/v1
kind: HealthCheckPolicy
metadata:
name: backend
spec:
default:
config:
type: HTTP
httpHealthCheck:
requestPath: /healthz/
targetRef:
group: net.gke.io
kind: ServiceImport
name: backend

View File

@@ -1,38 +1,31 @@
kind: Gateway
apiVersion: gateway.networking.k8s.io/v1beta1
metadata:
name: nomulus
spec:
gatewayClassName: gke-l7-global-external-managed-mc
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
kinds:
- kind: HTTPRoute
---
kind: HTTPRoute
apiVersion: gateway.networking.k8s.io/v1beta1
metadata:
name: nomulus
labels:
app: nomulus
name: console
spec:
parentRefs:
- kind: Gateway
name: nomulus
hostnames:
- "console.BASE_DOMAIN"
rules:
- backendRefs:
- matches:
- path:
type: PathPrefix
value: /console-api
- path:
type: PathPrefix
value: /console
backendRefs:
- group: net.gke.io
kind: ServiceImport
name: nomulus
name: console
port: 80
---
apiVersion: networking.gke.io/v1
kind: HealthCheckPolicy
metadata:
name: nomulus
name: console
spec:
default:
config:
@@ -42,5 +35,4 @@ spec:
targetRef:
group: net.gke.io
kind: ServiceImport
name: nomulus
name: console

View File

@@ -0,0 +1,35 @@
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: frontend
spec:
parentRefs:
- kind: Gateway
name: nomulus
hostnames:
- "frontend.BASE_DOMAIN"
rules:
- matches:
- path:
type: PathPrefix
value: /_dr/epp
backendRefs:
- group: net.gke.io
kind: ServiceImport
name: frontend
port: 80
---
apiVersion: networking.gke.io/v1
kind: HealthCheckPolicy
metadata:
name: frontend
spec:
default:
config:
type: HTTP
httpHealthCheck:
requestPath: /healthz/
targetRef:
group: net.gke.io
kind: ServiceImport
name: frontend

View File

@@ -0,0 +1,44 @@
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: pubapi
spec:
parentRefs:
- kind: Gateway
name: nomulus
hostnames:
- "pubapi.BASE_DOMAIN"
rules:
- matches:
- path:
type: PathPrefix
value: /_dr/whois
- path:
type: PathPrefix
value: /check
- path:
type: PathPrefix
value: /whois
- path:
type: PathPrefix
value: /rdap
backendRefs:
- group: net.gke.io
kind: ServiceImport
name: pubapi
port: 80
---
apiVersion: networking.gke.io/v1
kind: HealthCheckPolicy
metadata:
name: pubapi
spec:
default:
config:
type: HTTP
httpHealthCheck:
requestPath: /healthz/
targetRef:
group: net.gke.io
kind: ServiceImport
name: pubapi

View File

@@ -0,0 +1,60 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
spec:
selector:
matchLabels:
service: backend
template:
metadata:
labels:
service: backend
spec:
serviceAccountName: nomulus
containers:
- name: backend
image: gcr.io/GCP_PROJECT/nomulus
ports:
- containerPort: 8080
name: http
resources:
requests:
cpu: "500m"
args: [ENVIRONMENT]
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: backend
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: backend
minReplicas: 1
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 100
---
apiVersion: v1
kind: Service
metadata:
name: backend
spec:
selector:
service: backend
ports:
- port: 80
targetPort: http
name: http
---
apiVersion: net.gke.io/v1
kind: ServiceExport
metadata:
name: backend

View File

@@ -0,0 +1,60 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: console
spec:
selector:
matchLabels:
service: console
template:
metadata:
labels:
service: console
spec:
serviceAccountName: nomulus
containers:
- name: console
image: gcr.io/GCP_PROJECT/nomulus
ports:
- containerPort: 8080
name: http
resources:
requests:
cpu: "500m"
args: [ENVIRONMENT]
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: console
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: console
minReplicas: 1
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 100
---
apiVersion: v1
kind: Service
metadata:
name: console
spec:
selector:
service: console
ports:
- port: 80
targetPort: http
name: http
---
apiVersion: net.gke.io/v1
kind: ServiceExport
metadata:
name: console

View File

@@ -1,21 +1,19 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: nomulus
labels:
app: nomulus
name: frontend
spec:
selector:
matchLabels:
app: nomulus
service: frontend
template:
metadata:
labels:
app: nomulus
service: frontend
spec:
serviceAccountName: nomulus
containers:
- name: nomulus
- name: frontend
image: gcr.io/GCP_PROJECT/nomulus
ports:
- containerPort: 8080
@@ -50,14 +48,12 @@ spec:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: nomulus
labels:
app: nomulus
name: frontend
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: nomulus
name: frontend
minReplicas: 1
maxReplicas: 20
metrics:
@@ -67,4 +63,26 @@ spec:
target:
type: Utilization
averageUtilization: 100
---
apiVersion: v1
kind: Service
metadata:
name: frontend
spec:
selector:
service: frontend
ports:
- port: 80
targetPort: http
name: http
- port: 43
targetPort: whois
name: whois
- port: 700
targetPort: epp
name: epp
---
apiVersion: net.gke.io/v1
kind: ServiceExport
metadata:
name: frontend

View File

@@ -0,0 +1,60 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: pubapi
spec:
selector:
matchLabels:
service: pubapi
template:
metadata:
labels:
service: pubapi
spec:
serviceAccountName: nomulus
containers:
- name: pubapi
image: gcr.io/GCP_PROJECT/nomulus
ports:
- containerPort: 8080
name: http
resources:
requests:
cpu: "500m"
args: [ENVIRONMENT]
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: pubapi
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: pubapi
minReplicas: 1
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 100
---
apiVersion: v1
kind: Service
metadata:
name: pubapi
spec:
selector:
service: pubapi
ports:
- port: 80
targetPort: http
name: http
---
apiVersion: net.gke.io/v1
kind: ServiceExport
metadata:
name: pubapi

View File

@@ -1,22 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: nomulus
spec:
selector:
app: nomulus
ports:
- port: 80
targetPort: http
name: http
- port: 43
targetPort: whois
name: whois
- port: 700
targetPort: epp
name: epp
---
kind: ServiceExport
apiVersion: net.gke.io/v1
metadata:
name: nomulus

View File

@@ -25,3 +25,9 @@ rootProject.ext.projects = ['production': 'your-production-project',
// The project to host your development/deployment infrastructure. It hosts
// things like release artifacts, CI/CD system, etc.
rootProject.ext.devProject = 'your-dev-project'
rootProject.ext.baseDomains = ['production' : 'registry-production.test',
'sandbox' : 'registry-sandbox.test',
'alpha' : 'registry-alpha.test',
'crash' : 'registry-crash.test',
'qa' : 'registry-qa.test']