mirror of
https://github.com/google/nomulus
synced 2026-05-27 02:00:33 +00:00
Compare commits
8 Commits
proxy-2023
...
nomulus-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0801679173 | ||
|
|
a87c4a31a3 | ||
|
|
58c7e3a52c | ||
|
|
dded258864 | ||
|
|
759143535f | ||
|
|
46fdf2c996 | ||
|
|
fc1857717d | ||
|
|
e182692a5f |
@@ -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,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -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,7 @@ 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';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -81,6 +82,7 @@ import { SettingsWidgetComponent } from './home/widgets/settings-widget.componen
|
||||
BackendService,
|
||||
GlobalLoaderService,
|
||||
RegistrarGuard,
|
||||
UserDataService,
|
||||
{
|
||||
provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,
|
||||
useValue: {
|
||||
|
||||
@@ -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,16 @@
|
||||
<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 }}"
|
||||
>
|
||||
<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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
GlobalLoader,
|
||||
GlobalLoaderService,
|
||||
} from '../shared/services/globalLoader.service';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
|
||||
interface Address {
|
||||
street?: string[];
|
||||
@@ -52,7 +53,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 +84,8 @@ export class RegistrarService implements GlobalLoader {
|
||||
}
|
||||
|
||||
loadingTimeout() {
|
||||
// TODO: Decide what to do when timeout happens
|
||||
this._snackBar.open('Timeout loading registrars', undefined, {
|
||||
duration: 1500,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import { Registrar, RegistrarService } from './registrar.service';
|
||||
styleUrls: ['./registrarsTable.component.scss'],
|
||||
})
|
||||
export class RegistrarComponent {
|
||||
public static PATH = 'registrars';
|
||||
columns = [
|
||||
{
|
||||
columnDef: 'registrarId',
|
||||
|
||||
@@ -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 = {};
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
@@ -19,4 +19,6 @@ import { Component } from '@angular/core';
|
||||
templateUrl: './whois.component.html',
|
||||
styleUrls: ['./whois.component.scss'],
|
||||
})
|
||||
export default class WhoisComponent {}
|
||||
export default class WhoisComponent {
|
||||
public static PATH = 'whois';
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ 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';
|
||||
|
||||
@Injectable()
|
||||
export class BackendService {
|
||||
@@ -90,4 +91,10 @@ export class BackendService {
|
||||
securitySettings
|
||||
);
|
||||
}
|
||||
|
||||
getUserData(): Observable<UserData> {
|
||||
return this.http
|
||||
.get<UserData>(`/console-api/userdata`)
|
||||
.pipe(catchError((err) => this.errorCatcher<UserData>(err)));
|
||||
}
|
||||
}
|
||||
|
||||
56
console-webapp/src/app/shared/services/userData.service.ts
Normal file
56
console-webapp/src/app/shared/services/userData.service.ts
Normal file
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -45,15 +45,16 @@ body {
|
||||
padding: 0 !important;
|
||||
text-align: left;
|
||||
height: 20px !important;
|
||||
min-width: auto !important;
|
||||
}
|
||||
&-title {
|
||||
color: var(--primary) !important;
|
||||
}
|
||||
&-icon {
|
||||
font-size: 4rem;
|
||||
line-height: 4rem;
|
||||
height: 4rem !important;
|
||||
width: 4rem !important;
|
||||
font-size: 5rem;
|
||||
line-height: 5rem;
|
||||
height: 5rem !important;
|
||||
width: 5rem !important;
|
||||
}
|
||||
&_left {
|
||||
flex: 1;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
@@ -65,6 +68,57 @@ public class EntityYamlUtils {
|
||||
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> {
|
||||
|
||||
|
||||
@@ -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 == 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));
|
||||
}
|
||||
|
||||
@@ -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 ==");
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 "
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,8 @@ dnsDsTtl: null
|
||||
dnsNsTtl: null
|
||||
dnsPaused: false
|
||||
dnsWriters:
|
||||
- "baz"
|
||||
- "bang"
|
||||
- "baz"
|
||||
driveFolderId: null
|
||||
eapFeeSchedule:
|
||||
"1970-01-01T00:00:00.000Z":
|
||||
@@ -33,8 +33,8 @@ eapFeeSchedule:
|
||||
amount: 0.00
|
||||
escrowEnabled: false
|
||||
idnTables:
|
||||
- "JA"
|
||||
- "EXTENDED_LATIN"
|
||||
- "JA"
|
||||
invoicingEnabled: false
|
||||
lordnUsername: null
|
||||
numDnsPublishLocks: 1
|
||||
|
||||
62
core/src/test/resources/google/registry/tools/idns.yaml
Normal file
62
core/src/test/resources/google/registry/tools/idns.yaml
Normal file
@@ -0,0 +1,62 @@
|
||||
addGracePeriodLength: 432000000
|
||||
allowedFullyQualifiedHostNames:
|
||||
- "beta"
|
||||
- "zeta"
|
||||
- "alpha"
|
||||
- "gamma"
|
||||
allowedRegistrantContactIds: []
|
||||
anchorTenantAddGracePeriodLength: 2592000000
|
||||
autoRenewGracePeriodLength: 3888000000
|
||||
automaticTransferLength: 432000000
|
||||
claimsPeriodEnd: "294247-01-10T04:00:54.775Z"
|
||||
createBillingCost:
|
||||
currency: "USD"
|
||||
amount: 13.00
|
||||
creationTime: "2022-09-01T00:00:00.000Z"
|
||||
currency: "USD"
|
||||
defaultPromoTokens: []
|
||||
dnsAPlusAaaaTtl: null
|
||||
dnsDsTtl: null
|
||||
dnsNsTtl: null
|
||||
dnsPaused: false
|
||||
dnsWriters:
|
||||
- "VoidDnsWriter"
|
||||
driveFolderId: null
|
||||
eapFeeSchedule:
|
||||
"1970-01-01T00:00:00.000Z":
|
||||
currency: "USD"
|
||||
amount: 0.00
|
||||
escrowEnabled: false
|
||||
idnTables:
|
||||
- "EXTENDED_LATIN"
|
||||
- "JA"
|
||||
- "UNCONFUSABLE_LATIN"
|
||||
invoicingEnabled: false
|
||||
lordnUsername: null
|
||||
numDnsPublishLocks: 1
|
||||
pendingDeleteLength: 432000000
|
||||
premiumListName: "idns"
|
||||
pricingEngineClassName: "google.registry.model.pricing.StaticPremiumListPricingEngine"
|
||||
redemptionGracePeriodLength: 2592000000
|
||||
registryLockOrUnlockBillingCost:
|
||||
currency: "USD"
|
||||
amount: 0.00
|
||||
renewBillingCostTransitions:
|
||||
"1970-01-01T00:00:00.000Z":
|
||||
currency: "USD"
|
||||
amount: 11.00
|
||||
renewGracePeriodLength: 432000000
|
||||
reservedListNames: []
|
||||
restoreBillingCost:
|
||||
currency: "USD"
|
||||
amount: 17.00
|
||||
roidSuffix: "IDNS"
|
||||
serverStatusChangeBillingCost:
|
||||
currency: "USD"
|
||||
amount: 19.00
|
||||
tldStateTransitions:
|
||||
"1970-01-01T00:00:00.000Z": "GENERAL_AVAILABILITY"
|
||||
tldStr: "idns"
|
||||
tldType: "REAL"
|
||||
tldUnicode: "idns"
|
||||
transferGracePeriodLength: 432000000
|
||||
@@ -499,8 +499,10 @@ comes in at the exact millisecond that the domain would have expired.
|
||||
* Resource status prohibits this operation.
|
||||
* The allocation token is not currently valid.
|
||||
* 2305
|
||||
* The __REMOVEDOMAIN__ token is missing on a bulk pricing domain command
|
||||
* The __REMOVEDOMAIN__ token is not allowed on non bulk pricing domains
|
||||
* The __REMOVE_BULK_PRICING__ token is missing on a bulk pricing domain
|
||||
command
|
||||
* The __REMOVE_BULK_PRICING__ token is not allowed on non bulk pricing
|
||||
domains
|
||||
* The allocation token is not valid for this domain.
|
||||
* The allocation token is not valid for this registrar.
|
||||
* The allocation token is not valid for this TLD.
|
||||
|
||||
@@ -34,7 +34,7 @@ spec:
|
||||
name: https-whois
|
||||
type: NodePort
|
||||
---
|
||||
apiVersion: autoscaling/v2beta1
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
namespace: default
|
||||
|
||||
@@ -34,7 +34,7 @@ spec:
|
||||
name: https-whois
|
||||
type: NodePort
|
||||
---
|
||||
apiVersion: autoscaling/v2beta1
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
namespace: default
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
// 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.util;
|
||||
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectStreamClass;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Safely deserializes Nomulus http request parameters.
|
||||
*
|
||||
* <p>Serialized Java objects may be passed between Nomulus components that hold different
|
||||
* credentials. Deserialization of such objects should be protected against attacks through
|
||||
* compromised accounts.
|
||||
*
|
||||
* <p>This class protects against three types of attacks by restricting the classes used for
|
||||
* serialization:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Remote code execution by referencing bad classes in compromised jars. When a class with
|
||||
* malicious code in the static initialization block or the deserialization code path (e.g.,
|
||||
* the {@code readObject} method) is deserialized, such code will be executed. For Nomulus,
|
||||
* this risk comes from third-party dependencies. To counter this risk, this class only allows
|
||||
* Nomulus (google.registry.**) classes and specific core Java classes, and forbid others
|
||||
* including third-party dependencies. (As a side note, this class does not use allow lists
|
||||
* for Nomulus or third-party classes because it is infeasible in practice. Super classes of
|
||||
* the instance being deserialized must be resolved, and therefore must be on the allow list;
|
||||
* same for the field types of the instance. The allow list for the Joda {@code DateTime}
|
||||
* class alone would have more than 10 classes. Generated classes, e.g., by AutoValue, present
|
||||
* another problem: their real names are not meant to be a concern to the user).
|
||||
* <li>CPU-targeting denial-of-service attacks. Containers and arrays may be used to construct
|
||||
* object graphs that require enormous amount of computation during deserialization and/or
|
||||
* during invocations of methods such as {@code hashCode} or {@code equals}, taking minutes or
|
||||
* even hours to complete. See <a
|
||||
* href="https://owasp.org/www-community/vulnerabilities/Deserialization_of_untrusted_data">
|
||||
* here</a> for an example of such object graphs. To counter this risk, this class forbids
|
||||
* lists, maps, and arrays for deserialization.
|
||||
* <li>Memory-targeting denial-of-service attacks. By forbidding container and arrays, this class
|
||||
* also prevents some memory-targeting attacks, e.g., using wire format that claims to be an
|
||||
* array of a huge size, causing the JVM to preallocate excessive amount of memory and
|
||||
* triggering the {@code OutOfMemoryError}. This is actually a small risk for Nomulus, since
|
||||
* the impact of each error is limited to a single (spurious) request.
|
||||
* </ul>
|
||||
*
|
||||
* <p>Nomulus classes with fields of array, container, or third-party Java types must implement
|
||||
* their own serialization/deserialization methods to be safely deserialized. For the common use
|
||||
* case of passing a collection of `safe` objects, {@link
|
||||
* SafeSerializationUtils#serializeCollection} and {@link
|
||||
* SafeSerializationUtils#safeDeserializeCollection} may be used.
|
||||
*/
|
||||
public final class SafeObjectInputStream extends ObjectInputStream {
|
||||
|
||||
/**
|
||||
* Core Java classes allowed in deserialization. Add new classes as needed but do not add
|
||||
* third-party classes.
|
||||
*/
|
||||
private static final ImmutableSet<String> ALLOWED_CORE_JAVA_CLASSES =
|
||||
ImmutableSet.of(String.class, Byte.class, Short.class, Integer.class, Long.class).stream()
|
||||
.map(Class::getName)
|
||||
.collect(toImmutableSet());
|
||||
|
||||
public SafeObjectInputStream(InputStream in) throws IOException {
|
||||
super(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> resolveClass(ObjectStreamClass desc)
|
||||
throws ClassNotFoundException, IOException {
|
||||
String clazz = desc.getName();
|
||||
if (isNomulusClass(clazz) || ALLOWED_CORE_JAVA_CLASSES.contains(clazz)) {
|
||||
return checkNotArrayOrContainer(super.resolveClass(desc));
|
||||
}
|
||||
throw new ClassNotFoundException(clazz + " not found or not allowed in deserialization.");
|
||||
}
|
||||
|
||||
private Class<?> checkNotArrayOrContainer(Class<?> clazz) throws ClassNotFoundException {
|
||||
if (isContainer(clazz) || clazz.isArray()) {
|
||||
throw new ClassNotFoundException(clazz.getName() + " not allowed as non-root object.");
|
||||
}
|
||||
return clazz;
|
||||
}
|
||||
|
||||
private boolean isNomulusClass(String clazz) {
|
||||
return clazz.startsWith("google.registry.");
|
||||
}
|
||||
|
||||
private boolean isContainer(Class<?> clazz) {
|
||||
return Collection.class.isAssignableFrom(clazz) || Map.class.isAssignableFrom(clazz);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
// 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.util;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
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.Arrays;
|
||||
import java.util.Collection;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Helpers for using {@link SafeObjectInputStream}.
|
||||
*
|
||||
* <p>Please refer to {@code SafeObjectInputStream} for more information.
|
||||
*/
|
||||
public final class SafeSerializationUtils {
|
||||
|
||||
private SafeSerializationUtils() {}
|
||||
|
||||
/**
|
||||
* Maximum number of elements allowed in a serialized collection.
|
||||
*
|
||||
* <p>This value is sufficient for parameters embedded in a {@code URL} to typical cloud services.
|
||||
* E.g., as of Fall 2023, AWS limits request line size to 16KB and GCP limits total header size to
|
||||
* 64KB.
|
||||
*/
|
||||
public static final int MAX_COLLECTION_SIZE = 32768;
|
||||
|
||||
/**
|
||||
* Serializes a collection of objects that can be safely deserialized using {@link
|
||||
* #safeDeserializeCollection}.
|
||||
*
|
||||
* <p>If any element of the collection cannot be safely-deserialized, deserialization will fail.
|
||||
*/
|
||||
public static byte[] serializeCollection(Collection<?> collection) {
|
||||
checkNotNull(collection, "collection");
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
try (ObjectOutputStream os = new ObjectOutputStream(bos)) {
|
||||
os.writeInt(collection.size());
|
||||
for (Object obj : collection) {
|
||||
os.writeObject(obj);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to serialize: " + collection, e);
|
||||
}
|
||||
return bos.toByteArray();
|
||||
}
|
||||
|
||||
/** Safely deserializes an object using {@link SafeObjectInputStream}. */
|
||||
@Nullable
|
||||
public static Serializable safeDeserialize(@Nullable byte[] bytes) {
|
||||
if (bytes == null) {
|
||||
return null;
|
||||
}
|
||||
try (ObjectInputStream is = new SafeObjectInputStream(new ByteArrayInputStream(bytes))) {
|
||||
Serializable ret = (Serializable) is.readObject();
|
||||
return ret;
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
throw new IllegalArgumentException("Failed to deserialize: " + Arrays.toString(bytes), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely deserializes a collection of objects previously serialized with {@link
|
||||
* #serializeCollection}.
|
||||
*/
|
||||
public static <T> ImmutableList<T> safeDeserializeCollection(Class<T> elementType, byte[] bytes) {
|
||||
checkNotNull(bytes, "Serialized list must not be null.");
|
||||
try (ObjectInputStream is = new SafeObjectInputStream(new ByteArrayInputStream(bytes))) {
|
||||
int size = is.readInt();
|
||||
checkArgument(size >= 0, "Malformed data: negative collection size.");
|
||||
if (size > MAX_COLLECTION_SIZE) {
|
||||
throw new IllegalArgumentException("Too many elements in collection: " + size);
|
||||
}
|
||||
ImmutableList.Builder<T> builder = new ImmutableList.Builder<>();
|
||||
for (int i = 0; i < size; i++) {
|
||||
builder.add(elementType.cast(is.readObject()));
|
||||
}
|
||||
return builder.build();
|
||||
} catch (IOException | ClassNotFoundException | ClassCastException e) {
|
||||
throw new IllegalArgumentException("Failed to deserialize: " + Arrays.toString(bytes), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,10 +74,20 @@ public final class SerializeUtils {
|
||||
|
||||
private SerializeUtils() {}
|
||||
|
||||
/** Encodes a byte array as a URL-safe string. */
|
||||
public static String encodeBase64(byte[] bytes) {
|
||||
return Base64.encodeBase64URLSafeString(bytes);
|
||||
}
|
||||
|
||||
/** Turns a string encoded by {@link #encodeBase64} back into a byte array. */
|
||||
public static byte[] decodeBase64(String objectString) {
|
||||
return Base64.decodeBase64(objectString);
|
||||
}
|
||||
|
||||
/** Turns an object into an encoded string that can be used safely as a URI query parameter. */
|
||||
public static String stringify(Serializable object) {
|
||||
checkNotNull(object, "Object cannot be null");
|
||||
return Base64.encodeBase64URLSafeString(SerializeUtils.serialize(object));
|
||||
return encodeBase64(SerializeUtils.serialize(object));
|
||||
}
|
||||
|
||||
/** Turns a string encoded by stringify() into an object. */
|
||||
@@ -86,6 +96,6 @@ public final class SerializeUtils {
|
||||
checkNotNull(type, "Class type is not specified");
|
||||
checkNotNull(objectString, "Object string cannot be null");
|
||||
|
||||
return SerializeUtils.deserialize(type, Base64.decodeBase64(objectString));
|
||||
return SerializeUtils.deserialize(type, decodeBase64(objectString));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
// 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.util;
|
||||
|
||||
import static com.google.common.collect.Lists.newArrayList;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.util.SerializeUtils.serialize;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Maps;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import org.joda.time.Duration;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Unit tests for {@link SafeObjectInputStream}. */
|
||||
public class SafeObjectInputStreamTest {
|
||||
|
||||
@Test
|
||||
void javaUnitarySuccess() throws Exception {
|
||||
String orig = "some string";
|
||||
try (SafeObjectInputStream sois =
|
||||
new SafeObjectInputStream(new ByteArrayInputStream(serialize(orig)))) {
|
||||
assertThat(sois.readObject()).isEqualTo(orig);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void javaCollectionFailure() throws Exception {
|
||||
ArrayList<String> orig = newArrayList("a");
|
||||
try (SafeObjectInputStream sois =
|
||||
new SafeObjectInputStream(new ByteArrayInputStream(serialize(orig)))) {
|
||||
assertThrows(ClassNotFoundException.class, () -> sois.readObject());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void javaMapFailure() throws Exception {
|
||||
HashMap<Object, Object> orig = Maps.newHashMap();
|
||||
try (SafeObjectInputStream sois =
|
||||
new SafeObjectInputStream(new ByteArrayInputStream(serialize(orig)))) {
|
||||
assertThrows(ClassNotFoundException.class, () -> sois.readObject());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void javaArrayFailure() throws Exception {
|
||||
int[] orig = new int[] {1};
|
||||
try (SafeObjectInputStream sois =
|
||||
new SafeObjectInputStream(new ByteArrayInputStream(serialize(orig)))) {
|
||||
// For array, the parent class converts ClassNotFoundException in an undocumented way. Safer
|
||||
// to catch Exception than the one thrown by the current JVM.
|
||||
assertThrows(Exception.class, () -> sois.readObject());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonJavaNonNomulusUnitaryFailure() throws Exception {
|
||||
Serializable orig = Duration.millis(1);
|
||||
try (SafeObjectInputStream sois =
|
||||
new SafeObjectInputStream(new ByteArrayInputStream(serialize(orig)))) {
|
||||
assertThrows(ClassNotFoundException.class, () -> sois.readObject());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonJavaCollectionFailure() throws Exception {
|
||||
ImmutableList<String> orig = ImmutableList.of("a");
|
||||
try (SafeObjectInputStream sois =
|
||||
new SafeObjectInputStream(new ByteArrayInputStream(serialize(orig)))) {
|
||||
assertThrows(ClassNotFoundException.class, () -> sois.readObject());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void nomulusEntitySuccess() throws Exception {
|
||||
NomulusEntity orig = new NomulusEntity(1);
|
||||
byte[] serialized = serialize(orig);
|
||||
try (SafeObjectInputStream sois =
|
||||
new SafeObjectInputStream(new ByteArrayInputStream(serialized))) {
|
||||
Object deserialized = sois.readObject();
|
||||
assertThat(deserialized).isEqualTo(orig);
|
||||
}
|
||||
}
|
||||
|
||||
static class NomulusEntity implements Serializable {
|
||||
Integer value;
|
||||
|
||||
NomulusEntity(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof NomulusEntity)) {
|
||||
return false;
|
||||
}
|
||||
NomulusEntity that = (NomulusEntity) o;
|
||||
return Objects.equal(value, that.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
// 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.util;
|
||||
|
||||
import static com.google.common.collect.Lists.newArrayList;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.util.SafeSerializationUtils.safeDeserialize;
|
||||
import static google.registry.util.SafeSerializationUtils.safeDeserializeCollection;
|
||||
import static google.registry.util.SafeSerializationUtils.serializeCollection;
|
||||
import static google.registry.util.SerializeUtils.serialize;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Unit tests for {@link SafeSerializationUtils}. */
|
||||
public class SafeSerializationUtilsTest {
|
||||
|
||||
@Test
|
||||
void deserialize_array_failure() {
|
||||
assertThat(
|
||||
assertThrows(
|
||||
IllegalArgumentException.class, () -> safeDeserialize(serialize(new byte[0]))))
|
||||
.hasMessageThat()
|
||||
.contains("Failed to deserialize:");
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserialize_null_success() {
|
||||
assertThat(safeDeserialize(serialize(null))).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserialize_map_failure() {
|
||||
assertThat(
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> safeDeserialize(serialize(ImmutableMap.of()))))
|
||||
.hasMessageThat()
|
||||
.contains("Failed to deserialize:");
|
||||
}
|
||||
|
||||
@Test
|
||||
void serializeDeserialize_null_success() {
|
||||
assertThat(safeDeserialize(null)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void serializeDeserialize_notCollection_success() {
|
||||
Integer orig = 1;
|
||||
assertThat(safeDeserialize(serialize(orig))).isEqualTo(orig);
|
||||
}
|
||||
|
||||
@Test
|
||||
void serializeDeserializeCollection_success() {
|
||||
ArrayList<Integer> orig = newArrayList(1, 2, 3);
|
||||
ImmutableList<Integer> deserialized =
|
||||
safeDeserializeCollection(Integer.class, serializeCollection(orig));
|
||||
assertThat(deserialized).isEqualTo(orig);
|
||||
}
|
||||
|
||||
@Test
|
||||
void serializeDeserializeCollection_withMaxSize_success() {
|
||||
Integer[] array = new Integer[SafeSerializationUtils.MAX_COLLECTION_SIZE];
|
||||
Arrays.fill(array, 1);
|
||||
ArrayList<Integer> orig = newArrayList(array);
|
||||
assertThat(safeDeserializeCollection(Integer.class, serializeCollection(orig))).isEqualTo(orig);
|
||||
}
|
||||
|
||||
@Test
|
||||
void serializeDeserializeCollection_tooLarge_Failure() {
|
||||
Integer[] array = new Integer[SafeSerializationUtils.MAX_COLLECTION_SIZE + 1];
|
||||
Arrays.fill(array, 1);
|
||||
ArrayList<Integer> orig = newArrayList(array);
|
||||
assertThat(
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> safeDeserializeCollection(Integer.class, serializeCollection(orig))))
|
||||
.hasMessageThat()
|
||||
.contains("Too many elements");
|
||||
}
|
||||
|
||||
@Test
|
||||
void serializeDeserializeCollection_wrong_elementType_success() {
|
||||
ArrayList<Integer> orig = newArrayList(1, 2, 3);
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> safeDeserializeCollection(Long.class, serializeCollection(orig)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializeCollection_null_failure() {
|
||||
assertThrows(NullPointerException.class, () -> safeDeserializeCollection(Integer.class, null));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user