mirror of
https://github.com/google/nomulus
synced 2026-05-22 07:41:50 +00:00
Compare commits
27 Commits
nomulus-20
...
nomulus-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08570511f5 | ||
|
|
e62d970d34 | ||
|
|
067927b735 | ||
|
|
4ec2919ce3 | ||
|
|
19422075fa | ||
|
|
40b6984ffb | ||
|
|
6952e0f653 | ||
|
|
dcb55d27bb | ||
|
|
765bd9834a | ||
|
|
221088e738 | ||
|
|
6649e00df7 | ||
|
|
2ceb52a7c4 | ||
|
|
120bcc33be | ||
|
|
8987fd37c2 | ||
|
|
653e092ad4 | ||
|
|
5e97a8b412 | ||
|
|
229fcf3946 | ||
|
|
b775e4a178 | ||
|
|
e3c386a8a7 | ||
|
|
799f0449ad | ||
|
|
bf025445d5 | ||
|
|
9f22f2e8ae | ||
|
|
45c8b81823 | ||
|
|
4cfcc60655 | ||
|
|
e4ee63b8f3 | ||
|
|
f8407c74bc | ||
|
|
693467a165 |
10765
console-webapp/package-lock.json
generated
10765
console-webapp/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -16,31 +16,31 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^18.0.2",
|
||||
"@angular/cdk": "^18.0.2",
|
||||
"@angular/common": "^18.0.2",
|
||||
"@angular/compiler": "^18.0.2",
|
||||
"@angular/core": "^18.0.2",
|
||||
"@angular/forms": "^18.0.2",
|
||||
"@angular/material": "^18.0.2",
|
||||
"@angular/platform-browser": "^18.0.2",
|
||||
"@angular/platform-browser-dynamic": "^18.0.2",
|
||||
"@angular/router": "^18.0.2",
|
||||
"@angular/animations": "^19.1.4",
|
||||
"@angular/cdk": "^19.1.2",
|
||||
"@angular/common": "^19.1.4",
|
||||
"@angular/compiler": "^19.1.4",
|
||||
"@angular/core": "^19.1.4",
|
||||
"@angular/forms": "^19.1.4",
|
||||
"@angular/material": "^19.1.2",
|
||||
"@angular/platform-browser": "^19.1.4",
|
||||
"@angular/platform-browser-dynamic": "^19.1.4",
|
||||
"@angular/router": "^19.1.4",
|
||||
"rxjs": "~7.5.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.2"
|
||||
"zone.js": "~0.15.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^18.0.3",
|
||||
"@angular-eslint/builder": "18.0.1",
|
||||
"@angular-eslint/eslint-plugin": "18.0.1",
|
||||
"@angular-eslint/eslint-plugin-template": "18.0.1",
|
||||
"@angular-eslint/schematics": "18.0.1",
|
||||
"@angular-eslint/template-parser": "18.0.1",
|
||||
"@angular/cli": "~18.0.3",
|
||||
"@angular/compiler-cli": "^18.0.2",
|
||||
"@angular-devkit/build-angular": "^19.1.5",
|
||||
"@angular-eslint/builder": "19.0.2",
|
||||
"@angular-eslint/eslint-plugin": "19.0.2",
|
||||
"@angular-eslint/eslint-plugin-template": "19.0.2",
|
||||
"@angular-eslint/schematics": "19.0.2",
|
||||
"@angular-eslint/template-parser": "19.0.2",
|
||||
"@angular/cli": "~19.1.5",
|
||||
"@angular/compiler-cli": "^19.1.4",
|
||||
"@types/jasmine": "~4.0.0",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/node": "^18.19.74",
|
||||
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
||||
"@typescript-eslint/parser": "^7.2.0",
|
||||
"concurrently": "^7.6.0",
|
||||
@@ -52,6 +52,6 @@
|
||||
"karma-jasmine": "~5.1.0",
|
||||
"karma-jasmine-html-reporter": "~2.0.0",
|
||||
"prettier": "2.8.7",
|
||||
"typescript": "~5.4.5"
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,11 @@
|
||||
></mat-progress-bar>
|
||||
</div>
|
||||
<mat-sidenav-container class="console-app__container">
|
||||
<mat-sidenav-content class="console-app__content-wrapper">
|
||||
<div class="console-app__content" role="main">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</mat-sidenav-content>
|
||||
<mat-sidenav
|
||||
[mode]="breakpointObserver.isMobileView() ? 'over' : 'side'"
|
||||
[opened]="!breakpointObserver.isMobileView()"
|
||||
@@ -15,10 +20,5 @@
|
||||
>
|
||||
<app-navigation />
|
||||
</mat-sidenav>
|
||||
<mat-sidenav-content class="console-app__content-wrapper">
|
||||
<div class="console-app__content">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</mat-sidenav-content>
|
||||
</mat-sidenav-container>
|
||||
</div>
|
||||
|
||||
@@ -20,12 +20,18 @@ import { AppComponent } from './app.component';
|
||||
import { MaterialModule } from './material.module';
|
||||
import { BackendService } from './shared/services/backend.service';
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [AppComponent],
|
||||
imports: [MaterialModule, BrowserAnimationsModule, AppRoutingModule],
|
||||
imports: [
|
||||
MaterialModule,
|
||||
BrowserAnimationsModule,
|
||||
AppRoutingModule,
|
||||
AppModule,
|
||||
],
|
||||
providers: [
|
||||
BackendService,
|
||||
provideHttpClient(),
|
||||
@@ -36,6 +42,7 @@ describe('AppComponent', () => {
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
@@ -24,6 +24,7 @@ import { UserDataService } from './shared/services/userData.service';
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export class AppComponent implements AfterViewInit {
|
||||
@ViewChild(MatSidenav)
|
||||
|
||||
@@ -26,7 +26,11 @@ import { BackendService } from './shared/services/backend.service';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';
|
||||
import { BillingInfoComponent } from './billingInfo/billingInfo.component';
|
||||
import { DomainListComponent } from './domains/domainList.component';
|
||||
import {
|
||||
DomainListComponent,
|
||||
ReasonDialogComponent,
|
||||
ResponseDialogComponent,
|
||||
} from './domains/domainList.component';
|
||||
import { RegistryLockComponent } from './domains/registryLock.component';
|
||||
import { HeaderComponent } from './header/header.component';
|
||||
import { HomeComponent } from './home/home.component';
|
||||
@@ -55,6 +59,7 @@ 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 { ForceFocusDirective } from './shared/directives/forceFocus.directive';
|
||||
|
||||
@NgModule({
|
||||
declarations: [SelectedRegistrarWrapper],
|
||||
@@ -74,6 +79,7 @@ export class SelectedRegistrarModule {}
|
||||
HeaderComponent,
|
||||
HomeComponent,
|
||||
LocationBackDirective,
|
||||
ForceFocusDirective,
|
||||
UserLevelVisibility,
|
||||
NavigationComponent,
|
||||
NewRegistrarComponent,
|
||||
@@ -92,6 +98,8 @@ export class SelectedRegistrarModule {}
|
||||
TldsComponent,
|
||||
WhoisComponent,
|
||||
WhoisEditComponent,
|
||||
ReasonDialogComponent,
|
||||
ResponseDialogComponent,
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
imports: [
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
<app-selected-registrar-wrapper>
|
||||
<h1 class="mat-headline-4">Billing Info</h1>
|
||||
<h1 class="mat-headline-4" forceFocus>Billing Info</h1>
|
||||
<div class="console-app__billing">
|
||||
<div>
|
||||
<div class="console-app__billing-subhead">
|
||||
Billing records and information
|
||||
</div>
|
||||
<a class="text-l" href="{{ driveFolderUrl() }}" target="_blank"
|
||||
<a
|
||||
class="text-l"
|
||||
href="{{ driveFolderUrl() }}"
|
||||
target="_blank"
|
||||
aria-label="View billing records on Google Drive"
|
||||
>View on Google Drive</a
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<img src="./assets/billing.png" />
|
||||
<img src="./assets/billing.png" alt="Generic billing image" />
|
||||
</div>
|
||||
</div>
|
||||
</app-selected-registrar-wrapper>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
width: 100%;
|
||||
}
|
||||
&-subhead {
|
||||
font-size: 20px;
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
selector: 'app-billingInfo',
|
||||
templateUrl: './billingInfo.component.html',
|
||||
styleUrls: ['./billingInfo.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export class BillingInfoComponent {
|
||||
public static PATH = 'billingInfo';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<app-selected-registrar-wrapper>
|
||||
<div class="console-app-domains">
|
||||
<h1 class="mat-headline-4">Domains</h1>
|
||||
<h1 class="mat-headline-4" forceFocus>Domains</h1>
|
||||
|
||||
<div
|
||||
class="console-app-domains__actions-wrapper"
|
||||
@@ -25,7 +25,11 @@
|
||||
} @else {
|
||||
<mat-menu #actions="matMenu">
|
||||
<ng-template matMenuContent let-domainName="domainName">
|
||||
<button mat-menu-item (click)="openRegistryLock(domainName)">
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="openRegistryLock(domainName)"
|
||||
aria-label="Access registry lock for domain"
|
||||
>
|
||||
<mat-icon>key</mat-icon>
|
||||
<span>Registry Lock</span>
|
||||
</button>
|
||||
@@ -65,16 +69,67 @@
|
||||
/>
|
||||
</mat-form-field>
|
||||
|
||||
<div
|
||||
class="console-app__domains-selection"
|
||||
[elementId]="getElementIdForBulkDelete()"
|
||||
[ngClass]="{ active: selection.hasValue() }"
|
||||
>
|
||||
<div class="console-app__domains-selection-text">
|
||||
{{ selection.selected.length }} Selected
|
||||
</div>
|
||||
<div class="console-app__domains-selection-actions">
|
||||
<button
|
||||
mat-flat-button
|
||||
aria-label="Delete Selected Domains"
|
||||
[attr.aria-hidden]="!selection.hasValue()"
|
||||
(click)="deleteSelectedDomains()"
|
||||
>
|
||||
Delete Selected Domains
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-table
|
||||
[dataSource]="dataSource"
|
||||
class="mat-elevation-z0"
|
||||
class="console-app__domains-table"
|
||||
>
|
||||
<!-- Checkbox Column -->
|
||||
<ng-container matColumnDef="select">
|
||||
<mat-header-cell *matHeaderCellDef>
|
||||
<mat-checkbox
|
||||
(change)="$event ? toggleAllRows() : null"
|
||||
[checked]="selection.hasValue() && isAllSelected"
|
||||
[indeterminate]="selection.hasValue() && !isAllSelected"
|
||||
[aria-label]="checkboxLabel()"
|
||||
[elementId]="getElementIdForBulkDelete()"
|
||||
>
|
||||
</mat-checkbox>
|
||||
</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row">
|
||||
<mat-checkbox
|
||||
(click)="$event.stopPropagation()"
|
||||
(change)="$event ? selection.toggle(row) : null"
|
||||
[checked]="selection.isSelected(row)"
|
||||
[aria-label]="checkboxLabel(row)"
|
||||
[elementId]="getElementIdForBulkDelete()"
|
||||
>
|
||||
</mat-checkbox>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="domainName">
|
||||
<mat-header-cell *matHeaderCellDef>Domain Name</mat-header-cell>
|
||||
<mat-cell *matCellDef="let element">{{
|
||||
element.domainName
|
||||
}}</mat-cell>
|
||||
<mat-cell *matCellDef="let element">
|
||||
<mat-icon
|
||||
*ngIf="getOperationMessage(element.domainName)"
|
||||
[matTooltip]="getOperationMessage(element.domainName)"
|
||||
matTooltipPosition="above"
|
||||
class="primary-text"
|
||||
>info</mat-icon
|
||||
>
|
||||
<span>{{ element.domainName }}</span>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="creationTime">
|
||||
|
||||
@@ -12,6 +12,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__domains-selection {
|
||||
height: 60px;
|
||||
max-height: 0;
|
||||
transition: max-height 0.2s linear;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
gap: 20px;
|
||||
&-text {
|
||||
font-weight: bold;
|
||||
}
|
||||
&.active {
|
||||
max-height: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
&-domains__download {
|
||||
position: absolute;
|
||||
top: -55px;
|
||||
@@ -41,6 +57,22 @@
|
||||
overflow: hidden;
|
||||
word-break: break-word;
|
||||
}
|
||||
.mat-column-select {
|
||||
max-width: 60px;
|
||||
padding-left: 15px;
|
||||
}
|
||||
.mat-column-domainName {
|
||||
position: relative;
|
||||
padding-left: 25px;
|
||||
mat-icon {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
mat-cell:has([style*="display: none"]),
|
||||
mat-header-cell:has([style*="display: none"]) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__domains-spinner {
|
||||
|
||||
@@ -21,6 +21,7 @@ import { MaterialModule } from '../material.module';
|
||||
import { BackendService } from '../shared/services/backend.service';
|
||||
import { DomainListComponent } from './domainList.component';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { AppModule } from '../app.module';
|
||||
|
||||
describe('DomainListComponent', () => {
|
||||
let component: DomainListComponent;
|
||||
@@ -29,7 +30,12 @@ describe('DomainListComponent', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [DomainListComponent],
|
||||
imports: [MaterialModule, BrowserAnimationsModule, FormsModule],
|
||||
imports: [
|
||||
MaterialModule,
|
||||
BrowserAnimationsModule,
|
||||
FormsModule,
|
||||
AppModule,
|
||||
],
|
||||
providers: [
|
||||
BackendService,
|
||||
provideHttpClient(),
|
||||
|
||||
@@ -12,27 +12,107 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { SelectionModel } from '@angular/cdk/collections';
|
||||
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
|
||||
import { Component, ViewChild, effect } from '@angular/core';
|
||||
import { Component, ViewChild, effect, Inject } from '@angular/core';
|
||||
import { MatPaginator, PageEvent } from '@angular/material/paginator';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { Subject, debounceTime } from 'rxjs';
|
||||
import { Subject, debounceTime, take, filter } from 'rxjs';
|
||||
import { RegistrarService } from '../registrar/registrar.service';
|
||||
import { Domain, DomainListService } from './domainList.service';
|
||||
import { RegistryLockComponent } from './registryLock.component';
|
||||
import { RegistryLockService } from './registryLock.service';
|
||||
import {
|
||||
MAT_DIALOG_DATA,
|
||||
MatDialog,
|
||||
MatDialogRef,
|
||||
} from '@angular/material/dialog';
|
||||
import { RESTRICTED_ELEMENTS } from '../shared/directives/userLevelVisiblity.directive';
|
||||
|
||||
interface DomainResponse {
|
||||
message: string;
|
||||
responseCode: string;
|
||||
}
|
||||
|
||||
interface DomainData {
|
||||
[domain: string]: DomainResponse;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-response-dialog',
|
||||
template: `
|
||||
<h2 mat-dialog-title>{{ data.title }}</h2>
|
||||
<mat-dialog-content [innerHTML]="data.content" />
|
||||
<mat-dialog-actions>
|
||||
<button mat-button (click)="onClose()">Close</button>
|
||||
</mat-dialog-actions>
|
||||
`,
|
||||
standalone: false,
|
||||
})
|
||||
export class ResponseDialogComponent {
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<ReasonDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA)
|
||||
public data: { title: string; content: string }
|
||||
) {}
|
||||
|
||||
onClose(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-reason-dialog',
|
||||
template: `
|
||||
<h2 mat-dialog-title>
|
||||
Please provide a reason for {{ data.operation }} the domain(s):
|
||||
</h2>
|
||||
<mat-dialog-content>
|
||||
<mat-form-field appearance="outline" style="width:100%">
|
||||
<textarea matInput [(ngModel)]="reason" rows="4"></textarea>
|
||||
</mat-form-field>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
<button mat-button (click)="onCancel()">Cancel</button>
|
||||
<button mat-button color="warn" (click)="onDelete()" [disabled]="!reason">
|
||||
Delete
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
`,
|
||||
standalone: false,
|
||||
})
|
||||
export class ReasonDialogComponent {
|
||||
reason: string = '';
|
||||
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<ReasonDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA)
|
||||
public data: { operation: 'deleting' | 'suspending' }
|
||||
) {}
|
||||
|
||||
onDelete(): void {
|
||||
this.dialogRef.close(this.reason);
|
||||
}
|
||||
|
||||
onCancel(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-domain-list',
|
||||
templateUrl: './domainList.component.html',
|
||||
styleUrls: ['./domainList.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export class DomainListComponent {
|
||||
public static PATH = 'domain-list';
|
||||
private readonly DEBOUNCE_MS = 500;
|
||||
isAllSelected = false;
|
||||
|
||||
displayedColumns: string[] = [
|
||||
'select',
|
||||
'domainName',
|
||||
'creationTime',
|
||||
'registrationExpirationTime',
|
||||
@@ -42,6 +122,7 @@ export class DomainListComponent {
|
||||
];
|
||||
|
||||
dataSource: MatTableDataSource<Domain> = new MatTableDataSource();
|
||||
selection = new SelectionModel<Domain>(true, [], undefined, this.isChecked());
|
||||
isLoading = true;
|
||||
|
||||
searchTermSubject = new Subject<string>();
|
||||
@@ -51,13 +132,18 @@ export class DomainListComponent {
|
||||
resultsPerPage = 50;
|
||||
totalResults?: number = 0;
|
||||
|
||||
reason: string = '';
|
||||
|
||||
operationResult: DomainData | undefined;
|
||||
|
||||
@ViewChild(MatPaginator, { static: true }) paginator!: MatPaginator;
|
||||
|
||||
constructor(
|
||||
protected domainListService: DomainListService,
|
||||
protected registrarService: RegistrarService,
|
||||
protected registryLockService: RegistryLockService,
|
||||
private _snackBar: MatSnackBar
|
||||
private _snackBar: MatSnackBar,
|
||||
private dialog: MatDialog
|
||||
) {
|
||||
effect(() => {
|
||||
this.pageNumber = 0;
|
||||
@@ -134,6 +220,98 @@ export class DomainListComponent {
|
||||
onPageChange(event: PageEvent) {
|
||||
this.pageNumber = event.pageIndex;
|
||||
this.resultsPerPage = event.pageSize;
|
||||
this.selection.clear();
|
||||
this.reloadData();
|
||||
}
|
||||
|
||||
toggleAllRows() {
|
||||
if (this.isAllSelected) {
|
||||
this.selection.clear();
|
||||
this.isAllSelected = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.selection.select(...this.dataSource.data);
|
||||
this.isAllSelected = true;
|
||||
}
|
||||
|
||||
checkboxLabel(row?: Domain): string {
|
||||
if (!row) {
|
||||
return `${this.isAllSelected ? 'deselect' : 'select'} all`;
|
||||
}
|
||||
return `${this.selection.isSelected(row) ? 'deselect' : 'select'} row ${
|
||||
row.domainName
|
||||
}`;
|
||||
}
|
||||
|
||||
private isChecked(): ((o1: Domain, o2: Domain) => boolean) | undefined {
|
||||
return (o1: Domain, o2: Domain) => {
|
||||
if (!o1.domainName || !o2.domainName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.isAllSelected || o1.domainName === o2.domainName;
|
||||
};
|
||||
}
|
||||
|
||||
getElementIdForBulkDelete() {
|
||||
return RESTRICTED_ELEMENTS.BULK_DELETE;
|
||||
}
|
||||
|
||||
getOperationMessage(domain: string) {
|
||||
if (this.operationResult && this.operationResult[domain])
|
||||
return this.operationResult[domain].message;
|
||||
return '';
|
||||
}
|
||||
|
||||
sendDeleteRequest(reason: string) {
|
||||
this.isLoading = true;
|
||||
this.domainListService
|
||||
.deleteDomains(
|
||||
this.selection.selected,
|
||||
reason,
|
||||
this.registrarService.registrarId()
|
||||
)
|
||||
.pipe(take(1))
|
||||
.subscribe({
|
||||
next: (result: DomainData) => {
|
||||
this.isLoading = false;
|
||||
const successCount = Object.keys(result).filter((domainName) =>
|
||||
result[domainName].responseCode.toString().startsWith('1')
|
||||
).length;
|
||||
const failureCount = Object.keys(result).length - successCount;
|
||||
this.dialog.open(ResponseDialogComponent, {
|
||||
data: {
|
||||
title: 'Domain Deletion Results',
|
||||
content: `Successfully deleted - ${successCount} domain(s)<br/>Failed to delete - ${failureCount} domain(s)<br/>${
|
||||
failureCount
|
||||
? 'Some domains could not be deleted due to ongoing processes or server errors. '
|
||||
: ''
|
||||
}Please check the table for more information.`,
|
||||
},
|
||||
});
|
||||
this.selection.clear();
|
||||
this.operationResult = result;
|
||||
this.reloadData();
|
||||
},
|
||||
error: (err: HttpErrorResponse) =>
|
||||
this._snackBar.open(err.error || err.message),
|
||||
});
|
||||
}
|
||||
|
||||
deleteSelectedDomains() {
|
||||
const dialogRef = this.dialog.open(ReasonDialogComponent, {
|
||||
data: {
|
||||
operation: 'deleting',
|
||||
},
|
||||
});
|
||||
|
||||
dialogRef
|
||||
.afterClosed()
|
||||
.pipe(
|
||||
take(1),
|
||||
filter((reason) => !!reason)
|
||||
)
|
||||
.subscribe(this.sendDeleteRequest.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,6 @@ export class DomainListService {
|
||||
private backendService: BackendService,
|
||||
private registrarService: RegistrarService
|
||||
) {}
|
||||
|
||||
retrieveDomains(
|
||||
pageNumber?: number,
|
||||
resultsPerPage?: number,
|
||||
@@ -71,4 +70,13 @@ export class DomainListService {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
deleteDomains(domains: Domain[], reason: string, registrarId: string) {
|
||||
return this.backendService.bulkDomainAction(
|
||||
domains.map((d) => d.domainName),
|
||||
reason,
|
||||
'DELETE',
|
||||
registrarId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
color="primary"
|
||||
type="submit"
|
||||
[disabled]="!unlockDomain.valid"
|
||||
aria-label="Submit domain unlock request"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
@@ -73,6 +74,7 @@
|
||||
color="primary"
|
||||
type="submit"
|
||||
[disabled]="!lockDomain.valid"
|
||||
aria-label="Submit domain lock request"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
|
||||
@@ -25,6 +25,7 @@ import { RegistryLockService } from './registryLock.service';
|
||||
selector: 'app-registry-lock',
|
||||
templateUrl: './registryLock.component.html',
|
||||
styleUrls: ['./registryLock.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export class RegistryLockComponent {
|
||||
readonly isLocked = computed(() =>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<mat-toolbar>
|
||||
<button
|
||||
mat-icon-button
|
||||
aria-label="Open menu"
|
||||
aria-label="Open navigation menu"
|
||||
(click)="toggleNavPane()"
|
||||
*ngIf="breakpointObserver.isMobileView()"
|
||||
class="console-app__menu-btn"
|
||||
@@ -12,6 +12,7 @@
|
||||
<a
|
||||
[routerLink]="'/home'"
|
||||
routerLinkActive="active"
|
||||
aria-label="Google Registry logo"
|
||||
class="console-app__logo"
|
||||
>
|
||||
<svg
|
||||
|
||||
@@ -17,6 +17,12 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { HeaderComponent } from './header.component';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { MaterialModule } from '../material.module';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { AppModule, SelectedRegistrarModule } from '../app.module';
|
||||
import { AppRoutingModule } from '../app-routing.module';
|
||||
import { BackendService } from '../shared/services/backend.service';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing';
|
||||
|
||||
describe('HeaderComponent', () => {
|
||||
let component: HeaderComponent;
|
||||
@@ -24,7 +30,19 @@ describe('HeaderComponent', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [MaterialModule, BrowserAnimationsModule],
|
||||
imports: [
|
||||
SelectedRegistrarModule,
|
||||
MaterialModule,
|
||||
BrowserAnimationsModule,
|
||||
AppRoutingModule,
|
||||
AppModule,
|
||||
],
|
||||
providers: [
|
||||
BackendService,
|
||||
{ provide: ActivatedRoute, useValue: {} as ActivatedRoute },
|
||||
provideHttpClient(),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
declarations: [HeaderComponent],
|
||||
}).compileComponents();
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import { BreakPointObserverService } from '../shared/services/breakPoint.service
|
||||
selector: 'app-header',
|
||||
templateUrl: './header.component.html',
|
||||
styleUrls: ['./header.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export class HeaderComponent {
|
||||
private isNavOpen = false;
|
||||
|
||||
@@ -16,6 +16,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { HomeComponent } from './home.component';
|
||||
import { MaterialModule } from '../material.module';
|
||||
import { AppModule } from '../app.module';
|
||||
|
||||
describe('HomeComponent', () => {
|
||||
let component: HomeComponent;
|
||||
@@ -23,7 +24,7 @@ describe('HomeComponent', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [MaterialModule],
|
||||
imports: [MaterialModule, AppModule],
|
||||
declarations: [HomeComponent],
|
||||
}).compileComponents();
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import { BreakPointObserverService } from '../shared/services/breakPoint.service
|
||||
selector: 'app-home',
|
||||
templateUrl: './home.component.html',
|
||||
styleUrls: ['./home.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export class HomeComponent {
|
||||
constructor(
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
margin-top: 30px;
|
||||
}
|
||||
&-subhead {
|
||||
font-size: 20px;
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import { DomainListComponent } from '../domains/domainList.component';
|
||||
templateUrl: './registryLockVerify.component.html',
|
||||
styleUrls: ['./registryLockVerify.component.scss'],
|
||||
providers: [RegistryLockVerifyService],
|
||||
standalone: false,
|
||||
})
|
||||
export class RegistryLockVerifyComponent {
|
||||
public static PATH = 'registry-lock-verify';
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
<mat-tree-node
|
||||
*matTreeNodeDef="let node"
|
||||
matTreeNodeToggle
|
||||
tabindex="0"
|
||||
(click)="onClick(node)"
|
||||
(keyup.enter)="onClick(node)"
|
||||
[class.active]="router.url.includes(node.path)"
|
||||
[elementId]="getElementId(node)"
|
||||
>
|
||||
|
||||
@@ -33,6 +33,7 @@ interface NavMenuNode extends RouteWithIcon {
|
||||
selector: 'app-navigation',
|
||||
templateUrl: './navigation.component.html',
|
||||
styleUrls: ['./navigation.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export class NavigationComponent {
|
||||
renderRouter: boolean = true;
|
||||
|
||||
@@ -30,7 +30,14 @@
|
||||
>
|
||||
</mat-form-field>
|
||||
</p>
|
||||
<button mat-flat-button color="primary" type="submit">Save</button>
|
||||
<button
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
type="submit"
|
||||
aria-label="Submit new OT&E account"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -26,7 +26,6 @@ export interface OteCreateResponse extends Map<string, string> {
|
||||
|
||||
@Component({
|
||||
selector: 'app-ote',
|
||||
standalone: true,
|
||||
imports: [MaterialModule, SnackBarModule],
|
||||
templateUrl: './newOte.component.html',
|
||||
styleUrls: ['./newOte.component.scss'],
|
||||
|
||||
@@ -31,7 +31,6 @@ export interface OteStatusResponse {
|
||||
|
||||
@Component({
|
||||
selector: 'app-ote-status',
|
||||
standalone: true,
|
||||
imports: [MaterialModule, SnackBarModule, CommonModule],
|
||||
templateUrl: './oteStatus.component.html',
|
||||
styleUrls: ['./oteStatus.component.scss'],
|
||||
|
||||
@@ -175,6 +175,7 @@ JPY=billing-id-for-yen"
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
type="submit"
|
||||
aria-label="Submit new registrar request"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
|
||||
@@ -33,6 +33,7 @@ interface LocalizedAddressStreet {
|
||||
templateUrl: './newRegistrar.component.html',
|
||||
styleUrls: ['./newRegistrar.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
standalone: false,
|
||||
})
|
||||
export default class NewRegistrarComponent {
|
||||
protected newRegistrar: Registrar;
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
<div class="console-app__registrar-view">
|
||||
<div
|
||||
class="console-app__registrar-view"
|
||||
cdkTrapFocus
|
||||
[cdkTrapFocusAutoCapture]="true"
|
||||
>
|
||||
<h1 class="mat-headline-4">Registrars</h1>
|
||||
<mat-divider></mat-divider>
|
||||
<div class="console-app__registrar-view-content">
|
||||
@@ -12,7 +16,7 @@
|
||||
*ngIf="oteButtonVisible"
|
||||
mat-stroked-button
|
||||
(click)="checkOteStatus()"
|
||||
aria-label="Check OT&E account"
|
||||
aria-label="Check OT&E account status"
|
||||
[elementId]="getElementIdForOteBlock()"
|
||||
>
|
||||
Check OT&E Status
|
||||
|
||||
@@ -27,6 +27,7 @@ import { environment } from '../../environments/environment';
|
||||
selector: 'app-registrar-details',
|
||||
templateUrl: './registrarDetails.component.html',
|
||||
styleUrls: ['./registrarDetails.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export class RegistrarDetailsComponent implements OnInit {
|
||||
public static PATH = 'registrars/:id';
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
(focus)="onFocus()"
|
||||
[matAutocomplete]="auto"
|
||||
spellcheck="false"
|
||||
/>
|
||||
<mat-autocomplete
|
||||
autoActiveFirstOption
|
||||
|
||||
@@ -19,6 +19,7 @@ import { RegistrarService } from './registrar.service';
|
||||
selector: 'app-registrar-selector',
|
||||
templateUrl: './registrarSelector.component.html',
|
||||
styleUrls: ['./registrarSelector.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export class RegistrarSelectorComponent {
|
||||
registrarInput = signal<string>(this.registrarService.registrarId());
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
} @else {
|
||||
<div class="console-app__registrars">
|
||||
<div class="console-app__registrars-header">
|
||||
<h1 class="mat-headline-4">Registrars</h1>
|
||||
<h1 class="mat-headline-4" forceFocus>Registrars</h1>
|
||||
<div class="spacer"></div>
|
||||
<button
|
||||
mat-stroked-button
|
||||
@@ -59,6 +59,8 @@
|
||||
<mat-row
|
||||
*matRowDef="let row; columns: displayedColumns"
|
||||
(click)="openDetails(row.registrarId)"
|
||||
tabindex="0"
|
||||
(keyup.enter)="openDetails(row.registrarId)"
|
||||
></mat-row>
|
||||
</mat-table>
|
||||
|
||||
|
||||
@@ -78,6 +78,7 @@ export const columns = [
|
||||
templateUrl: './registrarsTable.component.html',
|
||||
styleUrls: ['./registrarsTable.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
standalone: false,
|
||||
})
|
||||
export class RegistrarComponent {
|
||||
public static PATH = 'registrars';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<h1 class="mat-headline-4">Resources</h1>
|
||||
<h1 class="mat-headline-4" forceFocus>Resources</h1>
|
||||
<div class="console-app__resources">
|
||||
<div>
|
||||
<div class="console-app__resources-subhead">Technical resources</div>
|
||||
@@ -11,6 +11,6 @@
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<img src="./assets/resources.png" />
|
||||
<img src="./assets/resources.png" alt="Generic resources image" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
width: 100%;
|
||||
}
|
||||
&-subhead {
|
||||
font-size: 20px;
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import { UserDataService } from '../shared/services/userData.service';
|
||||
selector: 'app-resources',
|
||||
templateUrl: './resources.component.html',
|
||||
styleUrls: ['./resources.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export class ResourcesComponent {
|
||||
public static PATH = 'resources';
|
||||
|
||||
@@ -32,7 +32,9 @@
|
||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||
<mat-row
|
||||
*matRowDef="let row; columns: displayedColumns"
|
||||
tabindex="0"
|
||||
(click)="openDetails(row)"
|
||||
(keyup.enter)="openDetails(row)"
|
||||
></mat-row>
|
||||
</mat-table>
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
templateUrl: './contact.component.html',
|
||||
styleUrls: ['./contact.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
standalone: false,
|
||||
})
|
||||
export default class ContactComponent {
|
||||
public static PATH = 'contact';
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
<div class="console-app__contact" *ngIf="contactService.contactInEdit">
|
||||
<div
|
||||
class="console-app__contact"
|
||||
*ngIf="contactService.contactInEdit"
|
||||
cdkTrapFocus
|
||||
[cdkTrapFocusAutoCapture]="true"
|
||||
>
|
||||
<div class="console-app__contact-controls">
|
||||
<button
|
||||
mat-icon-button
|
||||
@@ -123,6 +128,7 @@
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
type="submit"
|
||||
aria-label="Save contact updates"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
selector: 'app-contact-details',
|
||||
templateUrl: './contactDetails.component.html',
|
||||
styleUrls: ['./contactDetails.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export class ContactDetailsComponent {
|
||||
protected contactTypeToTextMap = contactTypeToTextMap;
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
<div class="settings-security__edit-password">
|
||||
<div
|
||||
class="settings-security__edit-password"
|
||||
cdkTrapFocus
|
||||
[cdkTrapFocusAutoCapture]="true"
|
||||
>
|
||||
<p>
|
||||
<button
|
||||
mat-icon-button
|
||||
|
||||
@@ -33,6 +33,7 @@ type errorFriendlyText = { [type in errorCode]: String };
|
||||
selector: 'app-epp-password-edit',
|
||||
templateUrl: './eppPasswordEdit.component.html',
|
||||
styleUrls: ['./eppPasswordEdit.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export default class EppPasswordEditComponent {
|
||||
MIN_MAX_LENGHT = new String(
|
||||
|
||||
@@ -42,7 +42,7 @@ describe('SecurityComponent', () => {
|
||||
fetchSecurityDetailsSpy =
|
||||
securityServiceSpy.fetchSecurityDetails.and.returnValue(of());
|
||||
|
||||
saveSpy = securityServiceSpy.saveChanges;
|
||||
saveSpy = securityServiceSpy.saveChanges.and.returnValue(of());
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [SecurityEditComponent, SecurityComponent],
|
||||
|
||||
@@ -23,6 +23,7 @@ import { SecurityService, apiToUiConverter } from './security.service';
|
||||
selector: 'app-security',
|
||||
templateUrl: './security.component.html',
|
||||
styleUrls: ['./security.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export default class SecurityComponent {
|
||||
public static PATH = 'security';
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
<div class="settings-security__edit">
|
||||
<div
|
||||
class="settings-security__edit"
|
||||
cdkTrapFocus
|
||||
[cdkTrapFocusAutoCapture]="true"
|
||||
>
|
||||
<h1>IP Allowlist</h1>
|
||||
<p>
|
||||
Restrict access to EPP production servers to the following IP/IPv6
|
||||
@@ -19,7 +23,7 @@
|
||||
<button
|
||||
matSuffix
|
||||
mat-icon-button
|
||||
aria-label="Remove"
|
||||
[attr.aria-label]="'Remove IP entry ' + ip.value"
|
||||
(click)="removeIpEntry(ip)"
|
||||
[disabled]="isUpdating"
|
||||
>
|
||||
@@ -32,6 +36,7 @@
|
||||
[disabled]="isUpdating"
|
||||
color="primary"
|
||||
(click)="createIpEntry()"
|
||||
aria-label="Add new IP address"
|
||||
type="button"
|
||||
>
|
||||
+ Add IP
|
||||
|
||||
@@ -26,6 +26,7 @@ import { SecurityService, apiToUiConverter } from './security.service';
|
||||
selector: 'app-security-edit',
|
||||
templateUrl: './securityEdit.component.html',
|
||||
styleUrls: ['./securityEdit.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export default class SecurityEditComponent {
|
||||
dataSource: SecuritySettings = {};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<app-selected-registrar-wrapper>
|
||||
<div class="console-settings">
|
||||
<h1 class="mat-headline-4">Settings</h1>
|
||||
<h1 class="mat-headline-4" forceFocus>Settings</h1>
|
||||
<nav
|
||||
mat-tab-nav-bar
|
||||
mat-stretch-tabs="false"
|
||||
@@ -10,22 +10,31 @@
|
||||
<a
|
||||
mat-tab-link
|
||||
routerLink="contact"
|
||||
routerLinkActive="active-link"
|
||||
routerLinkActive
|
||||
queryParamsHandling="merge"
|
||||
#rla="routerLinkActive"
|
||||
[active]="rla.isActive"
|
||||
aria-label="Access contacts settings"
|
||||
>Contacts</a
|
||||
>
|
||||
<a
|
||||
mat-tab-link
|
||||
routerLink="whois"
|
||||
routerLinkActive="active-link"
|
||||
routerLinkActive
|
||||
queryParamsHandling="merge"
|
||||
#rla2="routerLinkActive"
|
||||
[active]="rla2.isActive"
|
||||
aria-label="Access whois settings"
|
||||
>WHOIS Info</a
|
||||
>
|
||||
<a
|
||||
mat-tab-link
|
||||
routerLink="security"
|
||||
routerLinkActive="active-link"
|
||||
routerLinkActive
|
||||
queryParamsHandling="merge"
|
||||
#rla3="routerLinkActive"
|
||||
[active]="rla3.isActive"
|
||||
aria-label="Access security settings"
|
||||
>Security</a
|
||||
>
|
||||
</nav>
|
||||
|
||||
@@ -13,14 +13,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
.console-settings {
|
||||
.mdc-tab {
|
||||
&.active-link {
|
||||
border-bottom: 2px solid var(--primary);
|
||||
.mdc-tab__text-label {
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
nav {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,12 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { SettingsComponent } from './settings.component';
|
||||
import { MaterialModule } from '../material.module';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { AppModule, SelectedRegistrarModule } from '../app.module';
|
||||
import { BackendService } from '../shared/services/backend.service';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing';
|
||||
import { AppRoutingModule } from '../app-routing.module';
|
||||
|
||||
describe('SettingsComponent', () => {
|
||||
let component: SettingsComponent;
|
||||
@@ -24,7 +30,19 @@ describe('SettingsComponent', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [MaterialModule, BrowserAnimationsModule],
|
||||
imports: [
|
||||
SelectedRegistrarModule,
|
||||
MaterialModule,
|
||||
BrowserAnimationsModule,
|
||||
AppRoutingModule,
|
||||
AppModule,
|
||||
],
|
||||
providers: [
|
||||
BackendService,
|
||||
{ provide: ActivatedRoute, useValue: {} as ActivatedRoute },
|
||||
provideHttpClient(),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
declarations: [SettingsComponent],
|
||||
}).compileComponents();
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import { Component, ViewEncapsulation } from '@angular/core';
|
||||
templateUrl: './settings.component.html',
|
||||
styleUrls: ['./settings.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
standalone: false,
|
||||
})
|
||||
export class SettingsComponent {
|
||||
public static PATH = 'settings';
|
||||
|
||||
@@ -21,6 +21,7 @@ import { WhoisService } from './whois.service';
|
||||
selector: 'app-whois',
|
||||
templateUrl: './whois.component.html',
|
||||
styleUrls: ['./whois.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export default class WhoisComponent {
|
||||
public static PATH = 'whois';
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
<div class="console-app__whois-edit" *ngIf="registrarInEdit">
|
||||
<div
|
||||
class="console-app__whois-edit"
|
||||
*ngIf="registrarInEdit"
|
||||
cdkTrapFocus
|
||||
[cdkTrapFocusAutoCapture]="true"
|
||||
>
|
||||
<button
|
||||
mat-icon-button
|
||||
class="console-app__whois-edit-back"
|
||||
@@ -144,7 +149,14 @@
|
||||
</mat-form-field>
|
||||
}
|
||||
|
||||
<button mat-flat-button color="primary" type="submit">Save</button>
|
||||
<button
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
type="submit"
|
||||
aria-label="Save WHOIS settings"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -26,6 +26,7 @@ import { WhoisService } from './whois.service';
|
||||
selector: 'app-whois-edit',
|
||||
templateUrl: './whoisEdit.component.html',
|
||||
styleUrls: ['./whoisEdit.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export default class WhoisEditComponent {
|
||||
registrarInEdit: Registrar | undefined;
|
||||
|
||||
@@ -24,6 +24,7 @@ interface Notification {
|
||||
selector: 'app-notifications',
|
||||
templateUrl: './notifications.component.html',
|
||||
styleUrls: ['./notifications.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export class NotificationsComponent {
|
||||
protected mockNotifications: Notification[] = [
|
||||
|
||||
@@ -31,6 +31,7 @@ import { RegistrarService } from 'src/app/registrar/registrar.service';
|
||||
</div>
|
||||
}
|
||||
`,
|
||||
standalone: false,
|
||||
})
|
||||
export class SelectedRegistrarWrapper {
|
||||
constructor(protected registrarService: RegistrarService) {}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Directive, ElementRef, effect } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[forceFocus]',
|
||||
standalone: false,
|
||||
})
|
||||
export class ForceFocusDirective {
|
||||
constructor(private el: ElementRef) {
|
||||
effect(this.processElement.bind(this));
|
||||
}
|
||||
|
||||
processElement() {
|
||||
this.el.nativeElement.tabIndex = '1';
|
||||
this.el.nativeElement.focus();
|
||||
this.el.nativeElement.tabIndex = '-1';
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import { Directive, HostListener } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[backButton]',
|
||||
standalone: false,
|
||||
})
|
||||
export class LocationBackDirective {
|
||||
constructor(private location: Location) {}
|
||||
|
||||
@@ -19,6 +19,7 @@ export enum RESTRICTED_ELEMENTS {
|
||||
REGISTRAR_ELEMENT,
|
||||
OTE,
|
||||
USERS,
|
||||
BULK_DELETE,
|
||||
}
|
||||
|
||||
export const DISABLED_ELEMENTS_PER_ROLE = {
|
||||
@@ -26,6 +27,7 @@ export const DISABLED_ELEMENTS_PER_ROLE = {
|
||||
RESTRICTED_ELEMENTS.REGISTRAR_ELEMENT,
|
||||
RESTRICTED_ELEMENTS.OTE,
|
||||
RESTRICTED_ELEMENTS.USERS,
|
||||
RESTRICTED_ELEMENTS.BULK_DELETE,
|
||||
],
|
||||
SUPPORT_LEAD: [RESTRICTED_ELEMENTS.USERS],
|
||||
SUPPORT_AGENT: [RESTRICTED_ELEMENTS.USERS],
|
||||
@@ -33,6 +35,7 @@ export const DISABLED_ELEMENTS_PER_ROLE = {
|
||||
|
||||
@Directive({
|
||||
selector: '[elementId]',
|
||||
standalone: false,
|
||||
})
|
||||
export class UserLevelVisibility {
|
||||
@Input() elementId!: RESTRICTED_ELEMENTS | null;
|
||||
|
||||
@@ -180,6 +180,23 @@ export class BackendService {
|
||||
.pipe(catchError((err) => this.errorCatcher<any>(err)));
|
||||
}
|
||||
|
||||
bulkDomainAction(
|
||||
domainNames: string[],
|
||||
reason: string,
|
||||
bulkDomainAction: string,
|
||||
registrarId: string
|
||||
) {
|
||||
return this.http
|
||||
.post<any>(
|
||||
`/console-api/bulk-domain?registrarId=${registrarId}&bulkDomainAction=${bulkDomainAction}`,
|
||||
{
|
||||
domainList: domainNames,
|
||||
reason,
|
||||
}
|
||||
)
|
||||
.pipe(catchError((err) => this.errorCatcher<any>(err)));
|
||||
}
|
||||
|
||||
updateUser(registrarId: string, updatedUser: User): Observable<any> {
|
||||
return this.http
|
||||
.put<User>(`/console-api/users?registrarId=${registrarId}`, updatedUser)
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
For help with OT&E sandbox and certification, or new technical requirements
|
||||
for any of our new TLD launches.
|
||||
</p>
|
||||
<a class="text-l" href="mailto:registry-integration@google.com"
|
||||
<a
|
||||
class="text-l"
|
||||
href="mailto:registry-integration@google.com"
|
||||
aria-label="Email us with OT&E sandbox/certification or new TLD technical requirements questions."
|
||||
>registry-integration@google.com</a
|
||||
>
|
||||
<p class="text-l">
|
||||
@@ -19,6 +22,7 @@
|
||||
</p>
|
||||
<a
|
||||
class="text-l"
|
||||
aria-label="Email support with general purpose questions."
|
||||
href="mailto:{{ userDataService.userData()?.supportEmail }}"
|
||||
>{{ userDataService.userData()?.supportEmail }}</a
|
||||
>
|
||||
|
||||
@@ -19,6 +19,7 @@ import { UserDataService } from '../shared/services/userData.service';
|
||||
selector: 'app-support',
|
||||
templateUrl: './support.component.html',
|
||||
styleUrls: ['./support.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export class SupportComponent {
|
||||
public static PATH = 'support';
|
||||
|
||||
@@ -18,5 +18,6 @@ import { Component } from '@angular/core';
|
||||
selector: 'app-tlds',
|
||||
templateUrl: './tlds.component.html',
|
||||
styleUrls: ['./tlds.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export class TldsComponent {}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
<div class="console-app__user-details">
|
||||
<div
|
||||
class="console-app__user-details"
|
||||
cdkTrapFocus
|
||||
[cdkTrapFocusAutoCapture]="true"
|
||||
>
|
||||
@if(isEditing) {
|
||||
<h1 class="mat-headline-4">Editing {{ userDetails().emailAddress }}</h1>
|
||||
<mat-divider></mat-divider>
|
||||
@@ -86,11 +90,12 @@
|
||||
<input
|
||||
[type]="isPasswordVisible ? 'text' : 'password'"
|
||||
[value]="userDetails().password"
|
||||
disabled
|
||||
aria-label="Password field"
|
||||
readonly
|
||||
/>
|
||||
<button
|
||||
mat-button
|
||||
aria-label="Show password"
|
||||
aria-hidden="true"
|
||||
(click)="isPasswordVisible = !isPasswordVisible"
|
||||
>
|
||||
{{ isPasswordVisible ? "Hide" : "View" }} password
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
.console-app {
|
||||
&__user-details {
|
||||
max-width: 616px;
|
||||
&-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -34,6 +35,5 @@
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 10px;
|
||||
}
|
||||
max-width: 616px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ import { UserEditFormComponent } from './userEditForm.component';
|
||||
selector: 'app-user-edit',
|
||||
templateUrl: './userDetails.component.html',
|
||||
styleUrls: ['./userDetails.component.scss'],
|
||||
standalone: true,
|
||||
imports: [
|
||||
FormsModule,
|
||||
MaterialModule,
|
||||
|
||||
@@ -33,7 +33,13 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</p>
|
||||
<button mat-flat-button color="primary" aria-label="Save user" type="submit">
|
||||
<button
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
aria-label="Save user"
|
||||
type="submit"
|
||||
aria-label="Save changes to the user"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -29,19 +29,17 @@ import { User } from './users.service';
|
||||
selector: 'app-user-edit-form',
|
||||
templateUrl: './userEditForm.component.html',
|
||||
styleUrls: ['./userEditForm.component.scss'],
|
||||
standalone: true,
|
||||
imports: [FormsModule, MaterialModule, CommonModule],
|
||||
providers: [],
|
||||
})
|
||||
export class UserEditFormComponent {
|
||||
@ViewChild('form') form!: ElementRef;
|
||||
isNew = input<boolean>(false);
|
||||
user = input<User>(
|
||||
user = input<User, User>(
|
||||
{
|
||||
emailAddress: '',
|
||||
role: 'ACCOUNT_MANAGER',
|
||||
},
|
||||
// @ts-ignore - legit option, typescript fails to match it to a proper type
|
||||
{ transform: (user: User) => structuredClone(user) }
|
||||
);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<mat-spinner />
|
||||
</div>
|
||||
} @else if(selectingExistingUser) {
|
||||
<div class="console-app__users">
|
||||
<div class="console-app__users" cdkTrapFocus [cdkTrapFocusAutoCapture]="true">
|
||||
<h1 class="mat-headline-4">Add existing user</h1>
|
||||
<p>
|
||||
<button
|
||||
@@ -58,24 +58,32 @@
|
||||
}
|
||||
</div>
|
||||
} @else if(usersService.currentlyOpenUserEmail()) {
|
||||
<app-user-edit></app-user-edit>
|
||||
<div cdkTrapFocus [cdkTrapFocusAutoCapture]="true">
|
||||
<app-user-edit></app-user-edit>
|
||||
</div>
|
||||
|
||||
} @else if(isNew) {
|
||||
<h1 class="mat-headline-4">New User Form</h1>
|
||||
<div class="spacer"></div>
|
||||
<p>
|
||||
<button
|
||||
mat-icon-button
|
||||
aria-label="Back to users list"
|
||||
(click)="isNew = false"
|
||||
>
|
||||
<mat-icon>arrow_back</mat-icon>
|
||||
</button>
|
||||
</p>
|
||||
<app-user-edit-form [isNew]="true" (onEditComplete)="createNewUser($event)" />
|
||||
<div cdkTrapFocus [cdkTrapFocusAutoCapture]="true">
|
||||
<h1 class="mat-headline-4">New User Form</h1>
|
||||
<div class="spacer"></div>
|
||||
<p>
|
||||
<button
|
||||
mat-icon-button
|
||||
aria-label="Back to users list"
|
||||
(click)="isNew = false"
|
||||
>
|
||||
<mat-icon>arrow_back</mat-icon>
|
||||
</button>
|
||||
</p>
|
||||
<app-user-edit-form
|
||||
[isNew]="true"
|
||||
(onEditComplete)="createNewUser($event)"
|
||||
/>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="console-app__users">
|
||||
<div class="console-app__users-header">
|
||||
<h1 class="mat-headline-4">Users</h1>
|
||||
<h1 class="mat-headline-4" forceFocus>Users</h1>
|
||||
<div class="spacer"></div>
|
||||
<div class="console-app__users-header-buttons">
|
||||
<button
|
||||
@@ -88,12 +96,7 @@
|
||||
<mat-icon>add</mat-icon>
|
||||
Add existing user
|
||||
</button>
|
||||
<button
|
||||
mat-flat-button
|
||||
(click)="isNew = true"
|
||||
aria-label="Create new user"
|
||||
color="primary"
|
||||
>
|
||||
<button mat-flat-button (click)="isNew = true" color="primary">
|
||||
Create New User
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -32,7 +32,6 @@ import { UserEditFormComponent } from './userEditForm.component';
|
||||
selector: 'app-users',
|
||||
templateUrl: './users.component.html',
|
||||
styleUrls: ['./users.component.scss'],
|
||||
standalone: true,
|
||||
imports: [
|
||||
FormsModule,
|
||||
MaterialModule,
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
*matRowDef="let row; columns: displayedColumns"
|
||||
[class.rowSelected]="isRowSelected(row)"
|
||||
(click)="onClick(row)"
|
||||
(keyup.enter)="onClick(row)"
|
||||
tabindex="0"
|
||||
></mat-row>
|
||||
</mat-table>
|
||||
</div>
|
||||
|
||||
@@ -43,7 +43,6 @@ export const columns = [
|
||||
selector: 'app-users-list',
|
||||
templateUrl: './usersList.component.html',
|
||||
styleUrls: ['./usersList.component.scss'],
|
||||
standalone: true,
|
||||
imports: [MaterialModule, CommonModule],
|
||||
providers: [],
|
||||
})
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
@use "@angular/material" as mat;
|
||||
@import "app/registrar/registrarSelector.component.scss";
|
||||
@use "app/registrar/registrarSelector.component.scss";
|
||||
|
||||
html,
|
||||
body {
|
||||
@@ -55,13 +55,13 @@ body {
|
||||
font-weight: bold;
|
||||
width: var(--list-item-title-width);
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
font-size: 0.85rem;
|
||||
white-space: pre-line;
|
||||
color: #202124;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.console-app__list-value {
|
||||
font-size: 14px;
|
||||
font-size: 0.85rem;
|
||||
white-space: pre-line;
|
||||
word-break: break-word;
|
||||
color: var(--text);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
@use "sass:map";
|
||||
@use "sass:math";
|
||||
@use "@angular/material" as mat;
|
||||
@use "@material/textfield";
|
||||
|
||||
$secondary-color: #80868b;
|
||||
$border-color: #dadce0;
|
||||
@@ -13,7 +12,8 @@ $border-color: #dadce0;
|
||||
// Include the common styles for Angular Material. We include this here so that you only
|
||||
// have to load a single css file for Angular Material in your app.
|
||||
// Be sure that you only ever include this mixin once!
|
||||
@include mat.core();
|
||||
@include mat.elevation-classes();
|
||||
@include mat.app-background();
|
||||
|
||||
$typographyConfig: mat.m2-define-typography-config(
|
||||
$headline-1:
|
||||
@@ -82,16 +82,11 @@ $typographyConfig: mat.m2-define-typography-config(
|
||||
),
|
||||
);
|
||||
|
||||
// Access and define a class with secondary color exposed
|
||||
.secondary-text {
|
||||
color: $secondary-color;
|
||||
}
|
||||
|
||||
.text-xl {
|
||||
font-size: 18px;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
.text-l {
|
||||
font-size: 16px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
mat-row:nth-child(odd) {
|
||||
@@ -115,6 +110,15 @@ mat-row:hover {
|
||||
--mat-sidenav-container-width: 280px;
|
||||
}
|
||||
|
||||
// Access and define a class with secondary color exposed
|
||||
.secondary-text {
|
||||
color: #575757;
|
||||
}
|
||||
|
||||
.primary-text {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
$theme: mat.define-theme(
|
||||
(
|
||||
color: (
|
||||
|
||||
@@ -17,6 +17,7 @@ package google.registry.batch;
|
||||
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.config.RegistryConfig.CANARY_HEADER;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
import com.google.api.gax.rpc.ApiException;
|
||||
@@ -190,6 +191,9 @@ public class CloudTasksUtils implements Serializable {
|
||||
requestBuilder.setOidcToken(oidcTokenBuilder.build());
|
||||
String totalPath = String.format("%s%s", service.getServiceUrl(), path);
|
||||
requestBuilder.setUrl(totalPath);
|
||||
if (RegistryEnvironment.isCanary()) {
|
||||
requestBuilder.putHeaders(CANARY_HEADER, "true");
|
||||
}
|
||||
return Task.newBuilder().setHttpRequest(requestBuilder.build()).build();
|
||||
}
|
||||
|
||||
@@ -200,7 +204,7 @@ public class CloudTasksUtils implements Serializable {
|
||||
* default service account as the principal. That account must have permission to submit tasks to
|
||||
* Cloud Tasks.
|
||||
*
|
||||
* <p>Prefer this overload over the one where the path and service are explicit defined, as this
|
||||
* <p>Prefer this overload over the one where the path and service are explicitly defined, as this
|
||||
* class will automatically determine the service to use based on the action and the runtime.
|
||||
*
|
||||
* @param actionClazz the action class to run, must be annotated with {@link Action}.
|
||||
@@ -269,7 +273,7 @@ public class CloudTasksUtils implements Serializable {
|
||||
/**
|
||||
* Create a {@link Task} to be enqueued with a random delay up to {@code jitterSeconds}.
|
||||
*
|
||||
* <p>Prefer this overload over the one where the path and service are explicit defined, as this
|
||||
* <p>Prefer this overload over the one where the path and service are explicitly defined, as this
|
||||
* class will automatically determine the service to use based on the action and the runtime.
|
||||
*
|
||||
* @param actionClazz the action class to run, must be annotated with {@link Action}.
|
||||
@@ -306,7 +310,7 @@ public class CloudTasksUtils implements Serializable {
|
||||
* @param service the GAE/GKE service to route the request to.
|
||||
* @param params a multimap of URL query parameters. Duplicate keys are saved as is, and it is up
|
||||
* to the server to process the duplicate keys.
|
||||
* @param delay the amount of time that a task needs to delayed for.
|
||||
* @param delay the amount of time that a task needs to be delayed for.
|
||||
* @return the enqueued task.
|
||||
* @see <a
|
||||
* href=ttps://cloud.google.com/appengine/docs/standard/java/taskqueue/push/creating-tasks#target>Specifyinig
|
||||
@@ -330,14 +334,14 @@ public class CloudTasksUtils implements Serializable {
|
||||
/**
|
||||
* Create a {@link Task} to be enqueued with delay of {@code duration}.
|
||||
*
|
||||
* <p>Prefer this overload over the one where the path and service are explicit defined, as this
|
||||
* <p>Prefer this overload over the one where the path and service are explicitly defined, as this
|
||||
* class will automatically determine the service to use based on the action and the runtime.
|
||||
*
|
||||
* @param actionClazz the action class to run, must be annotated with {@link Action}.
|
||||
* @param method the HTTP method to be used for the request.
|
||||
* @param params a multimap of URL query parameters. Duplicate keys are saved as is, and it is up
|
||||
* to the server to process the duplicate keys.
|
||||
* @param delay the amount of time that a task needs to delayed for.
|
||||
* @param delay the amount of time that a task needs to be delayed for.
|
||||
* @return the enqueued task.
|
||||
* @see <a
|
||||
* href=ttps://cloud.google.com/appengine/docs/standard/java/taskqueue/push/creating-tasks#target>Specifyinig
|
||||
|
||||
@@ -20,6 +20,7 @@ import dagger.Lazy;
|
||||
import google.registry.config.CredentialModule;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.config.RegistryConfig.ConfigModule;
|
||||
import google.registry.keyring.KeyringModule;
|
||||
import google.registry.persistence.PersistenceModule;
|
||||
import google.registry.persistence.PersistenceModule.BeamJpaTm;
|
||||
import google.registry.persistence.PersistenceModule.BeamReadOnlyReplicaJpaTm;
|
||||
@@ -36,6 +37,7 @@ import javax.inject.Singleton;
|
||||
modules = {
|
||||
ConfigModule.class,
|
||||
CredentialModule.class,
|
||||
KeyringModule.class,
|
||||
PersistenceModule.class,
|
||||
SecretManagerModule.class,
|
||||
UtilsModule.class
|
||||
|
||||
@@ -71,6 +71,7 @@ import org.joda.time.Duration;
|
||||
*/
|
||||
public final class RegistryConfig {
|
||||
|
||||
public static final String CANARY_HEADER = "canary";
|
||||
private static final String ENVIRONMENT_CONFIG_FORMAT = "files/nomulus-config-%s.yaml";
|
||||
private static final String YAML_CONFIG_PROD =
|
||||
readResourceUtf8(RegistryConfig.class, "files/default-config.yaml");
|
||||
@@ -1098,12 +1099,6 @@ public final class RegistryConfig {
|
||||
return config.registryPolicy.greetingServerId;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("activeKeyring")
|
||||
public static String provideKeyring(RegistryConfigSettings config) {
|
||||
return config.keyring.activeKeyring;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("customLogicFactoryClass")
|
||||
public static String provideCustomLogicFactoryClass(RegistryConfigSettings config) {
|
||||
|
||||
@@ -37,7 +37,6 @@ public class RegistryConfigSettings {
|
||||
public Monitoring monitoring;
|
||||
public Misc misc;
|
||||
public Beam beam;
|
||||
public Keyring keyring;
|
||||
public RegistryTool registryTool;
|
||||
public SslCertificateValidation sslCertificateValidation;
|
||||
public ContactHistory contactHistory;
|
||||
@@ -214,11 +213,6 @@ public class RegistryConfigSettings {
|
||||
public int transientFailureRetries;
|
||||
}
|
||||
|
||||
/** Configuration for keyrings (used to store secrets outside of source). */
|
||||
public static class Keyring {
|
||||
public String activeKeyring;
|
||||
}
|
||||
|
||||
/** Configuration options for the registry tool. */
|
||||
public static class RegistryTool {
|
||||
public String clientId;
|
||||
|
||||
@@ -488,11 +488,6 @@ beam:
|
||||
initialWorkerCount: 24
|
||||
stagingBucketUrl: gcs-bucket-with-staged-templates
|
||||
|
||||
keyring:
|
||||
# The name of the active keyring, either "Dummy" or "CSM". The latter stands
|
||||
# for Cloud SecretManager.
|
||||
activeKeyring: Dummy
|
||||
|
||||
# Configuration options relevant to the "nomulus" registry tool.
|
||||
registryTool:
|
||||
# OAuth client ID used by the tool.
|
||||
|
||||
@@ -54,7 +54,8 @@ public class ExportPremiumTermsAction implements Runnable {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
static final MediaType EXPORT_MIME_TYPE = MediaType.PLAIN_TEXT_UTF_8;
|
||||
static final String PREMIUM_TERMS_FILENAME = "CONFIDENTIAL_premium_terms.txt";
|
||||
static final String TLD_IDENTIFIER_FORMAT = "# TLD: %s";
|
||||
static final String PREMIUM_TERMS_FILENAME_FORMAT = "CONFIDENTIAL_premium_terms_%s.txt";
|
||||
|
||||
@Inject DriveConnection driveConnection;
|
||||
|
||||
@@ -127,7 +128,7 @@ public class ExportPremiumTermsAction implements Runnable {
|
||||
try {
|
||||
String fileId =
|
||||
driveConnection.createOrUpdateFile(
|
||||
PREMIUM_TERMS_FILENAME,
|
||||
String.format(PREMIUM_TERMS_FILENAME_FORMAT, tldStr),
|
||||
EXPORT_MIME_TYPE,
|
||||
tld.getDriveFolderId(),
|
||||
getFormattedPremiumTerms(tld).getBytes(UTF_8));
|
||||
@@ -150,11 +151,9 @@ public class ExportPremiumTermsAction implements Runnable {
|
||||
.map(PremiumEntry::toString)
|
||||
.collect(ImmutableSortedSet.toImmutableSortedSet(String::compareTo));
|
||||
|
||||
return Joiner.on("\n")
|
||||
.appendTo(
|
||||
new StringBuilder(),
|
||||
Iterables.concat(ImmutableList.of(exportDisclaimer.trim()), premiumTerms))
|
||||
.append("\n")
|
||||
.toString();
|
||||
String tldIdentifier = String.format(TLD_IDENTIFIER_FORMAT, tldStr);
|
||||
Iterable<String> commentsAndTerms =
|
||||
Iterables.concat(ImmutableList.of(exportDisclaimer.trim(), tldIdentifier), premiumTerms);
|
||||
return Joiner.on("\n").join(commentsAndTerms) + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,6 +147,13 @@ public class FlowModule {
|
||||
.map(IsolationLevel::value);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FlowScope
|
||||
@LogSqlStatements
|
||||
boolean provideShouldLogSqlStatements(Class<? extends Flow> flowClass) {
|
||||
return SqlStatementLoggingFlow.class.isAssignableFrom(flowClass);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FlowScope
|
||||
@Superuser
|
||||
@@ -370,4 +377,9 @@ public class FlowModule {
|
||||
@Qualifier
|
||||
@Documented
|
||||
public @interface Transactional {}
|
||||
|
||||
/** Dagger qualifier for if we should log all SQL statements in a flow. */
|
||||
@Qualifier
|
||||
@Documented
|
||||
public @interface LogSqlStatements {}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import static google.registry.xml.XmlTransformer.prettyPrint;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.flows.FlowModule.DryRun;
|
||||
import google.registry.flows.FlowModule.InputXml;
|
||||
import google.registry.flows.FlowModule.LogSqlStatements;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.FlowModule.Transactional;
|
||||
@@ -49,6 +50,7 @@ public class FlowRunner {
|
||||
@Inject @DryRun boolean isDryRun;
|
||||
@Inject @Superuser boolean isSuperuser;
|
||||
@Inject @Transactional boolean isTransactional;
|
||||
@Inject @LogSqlStatements boolean logSqlStatements;
|
||||
@Inject SessionMetadata sessionMetadata;
|
||||
@Inject Trid trid;
|
||||
@Inject FlowReporter flowReporter;
|
||||
@@ -97,7 +99,8 @@ public class FlowRunner {
|
||||
} catch (EppException e) {
|
||||
throw new EppRuntimeException(e);
|
||||
}
|
||||
});
|
||||
},
|
||||
logSqlStatements);
|
||||
} catch (DryRunException e) {
|
||||
return e.output;
|
||||
} catch (EppRuntimeException e) {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.flows;
|
||||
|
||||
/**
|
||||
* Interface for a {@link Flow} that logs its SQL statements when running transactionally.
|
||||
*
|
||||
* <p>We don't wish to log all SQL statements ever executed (that'll create too much log bloat) but
|
||||
* for some flows and some occasions we may wish to know precisely what SQL statements are being
|
||||
* run.
|
||||
*/
|
||||
public interface SqlStatementLoggingFlow extends TransactionalFlow {}
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
package google.registry.flows.domain;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static google.registry.dns.DnsUtils.requestDomainDnsRefresh;
|
||||
import static google.registry.flows.FlowUtils.persistEntityChanges;
|
||||
@@ -120,9 +119,7 @@ import google.registry.model.tmch.ClaimsList;
|
||||
import google.registry.model.tmch.ClaimsListDao;
|
||||
import google.registry.tmch.LordnTaskUtils.LordnPhase;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
import org.joda.money.Money;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Duration;
|
||||
|
||||
@@ -363,9 +360,7 @@ public final class DomainCreateFlow implements MutatingFlow {
|
||||
// Create a new autorenew billing event and poll message starting at the expiration time.
|
||||
BillingRecurrence autorenewBillingEvent =
|
||||
createAutorenewBillingEvent(
|
||||
domainHistoryId,
|
||||
registrationExpirationTime,
|
||||
getRenewalPriceInfo(isAnchorTenant, allocationToken));
|
||||
domainHistoryId, registrationExpirationTime, isAnchorTenant, allocationToken);
|
||||
PollMessage.Autorenew autorenewPollMessage =
|
||||
createAutorenewPollMessage(domainHistoryId, registrationExpirationTime);
|
||||
ImmutableSet.Builder<ImmutableObject> entitiesToSave = new ImmutableSet.Builder<>();
|
||||
@@ -625,7 +620,17 @@ public final class DomainCreateFlow implements MutatingFlow {
|
||||
private BillingRecurrence createAutorenewBillingEvent(
|
||||
HistoryEntryId domainHistoryId,
|
||||
DateTime registrationExpirationTime,
|
||||
RenewalPriceInfo renewalpriceInfo) {
|
||||
boolean isAnchorTenant,
|
||||
Optional<AllocationToken> allocationToken) {
|
||||
// Non-standard renewal behaviors can occur for anchor tenants (always NONPREMIUM pricing) or if
|
||||
// explicitly configured in the token (either NONPREMIUM or directly SPECIFIED). Use DEFAULT if
|
||||
// none is configured.
|
||||
RenewalPriceBehavior renewalPriceBehavior =
|
||||
isAnchorTenant
|
||||
? RenewalPriceBehavior.NONPREMIUM
|
||||
: allocationToken
|
||||
.map(AllocationToken::getRenewalPriceBehavior)
|
||||
.orElse(RenewalPriceBehavior.DEFAULT);
|
||||
return new BillingRecurrence.Builder()
|
||||
.setReason(Reason.RENEW)
|
||||
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
||||
@@ -634,8 +639,8 @@ public final class DomainCreateFlow implements MutatingFlow {
|
||||
.setEventTime(registrationExpirationTime)
|
||||
.setRecurrenceEndTime(END_OF_TIME)
|
||||
.setDomainHistoryId(domainHistoryId)
|
||||
.setRenewalPriceBehavior(renewalpriceInfo.renewalPriceBehavior())
|
||||
.setRenewalPrice(renewalpriceInfo.renewalPrice())
|
||||
.setRenewalPriceBehavior(renewalPriceBehavior)
|
||||
.setRenewalPrice(allocationToken.flatMap(AllocationToken::getRenewalPrice).orElse(null))
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -679,41 +684,6 @@ public final class DomainCreateFlow implements MutatingFlow {
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the {@link RenewalPriceBehavior} and the renewal price that needs be stored in the
|
||||
* {@link BillingRecurrence} billing events.
|
||||
*
|
||||
* <p>By default, the renewal price is calculated during the process of renewal. Renewal price
|
||||
* should be the createCost if and only if the renewal price behavior in the {@link
|
||||
* AllocationToken} is 'SPECIFIED'.
|
||||
*/
|
||||
static RenewalPriceInfo getRenewalPriceInfo(
|
||||
boolean isAnchorTenant, Optional<AllocationToken> allocationToken) {
|
||||
if (isAnchorTenant) {
|
||||
allocationToken.ifPresent(
|
||||
token ->
|
||||
checkArgument(
|
||||
token.getRenewalPriceBehavior() != RenewalPriceBehavior.SPECIFIED,
|
||||
"Renewal price behavior cannot be SPECIFIED for anchor tenant"));
|
||||
return RenewalPriceInfo.create(RenewalPriceBehavior.NONPREMIUM, null);
|
||||
} else if (allocationToken.isPresent()
|
||||
&& allocationToken.get().getRenewalPriceBehavior() == RenewalPriceBehavior.SPECIFIED) {
|
||||
return RenewalPriceInfo.create(
|
||||
RenewalPriceBehavior.SPECIFIED, allocationToken.get().getRenewalPrice().get());
|
||||
} else {
|
||||
return RenewalPriceInfo.create(RenewalPriceBehavior.DEFAULT, null);
|
||||
}
|
||||
}
|
||||
|
||||
/** A record to store renewal info used in {@link BillingRecurrence} billing events. */
|
||||
public record RenewalPriceInfo(
|
||||
RenewalPriceBehavior renewalPriceBehavior, @Nullable Money renewalPrice) {
|
||||
static RenewalPriceInfo create(
|
||||
RenewalPriceBehavior renewalPriceBehavior, @Nullable Money renewalPrice) {
|
||||
return new RenewalPriceInfo(renewalPriceBehavior, renewalPrice);
|
||||
}
|
||||
}
|
||||
|
||||
private static ImmutableList<FeeTransformResponseExtension> createResponseExtensions(
|
||||
Optional<FeeCreateCommandExtension> feeCreate, FeesAndCredits feesAndCredits) {
|
||||
return feeCreate
|
||||
|
||||
@@ -55,6 +55,7 @@ import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.SessionMetadata;
|
||||
import google.registry.flows.SqlStatementLoggingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.custom.DomainDeleteFlowCustomLogic;
|
||||
import google.registry.flows.custom.DomainDeleteFlowCustomLogic.AfterValidationParameters;
|
||||
@@ -117,7 +118,7 @@ import org.joda.time.Duration;
|
||||
* @error {@link DomainFlowUtils.NotAuthorizedForTldException}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.DOMAIN_DELETE)
|
||||
public final class DomainDeleteFlow implements MutatingFlow {
|
||||
public final class DomainDeleteFlow implements MutatingFlow, SqlStatementLoggingFlow {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
|
||||
@@ -85,14 +85,10 @@ public final class DomainPricingLogic {
|
||||
createFee = Fee.create(zeroInCurrency(currency), FeeType.CREATE, false);
|
||||
} else {
|
||||
DomainPrices domainPrices = getPricesForDomainName(domainName, dateTime);
|
||||
if (allocationToken.isPresent()
|
||||
&& allocationToken
|
||||
.get()
|
||||
.getRegistrationBehavior()
|
||||
.equals(RegistrationBehavior.NONPREMIUM_CREATE)) {
|
||||
if (allocationToken.isPresent()) {
|
||||
// Handle any special NONPREMIUM / SPECIFIED cases configured in the token
|
||||
domainPrices =
|
||||
DomainPrices.create(
|
||||
false, tld.getCreateBillingCost(dateTime), domainPrices.getRenewCost());
|
||||
applyTokenToDomainPrices(domainPrices, tld, dateTime, years, allocationToken.get());
|
||||
}
|
||||
Money domainCreateCost =
|
||||
getDomainCreateCostWithDiscount(domainPrices, years, allocationToken, tld);
|
||||
@@ -291,6 +287,9 @@ public final class DomainPricingLogic {
|
||||
|| token.getRenewalPriceBehavior().equals(RenewalPriceBehavior.NONPREMIUM)) {
|
||||
return tld.getStandardRenewCost(dateTime).multipliedBy(years);
|
||||
}
|
||||
if (token.getRenewalPriceBehavior().equals(RenewalPriceBehavior.SPECIFIED)) {
|
||||
return token.getRenewalPrice().get();
|
||||
}
|
||||
}
|
||||
return getDomainCostWithDiscount(
|
||||
domainPrices.isPremium(),
|
||||
@@ -357,6 +356,27 @@ public final class DomainPricingLogic {
|
||||
return totalDomainFlowCost;
|
||||
}
|
||||
|
||||
private DomainPrices applyTokenToDomainPrices(
|
||||
DomainPrices domainPrices, Tld tld, DateTime dateTime, int years, AllocationToken token) {
|
||||
// Convert to nonpremium iff no premium charges are included (either in create or any renewal)
|
||||
boolean convertToNonPremium =
|
||||
token.getRegistrationBehavior().equals(RegistrationBehavior.NONPREMIUM_CREATE)
|
||||
&& (years == 1
|
||||
|| !token.getRenewalPriceBehavior().equals(RenewalPriceBehavior.DEFAULT));
|
||||
boolean isPremium = domainPrices.isPremium() && !convertToNonPremium;
|
||||
Money createCost =
|
||||
token.getRegistrationBehavior().equals(RegistrationBehavior.NONPREMIUM_CREATE)
|
||||
? tld.getCreateBillingCost(dateTime)
|
||||
: domainPrices.getCreateCost();
|
||||
Money renewCost =
|
||||
token.getRenewalPriceBehavior().equals(RenewalPriceBehavior.NONPREMIUM)
|
||||
? tld.getStandardRenewCost(dateTime)
|
||||
: token.getRenewalPriceBehavior().equals(RenewalPriceBehavior.SPECIFIED)
|
||||
? token.getRenewalPrice().get()
|
||||
: domainPrices.getRenewCost();
|
||||
return DomainPrices.create(isPremium, createCost, renewCost);
|
||||
}
|
||||
|
||||
/** An allocation token was provided that is invalid for premium domains. */
|
||||
public static class AllocationTokenInvalidForPremiumNameException
|
||||
extends CommandUseErrorException {
|
||||
|
||||
@@ -14,31 +14,22 @@
|
||||
|
||||
package google.registry.keyring;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.keyring.api.Keyring;
|
||||
import java.util.Map;
|
||||
import google.registry.keyring.secretmanager.SecretManagerKeyring;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/** Dagger module for {@link Keyring} */
|
||||
@Module
|
||||
public final class KeyringModule {
|
||||
public abstract class KeyringModule {
|
||||
|
||||
@Provides
|
||||
@Binds
|
||||
@Singleton
|
||||
public static Keyring provideKeyring(
|
||||
Map<String, Keyring> keyrings, @Config("activeKeyring") String activeKeyring) {
|
||||
checkState(
|
||||
keyrings.containsKey(activeKeyring),
|
||||
"Invalid Keyring %s is configured; valid choices are %s",
|
||||
activeKeyring,
|
||||
keyrings.keySet());
|
||||
return keyrings.get(activeKeyring);
|
||||
}
|
||||
public abstract Keyring provideKeyring(SecretManagerKeyring keyring);
|
||||
|
||||
@Provides
|
||||
@Config("cloudSqlInstanceConnectionName")
|
||||
|
||||
@@ -1,205 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.keyring.api;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.util.ComparingInvocationHandler;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import javax.annotation.Nullable;
|
||||
import org.bouncycastle.bcpg.BCPGKey;
|
||||
import org.bouncycastle.bcpg.PublicKeyPacket;
|
||||
import org.bouncycastle.openpgp.PGPKeyPair;
|
||||
import org.bouncycastle.openpgp.PGPPrivateKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
|
||||
/**
|
||||
* Checks that a second keyring returns the same result as the current one.
|
||||
*
|
||||
* <p>Will behave exactly like the "actualKeyring" - as in will throw / return the exact same values
|
||||
* - no matter what the "secondKeyring" does. But will log a warning if "secondKeyring" acts
|
||||
* differently than "actualKeyring".
|
||||
*
|
||||
* <p>If both keyrings threw exceptions, there is no check whether the exeptions are the same. The
|
||||
* assumption is that an error happened in both, but they might report that error differently.
|
||||
*/
|
||||
public final class ComparatorKeyring extends ComparingInvocationHandler<Keyring> {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
private ComparatorKeyring(Keyring original, Keyring second) {
|
||||
super(Keyring.class, original, second);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of Keyring that is an exact proxy of "original".
|
||||
*
|
||||
* <p>This proxy will log any differences in return value or thrown exceptions with "second".
|
||||
*/
|
||||
public static Keyring create(Keyring original, Keyring second) {
|
||||
return new ComparatorKeyring(original, second).makeProxy();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void log(Method method, String message) {
|
||||
logger.atSevere().log("ComparatorKeyring.%s: %s", method.getName(), message);
|
||||
}
|
||||
|
||||
/** Implements equals for the PGP classes. */
|
||||
@Override
|
||||
protected boolean compareResults(Method method, @Nullable Object a, @Nullable Object b) {
|
||||
Class<?> clazz = method.getReturnType();
|
||||
if (PGPPublicKey.class.equals(clazz)) {
|
||||
return compare((PGPPublicKey) a, (PGPPublicKey) b);
|
||||
}
|
||||
if (PGPPrivateKey.class.equals(clazz)) {
|
||||
return compare((PGPPrivateKey) a, (PGPPrivateKey) b);
|
||||
}
|
||||
if (PGPKeyPair.class.equals(clazz)) {
|
||||
return compare((PGPKeyPair) a, (PGPKeyPair) b);
|
||||
}
|
||||
return super.compareResults(method, a, b);
|
||||
}
|
||||
|
||||
/** Implements toString for the PGP classes. */
|
||||
@Override
|
||||
protected String stringifyResult(Method method, @Nullable Object a) {
|
||||
Class<?> clazz = method.getReturnType();
|
||||
if (PGPPublicKey.class.equals(clazz)) {
|
||||
return stringify((PGPPublicKey) a);
|
||||
}
|
||||
if (PGPPrivateKey.class.equals(clazz)) {
|
||||
return stringify((PGPPrivateKey) a);
|
||||
}
|
||||
if (PGPKeyPair.class.equals(clazz)) {
|
||||
return stringify((PGPKeyPair) a);
|
||||
}
|
||||
return super.stringifyResult(method, a);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String stringifyThrown(Method method, Throwable throwable) {
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
PrintWriter printWriter = new PrintWriter(stringWriter);
|
||||
throwable.printStackTrace(printWriter);
|
||||
return String.format("%s\nStack trace:\n%s", throwable.toString(), stringWriter.toString());
|
||||
}
|
||||
|
||||
// .equals implementation for PGP types.
|
||||
|
||||
@VisibleForTesting
|
||||
static boolean compare(@Nullable PGPKeyPair a, @Nullable PGPKeyPair b) {
|
||||
if (a == null || b == null) {
|
||||
return a == null && b == null;
|
||||
}
|
||||
return compare(a.getPublicKey(), b.getPublicKey())
|
||||
&& compare(a.getPrivateKey(), b.getPrivateKey());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static boolean compare(@Nullable PGPPublicKey a, @Nullable PGPPublicKey b) {
|
||||
if (a == null || b == null) {
|
||||
return a == null && b == null;
|
||||
}
|
||||
try {
|
||||
return Arrays.equals(a.getFingerprint(), b.getFingerprint())
|
||||
&& Arrays.equals(a.getEncoded(), b.getEncoded());
|
||||
} catch (IOException e) {
|
||||
logger.atSevere().withCause(e).log(
|
||||
"ComparatorKeyring error: PGPPublicKey.getEncoded failed.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static boolean compare(@Nullable PGPPrivateKey a, @Nullable PGPPrivateKey b) {
|
||||
if (a == null || b == null) {
|
||||
return a == null && b == null;
|
||||
}
|
||||
return a.getKeyID() == b.getKeyID()
|
||||
&& compare(a.getPrivateKeyDataPacket(), b.getPrivateKeyDataPacket())
|
||||
&& compare(a.getPublicKeyPacket(), b.getPublicKeyPacket());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static boolean compare(PublicKeyPacket a, PublicKeyPacket b) {
|
||||
if (a == null || b == null) {
|
||||
return a == null && b == null;
|
||||
}
|
||||
try {
|
||||
return Arrays.equals(a.getEncoded(), b.getEncoded());
|
||||
} catch (IOException e) {
|
||||
logger.atSevere().withCause(e).log(
|
||||
"ComparatorKeyring error: PublicKeyPacket.getEncoded failed.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static boolean compare(BCPGKey a, BCPGKey b) {
|
||||
if (a == null || b == null) {
|
||||
return a == null && b == null;
|
||||
}
|
||||
return Objects.equals(a.getFormat(), b.getFormat())
|
||||
&& Arrays.equals(a.getEncoded(), b.getEncoded());
|
||||
}
|
||||
|
||||
// toString implementations
|
||||
|
||||
@VisibleForTesting
|
||||
static String stringify(PGPKeyPair a) {
|
||||
if (a == null) {
|
||||
return "null";
|
||||
}
|
||||
return MoreObjects.toStringHelper(PGPKeyPair.class)
|
||||
.addValue(stringify(a.getPublicKey()))
|
||||
.addValue(stringify(a.getPrivateKey()))
|
||||
.toString();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static String stringify(PGPPublicKey a) {
|
||||
if (a == null) {
|
||||
return "null";
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (byte b : a.getFingerprint()) {
|
||||
builder.append(String.format("%02x:", b));
|
||||
}
|
||||
return MoreObjects.toStringHelper(PGPPublicKey.class)
|
||||
.add("fingerprint", builder.toString())
|
||||
.toString();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static String stringify(PGPPrivateKey a) {
|
||||
if (a == null) {
|
||||
return "null";
|
||||
}
|
||||
|
||||
// We need to be careful what information we output here. The private key should be private, and
|
||||
// I'm not sure what is safe to put in the logs.
|
||||
return MoreObjects.toStringHelper(PGPPrivateKey.class)
|
||||
.add("keyId", a.getKeyID())
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.keyring.api;
|
||||
|
||||
import static com.google.common.io.Resources.getResource;
|
||||
import static google.registry.keyring.api.PgpHelper.KeyRequirement.ENCRYPT_SIGN;
|
||||
import static google.registry.keyring.api.PgpHelper.lookupKeyPair;
|
||||
|
||||
import com.google.common.base.VerifyException;
|
||||
import com.google.common.io.ByteSource;
|
||||
import com.google.common.io.Resources;
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import dagger.multibindings.StringKey;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Named;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPKeyPair;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPUtil;
|
||||
import org.bouncycastle.openpgp.bc.BcPGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.bc.BcPGPSecretKeyRingCollection;
|
||||
|
||||
/**
|
||||
* Dagger keyring module that provides an {@link InMemoryKeyring} instance populated with dummy
|
||||
* values.
|
||||
*
|
||||
* <p>This dummy module allows the domain registry code to compile and run in an unmodified state,
|
||||
* with all attempted outgoing connections failing because the supplied dummy credentials aren't
|
||||
* valid. For a real system that needs to connect with external services, you should replace this
|
||||
* module with one that loads real credentials from secure sources.
|
||||
*
|
||||
* <p>The dummy PGP keyrings are created using gnupg1/pgp1 roughly like the following (using
|
||||
* gnupg2/pgp2 is an exercise left for the developer):
|
||||
*
|
||||
* <pre>{@code
|
||||
* # mkdir gpg
|
||||
* # chmod 700 gpg
|
||||
* # gpg1 --homedir gpg --gen-key <<<EOF
|
||||
* 1
|
||||
* 1024
|
||||
* 0
|
||||
* Y
|
||||
* Test Registry
|
||||
* test-registry@example.com
|
||||
*
|
||||
* O
|
||||
* EOF
|
||||
* [press enter twice at keyring password prompts]
|
||||
* # gpg1 --homedir gpg -a -o pgp-public-keyring.asc --export test-registry@example.com
|
||||
* # gpg1 --homedir gpg -a -o pgp-private-keyring.asc --export-secret-keys test-registry@example.com
|
||||
* # mv pgp*keyring.asc java/google/registry/keyring/api
|
||||
* # rm -rf gpg
|
||||
* }</pre>
|
||||
*/
|
||||
@Module
|
||||
@Immutable
|
||||
public abstract class DummyKeyringModule {
|
||||
|
||||
public static final String NAME = "Dummy";
|
||||
|
||||
/** The contents of a dummy PGP public key stored in a file. */
|
||||
private static final ByteSource PGP_PUBLIC_KEYRING =
|
||||
Resources.asByteSource(getResource(InMemoryKeyring.class, "pgp-public-keyring.asc"));
|
||||
|
||||
/** The contents of a dummy PGP private key stored in a file. */
|
||||
private static final ByteSource PGP_PRIVATE_KEYRING =
|
||||
Resources.asByteSource(getResource(InMemoryKeyring.class, "pgp-private-keyring.asc"));
|
||||
|
||||
/** The email address of the aforementioned PGP key. */
|
||||
private static final String EMAIL_ADDRESS = "test-registry@example.com";
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@StringKey(NAME)
|
||||
abstract Keyring provideKeyring(@Named("DummyKeyring") InMemoryKeyring keyring);
|
||||
|
||||
/** Always returns a {@link InMemoryKeyring} instance. */
|
||||
@Provides
|
||||
@Named("DummyKeyring")
|
||||
static InMemoryKeyring provideDummyKeyring() {
|
||||
PGPKeyPair dummyKey;
|
||||
try (InputStream publicInput = PGP_PUBLIC_KEYRING.openStream();
|
||||
InputStream privateInput = PGP_PRIVATE_KEYRING.openStream()) {
|
||||
PGPPublicKeyRingCollection publicKeys =
|
||||
new BcPGPPublicKeyRingCollection(PGPUtil.getDecoderStream(publicInput));
|
||||
PGPSecretKeyRingCollection privateKeys =
|
||||
new BcPGPSecretKeyRingCollection(PGPUtil.getDecoderStream(privateInput));
|
||||
dummyKey = lookupKeyPair(publicKeys, privateKeys, EMAIL_ADDRESS, ENCRYPT_SIGN);
|
||||
} catch (PGPException | IOException e) {
|
||||
throw new VerifyException("Failed to load PGP keys from jar", e);
|
||||
}
|
||||
// Use the same dummy PGP keypair for all required PGP keys -- a real production system would
|
||||
// have different values for these keys. Pass dummy values for all Strings.
|
||||
return new InMemoryKeyring(
|
||||
dummyKey,
|
||||
dummyKey,
|
||||
dummyKey.getPublicKey(),
|
||||
dummyKey,
|
||||
dummyKey.getPublicKey(),
|
||||
"not a real key",
|
||||
"not a real key",
|
||||
"not a real password",
|
||||
"not a real API key",
|
||||
"not a real login",
|
||||
"not a real password",
|
||||
"not a real login",
|
||||
"not a real credential",
|
||||
"not a real password",
|
||||
"not a real password",
|
||||
"not the real primary connection",
|
||||
"not the real replica connection");
|
||||
}
|
||||
|
||||
private DummyKeyringModule() {}
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.keyring.api;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import org.bouncycastle.openpgp.PGPKeyPair;
|
||||
import org.bouncycastle.openpgp.PGPPrivateKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
|
||||
/** A {@link Keyring} that uses in-memory values for all credentials. */
|
||||
@Immutable
|
||||
public final class InMemoryKeyring implements Keyring {
|
||||
|
||||
private final PGPKeyPair rdeStagingKey;
|
||||
private final PGPKeyPair rdeSigningKey;
|
||||
private final PGPPublicKey rdeReceiverKey;
|
||||
private final PGPKeyPair brdaSigningKey;
|
||||
private final PGPPublicKey brdaEncryptionKey;
|
||||
private final String rdeSshClientPublicKey;
|
||||
private final String rdeSshClientPrivateKey;
|
||||
private final String icannReportingPassword;
|
||||
private final String safeBrowsingAPIKey;
|
||||
private final String marksdbDnlLoginAndPassword;
|
||||
private final String marksdbLordnPassword;
|
||||
private final String marksdbSmdrlLoginAndPassword;
|
||||
private final String bsaApiKey;
|
||||
private final String sqlPrimaryConnectionName;
|
||||
private final String sqlReplicaConnectionName;
|
||||
|
||||
public InMemoryKeyring(
|
||||
PGPKeyPair rdeStagingKey,
|
||||
PGPKeyPair rdeSigningKey,
|
||||
PGPPublicKey rdeReceiverKey,
|
||||
PGPKeyPair brdaSigningKey,
|
||||
PGPPublicKey brdaEncryptionKey,
|
||||
String rdeSshClientPublicKey,
|
||||
String rdeSshClientPrivateKey,
|
||||
String icannReportingPassword,
|
||||
String safeBrowsingAPIKey,
|
||||
String marksdbDnlLoginAndPassword,
|
||||
String marksdbLordnPassword,
|
||||
String marksdbSmdrlLoginAndPassword,
|
||||
String cloudSqlPassword,
|
||||
String toolsCloudSqlPassword,
|
||||
String bsaApiKey,
|
||||
String sqlPrimaryConnectionName,
|
||||
String sqlReplicaConnectionName) {
|
||||
checkArgument(PgpHelper.isSigningKey(rdeSigningKey.getPublicKey()),
|
||||
"RDE signing key must support signing: %s", rdeSigningKey.getKeyID());
|
||||
checkArgument(rdeStagingKey.getPublicKey().isEncryptionKey(),
|
||||
"staging key must support encryption: %s", rdeStagingKey.getKeyID());
|
||||
checkArgument(rdeReceiverKey.isEncryptionKey(),
|
||||
"receiver key must support encryption: %s", rdeReceiverKey.getKeyID());
|
||||
checkArgument(PgpHelper.isSigningKey(brdaSigningKey.getPublicKey()),
|
||||
"BRDA signing key must support signing: %s", brdaSigningKey.getKeyID());
|
||||
checkArgument(brdaEncryptionKey.isEncryptionKey(),
|
||||
"encryption key must support encryption: %s", brdaEncryptionKey.getKeyID());
|
||||
this.rdeStagingKey = rdeStagingKey;
|
||||
this.rdeSigningKey = rdeSigningKey;
|
||||
this.rdeReceiverKey = rdeReceiverKey;
|
||||
this.brdaSigningKey = brdaSigningKey;
|
||||
this.brdaEncryptionKey = brdaEncryptionKey;
|
||||
this.rdeSshClientPublicKey = checkNotNull(rdeSshClientPublicKey, "rdeSshClientPublicKey");
|
||||
this.rdeSshClientPrivateKey = checkNotNull(rdeSshClientPrivateKey, "rdeSshClientPrivateKey");
|
||||
this.icannReportingPassword = checkNotNull(icannReportingPassword, "icannReportingPassword");
|
||||
this.safeBrowsingAPIKey = checkNotNull(safeBrowsingAPIKey, "safeBrowsingAPIKey");
|
||||
this.marksdbDnlLoginAndPassword =
|
||||
checkNotNull(marksdbDnlLoginAndPassword, "marksdbDnlLoginAndPassword");
|
||||
this.marksdbLordnPassword = checkNotNull(marksdbLordnPassword, "marksdbLordnPassword");
|
||||
this.marksdbSmdrlLoginAndPassword =
|
||||
checkNotNull(marksdbSmdrlLoginAndPassword, "marksdbSmdrlLoginAndPassword");
|
||||
this.bsaApiKey = checkNotNull(bsaApiKey, "bsaApiKey");
|
||||
this.sqlPrimaryConnectionName = sqlPrimaryConnectionName;
|
||||
this.sqlReplicaConnectionName = sqlReplicaConnectionName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PGPKeyPair getRdeSigningKey() {
|
||||
return rdeSigningKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PGPPublicKey getRdeStagingEncryptionKey() {
|
||||
return rdeStagingKey.getPublicKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PGPPrivateKey getRdeStagingDecryptionKey() {
|
||||
return rdeStagingKey.getPrivateKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PGPPublicKey getRdeReceiverKey() {
|
||||
return rdeReceiverKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PGPKeyPair getBrdaSigningKey() {
|
||||
return brdaSigningKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PGPPublicKey getBrdaReceiverKey() {
|
||||
return brdaEncryptionKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRdeSshClientPublicKey() {
|
||||
return rdeSshClientPublicKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRdeSshClientPrivateKey() {
|
||||
return rdeSshClientPrivateKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIcannReportingPassword() {
|
||||
return icannReportingPassword;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSafeBrowsingAPIKey() {
|
||||
return safeBrowsingAPIKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMarksdbDnlLoginAndPassword() {
|
||||
return marksdbDnlLoginAndPassword;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMarksdbLordnPassword() {
|
||||
return marksdbLordnPassword;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMarksdbSmdrlLoginAndPassword() {
|
||||
return marksdbSmdrlLoginAndPassword;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBsaApiKey() {
|
||||
return bsaApiKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSqlPrimaryConnectionName() {
|
||||
return sqlPrimaryConnectionName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSqlReplicaConnectionName() {
|
||||
return sqlReplicaConnectionName;
|
||||
}
|
||||
|
||||
/** Does nothing. */
|
||||
@Override
|
||||
public void close() {}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.keyring.secretmanager;
|
||||
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import dagger.multibindings.StringKey;
|
||||
import google.registry.keyring.api.Keyring;
|
||||
|
||||
/** Dagger module for {@link Keyring} backed by the Cloud SecretManager. */
|
||||
@Module
|
||||
public abstract class SecretManagerKeyringModule {
|
||||
|
||||
public static final String NAME = "CSM";
|
||||
// TODO(b/257276342): Remove after configs in nomulus-internal are updated.
|
||||
public static final String DEPRECATED_NAME = "KMS";
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@StringKey(DEPRECATED_NAME)
|
||||
abstract Keyring provideDeprecatedKeyring(SecretManagerKeyring keyring);
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@StringKey(NAME)
|
||||
abstract Keyring provideKeyring(SecretManagerKeyring keyring);
|
||||
}
|
||||
@@ -35,9 +35,7 @@ import google.registry.groups.GmailModule;
|
||||
import google.registry.groups.GroupsModule;
|
||||
import google.registry.groups.GroupssettingsModule;
|
||||
import google.registry.keyring.KeyringModule;
|
||||
import google.registry.keyring.api.DummyKeyringModule;
|
||||
import google.registry.keyring.api.KeyModule;
|
||||
import google.registry.keyring.secretmanager.SecretManagerKeyringModule;
|
||||
import google.registry.module.RegistryComponent.RegistryModule;
|
||||
import google.registry.module.RequestComponent.RequestComponentModule;
|
||||
import google.registry.monitoring.whitebox.StackdriverModule;
|
||||
@@ -69,7 +67,6 @@ import javax.inject.Singleton;
|
||||
CustomLogicFactoryModule.class,
|
||||
DirectoryModule.class,
|
||||
DriveModule.class,
|
||||
DummyKeyringModule.class,
|
||||
GmailModule.class,
|
||||
GroupsModule.class,
|
||||
GroupssettingsModule.class,
|
||||
@@ -81,7 +78,6 @@ import javax.inject.Singleton;
|
||||
PersistenceModule.class,
|
||||
RegistryModule.class,
|
||||
RequestComponentModule.class,
|
||||
SecretManagerKeyringModule.class,
|
||||
SecretManagerModule.class,
|
||||
ServerTridProviderModule.class,
|
||||
SheetsServiceModule.class,
|
||||
|
||||
@@ -14,7 +14,9 @@
|
||||
|
||||
package google.registry.module;
|
||||
|
||||
import static google.registry.util.GcpJsonFormatter.setCurrentRequest;
|
||||
import static google.registry.util.GcpJsonFormatter.setCurrentTraceId;
|
||||
import static google.registry.util.GcpJsonFormatter.unsetCurrentRequest;
|
||||
import static google.registry.util.RandomStringGenerator.insecureRandomStringGenerator;
|
||||
import static google.registry.util.StringGenerator.Alphabets.HEX_DIGITS_ONLY;
|
||||
|
||||
@@ -67,10 +69,16 @@ public class RegistryServlet extends ServletBase {
|
||||
@Override
|
||||
public void service(HttpServletRequest req, HttpServletResponse rsp) throws IOException {
|
||||
setCurrentTraceId(traceId());
|
||||
String requestMethod = req.getMethod();
|
||||
String requestUrl = req.getRequestURI();
|
||||
String userAgent = String.valueOf(req.getHeader("User-Agent"));
|
||||
String protocol = req.getProtocol();
|
||||
setCurrentRequest(requestMethod, requestUrl, userAgent, protocol);
|
||||
try {
|
||||
super.service(req, rsp);
|
||||
} finally {
|
||||
setCurrentTraceId(null);
|
||||
unsetCurrentRequest();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -121,6 +121,7 @@ import google.registry.ui.server.console.ConsoleUpdateRegistrarAction;
|
||||
import google.registry.ui.server.console.ConsoleUserDataAction;
|
||||
import google.registry.ui.server.console.ConsoleUsersAction;
|
||||
import google.registry.ui.server.console.RegistrarsAction;
|
||||
import google.registry.ui.server.console.domains.ConsoleBulkDomainAction;
|
||||
import google.registry.ui.server.console.settings.ContactAction;
|
||||
import google.registry.ui.server.console.settings.SecurityAction;
|
||||
import google.registry.ui.server.console.settings.WhoisRegistrarFieldsAction;
|
||||
@@ -174,6 +175,8 @@ interface RequestComponent {
|
||||
|
||||
CheckApiAction checkApiAction();
|
||||
|
||||
ConsoleBulkDomainAction consoleBulkDomainAction();
|
||||
|
||||
ConsoleDomainGetAction consoleDomainGetAction();
|
||||
|
||||
ConsoleDomainListAction consoleDomainListAction();
|
||||
|
||||
@@ -32,9 +32,7 @@ import google.registry.groups.GmailModule;
|
||||
import google.registry.groups.GroupsModule;
|
||||
import google.registry.groups.GroupssettingsModule;
|
||||
import google.registry.keyring.KeyringModule;
|
||||
import google.registry.keyring.api.DummyKeyringModule;
|
||||
import google.registry.keyring.api.KeyModule;
|
||||
import google.registry.keyring.secretmanager.SecretManagerKeyringModule;
|
||||
import google.registry.module.backend.BackendRequestComponent.BackendRequestComponentModule;
|
||||
import google.registry.monitoring.whitebox.StackdriverModule;
|
||||
import google.registry.persistence.PersistenceModule;
|
||||
@@ -60,7 +58,6 @@ import javax.inject.Singleton;
|
||||
CredentialModule.class,
|
||||
CustomLogicFactoryModule.class,
|
||||
DirectoryModule.class,
|
||||
DummyKeyringModule.class,
|
||||
DriveModule.class,
|
||||
GmailModule.class,
|
||||
GroupsModule.class,
|
||||
@@ -71,7 +68,6 @@ import javax.inject.Singleton;
|
||||
KeyringModule.class,
|
||||
NetHttpTransportModule.class,
|
||||
PersistenceModule.class,
|
||||
SecretManagerKeyringModule.class,
|
||||
SecretManagerModule.class,
|
||||
ServerTridProviderModule.class,
|
||||
SheetsServiceModule.class,
|
||||
|
||||
@@ -21,7 +21,6 @@ import google.registry.config.CredentialModule;
|
||||
import google.registry.config.RegistryConfig.ConfigModule;
|
||||
import google.registry.groups.GmailModule;
|
||||
import google.registry.keyring.KeyringModule;
|
||||
import google.registry.keyring.secretmanager.SecretManagerKeyringModule;
|
||||
import google.registry.module.bsa.BsaRequestComponent.BsaRequestComponentModule;
|
||||
import google.registry.monitoring.whitebox.StackdriverModule;
|
||||
import google.registry.persistence.PersistenceModule;
|
||||
@@ -43,7 +42,6 @@ import javax.inject.Singleton;
|
||||
GsonModule.class,
|
||||
PersistenceModule.class,
|
||||
KeyringModule.class,
|
||||
SecretManagerKeyringModule.class,
|
||||
SecretManagerModule.class,
|
||||
StackdriverModule.class,
|
||||
UrlConnectionServiceModule.class,
|
||||
|
||||
@@ -27,9 +27,7 @@ import google.registry.groups.GmailModule;
|
||||
import google.registry.groups.GroupsModule;
|
||||
import google.registry.groups.GroupssettingsModule;
|
||||
import google.registry.keyring.KeyringModule;
|
||||
import google.registry.keyring.api.DummyKeyringModule;
|
||||
import google.registry.keyring.api.KeyModule;
|
||||
import google.registry.keyring.secretmanager.SecretManagerKeyringModule;
|
||||
import google.registry.module.frontend.FrontendRequestComponent.FrontendRequestComponentModule;
|
||||
import google.registry.monitoring.whitebox.StackdriverModule;
|
||||
import google.registry.privileges.secretmanager.SecretManagerModule;
|
||||
@@ -52,7 +50,6 @@ import javax.inject.Singleton;
|
||||
CustomLogicFactoryModule.class,
|
||||
CloudTasksUtilsModule.class,
|
||||
DirectoryModule.class,
|
||||
DummyKeyringModule.class,
|
||||
FrontendRequestComponentModule.class,
|
||||
GmailModule.class,
|
||||
GroupsModule.class,
|
||||
@@ -61,7 +58,6 @@ import javax.inject.Singleton;
|
||||
KeyModule.class,
|
||||
KeyringModule.class,
|
||||
NetHttpTransportModule.class,
|
||||
SecretManagerKeyringModule.class,
|
||||
SecretManagerModule.class,
|
||||
ServerTridProviderModule.class,
|
||||
StackdriverModule.class,
|
||||
|
||||
@@ -37,6 +37,7 @@ import google.registry.ui.server.console.ConsoleUpdateRegistrarAction;
|
||||
import google.registry.ui.server.console.ConsoleUserDataAction;
|
||||
import google.registry.ui.server.console.ConsoleUsersAction;
|
||||
import google.registry.ui.server.console.RegistrarsAction;
|
||||
import google.registry.ui.server.console.domains.ConsoleBulkDomainAction;
|
||||
import google.registry.ui.server.console.settings.ContactAction;
|
||||
import google.registry.ui.server.console.settings.SecurityAction;
|
||||
import google.registry.ui.server.console.settings.WhoisRegistrarFieldsAction;
|
||||
@@ -53,6 +54,8 @@ import google.registry.ui.server.console.settings.WhoisRegistrarFieldsAction;
|
||||
WhiteboxModule.class,
|
||||
})
|
||||
public interface FrontendRequestComponent {
|
||||
ConsoleBulkDomainAction consoleBulkDomainAction();
|
||||
|
||||
ConsoleDomainGetAction consoleDomainGetAction();
|
||||
|
||||
ConsoleDomainListAction consoleDomainListAction();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user