Compare commits

...
Author SHA1 Message Date
Pavlo TkachandGitHub 45331be166 Add redirect to the new console from the old console for tech support (#2514) 2024-07-31 17:16:12 +00:00
gbrodmanandGitHub beb7c14adb Drop not-null constraint on UserUpdateHistory:user_id (#2513)
Dependency Submission / dependency-submission (push) Successful in 3m55s
CodeQL / Analyze (java) (push) Failing after 3m42s
CodeQL / Analyze (javascript) (push) Failing after 52s
CodeQL / Analyze (python) (push) Failing after 50s
This is nullable now that we're switching from using an ID field to
using the email address as the primary identifier.
2024-07-30 19:19:29 +00:00
gbrodmanandGitHub d33571dde3 Change pkey of User to emailAddress (#2505)
CodeQL / Analyze (java) (push) Failing after 1m22s
CodeQL / Analyze (javascript) (push) Failing after 1m13s
CodeQL / Analyze (python) (push) Failing after 51s
Dependency Submission / dependency-submission (push) Successful in 2m11s
Originally, we though that User entities were going to have mutable
email addresses, and thus would require a non-changing primary key. This
proved to not be the case. It'll simplify the User loading/saving code
if we just do everything by email address.

Obviously this doesn't change much functionality, but it prepares us for
removing the id field down the line once the changes propagate.
2024-07-29 22:27:06 +00:00
53a7d1b66c Add feature flag for new console release #2511 (#2512)
* Add feature flag for new console release

* Run feature flag query in a transaction

---------

Co-authored-by: ptkach <ptkach@google.com>
2024-07-29 21:55:12 +00:00
Pavlo TkachandGitHub fa721e82ff Mark console state field on new registrar form as required (#2509)
CodeQL / Analyze (java) (push) Failing after 58s
CodeQL / Analyze (javascript) (push) Failing after 56s
CodeQL / Analyze (python) (push) Failing after 53s
Dependency Submission / dependency-submission (push) Successful in 2m12s
2024-07-26 18:46:43 +00:00
Lai JiangandGitHub d4faa77ee4 Remove login_email_address from RegistrarPoc (#2507) 2024-07-26 17:56:34 +00:00
sarahcaseybotandGitHub 96d3d88c2f Remove TODOs assigned to sarahbot (#2508) 2024-07-26 17:50:35 +00:00
Pavlo TkachandGitHub 213e06f02e Add registry lock ui (#2500) 2024-07-26 16:02:19 +00:00
48 changed files with 1351 additions and 1101 deletions
+2
View File
@@ -27,6 +27,7 @@ import { provideHttpClient } from '@angular/common/http';
import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';
import { BillingInfoComponent } from './billingInfo/billingInfo.component';
import { DomainListComponent } from './domains/domainList.component';
import { RegistryLockComponent } from './domains/registryLock.component';
import { HeaderComponent } from './header/header.component';
import { HomeComponent } from './home/home.component';
import { NavigationComponent } from './navigation/navigation.component';
@@ -71,6 +72,7 @@ import { RegistryLockVerifyComponent } from './lock/registryLockVerify.component
NotificationsComponent,
RegistrarComponent,
RegistrarDetailsComponent,
RegistryLockComponent,
RegistrarSelectorComponent,
RegistryLockVerifyComponent,
ResourcesComponent,
@@ -2,6 +2,17 @@
<div class="console-app-domains">
<h1 class="mat-headline-4">Domains</h1>
<div
class="console-app-domains__actions-wrapper"
[hidden]="!domainListService.activeActionComponent"
>
<ng-container
v-if="domainListService.activeActionComponent"
*ngComponentOutlet="domainListService.activeActionComponent"
>
</ng-container>
</div>
@if (!isLoading && totalResults == 0) {
<div class="console-app__empty-domains">
<h1>
@@ -12,7 +23,18 @@
<h1>No domains found</h1>
</div>
} @else {
<div class="console-app__domains-table-parent">
<mat-menu #actions="matMenu">
<ng-template matMenuContent let-domainName="domainName">
<button mat-menu-item (click)="openRegistryLock(domainName)">
<mat-icon>key</mat-icon>
<span>Registry Lock</span>
</button>
</ng-template>
</mat-menu>
<div
class="console-app__domains-table-parent"
[hidden]="domainListService.activeActionComponent"
>
<div class="console-app__scrollable-wrapper">
<div class="console-app__scrollable">
@if (isLoading) {
@@ -78,6 +100,29 @@
}}</mat-cell>
</ng-container>
<ng-container matColumnDef="registryLock">
<mat-header-cell *matHeaderCellDef
>Registry-Locked</mat-header-cell
>
<mat-cell *matCellDef="let element">{{
isDomainLocked(element.domainName)
}}</mat-cell>
</ng-container>
<ng-container matColumnDef="actions">
<mat-header-cell *matHeaderCellDef>Actions</mat-header-cell>
<mat-cell *matCellDef="let element">
<button
mat-icon-button
[matMenuTriggerFor]="actions"
[matMenuTriggerData]="{ domainName: element.domainName }"
aria-label="Domain actions"
>
<mat-icon>more_horiz</mat-icon>
</button>
</mat-cell>
</ng-container>
<mat-header-row
*matHeaderRowDef="displayedColumns"
></mat-header-row>
@@ -85,7 +130,7 @@
<!-- Row shown when there is no matching data. -->
<mat-row *matNoDataRow>
<mat-cell colspan="4">No domains found</mat-cell>
<mat-cell colspan="6">No domains found</mat-cell>
</mat-row>
</mat-table>
<mat-paginator
@@ -30,6 +30,12 @@
&__domains-table {
min-width: $min-width !important;
.mat-column-actions {
max-width: 100px;
}
.mat-column-registryLock {
max-width: 150px;
}
}
&__domains-spinner {
@@ -19,14 +19,14 @@ import { MatSnackBar } from '@angular/material/snack-bar';
import { MatTableDataSource } from '@angular/material/table';
import { Subject, debounceTime } from 'rxjs';
import { RegistrarService } from '../registrar/registrar.service';
import { BackendService } from '../shared/services/backend.service';
import { Domain, DomainListService } from './domainList.service';
import { RegistryLockComponent } from './registryLock.component';
import { RegistryLockService } from './registryLock.service';
@Component({
selector: 'app-domain-list',
templateUrl: './domainList.component.html',
styleUrls: ['./domainList.component.scss'],
providers: [DomainListService],
})
export class DomainListComponent {
public static PATH = 'domain-list';
@@ -37,6 +37,8 @@ export class DomainListComponent {
'creationTime',
'registrationExpirationTime',
'statuses',
'registryLock',
'actions',
];
dataSource: MatTableDataSource<Domain> = new MatTableDataSource();
@@ -52,15 +54,16 @@ export class DomainListComponent {
@ViewChild(MatPaginator, { static: true }) paginator!: MatPaginator;
constructor(
private backendService: BackendService,
private domainListService: DomainListService,
protected domainListService: DomainListService,
protected registrarService: RegistrarService,
protected registryLockService: RegistryLockService,
private _snackBar: MatSnackBar
) {
effect(() => {
this.pageNumber = 0;
this.totalResults = 0;
if (this.registrarService.registrarId()) {
this.loadLocks();
this.reloadData();
}
});
@@ -80,6 +83,25 @@ export class DomainListComponent {
this.searchTermSubject.complete();
}
openRegistryLock(domainName: string) {
this.domainListService.selectedDomain = domainName;
this.domainListService.activeActionComponent = RegistryLockComponent;
}
loadLocks() {
this.registryLockService.retrieveLocks().subscribe({
error: (err: HttpErrorResponse) => {
this._snackBar.open(err.message);
},
});
}
isDomainLocked(domainName: string) {
return this.registryLockService.domainsLocks.some(
(d) => d.domainName === domainName
);
}
reloadData() {
this.isLoading = true;
this.domainListService
@@ -95,7 +117,7 @@ export class DomainListComponent {
this.isLoading = false;
},
next: (domainListResult) => {
this.dataSource.data = (domainListResult || {}).domains;
this.dataSource.data = this.domainListService.domainsList;
this.totalResults = (domainListResult || {}).totalResults || 0;
this.isLoading = false;
},
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Type } from '@angular/core';
import { tap } from 'rxjs';
import { RegistrarService } from '../registrar/registrar.service';
import { BackendService } from '../shared/services/backend.service';
@@ -35,9 +35,14 @@ export interface DomainListResult {
totalResults: number;
}
@Injectable()
@Injectable({
providedIn: 'root',
})
export class DomainListService {
checkpointTime?: string;
selectedDomain?: string;
public activeActionComponent: Type<any> | null = null;
public domainsList: Domain[] = [];
constructor(
private backendService: BackendService,
@@ -62,6 +67,7 @@ export class DomainListService {
.pipe(
tap((domainListResult: DomainListResult) => {
this.checkpointTime = domainListResult?.checkpointTime;
this.domainsList = domainListResult?.domains;
})
);
}
@@ -0,0 +1,81 @@
<div class="console-app__registry-lock">
<p>
<button
mat-icon-button
aria-label="Back to domains list"
(click)="goBack()"
>
<mat-icon>arrow_back</mat-icon>
</button>
</p>
@if(!registrarService.registrar()?.registryLockAllowed) {
<h1>
Sorry, your registrar hasn't enrolled in registry lock yet. To do so, please
contact {{ userDataService.userData()?.supportEmail }}.
</h1>
} @else if (isLocked()) {
<h1>Unlock the domain {{ domainListService.selectedDomain }}</h1>
<form (ngSubmit)="save(false)" [formGroup]="unlockDomain">
<p>
<mat-label for="password">Password: </mat-label>
<mat-form-field name="password" appearance="outline">
<input matInput type="text" formControlName="password" required />
</mat-form-field>
</p>
<p>
<mat-label for="relockTime"
>Automatically re-lock the domain after:</mat-label
>
<mat-radio-group
name="relockTime"
formControlName="relockTime"
aria-label="Automatically relock option"
>
@for (option of relockOptions; track option.name) {
<mat-radio-button [value]="option.duration">{{
option.name
}}</mat-radio-button>
}
</mat-radio-group>
</p>
<div class="console-app__registry-lock-notification">
<mat-icon>priority_high</mat-icon>Confirmation email will be sent to your
email address to confirm the unlock
</div>
<button
mat-flat-button
color="primary"
type="submit"
[disabled]="!unlockDomain.valid"
>
Save
</button>
</form>
} @else {
<h1>Lock the domain {{ domainListService.selectedDomain }}</h1>
<form (ngSubmit)="save(true)" [formGroup]="lockDomain">
<p>
<mat-label for="password">Password: </mat-label>
<mat-form-field name="password" appearance="outline">
<input matInput type="text" formControlName="password" required />
</mat-form-field>
</p>
<div class="console-app__registry-lock-notification">
<mat-icon>priority_high</mat-icon>The lock will not take effect until you
click the confirmation link that will be emailed to you. When it takes
effect, you will be billed the standard server status change billing cost.
</div>
<button
mat-flat-button
color="primary"
type="submit"
[disabled]="!lockDomain.valid"
>
Save
</button>
</form>
}
</div>
@@ -0,0 +1,20 @@
.console-app {
&__registry-lock {
mat-label {
display: block;
margin-bottom: 10px;
}
p {
margin-bottom: 40px;
}
}
&__registry-lock-notification {
padding: 20px;
border-radius: 10px;
background-color: var(--light-highlight);
margin-bottom: 20px;
width: max-content;
display: flex;
align-items: center;
}
}
@@ -0,0 +1,92 @@
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { HttpErrorResponse } from '@angular/common/http';
import { Component, computed } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { MatSnackBar } from '@angular/material/snack-bar';
import { RegistrarService } from '../registrar/registrar.service';
import { UserDataService } from '../shared/services/userData.service';
import { DomainListService } from './domainList.service';
import { RegistryLockService } from './registryLock.service';
@Component({
selector: 'app-registry-lock',
templateUrl: './registryLock.component.html',
styleUrls: ['./registryLock.component.scss'],
})
export class RegistryLockComponent {
readonly isLocked = computed(() =>
this.registryLockService.domainsLocks.some(
(dl) => dl.domainName === this.domainListService.selectedDomain
)
);
relockOptions = [
{ name: '1 hour', duration: 3600000 },
{ name: '6 hours', duration: 21600000 },
{ name: '24 hours', duration: 86400000 },
{ name: 'Never', duration: undefined },
];
lockDomain = new FormGroup({
password: new FormControl(''),
});
unlockDomain = new FormGroup({
password: new FormControl(''),
relockTime: new FormControl(undefined),
});
constructor(
protected registrarService: RegistrarService,
protected domainListService: DomainListService,
protected registryLockService: RegistryLockService,
protected userDataService: UserDataService,
private _snackBar: MatSnackBar
) {}
goBack() {
this.domainListService.selectedDomain = undefined;
this.domainListService.activeActionComponent = null;
}
save(isLock: boolean) {
let request;
if (!isLock) {
request = this.registryLockService.registryLockDomain(
this.domainListService.selectedDomain || '',
this.unlockDomain.value.password || '',
this.unlockDomain.value.relockTime || undefined,
isLock
);
} else {
request = this.registryLockService.registryLockDomain(
this.domainListService.selectedDomain || '',
this.lockDomain.value.password || '',
undefined,
isLock
);
}
request.subscribe({
complete: () => {
this.goBack();
},
error: (err: HttpErrorResponse) => {
this._snackBar.open(err.error);
},
});
}
}
@@ -0,0 +1,59 @@
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { tap } from 'rxjs';
import { RegistrarService } from '../registrar/registrar.service';
import { BackendService } from '../shared/services/backend.service';
export interface DomainLocksResult {
domainName: string;
}
@Injectable({
providedIn: 'root',
})
export class RegistryLockService {
public domainsLocks: DomainLocksResult[] = [];
constructor(
private backendService: BackendService,
private registrarService: RegistrarService
) {}
retrieveLocks() {
return this.backendService
.getLocks(this.registrarService.registrarId())
.pipe(
tap((domainLocksResult) => {
this.domainsLocks = domainLocksResult;
})
);
}
registryLockDomain(
domainName: string,
password: string,
relockDurationMillis: number | undefined,
isLock: boolean
) {
return this.backendService.registryLockDomain(
domainName,
password,
relockDurationMillis,
this.registrarService.registrarId(),
isLock
);
}
}
@@ -34,7 +34,10 @@
</mat-card-actions>
</mat-card>
<mat-card appearance="outlined">
<mat-card
appearance="outlined"
[elementId]="getElementIdForRegistrarsBlock()"
>
<mat-card-content>
<h3>
<mat-icon class="secondary-text">account_circle</mat-icon>
@@ -18,6 +18,7 @@ import { DomainListComponent } from '../domains/domainList.component';
import { RegistrarComponent } from '../registrar/registrarsTable.component';
import SecurityComponent from '../settings/security/security.component';
import { SettingsComponent } from '../settings/settings.component';
import { RESTRICTED_ELEMENTS } from '../shared/directives/userLevelVisiblity.directive';
import { BreakPointObserverService } from '../shared/services/breakPoint.service';
@Component({
@@ -30,6 +31,9 @@ export class HomeComponent {
protected breakPointObserverService: BreakPointObserverService,
private router: Router
) {}
getElementIdForRegistrarsBlock() {
return RESTRICTED_ELEMENTS.REGISTRAR_ELEMENT;
}
viewRegistrars() {
this.router.navigate([RegistrarComponent.PATH], {
queryParamsHandling: 'merge',
@@ -142,7 +142,7 @@ JPY=billing-id-for-yen"
<mat-label>State/Region: </mat-label>
<input
matInput
[required]="false"
[required]="true"
[(ngModel)]="newRegistrar.localizedAddress.state"
[ngModelOptions]="{ standalone: true }"
/>
@@ -4,7 +4,7 @@
<div class="console-app__resources-subhead">Technical resources</div>
<a
class="text-l"
href="{{ userDataService.userData.technicalDocsUrl }}"
href="{{ userDataService.userData()?.technicalDocsUrl }}"
target="_blank"
>View onboarding FAQs, TLD information, and technical documentation on
Google Drive</a
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Directive, ElementRef, Input, OnChanges } from '@angular/core';
import { Directive, ElementRef, Input, effect } from '@angular/core';
import { UserDataService } from '../services/userData.service';
export enum RESTRICTED_ELEMENTS {
@@ -26,29 +26,28 @@ export const DISABLED_ELEMENTS_PER_ROLE = {
@Directive({
selector: '[elementId]',
})
export class UserLevelVisibility implements OnChanges {
export class UserLevelVisibility {
@Input() elementId!: RESTRICTED_ELEMENTS | null;
constructor(
private userDataService: UserDataService,
private el: ElementRef
) {}
ngOnChanges() {
this.processElement();
) {
effect(this.processElement.bind(this));
}
processElement() {
const globalRole = this.userDataService?.userData()?.globalRole || 'NONE';
if (this.elementId === null) {
return;
}
const globalRole = this.userDataService?.userData?.globalRole || 'NONE';
if (
// @ts-ignore
(DISABLED_ELEMENTS_PER_ROLE[globalRole] || []).includes(this.elementId)
) {
this.el.nativeElement.style.display = 'none';
} else {
this.el.nativeElement.style.display = '';
}
}
}
@@ -17,6 +17,8 @@ import { Injectable } from '@angular/core';
import { Observable, catchError, of, throwError } from 'rxjs';
import { DomainListResult } from 'src/app/domains/domainList.service';
import { DomainLocksResult } from 'src/app/domains/registryLock.service';
import { RegistryLockVerificationResponse } from 'src/app/lock/registryLockVerify.service';
import {
Registrar,
SecuritySettingsBackendModel,
@@ -25,7 +27,6 @@ import {
import { Contact } from '../../settings/contact/contact.service';
import { EppPasswordBackendModel } from '../../settings/security/security.service';
import { UserData } from './userData.service';
import { RegistryLockVerificationResponse } from 'src/app/lock/registryLockVerify.service';
@Injectable()
export class BackendService {
@@ -171,6 +172,32 @@ export class BackendService {
);
}
registryLockDomain(
domainName: string,
password: string | undefined,
relockDurationMillis: number | undefined,
registrarId: string,
isLock: boolean
) {
return this.http.post(
`/console-api/registry-lock?registrarId=${registrarId}`,
{
domainName,
password,
isLock,
relockDurationMillis,
}
);
}
getLocks(registrarId: string): Observable<DomainLocksResult[]> {
return this.http
.get<DomainLocksResult[]>(
`/console-api/registry-lock?registrarId=${registrarId}`
)
.pipe(catchError((err) => this.errorCatcher<DomainLocksResult[]>(err)));
}
verifyRegistryLockRequest(
lockVerificationCode: string
): Observable<RegistryLockVerificationResponse> {
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, signal } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Observable, tap } from 'rxjs';
import { BackendService } from './backend.service';
@@ -33,7 +33,7 @@ export interface UserData {
providedIn: 'root',
})
export class UserDataService implements GlobalLoader {
public userData!: UserData;
userData = signal<UserData | undefined>(undefined);
constructor(
private backend: BackendService,
protected globalLoader: GlobalLoaderService,
@@ -48,7 +48,7 @@ export class UserDataService implements GlobalLoader {
getUserData(): Observable<UserData> {
return this.backend.getUserData().pipe(
tap((userData: UserData) => {
this.userData = userData;
this.userData.set(userData);
})
);
}
@@ -17,9 +17,11 @@
For general purpose questions once you are integrated with our registry
system. If the issue is urgent, please put "Urgent" in the email title.
</p>
<a class="text-l" href="mailto:{{ userDataService.userData.supportEmail }}">{{
userDataService.userData.supportEmail
}}</a>
<a
class="text-l"
href="mailto:{{ userDataService.userData()?.supportEmail }}"
>{{ userDataService.userData()?.supportEmail }}</a
>
<p class="secondary-text">
Note: You may receive occasional service announcements via
registrar-announcement&#64;google.com. You will not be able to reply to
@@ -29,13 +31,13 @@
<p class="text-l">For general support inquiries 24/7:</p>
<a
class="text-l"
href="tel:{{ userDataService.userData.supportPhoneNumber }}"
>{{ userDataService.userData.supportPhoneNumber }}</a
href="tel:{{ userDataService.userData()?.supportPhoneNumber }}"
>{{ userDataService.userData()?.supportPhoneNumber }}</a
>
@if (userDataService.userData.passcode) {
@if (userDataService.userData()?.passcode) {
<p class="text-l">Your telephone passcode:</p>
<p class="text-l console-app__support-passcode">
{{ userDataService.userData.passcode }}
{{ userDataService.userData()?.passcode }}
</p>
<p class="secondary-text">
Note: Please be ready with your account name and telephone passcode when
@@ -64,7 +64,7 @@ import org.joda.time.Duration;
auth = Auth.AUTH_ADMIN)
public class DeleteProberDataAction implements Runnable {
// TODO(b/323026070): Add email alert on failure of this action
// TODO(b/346390641): Add email alert on failure of this action
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
/**
@@ -266,7 +266,6 @@ public final class DomainCreateFlow implements MutatingFlow {
validateLaunchCreateNotice(launchCreate.get().getNotice(), domainLabel, isSuperuser, now);
}
boolean isSunriseCreate = hasSignedMarks && (tldState == START_DATE_SUNRISE);
// TODO(sarahbot@): Add check for valid EPP actions on the token
Optional<AllocationToken> allocationToken =
allocationTokenFlowUtils.verifyAllocationTokenCreateIfPresent(
command,
@@ -63,7 +63,8 @@ public class FeatureFlag extends ImmutableObject implements Buildable {
public enum FeatureName {
TEST_FEATURE,
MINIMUM_DATASET_CONTACTS_OPTIONAL,
MINIMUM_DATASET_CONTACTS_PROHIBITED
MINIMUM_DATASET_CONTACTS_PROHIBITED,
NEW_CONSOLE
}
/** The name of the flag/feature. */
@@ -14,9 +14,11 @@
package google.registry.model.console;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.tools.server.UpdateUserGroupAction.GROUP_UPDATE_QUEUE;
import com.google.cloud.tasks.v2.Task;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.flogger.FluentLogger;
import google.registry.batch.CloudTasksUtils;
@@ -27,25 +29,25 @@ import google.registry.tools.server.UpdateUserGroupAction;
import google.registry.tools.server.UpdateUserGroupAction.Mode;
import google.registry.util.RegistryEnvironment;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.Table;
/** A console user, either a registry employee or a registrar partner. */
@Embeddable
@Entity
@Table(indexes = {@Index(columnList = "emailAddress", name = "user_email_address_idx")})
@Table
public class User extends UserBase {
public static final String IAP_SECURED_WEB_APP_USER_ROLE = "roles/iap.httpsResourceAccessor";
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@VisibleForTesting public static final AtomicLong ID_GENERATOR_FOR_TESTING = new AtomicLong();
/**
* Grants the user permission to pass IAP.
*
@@ -114,13 +116,18 @@ public class User extends UserBase {
}
@Override
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Access(AccessType.PROPERTY)
public Long getId() {
return super.getId();
}
@Id
@Override
@Access(AccessType.PROPERTY)
public String getEmailAddress() {
return super.getEmailAddress();
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
@@ -139,5 +146,20 @@ public class User extends UserBase {
public Builder(User user) {
super(user);
}
@Override
public User build() {
// Sets the ID temporarily until we can get rid of the non-null constraint (and the field)
if (getInstance().getId() == null || getInstance().getId().equals(0L)) {
// In tests, we cannot guarantee that the database is fully set up -- so don't use it to
// generate a new long
if (RegistryEnvironment.get() == RegistryEnvironment.UNITTEST) {
getInstance().setId(ID_GENERATOR_FOR_TESTING.getAndIncrement());
} else {
getInstance().setId(tm().reTransact(tm()::allocateId));
}
}
return super.build();
}
}
}
@@ -53,8 +53,7 @@ public class UserBase extends UpdateAutoTimestampEntity implements Buildable {
@Transient private Long id;
/** Email address of the user in question. */
@Column(nullable = false)
String emailAddress;
@Transient String emailAddress;
/** Optional external email address to use for registry lock confirmation emails. */
@Column String registryLockEmailAddress;
@@ -90,6 +89,10 @@ public class UserBase extends UpdateAutoTimestampEntity implements Buildable {
this.id = id;
}
void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
public String getEmailAddress() {
return emailAddress;
}
@@ -14,7 +14,6 @@
package google.registry.model.console;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import java.util.Optional;
@@ -32,23 +31,8 @@ public class UserDao {
.findFirst());
}
/** Saves the given user, checking that no existing user already exists with this email. */
/** Saves the given user, updating it if it already exists. */
public static void saveUser(User user) {
tm().transact(
() -> {
// Check for an existing user (the unique constraint protects us, but this gives a
// nicer exception)
Optional<User> maybeSavedUser = loadUser(user.getEmailAddress());
if (maybeSavedUser.isPresent()) {
User savedUser = maybeSavedUser.get();
checkArgument(
savedUser.getId().equals(user.getId()),
String.format(
"Attempted save of User with email address %s and ID %s, user with that"
+ " email already exists with ID %s",
user.getEmailAddress(), user.getId(), savedUser.getId()));
}
tm().put(user);
});
tm().transact(() -> tm().put(user));
}
}
@@ -42,6 +42,9 @@ public class UserUpdateHistory extends ConsoleUpdateHistory {
@Column(nullable = false, name = "userId")
Long id;
@Column(nullable = false, name = "emailAddress")
String emailAddress;
public UserBase getUser() {
return user;
}
@@ -49,6 +52,7 @@ public class UserUpdateHistory extends ConsoleUpdateHistory {
@PostLoad
void postLoad() {
user.setId(id);
user.setEmailAddress(emailAddress);
}
/** Creates a {@link VKey} instance for this entity. */
@@ -80,6 +84,7 @@ public class UserUpdateHistory extends ConsoleUpdateHistory {
public Builder setUser(User user) {
getInstance().user = user;
getInstance().id = user.getId();
getInstance().emailAddress = user.getEmailAddress();
return this;
}
}
@@ -139,7 +139,7 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda
/** Token saved on a TLD to use if no other token is passed from the client */
DEFAULT_PROMO(/* isOneTimeUse= */ false),
/** This is the old name for what is now BULK_PRICING. */
// TODO(sarahbot@): Remove this type once all tokens of this type have been scrubbed from the
// TODO(b/261763205): Remove this type once all tokens of this type have been scrubbed from the
// database
@Deprecated
PACKAGE(/* isOneTimeUse= */ false),
@@ -25,8 +25,6 @@ import javax.persistence.AccessType;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Index;
import javax.persistence.Table;
/**
* A contact for a Registrar. Note, equality, hashCode and comparable have been overridden to only
@@ -37,7 +35,6 @@ import javax.persistence.Table;
* set to true.
*/
@Entity
@Table(indexes = @Index(columnList = "loginEmailAddress", name = "registrarpoc_login_email_idx"))
@IdClass(RegistrarPocId.class)
@Access(AccessType.FIELD)
public class RegistrarPoc extends RegistrarPocBase {
@@ -98,12 +98,7 @@ public class RegistrarPocBase extends ImmutableObject implements Jsonifiable, Un
/** The name of the contact. */
@Expose String name;
/**
* The contact email address of the contact.
*
* <p>This is different from the login email which is assgined to the regstrar and cannot be
* changed.
*/
/** The contact email address of the contact. */
@Expose @Transient String emailAddress;
@Expose @Transient public String registrarId;
@@ -123,9 +118,6 @@ public class RegistrarPocBase extends ImmutableObject implements Jsonifiable, Un
*/
@Expose Set<Type> types;
/** A GAIA email address that was assigned to the registrar for console login purpose. */
String loginEmailAddress;
/**
* Whether this contact is publicly visible in WHOIS registrar query results as an Admin contact.
*/
@@ -219,10 +211,6 @@ public class RegistrarPocBase extends ImmutableObject implements Jsonifiable, Un
return visibleInDomainWhoisAsAbuse;
}
public String getLoginEmailAddress() {
return loginEmailAddress;
}
public Builder<? extends RegistrarPocBase, ?> asBuilder() {
return new Builder<>(clone(this));
}
@@ -286,13 +274,6 @@ public class RegistrarPocBase extends ImmutableObject implements Jsonifiable, Un
+ "Registrar Abuse contact info: ")
.append(getVisibleInDomainWhoisAsAbuse() ? "Yes" : "No")
.append('\n');
result
.append("Registrar-Console access: ")
.append(getLoginEmailAddress() != null ? "Yes" : "No")
.append('\n');
if (getLoginEmailAddress() != null) {
result.append("Login Email Address: ").append(getLoginEmailAddress()).append('\n');
}
return result.toString();
}
@@ -310,7 +291,6 @@ public class RegistrarPocBase extends ImmutableObject implements Jsonifiable, Un
.put("visibleInDomainWhoisAsAbuse", visibleInDomainWhoisAsAbuse)
.put("allowedToSetRegistryLockPassword", allowedToSetRegistryLockPassword)
.put("registryLockAllowed", isRegistryLockAllowed())
.put("loginEmailAddress", loginEmailAddress)
.build();
}
@@ -411,11 +391,6 @@ public class RegistrarPocBase extends ImmutableObject implements Jsonifiable, Un
return thisCastToDerived();
}
public B setLoginEmailAddress(String loginEmailAddress) {
getInstance().loginEmailAddress = loginEmailAddress;
return thisCastToDerived();
}
public B setAllowedToSetRegistryLockPassword(boolean allowedToSetRegistryLockPassword) {
if (allowedToSetRegistryLockPassword) {
getInstance().registryLockPasswordSalt = null;
@@ -293,7 +293,7 @@ class GenerateAllocationTokensCommand implements Command {
// tokens should only be scheduled to end with a brief time period before the status
// transition occurs so that no new domains are registered using that token between when the
// status is scheduled and when the transition occurs.
// TODO(@sarahbot): Create a cleaner way to handle ending bulk pricing packages once we
// TODO(b/261763205): Create a cleaner way to handle ending bulk pricing packages once we
// actually have customers using them
boolean hasEnding =
tokenStatusTransitions.containsValue(TokenStatus.ENDED)
@@ -41,7 +41,6 @@ import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nullable;
@@ -87,12 +86,6 @@ final class RegistrarPocCommand extends MutatingCommand {
+ " and will be used as the console login email, if --login_email is not specified.")
String email;
@Nullable
@Parameter(
names = "--login_email",
description = "Console login email address. If not specified, --email will be used.")
String loginEmail;
@Nullable
@Parameter(
names = "--registry_lock_email",
@@ -115,13 +108,6 @@ final class RegistrarPocCommand extends MutatingCommand {
validateWith = OptionalPhoneNumberParameter.class)
private Optional<String> fax;
@Nullable
@Parameter(
names = "--allow_console_access",
description = "Enable or disable access to the registrar console for this contact.",
arity = 1)
Boolean allowConsoleAccess;
@Nullable
@Parameter(
names = "--visible_in_whois_as_admin",
@@ -173,7 +159,7 @@ final class RegistrarPocCommand extends MutatingCommand {
protected void init() throws Exception {
checkArgument(mainParameters.size() == 1,
"Must specify exactly one client identifier: %s", ImmutableList.copyOf(mainParameters));
String clientId = mainParameters.get(0);
String clientId = mainParameters.getFirst();
Registrar registrar =
checkArgumentPresent(
Registrar.loadByRegistrarId(clientId), "Registrar %s not found", clientId);
@@ -261,9 +247,6 @@ final class RegistrarPocCommand extends MutatingCommand {
}
builder.setTypes(nullToEmpty(contactTypes));
if (Objects.equals(allowConsoleAccess, Boolean.TRUE)) {
builder.setLoginEmailAddress(loginEmail == null ? email : loginEmail);
}
if (visibleInWhoisAsAdmin != null) {
builder.setVisibleInWhoisAsAdmin(visibleInWhoisAsAdmin);
}
@@ -308,13 +291,6 @@ final class RegistrarPocCommand extends MutatingCommand {
if (visibleInDomainWhoisAsAbuse != null) {
builder.setVisibleInDomainWhoisAsAbuse(visibleInDomainWhoisAsAbuse);
}
if (allowConsoleAccess != null) {
if (allowConsoleAccess.equals(Boolean.TRUE)) {
builder.setLoginEmailAddress(loginEmail == null ? email : loginEmail);
} else {
builder.setLoginEmailAddress(null);
}
}
if (allowedToSetRegistryLockPassword != null) {
builder.setAllowedToSetRegistryLockPassword(allowedToSetRegistryLockPassword);
}
@@ -193,9 +193,6 @@ public final class RegistrarFormFields {
public static final FormField<String, String> CONTACT_FAX_NUMBER_FIELD =
FormFields.PHONE_NUMBER.asBuilderNamed("faxNumber").build();
public static final FormField<String, String> CONTACT_LOGIN_EMAIL_ADDRESS_FIELD =
FormFields.NAME.asBuilderNamed("loginEmailAddress").build();
public static final FormField<Object, Boolean> CONTACT_ALLOWED_TO_SET_REGISTRY_LOCK_PASSWORD =
FormField.named("allowedToSetRegistryLockPassword", Object.class)
.transform(Boolean.class, b -> Boolean.valueOf(Objects.toString(b)))
@@ -384,8 +381,6 @@ public final class RegistrarFormFields {
builder.setPhoneNumber(CONTACT_PHONE_NUMBER_FIELD.extractUntyped(args).orElse(null));
builder.setFaxNumber(CONTACT_FAX_NUMBER_FIELD.extractUntyped(args).orElse(null));
builder.setTypes(CONTACT_TYPES.extractUntyped(args).orElse(ImmutableSet.of()));
builder.setLoginEmailAddress(
CONTACT_LOGIN_EMAIL_ADDRESS_FIELD.extractUntyped(args).orElse(null));
// The parser is inconsistent with whether it retrieves boolean values as strings or booleans.
// As a result, use a potentially-redundant converter that can deal with both.
builder.setAllowedToSetRegistryLockPassword(
@@ -16,6 +16,9 @@ package google.registry.ui.server.console;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.model.common.FeatureFlag.FeatureName.NEW_CONSOLE;
import static google.registry.model.common.FeatureFlag.isActiveNow;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.Action.Method.GET;
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
@@ -88,7 +91,10 @@ public abstract class ConsoleApiAction implements Runnable {
!GlobalRole.NONE.equals(userRoles.getGlobalRole())
|| userRoles.hasPermission(
registryAdminClientId, ConsolePermission.VIEW_REGISTRAR_DETAILS);
if (RegistryEnvironment.get() != RegistryEnvironment.UNITTEST && !hasGlobalOrTestingRole) {
if (!hasGlobalOrTestingRole
&& RegistryEnvironment.get() != RegistryEnvironment.UNITTEST
&& tm().transact(() -> !isActiveNow(NEW_CONSOLE))) {
try {
consoleApiParams.response().sendRedirect(ConsoleUiAction.PATH);
return;
@@ -164,7 +164,6 @@ public class RegistrarsAction extends ConsoleApiAction {
.setRegistrar(registrar)
.setName(registrarParam.getEmailAddress())
.setEmailAddress(registrarParam.getEmailAddress())
.setLoginEmailAddress(registrarParam.getEmailAddress())
.build();
tm().transact(
@@ -14,10 +14,12 @@
package google.registry.ui.server.registrar;
import static com.google.common.base.Strings.isNullOrEmpty;
import static google.registry.request.auth.AuthenticatedRegistrarAccessor.Role.ADMIN;
import static google.registry.request.auth.AuthenticatedRegistrarAccessor.Role.OWNER;
import static google.registry.ui.server.SoyTemplateUtils.CSS_RENAMING_MAP_SUPPLIER;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static jakarta.servlet.http.HttpServletResponse.SC_PERMANENT_REDIRECT;
import static jakarta.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
import com.google.common.base.Supplier;
@@ -26,6 +28,7 @@ import com.google.common.flogger.FluentLogger;
import com.google.template.soy.data.SoyMapData;
import com.google.template.soy.tofu.SoyTofu;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.console.GlobalRole;
import google.registry.request.Action;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
@@ -35,8 +38,10 @@ import google.registry.request.auth.AuthenticatedRegistrarAccessor.Role;
import google.registry.ui.server.SoyTemplateUtils;
import google.registry.ui.soy.registrar.ConsoleSoyInfo;
import google.registry.util.RegistryEnvironment;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
import javax.inject.Inject;
/** Action that serves Registrar Console single HTML page (SPA). */
@@ -100,6 +105,7 @@ public final class ConsoleUiAction extends HtmlAction {
soyMapData.put("announcementsEmail", announcementsEmail);
soyMapData.put("supportPhoneNumber", supportPhoneNumber);
soyMapData.put("technicalDocsUrl", technicalDocsUrl);
if (!enabled) {
response.setStatus(SC_SERVICE_UNAVAILABLE);
response.setPayload(
@@ -111,6 +117,22 @@ public final class ConsoleUiAction extends HtmlAction {
.render());
return;
}
// Set permanent redirect to the new console for tech support
if (isNullOrEmpty(req.getParameter("redirect"))
&& Stream.of(GlobalRole.SUPPORT_LEAD, GlobalRole.SUPPORT_AGENT)
.anyMatch(
globalRole ->
globalRole.equals(authResult.user().get().getUserRoles().getGlobalRole()))) {
response.setStatus(SC_PERMANENT_REDIRECT);
try {
response.sendRedirect("/console");
return;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
ImmutableSetMultimap<String, Role> roleMap = registrarAccessor.getAllRegistrarIdsWithRoles();
soyMapData.put("allClientIds", roleMap.keySet());
soyMapData.put("environment", RegistryEnvironment.get().toString());
@@ -172,7 +172,6 @@ public class SyncRegistrarsSheetTest {
// distinction to make sure we're not relying on it. Sigh.
.setVisibleInWhoisAsAdmin(false)
.setVisibleInWhoisAsTech(true)
.setLoginEmailAddress("john.doe@example.tld")
.build(),
new RegistrarPoc.Builder()
.setRegistrar(registrar)
@@ -191,7 +190,7 @@ public class SyncRegistrarsSheetTest {
ImmutableList<ImmutableMap<String, String>> rows = getOnlyElement(rowsCaptor.getAllValues());
assertThat(rows).hasSize(2);
ImmutableMap<String, String> row = rows.get(0);
ImmutableMap<String, String> row = rows.getFirst();
assertThat(row).containsEntry("registrarId", "aaaregistrar");
assertThat(row).containsEntry("registrarName", "AAA Registrar Inc.");
assertThat(row).containsEntry("state", "SUSPENDED");
@@ -208,7 +207,6 @@ public class SyncRegistrarsSheetTest {
Visible in registrar WHOIS query as Technical contact: No
Phone number and email visible in domain WHOIS query as Registrar Abuse contact\
info: No
Registrar-Console access: No
John Doe
john.doe@example.tld
@@ -219,8 +217,6 @@ public class SyncRegistrarsSheetTest {
Visible in registrar WHOIS query as Technical contact: Yes
Phone number and email visible in domain WHOIS query as Registrar Abuse contact\
info: No
Registrar-Console access: Yes
Login Email Address: john.doe@example.tld
""");
assertThat(row)
.containsEntry(
@@ -233,7 +229,6 @@ public class SyncRegistrarsSheetTest {
Visible in registrar WHOIS query as Technical contact: No
Phone number and email visible in domain WHOIS query as Registrar Abuse contact\
info: No
Registrar-Console access: No
""");
assertThat(row).containsEntry("marketingContacts", "");
assertThat(row).containsEntry("abuseContacts", "");
@@ -251,7 +246,6 @@ public class SyncRegistrarsSheetTest {
Visible in registrar WHOIS query as Technical contact: No
Phone number and email visible in domain WHOIS query as Registrar Abuse contact\
info: No
Registrar-Console access: No
""");
assertThat(row).containsEntry("contactsMarkedAsWhoisAdmin", "");
assertThat(row)
@@ -267,8 +261,6 @@ public class SyncRegistrarsSheetTest {
Visible in registrar WHOIS query as Technical contact: Yes
Phone number and email visible in domain WHOIS query as Registrar Abuse contact\
info: No
Registrar-Console access: Yes
Login Email Address: john.doe@example.tld
""");
assertThat(row).containsEntry("emailAddress", "nowhere@example.org");
assertThat(row).containsEntry(
@@ -17,7 +17,6 @@ package google.registry.model.console;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static org.junit.jupiter.api.Assertions.assertThrows;
import google.registry.model.EntityTestCase;
import org.junit.jupiter.api.Test;
@@ -64,7 +63,7 @@ public class UserDaoTest extends EntityTestCase {
}
@Test
void testFailure_sameEmail() {
void testSuccess_updateUser_sameEmail() {
User user1 =
new User.Builder()
.setEmailAddress("email@email.com")
@@ -73,12 +72,12 @@ public class UserDaoTest extends EntityTestCase {
User user2 =
new User.Builder()
.setEmailAddress("email@email.com")
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.SUPPORT_AGENT).build())
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build())
.build();
UserDao.saveUser(user1);
assertThrows(IllegalArgumentException.class, () -> UserDao.saveUser(user2));
UserDao.saveUser(user2);
assertAboutImmutableObjects()
.that(user1)
.isEqualExceptFields(UserDao.loadUser("email@email.com").get(), "id", "updateTimestamp");
.that(user2)
.isEqualExceptFields(UserDao.loadUser("email@email.com").get(), "updateTimestamp");
}
}
@@ -426,7 +426,6 @@ public abstract class JpaTransactionManagerExtension
.setEmailAddress("johndoe@theregistrar.com")
.setPhoneNumber("+1.1234567890")
.setTypes(ImmutableSet.of(RegistrarPocBase.Type.ADMIN))
.setLoginEmailAddress("johndoe@theregistrar.com")
.build();
}
@@ -438,7 +437,6 @@ public abstract class JpaTransactionManagerExtension
.setRegistryLockEmailAddress("Marla.Singer.RegistryLock@crr.com")
.setPhoneNumber("+1.2128675309")
.setTypes(ImmutableSet.of(RegistrarPocBase.Type.TECH))
.setLoginEmailAddress("Marla.Singer@crr.com")
.setAllowedToSetRegistryLockPassword(true)
.setRegistryLockPassword("hi")
.build();
@@ -23,7 +23,8 @@ import com.google.common.io.Resources;
import google.registry.persistence.NomulusPostgreSql;
import java.io.File;
import java.nio.file.Path;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
@@ -33,26 +34,31 @@ import org.testcontainers.junit.jupiter.Testcontainers;
@Testcontainers
class GenerateSqlSchemaCommandTest extends CommandTestCase<GenerateSqlSchemaCommand> {
private String containerHostName;
private int containerPort;
private static String containerHostName;
private static int containerPort;
@Container
private static PostgreSQLContainer postgres =
new PostgreSQLContainer(NomulusPostgreSql.getDockerTag())
private static final PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>(NomulusPostgreSql.getDockerTag())
.withDatabaseName("postgres")
.withUsername("postgres")
.withPassword("domain-registry");
@BeforeEach
void beforeEach() {
containerHostName = postgres.getContainerIpAddress();
@BeforeAll
static void beforeAll() {
containerHostName = postgres.getHost();
containerPort = postgres.getMappedPort(GenerateSqlSchemaCommand.POSTGRESQL_PORT);
}
@AfterAll
static void afterAll() {
postgres.close();
}
@Test
void testSchemaGeneration() throws Exception {
runCommand(
"--out_file=" + tmpDir.resolve("schema.sql").toString(),
"--out_file=" + tmpDir.resolve("schema.sql"),
"--db_host=" + containerHostName,
"--db_port=" + containerPort);
@@ -69,7 +75,7 @@ class GenerateSqlSchemaCommandTest extends CommandTestCase<GenerateSqlSchemaComm
@Test
void testIncompatibleFlags() throws Exception {
runCommand(
"--out_file=" + tmpDir.resolve("schema.sql").toString(),
"--out_file=" + tmpDir.resolve("schema.sql"),
"--db_host=" + containerHostName,
"--db_port=" + containerPort,
"--start_postgresql");
@@ -79,14 +85,14 @@ class GenerateSqlSchemaCommandTest extends CommandTestCase<GenerateSqlSchemaComm
@Test
void testDockerPostgresql() throws Exception {
Path schemaFile = tmpDir.resolve("schema.sql");
runCommand("--start_postgresql", "--out_file=" + schemaFile.toString());
runCommand("--start_postgresql", "--out_file=" + schemaFile);
assertThat(schemaFile.toFile().exists()).isTrue();
}
@Test
void validateGeneratedSchemaIsSameAsSchemaInFile() throws Exception {
Path schemaFile = tmpDir.resolve("schema.sql");
runCommand("--start_postgresql", "--out_file=" + schemaFile.toString());
runCommand("--start_postgresql", "--out_file=" + schemaFile);
assertThat(schemaFile.toFile().toURI().toURL())
.hasSameContentAs(Resources.getResource("sql/schema/db-schema.sql.generated"));
}
@@ -28,6 +28,7 @@ public class GetUserCommandTest extends CommandTestCase<GetUserCommand> {
@BeforeEach
void beforeEach() {
User.ID_GENERATOR_FOR_TESTING.set(0L);
UserDao.saveUser(
new User.Builder()
.setEmailAddress("johndoe@theregistrar.com")
@@ -52,7 +53,7 @@ public class GetUserCommandTest extends CommandTestCase<GetUserCommand> {
"""
User: {
emailAddress=fte@google.com
id=2
id=1
registryLockEmailAddress=null
registryLockPasswordHash=null
registryLockPasswordSalt=null
@@ -75,7 +76,7 @@ public class GetUserCommandTest extends CommandTestCase<GetUserCommand> {
"""
User: {
emailAddress=johndoe@theregistrar.com
id=1
id=0
registryLockEmailAddress=null
registryLockPasswordHash=null
registryLockPasswordSalt=null
@@ -90,7 +91,7 @@ public class GetUserCommandTest extends CommandTestCase<GetUserCommand> {
}
User: {
emailAddress=fte@google.com
id=2
id=1
registryLockEmailAddress=null
registryLockPasswordHash=null
registryLockPasswordSalt=null
@@ -113,7 +114,7 @@ public class GetUserCommandTest extends CommandTestCase<GetUserCommand> {
"""
User: {
emailAddress=johndoe@theregistrar.com
id=1
id=0
registryLockEmailAddress=null
registryLockPasswordHash=null
registryLockPasswordSalt=null
@@ -68,8 +68,7 @@ class RegistrarPocCommandTest extends CommandTestCase<RegistrarPocCommand> {
"Visible in registrar WHOIS query as Admin contact: Yes",
"Visible in registrar WHOIS query as Technical contact: No",
"Phone number and email visible in domain WHOIS query as "
+ "Registrar Abuse contact info: No",
"Registrar-Console access: No");
+ "Registrar Abuse contact info: No");
}
@Test
@@ -116,69 +115,6 @@ class RegistrarPocCommandTest extends CommandTestCase<RegistrarPocCommand> {
.build());
}
@Test
void testUpdate_enableConsoleAccess() throws Exception {
Registrar registrar = loadRegistrar("NewRegistrar");
persistSimpleResource(
new RegistrarPoc.Builder()
.setRegistrar(registrar)
.setName("Jane Doe")
.setEmailAddress("jane.doe@example.com")
.build());
runCommandForced(
"--mode=UPDATE",
"--email=jane.doe@example.com",
"--allow_console_access=true",
"NewRegistrar");
RegistrarPoc registrarPoc =
loadRegistrar("NewRegistrar").getContacts().stream()
.filter(rc -> "jane.doe@example.com".equals(rc.getEmailAddress()))
.findFirst()
.get();
assertThat(registrarPoc.getLoginEmailAddress()).isEqualTo("jane.doe@example.com");
}
@Test
void testUpdate_enableConsoleAccess_specifyLoginEmail() throws Exception {
Registrar registrar = loadRegistrar("NewRegistrar");
persistSimpleResource(
new RegistrarPoc.Builder()
.setRegistrar(registrar)
.setName("Jane Doe")
.setEmailAddress("jane.doe@example.com")
.build());
runCommandForced(
"--mode=UPDATE",
"--email=jane.doe@example.com",
"--login_email=jim.doe@example.com",
"--allow_console_access=true",
"NewRegistrar");
RegistrarPoc registrarPoc =
loadRegistrar("NewRegistrar").getContacts().stream()
.filter(rc -> "jane.doe@example.com".equals(rc.getEmailAddress()))
.findFirst()
.get();
assertThat(registrarPoc.getLoginEmailAddress()).isEqualTo("jim.doe@example.com");
}
@Test
void testUpdate_disableConsoleAccess() throws Exception {
Registrar registrar = loadRegistrar("NewRegistrar");
persistSimpleResource(
new RegistrarPoc.Builder()
.setRegistrar(registrar)
.setName("Judith Doe")
.setEmailAddress("judith.doe@example.com")
.build());
runCommandForced(
"--mode=UPDATE",
"--email=judith.doe@example.com",
"--allow_console_access=false",
"NewRegistrar");
RegistrarPoc registrarPoc = loadRegistrar("NewRegistrar").getContacts().asList().get(1);
assertThat(registrarPoc.getLoginEmailAddress()).isNull();
}
@Test
void testUpdate_unsetOtherWhoisAbuseFlags() throws Exception {
Registrar registrar = loadRegistrar("NewRegistrar");
@@ -334,7 +270,6 @@ class RegistrarPocCommandTest extends CommandTestCase<RegistrarPocCommand> {
.setVisibleInWhoisAsTech(false)
.setVisibleInDomainWhoisAsAbuse(true)
.build());
assertThat(registrarPoc.getLoginEmailAddress()).isNull();
}
@Test
@@ -346,7 +281,7 @@ class RegistrarPocCommandTest extends CommandTestCase<RegistrarPocCommand> {
@Test
void testDelete_failsOnDomainWhoisAbuseContact() {
RegistrarPoc registrarPoc = loadRegistrar("NewRegistrar").getContacts().asList().get(0);
RegistrarPoc registrarPoc = loadRegistrar("NewRegistrar").getContacts().asList().getFirst();
putInDb(registrarPoc.asBuilder().setVisibleInDomainWhoisAsAbuse(true).build());
IllegalArgumentException thrown =
assertThrows(
@@ -358,33 +293,6 @@ class RegistrarPocCommandTest extends CommandTestCase<RegistrarPocCommand> {
assertThat(loadRegistrar("NewRegistrar").getContacts()).isNotEmpty();
}
@Test
void testCreate_withConsoleAccessEnabled() throws Exception {
runCommandForced(
"--mode=CREATE",
"--name=Jim Doe",
"--email=jim.doe@example.com",
"--allow_console_access=true",
"--contact_type=ADMIN,ABUSE",
"NewRegistrar");
RegistrarPoc registrarPoc = loadRegistrar("NewRegistrar").getContacts().asList().get(1);
assertThat(registrarPoc.getEmailAddress()).isEqualTo("jim.doe@example.com");
}
@Test
void testCreate_withConsoleAccessEnabled_specifyLoginEmail() throws Exception {
runCommandForced(
"--mode=CREATE",
"--name=Jim Doe",
"--email=jim.doe@example.com",
"--login_email=jane.doe@example.com",
"--allow_console_access=true",
"--contact_type=ADMIN,ABUSE",
"NewRegistrar");
RegistrarPoc registrarPoc = loadRegistrar("NewRegistrar").getContacts().asList().get(1);
assertThat(registrarPoc.getLoginEmailAddress()).isEqualTo("jane.doe@example.com");
}
@Test
void testCreate_withNoContactTypes() throws Exception {
runCommandForced(
@@ -218,7 +218,7 @@ class ContactActionTest {
+ " {name=Test Registrar 1,"
+ " emailAddress=test.registrar1@example.com, registrarId=registrarId,"
+ " registryLockEmailAddress=null, phoneNumber=+1.9999999999,"
+ " faxNumber=+1.9999999991, types=[ADMIN, WHOIS], loginEmailAddress=null,"
+ " faxNumber=+1.9999999991, types=[ADMIN, WHOIS],"
+ " visibleInWhoisAsAdmin=true, visibleInWhoisAsTech=false,"
+ " visibleInDomainWhoisAsAbuse=false,"
+ " allowedToSetRegistryLockPassword=false}\n"
@@ -226,14 +226,14 @@ class ContactActionTest {
+ " {name=Test Registrar 1, emailAddress=incorrect@email.com,"
+ " registrarId=registrarId, registryLockEmailAddress=null,"
+ " phoneNumber=+1.9999999999, faxNumber=+1.9999999991, types=[WHOIS,"
+ " ADMIN], loginEmailAddress=null, visibleInWhoisAsAdmin=true,"
+ " ADMIN], visibleInWhoisAsAdmin=true,"
+ " visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false,"
+ " allowedToSetRegistryLockPassword=false}\n"
+ " FINAL CONTENTS:\n"
+ " {name=Test Registrar 1,"
+ " emailAddress=test.registrar1@example.com, registrarId=registrarId,"
+ " registryLockEmailAddress=null, phoneNumber=+1.9999999999,"
+ " faxNumber=+1.9999999991, types=[ADMIN, WHOIS], loginEmailAddress=null,"
+ " faxNumber=+1.9999999991, types=[ADMIN, WHOIS],"
+ " visibleInWhoisAsAdmin=true, visibleInWhoisAsTech=false,"
+ " visibleInDomainWhoisAsAbuse=false,"
+ " allowedToSetRegistryLockPassword=false}\n")
@@ -11,9 +11,9 @@ emailAddress: the.registrar@example.com -> thase@the.registrar
url: http://my.fake.url -> http://my.new.url
contacts:
ADDED:
{name=Extra Terrestrial, emailAddress=etphonehome@example.com, registrarId=TheRegistrar, registryLockEmailAddress=null, phoneNumber=+1.2345678901, faxNumber=null, types=[ADMIN, BILLING, TECH, WHOIS], loginEmailAddress=null, visibleInWhoisAsAdmin=true, visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false, allowedToSetRegistryLockPassword=false}
{name=Extra Terrestrial, emailAddress=etphonehome@example.com, registrarId=TheRegistrar, registryLockEmailAddress=null, phoneNumber=+1.2345678901, faxNumber=null, types=[ADMIN, BILLING, TECH, WHOIS], visibleInWhoisAsAdmin=true, visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false, allowedToSetRegistryLockPassword=false}
REMOVED:
{name=John Doe, emailAddress=johndoe@theregistrar.com, registrarId=TheRegistrar, registryLockEmailAddress=null, phoneNumber=+1.1234567890, faxNumber=null, types=[ADMIN], loginEmailAddress=johndoe@theregistrar.com, visibleInWhoisAsAdmin=false, visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false, allowedToSetRegistryLockPassword=false}
{name=John Doe, emailAddress=johndoe@theregistrar.com, registrarId=TheRegistrar, registryLockEmailAddress=null, phoneNumber=+1.1234567890, faxNumber=null, types=[ADMIN], visibleInWhoisAsAdmin=false, visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false, allowedToSetRegistryLockPassword=false}
FINAL CONTENTS:
{name=Extra Terrestrial, emailAddress=etphonehome@example.com, registrarId=TheRegistrar, registryLockEmailAddress=null, phoneNumber=+1.2345678901, faxNumber=null, types=[ADMIN, BILLING, TECH, WHOIS], loginEmailAddress=null, visibleInWhoisAsAdmin=true, visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false, allowedToSetRegistryLockPassword=false},
{name=Marla Singer, emailAddress=Marla.Singer@crr.com, registrarId=TheRegistrar, registryLockEmailAddress=Marla.Singer.RegistryLock@crr.com, phoneNumber=+1.2128675309, faxNumber=null, types=[TECH], loginEmailAddress=Marla.Singer@crr.com, visibleInWhoisAsAdmin=false, visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false, allowedToSetRegistryLockPassword=false}
{name=Extra Terrestrial, emailAddress=etphonehome@example.com, registrarId=TheRegistrar, registryLockEmailAddress=null, phoneNumber=+1.2345678901, faxNumber=null, types=[ADMIN, BILLING, TECH, WHOIS], visibleInWhoisAsAdmin=true, visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false, allowedToSetRegistryLockPassword=false},
{name=Marla Singer, emailAddress=Marla.Singer@crr.com, registrarId=TheRegistrar, registryLockEmailAddress=Marla.Singer.RegistryLock@crr.com, phoneNumber=+1.2128675309, faxNumber=null, types=[TECH], visibleInWhoisAsAdmin=false, visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false, allowedToSetRegistryLockPassword=false}
File diff suppressed because it is too large Load Diff
@@ -257,15 +257,15 @@ td.section {
<tbody>
<tr>
<td class="property_name">generated by</td>
<td class="property_value">SchemaCrawler 16.21.2</td>
<td class="property_value">SchemaCrawler 16.21.4</td>
</tr>
<tr>
<td class="property_name">generated on</td>
<td class="property_value">2024-06-20 20:07:00</td>
<td class="property_value">2024-07-30 18:27:55</td>
</tr>
<tr>
<td class="property_name">last flyway file</td>
<td id="lastFlywayFile" class="property_value">V173__create_feature_flag_table.sql</td>
<td id="lastFlywayFile" class="property_value">V175__user_update_history_id.sql</td>
</tr>
</tbody>
</table>
@@ -278,9 +278,9 @@ td.section {
</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-9412 4738,-9412 4738,4 -4,4" />
<text text-anchor="start" x="4494" y="-29.8" font-family="Helvetica,sans-Serif" font-size="14.00">generated by</text>
<text text-anchor="start" x="4577" y="-29.8" font-family="Helvetica,sans-Serif" font-size="14.00">SchemaCrawler 16.21.2</text>
<text text-anchor="start" x="4577" y="-29.8" font-family="Helvetica,sans-Serif" font-size="14.00">SchemaCrawler 16.21.4</text>
<text text-anchor="start" x="4493" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">generated on</text>
<text text-anchor="start" x="4577" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">2024-06-20 20:07:00</text>
<text text-anchor="start" x="4577" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">2024-07-30 18:27:55</text>
<polygon fill="none" stroke="#888888" points="4490,-4 4490,-44 4726,-44 4726,-4 4490,-4" /> <!-- allocationtoken_a08ccbef -->
<g id="node1" class="node">
<title>
@@ -2071,12 +2071,12 @@ td.section {
<text text-anchor="start" x="713" y="-504.3" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">public."User"</text>
<polygon fill="#e9c2f2" stroke="transparent" points="901,-497.5 901,-516.5 1011,-516.5 1011,-497.5 901,-497.5" />
<text text-anchor="start" x="972" y="-503.3" font-family="Helvetica,sans-Serif" font-size="14.00">[table]</text>
<text text-anchor="start" x="713" y="-485.3" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">id</text>
<text text-anchor="start" x="713" y="-484.3" font-family="Helvetica,sans-Serif" font-size="14.00">id</text>
<text text-anchor="start" x="895" y="-484.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="903" y="-484.3" font-family="Helvetica,sans-Serif" font-size="14.00">bigserial not null</text>
<text text-anchor="start" x="903" y="-484.3" font-family="Helvetica,sans-Serif" font-size="14.00">bigserial</text>
<text text-anchor="start" x="895" y="-465.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="903" y="-465.3" font-family="Helvetica,sans-Serif" font-size="14.00">auto-incremented</text>
<text text-anchor="start" x="713" y="-446.3" font-family="Helvetica,sans-Serif" font-size="14.00">email_address</text>
<text text-anchor="start" x="713" y="-447.3" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">email_address</text>
<text text-anchor="start" x="895" y="-446.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="903" y="-446.3" font-family="Helvetica,sans-Serif" font-size="14.00">text not null</text>
<text text-anchor="start" x="713" y="-427.3" font-family="Helvetica,sans-Serif" font-size="14.00">registry_lock_password_hash</text>
@@ -4065,7 +4065,7 @@ td.section {
<text text-anchor="start" x="1594" y="-485.3" font-family="Helvetica,sans-Serif" font-size="14.00">text not null</text>
<text text-anchor="start" x="1404" y="-466.3" font-family="Helvetica,sans-Serif" font-size="14.00">user_id</text>
<text text-anchor="start" x="1586" y="-466.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="1594" y="-466.3" font-family="Helvetica,sans-Serif" font-size="14.00">int8 not null</text>
<text text-anchor="start" x="1594" y="-466.3" font-family="Helvetica,sans-Serif" font-size="14.00">int8</text>
<text text-anchor="start" x="1404" y="-447.3" font-family="Helvetica,sans-Serif" font-size="14.00">email_address</text>
<text text-anchor="start" x="1586" y="-447.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="1594" y="-447.3" font-family="Helvetica,sans-Serif" font-size="14.00">text not null</text>
@@ -13113,8 +13113,8 @@ td.section {
<tbody>
<tr>
<td class="spacer"></td>
<td class="minwidth"><b><i>id</i></b></td>
<td class="minwidth">bigserial not null</td>
<td class="minwidth">id</td>
<td class="minwidth">bigserial</td>
</tr>
<tr>
<td class="spacer"></td>
@@ -13123,7 +13123,7 @@ td.section {
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth">email_address</td>
<td class="minwidth"><b><i>email_address</i></b></td>
<td class="minwidth">text not null</td>
</tr>
<tr>
@@ -13176,7 +13176,7 @@ td.section {
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth">id</td>
<td class="minwidth">email_address</td>
<td class="minwidth"></td>
</tr>
<tr>
@@ -13260,7 +13260,7 @@ td.section {
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth">id</td>
<td class="minwidth">email_address</td>
<td class="minwidth">ascending</td>
</tr>
<tr>
@@ -13275,18 +13275,6 @@ td.section {
<td class="minwidth">email_address</td>
<td class="minwidth">ascending</td>
</tr>
<tr>
<td colspan="3"></td>
</tr>
<tr>
<td colspan="2" class="name">user_email_address_idx</td>
<td class="description right">[non-unique index]</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth">email_address</td>
<td class="minwidth">ascending</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
@@ -13328,7 +13316,7 @@ td.section {
<tr>
<td class="spacer"></td>
<td class="minwidth">user_id</td>
<td class="minwidth">int8 not null</td>
<td class="minwidth">int8</td>
</tr>
<tr>
<td class="spacer"></td>
+2
View File
@@ -171,3 +171,5 @@ V170__user_update_registry_lock_email.sql
V171__drop_create_billing_cost_columns_from_tld.sql
V172__allocation_token_renewal_price.sql
V173__create_feature_flag_table.sql
V174__user_pkey.sql
V175__user_update_history_id.sql
@@ -0,0 +1,18 @@
-- Copyright 2024 The Nomulus Authors. All Rights Reserved.
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
ALTER TABLE "User" DROP CONSTRAINT "User_pkey";
ALTER TABLE "User" ADD CONSTRAINT "User_pkey" PRIMARY KEY (email_address);
ALTER TABLE "User" ALTER COLUMN id DROP NOT NULL;
DROP INDEX user_email_address_idx;
@@ -0,0 +1,15 @@
-- Copyright 2024 The Nomulus Authors. All Rights Reserved.
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
ALTER TABLE "UserUpdateHistory" ALTER COLUMN user_id DROP NOT NULL;
@@ -672,7 +672,6 @@
registrar_id text not null,
allowed_to_set_registry_lock_password boolean not null,
fax_number text,
login_email_address text,
name text,
phone_number text,
registry_lock_email_address text,
@@ -696,7 +695,6 @@
registrar_id text not null,
allowed_to_set_registry_lock_password boolean not null,
fax_number text,
login_email_address text,
name text,
phone_number text,
registry_lock_email_address text,
@@ -887,16 +885,16 @@
);
create table "User" (
id bigserial not null,
email_address text not null,
update_timestamp timestamptz,
email_address text not null,
registry_lock_email_address text,
registry_lock_password_hash text,
registry_lock_password_salt text,
global_role text not null,
is_admin boolean not null,
registrar_roles hstore,
primary key (id)
id int8,
primary key (email_address)
);
create table "UserUpdateHistory" (
@@ -906,8 +904,8 @@
history_request_body text,
history_type text not null,
history_url text not null,
user_id int8 not null,
email_address text not null,
user_id int8 not null,
registry_lock_email_address text,
registry_lock_password_hash text,
registry_lock_password_salt text,
@@ -1004,7 +1002,6 @@ create index IDXaydgox62uno9qx8cjlj5lauye on "PollMessage" (event_time);
create index premiumlist_name_idx on "PremiumList" (name);
create index registrar_name_idx on "Registrar" (registrar_name);
create index registrar_iana_identifier_idx on "Registrar" (iana_identifier);
create index registrarpoc_login_email_idx on "RegistrarPoc" (login_email_address);
create index IDXrn6posxkx58de1cp09g5257cw on "RegistrarPocUpdateHistory" (history_acting_user);
create index IDXr1cxua6it0rxgt9tpyugspxk on "RegistrarPocUpdateHistory" (email_address);
create index IDXfr24wvpg8qalwqy4pni7evrpj on "RegistrarPocUpdateHistory" (registrar_id);
@@ -1019,17 +1016,13 @@ create index reservedlist_name_idx on "ReservedList" (name);
create index spec11threatmatch_registrar_id_idx on "Spec11ThreatMatch" (registrar_id);
create index spec11threatmatch_tld_idx on "Spec11ThreatMatch" (tld);
create index spec11threatmatch_check_date_idx on "Spec11ThreatMatch" (check_date);
create index user_email_address_idx on "User" (email_address);
alter table if exists "User"
add constraint UK_seshmd8qn3lcffsjw5j8wnbnd unique (email_address);
create index IDXbjacjlm8ianc4kxxvamnu94k5 on "UserUpdateHistory" (history_acting_user);
create index IDX5yqacw829y5bm6f7eajsq1cts on "UserUpdateHistory" (email_address);
alter table if exists "ConsoleEppActionHistory"
add constraint FKb686b9os2nsjpv930npa4r3b4
foreign key (history_acting_user)
references "User" (email_address);
references "User";
alter table if exists "DelegationSignerData"
add constraint FKtr24j9v14ph2mfuw2gsmt12kq
@@ -1069,12 +1062,12 @@ create index IDX5yqacw829y5bm6f7eajsq1cts on "UserUpdateHistory" (email_address)
alter table if exists "RegistrarPocUpdateHistory"
add constraint FKftpbwctxtkc1i0njc0tdcaa2g
foreign key (history_acting_user)
references "User" (email_address);
references "User";
alter table if exists "RegistrarUpdateHistory"
add constraint FKsr7w342s7x5s5jvdti2axqeq8
foreign key (history_acting_user)
references "User" (email_address);
references "User";
alter table if exists "RegistryLock"
add constraint FK2lhcwpxlnqijr96irylrh1707
@@ -1089,4 +1082,4 @@ create index IDX5yqacw829y5bm6f7eajsq1cts on "UserUpdateHistory" (email_address)
alter table if exists "UserUpdateHistory"
add constraint FK1s7bopbl3pwrhv3jkkofnv3o0
foreign key (history_acting_user)
references "User" (email_address);
references "User";
@@ -1317,7 +1317,7 @@ CREATE TABLE public."TmchCrl" (
--
CREATE TABLE public."User" (
id bigint NOT NULL,
id bigint,
email_address text NOT NULL,
registry_lock_password_hash text,
registry_lock_password_salt text,
@@ -1340,7 +1340,7 @@ CREATE TABLE public."UserUpdateHistory" (
history_request_body text,
history_type text NOT NULL,
history_url text NOT NULL,
user_id bigint NOT NULL,
user_id bigint,
email_address text NOT NULL,
registry_lock_password_hash text,
registry_lock_password_salt text,
@@ -1833,7 +1833,7 @@ ALTER TABLE ONLY public."UserUpdateHistory"
--
ALTER TABLE ONLY public."User"
ADD CONSTRAINT "User_pkey" PRIMARY KEY (id);
ADD CONSTRAINT "User_pkey" PRIMARY KEY (email_address);
--
@@ -2547,13 +2547,6 @@ CREATE INDEX spec11threatmatch_registrar_id_idx ON public."Spec11ThreatMatch" US
CREATE INDEX spec11threatmatch_tld_idx ON public."Spec11ThreatMatch" USING btree (tld);
--
-- Name: user_email_address_idx; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX user_email_address_idx ON public."User" USING btree (email_address);
--
-- Name: UserUpdateHistory fk1s7bopbl3pwrhv3jkkofnv3o0; Type: FK CONSTRAINT; Schema: public; Owner: -
--