mirror of
https://github.com/google/nomulus
synced 2026-06-09 16:33:02 +00:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bd0d8af7b3 | |||
| 2da8ea0185 | |||
| 7a84844000 | |||
| 1580555d30 | |||
| 4fb8a1b50b | |||
| e07f25000d | |||
| cc1777af0c | |||
| 87e54c001f | |||
| 2dc87d42b4 | |||
| 1eed9c82dc | |||
| cf43de7755 | |||
| f54bec7553 | |||
| cf698c2586 | |||
| cb240a8f03 | |||
| 0801679173 | |||
| a87c4a31a3 | |||
| 58c7e3a52c | |||
| dded258864 | |||
| 759143535f | |||
| 46fdf2c996 | |||
| fc1857717d | |||
| e182692a5f |
@@ -41,8 +41,8 @@
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "500kb",
|
||||
"maximumError": "1mb"
|
||||
"maximumWarning": "2mb",
|
||||
"maximumError": "5mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
|
||||
@@ -24,6 +24,10 @@ import SettingsSecurityComponent from './settings/security/security.component';
|
||||
import { RegistrarGuard } from './registrar/registrar.guard';
|
||||
import { RegistrarComponent } from './registrar/registrarsTable.component';
|
||||
import { EmptyRegistrar } from './registrar/emptyRegistrar.component';
|
||||
import ContactComponent from './settings/contact/contact.component';
|
||||
import WhoisComponent from './settings/whois/whois.component';
|
||||
import SecurityComponent from './settings/security/security.component';
|
||||
import UsersComponent from './settings/users/users.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '', redirectTo: '/home', pathMatch: 'full' },
|
||||
@@ -32,7 +36,7 @@ const routes: Routes = [
|
||||
{ path: 'home', component: HomeComponent, canActivate: [RegistrarGuard] },
|
||||
{ path: 'tlds', component: TldsComponent, canActivate: [RegistrarGuard] },
|
||||
{
|
||||
path: 'settings',
|
||||
path: SettingsComponent.PATH,
|
||||
component: SettingsComponent,
|
||||
children: [
|
||||
{
|
||||
@@ -41,32 +45,27 @@ const routes: Routes = [
|
||||
pathMatch: 'full',
|
||||
},
|
||||
{
|
||||
path: 'contact',
|
||||
path: ContactComponent.PATH,
|
||||
component: SettingsContactComponent,
|
||||
canActivate: [RegistrarGuard],
|
||||
},
|
||||
{
|
||||
path: 'whois',
|
||||
path: WhoisComponent.PATH,
|
||||
component: SettingsWhoisComponent,
|
||||
canActivate: [RegistrarGuard],
|
||||
},
|
||||
{
|
||||
path: 'security',
|
||||
path: SecurityComponent.PATH,
|
||||
component: SettingsSecurityComponent,
|
||||
canActivate: [RegistrarGuard],
|
||||
},
|
||||
{
|
||||
path: 'epp-password',
|
||||
component: SettingsSecurityComponent,
|
||||
canActivate: [RegistrarGuard],
|
||||
},
|
||||
{
|
||||
path: 'users',
|
||||
path: UsersComponent.PATH,
|
||||
component: SettingsUsersComponent,
|
||||
canActivate: [RegistrarGuard],
|
||||
},
|
||||
{
|
||||
path: 'registrars',
|
||||
path: RegistrarComponent.PATH,
|
||||
component: RegistrarComponent,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
background-color: transparent;
|
||||
}
|
||||
.active {
|
||||
background: #eae1e1;
|
||||
background-color: var(--secondary);
|
||||
}
|
||||
}
|
||||
&__content-wrapper {
|
||||
|
||||
@@ -12,9 +12,12 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, ViewChild } from '@angular/core';
|
||||
import { RegistrarService } from './registrar/registrar.service';
|
||||
import { UserDataService } from './shared/services/userData.service';
|
||||
import { GlobalLoaderService } from './shared/services/globalLoader.service';
|
||||
import { NavigationEnd, Router } from '@angular/router';
|
||||
import { MatSidenav } from '@angular/material/sidenav';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@@ -23,9 +26,15 @@ import { GlobalLoaderService } from './shared/services/globalLoader.service';
|
||||
})
|
||||
export class AppComponent {
|
||||
renderRouter: boolean = true;
|
||||
|
||||
@ViewChild('sidenav')
|
||||
sidenav!: MatSidenav;
|
||||
|
||||
constructor(
|
||||
protected registrarService: RegistrarService,
|
||||
protected globalLoader: GlobalLoaderService
|
||||
protected userDataService: UserDataService,
|
||||
protected globalLoader: GlobalLoaderService,
|
||||
protected router: Router
|
||||
) {
|
||||
registrarService.activeRegistrarIdChange.subscribe(() => {
|
||||
this.renderRouter = false;
|
||||
@@ -34,4 +43,12 @@ export class AppComponent {
|
||||
}, 400);
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.router.events.subscribe((event) => {
|
||||
if (event instanceof NavigationEnd) {
|
||||
this.sidenav.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,8 @@ import { EppWidgetComponent } from './home/widgets/epp-widget.component';
|
||||
import { BillingWidgetComponent } from './home/widgets/billing-widget.component';
|
||||
import { DomainsWidgetComponent } from './home/widgets/domains-widget.component';
|
||||
import { SettingsWidgetComponent } from './home/widgets/settings-widget.component';
|
||||
import { UserDataService } from './shared/services/userData.service';
|
||||
import WhoisComponent from './settings/whois/whois.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -68,6 +70,7 @@ import { SettingsWidgetComponent } from './home/widgets/settings-widget.componen
|
||||
SettingsWidgetComponent,
|
||||
TldsComponent,
|
||||
TldsWidgetComponent,
|
||||
WhoisComponent,
|
||||
],
|
||||
imports: [
|
||||
AppRoutingModule,
|
||||
@@ -81,6 +84,7 @@ import { SettingsWidgetComponent } from './home/widgets/settings-widget.componen
|
||||
BackendService,
|
||||
GlobalLoaderService,
|
||||
RegistrarGuard,
|
||||
UserDataService,
|
||||
{
|
||||
provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,
|
||||
useValue: {
|
||||
|
||||
@@ -1,24 +1,30 @@
|
||||
<p>
|
||||
<p class="console-app__header">
|
||||
<mat-toolbar color="primary">
|
||||
<button mat-icon-button aria-label="Open menu" (click)="toggleNavPane()">
|
||||
<mat-icon>menu</mat-icon>
|
||||
</button>
|
||||
<span>
|
||||
<a
|
||||
[routerLink]="'/home'"
|
||||
routerLinkActive="active"
|
||||
class="console-app__logo"
|
||||
>
|
||||
Google Registry
|
||||
</a>
|
||||
</span>
|
||||
<a
|
||||
[routerLink]="'/home'"
|
||||
routerLinkActive="active"
|
||||
class="console-app__logo"
|
||||
>
|
||||
Google Registry
|
||||
</a>
|
||||
<span class="spacer"></span>
|
||||
<app-registrar-selector />
|
||||
<button mat-icon-button aria-label="Open FAQ">
|
||||
<mat-icon>question_mark</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button aria-label="Open user info">
|
||||
<button
|
||||
mat-icon-button
|
||||
[matMenuTriggerFor]="menu"
|
||||
#menuTrigger
|
||||
aria-label="Open user info"
|
||||
>
|
||||
<mat-icon>person</mat-icon>
|
||||
</button>
|
||||
<mat-menu #menu="matMenu">
|
||||
<button mat-menu-item (click)="logOut()">Log out</button>
|
||||
</mat-menu>
|
||||
</mat-toolbar>
|
||||
</p>
|
||||
|
||||
@@ -17,6 +17,21 @@
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
&__header {
|
||||
@media (max-width: 599px) {
|
||||
.mat-toolbar {
|
||||
padding: 0;
|
||||
}
|
||||
.console-app__logo {
|
||||
font-size: 16px;
|
||||
}
|
||||
button {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
width: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.spacer {
|
||||
flex: 1;
|
||||
|
||||
@@ -28,4 +28,8 @@ export class HeaderComponent {
|
||||
this.isNavOpen = !this.isNavOpen;
|
||||
this.toggleNavOpen.emit(this.isNavOpen);
|
||||
}
|
||||
|
||||
logOut() {
|
||||
window.open('/console?gcp-iap-mode=CLEAR_LOGIN_COOKIE', '_self');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,4 +29,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (max-width: 510px) {
|
||||
.console-app__widget-wrapper__wide {
|
||||
grid-column: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,13 +13,13 @@
|
||||
Give us a Call
|
||||
</button>
|
||||
<p class="secondary-text">
|
||||
Call Google Registry support at +1 (404) 978 8419
|
||||
Call Google Registry support at <b>+1 (404) 978 8419</b>
|
||||
</p>
|
||||
<button mat-button color="primary" class="console-app__widget-link">
|
||||
Send us an Email
|
||||
</button>
|
||||
<p class="secondary-text">
|
||||
Email Google Registry at support@google.com
|
||||
Email Google Registry at <b>support@google.com</b>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
<mat-card>
|
||||
<mat-card-content>
|
||||
<div class="console-app__widget">
|
||||
<div class="console-app__widget_left">
|
||||
<a
|
||||
class="console-app__widget_left"
|
||||
href="{{ userDataService.userData?.technicalDocsUrl }}"
|
||||
target="_blank"
|
||||
>
|
||||
<mat-icon class="console-app__widget-icon">menu_book</mat-icon>
|
||||
<h1 class="console-app__widget-title">Resources</h1>
|
||||
<h4 class="secondary-text text-center">
|
||||
Use Google Drive to view onboarding FAQs, and technical documentation.
|
||||
</h4>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
@@ -13,11 +13,12 @@
|
||||
// limitations under the License.
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import { UserDataService } from 'src/app/shared/services/userData.service';
|
||||
|
||||
@Component({
|
||||
selector: '[app-resources-widget]',
|
||||
templateUrl: './resources-widget.component.html',
|
||||
})
|
||||
export class ResourcesWidgetComponent {
|
||||
constructor() {}
|
||||
constructor(public userDataService: UserDataService) {}
|
||||
}
|
||||
|
||||
@@ -10,11 +10,21 @@
|
||||
</h4>
|
||||
</div>
|
||||
<div class="console-app__widget_right">
|
||||
<button mat-button color="primary" class="console-app__widget-link">
|
||||
<button
|
||||
mat-button
|
||||
color="primary"
|
||||
class="console-app__widget-link"
|
||||
(click)="openContactsPage()"
|
||||
>
|
||||
Contact Information
|
||||
</button>
|
||||
<p class="secondary-text">Manage Primary, Technical, etc contacts.</p>
|
||||
<button mat-button color="primary" class="console-app__widget-link">
|
||||
<button
|
||||
mat-button
|
||||
color="primary"
|
||||
class="console-app__widget-link"
|
||||
(click)="openSecurityPage()"
|
||||
>
|
||||
Security
|
||||
</button>
|
||||
<p class="secondary-text">
|
||||
@@ -28,7 +38,12 @@
|
||||
User Management
|
||||
</button>
|
||||
<p class="secondary-text">Create and manage console user accounts</p>
|
||||
<button mat-button color="primary" class="console-app__widget-link">
|
||||
<button
|
||||
mat-button
|
||||
color="primary"
|
||||
class="console-app__widget-link"
|
||||
(click)="openRegistrarsPage()"
|
||||
>
|
||||
Registrar Management
|
||||
</button>
|
||||
<p class="secondary-text">Create and manage registrar accounts</p>
|
||||
|
||||
@@ -13,11 +13,32 @@
|
||||
// limitations under the License.
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { RegistrarComponent } from 'src/app/registrar/registrarsTable.component';
|
||||
import ContactComponent from 'src/app/settings/contact/contact.component';
|
||||
import SecurityComponent from 'src/app/settings/security/security.component';
|
||||
import { SettingsComponent } from 'src/app/settings/settings.component';
|
||||
|
||||
@Component({
|
||||
selector: '[app-settings-widget]',
|
||||
templateUrl: './settings-widget.component.html',
|
||||
})
|
||||
export class SettingsWidgetComponent {
|
||||
constructor() {}
|
||||
constructor(private router: Router) {}
|
||||
|
||||
openRegistrarsPage() {
|
||||
this.navigate(RegistrarComponent.PATH);
|
||||
}
|
||||
|
||||
openSecurityPage() {
|
||||
this.navigate(SecurityComponent.PATH);
|
||||
}
|
||||
|
||||
openContactsPage() {
|
||||
this.navigate(ContactComponent.PATH);
|
||||
}
|
||||
|
||||
private navigate(route: string) {
|
||||
this.router.navigate([SettingsComponent.PATH, route]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
white-space: nowrap;
|
||||
|
||||
&-icon {
|
||||
transform: scale(3);
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
<div class="console-app__registrar">
|
||||
<div>
|
||||
<button
|
||||
mat-button
|
||||
[routerLink]="'/settings/registrars'"
|
||||
routerLinkActive="active"
|
||||
*ngIf="isMobile; else desktop"
|
||||
>
|
||||
{{ registrarService.activeRegistrarId || "Select registrar" }}
|
||||
<mat-icon>open_in_new</mat-icon>
|
||||
</button>
|
||||
<ng-template #desktop>
|
||||
<mat-form-field class="mat-form-field-density-5" appearance="fill">
|
||||
<mat-label>Registrar</mat-label>
|
||||
<mat-select
|
||||
@@ -14,5 +23,5 @@
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
@@ -12,14 +12,35 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { RegistrarService } from './registrar.service';
|
||||
import { BreakpointObserver } from '@angular/cdk/layout';
|
||||
import { distinctUntilChanged } from 'rxjs';
|
||||
|
||||
const MOBILE_LAYOUT_BREAKPOINT = '(max-width: 599px)';
|
||||
|
||||
@Component({
|
||||
selector: 'app-registrar-selector',
|
||||
templateUrl: './registrar-selector.component.html',
|
||||
styleUrls: ['./registrar-selector.component.scss'],
|
||||
})
|
||||
export class RegistrarSelectorComponent {
|
||||
constructor(protected registrarService: RegistrarService) {}
|
||||
export class RegistrarSelectorComponent implements OnInit {
|
||||
protected isMobile: boolean = false;
|
||||
|
||||
readonly breakpoint$ = this.breakpointObserver
|
||||
.observe([MOBILE_LAYOUT_BREAKPOINT])
|
||||
.pipe(distinctUntilChanged());
|
||||
|
||||
constructor(
|
||||
protected registrarService: RegistrarService,
|
||||
protected breakpointObserver: BreakpointObserver
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.breakpoint$.subscribe(() => this.breakpointChanged());
|
||||
}
|
||||
|
||||
private breakpointChanged() {
|
||||
this.isMobile = this.breakpointObserver.isMatched(MOBILE_LAYOUT_BREAKPOINT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,11 @@
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
Router,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
|
||||
import { RegistrarService } from './registrar.service';
|
||||
|
||||
@@ -26,13 +30,16 @@ export class RegistrarGuard {
|
||||
private registrarService: RegistrarService
|
||||
) {}
|
||||
|
||||
canActivate(): Promise<boolean> | boolean {
|
||||
canActivate(
|
||||
_: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot
|
||||
): Promise<boolean> | boolean {
|
||||
if (this.registrarService.activeRegistrarId) {
|
||||
return true;
|
||||
}
|
||||
// Get the full URL including any nested children (skip the initial '#/')
|
||||
// NB: an empty nextUrl takes the user to the home page
|
||||
const nextUrl = location.hash.split('#/')[1] || '';
|
||||
return this.router.navigate([`/empty-registrar`, { nextUrl }]);
|
||||
return this.router.navigate([
|
||||
`/empty-registrar`,
|
||||
{ nextUrl: state.url || '' },
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,14 +13,16 @@
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BackendService } from '../shared/services/backend.service';
|
||||
import { Observable, Subject, tap } from 'rxjs';
|
||||
|
||||
import { BackendService } from '../shared/services/backend.service';
|
||||
import {
|
||||
GlobalLoader,
|
||||
GlobalLoaderService,
|
||||
} from '../shared/services/globalLoader.service';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
|
||||
interface Address {
|
||||
export interface Address {
|
||||
street?: string[];
|
||||
city?: string;
|
||||
countryCode?: string;
|
||||
@@ -30,16 +32,20 @@ interface Address {
|
||||
|
||||
export interface Registrar {
|
||||
allowedTlds?: string[];
|
||||
ipAddressAllowList?: string[];
|
||||
emailAddress?: string;
|
||||
billingAccountMap?: object;
|
||||
driveFolderId?: string;
|
||||
emailAddress?: string;
|
||||
faxNumber?: string;
|
||||
ianaIdentifier?: number;
|
||||
icannReferralEmail?: string;
|
||||
ipAddressAllowList?: string[];
|
||||
localizedAddress?: Address;
|
||||
phoneNumber?: string;
|
||||
registrarId: string;
|
||||
registrarName: string;
|
||||
registryLockAllowed?: boolean;
|
||||
url?: string;
|
||||
whoisServer?: string;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
@@ -52,7 +58,8 @@ export class RegistrarService implements GlobalLoader {
|
||||
|
||||
constructor(
|
||||
private backend: BackendService,
|
||||
private globalLoader: GlobalLoaderService
|
||||
private globalLoader: GlobalLoaderService,
|
||||
private _snackBar: MatSnackBar
|
||||
) {
|
||||
this.loadRegistrars().subscribe((r) => {
|
||||
this.globalLoader.stopGlobalLoader(this);
|
||||
@@ -82,6 +89,8 @@ export class RegistrarService implements GlobalLoader {
|
||||
}
|
||||
|
||||
loadingTimeout() {
|
||||
// TODO: Decide what to do when timeout happens
|
||||
this._snackBar.open('Timeout loading registrars', undefined, {
|
||||
duration: 1500,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
<div class="console-app__registrars">
|
||||
<table
|
||||
mat-table
|
||||
[dataSource]="registrarService.registrars"
|
||||
<mat-table
|
||||
[dataSource]="dataSource"
|
||||
class="mat-elevation-z8"
|
||||
class="console-app__registrars-table"
|
||||
matSort
|
||||
>
|
||||
<ng-container
|
||||
*ngFor="let column of columns"
|
||||
[matColumnDef]="column.columnDef"
|
||||
>
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
{{ column.header }}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let row" [innerHTML]="column.cell(row)"></td>
|
||||
<mat-header-cell *matHeaderCellDef> {{ column.header }} </mat-header-cell>
|
||||
<mat-cell *matCellDef="let row" [innerHTML]="column.cell(row)"></mat-cell>
|
||||
</ng-container>
|
||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
|
||||
</mat-table>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
|
||||
</table>
|
||||
<mat-paginator
|
||||
class="mat-elevation-z8"
|
||||
[pageSizeOptions]="[5, 10, 20]"
|
||||
|
||||
@@ -1,5 +1,27 @@
|
||||
.console-app {
|
||||
$min-width: 756px;
|
||||
|
||||
&__registrars {
|
||||
margin-top: 1.5rem;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&__registrars-table {
|
||||
min-width: $min-width !important;
|
||||
}
|
||||
|
||||
.mat-mdc-paginator {
|
||||
min-width: $min-width !important;
|
||||
}
|
||||
|
||||
.mat-column {
|
||||
&-driveId {
|
||||
min-width: 200px;
|
||||
word-break: break-all;
|
||||
}
|
||||
&-registryLockAllowed {
|
||||
max-width: 80px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,15 +12,21 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, ViewChild, ViewEncapsulation } from '@angular/core';
|
||||
import { Registrar, RegistrarService } from './registrar.service';
|
||||
import { MatPaginator } from '@angular/material/paginator';
|
||||
import { MatSort } from '@angular/material/sort';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
|
||||
@Component({
|
||||
selector: 'app-registrar',
|
||||
templateUrl: './registrarsTable.component.html',
|
||||
styleUrls: ['./registrarsTable.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
})
|
||||
export class RegistrarComponent {
|
||||
public static PATH = 'registrars';
|
||||
dataSource: MatTableDataSource<Registrar>;
|
||||
columns = [
|
||||
{
|
||||
columnDef: 'registrarId',
|
||||
@@ -71,5 +77,18 @@ export class RegistrarComponent {
|
||||
},
|
||||
];
|
||||
displayedColumns = this.columns.map((c) => c.columnDef);
|
||||
constructor(protected registrarService: RegistrarService) {}
|
||||
|
||||
@ViewChild(MatPaginator) paginator!: MatPaginator;
|
||||
@ViewChild(MatSort) sort!: MatSort;
|
||||
|
||||
constructor(protected registrarService: RegistrarService) {
|
||||
this.dataSource = new MatTableDataSource<Registrar>(
|
||||
registrarService.registrars
|
||||
);
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.dataSource.paginator = this.paginator;
|
||||
this.dataSource.sort = this.sort;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<h3 mat-dialog-title>Contact details</h3>
|
||||
<div mat-dialog-content>
|
||||
<form (ngSubmit)="saveAndClose($event)">
|
||||
<div>
|
||||
<p>
|
||||
<mat-form-field class="contact-details__input">
|
||||
<mat-label>Name: </mat-label>
|
||||
<input
|
||||
@@ -11,9 +11,9 @@
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<p>
|
||||
<mat-form-field class="contact-details__input">
|
||||
<mat-label>Primary account email: </mat-label>
|
||||
<input
|
||||
@@ -25,9 +25,9 @@
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<p>
|
||||
<mat-form-field class="contact-details__input">
|
||||
<mat-label>Phone: </mat-label>
|
||||
<input
|
||||
@@ -36,9 +36,9 @@
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<p>
|
||||
<mat-form-field class="contact-details__input">
|
||||
<mat-label>Fax: </mat-label>
|
||||
<input
|
||||
@@ -47,7 +47,7 @@
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</p>
|
||||
|
||||
<div class="contact-details__group">
|
||||
<label>Contact type:</label>
|
||||
|
||||
@@ -143,6 +143,8 @@ export class ContactDetailsDialogComponent {
|
||||
styleUrls: ['./contact.component.scss'],
|
||||
})
|
||||
export default class ContactComponent {
|
||||
public static PATH = 'contact';
|
||||
|
||||
loading: boolean = false;
|
||||
constructor(
|
||||
private dialog: MatDialog,
|
||||
|
||||
@@ -29,6 +29,8 @@ import { RegistrarService } from 'src/app/registrar/registrar.service';
|
||||
providers: [SecurityService],
|
||||
})
|
||||
export default class SecurityComponent {
|
||||
public static PATH = 'security';
|
||||
|
||||
loading: boolean = false;
|
||||
inEdit: boolean = false;
|
||||
dataSource: SecuritySettings = {};
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
.console-settings {
|
||||
.mdc-tab {
|
||||
&.active-link {
|
||||
border-bottom: 2px solid #673ab7;
|
||||
border-bottom: 2px solid var(--primary);
|
||||
.mdc-tab__text-label {
|
||||
color: #673ab7;
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,4 +20,6 @@ import { Component, ViewEncapsulation } from '@angular/core';
|
||||
styleUrls: ['./settings.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
})
|
||||
export class SettingsComponent {}
|
||||
export class SettingsComponent {
|
||||
public static PATH = 'settings';
|
||||
}
|
||||
|
||||
@@ -19,4 +19,6 @@ import { Component } from '@angular/core';
|
||||
templateUrl: './users.component.html',
|
||||
styleUrls: ['./users.component.scss'],
|
||||
})
|
||||
export default class UsersComponent {}
|
||||
export default class UsersComponent {
|
||||
public static PATH = 'users';
|
||||
}
|
||||
|
||||
@@ -1 +1,250 @@
|
||||
<p>whois works!</p>
|
||||
<div class="settings-whois">
|
||||
<h2>WHOIS settings</h2>
|
||||
<h3>
|
||||
General registrar information for your WHOIS record. This information is
|
||||
always visible in WHOIS.
|
||||
</h3>
|
||||
<div *ngIf="loading" class="settings-whois__loading">
|
||||
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
|
||||
</div>
|
||||
<div class="settings-whois__section">
|
||||
<div class="settings-whois__section-description">
|
||||
<h3>Name:</h3>
|
||||
</div>
|
||||
<div class="settings-whois__section-form">
|
||||
<mat-form-field>
|
||||
<input
|
||||
matInput
|
||||
type="text"
|
||||
[(ngModel)]="registrar.registrarName"
|
||||
disabled
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-whois__section">
|
||||
<div class="settings-whois__section-description">
|
||||
<h3>IANA Identifier:</h3>
|
||||
</div>
|
||||
<div class="settings-whois__section-form">
|
||||
<mat-form-field>
|
||||
<input
|
||||
matInput
|
||||
type="text"
|
||||
[(ngModel)]="registrar.ianaIdentifier"
|
||||
disabled
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-whois__section">
|
||||
<div class="settings-whois__section-description">
|
||||
<h3>ICANN Referral Email:</h3>
|
||||
</div>
|
||||
<div class="settings-whois__section-form">
|
||||
<mat-form-field>
|
||||
<input
|
||||
matInput
|
||||
type="email"
|
||||
[(ngModel)]="registrar.icannReferralEmail"
|
||||
disabled
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-whois__section">
|
||||
<div class="settings-whois__section-description">
|
||||
<h3>WHOIS server:</h3>
|
||||
</div>
|
||||
<div class="settings-whois__section-form">
|
||||
<mat-form-field>
|
||||
<input
|
||||
matInput
|
||||
type="text"
|
||||
[(ngModel)]="registrar.whoisServer"
|
||||
[disabled]="!inEdit"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-whois__section">
|
||||
<div class="settings-whois__section-description">
|
||||
<h3>Referral URL:</h3>
|
||||
</div>
|
||||
<div class="settings-whois__section-form">
|
||||
<mat-form-field>
|
||||
<input
|
||||
matInput
|
||||
type="text"
|
||||
[(ngModel)]="registrar.url"
|
||||
[disabled]="!inEdit"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-whois__section">
|
||||
<div class="settings-whois__section-description">
|
||||
<h3>Email:</h3>
|
||||
</div>
|
||||
<div class="settings-whois__section-form">
|
||||
<mat-form-field>
|
||||
<input
|
||||
matInput
|
||||
type="email"
|
||||
[(ngModel)]="registrar.emailAddress"
|
||||
[disabled]="!inEdit"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-whois__section">
|
||||
<div class="settings-whois__section-description">
|
||||
<h3>Phone::</h3>
|
||||
</div>
|
||||
<div class="settings-whois__section-form">
|
||||
<mat-form-field>
|
||||
<input
|
||||
matInput
|
||||
type="text"
|
||||
[(ngModel)]="registrar.phoneNumber"
|
||||
[disabled]="!inEdit"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-whois__section">
|
||||
<div class="settings-whois__section-description">
|
||||
<h3>Fax:</h3>
|
||||
</div>
|
||||
<div class="settings-whois__section-form">
|
||||
<mat-form-field>
|
||||
<input
|
||||
matInput
|
||||
type="text"
|
||||
[(ngModel)]="registrar.faxNumber"
|
||||
[disabled]="!inEdit"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-whois__section">
|
||||
<div class="settings-whois__section-address">
|
||||
<div class="settings-whois__section-description">
|
||||
<h3>Address Line 1:</h3>
|
||||
</div>
|
||||
<div class="settings-whois__section-form">
|
||||
<mat-form-field>
|
||||
<input
|
||||
*ngIf="registrar.localizedAddress?.street"
|
||||
matInput
|
||||
type="text"
|
||||
[(ngModel)]="(registrar.localizedAddress?.street)![0]"
|
||||
[disabled]="!inEdit"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-whois__section-address">
|
||||
<div class="settings-whois__section-description">
|
||||
<h3>City:</h3>
|
||||
</div>
|
||||
<div class="settings-whois__section-form">
|
||||
<mat-form-field>
|
||||
<input
|
||||
*ngIf="registrar.localizedAddress"
|
||||
matInput
|
||||
type="text"
|
||||
[(ngModel)]="registrar.localizedAddress.city"
|
||||
[disabled]="!inEdit"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-whois__section">
|
||||
<div class="settings-whois__section-address">
|
||||
<div class="settings-whois__section-description">
|
||||
<h3>Address Line 2:</h3>
|
||||
</div>
|
||||
<div class="settings-whois__section-form">
|
||||
<mat-form-field>
|
||||
<input
|
||||
*ngIf="registrar.localizedAddress?.street"
|
||||
matInput
|
||||
type="text"
|
||||
[(ngModel)]="(registrar.localizedAddress?.street)![1]"
|
||||
[disabled]="!inEdit"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-whois__section-address">
|
||||
<div class="settings-whois__section-description">
|
||||
<h3>State/Region:</h3>
|
||||
</div>
|
||||
<div class="settings-whois__section-form">
|
||||
<mat-form-field>
|
||||
<input
|
||||
*ngIf="registrar.localizedAddress"
|
||||
matInput
|
||||
type="text"
|
||||
[(ngModel)]="registrar.localizedAddress.state"
|
||||
[disabled]="!inEdit"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-whois__section">
|
||||
<div class="settings-whois__section-address">
|
||||
<div class="settings-whois__section-description">
|
||||
<h3>Address Line 3:</h3>
|
||||
</div>
|
||||
<div class="settings-whois__section-form">
|
||||
<mat-form-field>
|
||||
<input
|
||||
*ngIf="registrar.localizedAddress?.street"
|
||||
matInput
|
||||
type="text"
|
||||
[(ngModel)]="(registrar.localizedAddress?.street)![2]"
|
||||
[disabled]="!inEdit"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-whois__section-address">
|
||||
<div class="settings-whois__section-description">
|
||||
<h3>Country Code:</h3>
|
||||
</div>
|
||||
<div class="settings-whois__section-form">
|
||||
<mat-form-field>
|
||||
<input
|
||||
*ngIf="registrar.localizedAddress"
|
||||
matInput
|
||||
type="text"
|
||||
[(ngModel)]="registrar.localizedAddress.countryCode"
|
||||
[disabled]="!inEdit"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-whois__actions">
|
||||
<ng-template [ngIf]="inEdit" [ngIfElse]="inView">
|
||||
<button
|
||||
class="actions-save"
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
(click)="save()"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
<button class="actions-cancel" mat-stroked-button (click)="cancel()">
|
||||
Cancel
|
||||
</button>
|
||||
</ng-template>
|
||||
<ng-template #inView>
|
||||
<button #elseBlock mat-raised-button (click)="enableEdit()">Edit</button>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,3 +11,49 @@
|
||||
// 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.
|
||||
|
||||
.settings-whois {
|
||||
margin-top: 1.5rem;
|
||||
&__section {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 10px;
|
||||
min-width: 400px;
|
||||
}
|
||||
&__section-address {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 5px;
|
||||
min-width: 450px;
|
||||
width: 50%;
|
||||
max-width: 50%;
|
||||
}
|
||||
&__section-description {
|
||||
display: inline-block;
|
||||
margin-block-start: 1em;
|
||||
min-width: 150px;
|
||||
}
|
||||
&__section-form {
|
||||
display: inline-block;
|
||||
width: 70%;
|
||||
mat-form-field {
|
||||
width: 90%;
|
||||
min-width: 300px;
|
||||
}
|
||||
input:disabled {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
&__loading {
|
||||
margin: 2rem 0;
|
||||
}
|
||||
&__actions {
|
||||
margin-top: 50px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-right: 50px;
|
||||
button {
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,11 +12,66 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { Component } from '@angular/core';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import {
|
||||
Registrar,
|
||||
RegistrarService,
|
||||
} from 'src/app/registrar/registrar.service';
|
||||
|
||||
import { WhoisService } from './whois.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-whois',
|
||||
templateUrl: './whois.component.html',
|
||||
styleUrls: ['./whois.component.scss'],
|
||||
providers: [WhoisService],
|
||||
})
|
||||
export default class WhoisComponent {}
|
||||
export default class WhoisComponent {
|
||||
public static PATH = 'whois';
|
||||
loading = false;
|
||||
inEdit = false;
|
||||
registrar: Registrar;
|
||||
|
||||
constructor(
|
||||
public whoisService: WhoisService,
|
||||
public registrarService: RegistrarService,
|
||||
private _snackBar: MatSnackBar
|
||||
) {
|
||||
this.registrar = JSON.parse(
|
||||
JSON.stringify(this.registrarService.registrar)
|
||||
);
|
||||
}
|
||||
|
||||
enableEdit() {
|
||||
this.inEdit = true;
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.inEdit = false;
|
||||
this.resetDataSource();
|
||||
}
|
||||
|
||||
save() {
|
||||
this.loading = true;
|
||||
this.whoisService.saveChanges(this.registrar).subscribe({
|
||||
complete: () => {
|
||||
this.loading = false;
|
||||
this.resetDataSource();
|
||||
},
|
||||
error: (err: HttpErrorResponse) => {
|
||||
this._snackBar.open(err.error, undefined, {
|
||||
duration: 1500,
|
||||
});
|
||||
},
|
||||
});
|
||||
this.cancel();
|
||||
}
|
||||
|
||||
resetDataSource() {
|
||||
this.registrar = JSON.parse(
|
||||
JSON.stringify(this.registrarService.registrar)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright 2023 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 { Injectable } from '@angular/core';
|
||||
import { switchMap } from 'rxjs';
|
||||
import { Address, RegistrarService } from 'src/app/registrar/registrar.service';
|
||||
import { BackendService } from 'src/app/shared/services/backend.service';
|
||||
|
||||
export interface WhoisRegistrarFields {
|
||||
ianaIdentifier?: number;
|
||||
icannReferralEmail?: string;
|
||||
localizedAddress?: Address;
|
||||
registrarId?: string;
|
||||
url?: string;
|
||||
whoisServer?: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class WhoisService {
|
||||
whoisRegistrarFields: WhoisRegistrarFields = {};
|
||||
|
||||
constructor(
|
||||
private backend: BackendService,
|
||||
private registrarService: RegistrarService
|
||||
) {}
|
||||
|
||||
saveChanges(newWhoisRegistrarFields: WhoisRegistrarFields) {
|
||||
return this.backend.postWhoisRegistrarFields(newWhoisRegistrarFields).pipe(
|
||||
switchMap(() => {
|
||||
return this.registrarService.loadRegistrars();
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,8 @@ import { SecuritySettingsBackendModel } from 'src/app/settings/security/security
|
||||
|
||||
import { Contact } from '../../settings/contact/contact.service';
|
||||
import { Registrar } from '../../registrar/registrar.service';
|
||||
import { UserData } from './userData.service';
|
||||
import { WhoisRegistrarFields } from 'src/app/settings/whois/whois.service';
|
||||
|
||||
@Injectable()
|
||||
export class BackendService {
|
||||
@@ -90,4 +92,19 @@ export class BackendService {
|
||||
securitySettings
|
||||
);
|
||||
}
|
||||
|
||||
getUserData(): Observable<UserData> {
|
||||
return this.http
|
||||
.get<UserData>('/console-api/userdata')
|
||||
.pipe(catchError((err) => this.errorCatcher<UserData>(err)));
|
||||
}
|
||||
|
||||
postWhoisRegistrarFields(
|
||||
whoisRegistrarFields: WhoisRegistrarFields
|
||||
): Observable<WhoisRegistrarFields> {
|
||||
return this.http.post<WhoisRegistrarFields>(
|
||||
'/console-api/settings/whois-fields',
|
||||
whoisRegistrarFields
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright 2023 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 { Injectable } from '@angular/core';
|
||||
import { Observable, tap } from 'rxjs';
|
||||
import { BackendService } from './backend.service';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { GlobalLoader, GlobalLoaderService } from './globalLoader.service';
|
||||
|
||||
export interface UserData {
|
||||
isAdmin: boolean;
|
||||
globalRole: string;
|
||||
technicalDocsUrl: string;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class UserDataService implements GlobalLoader {
|
||||
public userData?: UserData;
|
||||
constructor(
|
||||
private backend: BackendService,
|
||||
protected globalLoader: GlobalLoaderService,
|
||||
private _snackBar: MatSnackBar
|
||||
) {
|
||||
this.getUserData().subscribe(() => {
|
||||
this.globalLoader.stopGlobalLoader(this);
|
||||
});
|
||||
this.globalLoader.startGlobalLoader(this);
|
||||
}
|
||||
|
||||
getUserData(): Observable<UserData> {
|
||||
return this.backend.getUserData().pipe(
|
||||
tap((userData: UserData) => {
|
||||
this.userData = userData;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
loadingTimeout() {
|
||||
this._snackBar.open('Timeout loading user data', undefined, {
|
||||
duration: 1500,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -44,16 +44,19 @@ body {
|
||||
&-link {
|
||||
padding: 0 !important;
|
||||
text-align: left;
|
||||
height: 20px !important;
|
||||
min-width: auto !important;
|
||||
height: min-content !important;
|
||||
}
|
||||
&-title {
|
||||
color: var(--primary) !important;
|
||||
text-align: center;
|
||||
}
|
||||
&-icon {
|
||||
font-size: 4rem;
|
||||
line-height: 4rem;
|
||||
height: 4rem !important;
|
||||
width: 4rem !important;
|
||||
color: var(--text);
|
||||
font-size: 5rem;
|
||||
line-height: 5rem;
|
||||
height: 5rem !important;
|
||||
width: 5rem !important;
|
||||
}
|
||||
&_left {
|
||||
flex: 1;
|
||||
|
||||
@@ -17,20 +17,9 @@ $theme-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);
|
||||
// The warn palette is optional (defaults to red).
|
||||
$theme-warn: mat.define-palette(mat.$red-palette);
|
||||
|
||||
// Create the theme object. A theme consists of configurations for individual
|
||||
// theming systems such as "color" or "typography".
|
||||
$theme: mat.define-light-theme(
|
||||
(
|
||||
color: (
|
||||
primary: $theme-primary,
|
||||
accent: $theme-accent,
|
||||
warn: $theme-warn,
|
||||
),
|
||||
density: 0,
|
||||
)
|
||||
);
|
||||
|
||||
/** Application specific section **/
|
||||
/**
|
||||
** Application specific section - Global styles and mixins
|
||||
**/
|
||||
|
||||
@mixin form-field-density($density) {
|
||||
$field-typography: mat.define-typography-config(
|
||||
@@ -46,19 +35,6 @@ $theme: mat.define-light-theme(
|
||||
@include form-field-density(-5);
|
||||
}
|
||||
|
||||
$foreground: map.merge($theme, mat.$light-theme-foreground-palette);
|
||||
|
||||
// Access and define a class with secondary color exposed
|
||||
.secondary-text {
|
||||
color: map.get($foreground, "secondary-text");
|
||||
}
|
||||
|
||||
:root {
|
||||
--primary: #{mat.get-color-from-palette($theme-primary, 500)};
|
||||
--secondary: #{map.get($foreground, "secondary-text")};
|
||||
}
|
||||
|
||||
@include mat.all-component-themes($theme);
|
||||
@import "@angular/material/theming";
|
||||
|
||||
// Define application specific typography settings, font-family, etc
|
||||
@@ -67,3 +43,61 @@ $typography-configuration: mat-typography-config(
|
||||
);
|
||||
|
||||
@include angular-material-typography($typography-configuration);
|
||||
|
||||
/**
|
||||
** Light theme
|
||||
**/
|
||||
$light-theme: mat.define-light-theme(
|
||||
(
|
||||
color: (
|
||||
primary: $theme-primary,
|
||||
accent: $theme-accent,
|
||||
warn: $theme-warn,
|
||||
),
|
||||
density: 0,
|
||||
)
|
||||
);
|
||||
|
||||
// Access and define a class with secondary color exposed
|
||||
.secondary-text {
|
||||
color: map.get(mat.$light-theme-foreground-palette, "secondary-text");
|
||||
}
|
||||
|
||||
:root {
|
||||
--text: #{map.get(mat.$light-theme-foreground-palette, "base")};
|
||||
--primary: #{mat.get-color-from-palette($theme-primary, 500)};
|
||||
--secondary: #{map.get(mat.$light-theme-foreground-palette, "secondary-text")};
|
||||
}
|
||||
|
||||
@include mat.all-component-themes($light-theme);
|
||||
|
||||
/**
|
||||
** Dark theme
|
||||
**/
|
||||
$dark-theme: mat.define-dark-theme(
|
||||
(
|
||||
color: (
|
||||
primary: mat.define-palette(mat.$pink-palette),
|
||||
accent: mat.define-palette(mat.$blue-grey-palette),
|
||||
),
|
||||
density: 0,
|
||||
)
|
||||
);
|
||||
|
||||
@mixin _apply-dark-mode-colors() {
|
||||
@include mat.all-component-colors($dark-theme);
|
||||
|
||||
.secondary-text {
|
||||
color: map.get(mat.$dark-theme-foreground-palette, "secondary-text");
|
||||
}
|
||||
|
||||
:root {
|
||||
--text: #{map.get(mat.$dark-theme-foreground-palette, "base")};
|
||||
--primary: #{mat.get-color-from-palette(mat.$pink-palette, 500)};
|
||||
--secondary: #{map.get(mat.$dark-theme-background-palette, "secondary-text")};
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
@include _apply-dark-mode-colors();
|
||||
}
|
||||
|
||||
@@ -27,6 +27,10 @@ import static google.registry.beam.rde.RdePipeline.TupleTags.REVISION_ID;
|
||||
import static google.registry.beam.rde.RdePipeline.TupleTags.SUPERORDINATE_DOMAINS;
|
||||
import static google.registry.model.reporting.HistoryEntryDao.RESOURCE_TYPES_TO_HISTORY_TYPES;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.SafeSerializationUtils.safeDeserializeCollection;
|
||||
import static google.registry.util.SafeSerializationUtils.serializeCollection;
|
||||
import static google.registry.util.SerializeUtils.decodeBase64;
|
||||
import static google.registry.util.SerializeUtils.encodeBase64;
|
||||
import static org.apache.beam.sdk.values.TypeDescriptors.kvs;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@@ -65,11 +69,7 @@ import google.registry.rde.PendingDeposit.PendingDepositCoder;
|
||||
import google.registry.rde.RdeMarshaller;
|
||||
import google.registry.util.UtilsModule;
|
||||
import google.registry.xml.ValidationMode;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.HashSet;
|
||||
import javax.inject.Inject;
|
||||
@@ -658,14 +658,8 @@ public class RdePipeline implements Serializable {
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
static ImmutableSet<PendingDeposit> decodePendingDeposits(String encodedPendingDeposits) {
|
||||
try (ObjectInputStream ois =
|
||||
new ObjectInputStream(
|
||||
new ByteArrayInputStream(
|
||||
BaseEncoding.base64Url().omitPadding().decode(encodedPendingDeposits)))) {
|
||||
return (ImmutableSet<PendingDeposit>) ois.readObject();
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
throw new IllegalArgumentException("Unable to parse encoded pending deposit map.", e);
|
||||
}
|
||||
return ImmutableSet.copyOf(
|
||||
safeDeserializeCollection(PendingDeposit.class, decodeBase64(encodedPendingDeposits)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -674,12 +668,7 @@ public class RdePipeline implements Serializable {
|
||||
*/
|
||||
public static String encodePendingDeposits(ImmutableSet<PendingDeposit> pendingDeposits)
|
||||
throws IOException {
|
||||
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
|
||||
ObjectOutputStream oos = new ObjectOutputStream(baos);
|
||||
oos.writeObject(pendingDeposits);
|
||||
oos.flush();
|
||||
return BaseEncoding.base64Url().omitPadding().encode(baos.toByteArray());
|
||||
}
|
||||
return encodeBase64(serializeCollection(pendingDeposits));
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException, ClassNotFoundException {
|
||||
|
||||
@@ -879,6 +879,17 @@ public final class RegistryConfig {
|
||||
return Optional.ofNullable(config.misc.sheetExportId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the desired delay between outgoing emails when sending in bulk.
|
||||
*
|
||||
* <p>Gmail apparently has unpublished limits on peak throughput over short period.
|
||||
*/
|
||||
@Provides
|
||||
@Config("emailThrottleDuration")
|
||||
public static Duration provideEmailThrottleSeconds(RegistryConfigSettings config) {
|
||||
return Duration.standardSeconds(config.misc.emailThrottleSeconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the email address we send various alert e-mails to.
|
||||
*
|
||||
|
||||
@@ -208,6 +208,7 @@ public class RegistryConfigSettings {
|
||||
public static class Misc {
|
||||
public String sheetExportId;
|
||||
public boolean isEmailSendingEnabled;
|
||||
public int emailThrottleSeconds;
|
||||
public String alertRecipientEmailAddress;
|
||||
// TODO(b/279671974): remove below field after migration
|
||||
public String newAlertRecipientEmailAddress;
|
||||
|
||||
@@ -443,6 +443,9 @@ misc:
|
||||
# Whether emails may be sent. For Prod and Sandbox this should be true.
|
||||
isEmailSendingEnabled: false
|
||||
|
||||
# Delay between bulk messages to avoid triggering Gmail fraud checks
|
||||
emailThrottleSeconds: 30
|
||||
|
||||
# Address we send alert summary emails to.
|
||||
alertRecipientEmailAddress: email@example.com
|
||||
|
||||
|
||||
@@ -53,8 +53,8 @@ import google.registry.flows.custom.DomainRenewFlowCustomLogic.BeforeResponseRet
|
||||
import google.registry.flows.custom.DomainRenewFlowCustomLogic.BeforeSaveParameters;
|
||||
import google.registry.flows.custom.EntityChanges;
|
||||
import google.registry.flows.domain.token.AllocationTokenFlowUtils;
|
||||
import google.registry.flows.domain.token.AllocationTokenFlowUtils.MissingRemoveDomainTokenOnBulkPricingDomainException;
|
||||
import google.registry.flows.domain.token.AllocationTokenFlowUtils.RemoveDomainTokenOnNonBulkPricingDomainException;
|
||||
import google.registry.flows.domain.token.AllocationTokenFlowUtils.MissingRemoveBulkPricingTokenOnBulkPricingDomainException;
|
||||
import google.registry.flows.domain.token.AllocationTokenFlowUtils.RemoveBulkPricingTokenOnNonBulkPricingDomainException;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.billing.BillingBase.Reason;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
@@ -121,8 +121,8 @@ import org.joda.time.Duration;
|
||||
* @error {@link DomainFlowUtils.RegistrarMustBeActiveForThisOperationException}
|
||||
* @error {@link DomainFlowUtils.UnsupportedFeeAttributeException}
|
||||
* @error {@link DomainRenewFlow.IncorrectCurrentExpirationDateException}
|
||||
* @error {@link MissingRemoveDomainTokenOnBulkPricingDomainException}
|
||||
* @error {@link RemoveDomainTokenOnNonBulkPricingDomainException}
|
||||
* @error {@link MissingRemoveBulkPricingTokenOnBulkPricingDomainException}
|
||||
* @error {@link RemoveBulkPricingTokenOnNonBulkPricingDomainException}
|
||||
* @error {@link
|
||||
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForDomainException}
|
||||
* @error {@link
|
||||
@@ -328,7 +328,7 @@ public final class DomainRenewFlow implements MutatingFlow {
|
||||
checkHasBillingAccount(registrarId, existingDomain.getTld());
|
||||
}
|
||||
verifyUnitIsYears(command.getPeriod());
|
||||
// We only allow __REMOVEDOMAIN__ token on bulk pricing domains for now
|
||||
// We only allow __REMOVE_BULK_PRICING__ token on bulk pricing domains for now
|
||||
verifyTokenAllowedOnDomain(existingDomain, allocationToken);
|
||||
// If the date they specify doesn't match the expiration, fail. (This is an idempotence check).
|
||||
if (!command.getCurrentExpirationDate().equals(
|
||||
|
||||
+14
-14
@@ -243,21 +243,21 @@ public class AllocationTokenFlowUtils {
|
||||
Domain domain, Optional<AllocationToken> allocationToken) throws EppException {
|
||||
|
||||
boolean domainHasBulkToken = domain.getCurrentBulkToken().isPresent();
|
||||
boolean hasRemoveDomainToken =
|
||||
boolean hasRemoveBulkPricingToken =
|
||||
allocationToken.isPresent()
|
||||
&& TokenBehavior.REMOVE_DOMAIN.equals(allocationToken.get().getTokenBehavior());
|
||||
&& TokenBehavior.REMOVE_BULK_PRICING.equals(allocationToken.get().getTokenBehavior());
|
||||
|
||||
if (hasRemoveDomainToken && !domainHasBulkToken) {
|
||||
throw new RemoveDomainTokenOnNonBulkPricingDomainException();
|
||||
} else if (!hasRemoveDomainToken && domainHasBulkToken) {
|
||||
throw new MissingRemoveDomainTokenOnBulkPricingDomainException();
|
||||
if (hasRemoveBulkPricingToken && !domainHasBulkToken) {
|
||||
throw new RemoveBulkPricingTokenOnNonBulkPricingDomainException();
|
||||
} else if (!hasRemoveBulkPricingToken && domainHasBulkToken) {
|
||||
throw new MissingRemoveBulkPricingTokenOnBulkPricingDomainException();
|
||||
}
|
||||
}
|
||||
|
||||
public static Domain maybeApplyBulkPricingRemovalToken(
|
||||
Domain domain, Optional<AllocationToken> allocationToken) {
|
||||
if (!allocationToken.isPresent()
|
||||
|| !TokenBehavior.REMOVE_DOMAIN.equals(allocationToken.get().getTokenBehavior())) {
|
||||
|| !TokenBehavior.REMOVE_BULK_PRICING.equals(allocationToken.get().getTokenBehavior())) {
|
||||
return domain;
|
||||
}
|
||||
|
||||
@@ -338,19 +338,19 @@ public class AllocationTokenFlowUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/** The __REMOVEDOMAIN__ token is missing on a bulk pricing domain command */
|
||||
public static class MissingRemoveDomainTokenOnBulkPricingDomainException
|
||||
/** The __REMOVE_BULK_PRICING__ token is missing on a bulk pricing domain command */
|
||||
public static class MissingRemoveBulkPricingTokenOnBulkPricingDomainException
|
||||
extends AssociationProhibitsOperationException {
|
||||
MissingRemoveDomainTokenOnBulkPricingDomainException() {
|
||||
MissingRemoveBulkPricingTokenOnBulkPricingDomainException() {
|
||||
super("Domains that are inside bulk pricing cannot be explicitly renewed or transferred");
|
||||
}
|
||||
}
|
||||
|
||||
/** The __REMOVEDOMAIN__ token is not allowed on non bulk pricing domains */
|
||||
public static class RemoveDomainTokenOnNonBulkPricingDomainException
|
||||
/** The __REMOVE_BULK_PRICING__ token is not allowed on non bulk pricing domains */
|
||||
public static class RemoveBulkPricingTokenOnNonBulkPricingDomainException
|
||||
extends AssociationProhibitsOperationException {
|
||||
RemoveDomainTokenOnNonBulkPricingDomainException() {
|
||||
super("__REMOVEDOMAIN__ token is not allowed on non bulk pricing domains");
|
||||
RemoveBulkPricingTokenOnNonBulkPricingDomainException() {
|
||||
super("__REMOVE_BULK_PRICING__ token is not allowed on non bulk pricing domains");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
package google.registry.model;
|
||||
|
||||
import static com.google.common.collect.ImmutableSortedMap.toImmutableSortedMap;
|
||||
import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet;
|
||||
import static com.google.common.collect.Ordering.natural;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
@@ -29,6 +30,7 @@ import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
||||
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import google.registry.model.common.TimedTransitionProperty;
|
||||
import google.registry.model.domain.token.AllocationToken;
|
||||
import google.registry.model.tld.Tld.TldState;
|
||||
@@ -39,6 +41,7 @@ import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.SortedMap;
|
||||
import org.joda.money.CurrencyUnit;
|
||||
import org.joda.money.Money;
|
||||
@@ -55,16 +58,67 @@ public class EntityYamlUtils {
|
||||
SimpleModule module = new SimpleModule();
|
||||
module.addSerializer(Money.class, new MoneySerializer());
|
||||
module.addDeserializer(Money.class, new MoneyDeserializer());
|
||||
module.addSerializer(Duration.class, new DurationSerializer());
|
||||
ObjectMapper mapper =
|
||||
JsonMapper.builder(new YAMLFactory().disable(Feature.WRITE_DOC_START_MARKER))
|
||||
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
|
||||
.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY)
|
||||
.build()
|
||||
.registerModule(module);
|
||||
mapper.findAndRegisterModules();
|
||||
.build();
|
||||
mapper.findAndRegisterModules().registerModule(module);
|
||||
return mapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom serializer for String Set to sort the order and make YAML generation deterministic.
|
||||
*/
|
||||
public static class SortedSetSerializer extends StdSerializer<Set<String>> {
|
||||
public SortedSetSerializer() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public SortedSetSerializer(Class<Set<String>> t) {
|
||||
super(t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(Set<String> value, JsonGenerator g, SerializerProvider provider)
|
||||
throws IOException {
|
||||
ImmutableSortedSet<String> sorted =
|
||||
value.stream()
|
||||
.collect(toImmutableSortedSet(String::compareTo)); // sort the entries into a new set
|
||||
g.writeStartArray();
|
||||
for (String entry : sorted) {
|
||||
g.writeString(entry);
|
||||
}
|
||||
g.writeEndArray();
|
||||
}
|
||||
}
|
||||
|
||||
/** A custom serializer for Enum Set to sort the order and make YAML generation deterministic. */
|
||||
public static class SortedEnumSetSerializer extends StdSerializer<Set<Enum>> {
|
||||
public SortedEnumSetSerializer() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public SortedEnumSetSerializer(Class<Set<Enum>> t) {
|
||||
super(t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(Set<Enum> value, JsonGenerator g, SerializerProvider provider)
|
||||
throws IOException {
|
||||
ImmutableSortedSet<String> sorted =
|
||||
value.stream()
|
||||
.map(Enum::name)
|
||||
.collect(toImmutableSortedSet(String::compareTo)); // sort the entries into a new set
|
||||
g.writeStartArray();
|
||||
for (String entry : sorted) {
|
||||
g.writeString(entry);
|
||||
}
|
||||
g.writeEndArray();
|
||||
}
|
||||
}
|
||||
|
||||
/** A custom JSON serializer for {@link Money}. */
|
||||
public static class MoneySerializer extends StdSerializer<Money> {
|
||||
|
||||
@@ -147,6 +201,24 @@ public class EntityYamlUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/** A custom JSON serializer for a {@link Duration} object. */
|
||||
public static class DurationSerializer extends StdSerializer<Duration> {
|
||||
|
||||
public DurationSerializer() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public DurationSerializer(Class<Duration> t) {
|
||||
super(t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(Duration value, JsonGenerator gen, SerializerProvider provider)
|
||||
throws IOException {
|
||||
gen.writeString(value.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/** A custom JSON serializer for an Optional of a {@link Duration} object. */
|
||||
public static class OptionalDurationSerializer extends StdSerializer<Optional<Duration>> {
|
||||
|
||||
@@ -162,7 +234,7 @@ public class EntityYamlUtils {
|
||||
public void serialize(Optional<Duration> value, JsonGenerator gen, SerializerProvider provider)
|
||||
throws IOException {
|
||||
if (value.isPresent()) {
|
||||
gen.writeNumber(value.get().getMillis());
|
||||
gen.writeString(value.get().toString());
|
||||
} else {
|
||||
gen.writeNull();
|
||||
}
|
||||
|
||||
@@ -35,21 +35,16 @@ import javax.persistence.Table;
|
||||
|
||||
/** A console user, either a registry employee or a registrar partner. */
|
||||
@Entity
|
||||
@Table(
|
||||
indexes = {
|
||||
@Index(columnList = "gaiaId", name = "user_gaia_id_idx"),
|
||||
@Index(columnList = "emailAddress", name = "user_email_address_idx")
|
||||
})
|
||||
@Table(indexes = {@Index(columnList = "emailAddress", name = "user_email_address_idx")})
|
||||
public class User extends UpdateAutoTimestampEntity implements Buildable {
|
||||
|
||||
private static final long serialVersionUID = 6936728603828566721L;
|
||||
|
||||
/** Autogenerated unique ID of this user. */
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
/** GAIA ID associated with the user in question. */
|
||||
private String gaiaId;
|
||||
|
||||
/** Email address of the user in question. */
|
||||
@Column(nullable = false)
|
||||
private String emailAddress;
|
||||
@@ -71,10 +66,6 @@ public class User extends UpdateAutoTimestampEntity implements Buildable {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getGaiaId() {
|
||||
return gaiaId;
|
||||
}
|
||||
|
||||
public String getEmailAddress() {
|
||||
return emailAddress;
|
||||
}
|
||||
@@ -139,12 +130,6 @@ public class User extends UpdateAutoTimestampEntity implements Buildable {
|
||||
return super.build();
|
||||
}
|
||||
|
||||
public Builder setGaiaId(String gaiaId) {
|
||||
checkArgument(!isNullOrEmpty(gaiaId), "Gaia ID cannot be null or empty");
|
||||
getInstance().gaiaId = gaiaId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setEmailAddress(String emailAddress) {
|
||||
getInstance().emailAddress = checkValidEmail(emailAddress);
|
||||
return this;
|
||||
|
||||
@@ -77,10 +77,10 @@ import org.joda.time.DateTime;
|
||||
public class AllocationToken extends UpdateAutoTimestampEntity implements Buildable {
|
||||
|
||||
private static final long serialVersionUID = -3954475393220876903L;
|
||||
private static final String REMOVE_DOMAIN = "__REMOVEDOMAIN__";
|
||||
private static final String REMOVE_BULK_PRICING = "__REMOVE_BULK_PRICING__";
|
||||
|
||||
private static final ImmutableMap<String, TokenBehavior> STATIC_TOKEN_BEHAVIORS =
|
||||
ImmutableMap.of(REMOVE_DOMAIN, TokenBehavior.REMOVE_DOMAIN);
|
||||
ImmutableMap.of(REMOVE_BULK_PRICING, TokenBehavior.REMOVE_BULK_PRICING);
|
||||
|
||||
// Promotions should only move forward, and ENDED / CANCELLED are terminal states.
|
||||
private static final ImmutableMultimap<TokenStatus, TokenStatus> VALID_TOKEN_STATUS_TRANSITIONS =
|
||||
@@ -91,10 +91,10 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda
|
||||
|
||||
private static final ImmutableMap<String, AllocationToken> BEHAVIORAL_TOKENS =
|
||||
ImmutableMap.of(
|
||||
REMOVE_DOMAIN,
|
||||
REMOVE_BULK_PRICING,
|
||||
new AllocationToken.Builder()
|
||||
.setTokenType(TokenType.UNLIMITED_USE)
|
||||
.setToken(REMOVE_DOMAIN)
|
||||
.setToken(REMOVE_BULK_PRICING)
|
||||
.build());
|
||||
|
||||
public static Optional<AllocationToken> maybeGetStaticTokenInstance(String name) {
|
||||
@@ -142,10 +142,10 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda
|
||||
/** No special behavior */
|
||||
DEFAULT,
|
||||
/**
|
||||
* REMOVE_DOMAIN triggers domain removal from a bulk pricing package, bypasses DEFAULT token
|
||||
* validations.
|
||||
* REMOVE_BULK_PRICING triggers domain removal from a bulk pricing package, bypasses DEFAULT
|
||||
* token validations.
|
||||
*/
|
||||
REMOVE_DOMAIN
|
||||
REMOVE_BULK_PRICING
|
||||
}
|
||||
|
||||
/** The status of this token with regard to any potential promotion. */
|
||||
|
||||
@@ -19,6 +19,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static com.google.common.collect.Maps.toMap;
|
||||
import static google.registry.config.RegistryConfig.getSingletonCacheRefreshDuration;
|
||||
import static google.registry.model.EntityYamlUtils.createObjectMapper;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
|
||||
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
@@ -28,6 +29,8 @@ import static org.joda.money.CurrencyUnit.USD;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.github.benmanes.caffeine.cache.CacheLoader;
|
||||
@@ -50,6 +53,8 @@ import google.registry.model.EntityYamlUtils.CurrencyDeserializer;
|
||||
import google.registry.model.EntityYamlUtils.CurrencySerializer;
|
||||
import google.registry.model.EntityYamlUtils.OptionalDurationSerializer;
|
||||
import google.registry.model.EntityYamlUtils.OptionalStringSerializer;
|
||||
import google.registry.model.EntityYamlUtils.SortedEnumSetSerializer;
|
||||
import google.registry.model.EntityYamlUtils.SortedSetSerializer;
|
||||
import google.registry.model.EntityYamlUtils.TimedTransitionPropertyMoneyDeserializer;
|
||||
import google.registry.model.EntityYamlUtils.TimedTransitionPropertyTldStateDeserializer;
|
||||
import google.registry.model.EntityYamlUtils.TokenVKeyListDeserializer;
|
||||
@@ -124,6 +129,20 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl
|
||||
public static final Money DEFAULT_SERVER_STATUS_CHANGE_BILLING_COST = Money.of(USD, 20);
|
||||
public static final Money DEFAULT_REGISTRY_LOCK_OR_UNLOCK_BILLING_COST = Money.of(USD, 0);
|
||||
|
||||
public boolean equalYaml(Tld tldToCompare) {
|
||||
if (this.equals(tldToCompare)) {
|
||||
return true;
|
||||
}
|
||||
ObjectMapper mapper = createObjectMapper();
|
||||
try {
|
||||
String thisYaml = mapper.writeValueAsString(this);
|
||||
String otherYaml = mapper.writeValueAsString(tldToCompare);
|
||||
return thisYaml.equals(otherYaml);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/** The type of TLD, which determines things like backups and escrow policy. */
|
||||
public enum TldType {
|
||||
/**
|
||||
@@ -255,6 +274,7 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl
|
||||
* <p>All entries of this list must be valid keys for the map of {@code DnsWriter}s injected by
|
||||
* {@code @Inject Map<String, DnsWriter>}
|
||||
*/
|
||||
@JsonSerialize(using = SortedSetSerializer.class)
|
||||
@Column(nullable = false)
|
||||
Set<String> dnsWriters;
|
||||
|
||||
@@ -354,6 +374,7 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl
|
||||
CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null);
|
||||
|
||||
/** The set of reserved list names that are applicable to this tld. */
|
||||
@JsonSerialize(using = SortedSetSerializer.class)
|
||||
@Column(name = "reserved_list_names")
|
||||
Set<String> reservedListNames;
|
||||
|
||||
@@ -493,10 +514,14 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl
|
||||
DateTime claimsPeriodEnd = END_OF_TIME;
|
||||
|
||||
/** An allowlist of clients allowed to be used on domains on this TLD (ignored if empty). */
|
||||
@Nullable Set<String> allowedRegistrantContactIds;
|
||||
@Nullable
|
||||
@JsonSerialize(using = SortedSetSerializer.class)
|
||||
Set<String> allowedRegistrantContactIds;
|
||||
|
||||
/** An allowlist of hosts allowed to be used on domains on this TLD (ignored if empty). */
|
||||
@Nullable Set<String> allowedFullyQualifiedHostNames;
|
||||
@Nullable
|
||||
@JsonSerialize(using = SortedSetSerializer.class)
|
||||
Set<String> allowedFullyQualifiedHostNames;
|
||||
|
||||
/**
|
||||
* Indicates when the TLD is being modified using locally modified files to override the source
|
||||
@@ -521,6 +546,7 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl
|
||||
List<VKey<AllocationToken>> defaultPromoTokens;
|
||||
|
||||
/** A set of allowed {@link IdnTableEnum}s for this TLD, or empty if we should use the default. */
|
||||
@JsonSerialize(using = SortedEnumSetSerializer.class)
|
||||
Set<IdnTableEnum> idnTables;
|
||||
|
||||
public String getTldStr() {
|
||||
|
||||
@@ -16,6 +16,8 @@ package google.registry.persistence;
|
||||
|
||||
import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
import static google.registry.util.SafeSerializationUtils.safeDeserialize;
|
||||
import static google.registry.util.SerializeUtils.decodeBase64;
|
||||
import static java.util.function.Function.identity;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
@@ -97,7 +99,7 @@ public class VKey<T> extends ImmutableObject implements Serializable {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("\"%s\" missing from the string: %s", LOOKUP_KEY, keyString));
|
||||
}
|
||||
return VKey.create(classType, SerializeUtils.parse(Serializable.class, kvs.get(LOOKUP_KEY)));
|
||||
return VKey.create(classType, safeDeserialize(decodeBase64(kvs.get(LOOKUP_KEY))));
|
||||
}
|
||||
|
||||
/** Returns the type of the entity. */
|
||||
|
||||
@@ -14,18 +14,23 @@
|
||||
|
||||
package google.registry.rde;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import google.registry.model.common.Cursor.CursorType;
|
||||
import google.registry.model.rde.RdeMode;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.ObjectStreamException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Nullable;
|
||||
import org.apache.beam.sdk.coders.AtomicCoder;
|
||||
import org.apache.beam.sdk.coders.BooleanCoder;
|
||||
import org.apache.beam.sdk.coders.NullableCoder;
|
||||
import org.apache.beam.sdk.coders.SerializableCoder;
|
||||
import org.apache.beam.sdk.coders.StringUtf8Coder;
|
||||
import org.apache.beam.sdk.coders.VarIntCoder;
|
||||
import org.joda.time.DateTime;
|
||||
@@ -35,6 +40,12 @@ import org.joda.time.Duration;
|
||||
* Container representing a single RDE or BRDA XML escrow deposit that needs to be created.
|
||||
*
|
||||
* <p>There are some {@code @Nullable} fields here because Optionals aren't Serializable.
|
||||
*
|
||||
* <p>Note that this class is serialized in two ways: by Beam pipelines using custom serialization
|
||||
* mechanism and the {@code Coder} API, and by Java serialization when passed as command-line
|
||||
* arguments (see {@code RdePipeline#decodePendingDeposits}). The latter requires safe
|
||||
* deserialization because the data crosses credential boundaries (See {@code
|
||||
* SafeObjectInputStream}).
|
||||
*/
|
||||
@AutoValue
|
||||
public abstract class PendingDeposit implements Serializable {
|
||||
@@ -95,11 +106,61 @@ public abstract class PendingDeposit implements Serializable {
|
||||
|
||||
PendingDeposit() {}
|
||||
|
||||
/**
|
||||
* Specifies that {@link SerializedForm} be used for {@code SafeObjectInputStream}-compatible
|
||||
* custom-serialization of {@link AutoValue_PendingDeposit the AutoValue implementation class}.
|
||||
*
|
||||
* <p>This method is package-protected so that the AutoValue implementation class inherits this
|
||||
* behavior.
|
||||
*
|
||||
* <p>This method leverages {@link PendingDepositCoder} to serializes an instance. However, it is
|
||||
* not invoked in Beam pipelines.
|
||||
*/
|
||||
Object writeReplace() throws ObjectStreamException {
|
||||
return new SerializedForm(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy for custom-serialization of {@link PendingDeposit}. This is necessary because the actual
|
||||
* class to be (de)serialized is the generated AutoValue implementation. See also {@link
|
||||
* #writeReplace}.
|
||||
*
|
||||
* <p>This class leverages {@link PendingDepositCoder} to safely deserializes an instance.
|
||||
* However, it is not used in Beam pipelines.
|
||||
*/
|
||||
private static class SerializedForm implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 3141095605225904433L;
|
||||
|
||||
private PendingDeposit value;
|
||||
|
||||
private SerializedForm(PendingDeposit value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
private void writeObject(ObjectOutputStream os) throws IOException {
|
||||
checkState(value != null, "Non-null value expected for serialization.");
|
||||
PendingDepositCoder.INSTANCE.encode(value, os);
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream is) throws IOException, ClassNotFoundException {
|
||||
checkState(value == null, "Non-null value unexpected for deserialization.");
|
||||
this.value = PendingDepositCoder.INSTANCE.decode(is);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private Object readResolve() throws ObjectStreamException {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A deterministic coder for {@link PendingDeposit} used during a GroupBy transform.
|
||||
*
|
||||
* <p>We cannot use a {@link SerializableCoder} directly because it does not guarantee
|
||||
* determinism, which is required by GroupBy.
|
||||
* <p>We cannot use a {@code SerializableCoder} directly for two reasons: the default
|
||||
* serialization does not guarantee determinism, which is required by GroupBy in Beam; and the
|
||||
* default deserialization is not robust against deserialization-based attacks (See {@code
|
||||
* SafeObjectInputStream} for more information).
|
||||
*/
|
||||
public static class PendingDepositCoder extends AtomicCoder<PendingDeposit> {
|
||||
|
||||
@@ -117,10 +178,15 @@ public abstract class PendingDeposit implements Serializable {
|
||||
public void encode(PendingDeposit value, OutputStream outStream) throws IOException {
|
||||
BooleanCoder.of().encode(value.manual(), outStream);
|
||||
StringUtf8Coder.of().encode(value.tld(), outStream);
|
||||
SerializableCoder.of(DateTime.class).encode(value.watermark(), outStream);
|
||||
SerializableCoder.of(RdeMode.class).encode(value.mode(), outStream);
|
||||
NullableCoder.of(SerializableCoder.of(CursorType.class)).encode(value.cursor(), outStream);
|
||||
NullableCoder.of(SerializableCoder.of(Duration.class)).encode(value.interval(), outStream);
|
||||
StringUtf8Coder.of().encode(value.watermark().toString(), outStream);
|
||||
StringUtf8Coder.of().encode(value.mode().name(), outStream);
|
||||
NullableCoder.of(StringUtf8Coder.of())
|
||||
.encode(
|
||||
Optional.ofNullable(value.cursor()).map(CursorType::name).orElse(null), outStream);
|
||||
NullableCoder.of(StringUtf8Coder.of())
|
||||
.encode(
|
||||
Optional.ofNullable(value.interval()).map(Duration::toString).orElse(null),
|
||||
outStream);
|
||||
NullableCoder.of(StringUtf8Coder.of()).encode(value.directoryWithTrailingSlash(), outStream);
|
||||
NullableCoder.of(VarIntCoder.of()).encode(value.revision(), outStream);
|
||||
}
|
||||
@@ -130,10 +196,14 @@ public abstract class PendingDeposit implements Serializable {
|
||||
return new AutoValue_PendingDeposit(
|
||||
BooleanCoder.of().decode(inStream),
|
||||
StringUtf8Coder.of().decode(inStream),
|
||||
SerializableCoder.of(DateTime.class).decode(inStream),
|
||||
SerializableCoder.of(RdeMode.class).decode(inStream),
|
||||
NullableCoder.of(SerializableCoder.of(CursorType.class)).decode(inStream),
|
||||
NullableCoder.of(SerializableCoder.of(Duration.class)).decode(inStream),
|
||||
DateTime.parse(StringUtf8Coder.of().decode(inStream)),
|
||||
RdeMode.valueOf(StringUtf8Coder.of().decode(inStream)),
|
||||
Optional.ofNullable(NullableCoder.of(StringUtf8Coder.of()).decode(inStream))
|
||||
.map(CursorType::valueOf)
|
||||
.orElse(null),
|
||||
Optional.ofNullable(NullableCoder.of(StringUtf8Coder.of()).decode(inStream))
|
||||
.map(Duration::parse)
|
||||
.orElse(null),
|
||||
NullableCoder.of(StringUtf8Coder.of()).decode(inStream),
|
||||
NullableCoder.of(VarIntCoder.of()).decode(inStream));
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ public class BillingEmailUtils {
|
||||
GmailClient gmailClient,
|
||||
YearMonth yearMonth,
|
||||
@Config("gSuiteOutgoingEmailAddress") InternetAddress outgoingEmailAddress,
|
||||
@Config("alertRecipientEmailAddress") InternetAddress alertRecipientAddress,
|
||||
@Config("newAlertRecipientEmailAddress") InternetAddress alertRecipientAddress,
|
||||
@Config("invoiceEmailRecipients") ImmutableList<InternetAddress> invoiceEmailRecipients,
|
||||
@Config("invoiceReplyToEmailAddress") Optional<InternetAddress> replyToEmailAddress,
|
||||
@Config("billingBucket") String billingBucket,
|
||||
|
||||
@@ -37,11 +37,13 @@ import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
import google.registry.reporting.spec11.soy.Spec11EmailSoyInfo;
|
||||
import google.registry.util.EmailMessage;
|
||||
import google.registry.util.Sleeper;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.inject.Inject;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
import org.joda.time.Duration;
|
||||
import org.joda.time.LocalDate;
|
||||
|
||||
/** Provides e-mail functionality for Spec11 tasks, such as sending Spec11 reports to registrars. */
|
||||
@@ -57,6 +59,8 @@ public class Spec11EmailUtils {
|
||||
.build()
|
||||
.compileToTofu();
|
||||
private final GmailClient gmailClient;
|
||||
private final Sleeper sleeper;
|
||||
private final Duration emailThrottleDuration;
|
||||
private final InternetAddress outgoingEmailAddress;
|
||||
private final ImmutableList<InternetAddress> spec11BccEmailAddresses;
|
||||
private final InternetAddress alertRecipientAddress;
|
||||
@@ -66,12 +70,16 @@ public class Spec11EmailUtils {
|
||||
@Inject
|
||||
Spec11EmailUtils(
|
||||
GmailClient gmailClient,
|
||||
Sleeper sleeper,
|
||||
@Config("emailThrottleDuration") Duration emailThrottleDuration,
|
||||
@Config("newAlertRecipientEmailAddress") InternetAddress alertRecipientAddress,
|
||||
@Config("spec11OutgoingEmailAddress") InternetAddress spec11OutgoingEmailAddress,
|
||||
@Config("spec11BccEmailAddresses") ImmutableList<InternetAddress> spec11BccEmailAddresses,
|
||||
@Config("spec11WebResources") ImmutableList<String> spec11WebResources,
|
||||
@Config("registryName") String registryName) {
|
||||
this.gmailClient = gmailClient;
|
||||
this.sleeper = sleeper;
|
||||
this.emailThrottleDuration = emailThrottleDuration;
|
||||
this.outgoingEmailAddress = spec11OutgoingEmailAddress;
|
||||
this.spec11BccEmailAddresses = spec11BccEmailAddresses;
|
||||
this.alertRecipientAddress = alertRecipientAddress;
|
||||
@@ -94,6 +102,13 @@ public class Spec11EmailUtils {
|
||||
for (RegistrarThreatMatches registrarThreatMatches : registrarThreatMatchesSet) {
|
||||
RegistrarThreatMatches filteredMatches = filterOutNonPublishedMatches(registrarThreatMatches);
|
||||
if (!filteredMatches.threatMatches().isEmpty()) {
|
||||
if (numRegistrarsEmailed > 0) {
|
||||
try {
|
||||
sleeper.sleep(emailThrottleDuration);
|
||||
} catch (InterruptedException ie) {
|
||||
throw new RuntimeException(ie);
|
||||
}
|
||||
}
|
||||
try {
|
||||
// Handle exceptions individually per registrar so that one failed email doesn't prevent
|
||||
// the rest from being sent.
|
||||
@@ -156,7 +171,7 @@ public class Spec11EmailUtils {
|
||||
gmailClient.sendEmail(
|
||||
EmailMessage.newBuilder()
|
||||
.setSubject(subject)
|
||||
.setBody(getContent(date, soyTemplateInfo, registrarThreatMatches))
|
||||
.setBody(getEmailBody(date, soyTemplateInfo, registrarThreatMatches))
|
||||
.setContentType(MediaType.HTML_UTF_8)
|
||||
.setFrom(outgoingEmailAddress)
|
||||
.addRecipient(getEmailAddressForRegistrar(registrarThreatMatches.clientId()))
|
||||
@@ -164,7 +179,7 @@ public class Spec11EmailUtils {
|
||||
.build());
|
||||
}
|
||||
|
||||
private String getContent(
|
||||
private String getEmailBody(
|
||||
LocalDate date,
|
||||
SoyTemplateInfo soyTemplateInfo,
|
||||
RegistrarThreatMatches registrarThreatMatches) {
|
||||
@@ -175,7 +190,7 @@ public class Spec11EmailUtils {
|
||||
.map(
|
||||
threatMatch ->
|
||||
ImmutableMap.of(
|
||||
"domainName", threatMatch.domainName(),
|
||||
"domainName", toEmailSafeString(threatMatch.domainName()),
|
||||
"threatType", threatMatch.threatType()))
|
||||
.collect(toImmutableList());
|
||||
|
||||
@@ -190,6 +205,12 @@ public class Spec11EmailUtils {
|
||||
return renderer.render();
|
||||
}
|
||||
|
||||
// Mutates a known bad domain to pass spam checks by Email sender and clients, as suggested by
|
||||
// the Gmail abuse-detection team.
|
||||
private String toEmailSafeString(String knownUnsafeDomain) {
|
||||
return knownUnsafeDomain.replace(".", "[.]");
|
||||
}
|
||||
|
||||
/** Sends an e-mail indicating the state of the spec11 pipeline, with a given subject and body. */
|
||||
void sendAlertEmail(String subject, String body) {
|
||||
try {
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.collect.Sets.SetView;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.model.tld.label.PremiumList;
|
||||
import google.registry.model.tld.label.PremiumListDao;
|
||||
@@ -53,6 +54,8 @@ import org.yaml.snakeyaml.Yaml;
|
||||
@Parameters(separators = " =", commandDescription = "Create or update TLD using YAML")
|
||||
public class ConfigureTldCommand extends MutatingCommand {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
@Parameter(
|
||||
names = {"-i", "--input"},
|
||||
description = "Filename of TLD YAML file.",
|
||||
@@ -60,17 +63,28 @@ public class ConfigureTldCommand extends MutatingCommand {
|
||||
required = true)
|
||||
Path inputFile;
|
||||
|
||||
@Parameter(
|
||||
names = {"-b", "--breakglass"},
|
||||
description =
|
||||
"Sets the breakglass field on the TLD to true, preventing Cloud Build from overwriting"
|
||||
+ " these new changes until the TLD configuration file stored internally matches the"
|
||||
+ " configuration in the database.")
|
||||
boolean breakglass;
|
||||
|
||||
@Inject ObjectMapper mapper;
|
||||
|
||||
@Inject
|
||||
@Named("dnsWriterNames")
|
||||
Set<String> validDnsWriterNames;
|
||||
|
||||
// TODO(sarahbot@): Add a breakglass setting to this tool to indicate when a TLD has been modified
|
||||
// outside of source control
|
||||
/** Indicates if the passed in file contains new changes to the TLD */
|
||||
boolean newDiff = true;
|
||||
|
||||
// TODO(sarahbot@): Add a check for diffs between passed in file and current TLD and exit if there
|
||||
// is no diff. Treat nulls and empty sets as the same value.
|
||||
/**
|
||||
* Indicates if the existing TLD is currently in breakglass mode and should not be modified unless
|
||||
* the breakglass flag is used
|
||||
*/
|
||||
boolean oldTldInBreakglass = false;
|
||||
|
||||
@Override
|
||||
protected void init() throws Exception {
|
||||
@@ -80,12 +94,52 @@ public class ConfigureTldCommand extends MutatingCommand {
|
||||
checkForMissingFields(tldData);
|
||||
Tld oldTld = getTlds().contains(name) ? Tld.get(name) : null;
|
||||
Tld newTld = mapper.readValue(inputFile.toFile(), Tld.class);
|
||||
if (oldTld != null) {
|
||||
oldTldInBreakglass = oldTld.getBreakglassMode();
|
||||
newDiff = !oldTld.equalYaml(newTld);
|
||||
}
|
||||
|
||||
if (!newDiff && !oldTldInBreakglass) {
|
||||
// Don't construct a new object if there is no new diff
|
||||
return;
|
||||
}
|
||||
|
||||
if (oldTldInBreakglass && !breakglass) {
|
||||
checkArgument(
|
||||
!newDiff,
|
||||
"Changes can not be applied since TLD is in breakglass mode but the breakglass flag was"
|
||||
+ " not used");
|
||||
// if there are no new diffs, then the YAML file has caught up to the database and the
|
||||
// breakglass mode should be removed
|
||||
logger.atInfo().log("Breakglass mode removed from TLD: %s", name);
|
||||
}
|
||||
|
||||
checkPremiumList(newTld);
|
||||
checkDnsWriters(newTld);
|
||||
checkCurrency(newTld);
|
||||
// Set the new TLD to breakglass mode if breakglass flag was used
|
||||
if (breakglass) {
|
||||
newTld = newTld.asBuilder().setBreakglassMode(true).build();
|
||||
}
|
||||
stageEntityChange(oldTld, newTld);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean dontRunCommand() {
|
||||
if (!newDiff) {
|
||||
if (oldTldInBreakglass && !breakglass) {
|
||||
// Run command to remove breakglass mode
|
||||
return false;
|
||||
}
|
||||
logger.atInfo().log("TLD YAML file contains no new changes");
|
||||
checkArgument(
|
||||
!breakglass || oldTldInBreakglass,
|
||||
"Breakglass mode can only be set when making new changes to a TLD configuration");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void checkName(String name, Map<String, Object> tldData) {
|
||||
checkArgument(CharMatcher.ascii().matchesAllOf(name), "A TLD name must be in plain ASCII");
|
||||
checkArgument(!Character.isDigit(name.charAt(0)), "TLDs cannot begin with a number");
|
||||
@@ -122,7 +176,9 @@ public class ConfigureTldCommand extends MutatingCommand {
|
||||
|
||||
private void checkPremiumList(Tld newTld) {
|
||||
Optional<String> premiumListName = newTld.getPremiumListName();
|
||||
if (!premiumListName.isPresent()) return;
|
||||
if (!premiumListName.isPresent()) {
|
||||
return;
|
||||
}
|
||||
Optional<PremiumList> premiumList = PremiumListDao.getLatestRevision(premiumListName.get());
|
||||
checkArgument(
|
||||
premiumList.isPresent(),
|
||||
|
||||
@@ -15,9 +15,12 @@
|
||||
package google.registry.tools;
|
||||
|
||||
import static google.registry.tools.CommandUtilities.promptForYes;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.google.common.base.Strings;
|
||||
import java.io.PrintStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
/** A {@link Command} that implements a confirmation step before executing. */
|
||||
public abstract class ConfirmingCommand implements Command {
|
||||
@@ -27,23 +30,37 @@ public abstract class ConfirmingCommand implements Command {
|
||||
description = "Do not prompt before executing")
|
||||
boolean force;
|
||||
|
||||
public PrintStream printStream;
|
||||
public PrintStream errorPrintStream;
|
||||
|
||||
protected ConfirmingCommand() {
|
||||
try {
|
||||
printStream = new PrintStream(System.out, false, UTF_8.name());
|
||||
errorPrintStream = new PrintStream(System.err, false, UTF_8.name());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void run() throws Exception {
|
||||
if (checkExecutionState()) {
|
||||
init();
|
||||
printLineIfNotEmpty(prompt());
|
||||
printLineIfNotEmpty(prompt(), printStream);
|
||||
if (dontRunCommand()) {
|
||||
// This typically happens when all of the work is accomplished inside of prompt(), so do
|
||||
// nothing further.
|
||||
return;
|
||||
} else if (force || promptForYes("Perform this command?")) {
|
||||
System.out.println("Running ... ");
|
||||
System.out.println(execute());
|
||||
printLineIfNotEmpty(postExecute());
|
||||
printStream.println("Running ... ");
|
||||
printStream.println(execute());
|
||||
printLineIfNotEmpty(postExecute(), printStream);
|
||||
} else {
|
||||
System.out.println("Command aborted.");
|
||||
printStream.println("Command aborted.");
|
||||
}
|
||||
}
|
||||
printStream.close();
|
||||
errorPrintStream.close();
|
||||
}
|
||||
|
||||
/** Run any pre-execute command checks and return true if they all pass. */
|
||||
@@ -76,9 +93,9 @@ public abstract class ConfirmingCommand implements Command {
|
||||
}
|
||||
|
||||
/** Prints the provided text with a trailing newline, if text is not null or empty. */
|
||||
private static void printLineIfNotEmpty(String text) {
|
||||
private static void printLineIfNotEmpty(String text, PrintStream printStream) {
|
||||
if (!Strings.isNullOrEmpty(text)) {
|
||||
System.out.println(text);
|
||||
printStream.println(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ final class CreateDomainCommand extends CreateOrUpdateDomainCommand {
|
||||
Money createCost = prices.getCreateCost();
|
||||
currency = createCost.getCurrencyUnit().getCode();
|
||||
cost = createCost.multipliedBy(period).getAmount().toString();
|
||||
System.out.printf(
|
||||
printStream.printf(
|
||||
"NOTE: %s is premium at %s per year; sending total cost for %d year(s) of %s %s.\n",
|
||||
domain, createCost, period, currency, cost);
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ import google.registry.tools.params.OptionalStringParameter;
|
||||
import google.registry.tools.params.StringListParameter;
|
||||
import google.registry.tools.params.TransitionListParameter.BillingCostTransitions;
|
||||
import google.registry.tools.params.TransitionListParameter.TldStateTransitions;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -233,12 +234,11 @@ abstract class CreateOrUpdateTldCommand extends MutatingCommand {
|
||||
|
||||
@Nullable
|
||||
@Parameter(
|
||||
names = {"--num_dns_publish_locks"},
|
||||
description =
|
||||
"The number of publish locks we allow in parallel for DNS updates under this tld "
|
||||
+ "(1 for TLD-wide locks)",
|
||||
arity = 1
|
||||
)
|
||||
names = {"--num_dns_publish_locks"},
|
||||
description =
|
||||
"The number of publish locks we allow in parallel for DNS updates under this tld "
|
||||
+ "(1 for TLD-wide locks)",
|
||||
arity = 1)
|
||||
Integer numDnsPublishShards;
|
||||
|
||||
@Nullable
|
||||
@@ -301,7 +301,7 @@ abstract class CreateOrUpdateTldCommand extends MutatingCommand {
|
||||
protected abstract void initTldCommand();
|
||||
|
||||
@Override
|
||||
protected final void init() {
|
||||
protected final void init() throws UnsupportedEncodingException {
|
||||
assertAllowedEnvironment();
|
||||
initTldCommand();
|
||||
String duplicates = Joiner.on(", ").join(findDuplicates(mainParameters));
|
||||
@@ -360,7 +360,7 @@ abstract class CreateOrUpdateTldCommand extends MutatingCommand {
|
||||
if (!renewBillingCostTransitions.isEmpty()) {
|
||||
// TODO(b/20764952): need invoicing support for multiple renew billing costs.
|
||||
if (renewBillingCostTransitions.size() > 1) {
|
||||
System.err.println(
|
||||
errorPrintStream.println(
|
||||
"----------------------\n"
|
||||
+ "WARNING: Do not set multiple renew cost transitions "
|
||||
+ "until b/20764952 is fixed.\n"
|
||||
@@ -463,7 +463,8 @@ abstract class CreateOrUpdateTldCommand extends MutatingCommand {
|
||||
}
|
||||
}
|
||||
|
||||
private void checkReservedListValidityForTld(String tld, Set<String> reservedListNames) {
|
||||
private void checkReservedListValidityForTld(String tld, Set<String> reservedListNames)
|
||||
throws UnsupportedEncodingException {
|
||||
ImmutableList.Builder<String> builder = new ImmutableList.Builder<>();
|
||||
for (String reservedListName : reservedListNames) {
|
||||
if (!reservedListName.startsWith("common_") && !reservedListName.startsWith(tld + "_")) {
|
||||
@@ -476,7 +477,7 @@ abstract class CreateOrUpdateTldCommand extends MutatingCommand {
|
||||
Joiner.on(", ").join(invalidNames),
|
||||
tld);
|
||||
if (overrideReservedListRules) {
|
||||
System.err.println("Error overridden: " + errMsg);
|
||||
errorPrintStream.println("Error overridden: " + errMsg);
|
||||
} else {
|
||||
throw new IllegalArgumentException(errMsg);
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ final class DeleteAllocationTokensCommand extends UpdateOrDeleteAllocationTokens
|
||||
if (!dryRun) {
|
||||
tm().delete(tokensToDelete);
|
||||
}
|
||||
System.out.printf(
|
||||
printStream.printf(
|
||||
"%s tokens: %s\n",
|
||||
dryRun ? "Would delete" : "Deleted",
|
||||
JOINER.join(tokensToDelete.stream().map(VKey::getKey).sorted().collect(toImmutableList())));
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
package google.registry.tools;
|
||||
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
@@ -86,17 +87,17 @@ class LoadTestCommand extends ConfirmingCommand implements CommandWithConnection
|
||||
@Override
|
||||
protected boolean checkExecutionState() {
|
||||
if (RegistryToolEnvironment.get() == RegistryToolEnvironment.PRODUCTION) {
|
||||
System.err.println("You may not run a load test against production.");
|
||||
errorPrintStream.println("You may not run a load test against production.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check validity of TLD and Client Id.
|
||||
if (!Tlds.getTlds().contains(tld)) {
|
||||
System.err.printf("No such TLD: %s\n", tld);
|
||||
errorPrintStream.printf("No such TLD: %s\n", tld);
|
||||
return false;
|
||||
}
|
||||
if (!Registrar.loadByRegistrarId(clientId).isPresent()) {
|
||||
System.err.printf("No such client: %s\n", clientId);
|
||||
errorPrintStream.printf("No such client: %s\n", clientId);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -112,7 +113,7 @@ class LoadTestCommand extends ConfirmingCommand implements CommandWithConnection
|
||||
|
||||
@Override
|
||||
protected String execute() throws Exception {
|
||||
System.err.println("Initiating load test...");
|
||||
errorPrintStream.println("Initiating load test...");
|
||||
|
||||
ImmutableMap<String, Object> params = new ImmutableMap.Builder<String, Object>()
|
||||
.put("tld", tld)
|
||||
|
||||
@@ -71,7 +71,7 @@ public abstract class LockOrUnlockDomainCommand extends ConfirmingCommand {
|
||||
}
|
||||
String duplicates = Joiner.on(", ").join(findDuplicates(mainParameters));
|
||||
checkArgument(duplicates.isEmpty(), "Duplicate domain arguments found: '%s'", duplicates);
|
||||
System.out.println(
|
||||
printStream.println(
|
||||
"== ENSURE THAT YOU HAVE AUTHENTICATED THE REGISTRAR BEFORE RUNNING THIS COMMAND ==");
|
||||
}
|
||||
|
||||
|
||||
@@ -43,34 +43,41 @@ final class RegistryCli implements CommandRunner {
|
||||
// The environment parameter is parsed twice: once here, and once with {@link
|
||||
// RegistryToolEnvironment#parseFromArgs} in the {@link RegistryTool#main} function.
|
||||
//
|
||||
// The flag names must be in sync between the two, and also - this is ugly and we should feel bad.
|
||||
// The flag names must be in sync between the two, and also - this is ugly, and we should feel
|
||||
// bad.
|
||||
@Parameter(
|
||||
names = {"-e", "--environment"},
|
||||
description = "Sets the default environment to run the command.")
|
||||
private RegistryToolEnvironment environment = RegistryToolEnvironment.PRODUCTION;
|
||||
|
||||
@Parameter(
|
||||
names = "--oauth",
|
||||
description =
|
||||
"Turn on OAuth-based authentication, the usage of which is to be deprecated. Use"
|
||||
+ " `create_user` to create an Admin user that allows for OIDC-based authentication.")
|
||||
private boolean oAuth = false;
|
||||
|
||||
@Parameter(
|
||||
names = {"-c", "--commands"},
|
||||
description = "Returns all command names.")
|
||||
private boolean showAllCommands;
|
||||
|
||||
@Parameter(
|
||||
names = {"--credential"},
|
||||
names = "--credential",
|
||||
description =
|
||||
"Name of a JSON file containing credential information used by the tool. "
|
||||
+ "If not set, credentials saved by running `nomulus login' will be used.")
|
||||
private String credentialJson = null;
|
||||
|
||||
@Parameter(
|
||||
names = {"--sql_access_info"},
|
||||
names = "--sql_access_info",
|
||||
description =
|
||||
"Name of a file containing space-separated SQL access info used when deploying "
|
||||
+ "Beam pipelines")
|
||||
private String sqlAccessInfoFile = null;
|
||||
|
||||
// Do not make this final - compile-time constant inlining may interfere with JCommander.
|
||||
@ParametersDelegate
|
||||
private LoggingParameters loggingParams = new LoggingParameters();
|
||||
@ParametersDelegate private LoggingParameters loggingParams = new LoggingParameters();
|
||||
|
||||
RegistryToolComponent component;
|
||||
|
||||
@@ -105,8 +112,8 @@ final class RegistryCli implements CommandRunner {
|
||||
jcommander.setProgramName(programName);
|
||||
|
||||
// Create all command instances. It would be preferrable to do this in the constructor, but
|
||||
// JCommander mutates the command instances and doesn't reset them so we have to do it for every
|
||||
// run.
|
||||
// JCommander mutates the command instances and doesn't reset them, so we have to do it for
|
||||
// every run.
|
||||
try {
|
||||
for (Map.Entry<String, ? extends Class<? extends Command>> entry : commands.entrySet()) {
|
||||
Command command = entry.getValue().getDeclaredConstructor().newInstance();
|
||||
@@ -161,6 +168,7 @@ final class RegistryCli implements CommandRunner {
|
||||
DaggerRegistryToolComponent.builder()
|
||||
.credentialFilePath(credentialJson)
|
||||
.sqlAccessInfoFile(sqlAccessInfoFile)
|
||||
.addOAuthHeader(oAuth)
|
||||
.build();
|
||||
|
||||
// JCommander stores sub-commands as nested JCommander objects containing a list of user objects
|
||||
@@ -169,7 +177,7 @@ final class RegistryCli implements CommandRunner {
|
||||
Command command =
|
||||
(Command)
|
||||
Iterables.getOnlyElement(jcommander.getCommands().get(parsedCommand).getObjects());
|
||||
loggingParams.configureLogging(); // Must be called after parameters are parsed.
|
||||
loggingParams.configureLogging(); // Must be called after parameters are parsed.
|
||||
|
||||
try {
|
||||
runCommand(command);
|
||||
|
||||
@@ -123,6 +123,7 @@ interface RegistryToolComponent {
|
||||
void inject(GetDomainCommand command);
|
||||
|
||||
void inject(GetHostCommand command);
|
||||
|
||||
void inject(GetKeyringSecretCommand command);
|
||||
|
||||
void inject(GetSqlCredentialCommand command);
|
||||
@@ -190,6 +191,9 @@ interface RegistryToolComponent {
|
||||
@BindsInstance
|
||||
Builder sqlAccessInfoFile(@Nullable @Config("sqlAccessInfoFile") String sqlAccessInfoFile);
|
||||
|
||||
@BindsInstance
|
||||
Builder addOAuthHeader(@Config("addOauthHeader") boolean addOauthHeader);
|
||||
|
||||
RegistryToolComponent build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,8 @@ final class RequestFactoryModule {
|
||||
@Provides
|
||||
static HttpRequestFactory provideHttpRequestFactory(
|
||||
@ApplicationDefaultCredential GoogleCredentialsBundle credentialsBundle,
|
||||
@Config("oauthClientId") String oauthClientId) {
|
||||
@Config("oauthClientId") String oauthClientId,
|
||||
@Config("addOauthHeader") boolean addOauthHeader) {
|
||||
if (RegistryConfig.areServersLocal()) {
|
||||
return new NetHttpTransport()
|
||||
.createRequestFactory(
|
||||
@@ -54,8 +55,10 @@ final class RequestFactoryModule {
|
||||
return new NetHttpTransport()
|
||||
.createRequestFactory(
|
||||
request -> {
|
||||
// Use the standard credential initializer to set the Authorization header
|
||||
credentialsBundle.getHttpRequestInitializer().initialize(request);
|
||||
if (addOauthHeader) {
|
||||
// Use the standard credential initializer to set the Authorization header
|
||||
credentialsBundle.getHttpRequestInitializer().initialize(request);
|
||||
}
|
||||
// Set OIDC token as the alternative bearer token.
|
||||
request
|
||||
.getHeaders()
|
||||
|
||||
@@ -42,6 +42,7 @@ import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.reporting.HistoryEntry.Type;
|
||||
import google.registry.util.Clock;
|
||||
import google.registry.util.NonFinalForTesting;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
@@ -76,7 +77,7 @@ class UnrenewDomainCommand extends ConfirmingCommand {
|
||||
StatusValue.SERVER_UPDATE_PROHIBITED);
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
protected void init() throws UnsupportedEncodingException {
|
||||
checkArgument(period >= 1 && period <= 9, "Period must be in the range 1-9");
|
||||
DateTime now = clock.nowUtc();
|
||||
ImmutableSet.Builder<String> domainsNonexistentBuilder = new ImmutableSet.Builder<>();
|
||||
@@ -116,20 +117,24 @@ class UnrenewDomainCommand extends ConfirmingCommand {
|
||||
&& domainsDeleting.isEmpty()
|
||||
&& domainsWithDisallowedStatuses.isEmpty()
|
||||
&& domainsExpiringTooSoon.isEmpty());
|
||||
|
||||
if (foundInvalidDomains) {
|
||||
System.err.print("Found domains that cannot be unrenewed for the following reasons:\n\n");
|
||||
errorPrintStream.print(
|
||||
"Found domains that cannot be unrenewed for the following reasons:\n\n");
|
||||
}
|
||||
if (!domainsNonexistent.isEmpty()) {
|
||||
System.err.printf("Domains that don't exist: %s\n\n", domainsNonexistent);
|
||||
errorPrintStream.printf("Domains that don't exist: %s\n\n", domainsNonexistent);
|
||||
}
|
||||
if (!domainsDeleting.isEmpty()) {
|
||||
System.err.printf("Domains that are deleted or pending delete: %s\n\n", domainsDeleting);
|
||||
errorPrintStream.printf(
|
||||
"Domains that are deleted or pending delete: %s\n\n", domainsDeleting);
|
||||
}
|
||||
if (!domainsWithDisallowedStatuses.isEmpty()) {
|
||||
System.err.printf("Domains with disallowed statuses: %s\n\n", domainsWithDisallowedStatuses);
|
||||
errorPrintStream.printf(
|
||||
"Domains with disallowed statuses: %s\n\n", domainsWithDisallowedStatuses);
|
||||
}
|
||||
if (!domainsExpiringTooSoon.isEmpty()) {
|
||||
System.err.printf("Domains expiring too soon: %s\n\n", domainsExpiringTooSoon);
|
||||
errorPrintStream.printf("Domains expiring too soon: %s\n\n", domainsExpiringTooSoon);
|
||||
}
|
||||
checkArgument(!foundInvalidDomains, "Aborting because some domains cannot be unrenewed");
|
||||
}
|
||||
@@ -154,7 +159,7 @@ class UnrenewDomainCommand extends ConfirmingCommand {
|
||||
protected String execute() {
|
||||
for (String domainName : mainParameters) {
|
||||
tm().transact(() -> unrenewDomain(domainName));
|
||||
System.out.printf("Unrenewed %s\n", domainName);
|
||||
printStream.printf("Unrenewed %s\n", domainName);
|
||||
}
|
||||
return "Successfully unrenewed all domains.";
|
||||
}
|
||||
|
||||
@@ -208,7 +208,7 @@ final class UpdateAllocationTokensCommand extends UpdateOrDeleteAllocationTokens
|
||||
if (!dryRun) {
|
||||
tm().putAll(batch);
|
||||
}
|
||||
System.out.printf(
|
||||
printStream.printf(
|
||||
"%s tokens: %s\n",
|
||||
dryRun ? "Would update" : "Updated",
|
||||
JOINER.join(
|
||||
|
||||
+5
-5
@@ -70,14 +70,14 @@ public class CreateCancellationsForBillingEventsCommand extends ConfirmingComman
|
||||
}
|
||||
});
|
||||
billingEventsToCancel = billingEventsBuilder.build();
|
||||
System.out.printf("Found %d BillingEvent(s) to cancel\n", billingEventsToCancel.size());
|
||||
printStream.printf("Found %d BillingEvent(s) to cancel\n", billingEventsToCancel.size());
|
||||
ImmutableSet<Long> missingIds = missingIdsBuilder.build();
|
||||
if (!missingIds.isEmpty()) {
|
||||
System.out.printf("Missing BillingEvent(s) for IDs %s\n", missingIds);
|
||||
printStream.printf("Missing BillingEvent(s) for IDs %s\n", missingIds);
|
||||
}
|
||||
ImmutableSet<Long> alreadyCancelledIds = alreadyCancelledIdsBuilder.build();
|
||||
if (!alreadyCancelledIds.isEmpty()) {
|
||||
System.out.printf(
|
||||
printStream.printf(
|
||||
"The following BillingEvent IDs were already cancelled: %s\n", alreadyCancelledIds);
|
||||
}
|
||||
}
|
||||
@@ -96,7 +96,7 @@ public class CreateCancellationsForBillingEventsCommand extends ConfirmingComman
|
||||
tm().transact(
|
||||
() -> {
|
||||
if (alreadyCancelled(billingEvent)) {
|
||||
System.out.printf(
|
||||
printStream.printf(
|
||||
"BillingEvent %d already cancelled, this is unexpected.\n",
|
||||
billingEvent.getId());
|
||||
return 0;
|
||||
@@ -111,7 +111,7 @@ public class CreateCancellationsForBillingEventsCommand extends ConfirmingComman
|
||||
.setReason(BillingBase.Reason.ERROR)
|
||||
.setTargetId(billingEvent.getTargetId())
|
||||
.build());
|
||||
System.out.printf(
|
||||
printStream.printf(
|
||||
"Added BillingCancellation for BillingEvent with ID %d\n",
|
||||
billingEvent.getId());
|
||||
return 1;
|
||||
|
||||
@@ -1,105 +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.xjc;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import google.registry.xml.XmlException;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* JAXB element wrapper for java object serialization.
|
||||
*
|
||||
* Instances of {@link JaxbFragment} wrap a non-serializable JAXB element instance, and provide
|
||||
* hooks into the java object serialization process that allow the elements to be safely
|
||||
* marshalled and unmarshalled using {@link ObjectOutputStream} and {@link ObjectInputStream},
|
||||
* respectively.
|
||||
*/
|
||||
public class JaxbFragment<T> implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 5651243983008818813L;
|
||||
|
||||
private T instance;
|
||||
|
||||
/** Stores a JAXB element in a {@link JaxbFragment} */
|
||||
public static <T> JaxbFragment<T> create(T object) {
|
||||
JaxbFragment<T> fragment = new JaxbFragment<>();
|
||||
fragment.instance = object;
|
||||
return fragment;
|
||||
}
|
||||
|
||||
/** Serializes a JAXB element into xml bytes. */
|
||||
private static <T> byte[] freezeInstance(T instance) throws IOException {
|
||||
try {
|
||||
ByteArrayOutputStream bout = new ByteArrayOutputStream();
|
||||
XjcXmlTransformer.marshalLenient(instance, bout, UTF_8);
|
||||
return bout.toByteArray();
|
||||
} catch (XmlException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Deserializes a JAXB element from xml bytes. */
|
||||
private static <T> T unfreezeInstance(byte[] instanceData, Class<T> instanceType)
|
||||
throws IOException {
|
||||
try {
|
||||
ByteArrayInputStream bin = new ByteArrayInputStream(instanceData);
|
||||
return XjcXmlTransformer.unmarshal(instanceType, bin);
|
||||
} catch (XmlException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the JAXB element that is wrapped by this fragment.
|
||||
*/
|
||||
public T getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
try {
|
||||
return new String(freezeInstance(instance), UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeObject(ObjectOutputStream out) throws IOException {
|
||||
// write instanceType, then instanceData
|
||||
out.writeObject(instance.getClass());
|
||||
out.writeObject(freezeInstance(instance));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void readObject(ObjectInputStream in) throws IOException {
|
||||
// read instanceType, then instanceData
|
||||
Class<T> instanceType;
|
||||
byte[] instanceData;
|
||||
try {
|
||||
instanceType = (Class<T>) in.readObject();
|
||||
instanceData = (byte[]) in.readObject();
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
instance = unfreezeInstance(instanceData, instanceType);
|
||||
}
|
||||
}
|
||||
@@ -77,8 +77,8 @@ import google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTok
|
||||
import google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForTldException;
|
||||
import google.registry.flows.domain.token.AllocationTokenFlowUtils.AlreadyRedeemedAllocationTokenException;
|
||||
import google.registry.flows.domain.token.AllocationTokenFlowUtils.InvalidAllocationTokenException;
|
||||
import google.registry.flows.domain.token.AllocationTokenFlowUtils.MissingRemoveDomainTokenOnBulkPricingDomainException;
|
||||
import google.registry.flows.domain.token.AllocationTokenFlowUtils.RemoveDomainTokenOnNonBulkPricingDomainException;
|
||||
import google.registry.flows.domain.token.AllocationTokenFlowUtils.MissingRemoveBulkPricingTokenOnBulkPricingDomainException;
|
||||
import google.registry.flows.domain.token.AllocationTokenFlowUtils.RemoveBulkPricingTokenOnNonBulkPricingDomainException;
|
||||
import google.registry.flows.exceptions.ResourceStatusProhibitsOperationException;
|
||||
import google.registry.model.billing.BillingBase.Flag;
|
||||
import google.registry.model.billing.BillingBase.Reason;
|
||||
@@ -1276,12 +1276,13 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
|
||||
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2", "TOKEN", "token"));
|
||||
|
||||
EppException thrown =
|
||||
assertThrows(MissingRemoveDomainTokenOnBulkPricingDomainException.class, this::runFlow);
|
||||
assertThrows(
|
||||
MissingRemoveBulkPricingTokenOnBulkPricingDomainException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailsToRenewBulkPricingDomainNoRemoveDomainToken() throws Exception {
|
||||
void testFailsToRenewBulkPricingDomainNoRemoveBulkPricingToken() throws Exception {
|
||||
AllocationToken token =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
@@ -1299,25 +1300,26 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
|
||||
setEppInput("domain_renew.xml", ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "5"));
|
||||
|
||||
EppException thrown =
|
||||
assertThrows(MissingRemoveDomainTokenOnBulkPricingDomainException.class, this::runFlow);
|
||||
assertThrows(
|
||||
MissingRemoveBulkPricingTokenOnBulkPricingDomainException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailsToRenewNonBulkPricingDomainWithRemoveDomainToken() throws Exception {
|
||||
void testFailsToRenewNonBulkPricingDomainWithRemoveBulkPricingToken() throws Exception {
|
||||
persistDomain();
|
||||
|
||||
setEppInput(
|
||||
"domain_renew_allocationtoken.xml",
|
||||
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2", "TOKEN", "__REMOVEDOMAIN__"));
|
||||
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2", "TOKEN", "__REMOVE_BULK_PRICING__"));
|
||||
|
||||
EppException thrown =
|
||||
assertThrows(RemoveDomainTokenOnNonBulkPricingDomainException.class, this::runFlow);
|
||||
assertThrows(RemoveBulkPricingTokenOnNonBulkPricingDomainException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccesfullyAppliesRemoveDomainToken() throws Exception {
|
||||
void testSuccesfullyAppliesRemoveBulkPricingToken() throws Exception {
|
||||
AllocationToken token =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
@@ -1333,7 +1335,7 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
|
||||
reloadResourceByForeignKey().asBuilder().setCurrentBulkToken(token.createVKey()).build());
|
||||
setEppInput(
|
||||
"domain_renew_allocationtoken.xml",
|
||||
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2", "TOKEN", "__REMOVEDOMAIN__"));
|
||||
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2", "TOKEN", "__REMOVE_BULK_PRICING__"));
|
||||
|
||||
doSuccessfulTest(
|
||||
"domain_renew_response.xml",
|
||||
@@ -1347,7 +1349,7 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDryRunRemoveDomainToken() throws Exception {
|
||||
void testDryRunRemoveBulkPricingToken() throws Exception {
|
||||
AllocationToken token =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
@@ -1364,7 +1366,7 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
|
||||
|
||||
setEppInput(
|
||||
"domain_renew_allocationtoken.xml",
|
||||
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2", "TOKEN", "__REMOVEDOMAIN__"));
|
||||
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2", "TOKEN", "__REMOVE_BULK_PRICING__"));
|
||||
|
||||
dryRunFlowAssertResponse(
|
||||
loadFile(
|
||||
|
||||
@@ -34,7 +34,7 @@ import com.google.api.client.http.HttpTransport;
|
||||
import com.google.api.client.http.LowLevelHttpRequest;
|
||||
import com.google.api.client.http.LowLevelHttpResponse;
|
||||
import com.google.api.client.json.Json;
|
||||
import com.google.api.client.json.jackson2.JacksonFactory;
|
||||
import com.google.api.client.json.gson.GsonFactory;
|
||||
import com.google.api.client.testing.http.HttpTesting;
|
||||
import com.google.api.client.testing.http.MockHttpTransport;
|
||||
import com.google.api.client.testing.http.MockLowLevelHttpRequest;
|
||||
@@ -300,6 +300,6 @@ class DirectoryGroupsConnectionTest {
|
||||
HttpRequest request = transport.createRequestFactory()
|
||||
.buildGetRequest(HttpTesting.SIMPLE_GENERIC_URL)
|
||||
.setThrowExceptionOnExecuteError(false);
|
||||
return GoogleJsonResponseException.from(new JacksonFactory(), request.execute());
|
||||
return GoogleJsonResponseException.from(new GsonFactory(), request.execute());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,13 +30,11 @@ public class UserDaoTest extends EntityTestCase {
|
||||
User user1 =
|
||||
new User.Builder()
|
||||
.setEmailAddress("email@email.com")
|
||||
.setGaiaId("gaiaId")
|
||||
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.SUPPORT_AGENT).build())
|
||||
.build();
|
||||
User user2 =
|
||||
new User.Builder()
|
||||
.setEmailAddress("foo@bar.com")
|
||||
.setGaiaId("otherId")
|
||||
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.SUPPORT_AGENT).build())
|
||||
.build();
|
||||
UserDao.saveUser(user1);
|
||||
@@ -54,7 +52,6 @@ public class UserDaoTest extends EntityTestCase {
|
||||
User user =
|
||||
new User.Builder()
|
||||
.setEmailAddress("email@email.com")
|
||||
.setGaiaId("gaiaId")
|
||||
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.SUPPORT_AGENT).build())
|
||||
.build();
|
||||
UserDao.saveUser(user);
|
||||
@@ -71,13 +68,11 @@ public class UserDaoTest extends EntityTestCase {
|
||||
User user1 =
|
||||
new User.Builder()
|
||||
.setEmailAddress("email@email.com")
|
||||
.setGaiaId("gaiaId")
|
||||
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.SUPPORT_AGENT).build())
|
||||
.build();
|
||||
User user2 =
|
||||
new User.Builder()
|
||||
.setEmailAddress("email@email.com")
|
||||
.setGaiaId("otherId")
|
||||
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.SUPPORT_AGENT).build())
|
||||
.build();
|
||||
UserDao.saveUser(user1);
|
||||
|
||||
@@ -30,10 +30,9 @@ public class UserTest extends EntityTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPersistence_lookupByGaiaId() {
|
||||
void testPersistence_lookupByEmail() {
|
||||
User user =
|
||||
new User.Builder()
|
||||
.setGaiaId("gaiaId")
|
||||
.setEmailAddress("email@email.com")
|
||||
.setUserRoles(
|
||||
new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).setIsAdmin(true).build())
|
||||
@@ -43,10 +42,11 @@ public class UserTest extends EntityTestCase {
|
||||
() -> {
|
||||
assertAboutImmutableObjects()
|
||||
.that(
|
||||
tm().query("FROM User WHERE gaiaId = 'gaiaId'", User.class).getSingleResult())
|
||||
tm().query("FROM User WHERE emailAddress = 'email@email.com'", User.class)
|
||||
.getSingleResult())
|
||||
.isEqualExceptFields(user, "id", "updateTimestamp");
|
||||
assertThat(
|
||||
tm().query("FROM User WHERE gaiaId = 'badGaiaId'", User.class)
|
||||
tm().query("FROM User WHERE emailAddress = 'nobody@email.com'", User.class)
|
||||
.getResultList())
|
||||
.isEmpty();
|
||||
});
|
||||
@@ -55,9 +55,6 @@ public class UserTest extends EntityTestCase {
|
||||
@Test
|
||||
void testFailure_badInputs() {
|
||||
User.Builder builder = new User.Builder();
|
||||
assertThat(assertThrows(IllegalArgumentException.class, () -> builder.setGaiaId(null)))
|
||||
.hasMessageThat()
|
||||
.isEqualTo("Gaia ID cannot be null or empty");
|
||||
assertThat(assertThrows(IllegalArgumentException.class, () -> builder.setEmailAddress("")))
|
||||
.hasMessageThat()
|
||||
.isEqualTo("Provided email is not a valid email address");
|
||||
@@ -72,7 +69,7 @@ public class UserTest extends EntityTestCase {
|
||||
assertThat(assertThrows(IllegalArgumentException.class, () -> builder.setUserRoles(null)))
|
||||
.hasMessageThat()
|
||||
.isEqualTo("User roles cannot be null");
|
||||
|
||||
|
||||
assertThat(assertThrows(IllegalArgumentException.class, builder::build))
|
||||
.hasMessageThat()
|
||||
.isEqualTo("Email address cannot be null");
|
||||
@@ -99,7 +96,6 @@ public class UserTest extends EntityTestCase {
|
||||
|
||||
User user =
|
||||
new User.Builder()
|
||||
.setGaiaId("gaiaId")
|
||||
.setEmailAddress("email@email.com")
|
||||
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build())
|
||||
.build();
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright 2023 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.rde;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.common.Cursor.CursorType.RDE_STAGING;
|
||||
import static google.registry.model.rde.RdeMode.FULL;
|
||||
import static google.registry.util.SafeSerializationUtils.safeDeserialize;
|
||||
import static google.registry.util.SerializeUtils.deserialize;
|
||||
import static google.registry.util.SerializeUtils.serialize;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Duration;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Unit tests for {@link PendingDeposit}. */
|
||||
public class PendingDepositTest {
|
||||
private final DateTime now = DateTime.parse("2000-01-01TZ");
|
||||
|
||||
PendingDeposit pendingDeposit =
|
||||
PendingDeposit.create("soy", now, FULL, RDE_STAGING, Duration.standardDays(1));
|
||||
|
||||
PendingDeposit manualPendingDeposit =
|
||||
PendingDeposit.createInManualOperation("soy", now, FULL, "/", null);
|
||||
|
||||
@Test
|
||||
void deserialize_normalDeposit_success() {
|
||||
assertThat(deserialize(PendingDeposit.class, serialize(pendingDeposit)))
|
||||
.isEqualTo(pendingDeposit);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserialize_manualDeposit_success() {
|
||||
assertThat(deserialize(PendingDeposit.class, serialize(manualPendingDeposit)))
|
||||
.isEqualTo(manualPendingDeposit);
|
||||
}
|
||||
|
||||
@Test
|
||||
void safeDeserialize_normalDeposit_success() {
|
||||
assertThat(safeDeserialize(serialize(pendingDeposit))).isEqualTo(pendingDeposit);
|
||||
}
|
||||
|
||||
@Test
|
||||
void safeDeserialize_manualDeposit_success() {
|
||||
assertThat(safeDeserialize(serialize(manualPendingDeposit))).isEqualTo(manualPendingDeposit);
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,8 @@ import static google.registry.testing.DatabaseHelper.loadByEntity;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveHost;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.same;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@@ -41,11 +43,13 @@ import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationT
|
||||
import google.registry.reporting.spec11.soy.Spec11EmailSoyInfo;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
import google.registry.util.EmailMessage;
|
||||
import google.registry.util.Sleeper;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
import org.joda.time.Duration;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -101,6 +105,8 @@ class Spec11EmailUtilsTest {
|
||||
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
|
||||
|
||||
@Mock private GmailClient gmailClient;
|
||||
@Mock private Sleeper sleeper;
|
||||
private Duration emailThrottleDuration = Duration.millis(1);
|
||||
private Spec11EmailUtils emailUtils;
|
||||
private ArgumentCaptor<EmailMessage> contentCaptor;
|
||||
private final LocalDate date = new LocalDate(2018, 7, 15);
|
||||
@@ -114,6 +120,8 @@ class Spec11EmailUtilsTest {
|
||||
emailUtils =
|
||||
new Spec11EmailUtils(
|
||||
gmailClient,
|
||||
sleeper,
|
||||
emailThrottleDuration,
|
||||
new InternetAddress("my-receiver@test.com"),
|
||||
new InternetAddress("abuse@test.com"),
|
||||
ImmutableList.of(
|
||||
@@ -128,6 +136,19 @@ class Spec11EmailUtilsTest {
|
||||
persistDomainWithHost("c.com", host);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_sleepsBetweenSending() throws Exception {
|
||||
emailUtils.emailSpec11Reports(
|
||||
date,
|
||||
Spec11EmailSoyInfo.MONTHLY_SPEC_11_EMAIL,
|
||||
"Super Cool Registry Monthly Threat Detector [2018-07-15]",
|
||||
sampleThreatMatches());
|
||||
// We inspect individual parameters because Message doesn't implement equals().
|
||||
verify(gmailClient, times(3)).sendEmail(any(EmailMessage.class));
|
||||
// Sleep once between two reports sent in a tight loop. No sleep before the final alert message.
|
||||
verify(sleeper, times(1)).sleep(same(emailThrottleDuration));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_emailMonthlySpec11Reports() throws Exception {
|
||||
emailUtils.emailSpec11Reports(
|
||||
@@ -144,7 +165,7 @@ class Spec11EmailUtilsTest {
|
||||
"the.registrar@example.com",
|
||||
ImmutableList.of("abuse@test.com", "bcc@test.com"),
|
||||
"Super Cool Registry Monthly Threat Detector [2018-07-15]",
|
||||
String.format(MONTHLY_EMAIL_FORMAT, "<tr><td>a.com</td><td>MALWARE</td></tr>"),
|
||||
String.format(MONTHLY_EMAIL_FORMAT, "<tr><td>a[.]com</td><td>MALWARE</td></tr>"),
|
||||
Optional.of(MediaType.HTML_UTF_8));
|
||||
validateMessage(
|
||||
capturedContents.get(1),
|
||||
@@ -154,7 +175,7 @@ class Spec11EmailUtilsTest {
|
||||
"Super Cool Registry Monthly Threat Detector [2018-07-15]",
|
||||
String.format(
|
||||
MONTHLY_EMAIL_FORMAT,
|
||||
"<tr><td>b.com</td><td>MALWARE</td></tr><tr><td>c.com</td><td>MALWARE</td></tr>"),
|
||||
"<tr><td>b[.]com</td><td>MALWARE</td></tr><tr><td>c[.]com</td><td>MALWARE</td></tr>"),
|
||||
Optional.of(MediaType.HTML_UTF_8));
|
||||
validateMessage(
|
||||
capturedContents.get(2),
|
||||
@@ -182,7 +203,7 @@ class Spec11EmailUtilsTest {
|
||||
"the.registrar@example.com",
|
||||
ImmutableList.of("abuse@test.com", "bcc@test.com"),
|
||||
"Super Cool Registry Daily Threat Detector [2018-07-15]",
|
||||
String.format(DAILY_EMAIL_FORMAT, "<tr><td>a.com</td><td>MALWARE</td></tr>"),
|
||||
String.format(DAILY_EMAIL_FORMAT, "<tr><td>a[.]com</td><td>MALWARE</td></tr>"),
|
||||
Optional.of(MediaType.HTML_UTF_8));
|
||||
validateMessage(
|
||||
capturedMessages.get(1),
|
||||
@@ -192,7 +213,7 @@ class Spec11EmailUtilsTest {
|
||||
"Super Cool Registry Daily Threat Detector [2018-07-15]",
|
||||
String.format(
|
||||
DAILY_EMAIL_FORMAT,
|
||||
"<tr><td>b.com</td><td>MALWARE</td></tr><tr><td>c.com</td><td>MALWARE</td></tr>"),
|
||||
"<tr><td>b[.]com</td><td>MALWARE</td></tr><tr><td>c[.]com</td><td>MALWARE</td></tr>"),
|
||||
Optional.of(MediaType.HTML_UTF_8));
|
||||
validateMessage(
|
||||
capturedMessages.get(2),
|
||||
@@ -223,7 +244,7 @@ class Spec11EmailUtilsTest {
|
||||
"new.registrar@example.com",
|
||||
ImmutableList.of("abuse@test.com", "bcc@test.com"),
|
||||
"Super Cool Registry Monthly Threat Detector [2018-07-15]",
|
||||
String.format(MONTHLY_EMAIL_FORMAT, "<tr><td>c.com</td><td>MALWARE</td></tr>"),
|
||||
String.format(MONTHLY_EMAIL_FORMAT, "<tr><td>c[.]com</td><td>MALWARE</td></tr>"),
|
||||
Optional.of(MediaType.HTML_UTF_8));
|
||||
validateMessage(
|
||||
capturedContents.get(1),
|
||||
@@ -256,7 +277,7 @@ class Spec11EmailUtilsTest {
|
||||
"the.registrar@example.com",
|
||||
ImmutableList.of("abuse@test.com", "bcc@test.com"),
|
||||
"Super Cool Registry Monthly Threat Detector [2018-07-15]",
|
||||
String.format(MONTHLY_EMAIL_FORMAT, "<tr><td>a.com</td><td>MALWARE</td></tr>"),
|
||||
String.format(MONTHLY_EMAIL_FORMAT, "<tr><td>a[.]com</td><td>MALWARE</td></tr>"),
|
||||
Optional.of(MediaType.HTML_UTF_8));
|
||||
validateMessage(
|
||||
capturedContents.get(1),
|
||||
@@ -266,7 +287,7 @@ class Spec11EmailUtilsTest {
|
||||
"Super Cool Registry Monthly Threat Detector [2018-07-15]",
|
||||
String.format(
|
||||
MONTHLY_EMAIL_FORMAT,
|
||||
"<tr><td>b.com</td><td>MALWARE</td></tr><tr><td>c.com</td><td>MALWARE</td></tr>"),
|
||||
"<tr><td>b[.]com</td><td>MALWARE</td></tr><tr><td>c[.]com</td><td>MALWARE</td></tr>"),
|
||||
Optional.of(MediaType.HTML_UTF_8));
|
||||
validateMessage(
|
||||
capturedContents.get(2),
|
||||
@@ -311,7 +332,7 @@ class Spec11EmailUtilsTest {
|
||||
"the.registrar@example.com",
|
||||
ImmutableList.of("abuse@test.com", "bcc@test.com"),
|
||||
"Super Cool Registry Monthly Threat Detector [2018-07-15]",
|
||||
String.format(MONTHLY_EMAIL_FORMAT, "<tr><td>a.com</td><td>MALWARE</td></tr>"),
|
||||
String.format(MONTHLY_EMAIL_FORMAT, "<tr><td>a[.]com</td><td>MALWARE</td></tr>"),
|
||||
Optional.of(MediaType.HTML_UTF_8));
|
||||
validateMessage(
|
||||
capturedMessages.get(1),
|
||||
@@ -321,7 +342,7 @@ class Spec11EmailUtilsTest {
|
||||
"Super Cool Registry Monthly Threat Detector [2018-07-15]",
|
||||
String.format(
|
||||
MONTHLY_EMAIL_FORMAT,
|
||||
"<tr><td>b.com</td><td>MALWARE</td></tr><tr><td>c.com</td><td>MALWARE</td></tr>"),
|
||||
"<tr><td>b[.]com</td><td>MALWARE</td></tr><tr><td>c[.]com</td><td>MALWARE</td></tr>"),
|
||||
Optional.of(MediaType.HTML_UTF_8));
|
||||
validateMessage(
|
||||
capturedMessages.get(2),
|
||||
|
||||
-3
@@ -417,7 +417,6 @@ class AuthenticatedRegistrarAccessorTest {
|
||||
void testConsoleUser_admin() {
|
||||
google.registry.model.console.User consoleUser =
|
||||
new google.registry.model.console.User.Builder()
|
||||
.setGaiaId("gaiaId")
|
||||
.setEmailAddress("email@email.com")
|
||||
.setUserRoles(
|
||||
new UserRoles.Builder().setIsAdmin(true).setGlobalRole(GlobalRole.FTE).build())
|
||||
@@ -444,7 +443,6 @@ class AuthenticatedRegistrarAccessorTest {
|
||||
// not admins
|
||||
google.registry.model.console.User consoleUser =
|
||||
new google.registry.model.console.User.Builder()
|
||||
.setGaiaId("gaiaId")
|
||||
.setEmailAddress("email@email.com")
|
||||
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.SUPPORT_AGENT).build())
|
||||
.build();
|
||||
@@ -462,7 +460,6 @@ class AuthenticatedRegistrarAccessorTest {
|
||||
// Registrar employees should have OWNER access to their registrars
|
||||
google.registry.model.console.User consoleUser =
|
||||
new google.registry.model.console.User.Builder()
|
||||
.setGaiaId("gaiaId")
|
||||
.setEmailAddress("email@email.com")
|
||||
.setUserRoles(
|
||||
new UserRoles.Builder()
|
||||
|
||||
-2
@@ -61,7 +61,6 @@ public class OidcTokenAuthenticationMechanismTest {
|
||||
private final User user =
|
||||
new User.Builder()
|
||||
.setEmailAddress(email)
|
||||
.setGaiaId(gaiaId)
|
||||
.setUserRoles(
|
||||
new UserRoles.Builder().setIsAdmin(true).setGlobalRole(GlobalRole.FTE).build())
|
||||
.build();
|
||||
@@ -141,7 +140,6 @@ public class OidcTokenAuthenticationMechanismTest {
|
||||
User serviceUser =
|
||||
new User.Builder()
|
||||
.setEmailAddress("service@email.test")
|
||||
.setGaiaId("service-gaia-id")
|
||||
.setUserRoles(
|
||||
new UserRoles.Builder().setIsAdmin(true).setGlobalRole(GlobalRole.FTE).build())
|
||||
.build();
|
||||
|
||||
@@ -61,7 +61,7 @@ import org.junit.runner.RunWith;
|
||||
* and have at least one test method that persists a JPA entity declared in persistence.xml.
|
||||
*
|
||||
* <p>Note that with {@link JpaIntegrationWithCoverageExtension}, each method starts with an empty
|
||||
* database. Therefore this is not the right place for verifying backwards data compatibility in
|
||||
* database. Therefore, this is not the right place for verifying backwards data compatibility in
|
||||
* end-to-end functional tests.
|
||||
*
|
||||
* <p>As of April 2020, none of the before/after annotations ({@code BeforeClass} and {@code
|
||||
@@ -107,7 +107,9 @@ import org.junit.runner.RunWith;
|
||||
// AfterSuiteTest must be the last entry. See class javadoc for details.
|
||||
AfterSuiteTest.class
|
||||
})
|
||||
public class SqlIntegrationTestSuite {
|
||||
public final class SqlIntegrationTestSuite {
|
||||
|
||||
private SqlIntegrationTestSuite() {}
|
||||
|
||||
@BeforeAll // Not yet supported in JUnit 5. Called through BeforeSuiteTest.
|
||||
public static void initJpaEntityCoverage() {
|
||||
|
||||
@@ -144,7 +144,6 @@ public final class RegistryTestServerMain {
|
||||
User user =
|
||||
new User.Builder()
|
||||
.setEmailAddress(loginEmail)
|
||||
.setGaiaId("123457890")
|
||||
.setUserRoles(userRoles)
|
||||
.setRegistryLockPassword("registryLockPassword")
|
||||
.build();
|
||||
|
||||
@@ -26,7 +26,7 @@ import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
|
||||
import com.google.api.client.http.GenericUrl;
|
||||
import com.google.api.client.http.HttpRequest;
|
||||
import com.google.api.client.http.javanet.NetHttpTransport;
|
||||
import com.google.api.client.json.jackson2.JacksonFactory;
|
||||
import com.google.api.client.json.gson.GsonFactory;
|
||||
import com.google.api.client.util.store.AbstractDataStoreFactory;
|
||||
import com.google.api.client.util.store.DataStore;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@@ -74,7 +74,7 @@ class AuthModuleTest {
|
||||
// We need to set the following fields because they are checked when
|
||||
// Credential#setRefreshToken is called. However they are not actually persisted in the
|
||||
// DataStore and not actually used in tests.
|
||||
.setJsonFactory(new JacksonFactory())
|
||||
.setJsonFactory(new GsonFactory())
|
||||
.setTransport(new NetHttpTransport())
|
||||
.setTokenServerUrl(new GenericUrl("https://accounts.google.com/o/oauth2/token"))
|
||||
.setClientAuthentication(new ClientParametersAuthentication(CLIENT_ID, CLIENT_SECRET))
|
||||
@@ -146,7 +146,7 @@ class AuthModuleTest {
|
||||
private Credential getCredential() {
|
||||
// Reconstruct the entire dependency graph, injecting FakeDataStoreFactory and credential
|
||||
// parameters.
|
||||
JacksonFactory jsonFactory = new JacksonFactory();
|
||||
GsonFactory jsonFactory = new GsonFactory();
|
||||
GoogleClientSecrets clientSecrets = getSecrets();
|
||||
ImmutableList<String> scopes = ImmutableList.of("scope1");
|
||||
return AuthModule.provideCredential(
|
||||
|
||||
@@ -198,7 +198,7 @@ public abstract class CommandTestCase<C extends Command> {
|
||||
}
|
||||
|
||||
void assertInStderr(String... expected) {
|
||||
String stderror = new String(stderr.toByteArray(), UTF_8);
|
||||
String stderror = getStderrAsString();
|
||||
for (String line : expected) {
|
||||
assertThat(stderror).contains(line);
|
||||
}
|
||||
|
||||
@@ -14,14 +14,18 @@
|
||||
package google.registry.tools;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.EntityYamlUtils.createObjectMapper;
|
||||
import static google.registry.model.domain.token.AllocationToken.TokenType.DEFAULT_PROMO;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.persistPremiumList;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.LogsSubject.assertAboutLogs;
|
||||
import static google.registry.testing.TestDataHelper.loadFile;
|
||||
import static google.registry.tldconfig.idn.IdnTableEnum.EXTENDED_LATIN;
|
||||
import static google.registry.tldconfig.idn.IdnTableEnum.JA;
|
||||
import static google.registry.tldconfig.idn.IdnTableEnum.UNCONFUSABLE_LATIN;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static org.joda.money.CurrencyUnit.JPY;
|
||||
import static org.joda.money.CurrencyUnit.USD;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
@@ -34,12 +38,13 @@ import com.google.common.base.Ascii;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.io.Files;
|
||||
import google.registry.model.EntityYamlUtils;
|
||||
import com.google.common.testing.TestLogHandler;
|
||||
import google.registry.model.domain.token.AllocationToken;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.model.tld.label.PremiumList;
|
||||
import google.registry.model.tld.label.PremiumListDao;
|
||||
import java.io.File;
|
||||
import java.util.logging.Logger;
|
||||
import org.joda.money.Money;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
@@ -50,13 +55,16 @@ import org.testcontainers.shaded.com.google.common.collect.ImmutableMap;
|
||||
public class ConfigureTldCommandTest extends CommandTestCase<ConfigureTldCommand> {
|
||||
|
||||
PremiumList premiumList;
|
||||
ObjectMapper objectMapper = EntityYamlUtils.createObjectMapper();
|
||||
ObjectMapper objectMapper = createObjectMapper();
|
||||
private final TestLogHandler logHandler = new TestLogHandler();
|
||||
private final Logger logger = Logger.getLogger(ConfigureTldCommand.class.getCanonicalName());
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
command.mapper = objectMapper;
|
||||
premiumList = persistPremiumList("test", USD, "silver,USD 50", "gold,USD 80");
|
||||
command.validDnsWriterNames = ImmutableSet.of("VoidDnsWriter", "FooDnsWriter");
|
||||
logger.addHandler(logHandler);
|
||||
}
|
||||
|
||||
private void testTldConfiguredSuccessfully(Tld tld, String filename)
|
||||
@@ -75,6 +83,7 @@ public class ConfigureTldCommandTest extends CommandTestCase<ConfigureTldCommand
|
||||
assertThat(tld.getDriveFolderId()).isEqualTo("driveFolder");
|
||||
assertThat(tld.getCreateBillingCost()).isEqualTo(Money.of(USD, 25));
|
||||
testTldConfiguredSuccessfully(tld, "tld.yaml");
|
||||
assertThat(tld.getBreakglassMode()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -87,6 +96,23 @@ public class ConfigureTldCommandTest extends CommandTestCase<ConfigureTldCommand
|
||||
Tld updatedTld = Tld.get("tld");
|
||||
assertThat(updatedTld.getCreateBillingCost()).isEqualTo(Money.of(USD, 25));
|
||||
testTldConfiguredSuccessfully(updatedTld, "tld.yaml");
|
||||
assertThat(updatedTld.getBreakglassMode()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_noDiff() throws Exception {
|
||||
Tld tld = createTld("idns");
|
||||
persistResource(
|
||||
tld.asBuilder()
|
||||
.setIdnTables(ImmutableSet.of(JA, UNCONFUSABLE_LATIN, EXTENDED_LATIN))
|
||||
.setAllowedFullyQualifiedHostNames(ImmutableSet.of("zeta", "alpha", "gamma", "beta"))
|
||||
.build());
|
||||
File tldFile = tmpDir.resolve("idns.yaml").toFile();
|
||||
Files.asCharSink(tldFile, UTF_8).write(loadFile(getClass(), "idns.yaml"));
|
||||
runCommandForced("--input=" + tldFile);
|
||||
assertAboutLogs()
|
||||
.that(logHandler)
|
||||
.hasLogAtLevelWithMessage(INFO, "TLD YAML file contains no new changes");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -447,4 +473,101 @@ public class ConfigureTldCommandTest extends CommandTestCase<ConfigureTldCommand
|
||||
assertThrows(IllegalArgumentException.class, () -> runCommandForced("--input=" + tldFile));
|
||||
assertThat(thrown.getMessage()).isEqualTo("The premium list must use the TLD's currency");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_breakglassFlag_NoChanges() throws Exception {
|
||||
Tld tld = createTld("idns");
|
||||
persistResource(
|
||||
tld.asBuilder()
|
||||
.setIdnTables(ImmutableSet.of(JA, UNCONFUSABLE_LATIN, EXTENDED_LATIN))
|
||||
.setAllowedFullyQualifiedHostNames(ImmutableSet.of("zeta", "alpha", "gamma", "beta"))
|
||||
.build());
|
||||
File tldFile = tmpDir.resolve("idns.yaml").toFile();
|
||||
Files.asCharSink(tldFile, UTF_8).write(loadFile(getClass(), "idns.yaml"));
|
||||
IllegalArgumentException thrown =
|
||||
assertThrows(
|
||||
IllegalArgumentException.class, () -> runCommandForced("--input=" + tldFile, "-b"));
|
||||
assertThat(thrown.getMessage())
|
||||
.isEqualTo(
|
||||
"Breakglass mode can only be set when making new changes to a TLD configuration");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_breakglassFlag_startsBreakglassMode() throws Exception {
|
||||
Tld tld = createTld("tld");
|
||||
assertThat(tld.getCreateBillingCost()).isEqualTo(Money.of(USD, 13));
|
||||
File tldFile = tmpDir.resolve("tld.yaml").toFile();
|
||||
Files.asCharSink(tldFile, UTF_8).write(loadFile(getClass(), "tld.yaml"));
|
||||
runCommandForced("--input=" + tldFile, "--breakglass");
|
||||
Tld updatedTld = Tld.get("tld");
|
||||
assertThat(updatedTld.getCreateBillingCost()).isEqualTo(Money.of(USD, 25));
|
||||
testTldConfiguredSuccessfully(updatedTld, "tld.yaml");
|
||||
assertThat(updatedTld.getBreakglassMode()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_breakglassFlag_continuesBreakglassMode() throws Exception {
|
||||
Tld tld = createTld("tld");
|
||||
assertThat(tld.getCreateBillingCost()).isEqualTo(Money.of(USD, 13));
|
||||
persistResource(tld.asBuilder().setBreakglassMode(true).build());
|
||||
File tldFile = tmpDir.resolve("tld.yaml").toFile();
|
||||
Files.asCharSink(tldFile, UTF_8).write(loadFile(getClass(), "tld.yaml"));
|
||||
runCommandForced("--input=" + tldFile, "--breakglass");
|
||||
Tld updatedTld = Tld.get("tld");
|
||||
assertThat(updatedTld.getCreateBillingCost()).isEqualTo(Money.of(USD, 25));
|
||||
testTldConfiguredSuccessfully(updatedTld, "tld.yaml");
|
||||
assertThat(updatedTld.getBreakglassMode()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_NoDiffNoBreakglassFlag_endsBreakglassMode() throws Exception {
|
||||
Tld tld = createTld("idns");
|
||||
persistResource(
|
||||
tld.asBuilder()
|
||||
.setIdnTables(ImmutableSet.of(JA, UNCONFUSABLE_LATIN, EXTENDED_LATIN))
|
||||
.setAllowedFullyQualifiedHostNames(ImmutableSet.of("zeta", "alpha", "gamma", "beta"))
|
||||
.setBreakglassMode(true)
|
||||
.build());
|
||||
File tldFile = tmpDir.resolve("idns.yaml").toFile();
|
||||
Files.asCharSink(tldFile, UTF_8).write(loadFile(getClass(), "idns.yaml"));
|
||||
runCommandForced("--input=" + tldFile);
|
||||
Tld updatedTld = Tld.get("idns");
|
||||
assertThat(updatedTld.getBreakglassMode()).isFalse();
|
||||
assertAboutLogs()
|
||||
.that(logHandler)
|
||||
.hasLogAtLevelWithMessage(INFO, "Breakglass mode removed from TLD: idns");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_noDiffBreakglassFlag_continuesBreakglassMode() throws Exception {
|
||||
Tld tld = createTld("idns");
|
||||
persistResource(
|
||||
tld.asBuilder()
|
||||
.setIdnTables(ImmutableSet.of(JA, UNCONFUSABLE_LATIN, EXTENDED_LATIN))
|
||||
.setAllowedFullyQualifiedHostNames(ImmutableSet.of("zeta", "alpha", "gamma", "beta"))
|
||||
.setBreakglassMode(true)
|
||||
.build());
|
||||
File tldFile = tmpDir.resolve("idns.yaml").toFile();
|
||||
Files.asCharSink(tldFile, UTF_8).write(loadFile(getClass(), "idns.yaml"));
|
||||
runCommandForced("--input=" + tldFile, "-b");
|
||||
Tld updatedTld = Tld.get("idns");
|
||||
assertThat(updatedTld.getBreakglassMode()).isTrue();
|
||||
assertAboutLogs()
|
||||
.that(logHandler)
|
||||
.hasLogAtLevelWithMessage(INFO, "TLD YAML file contains no new changes");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_noBreakglassFlag_inBreakglassMode() throws Exception {
|
||||
Tld tld = createTld("tld");
|
||||
persistResource(tld.asBuilder().setBreakglassMode(true).build());
|
||||
File tldFile = tmpDir.resolve("tld.yaml").toFile();
|
||||
Files.asCharSink(tldFile, UTF_8).write(loadFile(getClass(), "tld.yaml"));
|
||||
IllegalArgumentException thrown =
|
||||
assertThrows(IllegalArgumentException.class, () -> runCommandForced("--input=" + tldFile));
|
||||
assertThat(thrown.getMessage())
|
||||
.isEqualTo(
|
||||
"Changes can not be applied since TLD is in breakglass mode but the breakglass flag"
|
||||
+ " was not used");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ class CreateDomainCommandTest extends EppToolCommandTestCase<CreateDomainCommand
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
command.passwordGenerator = new DeterministicStringGenerator("abcdefghijklmnopqrstuvwxyz");
|
||||
command.printStream = System.out;
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -68,6 +68,7 @@ class CreateRegistrarCommandTest extends CommandTestCase<CreateRegistrarCommand>
|
||||
2048,
|
||||
ImmutableSet.of("secp256r1", "secp384r1"),
|
||||
fakeClock);
|
||||
command.printStream = System.out;
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -37,6 +37,7 @@ class CreateRegistrarGroupsCommandTest extends CommandTestCase<CreateRegistrarGr
|
||||
|
||||
@Test
|
||||
void test_createGroupsForTwoRegistrars() throws Exception {
|
||||
command.printStream = System.out;
|
||||
runCommandForced("NewRegistrar", "TheRegistrar");
|
||||
verify(connection)
|
||||
.sendPostRequest(
|
||||
|
||||
@@ -521,6 +521,7 @@ class CreateTldCommandTest extends CommandTestCase<CreateTldCommand> {
|
||||
|
||||
@Test
|
||||
void testSuccess_setCommonAndReservedListFromOtherTld_withOverride() throws Exception {
|
||||
command.errorPrintStream = System.err;
|
||||
runReservedListsTestOverride("common_abuse,tld_banned");
|
||||
String errMsg =
|
||||
"Error overridden: The reserved list(s) tld_banned "
|
||||
|
||||
@@ -53,6 +53,7 @@ class DeleteAllocationTokensCommandTest extends CommandTestCase<DeleteAllocation
|
||||
preNot2 = persistToken("prefix8ZZZhs8", null, false);
|
||||
othrRed = persistToken("h97987sasdfhh", null, true);
|
||||
othrNot = persistToken("asdgfho7HASDS", null, false);
|
||||
command.printStream = System.out;
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -45,7 +45,7 @@ class GetTldCommandTest extends CommandTestCase<GetTldCommand> {
|
||||
PremiumList premiumList = persistPremiumList("test", USD, "silver,USD 50", "gold,USD 80");
|
||||
persistResource(
|
||||
tld.asBuilder()
|
||||
.setDnsAPlusAaaaTtl(Duration.millis(900))
|
||||
.setDnsAPlusAaaaTtl(Duration.standardMinutes(15))
|
||||
.setDriveFolderId("driveFolder")
|
||||
.setCreateBillingCost(Money.of(USD, 25))
|
||||
.setPremiumList(premiumList)
|
||||
|
||||
@@ -37,6 +37,8 @@ class LoadTestCommandTest extends CommandTestCase<LoadTestCommand> {
|
||||
command.setConnection(connection);
|
||||
createTld("example");
|
||||
persistNewRegistrar("acme", "ACME", Registrar.Type.REAL, 99L);
|
||||
command.printStream = System.out;
|
||||
command.errorPrintStream = System.err;
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -50,6 +50,7 @@ class LockDomainCommandTest extends CommandTestCase<LockDomainCommand> {
|
||||
new DeterministicStringGenerator(Alphabets.BASE_58),
|
||||
"adminreg",
|
||||
new CloudTasksHelper(fakeClock).getTestCloudTasksUtils());
|
||||
command.printStream = System.out;
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -64,7 +64,7 @@ public class RequestFactoryModuleTest {
|
||||
RegistryConfig.CONFIG_SETTINGS.get().gcpProject.isLocal = true;
|
||||
try {
|
||||
HttpRequestFactory factory =
|
||||
RequestFactoryModule.provideHttpRequestFactory(credentialsBundle, "client-id");
|
||||
RequestFactoryModule.provideHttpRequestFactory(credentialsBundle, "client-id", false);
|
||||
HttpRequestInitializer initializer = factory.getInitializer();
|
||||
assertThat(initializer).isNotNull();
|
||||
HttpRequest request = factory.buildGetRequest(new GenericUrl("http://localhost"));
|
||||
@@ -97,14 +97,23 @@ public class RequestFactoryModuleTest {
|
||||
boolean origIsLocal = RegistryConfig.CONFIG_SETTINGS.get().gcpProject.isLocal;
|
||||
RegistryConfig.CONFIG_SETTINGS.get().gcpProject.isLocal = false;
|
||||
try {
|
||||
// With OAuth header.
|
||||
HttpRequestFactory factory =
|
||||
RequestFactoryModule.provideHttpRequestFactory(credentialsBundle, "clientId");
|
||||
RequestFactoryModule.provideHttpRequestFactory(credentialsBundle, "clientId", true);
|
||||
HttpRequest request = factory.buildGetRequest(new GenericUrl("http://localhost"));
|
||||
assertThat(request.getHeaders().get("Proxy-Authorization")).isEqualTo("Bearer oidc.token");
|
||||
assertThat(request.getConnectTimeout()).isEqualTo(REQUEST_TIMEOUT_MS);
|
||||
assertThat(request.getReadTimeout()).isEqualTo(REQUEST_TIMEOUT_MS);
|
||||
verify(httpRequestInitializer).initialize(request);
|
||||
verifyNoMoreInteractions(httpRequestInitializer);
|
||||
// No OAuth header.
|
||||
factory =
|
||||
RequestFactoryModule.provideHttpRequestFactory(credentialsBundle, "clientId", false);
|
||||
request = factory.buildGetRequest(new GenericUrl("http://localhost"));
|
||||
assertThat(request.getHeaders().get("Proxy-Authorization")).isEqualTo("Bearer oidc.token");
|
||||
assertThat(request.getConnectTimeout()).isEqualTo(REQUEST_TIMEOUT_MS);
|
||||
assertThat(request.getReadTimeout()).isEqualTo(REQUEST_TIMEOUT_MS);
|
||||
verifyNoMoreInteractions(httpRequestInitializer);
|
||||
} finally {
|
||||
RegistryConfig.CONFIG_SETTINGS.get().gcpProject.isLocal = origIsLocal;
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ class UniformRapidSuspensionCommandTest
|
||||
ImmutableSet.of(
|
||||
DomainDsData.create(1, 2, 3, new HexBinaryAdapter().unmarshal("dead")),
|
||||
DomainDsData.create(4, 5, 6, new HexBinaryAdapter().unmarshal("beef")));
|
||||
command.printStream = System.out;
|
||||
}
|
||||
|
||||
private void persistDomainWithHosts(
|
||||
|
||||
@@ -53,6 +53,7 @@ class UnlockDomainCommandTest extends CommandTestCase<UnlockDomainCommand> {
|
||||
new DeterministicStringGenerator(Alphabets.BASE_58),
|
||||
"adminreg",
|
||||
new CloudTasksHelper(fakeClock).getTestCloudTasksUtils());
|
||||
command.printStream = System.out;
|
||||
}
|
||||
|
||||
private Domain persistLockedDomain(String domainName, String registrarId) {
|
||||
|
||||
@@ -55,6 +55,7 @@ public class UnrenewDomainCommandTest extends CommandTestCase<UnrenewDomainComma
|
||||
createTld("tld");
|
||||
fakeClock.setTo(DateTime.parse("2016-12-06T13:55:01Z"));
|
||||
command.clock = fakeClock;
|
||||
command.printStream = System.out;
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -178,6 +179,7 @@ public class UnrenewDomainCommandTest extends CommandTestCase<UnrenewDomainComma
|
||||
|
||||
@Test
|
||||
void test_varietyOfInvalidDomains_displaysErrors() {
|
||||
command.errorPrintStream = System.err;
|
||||
DateTime now = fakeClock.nowUtc();
|
||||
persistResource(
|
||||
DatabaseHelper.newDomain("deleting.tld")
|
||||
|
||||
@@ -1048,6 +1048,7 @@ class UpdateTldCommandTest extends CommandTestCase<UpdateTldCommand> {
|
||||
|
||||
@Test
|
||||
void testSuccess_setCommonAndReservedListFromOtherTld_withOverride() throws Exception {
|
||||
command.errorPrintStream = System.err;
|
||||
runReservedListsTestOverride("common_abuse,tld_banned");
|
||||
String errMsg =
|
||||
"Error overridden: The reserved list(s) tld_banned "
|
||||
|
||||
+4
@@ -34,6 +34,7 @@ import google.registry.model.reporting.HistoryEntryDao;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
import google.registry.tools.CommandTestCase;
|
||||
import java.io.PrintStream;
|
||||
import org.joda.money.CurrencyUnit;
|
||||
import org.joda.money.Money;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -59,6 +60,7 @@ public class CreateCancellationsForBillingEventsCommandTest
|
||||
fakeClock.nowUtc(),
|
||||
fakeClock.nowUtc().plusYears(2));
|
||||
billingEventToCancel = createBillingEvent();
|
||||
command.printStream = System.out;
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -97,8 +99,10 @@ public class CreateCancellationsForBillingEventsCommandTest
|
||||
@Test
|
||||
void testAlreadyCancelled() throws Exception {
|
||||
// multiple runs / cancellations should be a no-op
|
||||
command.printStream = new PrintStream(tmpDir.resolve("test.txt").toFile());
|
||||
runCommandForced(String.valueOf(billingEventToCancel.getId()));
|
||||
assertBillingEventCancelled();
|
||||
command.printStream = System.out;
|
||||
runCommandForced(String.valueOf(billingEventToCancel.getId()));
|
||||
assertBillingEventCancelled();
|
||||
assertThat(DatabaseHelper.loadAllOf(BillingCancellation.class)).hasSize(1);
|
||||
|
||||
@@ -128,7 +128,6 @@ public class ConsoleDomainGetActionTest {
|
||||
private User createUser(UserRoles userRoles) {
|
||||
return new User.Builder()
|
||||
.setEmailAddress("email@email.com")
|
||||
.setGaiaId("gaiaId")
|
||||
.setUserRoles(userRoles)
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -15,14 +15,12 @@
|
||||
package google.registry.ui.server.console;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import com.google.api.client.http.HttpStatusCodes;
|
||||
import com.google.gson.Gson;
|
||||
import google.registry.model.console.GlobalRole;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.console.UserRoles;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.request.RequestModule;
|
||||
import google.registry.request.auth.AuthResult;
|
||||
@@ -31,15 +29,12 @@ import google.registry.request.auth.UserAuthInfo;
|
||||
import google.registry.testing.FakeResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
/** Tests for {@link google.registry.ui.server.console.ConsoleUserDataAction}. */
|
||||
class ConsoleUserDataActionTest {
|
||||
|
||||
private final HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
private RegistrarPoc testRegistrarPoc;
|
||||
private static final Gson GSON = RequestModule.provideGson();
|
||||
private FakeResponse response = new FakeResponse();
|
||||
|
||||
@@ -52,7 +47,6 @@ class ConsoleUserDataActionTest {
|
||||
User user =
|
||||
new User.Builder()
|
||||
.setEmailAddress("email@email.com")
|
||||
.setGaiaId("gaiaId")
|
||||
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build())
|
||||
.build();
|
||||
|
||||
|
||||
@@ -232,7 +232,6 @@ class RegistrarsActionTest {
|
||||
private User createUser(UserRoles userRoles) {
|
||||
return new User.Builder()
|
||||
.setEmailAddress("email@email.com")
|
||||
.setGaiaId("gaiaId")
|
||||
.setUserRoles(userRoles)
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -226,7 +226,6 @@ class ContactActionTest {
|
||||
private User createUser(UserRoles userRoles) {
|
||||
return new User.Builder()
|
||||
.setEmailAddress("email@email.com")
|
||||
.setGaiaId("gaiaId")
|
||||
.setUserRoles(userRoles)
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -109,7 +109,6 @@ class SecurityActionTest {
|
||||
private User createUser(UserRoles userRoles) {
|
||||
return new User.Builder()
|
||||
.setEmailAddress("email@email.com")
|
||||
.setGaiaId("TestUserId")
|
||||
.setUserRoles(userRoles)
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -101,7 +101,6 @@ final class RegistryLockGetActionTest {
|
||||
google.registry.model.console.User consoleUser =
|
||||
new google.registry.model.console.User.Builder()
|
||||
.setEmailAddress("johndoe@theregistrar.com")
|
||||
.setGaiaId("gaiaId")
|
||||
.setUserRoles(
|
||||
new UserRoles.Builder()
|
||||
.setRegistrarRoles(
|
||||
|
||||
@@ -231,7 +231,6 @@ final class RegistryLockPostActionTest {
|
||||
google.registry.model.console.User consoleUser =
|
||||
new google.registry.model.console.User.Builder()
|
||||
.setEmailAddress("johndoe@theregistrar.com")
|
||||
.setGaiaId("gaiaId")
|
||||
.setUserRoles(
|
||||
new UserRoles.Builder()
|
||||
.setRegistrarRoles(
|
||||
@@ -252,7 +251,6 @@ final class RegistryLockPostActionTest {
|
||||
google.registry.model.console.User consoleUser =
|
||||
new google.registry.model.console.User.Builder()
|
||||
.setEmailAddress("johndoe@theregistrar.com")
|
||||
.setGaiaId("gaiaId")
|
||||
.setUserRoles(new UserRoles.Builder().setIsAdmin(true).build())
|
||||
.build();
|
||||
AuthResult consoleAuthResult =
|
||||
@@ -447,7 +445,6 @@ final class RegistryLockPostActionTest {
|
||||
google.registry.model.console.User consoleUser =
|
||||
new google.registry.model.console.User.Builder()
|
||||
.setEmailAddress("johndoe@theregistrar.com")
|
||||
.setGaiaId("gaiaId")
|
||||
.setUserRoles(
|
||||
new UserRoles.Builder()
|
||||
.setRegistrarRoles(
|
||||
|
||||
@@ -1,56 +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.xjc;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.testing.TestDataHelper.loadFile;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import google.registry.xjc.rdehost.XjcRdeHostElement;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Unit tests for {@link JaxbFragment}. */
|
||||
class JaxbFragmentTest {
|
||||
|
||||
private static final String HOST_FRAGMENT = loadFile(XjcObjectTest.class, "host_fragment.xml");
|
||||
|
||||
/** Verifies that a {@link JaxbFragment} can be serialized and deserialized successfully. */
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
void testJavaSerialization() throws Exception {
|
||||
// Load rdeHost xml fragment into a jaxb object, wrap it, marshal, unmarshal, verify host.
|
||||
// The resulting host name should be "ns1.example1.test", from the original xml fragment.
|
||||
try (InputStream source = new ByteArrayInputStream(HOST_FRAGMENT.getBytes(UTF_8))) {
|
||||
// Load xml
|
||||
JaxbFragment<XjcRdeHostElement> hostFragment =
|
||||
JaxbFragment.create(XjcXmlTransformer.unmarshal(XjcRdeHostElement.class, source));
|
||||
// Marshal
|
||||
ByteArrayOutputStream bout = new ByteArrayOutputStream();
|
||||
new ObjectOutputStream(bout).writeObject(hostFragment);
|
||||
// Unmarshal
|
||||
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bout.toByteArray()));
|
||||
JaxbFragment<XjcRdeHostElement> restoredHostFragment =
|
||||
(JaxbFragment<XjcRdeHostElement>) in.readObject();
|
||||
// Verify host name
|
||||
assertThat(restoredHostFragment.getInstance().getValue().getName())
|
||||
.isEqualTo("ns1.example1.test");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
addGracePeriodLength: 432000000
|
||||
addGracePeriodLength: "PT432000S"
|
||||
allowedFullyQualifiedHostNames:
|
||||
- "foo"
|
||||
allowedRegistrantContactIds: []
|
||||
anchorTenantAddGracePeriodLength: 2592000000
|
||||
autoRenewGracePeriodLength: 3888000000
|
||||
automaticTransferLength: 432000000
|
||||
anchorTenantAddGracePeriodLength: "PT2592000S"
|
||||
autoRenewGracePeriodLength: "PT3888000S"
|
||||
automaticTransferLength: "PT432000S"
|
||||
claimsPeriodEnd: "294247-01-10T04:00:54.775Z"
|
||||
createBillingCost:
|
||||
currency: "USD"
|
||||
@@ -13,13 +13,13 @@ creationTime: "1970-01-01T00:00:00.000Z"
|
||||
currency: "USD"
|
||||
defaultPromoTokens:
|
||||
- "bbbbb"
|
||||
dnsAPlusAaaaTtl: 3600000
|
||||
dnsAPlusAaaaTtl: "PT3600S"
|
||||
dnsDsTtl: null
|
||||
dnsNsTtl: null
|
||||
dnsPaused: false
|
||||
dnsWriters:
|
||||
- "baz"
|
||||
- "bang"
|
||||
- "baz"
|
||||
driveFolderId: null
|
||||
eapFeeSchedule:
|
||||
"1970-01-01T00:00:00.000Z":
|
||||
@@ -33,15 +33,15 @@ eapFeeSchedule:
|
||||
amount: 0.00
|
||||
escrowEnabled: false
|
||||
idnTables:
|
||||
- "JA"
|
||||
- "EXTENDED_LATIN"
|
||||
- "JA"
|
||||
invoicingEnabled: false
|
||||
lordnUsername: null
|
||||
numDnsPublishLocks: 1
|
||||
pendingDeleteLength: 432000000
|
||||
pendingDeleteLength: "PT432000S"
|
||||
premiumListName: "tld"
|
||||
pricingEngineClassName: "google.registry.model.pricing.StaticPremiumListPricingEngine"
|
||||
redemptionGracePeriodLength: 2592000000
|
||||
redemptionGracePeriodLength: "PT2592000S"
|
||||
registryLockOrUnlockBillingCost:
|
||||
currency: "USD"
|
||||
amount: 0.00
|
||||
@@ -49,7 +49,7 @@ renewBillingCostTransitions:
|
||||
"1970-01-01T00:00:00.000Z":
|
||||
currency: "USD"
|
||||
amount: 11.00
|
||||
renewGracePeriodLength: 432000000
|
||||
renewGracePeriodLength: "PT432000S"
|
||||
reservedListNames: []
|
||||
restoreBillingCost:
|
||||
currency: "USD"
|
||||
@@ -63,4 +63,4 @@ tldStateTransitions:
|
||||
tldStr: "tld"
|
||||
tldType: "REAL"
|
||||
tldUnicode: "tld"
|
||||
transferGracePeriodLength: 432000000
|
||||
transferGracePeriodLength: "PT432000S"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user