Compare commits

..
45 changed files with 4113 additions and 4992 deletions
+5 -3
View File
@@ -49,14 +49,15 @@ import { SettingsWidgetComponent } from './home/widgets/settingsWidget.component
import { UserDataService } from './shared/services/userData.service';
import WhoisComponent from './settings/whois/whois.component';
import { SnackBarModule } from './snackbar.module';
import { RegistrarDetailsComponent } from './registrar/registrarDetails.component';
import {
RegistrarDetailsComponent,
RegistrarDetailsWrapperComponent,
} from './registrar/registrarDetails.component';
import { DomainListComponent } from './domains/domainList.component';
import { DialogBottomSheetWrapper } from './shared/components/dialogBottomSheet.component';
@NgModule({
declarations: [
AppComponent,
DialogBottomSheetWrapper,
BillingWidgetComponent,
ContactDetailsDialogComponent,
ContactWidgetComponent,
@@ -69,6 +70,7 @@ import { DialogBottomSheetWrapper } from './shared/components/dialogBottomSheet.
PromotionsWidgetComponent,
RegistrarComponent,
RegistrarDetailsComponent,
RegistrarDetailsWrapperComponent,
RegistrarSelectorComponent,
ResourcesWidgetComponent,
SecurityComponent,
@@ -1,13 +1,7 @@
<div class="console-domains">
<mat-form-field>
<mat-label>Filter</mat-label>
<input
type="search"
matInput
[(ngModel)]="searchTerm"
(ngModelChange)="sendInput()"
#input
/>
<input matInput (keyup)="applyFilter($event)" #input />
</mat-form-field>
<div *ngIf="isLoading; else domains_content" class="console-domains__loading">
@@ -18,7 +18,6 @@ import { BackendService } from '../shared/services/backend.service';
import { MatPaginator, PageEvent } from '@angular/material/paginator';
import { RegistrarService } from '../registrar/registrar.service';
import { Domain, DomainListService } from './domainList.service';
import { Subject, debounceTime } from 'rxjs';
@Component({
selector: 'app-domain-list',
@@ -28,7 +27,6 @@ import { Subject, debounceTime } from 'rxjs';
})
export class DomainListComponent {
public static PATH = 'domain-list';
private readonly DEBOUNCE_MS = 500;
displayedColumns: string[] = [
'domainName',
@@ -40,9 +38,6 @@ export class DomainListComponent {
dataSource: MatTableDataSource<Domain> = new MatTableDataSource();
isLoading = true;
searchTermSubject = new Subject<string>();
searchTerm?: string;
pageNumber?: number;
resultsPerPage = 50;
totalResults?: number;
@@ -57,28 +52,13 @@ export class DomainListComponent {
ngOnInit() {
this.dataSource.paginator = this.paginator;
// Don't spam the server unnecessarily while the user is typing
this.searchTermSubject
.pipe(debounceTime(this.DEBOUNCE_MS))
.subscribe((searchTermValue) => {
this.reloadData();
});
this.reloadData();
}
ngOnDestroy() {
this.searchTermSubject.complete();
}
reloadData() {
this.isLoading = true;
this.domainListService
.retrieveDomains(
this.pageNumber,
this.resultsPerPage,
this.totalResults,
this.searchTerm
)
.retrieveDomains(this.pageNumber, this.resultsPerPage, this.totalResults)
.subscribe((domainListResult) => {
this.dataSource.data = domainListResult.domains;
this.totalResults = domainListResult.totalResults;
@@ -86,8 +66,10 @@ export class DomainListComponent {
});
}
sendInput() {
this.searchTermSubject.next(this.searchTerm!);
/** TODO: the backend will need to accept a filter string. */
applyFilter(event: KeyboardEvent) {
// const filterValue = (event.target as HTMLInputElement).value;
this.reloadData();
}
onPageChange(event: PageEvent) {
@@ -47,8 +47,7 @@ export class DomainListService {
retrieveDomains(
pageNumber?: number,
resultsPerPage?: number,
totalResults?: number,
searchTerm?: string
totalResults?: number
) {
return this.backendService
.getDomains(
@@ -56,8 +55,7 @@ export class DomainListService {
this.checkpointTime,
pageNumber,
resultsPerPage,
totalResults,
searchTerm
totalResults
)
.pipe(
tap((domainListResult: DomainListResult) => {
@@ -1,7 +1,7 @@
<div class="registrarDetails" *ngIf="registrarInEdit">
<div class="registrarDetails">
<h3 mat-dialog-title>Edit Registrar: {{ registrarInEdit.registrarId }}</h3>
<div mat-dialog-content>
<form (ngSubmit)="saveAndClose()">
<form (ngSubmit)="saveAndClose($event)">
<mat-form-field class="registrarDetails__input">
<mat-label>Registry Lock:</mat-label>
<mat-select
@@ -32,7 +32,7 @@
/>
</mat-form-field>
<mat-dialog-actions>
<button mat-button (click)="this.params?.close()">Cancel</button>
<button mat-button (click)="onCancel($event)">Cancel</button>
<button type="submit" mat-button color="primary">Save</button>
</mat-dialog-actions>
</form>
@@ -12,38 +12,61 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component } from '@angular/core';
import { Component, Injector } from '@angular/core';
import { Registrar, RegistrarService } from './registrar.service';
import { BreakpointObserver } from '@angular/cdk/layout';
import {
MAT_BOTTOM_SHEET_DATA,
MatBottomSheet,
MatBottomSheetRef,
} from '@angular/material/bottom-sheet';
import {
MAT_DIALOG_DATA,
MatDialog,
MatDialogRef,
} from '@angular/material/dialog';
import { MatChipInputEvent } from '@angular/material/chips';
import { DialogBottomSheetContent } from '../shared/components/dialogBottomSheet.component';
type RegistrarDetailsParams = {
close: Function;
data: {
registrar: Registrar;
};
};
const MOBILE_LAYOUT_BREAKPOINT = '(max-width: 599px)';
@Component({
selector: 'app-registrar-details',
templateUrl: './registrarDetails.component.html',
styleUrls: ['./registrarDetails.component.scss'],
})
export class RegistrarDetailsComponent implements DialogBottomSheetContent {
export class RegistrarDetailsComponent {
registrarInEdit!: Registrar;
params?: RegistrarDetailsParams;
private elementRef:
| MatBottomSheetRef<RegistrarDetailsComponent>
| MatDialogRef<RegistrarDetailsComponent>;
constructor(protected registrarService: RegistrarService) {}
init(params: RegistrarDetailsParams) {
this.params = params;
this.registrarInEdit = JSON.parse(
JSON.stringify(this.params.data.registrar)
);
constructor(
protected registrarService: RegistrarService,
private injector: Injector
) {
// We only inject one, either Dialog or Bottom Sheet data
// so one of the injectors is expected to fail
try {
var params = this.injector.get(MAT_DIALOG_DATA);
this.elementRef = this.injector.get(MatDialogRef);
} catch (e) {
var params = this.injector.get(MAT_BOTTOM_SHEET_DATA);
this.elementRef = this.injector.get(MatBottomSheetRef);
}
this.registrarInEdit = JSON.parse(JSON.stringify(params.registrar));
}
saveAndClose() {
this.params?.close();
onCancel(e: MouseEvent) {
if (this.elementRef instanceof MatBottomSheetRef) {
this.elementRef.dismiss();
} else if (this.elementRef instanceof MatDialogRef) {
this.elementRef.close();
}
}
saveAndClose(e: MouseEvent) {
// TODO: Implement save call to API
this.onCancel(e);
}
addTLD(e: MatChipInputEvent) {
@@ -59,3 +82,24 @@ export class RegistrarDetailsComponent implements DialogBottomSheetContent {
);
}
}
@Component({
selector: 'app-registrar-details-wrapper',
template: '',
})
export class RegistrarDetailsWrapperComponent {
constructor(
private dialog: MatDialog,
private bottomSheet: MatBottomSheet,
protected breakpointObserver: BreakpointObserver
) {}
open(registrar: Registrar) {
const config = { data: { registrar } };
if (this.breakpointObserver.isMatched(MOBILE_LAYOUT_BREAKPOINT)) {
this.bottomSheet.open(RegistrarDetailsComponent, config);
} else {
this.dialog.open(RegistrarDetailsComponent, config);
}
}
}
@@ -48,7 +48,7 @@
[pageSizeOptions]="[5, 10, 20]"
showFirstLastButtons
></mat-paginator>
<app-dialog-bottom-sheet-wrapper
<app-registrar-details-wrapper
#registrarDetailsView
></app-dialog-bottom-sheet-wrapper>
></app-registrar-details-wrapper>
</div>
@@ -17,8 +17,7 @@ import { Registrar, RegistrarService } from './registrar.service';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { RegistrarDetailsComponent } from './registrarDetails.component';
import { DialogBottomSheetWrapper } from '../shared/components/dialogBottomSheet.component';
import { RegistrarDetailsWrapperComponent } from './registrarDetails.component';
@Component({
selector: 'app-registrar',
@@ -83,7 +82,7 @@ export class RegistrarComponent {
@ViewChild(MatPaginator) paginator!: MatPaginator;
@ViewChild(MatSort) sort!: MatSort;
@ViewChild('registrarDetailsView')
detailsComponentWrapper!: DialogBottomSheetWrapper;
detailsComponentWrapper!: RegistrarDetailsWrapperComponent;
constructor(protected registrarService: RegistrarService) {
this.dataSource = new MatTableDataSource<Registrar>(
@@ -98,10 +97,7 @@ export class RegistrarComponent {
openDetails(event: MouseEvent, registrar: Registrar) {
event.stopPropagation();
this.detailsComponentWrapper.open<RegistrarDetailsComponent>(
RegistrarDetailsComponent,
{ registrar }
);
this.detailsComponentWrapper.open(registrar);
}
applyFilter(event: Event) {
@@ -41,7 +41,4 @@
<mat-icon>add</mat-icon>Create a Contact
</button>
</div>
<app-dialog-bottom-sheet-wrapper
#contactDetailsWrapper
></app-dialog-bottom-sheet-wrapper>
</div>
@@ -12,14 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, ViewChild } from '@angular/core';
import { Component, Inject } from '@angular/core';
import {
MatDialog,
MAT_DIALOG_DATA,
MatDialogRef,
} from '@angular/material/dialog';
import {
MatBottomSheet,
MAT_BOTTOM_SHEET_DATA,
MatBottomSheetRef,
} from '@angular/material/bottom-sheet';
import { Contact, ContactService } from './contact.service';
import { BreakpointObserver } from '@angular/cdk/layout';
import { HttpErrorResponse } from '@angular/common/http';
import { MatSnackBar } from '@angular/material/snack-bar';
import {
DialogBottomSheetContent,
DialogBottomSheetWrapper,
} from 'src/app/shared/components/dialogBottomSheet.component';
enum Operations {
DELETE,
@@ -33,13 +40,7 @@ interface GroupedContacts {
contacts: Array<Contact>;
}
type ContactDetailsParams = {
close: Function;
data: {
contact: Contact;
operation: Operations;
};
};
let isMobile: boolean;
const contactTypes: Array<GroupedContacts> = [
{ value: 'ADMIN', label: 'Primary contact', contacts: [] },
@@ -51,46 +52,72 @@ const contactTypes: Array<GroupedContacts> = [
{ value: 'WHOIS', label: 'WHOIS-Inquiry contact', contacts: [] },
];
class ContactDetailsEventsResponder {
private ref?: MatDialogRef<any> | MatBottomSheetRef;
constructor() {
this.onClose = this.onClose.bind(this);
}
setRef(ref: MatDialogRef<any> | MatBottomSheetRef) {
this.ref = ref;
}
onClose() {
if (this.ref == undefined) {
throw "Reference to ContactDetailsDialogComponent hasn't been set. ";
}
if (this.ref instanceof MatBottomSheetRef) {
this.ref.dismiss();
} else if (this.ref instanceof MatDialogRef) {
this.ref.close();
}
}
}
@Component({
selector: 'app-contact-details-dialog',
templateUrl: 'contactDetails.component.html',
styleUrls: ['./contact.component.scss'],
})
export class ContactDetailsDialogComponent implements DialogBottomSheetContent {
contact?: Contact;
export class ContactDetailsDialogComponent {
contact: Contact;
contactTypes = contactTypes;
contactIndex?: number;
params?: ContactDetailsParams;
operation: Operations;
contactIndex: number;
onCloseCallback: Function;
constructor(
public contactService: ContactService,
private _snackBar: MatSnackBar
) {}
init(params: ContactDetailsParams) {
this.params = params;
this.contactIndex = this.contactService.contacts.findIndex(
(c) => c === params.data.contact
private _snackBar: MatSnackBar,
@Inject(isMobile ? MAT_BOTTOM_SHEET_DATA : MAT_DIALOG_DATA)
public data: {
onClose: Function;
contact: Contact;
operation: Operations;
}
) {
this.onCloseCallback = data.onClose;
this.contactIndex = contactService.contacts.findIndex(
(c) => c === data.contact
);
this.contact = JSON.parse(JSON.stringify(params.data.contact));
this.contact = JSON.parse(JSON.stringify(data.contact));
this.operation = data.operation;
}
close() {
this.params?.close();
onClose(e: MouseEvent) {
e.preventDefault();
this.onCloseCallback.call(this);
}
saveAndClose(e: SubmitEvent) {
e.preventDefault();
if (!this.contact || this.contactIndex === undefined) return;
if (!(e.target as HTMLFormElement).checkValidity()) {
return;
}
const operation = this.params?.data.operation;
let operationObservable;
if (operation === Operations.ADD) {
if (this.operation === Operations.ADD) {
operationObservable = this.contactService.addContact(this.contact);
} else if (operation === Operations.UPDATE) {
} else if (this.operation === Operations.UPDATE) {
operationObservable = this.contactService.updateContact(
this.contactIndex,
this.contact
@@ -100,7 +127,7 @@ export class ContactDetailsDialogComponent implements DialogBottomSheetContent {
}
operationObservable.subscribe({
complete: () => this.close(),
complete: this.onCloseCallback.bind(this),
error: (err: HttpErrorResponse) => {
this._snackBar.open(err.error);
},
@@ -116,11 +143,11 @@ export class ContactDetailsDialogComponent implements DialogBottomSheetContent {
export default class ContactComponent {
public static PATH = 'contact';
@ViewChild('contactDetailsWrapper')
detailsComponentWrapper!: DialogBottomSheetWrapper;
loading: boolean = false;
constructor(
private dialog: MatDialog,
private bottomSheet: MatBottomSheet,
private breakpointObserver: BreakpointObserver,
public contactService: ContactService,
private _snackBar: MatSnackBar
) {
@@ -168,9 +195,20 @@ export default class ContactComponent {
operation: Operations = Operations.UPDATE
) {
e.preventDefault();
this.detailsComponentWrapper.open<ContactDetailsDialogComponent>(
ContactDetailsDialogComponent,
{ contact, operation }
);
// TODO: handle orientation change
isMobile = this.breakpointObserver.isMatched('(max-width: 599px)');
const responder = new ContactDetailsEventsResponder();
const config = { data: { onClose: responder.onClose, contact, operation } };
if (isMobile) {
const bottomSheetRef = this.bottomSheet.open(
ContactDetailsDialogComponent,
config
);
responder.setRef(bottomSheetRef);
} else {
const dialogRef = this.dialog.open(ContactDetailsDialogComponent, config);
responder.setRef(dialogRef);
}
}
}
@@ -45,7 +45,7 @@ export class ContactService {
return this.backend
.getContacts(this.registrarService.activeRegistrarId)
.pipe(
tap((contacts = []) => {
tap((contacts) => {
this.contacts = contacts;
})
);
@@ -1,5 +1,5 @@
<h3 mat-dialog-title>Contact details</h3>
<div mat-dialog-content *ngIf="contact">
<div mat-dialog-content>
<form (ngSubmit)="saveAndClose($event)">
<p>
<mat-form-field class="contact-details__input">
@@ -97,7 +97,7 @@
>
</section>
<mat-dialog-actions>
<button mat-button (click)="close()">Cancel</button>
<button mat-button (click)="onClose($event)">Cancel</button>
<button type="submit" mat-button>Save</button>
</mat-dialog-actions>
</form>
@@ -1,69 +0,0 @@
// 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 { BreakpointObserver } from '@angular/cdk/layout';
import { ComponentType } from '@angular/cdk/portal';
import { Component } from '@angular/core';
import {
MatBottomSheet,
MatBottomSheetRef,
} from '@angular/material/bottom-sheet';
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
const MOBILE_LAYOUT_BREAKPOINT = '(max-width: 599px)';
export interface DialogBottomSheetContent {
init(data: Object): void;
}
/**
* Wraps up a child component in an Angular Material Dalog for desktop or a Bottom Sheet
* component for mobile depending on a screen resolution, with Breaking Point being 599px.
* Child component is required to implement @see DialogBottomSheetContent interface
*/
@Component({
selector: 'app-dialog-bottom-sheet-wrapper',
template: '',
})
export class DialogBottomSheetWrapper {
private elementRef?: MatBottomSheetRef | MatDialogRef<any>;
constructor(
private dialog: MatDialog,
private bottomSheet: MatBottomSheet,
protected breakpointObserver: BreakpointObserver
) {}
open<T extends DialogBottomSheetContent>(
component: ComponentType<T>,
data: any
) {
const config = { data, close: () => this.close() };
if (this.breakpointObserver.isMatched(MOBILE_LAYOUT_BREAKPOINT)) {
this.elementRef = this.bottomSheet.open(component);
this.elementRef.instance.init(config);
} else {
this.elementRef = this.dialog.open(component);
this.elementRef.componentInstance.init(config);
}
}
close() {
if (this.elementRef instanceof MatBottomSheetRef) {
this.elementRef.dismiss();
} else if (this.elementRef instanceof MatDialogRef) {
this.elementRef.close();
}
}
}
@@ -69,8 +69,7 @@ export class BackendService {
checkpointTime?: string,
pageNumber?: number,
resultsPerPage?: number,
totalResults?: number,
searchTerm?: string
totalResults?: number
): Observable<DomainListResult> {
var url = `/console-api/domain-list?registrarId=${registrarId}`;
if (checkpointTime) {
@@ -85,9 +84,6 @@ export class BackendService {
if (totalResults) {
url += `&totalResults=${totalResults}`;
}
if (searchTerm) {
url += `&searchTerm=${searchTerm}`;
}
return this.http
.get<DomainListResult>(url)
.pipe(catchError((err) => this.errorCatcher<DomainListResult>(err)));
@@ -16,8 +16,8 @@ package google.registry.bsa;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.Maps.transformValues;
import static google.registry.model.tld.Tld.isEnrolledWithBsa;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
@@ -50,6 +50,14 @@ public class IdnChecker {
allTlds = idnToTlds.values().stream().flatMap(ImmutableSet::stream).collect(toImmutableSet());
}
// TODO(11/30/2023): Remove below when new Tld schema is deployed and the `getBsaEnrollStartTime`
// method is no longer hardcoded.
@VisibleForTesting
IdnChecker(ImmutableMap<IdnTableEnum, ImmutableSet<Tld>> idnToTlds) {
this.idnToTlds = idnToTlds;
allTlds = idnToTlds.values().stream().flatMap(ImmutableSet::stream).collect(toImmutableSet());
}
/** Returns all IDNs in which the {@code label} is valid. */
ImmutableSet<IdnTableEnum> getAllValidIdns(String label) {
return idnToTlds.keySet().stream()
@@ -80,6 +88,11 @@ public class IdnChecker {
return Sets.difference(allTlds, getSupportingTlds(idnTables));
}
private static boolean isEnrolledWithBsa(Tld tld, DateTime now) {
DateTime enrollTime = tld.getBsaEnrollStartTime();
return enrollTime != null && enrollTime.isBefore(now);
}
private static ImmutableMap<IdnTableEnum, ImmutableSet<Tld>> getIdnToTldMap(DateTime now) {
ImmutableMultimap.Builder<IdnTableEnum, Tld> idnToTldMap = new ImmutableMultimap.Builder();
Tlds.getTldEntitiesOfType(TldType.REAL).stream()
@@ -1,117 +0,0 @@
// 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.bsa.persistence;
import static google.registry.bsa.persistence.BsaDomainRefresh.Stage.MAKE_DIFF;
import com.google.common.base.Objects;
import google.registry.model.CreateAutoTimestamp;
import google.registry.model.UpdateAutoTimestamp;
import google.registry.persistence.VKey;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import org.joda.time.DateTime;
/**
* Records of completed and ongoing refresh actions, which recomputes the set of unblockable domains
* and reports changes to BSA.
*
* <p>The refresh action only handles registered and reserved domain names. Invalid names only
* change status when the IDN tables change, and will be handled by a separate tool when it happens.
*/
@Entity
public class BsaDomainRefresh {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long jobId;
@Column(nullable = false)
CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null);
@Column(nullable = false)
UpdateAutoTimestamp updateTime = UpdateAutoTimestamp.create(null);
@Column(nullable = false)
@Enumerated(EnumType.STRING)
Stage stage = MAKE_DIFF;
BsaDomainRefresh() {}
long getJobId() {
return jobId;
}
DateTime getCreationTime() {
return creationTime.getTimestamp();
}
/**
* Returns the starting time of this job as a string, which can be used as folder name on GCS when
* storing download data.
*/
public String getJobName() {
return "refresh-" + getCreationTime().toString();
}
public Stage getStage() {
return this.stage;
}
BsaDomainRefresh setStage(Stage stage) {
this.stage = stage;
return this;
}
VKey<BsaDomainRefresh> vKey() {
return vKey(this);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof BsaDomainRefresh)) {
return false;
}
BsaDomainRefresh that = (BsaDomainRefresh) o;
return Objects.equal(jobId, that.jobId)
&& Objects.equal(creationTime, that.creationTime)
&& Objects.equal(updateTime, that.updateTime)
&& stage == that.stage;
}
@Override
public int hashCode() {
return Objects.hashCode(jobId, creationTime, updateTime, stage);
}
static VKey vKey(BsaDomainRefresh bsaDomainRefresh) {
return VKey.create(BsaDomainRefresh.class, bsaDomainRefresh.jobId);
}
enum Stage {
MAKE_DIFF,
APPLY_DIFF,
REPORT_REMOVALS,
REPORT_ADDITIONS;
}
}
@@ -28,7 +28,7 @@ import org.joda.time.DateTime;
* <p>The label is valid (wrt IDN) in at least one TLD.
*/
@Entity
final class BsaLabel {
public final class BsaLabel {
@Id String label;
@@ -52,7 +52,7 @@ final class BsaLabel {
}
/** Returns the label to be blocked. */
String getLabel() {
public String getLabel() {
return label;
}
@@ -1,87 +0,0 @@
// 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.bsa.persistence;
import static google.registry.config.RegistryConfig.getEppResourceCachingDuration;
import static google.registry.config.RegistryConfig.getEppResourceMaxCachedEntries;
import static google.registry.model.CacheUtils.newCacheBuilder;
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.annotations.VisibleForTesting;
import google.registry.persistence.VKey;
import java.time.Duration;
import java.util.Map;
import java.util.Optional;
/** Helpers for {@link BsaLabel}. */
public final class BsaLabelUtils {
private BsaLabelUtils() {}
static final CacheLoader<VKey<BsaLabel>, Optional<BsaLabel>> CACHE_LOADER =
new CacheLoader<VKey<BsaLabel>, Optional<BsaLabel>>() {
@Override
public Optional<BsaLabel> load(VKey<BsaLabel> key) {
return replicaTm().reTransact(() -> replicaTm().loadByKeyIfPresent(key));
}
@Override
public Map<VKey<BsaLabel>, Optional<BsaLabel>> loadAll(
Iterable<? extends VKey<BsaLabel>> keys) {
// TODO(b/309173359): need this for DomainCheckFlow
throw new UnsupportedOperationException(
"LoadAll not supported by the BsaLabel cache loader.");
}
};
/**
* A limited size, limited expiry cache of BSA labels.
*
* <p>BSA labels are used by the domain creation flow to verify that the requested domain name is
* not blocked by the BSA program. Label caching is mainly a defense against two scenarios, the
* initial rush and drop-catching, when clients run back-to-back domain creation requests around
* the time when a domain becomes available.
*
* <p>Because of caching and the use of the replica database, new BSA labels installed in the
* database will not take effect immediately. A blocked domain may be created due to race
* condition. A `refresh` job will detect such domains and report them to BSA as unblockable
* domains.
*
* <p>Since the cached BSA labels have the same usage pattern as the cached EppResources, the
* cache configuration for the latter are reused here.
*/
private static LoadingCache<VKey<BsaLabel>, Optional<BsaLabel>> cacheBsaLabels =
createBsaLabelsCache(getEppResourceCachingDuration());
private static LoadingCache<VKey<BsaLabel>, Optional<BsaLabel>> createBsaLabelsCache(
Duration expiry) {
return newCacheBuilder(expiry)
.maximumSize(getEppResourceMaxCachedEntries())
.build(CACHE_LOADER);
}
@VisibleForTesting
void clearCache() {
cacheBsaLabels.invalidateAll();
}
/** Checks if the {@code domainLabel} (the leading `part` of a domain name) is blocked by BSA. */
public static boolean isLabelBlocked(String domainLabel) {
return cacheBsaLabels.get(BsaLabel.vKey(domainLabel)).isPresent();
}
}
@@ -40,7 +40,6 @@ import static google.registry.flows.domain.DomainFlowUtils.verifyClaimsNoticeIfA
import static google.registry.flows.domain.DomainFlowUtils.verifyClaimsPeriodNotEnded;
import static google.registry.flows.domain.DomainFlowUtils.verifyLaunchPhaseMatchesRegistryPhase;
import static google.registry.flows.domain.DomainFlowUtils.verifyNoCodeMarks;
import static google.registry.flows.domain.DomainFlowUtils.verifyNotBlockedByBsa;
import static google.registry.flows.domain.DomainFlowUtils.verifyNotReserved;
import static google.registry.flows.domain.DomainFlowUtils.verifyPremiumNameIsNotBlocked;
import static google.registry.flows.domain.DomainFlowUtils.verifyRegistrarIsActive;
@@ -169,7 +168,6 @@ import org.joda.time.Duration;
* @error {@link DomainFlowUtils.CurrencyUnitMismatchException}
* @error {@link DomainFlowUtils.CurrencyValueScaleException}
* @error {@link DomainFlowUtils.DashesInThirdAndFourthException}
* @error {@link DomainFlowUtils.DomainLabelBlockedByBsaException}
* @error {@link DomainFlowUtils.DomainLabelTooLongException}
* @error {@link DomainFlowUtils.DomainReservedException}
* @error {@link DomainFlowUtils.DuplicateContactForRoleException}
@@ -330,7 +328,6 @@ public final class DomainCreateFlow implements MutatingFlow {
.verifySignedMarks(launchCreate.get().getSignedMarks(), domainLabel, now)
.getId();
}
verifyNotBlockedByBsa(domainLabel, tld, now);
flowCustomLogic.afterValidation(
DomainCreateFlowCustomLogic.AfterValidationParameters.newBuilder()
.setDomainName(domainName)
@@ -25,13 +25,11 @@ import static com.google.common.collect.Iterables.any;
import static com.google.common.collect.Sets.difference;
import static com.google.common.collect.Sets.intersection;
import static com.google.common.collect.Sets.union;
import static google.registry.bsa.persistence.BsaLabelUtils.isLabelBlocked;
import static google.registry.model.domain.Domain.MAX_REGISTRATION_YEARS;
import static google.registry.model.tld.Tld.TldState.GENERAL_AVAILABILITY;
import static google.registry.model.tld.Tld.TldState.PREDELEGATION;
import static google.registry.model.tld.Tld.TldState.QUIET_PERIOD;
import static google.registry.model.tld.Tld.TldState.START_DATE_SUNRISE;
import static google.registry.model.tld.Tld.isEnrolledWithBsa;
import static google.registry.model.tld.Tlds.findTldForName;
import static google.registry.model.tld.Tlds.getTlds;
import static google.registry.model.tld.label.ReservationType.ALLOWED_IN_SUNRISE;
@@ -261,19 +259,6 @@ public class DomainFlowUtils {
return idnTableName.get();
}
/**
* Verifies that the {@code domainLabel} is not blocked by any BSA block label for the given
* {@code tld} at the specified time.
*
* @throws DomainLabelBlockedByBsaException
*/
public static void verifyNotBlockedByBsa(String domainLabel, Tld tld, DateTime now)
throws DomainLabelBlockedByBsaException {
if (isEnrolledWithBsa(tld, now) && isLabelBlocked(domainLabel)) {
throw new DomainLabelBlockedByBsaException();
}
}
/** Returns whether a given domain create request is for a valid anchor tenant. */
public static boolean isAnchorTenant(
InternetDomainName domainName,
@@ -1757,12 +1742,4 @@ public class DomainFlowUtils {
super("Registrar must be active in order to perform this operation");
}
}
/** Domain label is blocked by the Brand Safety Alliance. */
static class DomainLabelBlockedByBsaException extends ParameterValuePolicyErrorException {
public DomainLabelBlockedByBsaException() {
// TODO(b/309174065): finalize the exception message.
super("Domain label is blocked by the Brand Safety Alliance");
}
}
}
@@ -256,11 +256,6 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl
return VKey.create(Tld.class, tldStr);
}
/** Checks if {@code tld} is enrolled with BSA. */
public static boolean isEnrolledWithBsa(Tld tld, DateTime now) {
return tld.getBsaEnrollStartTime().orElse(END_OF_TIME).isBefore(now);
}
/**
* The name of the pricing engine that this TLD uses.
*
@@ -555,15 +550,9 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl
@JsonSerialize(using = SortedEnumSetSerializer.class)
Set<IdnTableEnum> idnTables;
/**
* The start time of this TLD's enrollment in the BSA program, if applicable.
*
* <p>This property is excluded from source-based configuration and is managed directly in the
* database.
*/
// TODO(b/309175410): implement setup and cleanup procedure for joining or leaving BSA, and see
// if it can be integrated with the ConfigTldCommand.
@JsonIgnore @Nullable DateTime bsaEnrollStartTime;
// TODO(11/30/2023): uncomment below two lines
// /** The start time of this TLD's enrollment in the BSA program, if applicable. */
// @JsonIgnore @Nullable DateTime bsaEnrollStartTime;
public String getTldStr() {
return tldStr;
@@ -585,9 +574,12 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl
}
/** Returns the time when this TLD was enrolled in the Brand Safety Alliance (BSA) program. */
@JsonIgnore
public Optional<DateTime> getBsaEnrollStartTime() {
return Optional.ofNullable(this.bsaEnrollStartTime);
@JsonIgnore // Annotation can be removed once we add the field and annotate it.
@Nullable
public DateTime getBsaEnrollStartTime() {
// TODO(11/30/2023): uncomment below.
// return this.bsaEnrollStartTime;
return null;
}
/** Retrieve whether invoicing is enabled. */
@@ -1109,9 +1101,10 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl
return this;
}
public Builder setBsaEnrollStartTime(Optional<DateTime> enrollTime) {
public Builder setBsaEnrollStartTime(DateTime enrollTime) {
// TODO(b/309175133): forbid if enrolled with BSA
getInstance().bsaEnrollStartTime = enrollTime.orElse(null);
// TODO(11/30/2023): uncomment below line
// getInstance().bsaEnrollStartTime = enrollTime;
return this;
}
@@ -33,7 +33,6 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;
import com.google.common.flogger.FluentLogger;
import com.google.common.flogger.StackSize;
import google.registry.config.RegistryEnvironment;
import google.registry.model.ImmutableObject;
import google.registry.persistence.JpaRetries;
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
@@ -165,10 +164,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
if (!getHibernateAllowNestedTransactions()) {
throw new IllegalStateException(NESTED_TRANSACTION_MESSAGE);
}
if (RegistryEnvironment.get() != RegistryEnvironment.PRODUCTION
&& RegistryEnvironment.get() != RegistryEnvironment.UNITTEST) {
logger.atWarning().withStackTrace(StackSize.MEDIUM).log(NESTED_TRANSACTION_MESSAGE);
}
logger.atWarning().withStackTrace(StackSize.MEDIUM).log(NESTED_TRANSACTION_MESSAGE);
// This prevents inner transaction from retrying, thus avoiding a cascade retry effect.
return transactNoRetry(work, isolationLevel);
}
@@ -14,6 +14,7 @@
package google.registry.reporting.icann;
import static com.google.api.client.http.HttpStatusCodes.STATUS_CODE_BAD_REQUEST;
import static com.google.api.client.http.HttpStatusCodes.STATUS_CODE_OK;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.net.MediaType.CSV_UTF_8;
@@ -37,7 +38,6 @@ import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.List;
import javax.inject.Inject;
@@ -90,31 +90,30 @@ public class IcannHttpReporter {
UrlConnectionUtils.setPayload(connection, reportBytes, CSV_UTF_8.toString());
connection.setInstanceFollowRedirects(false);
int responseCode = 0;
byte[] content = null;
int responseCode;
byte[] content;
try {
responseCode = connection.getResponseCode();
content = UrlConnectionUtils.getResponseBytes(connection);
if (responseCode != STATUS_CODE_OK) {
XjcIirdeaResult result = parseResult(content);
logger.atWarning().log(
"PUT rejected, status code %s:\n%s\n%s",
result.getCode().getValue(), result.getMsg(), result.getDescription());
// Only responses with a 200 or 400 status have a body. For everything else, we can return
// false early.
if (responseCode != STATUS_CODE_OK && responseCode != STATUS_CODE_BAD_REQUEST) {
logger.atWarning().log("Connection to ICANN server failed", connection);
return false;
}
} catch (IOException e) {
logger.atWarning().withCause(e).log(
"Connection to ICANN server failed with responseCode %s and connection %s",
responseCode == 0 ? "not available" : responseCode, connection);
return false;
} catch (XmlException e) {
logger.atWarning().withCause(e).log(
"Failed to parse ICANN response with responseCode %s and content %s",
responseCode, new String(content, StandardCharsets.UTF_8));
return false;
content = UrlConnectionUtils.getResponseBytes(connection);
} finally {
connection.disconnect();
}
// We know that an HTTP 200 response can only contain a result code of
// 1000 (i. e. success), there is no need to parse it.
// See: https://tools.ietf.org/html/draft-lozano-icann-registry-interfaces-13#page-16
if (responseCode != STATUS_CODE_OK) {
XjcIirdeaResult result = parseResult(content);
logger.atWarning().log(
"PUT rejected, status code %s:\n%s\n%s",
result.getCode().getValue(), result.getMsg(), result.getDescription());
return false;
}
return true;
}
@@ -165,5 +164,4 @@ public class IcannHttpReporter {
reportType));
}
}
}
@@ -53,7 +53,6 @@ import java.net.URL;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.util.List;
import java.util.Optional;
import java.util.Random;
import javax.inject.Inject;
import org.joda.time.Duration;
@@ -127,62 +126,55 @@ public final class NordnUploadAction implements Runnable {
phase.equals(PARAM_LORDN_PHASE_SUNRISE) || phase.equals(PARAM_LORDN_PHASE_CLAIMS),
"Invalid phase specified to NordnUploadAction: %s.",
phase);
Optional<URL> uploadUrl =
tm().transact(
() -> {
// Note here that we load all domains pending Nordn in one batch, which should not
// be a problem for the rate of domain registration that we see. If we anticipate
// a peak in claims during TLD launch (sunrise is NOT first-come-first-serve, so
// there should be no expectation of a peak during it), we can consider
// temporarily increasing the frequency of Nordn upload to reduce the size of each
// batch.
//
// We did not further divide the domains into smaller batches because the
// read-upload-write operation per small batch needs to be inside a single
// transaction to prevent race conditions, and running several uploads in rapid
// succession will likely overwhelm the MarksDB upload server, which recommends a
// maximum upload frequency of every 3 hours.
//
// See:
// https://datatracker.ietf.org/doc/html/draft-ietf-regext-tmch-func-spec-01#section-5.2.3.3
List<Domain> domains =
tm().createQueryComposer(Domain.class)
.where("lordnPhase", EQ, LordnPhase.valueOf(Ascii.toUpperCase(phase)))
.where("tld", EQ, tld)
.orderBy("creationTime")
.list();
if (domains.isEmpty()) {
return Optional.empty();
}
StringBuilder csv = new StringBuilder();
ImmutableList.Builder<Domain> newDomains = new ImmutableList.Builder<>();
tm().transact(
() -> {
// Note here that we load all domains pending Nordn in one batch, which should not
// be a problem for the rate of domain registration that we see. If we anticipate
// a peak in claims during TLD launch (sunrise is NOT first-come-first-serve, so
// there should be no expectation of a peak during it), we can consider temporarily
// increasing the frequency of Nordn upload to reduce the size of each batch.
//
// We did not further divide the domains into smaller batches because the
// read-upload-write operation per small batch needs to be inside a single
// transaction to prevent race conditions, and running several uploads in rapid
// sucession will likely overwhelm the MarksDB upload server, which recommands a
// maximum upload frequency of every 3 hours.
//
// See:
// https://datatracker.ietf.org/doc/html/draft-ietf-regext-tmch-func-spec-01#section-5.2.3.3
List<Domain> domains =
tm().createQueryComposer(Domain.class)
.where("lordnPhase", EQ, LordnPhase.valueOf(Ascii.toUpperCase(phase)))
.where("tld", EQ, tld)
.orderBy("creationTime")
.list();
if (domains.isEmpty()) {
return;
}
StringBuilder csv = new StringBuilder();
ImmutableList.Builder<Domain> newDomains = new ImmutableList.Builder<>();
domains.forEach(
domain -> {
if (phase.equals(PARAM_LORDN_PHASE_SUNRISE)) {
csv.append(getCsvLineForSunriseDomain(domain)).append('\n');
} else {
csv.append(getCsvLineForClaimsDomain(domain)).append('\n');
}
Domain newDomain =
domain.asBuilder().setLordnPhase(LordnPhase.NONE).build();
newDomains.add(newDomain);
});
String columns =
phase.equals(PARAM_LORDN_PHASE_SUNRISE) ? COLUMNS_SUNRISE : COLUMNS_CLAIMS;
String header =
String.format("1,%s,%d\n%s\n", clock.nowUtc(), domains.size(), columns);
try {
URL url =
uploadCsvToLordn(String.format("/LORDN/%s/%s", tld, phase), header + csv);
tm().updateAll(newDomains.build());
return Optional.of(url);
} catch (IOException | GeneralSecurityException e) {
throw new RuntimeException(e);
}
});
uploadUrl.ifPresent(
url -> cloudTasksUtils.enqueue(NordnVerifyAction.QUEUE, makeVerifyTask(url)));
domains.forEach(
domain -> {
if (phase.equals(PARAM_LORDN_PHASE_SUNRISE)) {
csv.append(getCsvLineForSunriseDomain(domain)).append('\n');
} else {
csv.append(getCsvLineForClaimsDomain(domain)).append('\n');
}
Domain newDomain = domain.asBuilder().setLordnPhase(LordnPhase.NONE).build();
newDomains.add(newDomain);
});
String columns =
phase.equals(PARAM_LORDN_PHASE_SUNRISE) ? COLUMNS_SUNRISE : COLUMNS_CLAIMS;
String header =
String.format("1,%s,%d\n%s\n", clock.nowUtc(), domains.size(), columns);
try {
uploadCsvToLordn(String.format("/LORDN/%s/%s", tld, phase), header + csv);
} catch (IOException | GeneralSecurityException e) {
throw new RuntimeException(e);
}
tm().updateAll(newDomains.build());
});
}
/**
@@ -194,7 +186,7 @@ public final class NordnUploadAction implements Runnable {
* @see <a href="http://tools.ietf.org/html/draft-lozano-tmch-func-spec-08#section-6.3">TMCH
* functional specifications - LORDN File</a>
*/
private URL uploadCsvToLordn(String urlPath, String csvData)
private void uploadCsvToLordn(String urlPath, String csvData)
throws IOException, GeneralSecurityException {
String url = tmchMarksdbUrl + urlPath;
logger.atInfo().log(
@@ -230,7 +222,7 @@ public final class NordnUploadAction implements Runnable {
actionLogId),
connection);
}
return new URL(location);
cloudTasksUtils.enqueue(NordnVerifyAction.QUEUE, makeVerifyTask(new URL(location)));
} catch (IOException e) {
throw new IOException(String.format("Error connecting to MarksDB at URL %s", url), e);
} finally {
@@ -72,9 +72,9 @@ public class ConfigureTldCommand extends MutatingCommand {
boolean breakglass;
@Parameter(
names = {"-d", "--dry_run"},
names = {"-d", "--dryrun"},
description = "Does not execute the entity mutation")
boolean dryRun;
boolean dryrun;
@Inject ObjectMapper mapper;
@@ -122,13 +122,6 @@ public class ConfigureTldCommand extends MutatingCommand {
checkPremiumList(newTld);
checkDnsWriters(newTld);
checkCurrency(newTld);
// bsaEnrollStartTime only exists in DB. Need to carry it over to the updated copy. See Tld.java
// for more information.
Optional<DateTime> bsaEnrollTime =
Optional.ofNullable(oldTld).flatMap(Tld::getBsaEnrollStartTime);
if (bsaEnrollTime.isPresent()) {
newTld = newTld.asBuilder().setBsaEnrollStartTime(bsaEnrollTime).build();
}
// Set the new TLD to breakglass mode if breakglass flag was used
if (breakglass) {
newTld = newTld.asBuilder().setBreakglassMode(true).build();
@@ -138,7 +131,7 @@ public class ConfigureTldCommand extends MutatingCommand {
@Override
protected boolean dontRunCommand() {
if (dryRun) {
if (dryrun) {
return true;
}
if (!newDiff) {
@@ -67,12 +67,9 @@ abstract class UpdateOrDeleteAllocationTokensCommand extends ConfirmingCommand {
checkArgument(!prefix.isEmpty(), "Provided prefix should not be blank");
return tm().transact(
() ->
tm().query(
"SELECT token FROM AllocationToken WHERE token LIKE :prefix",
String.class)
.setParameter("prefix", String.format("%s%%", prefix))
.getResultStream()
.map(token -> VKey.create(AllocationToken.class, token))
tm().loadAllOf(AllocationToken.class).stream()
.filter(token -> token.getToken().startsWith(prefix))
.map(AllocationToken::createVKey)
.collect(toImmutableList()));
}
}
@@ -18,7 +18,6 @@ import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.util.ListNamingUtils.convertFilePathToName;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.base.Strings;
import google.registry.model.tld.label.PremiumList;
@@ -30,14 +29,6 @@ import java.nio.file.Files;
@Parameters(separators = " =", commandDescription = "Update a PremiumList in Database.")
class UpdatePremiumListCommand extends CreateOrUpdatePremiumListCommand {
@Parameter(
names = {"-d", "--dry_run"},
description = "Does not execute the entity mutation")
boolean dryRun;
// indicates if there is a new change made by this command
private boolean newChange = false;
@Override
protected String prompt() throws Exception {
name = Strings.isNullOrEmpty(name) ? convertFilePathToName(inputFile) : name;
@@ -52,23 +43,8 @@ class UpdatePremiumListCommand extends CreateOrUpdatePremiumListCommand {
checkArgument(!inputData.isEmpty(), "New premium list data cannot be empty");
currency = existingList.getCurrency();
PremiumList updatedPremiumList = PremiumListUtils.parseToPremiumList(name, currency, inputData);
if (!existingList
.getLabelsToPrices()
.entrySet()
.equals(updatedPremiumList.getLabelsToPrices().entrySet())) {
newChange = true;
return String.format(
"Update premium list for %s?\n Old List: %s\n New List: %s",
name, existingList, updatedPremiumList);
} else {
return String.format(
"This update contains no changes to the premium list for %s.\n List Contents: %s",
name, existingList);
}
}
@Override
protected boolean dontRunCommand() {
return dryRun || !newChange;
return String.format(
"Update premium list for %s?\n Old List: %s\n New List: %s",
name, existingList, updatedPremiumList);
}
}
@@ -38,7 +38,6 @@
<mapping-file>META-INF/orm.xml</mapping-file>
<class>google.registry.bsa.persistence.BsaDomainRefresh</class>
<class>google.registry.bsa.persistence.BsaDownload</class>
<class>google.registry.bsa.persistence.BsaLabel</class>
<class>google.registry.bsa.persistence.BsaDomainInUse</class>
@@ -15,65 +15,38 @@
package google.registry.bsa;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.persistResource;
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 com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import google.registry.model.tld.Tld;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationWithCoverageExtension;
import google.registry.testing.FakeClock;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
/** Unit tests for {@link IdnChecker}. */
@ExtendWith(MockitoExtension.class)
public class IdnCheckerTest {
FakeClock fakeClock = new FakeClock();
@RegisterExtension
final JpaIntegrationWithCoverageExtension jpa =
new JpaTestExtensions.Builder().withClock(fakeClock).buildIntegrationWithCoverageExtension();
Tld jaonly;
Tld jandelatin;
Tld strictlatin;
@Mock Tld jaonly;
@Mock Tld jandelatin;
@Mock Tld strictlatin;
IdnChecker idnChecker;
@BeforeEach
void setup() {
jaonly = createTld("jaonly");
jandelatin = createTld("jandelatin");
strictlatin = createTld("strictlatin");
jaonly =
persistResource(
jaonly
.asBuilder()
.setBsaEnrollStartTime(Optional.of(fakeClock.nowUtc()))
.setIdnTables(ImmutableSet.of(JA))
.build());
jandelatin =
persistResource(
jandelatin
.asBuilder()
.setBsaEnrollStartTime(Optional.of(fakeClock.nowUtc()))
.setIdnTables(ImmutableSet.of(JA, EXTENDED_LATIN))
.build());
strictlatin =
persistResource(
strictlatin
.asBuilder()
.setBsaEnrollStartTime(Optional.of(fakeClock.nowUtc()))
.setIdnTables(ImmutableSet.of(UNCONFUSABLE_LATIN))
.build());
fakeClock.advanceOneMilli();
idnChecker = new IdnChecker(fakeClock);
idnChecker =
new IdnChecker(
ImmutableMap.of(
JA,
ImmutableSet.of(jandelatin, jaonly),
EXTENDED_LATIN,
ImmutableSet.of(jandelatin),
UNCONFUSABLE_LATIN,
ImmutableSet.of(strictlatin)));
}
@Test
@@ -1,54 +0,0 @@
// 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.bsa.persistence;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.bsa.persistence.BsaDomainRefresh.Stage.MAKE_DIFF;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static org.joda.time.DateTimeZone.UTC;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationWithCoverageExtension;
import google.registry.testing.FakeClock;
import org.joda.time.DateTime;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit test for {@link BsaDomainRefresh}. */
public class BsaDomainRefreshTest {
protected FakeClock fakeClock = new FakeClock(DateTime.now(UTC));
@RegisterExtension
final JpaIntegrationWithCoverageExtension jpa =
new JpaTestExtensions.Builder().withClock(fakeClock).buildIntegrationWithCoverageExtension();
@Test
void saveJob() {
BsaDomainRefresh persisted =
tm().transact(() -> tm().getEntityManager().merge(new BsaDomainRefresh()));
assertThat(persisted.jobId).isNotNull();
assertThat(persisted.creationTime.getTimestamp()).isEqualTo(fakeClock.nowUtc());
assertThat(persisted.stage).isEqualTo(MAKE_DIFF);
}
@Test
void loadJobByKey() {
BsaDomainRefresh persisted =
tm().transact(() -> tm().getEntityManager().merge(new BsaDomainRefresh()));
assertThat(tm().transact(() -> tm().loadByKey(BsaDomainRefresh.vKey(persisted))))
.isEqualTo(persisted);
}
}
@@ -41,15 +41,4 @@ public class BsaLabelTest {
assertThat(persisted.getLabel()).isEqualTo("label");
assertThat(persisted.creationTime).isEqualTo(fakeClock.nowUtc());
}
@Test
void isLabelBlocked_no() {
assertThat(tm().transact(() -> BsaLabelUtils.isLabelBlocked("abc"))).isFalse();
}
@Test
void isLabelBlocked_yes() {
tm().transact(() -> tm().put(new BsaLabel("abc", fakeClock.nowUtc())));
assertThat(tm().transact(() -> BsaLabelUtils.isLabelBlocked("abc"))).isTrue();
}
}
@@ -1,29 +0,0 @@
// 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.bsa.persistence;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import org.joda.time.DateTime;
/** Testing utils for users of {@link BsaLabel}. */
public final class BsaLabelTestingUtils {
private BsaLabelTestingUtils() {}
public static void persistBsaLabel(String domainLabel, DateTime creationTime) {
tm().transact(() -> tm().put(new BsaLabel(domainLabel, creationTime)));
}
}
@@ -1,102 +0,0 @@
// 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.bsa.persistence;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.bsa.persistence.BsaLabelTestingUtils.persistBsaLabel;
import static google.registry.bsa.persistence.BsaLabelUtils.isLabelBlocked;
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.setJpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.setReplicaJpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static org.joda.time.DateTimeZone.UTC;
import static org.joda.time.Duration.millis;
import static org.joda.time.Duration.standardMinutes;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationWithCoverageExtension;
import google.registry.persistence.transaction.JpaTransactionManager;
import google.registry.testing.FakeClock;
import org.joda.time.DateTime;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link BsaLabelUtils}. */
public class BsaLabelUtilsTest {
protected FakeClock fakeClock = new FakeClock(DateTime.now(UTC));
@RegisterExtension
final JpaIntegrationWithCoverageExtension jpa =
new JpaTestExtensions.Builder().withClock(fakeClock).buildIntegrationWithCoverageExtension();
@Test
void isLabelBlocked_yes() {
persistBsaLabel("abc", fakeClock.nowUtc());
assertThat(isLabelBlocked("abc")).isTrue();
}
@Test
void isLabelBlocked_no() {
assertThat(isLabelBlocked("abc")).isFalse();
}
@Test
void isLabelBlocked_isCacheUsed_withReplica() throws Throwable {
JpaTransactionManager primaryTmSave = tm();
JpaTransactionManager replicaTmSave = replicaTm();
JpaTransactionManager primaryTm = mock(JpaTransactionManager.class);
JpaTransactionManager replicaTm = mock(JpaTransactionManager.class);
setJpaTm(() -> primaryTm);
setReplicaJpaTm(() -> replicaTm);
when(replicaTm.loadByKey(any())).thenReturn(new BsaLabel("abc", fakeClock.nowUtc()));
try {
assertThat(isLabelBlocked("abc")).isTrue();
assertThat(isLabelBlocked("abc")).isTrue();
verify(replicaTm, times(1)).loadByKey(any());
verify(primaryTm, never()).loadByKey(any());
} catch (Throwable e) {
setJpaTm(() -> primaryTmSave);
setReplicaJpaTm(() -> replicaTmSave);
}
}
@Test
void isLabelBlocked_isCacheUsed_withOneMinuteExpiry() throws Throwable {
JpaTransactionManager replicaTmSave = replicaTm();
JpaTransactionManager replicaTm = mock(JpaTransactionManager.class);
setReplicaJpaTm(() -> replicaTm);
when(replicaTm.loadByKey(any())).thenReturn(new BsaLabel("abc", fakeClock.nowUtc()));
try {
assertThat(isLabelBlocked("abc")).isTrue();
/**
* If test fails, check and fix cache expiry in the config file. Do not increase the duration
* on the line below without proper discussion.
*/
fakeClock.advanceBy(standardMinutes(1).plus(millis(1)));
assertThat(isLabelBlocked("abc")).isTrue();
verify(replicaTm, times(2)).loadByKey(any());
} catch (Throwable e) {
setReplicaJpaTm(() -> replicaTmSave);
}
}
}
@@ -18,7 +18,6 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.io.BaseEncoding.base16;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static google.registry.bsa.persistence.BsaLabelTestingUtils.persistBsaLabel;
import static google.registry.flows.FlowTestCase.UserPrivileges.SUPERUSER;
import static google.registry.model.billing.BillingBase.Flag.ANCHOR_TENANT;
import static google.registry.model.billing.BillingBase.Flag.RESERVED;
@@ -31,7 +30,6 @@ import static google.registry.model.domain.token.AllocationToken.TokenType.BULK_
import static google.registry.model.domain.token.AllocationToken.TokenType.DEFAULT_PROMO;
import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE;
import static google.registry.model.domain.token.AllocationToken.TokenType.UNLIMITED_USE;
import static google.registry.model.eppcommon.EppXmlTransformer.marshal;
import static google.registry.model.eppcommon.StatusValue.PENDING_DELETE;
import static google.registry.model.eppcommon.StatusValue.SERVER_HOLD;
import static google.registry.model.tld.Tld.TldState.GENERAL_AVAILABILITY;
@@ -98,7 +96,6 @@ import google.registry.flows.domain.DomainFlowUtils.ClaimsPeriodEndedException;
import google.registry.flows.domain.DomainFlowUtils.CurrencyUnitMismatchException;
import google.registry.flows.domain.DomainFlowUtils.CurrencyValueScaleException;
import google.registry.flows.domain.DomainFlowUtils.DashesInThirdAndFourthException;
import google.registry.flows.domain.DomainFlowUtils.DomainLabelBlockedByBsaException;
import google.registry.flows.domain.DomainFlowUtils.DomainLabelTooLongException;
import google.registry.flows.domain.DomainFlowUtils.DomainNameExistsAsTldException;
import google.registry.flows.domain.DomainFlowUtils.DomainReservedException;
@@ -168,9 +165,6 @@ import google.registry.model.domain.secdns.DomainDsData;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationToken.RegistrationBehavior;
import google.registry.model.domain.token.AllocationToken.TokenStatus;
import google.registry.model.eppcommon.Trid;
import google.registry.model.eppoutput.EppOutput;
import google.registry.model.eppoutput.EppResponse;
import google.registry.model.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse;
import google.registry.model.poll.PollMessage;
import google.registry.model.registrar.Registrar;
@@ -189,9 +183,7 @@ import google.registry.tmch.LordnTaskUtils.LordnPhase;
import google.registry.tmch.SmdrlCsvParser;
import google.registry.tmch.TmchData;
import google.registry.tmch.TmchTestData;
import google.registry.xml.ValidationMode;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nullable;
@@ -2570,53 +2562,6 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testSuccess_bsaLabelMatch_notEnrolled() throws Exception {
persistResource(Tld.get("tld").asBuilder().setBsaEnrollStartTime(Optional.empty()).build());
persistBsaLabel("example", clock.nowUtc());
persistContactsAndHosts();
doSuccessfulTest();
}
@Test
void testSuccess_bsaLabelMatch_notEnrolledYet() throws Exception {
persistResource(
Tld.get("tld")
.asBuilder()
.setBsaEnrollStartTime(Optional.of(clock.nowUtc().plusSeconds(1)))
.build());
persistBsaLabel("example", clock.nowUtc());
persistContactsAndHosts();
doSuccessfulTest();
}
@Test
void testFailure_blockedByBsa() throws Exception {
persistResource(
Tld.get("tld")
.asBuilder()
.setBsaEnrollStartTime(Optional.of(clock.nowUtc().minusSeconds(1)))
.build());
persistBsaLabel("example", clock.nowUtc());
persistContactsAndHosts();
EppException thrown = assertThrows(DomainLabelBlockedByBsaException.class, this::runFlow);
assertAboutEppExceptions()
.that(thrown)
.marshalsToXml()
.and()
.hasMessage("Domain label is blocked by the Brand Safety Alliance");
byte[] responseXmlBytes =
marshal(
EppOutput.create(
new EppResponse.Builder()
.setTrid(Trid.create(null, "server-trid"))
.setResult(thrown.getResult())
.build()),
ValidationMode.STRICT);
assertThat(new String(responseXmlBytes, StandardCharsets.UTF_8))
.isEqualTo(loadFile("domain_create_blocked_by_bsa.xml"));
}
@Test
void testFailure_uppercase() {
doFailingDomainNameTest("Example.tld", BadDomainNameCharacterException.class);
@@ -17,7 +17,6 @@ package google.registry.schema.integration;
import static com.google.common.truth.Truth.assert_;
import google.registry.bsa.persistence.BsaDomainInUseTest;
import google.registry.bsa.persistence.BsaDomainRefreshTest;
import google.registry.bsa.persistence.BsaDownloadTest;
import google.registry.bsa.persistence.BsaLabelTest;
import google.registry.model.billing.BillingBaseTest;
@@ -87,7 +86,6 @@ import org.junit.runner.RunWith;
AllocationTokenTest.class,
BillingBaseTest.class,
BsaDomainInUseTest.class,
BsaDomainRefreshTest.class,
BsaDownloadTest.class,
BsaLabelTest.class,
BulkPricingPackageTest.class,
@@ -14,9 +14,9 @@
package google.registry.tools;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static google.registry.model.EntityYamlUtils.createObjectMapper;
import static google.registry.model.domain.token.AllocationToken.TokenType.DEFAULT_PROMO;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.persistPremiumList;
import static google.registry.testing.DatabaseHelper.persistResource;
@@ -46,7 +46,6 @@ import google.registry.model.tld.Tld.TldNotFoundException;
import google.registry.model.tld.label.PremiumList;
import google.registry.model.tld.label.PremiumListDao;
import java.io.File;
import java.util.Optional;
import java.util.logging.Logger;
import org.joda.money.Money;
import org.joda.time.DateTime;
@@ -102,19 +101,20 @@ public class ConfigureTldCommandTest extends CommandTestCase<ConfigureTldCommand
assertThat(updatedTld.getCreateBillingCost()).isEqualTo(Money.of(USD, 25));
testTldConfiguredSuccessfully(updatedTld, "tld.yaml");
assertThat(updatedTld.getBreakglassMode()).isFalse();
assertThat(tld.getBsaEnrollStartTime()).isEmpty();
assertThat(tld.getBsaEnrollStartTime()).isNull();
}
@Test
void testSuccess_updateTld_existingBsaTimeCarriedOver() throws Exception {
void testSuccess_updateTld_bsaTimeUnaffected() throws Exception {
Tld tld = createTld("tld");
DateTime bsaStartTime = DateTime.now(DateTimeZone.UTC);
persistResource(tld.asBuilder().setBsaEnrollStartTime(Optional.of(bsaStartTime)).build());
tm().transact(() -> tm().put(tld.asBuilder().setBsaEnrollStartTime(bsaStartTime).build()));
File tldFile = tmpDir.resolve("tld.yaml").toFile();
Files.asCharSink(tldFile, UTF_8).write(loadFile(getClass(), "tld.yaml"));
runCommandForced("--input=" + tldFile);
Tld updatedTld = Tld.get("tld");
assertThat(updatedTld.getBsaEnrollStartTime()).hasValue(bsaStartTime);
// TODO(11/30/2023): uncomment below two lines
// Tld updatedTld = Tld.get("tld");
// assertThat(tld.getBsaEnrollStartTime()).isEqualTo(bsaStartTime);
}
@Test
@@ -593,7 +593,7 @@ public class ConfigureTldCommandTest extends CommandTestCase<ConfigureTldCommand
void testSuccess_dryRunOnCreate_noChanges() throws Exception {
File tldFile = tmpDir.resolve("tld.yaml").toFile();
Files.asCharSink(tldFile, UTF_8).write(loadFile(getClass(), "tld.yaml"));
runCommandForced("--input=" + tldFile, "--dry_run");
runCommandForced("--input=" + tldFile, "--dryrun");
assertThrows(TldNotFoundException.class, () -> Tld.get("tld"));
}
@@ -63,19 +63,10 @@ class UpdatePremiumListCommandTest<C extends UpdatePremiumListCommand>
UpdatePremiumListCommand command = new UpdatePremiumListCommand();
command.inputFile = Paths.get(tmpFile.getPath());
command.name = TLD_TEST;
command.prompt();
assertThat(command.prompt()).contains("Update premium list for prime?");
}
@Test
void commandPrompt_successStageNoChange() throws Exception {
File tmpFile = tmpDir.resolve(String.format("%s.txt", TLD_TEST)).toFile();
UpdatePremiumListCommand command = new UpdatePremiumListCommand();
command.inputFile = Paths.get(tmpFile.getPath());
command.name = TLD_TEST;
assertThat(command.prompt())
.contains("This update contains no changes to the premium list for prime.");
}
@Test
void commandRun_successUpdateList() throws Exception {
File tmpFile = tmpDir.resolve(String.format("%s.txt", TLD_TEST)).toFile();
@@ -92,18 +83,6 @@ class UpdatePremiumListCommandTest<C extends UpdatePremiumListCommand>
.containsExactly(PremiumEntry.create(0L, new BigDecimal("9999.00"), "eth"));
}
@Test
void commandRun_successNoChange() throws Exception {
File tmpFile = tmpDir.resolve(String.format("%s.txt", TLD_TEST)).toFile();
UpdatePremiumListCommand command = new UpdatePremiumListCommand();
command.inputFile = Paths.get(tmpFile.getPath());
runCommandForced("--name=" + TLD_TEST, "--input=" + command.inputFile);
assertThat(PremiumListDao.loadAllPremiumEntries(TLD_TEST))
.containsExactly(PremiumEntry.create(0L, new BigDecimal("9090.00"), "doge"));
}
@Test
void commandRun_successUpdateList_whenExistingListIsEmpty() throws Exception {
File existingPremiumFile = tmpDir.resolve(TLD_TEST + ".txt").toFile();
@@ -190,19 +169,4 @@ class UpdatePremiumListCommandTest<C extends UpdatePremiumListCommand>
.hasMessageThat()
.isEqualTo("Could not update premium list random3 because it doesn't exist");
}
@Test
void commandDryRun_noChangesMade() throws Exception {
File tmpFile = tmpDir.resolve(String.format("%s.txt", TLD_TEST)).toFile();
String newPremiumListData = "eth,USD 9999";
Files.asCharSink(tmpFile, UTF_8).write(newPremiumListData);
UpdatePremiumListCommand command = new UpdatePremiumListCommand();
command.inputFile = Paths.get(tmpFile.getPath());
runCommandForced("--name=" + TLD_TEST, "--input=" + command.inputFile, "--dry_run");
assertThat(PremiumListDao.loadAllPremiumEntries(TLD_TEST))
.comparingElementsUsing(immutableObjectCorrespondence("revisionId"))
.containsExactly(PremiumEntry.create(0L, new BigDecimal("9090.00"), "doge"));
}
}
@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp xmlns:domain="urn:ietf:params:xml:ns:domain-1.0" xmlns:contact="urn:ietf:params:xml:ns:contact-1.0" xmlns:fee="urn:ietf:params:xml:ns:fee-0.6" xmlns="urn:ietf:params:xml:ns:epp-1.0" xmlns:rgp="urn:ietf:params:xml:ns:rgp-1.0" xmlns:bulkToken="urn:google:params:xml:ns:bulkToken-1.0" xmlns:fee11="urn:ietf:params:xml:ns:fee-0.11" xmlns:fee12="urn:ietf:params:xml:ns:fee-0.12" xmlns:launch="urn:ietf:params:xml:ns:launch-1.0" xmlns:secDNS="urn:ietf:params:xml:ns:secDNS-1.1" xmlns:host="urn:ietf:params:xml:ns:host-1.0">
<response>
<result code="2306">
<msg>Domain label is blocked by the Brand Safety Alliance</msg>
</result>
<trID>
<svTRID>server-trid</svTRID>
</trID>
</response>
</epp>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-1
View File
@@ -149,4 +149,3 @@ V148__add_bsa_download_and_label_tables.sql
V149__add_bsa_domain_in_use_table.sql
V150__add_tld_bsa_enroll_date.sql
V151__add_bsa_unblockable_domain_table.sql
V152__add_bsa_domain_refresh_table.sql
@@ -1,21 +0,0 @@
-- 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.
CREATE TABLE "BsaDomainRefresh" (
job_id bigserial not null,
creation_time timestamptz not null,
stage text not null,
update_timestamp timestamptz,
primary key (job_id)
);
@@ -93,14 +93,6 @@
primary key (label, tld)
);
create table "BsaDomainRefresh" (
job_id bigserial not null,
creation_time timestamptz not null,
stage text not null,
update_timestamp timestamptz,
primary key (job_id)
);
create table "BsaDownload" (
job_id bigserial not null,
block_list_checksums text not null,
@@ -736,7 +728,6 @@
auto_renew_grace_period_length interval not null,
automatic_transfer_length interval not null,
breakglass_mode boolean not null,
bsa_enroll_start_time timestamptz,
claims_period_end timestamptz not null,
create_billing_cost_amount numeric(19, 2),
create_billing_cost_currency text,
@@ -135,37 +135,6 @@ CREATE TABLE public."BsaDomainInUse" (
);
--
-- Name: BsaDomainRefresh; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public."BsaDomainRefresh" (
job_id bigint NOT NULL,
creation_time timestamp with time zone NOT NULL,
stage text NOT NULL,
update_timestamp timestamp with time zone
);
--
-- Name: BsaDomainRefresh_job_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
CREATE SEQUENCE public."BsaDomainRefresh_job_id_seq"
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: BsaDomainRefresh_job_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--
ALTER SEQUENCE public."BsaDomainRefresh_job_id_seq" OWNED BY public."BsaDomainRefresh".job_id;
--
-- Name: BsaDownload; Type: TABLE; Schema: public; Owner: -
--
@@ -1253,13 +1222,6 @@ CREATE SEQUENCE public.project_wide_unique_id_seq
CACHE 10;
--
-- Name: BsaDomainRefresh job_id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."BsaDomainRefresh" ALTER COLUMN job_id SET DEFAULT nextval('public."BsaDomainRefresh_job_id_seq"'::regclass);
--
-- Name: BsaDownload job_id; Type: DEFAULT; Schema: public; Owner: -
--
@@ -1377,14 +1339,6 @@ ALTER TABLE ONLY public."BsaDomainInUse"
ADD CONSTRAINT "BsaDomainInUse_pkey" PRIMARY KEY (label, tld);
--
-- Name: BsaDomainRefresh BsaDomainRefresh_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."BsaDomainRefresh"
ADD CONSTRAINT "BsaDomainRefresh_pkey" PRIMARY KEY (job_id);
--
-- Name: BsaDownload BsaDownload_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
-1
View File
@@ -384,7 +384,6 @@ An EPP flow that creates a new domain resource.
* The requested fees cannot be provided in the requested currency.
* Non-IDN domain names cannot contain hyphens in the third or fourth
position.
* Domain label is blocked by the Brand Safety Alliance.
* Domain labels cannot be longer than 63 characters.
* More than one contact for a given role is not allowed.
* No part of a domain name can be empty.