mirror of
https://github.com/google/nomulus
synced 2026-05-20 06:41:51 +00:00
Compare commits
5 Commits
nomulus-20
...
nomulus-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
35f95bbbe4 | ||
|
|
ae61cd443d | ||
|
|
cc20f7d76d | ||
|
|
5603b91526 | ||
|
|
332f491ac7 |
@@ -95,9 +95,9 @@
|
||||
|
||||
<ng-container matColumnDef="statuses">
|
||||
<mat-header-cell *matHeaderCellDef>Statuses</mat-header-cell>
|
||||
<mat-cell *matCellDef="let element">{{
|
||||
element.statuses
|
||||
}}</mat-cell>
|
||||
<mat-cell *matCellDef="let element">
|
||||
<span>{{ element.statuses?.join(", ") }}</span>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="registryLock">
|
||||
|
||||
@@ -36,6 +36,11 @@
|
||||
.mat-column-registryLock {
|
||||
max-width: 150px;
|
||||
}
|
||||
.mat-column-statuses span {
|
||||
padding: 10px 0;
|
||||
overflow: hidden;
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
&__domains-spinner {
|
||||
|
||||
@@ -172,6 +172,14 @@ export class BackendService {
|
||||
.pipe(catchError((err) => this.errorCatcher<User>(err)));
|
||||
}
|
||||
|
||||
deleteUser(registrarId: string, emailAddress: string): Observable<any> {
|
||||
return this.http
|
||||
.delete<any>(`/console-api/users?registrarId=${registrarId}`, {
|
||||
body: JSON.stringify({ emailAddress }),
|
||||
})
|
||||
.pipe(catchError((err) => this.errorCatcher<any>(err)));
|
||||
}
|
||||
|
||||
getUserData(): Observable<UserData> {
|
||||
return this.http
|
||||
.get<UserData>('/console-api/userdata')
|
||||
|
||||
79
console-webapp/src/app/users/userEdit.component.html
Normal file
79
console-webapp/src/app/users/userEdit.component.html
Normal file
@@ -0,0 +1,79 @@
|
||||
<div class="console-app__user-details">
|
||||
@if(isNewUser) {
|
||||
<h1 class="mat-headline-4">
|
||||
{{ userDetails.emailAddress + " succesfully created" }}
|
||||
</h1>
|
||||
} @else {
|
||||
<h1 class="mat-headline-4">User details</h1>
|
||||
}
|
||||
<mat-divider></mat-divider>
|
||||
<div>
|
||||
<div class="console-app__user-details-controls">
|
||||
<button
|
||||
mat-icon-button
|
||||
aria-label="Back to users list"
|
||||
(click)="goBack()"
|
||||
>
|
||||
<mat-icon>arrow_back</mat-icon>
|
||||
</button>
|
||||
<div class="spacer"></div>
|
||||
<button
|
||||
mat-icon-button
|
||||
aria-label="Delete User"
|
||||
(click)="deleteUser()"
|
||||
[disabled]="isLoading"
|
||||
>
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p *ngIf="isLoading">
|
||||
<mat-progress-bar mode="query"></mat-progress-bar>
|
||||
</p>
|
||||
|
||||
<mat-card appearance="outlined">
|
||||
<mat-card-content>
|
||||
<mat-list role="list">
|
||||
<mat-list-item role="listitem">
|
||||
<h2>User details</h2>
|
||||
</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
<mat-list-item role="listitem">
|
||||
<span class="console-app__list-key">User email</span>
|
||||
<span class="console-app__list-value">{{
|
||||
userDetails.emailAddress
|
||||
}}</span>
|
||||
</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
<mat-list-item role="listitem">
|
||||
<span class="console-app__list-key">User role</span>
|
||||
<span class="console-app__list-value">{{
|
||||
roleToDescription(userDetails.role)
|
||||
}}</span>
|
||||
</mat-list-item>
|
||||
@if (userDetails.password) {
|
||||
<mat-divider></mat-divider>
|
||||
<mat-list-item role="listitem">
|
||||
<span class="console-app__list-key">Password</span>
|
||||
<span
|
||||
class="console-app__list-value console-app__user-details-password"
|
||||
>
|
||||
<input
|
||||
[type]="isPasswordVisible ? 'text' : 'password'"
|
||||
[value]="userDetails.password"
|
||||
disabled
|
||||
/>
|
||||
<button
|
||||
mat-button
|
||||
aria-label="Show password"
|
||||
(click)="isPasswordVisible = !isPasswordVisible"
|
||||
>
|
||||
{{ isPasswordVisible ? "Hide" : "View" }} password
|
||||
</button>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
}
|
||||
</mat-list>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
30
console-webapp/src/app/users/userEdit.component.scss
Normal file
30
console-webapp/src/app/users/userEdit.component.scss
Normal file
@@ -0,0 +1,30 @@
|
||||
// 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.
|
||||
|
||||
.console-app {
|
||||
&__user-details {
|
||||
&-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 20px 0;
|
||||
}
|
||||
&-password {
|
||||
input {
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
max-width: 616px;
|
||||
}
|
||||
}
|
||||
81
console-webapp/src/app/users/userEdit.component.ts
Normal file
81
console-webapp/src/app/users/userEdit.component.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
// 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 { CommonModule } from '@angular/common';
|
||||
import { Component } from '@angular/core';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { SelectedRegistrarModule } from '../app.module';
|
||||
import { MaterialModule } from '../material.module';
|
||||
import { RegistrarService } from '../registrar/registrar.service';
|
||||
import { SnackBarModule } from '../snackbar.module';
|
||||
import { User, UsersService, roleToDescription } from './users.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-user-edit',
|
||||
templateUrl: './userEdit.component.html',
|
||||
styleUrls: ['./userEdit.component.scss'],
|
||||
standalone: true,
|
||||
imports: [
|
||||
MaterialModule,
|
||||
SnackBarModule,
|
||||
CommonModule,
|
||||
SelectedRegistrarModule,
|
||||
],
|
||||
providers: [],
|
||||
})
|
||||
export class UserEditComponent {
|
||||
inEdit = false;
|
||||
isPasswordVisible = false;
|
||||
isNewUser = false;
|
||||
isLoading = false;
|
||||
userDetails: User;
|
||||
|
||||
constructor(
|
||||
protected registrarService: RegistrarService,
|
||||
protected usersService: UsersService,
|
||||
private _snackBar: MatSnackBar
|
||||
) {
|
||||
this.userDetails = this.usersService
|
||||
.users()
|
||||
.filter(
|
||||
(u) => u.emailAddress === this.usersService.currentlyOpenUserEmail()
|
||||
)[0];
|
||||
if (this.usersService.isNewUser) {
|
||||
this.isNewUser = true;
|
||||
this.usersService.isNewUser = false;
|
||||
}
|
||||
}
|
||||
|
||||
roleToDescription(role: string) {
|
||||
return roleToDescription(role);
|
||||
}
|
||||
|
||||
deleteUser() {
|
||||
this.isLoading = true;
|
||||
this.usersService.deleteUser(this.userDetails.emailAddress).subscribe({
|
||||
error: (err) => {
|
||||
this._snackBar.open(err.error || err.message);
|
||||
this.isLoading = false;
|
||||
},
|
||||
complete: () => {
|
||||
this.isLoading = false;
|
||||
this.goBack();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
goBack() {
|
||||
this.usersService.currentlyOpenUserEmail.set('');
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,11 @@
|
||||
<app-selected-registrar-wrapper>
|
||||
@if (!isLoading) {
|
||||
@if(isLoading) {
|
||||
<div class="console-app__users-spinner">
|
||||
<mat-spinner />
|
||||
</div>
|
||||
} @else if(usersService.currentlyOpenUserEmail()) {
|
||||
<app-user-edit></app-user-edit>
|
||||
} @else {
|
||||
<div class="console-app__users">
|
||||
<div class="console-app__users-header">
|
||||
<h1 class="mat-headline-4">Users</h1>
|
||||
@@ -32,12 +38,11 @@
|
||||
></mat-cell>
|
||||
</ng-container>
|
||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
|
||||
<mat-row
|
||||
*matRowDef="let row; columns: displayedColumns"
|
||||
(click)="openDetails(row.emailAddress)"
|
||||
></mat-row>
|
||||
</mat-table>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="console-app__users-spinner">
|
||||
<mat-spinner />
|
||||
</div>
|
||||
}
|
||||
</app-selected-registrar-wrapper>
|
||||
|
||||
@@ -22,15 +22,8 @@ import { SelectedRegistrarModule } from '../app.module';
|
||||
import { MaterialModule } from '../material.module';
|
||||
import { RegistrarService } from '../registrar/registrar.service';
|
||||
import { SnackBarModule } from '../snackbar.module';
|
||||
import { User, UsersService } from './users.service';
|
||||
|
||||
const roleToDescription = (role: String) => {
|
||||
if (!role) return 'N/A';
|
||||
else if (role.toLowerCase().startsWith('account_manager')) {
|
||||
return 'Viewer';
|
||||
}
|
||||
return 'Editor';
|
||||
};
|
||||
import { UserEditComponent } from './userEdit.component';
|
||||
import { roleToDescription, User, UsersService } from './users.service';
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
@@ -55,6 +48,7 @@ export const columns = [
|
||||
SnackBarModule,
|
||||
CommonModule,
|
||||
SelectedRegistrarModule,
|
||||
UserEditComponent,
|
||||
],
|
||||
providers: [UsersService],
|
||||
})
|
||||
@@ -92,6 +86,7 @@ export class UsersComponent {
|
||||
this.usersService.fetchUsers().subscribe({
|
||||
error: (err: HttpErrorResponse) => {
|
||||
this._snackBar.open(err.error || err.message);
|
||||
this.isLoading = false;
|
||||
},
|
||||
complete: () => {
|
||||
this.isLoading = false;
|
||||
@@ -102,21 +97,17 @@ export class UsersComponent {
|
||||
createNewUser() {
|
||||
this.isLoading = true;
|
||||
this.usersService.createNewUser().subscribe({
|
||||
next: (newUser) => {
|
||||
this._snackBar.open(
|
||||
`New user with email ${newUser.emailAddress} has been created.`,
|
||||
'',
|
||||
{
|
||||
duration: 2000,
|
||||
}
|
||||
);
|
||||
},
|
||||
error: (err: HttpErrorResponse) => {
|
||||
this._snackBar.open(err.error || err.message);
|
||||
this.isLoading = false;
|
||||
},
|
||||
complete: () => {
|
||||
this.isLoading = false;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
openDetails(emailAddress: string) {
|
||||
this.usersService.currentlyOpenUserEmail.set(emailAddress);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,19 +17,29 @@ import { tap } from 'rxjs';
|
||||
import { RegistrarService } from '../registrar/registrar.service';
|
||||
import { BackendService } from '../shared/services/backend.service';
|
||||
|
||||
export const roleToDescription = (role: string) => {
|
||||
if (!role) return 'N/A';
|
||||
else if (role.toLowerCase().startsWith('account_manager')) {
|
||||
return 'Viewer';
|
||||
}
|
||||
return 'Editor';
|
||||
};
|
||||
|
||||
export interface CreateAutoTimestamp {
|
||||
creationTime: string;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
emailAddress: String;
|
||||
role: String;
|
||||
password?: String;
|
||||
emailAddress: string;
|
||||
role: string;
|
||||
password?: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class UsersService {
|
||||
users = signal<User[]>([]);
|
||||
currentlyOpenUserEmail = signal<string>('');
|
||||
isNewUser: boolean = false;
|
||||
|
||||
constructor(
|
||||
private backendService: BackendService,
|
||||
@@ -52,7 +62,15 @@ export class UsersService {
|
||||
.pipe(
|
||||
tap((newUser: User) => {
|
||||
this.users.set([...this.users(), newUser]);
|
||||
this.currentlyOpenUserEmail.set(newUser.emailAddress);
|
||||
this.isNewUser = true;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
deleteUser(emailAddress: string) {
|
||||
return this.backendService
|
||||
.deleteUser(this.registrarService.registrarId(), emailAddress)
|
||||
.pipe(tap((_) => this.fetchUsers()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ package google.registry.export;
|
||||
import static com.google.common.base.Verify.verifyNotNull;
|
||||
import static google.registry.model.tld.Tlds.getTldsOfType;
|
||||
import static google.registry.persistence.PersistenceModule.TransactionIsolationLevel.TRANSACTION_REPEATABLE_READ;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.request.Action.Method.POST;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
@@ -28,6 +29,9 @@ import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.net.MediaType;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.gcs.GcsUtils;
|
||||
import google.registry.model.common.FeatureFlag;
|
||||
import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.model.tld.Tld.TldType;
|
||||
import google.registry.request.Action;
|
||||
@@ -38,8 +42,13 @@ import google.registry.util.Clock;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
import org.hibernate.query.NativeQuery;
|
||||
import org.hibernate.query.TupleTransformer;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
|
||||
/**
|
||||
* An action that exports the list of active domains on all real TLDs to Google Drive and GCS.
|
||||
@@ -55,7 +64,21 @@ import javax.inject.Inject;
|
||||
public class ExportDomainListsAction implements Runnable {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
public static final String REGISTERED_DOMAINS_FILENAME = "registered_domains.txt";
|
||||
private static final String SELECT_DOMAINS_STATEMENT =
|
||||
"SELECT domainName FROM Domain WHERE tld = :tld AND deletionTime > :now ORDER by domainName";
|
||||
private static final String SELECT_DOMAINS_AND_DELETION_TIMES_STATEMENT =
|
||||
"""
|
||||
SELECT d.domain_name, d.deletion_time, d.statuses, gp.type FROM "Domain" d
|
||||
LEFT JOIN (SELECT type, domain_repo_id FROM "GracePeriod"
|
||||
WHERE type = 'REDEMPTION'
|
||||
AND expiration_time > CAST(:now AS timestamptz)) AS gp
|
||||
ON d.repo_id = gp.domain_repo_id
|
||||
WHERE d.tld = :tld
|
||||
AND d.deletion_time > CAST(:now AS timestamptz)
|
||||
ORDER BY d.domain_name""";
|
||||
|
||||
// This may be a CSV, but it is uses a .txt file extension for back-compatibility
|
||||
static final String REGISTERED_DOMAINS_FILENAME = "registered_domains.txt";
|
||||
|
||||
@Inject Clock clock;
|
||||
@Inject DriveConnection driveConnection;
|
||||
@@ -68,47 +91,50 @@ public class ExportDomainListsAction implements Runnable {
|
||||
public void run() {
|
||||
ImmutableSet<String> realTlds = getTldsOfType(TldType.REAL);
|
||||
logger.atInfo().log("Exporting domain lists for TLDs %s.", realTlds);
|
||||
|
||||
boolean includeDeletionTimes =
|
||||
tm().transact(
|
||||
() ->
|
||||
FeatureFlag.isActiveNowOrElse(
|
||||
FeatureFlag.FeatureName.INCLUDE_PENDING_DELETE_DATE_FOR_DOMAINS, false));
|
||||
realTlds.forEach(
|
||||
tld -> {
|
||||
List<String> domains =
|
||||
tm().transact(
|
||||
List<String> domainsList =
|
||||
replicaTm()
|
||||
.transact(
|
||||
TRANSACTION_REPEATABLE_READ,
|
||||
() ->
|
||||
// Note that if we had "creationTime <= :now" in the condition (not
|
||||
// necessary as there is no pending creation, the order of deletionTime
|
||||
// and creationTime in the query would have been significant and it
|
||||
// should come after deletionTime. When Hibernate substitutes "now" it
|
||||
// will first validate that the **first** field that is to be compared
|
||||
// with it (deletionTime) is assignable from the substituted Java object
|
||||
// (click.nowUtc()). Since creationTime is a CreateAutoTimestamp, if it
|
||||
// comes first, we will need to substitute "now" with
|
||||
// CreateAutoTimestamp.create(clock.nowUtc()). This might look a bit
|
||||
// strange as the Java object type is clearly incompatible between the
|
||||
// two fields deletionTime (DateTime) and creationTime, yet they are
|
||||
// compared with the same "now". It is actually OK because in the end
|
||||
// Hibernate converts everything to SQL types (and Java field names to
|
||||
// SQL column names) to run the query. Both CreateAutoTimestamp and
|
||||
// DateTime are persisted as timestamp_z in SQL. It is only the
|
||||
// validation that compares the Java types, and only with the first
|
||||
// field that compares with the substituted value.
|
||||
tm().query(
|
||||
"SELECT domainName FROM Domain "
|
||||
+ "WHERE tld = :tld "
|
||||
+ "AND deletionTime > :now "
|
||||
+ "ORDER by domainName ASC",
|
||||
String.class)
|
||||
() -> {
|
||||
if (includeDeletionTimes) {
|
||||
// We want to include deletion times, but only for domains in the 5-day
|
||||
// PENDING_DELETE period after the REDEMPTION grace period. In order to
|
||||
// accomplish this without loading the entire list of domains, we use a
|
||||
// native query to join against the GracePeriod table to find
|
||||
// PENDING_DELETE domains that don't have a REDEMPTION grace period.
|
||||
return replicaTm()
|
||||
.getEntityManager()
|
||||
.createNativeQuery(SELECT_DOMAINS_AND_DELETION_TIMES_STATEMENT)
|
||||
.unwrap(NativeQuery.class)
|
||||
.setTupleTransformer(new DomainResultTransformer())
|
||||
.setParameter("tld", tld)
|
||||
.setParameter("now", clock.nowUtc())
|
||||
.getResultList());
|
||||
String domainsList = Joiner.on("\n").join(domains);
|
||||
.setParameter("now", replicaTm().getTransactionTime().toString())
|
||||
.getResultList();
|
||||
} else {
|
||||
return replicaTm()
|
||||
.query(SELECT_DOMAINS_STATEMENT, String.class)
|
||||
.setParameter("tld", tld)
|
||||
.setParameter("now", replicaTm().getTransactionTime())
|
||||
.getResultList();
|
||||
}
|
||||
});
|
||||
logger.atInfo().log(
|
||||
"Exporting %d domains for TLD %s to GCS and Drive.", domains.size(), tld);
|
||||
exportToGcs(tld, domainsList, gcsBucket, gcsUtils);
|
||||
exportToDrive(tld, domainsList, driveConnection);
|
||||
"Exporting %d domains for TLD %s to GCS and Drive.", domainsList.size(), tld);
|
||||
String domainsListOutput = Joiner.on('\n').join(domainsList);
|
||||
exportToGcs(tld, domainsListOutput, gcsBucket, gcsUtils);
|
||||
exportToDrive(tld, domainsListOutput, driveConnection);
|
||||
});
|
||||
}
|
||||
|
||||
protected static boolean exportToDrive(
|
||||
protected static void exportToDrive(
|
||||
String tldStr, String domains, DriveConnection driveConnection) {
|
||||
verifyNotNull(driveConnection, "Expecting non-null driveConnection");
|
||||
try {
|
||||
@@ -131,12 +157,10 @@ public class ExportDomainListsAction implements Runnable {
|
||||
} catch (Throwable e) {
|
||||
logger.atSevere().withCause(e).log(
|
||||
"Error exporting registered domains for TLD %s to Drive, skipping...", tldStr);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected static boolean exportToGcs(
|
||||
protected static void exportToGcs(
|
||||
String tld, String domains, String gcsBucket, GcsUtils gcsUtils) {
|
||||
BlobId blobId = BlobId.of(gcsBucket, tld + ".txt");
|
||||
try (OutputStream gcsOutput = gcsUtils.openOutputStream(blobId);
|
||||
@@ -145,8 +169,22 @@ public class ExportDomainListsAction implements Runnable {
|
||||
} catch (Throwable e) {
|
||||
logger.atSevere().withCause(e).log(
|
||||
"Error exporting registered domains for TLD %s to GCS, skipping...", tld);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Transforms the multiple columns selected from SQL into the output line. */
|
||||
private static class DomainResultTransformer implements TupleTransformer<String> {
|
||||
@Override
|
||||
public String transformTuple(Object[] domainResult, String[] strings) {
|
||||
String domainName = (String) domainResult[0];
|
||||
Instant deletionInstant = (Instant) domainResult[1];
|
||||
DateTime deletionTime = new DateTime(deletionInstant.toEpochMilli(), DateTimeZone.UTC);
|
||||
String[] domainStatuses = (String[]) domainResult[2];
|
||||
String gracePeriodType = (String) domainResult[3];
|
||||
boolean inPendingDelete =
|
||||
ImmutableSet.copyOf(domainStatuses).contains(StatusValue.PENDING_DELETE.toString())
|
||||
&& !GracePeriodStatus.REDEMPTION.toString().equals(gracePeriodType);
|
||||
return String.format("%s,%s", domainName, inPendingDelete ? deletionTime : "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@ public class FeatureFlag extends ImmutableObject implements Buildable {
|
||||
TEST_FEATURE,
|
||||
MINIMUM_DATASET_CONTACTS_OPTIONAL,
|
||||
MINIMUM_DATASET_CONTACTS_PROHIBITED,
|
||||
INCLUDE_PENDING_DELETE_DATE_FOR_DOMAINS
|
||||
}
|
||||
|
||||
/** The name of the flag/feature. */
|
||||
@@ -154,6 +155,15 @@ public class FeatureFlag extends ImmutableObject implements Buildable {
|
||||
return status.getValueAtTime(time);
|
||||
}
|
||||
|
||||
/** Returns if the flag is active, or the default value if the flag does not exist. */
|
||||
public static boolean isActiveNowOrElse(FeatureName featureName, boolean defaultValue) {
|
||||
tm().assertInTransaction();
|
||||
return CACHE
|
||||
.get(featureName)
|
||||
.map(flag -> flag.getStatus(tm().getTransactionTime()).equals(ACTIVE))
|
||||
.orElse(defaultValue);
|
||||
}
|
||||
|
||||
/** Returns if the FeatureFlag with the given FeatureName is active now. */
|
||||
public static boolean isActiveNow(FeatureName featureName) {
|
||||
tm().assertInTransaction();
|
||||
|
||||
@@ -185,4 +185,9 @@ public class TimedTransitionProperty<V extends Serializable> implements UnsafeSe
|
||||
public int hashCode() {
|
||||
return this.backingMap.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.backingMap.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,8 @@ public @interface Action {
|
||||
enum Method {
|
||||
GET,
|
||||
HEAD,
|
||||
POST
|
||||
POST,
|
||||
DELETE
|
||||
}
|
||||
|
||||
interface Service {
|
||||
|
||||
@@ -30,6 +30,7 @@ import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Nullable;
|
||||
@@ -174,7 +175,8 @@ public class UpdateRecurrenceCommand extends ConfirmingCommand {
|
||||
"Domain %s has already had a deletion time set",
|
||||
domainName);
|
||||
checkArgument(
|
||||
domain.getTransferData().isEmpty(),
|
||||
domain.getTransferData().isEmpty()
|
||||
|| domain.getTransferData().getTransferStatus() != TransferStatus.PENDING,
|
||||
"Domain %s has a pending transfer: %s",
|
||||
domainName,
|
||||
domain.getTransferData());
|
||||
|
||||
@@ -16,7 +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.request.Action.Method.DELETE;
|
||||
import static google.registry.request.Action.Method.GET;
|
||||
import static google.registry.request.Action.Method.HEAD;
|
||||
import static google.registry.request.Action.Method.POST;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
|
||||
@@ -75,13 +77,19 @@ public abstract class ConsoleApiAction implements Runnable {
|
||||
return;
|
||||
}
|
||||
User user = consoleApiParams.authResult().user().get();
|
||||
|
||||
String requestMethod = consoleApiParams.request().getMethod();
|
||||
try {
|
||||
if (consoleApiParams.request().getMethod().equals(GET.toString())) {
|
||||
if (requestMethod.equals(GET.toString())) {
|
||||
getHandler(user);
|
||||
} else if (requestMethod.equals(HEAD.toString())) {
|
||||
headHandler(user);
|
||||
} else {
|
||||
if (verifyXSRF(user)) {
|
||||
postHandler(user);
|
||||
if (requestMethod.equals(DELETE.toString())) {
|
||||
deleteHandler(user);
|
||||
} else {
|
||||
postHandler(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ConsolePermissionForbiddenException e) {
|
||||
@@ -113,6 +121,14 @@ public abstract class ConsoleApiAction implements Runnable {
|
||||
throw new UnsupportedOperationException("Console API GET handler not implemented");
|
||||
}
|
||||
|
||||
protected void deleteHandler(User user) {
|
||||
throw new UnsupportedOperationException("Console API DELETE handler not implemented");
|
||||
}
|
||||
|
||||
protected void headHandler(User user) {
|
||||
throw new UnsupportedOperationException("Console API HEAD handler not implemented");
|
||||
}
|
||||
|
||||
protected void setFailedResponse(String message, int code) {
|
||||
consoleApiParams.response().setStatus(code);
|
||||
consoleApiParams.response().setPayload(message);
|
||||
|
||||
@@ -36,6 +36,7 @@ import google.registry.ui.server.SendEmailUtils;
|
||||
import google.registry.ui.server.console.ConsoleEppPasswordAction.EppPasswordData;
|
||||
import google.registry.ui.server.console.ConsoleOteAction.OteCreateData;
|
||||
import google.registry.ui.server.console.ConsoleRegistryLockAction.ConsoleRegistryLockPostInput;
|
||||
import google.registry.ui.server.console.ConsoleUsersAction.UserDeleteData;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
@@ -245,6 +246,13 @@ public final class ConsoleModule {
|
||||
return payload.map(s -> gson.fromJson(s, EppPasswordData.class));
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter("userDeleteData")
|
||||
public static Optional<UserDeleteData> provideUserDeleteData(
|
||||
Gson gson, @OptionalJsonPayload Optional<JsonElement> payload) {
|
||||
return payload.map(s -> gson.fromJson(s, UserDeleteData.class));
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter("oteCreateData")
|
||||
public static Optional<OteCreateData> provideOteCreateData(
|
||||
|
||||
@@ -14,11 +14,14 @@
|
||||
|
||||
package google.registry.ui.server.console;
|
||||
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static google.registry.model.console.RegistrarRole.ACCOUNT_MANAGER;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.request.Action.Method.DELETE;
|
||||
import static google.registry.request.Action.Method.GET;
|
||||
import static google.registry.request.Action.Method.POST;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_CREATED;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
@@ -29,6 +32,8 @@ import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.annotations.Expose;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.console.UserRoles;
|
||||
@@ -38,11 +43,13 @@ import google.registry.request.Action.GkeService;
|
||||
import google.registry.request.HttpException.BadRequestException;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.tools.IamClient;
|
||||
import google.registry.util.StringGenerator;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.IntStream;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
@@ -50,36 +57,42 @@ import javax.inject.Named;
|
||||
service = Action.GaeService.DEFAULT,
|
||||
gkeService = GkeService.CONSOLE,
|
||||
path = ConsoleUsersAction.PATH,
|
||||
method = {GET, POST},
|
||||
method = {GET, POST, DELETE},
|
||||
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
|
||||
public class ConsoleUsersAction extends ConsoleApiAction {
|
||||
static final String PATH = "/console-api/users";
|
||||
private static final int PASSWORD_LENGTH = 16;
|
||||
|
||||
private static final Splitter EMAIL_SPLITTER = Splitter.on('@').trimResults();
|
||||
|
||||
private final Gson gson;
|
||||
private final String registrarId;
|
||||
private final Directory directory;
|
||||
private final StringGenerator passwordGenerator;
|
||||
private final Optional<UserDeleteData> userDeleteData;
|
||||
private final Optional<String> maybeGroupEmailAddress;
|
||||
private final IamClient iamClient;
|
||||
private final String gSuiteDomainName;
|
||||
|
||||
@Inject
|
||||
public ConsoleUsersAction(
|
||||
ConsoleApiParams consoleApiParams,
|
||||
Gson gson,
|
||||
Directory directory,
|
||||
IamClient iamClient,
|
||||
@Config("gSuiteDomainName") String gSuiteDomainName,
|
||||
@Config("gSuiteConsoleUserGroupEmailAddress") Optional<String> maybeGroupEmailAddress,
|
||||
@Named("base58StringGenerator") StringGenerator passwordGenerator,
|
||||
@Parameter("userDeleteData") Optional<UserDeleteData> userDeleteData,
|
||||
@Parameter("registrarId") String registrarId) {
|
||||
super(consoleApiParams);
|
||||
this.gson = gson;
|
||||
this.registrarId = registrarId;
|
||||
this.directory = directory;
|
||||
this.passwordGenerator = passwordGenerator;
|
||||
}
|
||||
|
||||
private static String generateNewEmailAddress(User user, String increment) {
|
||||
List<String> emailParts = EMAIL_SPLITTER.splitToList(user.getEmailAddress());
|
||||
return String.format("%s-%s@%s", emailParts.get(0), increment, emailParts.get(1));
|
||||
this.userDeleteData = userDeleteData;
|
||||
this.maybeGroupEmailAddress = maybeGroupEmailAddress;
|
||||
this.iamClient = iamClient;
|
||||
this.gSuiteDomainName = gSuiteDomainName;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -87,7 +100,7 @@ public class ConsoleUsersAction extends ConsoleApiAction {
|
||||
// Temporary flag while testing
|
||||
if (user.getUserRoles().isAdmin()) {
|
||||
checkPermission(user, registrarId, ConsolePermission.MANAGE_USERS);
|
||||
tm().transact(() -> runInTransaction(user));
|
||||
tm().transact(() -> runCreateInTransaction());
|
||||
} else {
|
||||
consoleApiParams.response().setStatus(SC_FORBIDDEN);
|
||||
}
|
||||
@@ -97,8 +110,7 @@ public class ConsoleUsersAction extends ConsoleApiAction {
|
||||
protected void getHandler(User user) {
|
||||
checkPermission(user, registrarId, ConsolePermission.MANAGE_USERS);
|
||||
List<ImmutableMap> users =
|
||||
getAllUsers().stream()
|
||||
.filter(u -> u.getUserRoles().getRegistrarRoles().containsKey(registrarId))
|
||||
getAllRegistrarUsers(registrarId).stream()
|
||||
.map(
|
||||
u ->
|
||||
ImmutableMap.of(
|
||||
@@ -112,24 +124,71 @@ public class ConsoleUsersAction extends ConsoleApiAction {
|
||||
consoleApiParams.response().setStatus(SC_OK);
|
||||
}
|
||||
|
||||
private void runInTransaction(User user) throws IOException {
|
||||
String nextAvailableIncrement =
|
||||
Stream.of("1", "2", "3")
|
||||
.filter(
|
||||
increment ->
|
||||
tm().loadByKeyIfPresent(
|
||||
VKey.create(User.class, generateNewEmailAddress(user, increment)))
|
||||
.isEmpty())
|
||||
@Override
|
||||
protected void deleteHandler(User user) {
|
||||
// Temporary flag while testing
|
||||
if (user.getUserRoles().isAdmin()) {
|
||||
checkPermission(user, registrarId, ConsolePermission.MANAGE_USERS);
|
||||
tm().transact(() -> runDeleteInTransaction());
|
||||
} else {
|
||||
consoleApiParams.response().setStatus(SC_FORBIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
private void runDeleteInTransaction() throws IOException {
|
||||
if (userDeleteData.isEmpty() || isNullOrEmpty(userDeleteData.get().userEmail)) {
|
||||
throw new BadRequestException("Missing user data param");
|
||||
}
|
||||
String email = userDeleteData.get().userEmail;
|
||||
User userToDelete =
|
||||
tm().loadByKeyIfPresent(VKey.create(User.class, email))
|
||||
.orElseThrow(
|
||||
() -> new BadRequestException(String.format("User %s doesn't exist", email)));
|
||||
|
||||
if (!userToDelete.getUserRoles().getRegistrarRoles().containsKey(registrarId)) {
|
||||
setFailedResponse(
|
||||
String.format("Can't delete user not associated with registrarId %s", registrarId),
|
||||
SC_FORBIDDEN);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
directory.users().delete(email).execute();
|
||||
} catch (IOException e) {
|
||||
setFailedResponse("Failed to delete the user workspace account", SC_INTERNAL_SERVER_ERROR);
|
||||
throw e;
|
||||
}
|
||||
|
||||
VKey<User> key = VKey.create(User.class, email);
|
||||
tm().delete(key);
|
||||
User.revokeIapPermission(email, maybeGroupEmailAddress, cloudTasksUtils, null, iamClient);
|
||||
|
||||
consoleApiParams.response().setStatus(SC_OK);
|
||||
}
|
||||
|
||||
private void runCreateInTransaction() throws IOException {
|
||||
ImmutableList<User> allRegistrarUsers = getAllRegistrarUsers(registrarId);
|
||||
if (allRegistrarUsers.size() >= 4)
|
||||
throw new BadRequestException("Total users amount per registrar is limited to 4");
|
||||
|
||||
String nextAvailableEmail =
|
||||
IntStream.range(1, 5)
|
||||
.mapToObj(i -> String.format("%s-user%s@%s", registrarId, i, gSuiteDomainName))
|
||||
.filter(email -> tm().loadByKeyIfPresent(VKey.create(User.class, email)).isEmpty())
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new BadRequestException("Extra users amount is limited to 3"));
|
||||
// Can only happen if registrar cycled through 20 users, which is unlikely
|
||||
.orElseThrow(
|
||||
() -> new BadRequestException("Failed to find available increment for new user"));
|
||||
|
||||
com.google.api.services.directory.model.User newUser =
|
||||
new com.google.api.services.directory.model.User();
|
||||
|
||||
newUser.setName(
|
||||
new UserName().setFamilyName(registrarId).setGivenName("User" + nextAvailableIncrement));
|
||||
new UserName()
|
||||
.setFamilyName(registrarId)
|
||||
.setGivenName(EMAIL_SPLITTER.splitToList(nextAvailableEmail).get(0)));
|
||||
newUser.setPassword(passwordGenerator.createString(PASSWORD_LENGTH));
|
||||
newUser.setPrimaryEmail(generateNewEmailAddress(user, nextAvailableIncrement));
|
||||
newUser.setPrimaryEmail(nextAvailableEmail);
|
||||
|
||||
try {
|
||||
directory.users().insert(newUser).execute();
|
||||
@@ -146,8 +205,10 @@ public class ConsoleUsersAction extends ConsoleApiAction {
|
||||
User.Builder builder =
|
||||
new User.Builder().setUserRoles(userRoles).setEmailAddress(newUser.getPrimaryEmail());
|
||||
tm().put(builder.build());
|
||||
User.grantIapPermission(
|
||||
nextAvailableEmail, maybeGroupEmailAddress, cloudTasksUtils, null, iamClient);
|
||||
|
||||
consoleApiParams.response().setStatus(SC_OK);
|
||||
consoleApiParams.response().setStatus(SC_CREATED);
|
||||
consoleApiParams
|
||||
.response()
|
||||
.setPayload(
|
||||
@@ -161,11 +222,13 @@ public class ConsoleUsersAction extends ConsoleApiAction {
|
||||
ACCOUNT_MANAGER)));
|
||||
}
|
||||
|
||||
private ImmutableList<User> getAllUsers() {
|
||||
private ImmutableList<User> getAllRegistrarUsers(String registrarId) {
|
||||
return tm().transact(
|
||||
() ->
|
||||
tm().loadAllOf(User.class).stream()
|
||||
.filter(u -> !u.getUserRoles().getRegistrarRoles().isEmpty())
|
||||
.filter(u -> u.getUserRoles().getRegistrarRoles().containsKey(registrarId))
|
||||
.collect(toImmutableList()));
|
||||
}
|
||||
|
||||
public record UserDeleteData(@Expose String userEmail) {}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,14 @@ package google.registry.export;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.export.ExportDomainListsAction.REGISTERED_DOMAINS_FILENAME;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureName.INCLUDE_PENDING_DELETE_DATE_FOR_DOMAINS;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureStatus.ACTIVE;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureStatus.INACTIVE;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
|
||||
import static google.registry.testing.DatabaseHelper.persistDeletedDomain;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
@@ -31,8 +35,14 @@ import com.google.cloud.storage.BlobId;
|
||||
import com.google.cloud.storage.StorageException;
|
||||
import com.google.cloud.storage.contrib.nio.testing.LocalStorageHelper;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import com.google.common.net.MediaType;
|
||||
import google.registry.gcs.GcsUtils;
|
||||
import google.registry.model.common.FeatureFlag;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.GracePeriod;
|
||||
import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.model.tld.Tld.TldType;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
@@ -56,7 +66,7 @@ class ExportDomainListsActionTest {
|
||||
|
||||
@RegisterExtension
|
||||
final JpaIntegrationTestExtension jpa =
|
||||
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
|
||||
new JpaTestExtensions.Builder().withClock(clock).buildIntegrationTestExtension();
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
@@ -70,6 +80,7 @@ class ExportDomainListsActionTest {
|
||||
action.gcsUtils = gcsUtils;
|
||||
action.clock = clock;
|
||||
action.driveConnection = driveConnection;
|
||||
persistFeatureFlag(INACTIVE);
|
||||
}
|
||||
|
||||
private void verifyExportedToDrive(String folderId, String domains) throws Exception {
|
||||
@@ -83,7 +94,7 @@ class ExportDomainListsActionTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_outputsOnlyActiveDomains() throws Exception {
|
||||
void test_outputsOnlyActiveDomains_txt() throws Exception {
|
||||
persistActiveDomain("onetwo.tld");
|
||||
persistActiveDomain("rudnitzky.tld");
|
||||
persistDeletedDomain("mortuary.tld", DateTime.parse("2001-03-14T10:11:12Z"));
|
||||
@@ -97,7 +108,22 @@ class ExportDomainListsActionTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_outputsOnlyDomainsOnRealTlds() throws Exception {
|
||||
void test_outputsOnlyActiveDomains_csv() throws Exception {
|
||||
persistFeatureFlag(ACTIVE);
|
||||
persistActiveDomain("onetwo.tld");
|
||||
persistActiveDomain("rudnitzky.tld");
|
||||
persistDeletedDomain("mortuary.tld", DateTime.parse("2001-03-14T10:11:12Z"));
|
||||
action.run();
|
||||
BlobId existingFile = BlobId.of("outputbucket", "tld.txt");
|
||||
String tlds = new String(gcsUtils.readBytesFrom(existingFile), UTF_8);
|
||||
// Check that it only contains the active domains, not the dead one.
|
||||
assertThat(tlds).isEqualTo("onetwo.tld,\nrudnitzky.tld,");
|
||||
verifyExportedToDrive("brouhaha", "onetwo.tld,\nrudnitzky.tld,");
|
||||
verifyNoMoreInteractions(driveConnection);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_outputsOnlyDomainsOnRealTlds_txt() throws Exception {
|
||||
persistActiveDomain("onetwo.tld");
|
||||
persistActiveDomain("rudnitzky.tld");
|
||||
persistActiveDomain("wontgo.testtld");
|
||||
@@ -116,7 +142,58 @@ class ExportDomainListsActionTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_outputsDomainsFromDifferentTldsToMultipleFiles() throws Exception {
|
||||
void test_outputsOnlyDomainsOnRealTlds_csv() throws Exception {
|
||||
persistFeatureFlag(ACTIVE);
|
||||
persistActiveDomain("onetwo.tld");
|
||||
persistActiveDomain("rudnitzky.tld");
|
||||
persistActiveDomain("wontgo.testtld");
|
||||
action.run();
|
||||
BlobId existingFile = BlobId.of("outputbucket", "tld.txt");
|
||||
String tlds = new String(gcsUtils.readBytesFrom(existingFile), UTF_8).trim();
|
||||
// Check that it only contains the domains on the real TLD, and not the test one.
|
||||
assertThat(tlds).isEqualTo("onetwo.tld,\nrudnitzky.tld,");
|
||||
// Make sure that the test TLD file wasn't written out.
|
||||
BlobId nonexistentFile = BlobId.of("outputbucket", "testtld.txt");
|
||||
assertThrows(StorageException.class, () -> gcsUtils.readBytesFrom(nonexistentFile));
|
||||
ImmutableList<String> ls = gcsUtils.listFolderObjects("outputbucket", "");
|
||||
assertThat(ls).containsExactly("tld.txt");
|
||||
verifyExportedToDrive("brouhaha", "onetwo.tld,\nrudnitzky.tld,");
|
||||
verifyNoMoreInteractions(driveConnection);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_outputIncludesDeletionTimes_forPendingDeletes_notRdemption() throws Exception {
|
||||
persistFeatureFlag(ACTIVE);
|
||||
// Domains pending delete (meaning the 5 day period, not counting the 30 day redemption period)
|
||||
// should include their pending deletion date
|
||||
persistActiveDomain("active.tld");
|
||||
Domain redemption = persistActiveDomain("redemption.tld");
|
||||
persistResource(
|
||||
redemption
|
||||
.asBuilder()
|
||||
.addStatusValue(StatusValue.PENDING_DELETE)
|
||||
.addGracePeriod(
|
||||
GracePeriod.createWithoutBillingEvent(
|
||||
GracePeriodStatus.REDEMPTION,
|
||||
redemption.getRepoId(),
|
||||
clock.nowUtc().plusDays(20),
|
||||
redemption.getCurrentSponsorRegistrarId()))
|
||||
.build());
|
||||
persistResource(
|
||||
persistActiveDomain("pendingdelete.tld")
|
||||
.asBuilder()
|
||||
.addStatusValue(StatusValue.PENDING_DELETE)
|
||||
.setDeletionTime(clock.nowUtc().plusDays(3))
|
||||
.build());
|
||||
|
||||
action.run();
|
||||
|
||||
verifyExportedToDrive(
|
||||
"brouhaha", "active.tld,\npendingdelete.tld,2020-02-05T02:02:02.000Z\nredemption.tld,");
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_outputsDomainsFromDifferentTldsToMultipleFiles_txt() throws Exception {
|
||||
createTld("tldtwo");
|
||||
persistResource(Tld.get("tldtwo").asBuilder().setDriveFolderId("hooray").build());
|
||||
|
||||
@@ -143,4 +220,43 @@ class ExportDomainListsActionTest {
|
||||
// tldthree does not have a drive id, so no export to drive is performed.
|
||||
verifyNoMoreInteractions(driveConnection);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_outputsDomainsFromDifferentTldsToMultipleFiles_csv() throws Exception {
|
||||
persistFeatureFlag(ACTIVE);
|
||||
createTld("tldtwo");
|
||||
persistResource(Tld.get("tldtwo").asBuilder().setDriveFolderId("hooray").build());
|
||||
|
||||
createTld("tldthree");
|
||||
// You'd think this test was written around Christmas, but it wasn't.
|
||||
persistActiveDomain("dasher.tld");
|
||||
persistActiveDomain("prancer.tld");
|
||||
persistActiveDomain("rudolph.tldtwo");
|
||||
persistActiveDomain("santa.tldtwo");
|
||||
persistActiveDomain("buddy.tldtwo");
|
||||
persistActiveDomain("cupid.tldthree");
|
||||
action.run();
|
||||
BlobId firstTldFile = BlobId.of("outputbucket", "tld.txt");
|
||||
String tlds = new String(gcsUtils.readBytesFrom(firstTldFile), UTF_8).trim();
|
||||
assertThat(tlds).isEqualTo("dasher.tld,\nprancer.tld,");
|
||||
BlobId secondTldFile = BlobId.of("outputbucket", "tldtwo.txt");
|
||||
String moreTlds = new String(gcsUtils.readBytesFrom(secondTldFile), UTF_8).trim();
|
||||
assertThat(moreTlds).isEqualTo("buddy.tldtwo,\nrudolph.tldtwo,\nsanta.tldtwo,");
|
||||
BlobId thirdTldFile = BlobId.of("outputbucket", "tldthree.txt");
|
||||
String evenMoreTlds = new String(gcsUtils.readBytesFrom(thirdTldFile), UTF_8).trim();
|
||||
assertThat(evenMoreTlds).isEqualTo("cupid.tldthree,");
|
||||
verifyExportedToDrive("brouhaha", "dasher.tld,\nprancer.tld,");
|
||||
verifyExportedToDrive("hooray", "buddy.tldtwo,\nrudolph.tldtwo,\nsanta.tldtwo,");
|
||||
// tldthree does not have a drive id, so no export to drive is performed.
|
||||
verifyNoMoreInteractions(driveConnection);
|
||||
}
|
||||
|
||||
private void persistFeatureFlag(FeatureFlag.FeatureStatus status) {
|
||||
persistResource(
|
||||
new FeatureFlag()
|
||||
.asBuilder()
|
||||
.setFeatureName(INCLUDE_PENDING_DELETE_DATE_FOR_DOMAINS)
|
||||
.setStatusMap(ImmutableSortedMap.of(START_OF_TIME, status))
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
|
||||
import static google.registry.testing.DatabaseHelper.loadByEntity;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static org.joda.time.DateTimeZone.UTC;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
@@ -33,6 +32,7 @@ import google.registry.model.EntityTestCase;
|
||||
import google.registry.model.common.FeatureFlag.FeatureFlagNotFoundException;
|
||||
import google.registry.model.common.FeatureFlag.FeatureStatus;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Duration;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Unit tests for {@link FeatureFlag}. */
|
||||
@@ -50,7 +50,7 @@ public class FeatureFlagTest extends EntityTestCase {
|
||||
.setStatusMap(
|
||||
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
|
||||
.put(START_OF_TIME, INACTIVE)
|
||||
.put(DateTime.now(UTC).plusWeeks(8), ACTIVE)
|
||||
.put(fakeClock.nowUtc().plusWeeks(8), ACTIVE)
|
||||
.build())
|
||||
.build();
|
||||
persistResource(featureFlag);
|
||||
@@ -66,7 +66,7 @@ public class FeatureFlagTest extends EntityTestCase {
|
||||
.setStatusMap(
|
||||
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
|
||||
.put(START_OF_TIME, INACTIVE)
|
||||
.put(DateTime.now(UTC).plusWeeks(8), ACTIVE)
|
||||
.put(fakeClock.nowUtc().plusWeeks(8), ACTIVE)
|
||||
.build())
|
||||
.build();
|
||||
persistResource(featureFlag);
|
||||
@@ -82,7 +82,7 @@ public class FeatureFlagTest extends EntityTestCase {
|
||||
.setStatusMap(
|
||||
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
|
||||
.put(START_OF_TIME, INACTIVE)
|
||||
.put(DateTime.now(UTC).plusWeeks(8), ACTIVE)
|
||||
.put(fakeClock.nowUtc().plusWeeks(8), ACTIVE)
|
||||
.build())
|
||||
.build());
|
||||
FeatureFlag featureFlag2 =
|
||||
@@ -92,7 +92,7 @@ public class FeatureFlagTest extends EntityTestCase {
|
||||
.setStatusMap(
|
||||
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
|
||||
.put(START_OF_TIME, INACTIVE)
|
||||
.put(DateTime.now(UTC).plusWeeks(3), INACTIVE)
|
||||
.put(fakeClock.nowUtc().plusWeeks(3), INACTIVE)
|
||||
.build())
|
||||
.build());
|
||||
FeatureFlag featureFlag3 =
|
||||
@@ -122,7 +122,7 @@ public class FeatureFlagTest extends EntityTestCase {
|
||||
.setStatusMap(
|
||||
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
|
||||
.put(START_OF_TIME, INACTIVE)
|
||||
.put(DateTime.now(UTC).plusWeeks(8), ACTIVE)
|
||||
.put(fakeClock.nowUtc().plusWeeks(8), ACTIVE)
|
||||
.build())
|
||||
.build());
|
||||
persistResource(
|
||||
@@ -131,7 +131,7 @@ public class FeatureFlagTest extends EntityTestCase {
|
||||
.setStatusMap(
|
||||
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
|
||||
.put(START_OF_TIME, INACTIVE)
|
||||
.put(DateTime.now(UTC).plusWeeks(3), INACTIVE)
|
||||
.put(fakeClock.nowUtc().plusWeeks(3), INACTIVE)
|
||||
.build())
|
||||
.build());
|
||||
FeatureFlagNotFoundException thrown =
|
||||
@@ -164,7 +164,7 @@ public class FeatureFlagTest extends EntityTestCase {
|
||||
.setStatusMap(
|
||||
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
|
||||
.put(START_OF_TIME, INACTIVE)
|
||||
.put(DateTime.now(UTC).plusWeeks(8), ACTIVE)
|
||||
.put(fakeClock.nowUtc().plusWeeks(8), ACTIVE)
|
||||
.build());
|
||||
IllegalArgumentException thrown =
|
||||
assertThrows(IllegalArgumentException.class, () -> featureFlagBuilder.build());
|
||||
@@ -180,7 +180,7 @@ public class FeatureFlagTest extends EntityTestCase {
|
||||
() ->
|
||||
featureFlagBuilder.setStatusMap(
|
||||
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
|
||||
.put(DateTime.now(UTC).plusWeeks(8), ACTIVE)
|
||||
.put(fakeClock.nowUtc().plusWeeks(8), ACTIVE)
|
||||
.build()));
|
||||
assertThat(thrown)
|
||||
.hasMessageThat()
|
||||
@@ -203,4 +203,37 @@ public class FeatureFlagTest extends EntityTestCase {
|
||||
fakeClock.setTo(DateTime.parse("2011-10-17TZ"));
|
||||
assertThat(tm().transact(() -> FeatureFlag.isActiveNow(TEST_FEATURE))).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_default_exists() {
|
||||
persistResource(
|
||||
new FeatureFlag.Builder()
|
||||
.setFeatureName(TEST_FEATURE)
|
||||
.setStatusMap(
|
||||
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
|
||||
.put(START_OF_TIME, INACTIVE)
|
||||
.put(fakeClock.nowUtc().plusWeeks(8), ACTIVE)
|
||||
.build())
|
||||
.build());
|
||||
tm().transact(
|
||||
() -> {
|
||||
assertThat(FeatureFlag.isActiveNowOrElse(TEST_FEATURE, false)).isFalse();
|
||||
assertThat(FeatureFlag.isActiveNowOrElse(TEST_FEATURE, true)).isFalse();
|
||||
});
|
||||
fakeClock.advanceBy(Duration.standardDays(365));
|
||||
tm().transact(
|
||||
() -> {
|
||||
assertThat(FeatureFlag.isActiveNowOrElse(TEST_FEATURE, false)).isTrue();
|
||||
assertThat(FeatureFlag.isActiveNowOrElse(TEST_FEATURE, true)).isTrue();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_default_doesNotExist() {
|
||||
tm().transact(
|
||||
() -> {
|
||||
assertThat(FeatureFlag.isActiveNowOrElse(TEST_FEATURE, false)).isFalse();
|
||||
assertThat(FeatureFlag.isActiveNowOrElse(TEST_FEATURE, true)).isTrue();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,12 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import com.beust.jcommander.ParameterException;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.fee.FeeQueryCommandExtensionItem;
|
||||
import google.registry.model.domain.token.AllocationToken;
|
||||
import google.registry.util.DateTimeUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -41,10 +45,49 @@ class GetAllocationTokenCommandTest extends CommandTestCase<GetAllocationTokenCo
|
||||
new AllocationToken.Builder()
|
||||
.setToken("foo")
|
||||
.setTokenType(SINGLE_USE)
|
||||
.setAllowedEppActions(
|
||||
ImmutableSet.of(FeeQueryCommandExtensionItem.CommandName.CREATE))
|
||||
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
|
||||
.setAllowedTlds(ImmutableSet.of("bar"))
|
||||
.setDiscountFraction(0.5)
|
||||
.setDiscountYears(2)
|
||||
.setTokenStatusTransitions(
|
||||
ImmutableSortedMap.of(
|
||||
DateTimeUtils.START_OF_TIME,
|
||||
AllocationToken.TokenStatus.NOT_STARTED,
|
||||
fakeClock.nowUtc(),
|
||||
AllocationToken.TokenStatus.VALID))
|
||||
.setDomainName("foo.bar")
|
||||
.build());
|
||||
runCommand("foo");
|
||||
assertInStdout(token.toString(), "Token foo was not redeemed.");
|
||||
assertStdoutIs(
|
||||
"""
|
||||
AllocationToken: {
|
||||
allowedClientIds=[TheRegistrar]
|
||||
allowedEppActions=[CREATE]
|
||||
allowedTlds=[bar]
|
||||
creationTime=CreateAutoTimestamp: {
|
||||
creationTime=2022-09-01T00:00:00.000Z
|
||||
}
|
||||
discountFraction=0.5
|
||||
discountPremiums=false
|
||||
discountPrice=null
|
||||
discountYears=2
|
||||
domainName=foo.bar
|
||||
redemptionHistoryId=null
|
||||
registrationBehavior=DEFAULT
|
||||
renewalPrice=null
|
||||
renewalPriceBehavior=DEFAULT
|
||||
token=foo
|
||||
tokenStatusTransitions={1970-01-01T00:00:00.000Z=NOT_STARTED, 2022-09-01T00:00:00.000Z=VALID}
|
||||
tokenType=SINGLE_USE
|
||||
updateTimestamp=UpdateAutoTimestamp: {
|
||||
lastUpdateTime=2022-09-01T00:00:00.000Z
|
||||
}
|
||||
}
|
||||
Token foo was not redeemed.
|
||||
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -33,6 +33,8 @@ import google.registry.model.billing.BillingRecurrence;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.transfer.DomainTransferData;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Nullable;
|
||||
import org.joda.money.CurrencyUnit;
|
||||
@@ -121,6 +123,25 @@ public class UpdateRecurrenceCommandTest extends CommandTestCase<UpdateRecurrenc
|
||||
Money.of(CurrencyUnit.USD, 9001));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_completedTransfer() throws Exception {
|
||||
Domain domain = persistDomain();
|
||||
domain =
|
||||
persistResource(
|
||||
domain
|
||||
.asBuilder()
|
||||
.setTransferData(
|
||||
new DomainTransferData.Builder()
|
||||
.setTransferStatus(TransferStatus.CLIENT_APPROVED)
|
||||
.setPendingTransferExpirationTime(fakeClock.nowUtc().minusDays(8))
|
||||
.build())
|
||||
.build());
|
||||
BillingRecurrence billingRecurrence = loadByKey(domain.getAutorenewBillingEvent());
|
||||
runCommandForced("domain.tld", "--renewal_price_behavior", "NONPREMIUM");
|
||||
assertNewBillingEventAndHistory(
|
||||
billingRecurrence.getId(), RenewalPriceBehavior.NONPREMIUM, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_nonexistentDomain() {
|
||||
assertThrows(
|
||||
|
||||
@@ -16,6 +16,8 @@ package google.registry.ui.server.console;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_CREATED;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -23,6 +25,7 @@ import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.api.services.directory.Directory;
|
||||
import com.google.api.services.directory.Directory.Users;
|
||||
import com.google.api.services.directory.Directory.Users.Delete;
|
||||
import com.google.api.services.directory.Directory.Users.Insert;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
@@ -31,17 +34,23 @@ import google.registry.model.console.GlobalRole;
|
||||
import google.registry.model.console.RegistrarRole;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.console.UserRoles;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.request.RequestModule;
|
||||
import google.registry.request.auth.AuthResult;
|
||||
import google.registry.testing.CloudTasksHelper;
|
||||
import google.registry.testing.ConsoleApiParamsUtils;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
import google.registry.testing.DeterministicStringGenerator;
|
||||
import google.registry.testing.FakeResponse;
|
||||
import google.registry.tools.IamClient;
|
||||
import google.registry.ui.server.console.ConsoleUsersAction.UserDeleteData;
|
||||
import google.registry.util.StringGenerator;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
@@ -53,6 +62,10 @@ class ConsoleUsersActionTest {
|
||||
private final Directory directory = mock(Directory.class);
|
||||
private final Users users = mock(Users.class);
|
||||
private final Insert insert = mock(Insert.class);
|
||||
private final Delete delete = mock(Delete.class);
|
||||
private final IamClient iamClient = mock(IamClient.class);
|
||||
|
||||
private final CloudTasksHelper cloudTasksHelper = new CloudTasksHelper();
|
||||
|
||||
private StringGenerator passwordGenerator =
|
||||
new DeterministicStringGenerator("abcdefghijklmnopqrstuvwxyz");
|
||||
@@ -112,7 +125,10 @@ class ConsoleUsersActionTest {
|
||||
|
||||
AuthResult authResult = AuthResult.createUser(user);
|
||||
ConsoleUsersAction action =
|
||||
createAction(Optional.of(ConsoleApiParamsUtils.createFake(authResult)), Optional.of("GET"));
|
||||
createAction(
|
||||
Optional.of(ConsoleApiParamsUtils.createFake(authResult)),
|
||||
Optional.of("GET"),
|
||||
Optional.empty());
|
||||
action.run();
|
||||
var response = ((FakeResponse) consoleApiParams.response());
|
||||
assertThat(response.getPayload())
|
||||
@@ -134,7 +150,10 @@ class ConsoleUsersActionTest {
|
||||
|
||||
AuthResult authResult = AuthResult.createUser(user);
|
||||
ConsoleUsersAction action =
|
||||
createAction(Optional.of(ConsoleApiParamsUtils.createFake(authResult)), Optional.of("GET"));
|
||||
createAction(
|
||||
Optional.of(ConsoleApiParamsUtils.createFake(authResult)),
|
||||
Optional.of("GET"),
|
||||
Optional.empty());
|
||||
action.run();
|
||||
var response = ((FakeResponse) consoleApiParams.response());
|
||||
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN);
|
||||
@@ -146,43 +165,141 @@ class ConsoleUsersActionTest {
|
||||
AuthResult authResult = AuthResult.createUser(user);
|
||||
ConsoleUsersAction action =
|
||||
createAction(
|
||||
Optional.of(ConsoleApiParamsUtils.createFake(authResult)), Optional.of("POST"));
|
||||
Optional.of(ConsoleApiParamsUtils.createFake(authResult)),
|
||||
Optional.of("POST"),
|
||||
Optional.empty());
|
||||
action.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
|
||||
when(directory.users()).thenReturn(users);
|
||||
when(users.insert(any(com.google.api.services.directory.model.User.class))).thenReturn(insert);
|
||||
action.run();
|
||||
var response = ((FakeResponse) consoleApiParams.response());
|
||||
assertThat(response.getStatus()).isEqualTo(SC_OK);
|
||||
assertThat(response.getStatus()).isEqualTo(SC_CREATED);
|
||||
assertThat(response.getPayload())
|
||||
.contains(
|
||||
"{\"password\":\"abcdefghijklmnop\",\"emailAddress\":\"email-1@email.com\",\"role\":\"ACCOUNT_MANAGER\"}");
|
||||
"{\"password\":\"abcdefghijklmnop\",\"emailAddress\":\"TheRegistrar-user1@email.com\",\"role\":\"ACCOUNT_MANAGER\"}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_limitedTo3NewUsers() throws IOException {
|
||||
void testFailure_noPermissionToDeleteUser() throws IOException {
|
||||
User user1 = DatabaseHelper.loadByKey(VKey.create(User.class, "test1@test.com"));
|
||||
AuthResult authResult =
|
||||
AuthResult.createUser(
|
||||
user1
|
||||
.asBuilder()
|
||||
.setUserRoles(user1.getUserRoles().asBuilder().setIsAdmin(true).build())
|
||||
.build());
|
||||
ConsoleUsersAction action =
|
||||
createAction(
|
||||
Optional.of(ConsoleApiParamsUtils.createFake(authResult)),
|
||||
Optional.of("DELETE"),
|
||||
Optional.of(new UserDeleteData("test3@test.com")));
|
||||
when(directory.users()).thenReturn(users);
|
||||
when(users.delete(any(String.class))).thenReturn(delete);
|
||||
action.run();
|
||||
var response = ((FakeResponse) consoleApiParams.response());
|
||||
assertThat(response.getStatus()).isEqualTo(SC_FORBIDDEN);
|
||||
assertThat(response.getPayload())
|
||||
.contains("Can't delete user not associated with registrarId TheRegistrar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_userDoesntExist() throws IOException {
|
||||
User user = DatabaseHelper.createAdminUser("email@email.com");
|
||||
DatabaseHelper.createAdminUser("email-1@email.com");
|
||||
DatabaseHelper.createAdminUser("email-2@email.com");
|
||||
DatabaseHelper.createAdminUser("email-3@email.com");
|
||||
AuthResult authResult = AuthResult.createUser(user);
|
||||
ConsoleUsersAction action =
|
||||
createAction(
|
||||
Optional.of(ConsoleApiParamsUtils.createFake(authResult)), Optional.of("POST"));
|
||||
Optional.of(ConsoleApiParamsUtils.createFake(authResult)),
|
||||
Optional.of("DELETE"),
|
||||
Optional.of(new UserDeleteData("email-1@email.com")));
|
||||
when(directory.users()).thenReturn(users);
|
||||
when(users.delete(any(String.class))).thenReturn(delete);
|
||||
action.run();
|
||||
var response = ((FakeResponse) consoleApiParams.response());
|
||||
assertThat(response.getStatus()).isEqualTo(SC_BAD_REQUEST);
|
||||
assertThat(response.getPayload()).contains("User email-1@email.com doesn't exist");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_deletesUser() throws IOException {
|
||||
User user1 = DatabaseHelper.loadByKey(VKey.create(User.class, "test1@test.com"));
|
||||
AuthResult authResult =
|
||||
AuthResult.createUser(
|
||||
user1
|
||||
.asBuilder()
|
||||
.setUserRoles(user1.getUserRoles().asBuilder().setIsAdmin(true).build())
|
||||
.build());
|
||||
ConsoleUsersAction action =
|
||||
createAction(
|
||||
Optional.of(ConsoleApiParamsUtils.createFake(authResult)),
|
||||
Optional.of("DELETE"),
|
||||
Optional.of(new UserDeleteData("test2@test.com")));
|
||||
action.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
|
||||
when(directory.users()).thenReturn(users);
|
||||
when(users.delete(any(String.class))).thenReturn(delete);
|
||||
action.run();
|
||||
var response = ((FakeResponse) consoleApiParams.response());
|
||||
assertThat(response.getStatus()).isEqualTo(SC_OK);
|
||||
assertThat(DatabaseHelper.loadByKeyIfPresent(VKey.create(User.class, "test2@test.com")))
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_limitedTo4UsersPerRegistrar() throws IOException {
|
||||
User user1 = DatabaseHelper.loadByKey(VKey.create(User.class, "test1@test.com"));
|
||||
AuthResult authResult =
|
||||
AuthResult.createUser(
|
||||
user1
|
||||
.asBuilder()
|
||||
.setUserRoles(user1.getUserRoles().asBuilder().setIsAdmin(true).build())
|
||||
.build());
|
||||
|
||||
DatabaseHelper.persistResources(
|
||||
IntStream.range(3, 5)
|
||||
.mapToObj(
|
||||
i ->
|
||||
new User.Builder()
|
||||
.setEmailAddress(String.format("test%s@test.com", i))
|
||||
.setUserRoles(
|
||||
new UserRoles()
|
||||
.asBuilder()
|
||||
.setRegistrarRoles(
|
||||
ImmutableMap.of("TheRegistrar", RegistrarRole.PRIMARY_CONTACT))
|
||||
.build())
|
||||
.build())
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
ConsoleUsersAction action =
|
||||
createAction(
|
||||
Optional.of(ConsoleApiParamsUtils.createFake(authResult)),
|
||||
Optional.of("POST"),
|
||||
Optional.empty());
|
||||
action.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
|
||||
when(directory.users()).thenReturn(users);
|
||||
when(users.insert(any(com.google.api.services.directory.model.User.class))).thenReturn(insert);
|
||||
action.run();
|
||||
var response = ((FakeResponse) consoleApiParams.response());
|
||||
assertThat(response.getStatus()).isEqualTo(SC_BAD_REQUEST);
|
||||
assertThat(response.getPayload()).contains("Extra users amount is limited to 3");
|
||||
assertThat(response.getPayload()).contains("Total users amount per registrar is limited to 4");
|
||||
}
|
||||
|
||||
private ConsoleUsersAction createAction(
|
||||
Optional<ConsoleApiParams> maybeConsoleApiParams, Optional<String> method)
|
||||
Optional<ConsoleApiParams> maybeConsoleApiParams,
|
||||
Optional<String> method,
|
||||
Optional<UserDeleteData> userDeleteData)
|
||||
throws IOException {
|
||||
consoleApiParams =
|
||||
maybeConsoleApiParams.orElseGet(
|
||||
() -> ConsoleApiParamsUtils.createFake(AuthResult.NOT_AUTHENTICATED));
|
||||
when(consoleApiParams.request().getMethod()).thenReturn(method.orElse("GET"));
|
||||
return new ConsoleUsersAction(
|
||||
consoleApiParams, GSON, directory, passwordGenerator, "TheRegistrar");
|
||||
consoleApiParams,
|
||||
GSON,
|
||||
directory,
|
||||
iamClient,
|
||||
"email.com",
|
||||
Optional.of("someRandomString"),
|
||||
passwordGenerator,
|
||||
userDeleteData,
|
||||
"TheRegistrar");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
SERVICE PATH CLASS METHODS OK MIN USER_POLICY
|
||||
FRONTEND /_dr/epp EppTlsAction POST n APP ADMIN
|
||||
CONSOLE /console-api/domain ConsoleDomainGetAction GET n USER PUBLIC
|
||||
CONSOLE /console-api/domain-list ConsoleDomainListAction GET n USER PUBLIC
|
||||
CONSOLE /console-api/dum-download ConsoleDumDownloadAction GET n USER PUBLIC
|
||||
CONSOLE /console-api/eppPassword ConsoleEppPasswordAction POST n USER PUBLIC
|
||||
CONSOLE /console-api/ote ConsoleOteAction GET,POST n USER PUBLIC
|
||||
CONSOLE /console-api/registrar ConsoleUpdateRegistrarAction POST n USER PUBLIC
|
||||
CONSOLE /console-api/registrars RegistrarsAction GET,POST n USER PUBLIC
|
||||
CONSOLE /console-api/registry-lock ConsoleRegistryLockAction GET,POST n USER PUBLIC
|
||||
CONSOLE /console-api/registry-lock-verify ConsoleRegistryLockVerifyAction GET n USER PUBLIC
|
||||
CONSOLE /console-api/settings/contacts ContactAction GET,POST n USER PUBLIC
|
||||
CONSOLE /console-api/settings/security SecurityAction POST n USER PUBLIC
|
||||
CONSOLE /console-api/settings/whois-fields WhoisRegistrarFieldsAction POST n USER PUBLIC
|
||||
CONSOLE /console-api/userdata ConsoleUserDataAction GET n USER PUBLIC
|
||||
CONSOLE /console-api/users ConsoleUsersAction GET,POST n USER PUBLIC
|
||||
SERVICE PATH CLASS METHODS OK MIN USER_POLICY
|
||||
FRONTEND /_dr/epp EppTlsAction POST n APP ADMIN
|
||||
CONSOLE /console-api/domain ConsoleDomainGetAction GET n USER PUBLIC
|
||||
CONSOLE /console-api/domain-list ConsoleDomainListAction GET n USER PUBLIC
|
||||
CONSOLE /console-api/dum-download ConsoleDumDownloadAction GET n USER PUBLIC
|
||||
CONSOLE /console-api/eppPassword ConsoleEppPasswordAction POST n USER PUBLIC
|
||||
CONSOLE /console-api/ote ConsoleOteAction GET,POST n USER PUBLIC
|
||||
CONSOLE /console-api/registrar ConsoleUpdateRegistrarAction POST n USER PUBLIC
|
||||
CONSOLE /console-api/registrars RegistrarsAction GET,POST n USER PUBLIC
|
||||
CONSOLE /console-api/registry-lock ConsoleRegistryLockAction GET,POST n USER PUBLIC
|
||||
CONSOLE /console-api/registry-lock-verify ConsoleRegistryLockVerifyAction GET n USER PUBLIC
|
||||
CONSOLE /console-api/settings/contacts ContactAction GET,POST n USER PUBLIC
|
||||
CONSOLE /console-api/settings/security SecurityAction POST n USER PUBLIC
|
||||
CONSOLE /console-api/settings/whois-fields WhoisRegistrarFieldsAction POST n USER PUBLIC
|
||||
CONSOLE /console-api/userdata ConsoleUserDataAction GET n USER PUBLIC
|
||||
CONSOLE /console-api/users ConsoleUsersAction GET,POST,DELETE n USER PUBLIC
|
||||
@@ -1,82 +1,82 @@
|
||||
SERVICE PATH CLASS METHODS OK MIN USER_POLICY
|
||||
FRONTEND /_dr/epp EppTlsAction POST n APP ADMIN
|
||||
BACKEND /_dr/admin/createGroups CreateGroupsAction POST n APP ADMIN
|
||||
BACKEND /_dr/admin/list/domains ListDomainsAction GET,POST n APP ADMIN
|
||||
BACKEND /_dr/admin/list/hosts ListHostsAction GET,POST n APP ADMIN
|
||||
BACKEND /_dr/admin/list/premiumLists ListPremiumListsAction GET,POST n APP ADMIN
|
||||
BACKEND /_dr/admin/list/registrars ListRegistrarsAction GET,POST n APP ADMIN
|
||||
BACKEND /_dr/admin/list/reservedLists ListReservedListsAction GET,POST n APP ADMIN
|
||||
BACKEND /_dr/admin/list/tlds ListTldsAction GET,POST n APP ADMIN
|
||||
BACKEND /_dr/admin/updateUserGroup UpdateUserGroupAction POST n APP ADMIN
|
||||
BACKEND /_dr/admin/verifyOte VerifyOteAction POST n APP ADMIN
|
||||
BACKEND /_dr/cron/fanout TldFanoutAction GET y APP ADMIN
|
||||
BACKEND /_dr/epptool EppToolAction POST n APP ADMIN
|
||||
BACKEND /_dr/loadtest LoadTestAction POST y APP ADMIN
|
||||
BACKEND /_dr/task/brdaCopy BrdaCopyAction POST y APP ADMIN
|
||||
BACKEND /_dr/task/bsaDownload BsaDownloadAction GET,POST n APP ADMIN
|
||||
BACKEND /_dr/task/bsaRefresh BsaRefreshAction GET,POST n APP ADMIN
|
||||
BACKEND /_dr/task/bsaValidate BsaValidateAction GET,POST n APP ADMIN
|
||||
BACKEND /_dr/task/copyDetailReports CopyDetailReportsAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/deleteExpiredDomains DeleteExpiredDomainsAction GET n APP ADMIN
|
||||
BACKEND /_dr/task/deleteLoadTestData DeleteLoadTestDataAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/deleteProberData DeleteProberDataAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/dnsRefresh RefreshDnsAction GET y APP ADMIN
|
||||
BACKEND /_dr/task/executeCannedScript CannedScriptExecutionAction POST,GET y APP ADMIN
|
||||
BACKEND /_dr/task/expandBillingRecurrences ExpandBillingRecurrencesAction GET n APP ADMIN
|
||||
BACKEND /_dr/task/exportDomainLists ExportDomainListsAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/exportPremiumTerms ExportPremiumTermsAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/exportReservedTerms ExportReservedTermsAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/generateInvoices GenerateInvoicesAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/generateSpec11 GenerateSpec11ReportAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/generateZoneFiles GenerateZoneFilesAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/icannReportingStaging IcannReportingStagingAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/icannReportingUpload IcannReportingUploadAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/nordnUpload NordnUploadAction POST y APP ADMIN
|
||||
BACKEND /_dr/task/nordnVerify NordnVerifyAction POST y APP ADMIN
|
||||
BACKEND /_dr/task/publishDnsUpdates PublishDnsUpdatesAction POST y APP ADMIN
|
||||
BACKEND /_dr/task/publishInvoices PublishInvoicesAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/publishSpec11 PublishSpec11ReportAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/rdeReport RdeReportAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/rdeStaging RdeStagingAction GET,POST n APP ADMIN
|
||||
BACKEND /_dr/task/rdeUpload RdeUploadAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/readDnsRefreshRequests ReadDnsRefreshRequestsAction POST y APP ADMIN
|
||||
BACKEND /_dr/task/refreshDnsForAllDomains RefreshDnsForAllDomainsAction GET n APP ADMIN
|
||||
BACKEND /_dr/task/refreshDnsOnHostRename RefreshDnsOnHostRenameAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/relockDomain RelockDomainAction POST y APP ADMIN
|
||||
BACKEND /_dr/task/resaveAllEppResourcesPipeline ResaveAllEppResourcesPipelineAction GET n APP ADMIN
|
||||
BACKEND /_dr/task/resaveEntity ResaveEntityAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/sendExpiringCertificateNotificationEmail SendExpiringCertificateNotificationEmailAction GET n APP ADMIN
|
||||
BACKEND /_dr/task/syncGroupMembers SyncGroupMembersAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/syncRegistrarsSheet SyncRegistrarsSheetAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/tmchCrl TmchCrlAction POST y APP ADMIN
|
||||
BACKEND /_dr/task/tmchDnl TmchDnlAction POST y APP ADMIN
|
||||
BACKEND /_dr/task/tmchSmdrl TmchSmdrlAction POST y APP ADMIN
|
||||
BACKEND /_dr/task/updateRegistrarRdapBaseUrls UpdateRegistrarRdapBaseUrlsAction GET y APP ADMIN
|
||||
BACKEND /_dr/task/uploadBsaUnavailableNames UploadBsaUnavailableDomainsAction GET,POST n APP ADMIN
|
||||
BACKEND /_dr/task/wipeOutContactHistoryPii WipeOutContactHistoryPiiAction GET n APP ADMIN
|
||||
PUBAPI /_dr/whois WhoisAction POST n APP ADMIN
|
||||
PUBAPI /check CheckApiAction GET n NONE PUBLIC
|
||||
PUBAPI /rdap/autnum/(*) RdapAutnumAction GET,HEAD n NONE PUBLIC
|
||||
PUBAPI /rdap/domain/(*) RdapDomainAction GET,HEAD n NONE PUBLIC
|
||||
PUBAPI /rdap/domains RdapDomainSearchAction GET,HEAD n NONE PUBLIC
|
||||
PUBAPI /rdap/entities RdapEntitySearchAction GET,HEAD n NONE PUBLIC
|
||||
PUBAPI /rdap/entity/(*) RdapEntityAction GET,HEAD n NONE PUBLIC
|
||||
PUBAPI /rdap/help(*) RdapHelpAction GET,HEAD n NONE PUBLIC
|
||||
PUBAPI /rdap/ip/(*) RdapIpAction GET,HEAD n NONE PUBLIC
|
||||
PUBAPI /rdap/nameserver/(*) RdapNameserverAction GET,HEAD n NONE PUBLIC
|
||||
PUBAPI /rdap/nameservers RdapNameserverSearchAction GET,HEAD n NONE PUBLIC
|
||||
PUBAPI /whois/(*) WhoisHttpAction GET n NONE PUBLIC
|
||||
CONSOLE /console-api/domain ConsoleDomainGetAction GET n USER PUBLIC
|
||||
CONSOLE /console-api/domain-list ConsoleDomainListAction GET n USER PUBLIC
|
||||
CONSOLE /console-api/dum-download ConsoleDumDownloadAction GET n USER PUBLIC
|
||||
CONSOLE /console-api/eppPassword ConsoleEppPasswordAction POST n USER PUBLIC
|
||||
CONSOLE /console-api/ote ConsoleOteAction GET,POST n USER PUBLIC
|
||||
CONSOLE /console-api/registrar ConsoleUpdateRegistrarAction POST n USER PUBLIC
|
||||
CONSOLE /console-api/registrars RegistrarsAction GET,POST n USER PUBLIC
|
||||
CONSOLE /console-api/registry-lock ConsoleRegistryLockAction GET,POST n USER PUBLIC
|
||||
CONSOLE /console-api/registry-lock-verify ConsoleRegistryLockVerifyAction GET n USER PUBLIC
|
||||
CONSOLE /console-api/settings/contacts ContactAction GET,POST n USER PUBLIC
|
||||
CONSOLE /console-api/settings/security SecurityAction POST n USER PUBLIC
|
||||
CONSOLE /console-api/settings/whois-fields WhoisRegistrarFieldsAction POST n USER PUBLIC
|
||||
CONSOLE /console-api/userdata ConsoleUserDataAction GET n USER PUBLIC
|
||||
CONSOLE /console-api/users ConsoleUsersAction GET,POST n USER PUBLIC
|
||||
SERVICE PATH CLASS METHODS OK MIN USER_POLICY
|
||||
FRONTEND /_dr/epp EppTlsAction POST n APP ADMIN
|
||||
BACKEND /_dr/admin/createGroups CreateGroupsAction POST n APP ADMIN
|
||||
BACKEND /_dr/admin/list/domains ListDomainsAction GET,POST n APP ADMIN
|
||||
BACKEND /_dr/admin/list/hosts ListHostsAction GET,POST n APP ADMIN
|
||||
BACKEND /_dr/admin/list/premiumLists ListPremiumListsAction GET,POST n APP ADMIN
|
||||
BACKEND /_dr/admin/list/registrars ListRegistrarsAction GET,POST n APP ADMIN
|
||||
BACKEND /_dr/admin/list/reservedLists ListReservedListsAction GET,POST n APP ADMIN
|
||||
BACKEND /_dr/admin/list/tlds ListTldsAction GET,POST n APP ADMIN
|
||||
BACKEND /_dr/admin/updateUserGroup UpdateUserGroupAction POST n APP ADMIN
|
||||
BACKEND /_dr/admin/verifyOte VerifyOteAction POST n APP ADMIN
|
||||
BACKEND /_dr/cron/fanout TldFanoutAction GET y APP ADMIN
|
||||
BACKEND /_dr/epptool EppToolAction POST n APP ADMIN
|
||||
BACKEND /_dr/loadtest LoadTestAction POST y APP ADMIN
|
||||
BACKEND /_dr/task/brdaCopy BrdaCopyAction POST y APP ADMIN
|
||||
BACKEND /_dr/task/bsaDownload BsaDownloadAction GET,POST n APP ADMIN
|
||||
BACKEND /_dr/task/bsaRefresh BsaRefreshAction GET,POST n APP ADMIN
|
||||
BACKEND /_dr/task/bsaValidate BsaValidateAction GET,POST n APP ADMIN
|
||||
BACKEND /_dr/task/copyDetailReports CopyDetailReportsAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/deleteExpiredDomains DeleteExpiredDomainsAction GET n APP ADMIN
|
||||
BACKEND /_dr/task/deleteLoadTestData DeleteLoadTestDataAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/deleteProberData DeleteProberDataAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/dnsRefresh RefreshDnsAction GET y APP ADMIN
|
||||
BACKEND /_dr/task/executeCannedScript CannedScriptExecutionAction POST,GET y APP ADMIN
|
||||
BACKEND /_dr/task/expandBillingRecurrences ExpandBillingRecurrencesAction GET n APP ADMIN
|
||||
BACKEND /_dr/task/exportDomainLists ExportDomainListsAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/exportPremiumTerms ExportPremiumTermsAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/exportReservedTerms ExportReservedTermsAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/generateInvoices GenerateInvoicesAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/generateSpec11 GenerateSpec11ReportAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/generateZoneFiles GenerateZoneFilesAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/icannReportingStaging IcannReportingStagingAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/icannReportingUpload IcannReportingUploadAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/nordnUpload NordnUploadAction POST y APP ADMIN
|
||||
BACKEND /_dr/task/nordnVerify NordnVerifyAction POST y APP ADMIN
|
||||
BACKEND /_dr/task/publishDnsUpdates PublishDnsUpdatesAction POST y APP ADMIN
|
||||
BACKEND /_dr/task/publishInvoices PublishInvoicesAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/publishSpec11 PublishSpec11ReportAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/rdeReport RdeReportAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/rdeStaging RdeStagingAction GET,POST n APP ADMIN
|
||||
BACKEND /_dr/task/rdeUpload RdeUploadAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/readDnsRefreshRequests ReadDnsRefreshRequestsAction POST y APP ADMIN
|
||||
BACKEND /_dr/task/refreshDnsForAllDomains RefreshDnsForAllDomainsAction GET n APP ADMIN
|
||||
BACKEND /_dr/task/refreshDnsOnHostRename RefreshDnsOnHostRenameAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/relockDomain RelockDomainAction POST y APP ADMIN
|
||||
BACKEND /_dr/task/resaveAllEppResourcesPipeline ResaveAllEppResourcesPipelineAction GET n APP ADMIN
|
||||
BACKEND /_dr/task/resaveEntity ResaveEntityAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/sendExpiringCertificateNotificationEmail SendExpiringCertificateNotificationEmailAction GET n APP ADMIN
|
||||
BACKEND /_dr/task/syncGroupMembers SyncGroupMembersAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/syncRegistrarsSheet SyncRegistrarsSheetAction POST n APP ADMIN
|
||||
BACKEND /_dr/task/tmchCrl TmchCrlAction POST y APP ADMIN
|
||||
BACKEND /_dr/task/tmchDnl TmchDnlAction POST y APP ADMIN
|
||||
BACKEND /_dr/task/tmchSmdrl TmchSmdrlAction POST y APP ADMIN
|
||||
BACKEND /_dr/task/updateRegistrarRdapBaseUrls UpdateRegistrarRdapBaseUrlsAction GET y APP ADMIN
|
||||
BACKEND /_dr/task/uploadBsaUnavailableNames UploadBsaUnavailableDomainsAction GET,POST n APP ADMIN
|
||||
BACKEND /_dr/task/wipeOutContactHistoryPii WipeOutContactHistoryPiiAction GET n APP ADMIN
|
||||
PUBAPI /_dr/whois WhoisAction POST n APP ADMIN
|
||||
PUBAPI /check CheckApiAction GET n NONE PUBLIC
|
||||
PUBAPI /rdap/autnum/(*) RdapAutnumAction GET,HEAD n NONE PUBLIC
|
||||
PUBAPI /rdap/domain/(*) RdapDomainAction GET,HEAD n NONE PUBLIC
|
||||
PUBAPI /rdap/domains RdapDomainSearchAction GET,HEAD n NONE PUBLIC
|
||||
PUBAPI /rdap/entities RdapEntitySearchAction GET,HEAD n NONE PUBLIC
|
||||
PUBAPI /rdap/entity/(*) RdapEntityAction GET,HEAD n NONE PUBLIC
|
||||
PUBAPI /rdap/help(*) RdapHelpAction GET,HEAD n NONE PUBLIC
|
||||
PUBAPI /rdap/ip/(*) RdapIpAction GET,HEAD n NONE PUBLIC
|
||||
PUBAPI /rdap/nameserver/(*) RdapNameserverAction GET,HEAD n NONE PUBLIC
|
||||
PUBAPI /rdap/nameservers RdapNameserverSearchAction GET,HEAD n NONE PUBLIC
|
||||
PUBAPI /whois/(*) WhoisHttpAction GET n NONE PUBLIC
|
||||
CONSOLE /console-api/domain ConsoleDomainGetAction GET n USER PUBLIC
|
||||
CONSOLE /console-api/domain-list ConsoleDomainListAction GET n USER PUBLIC
|
||||
CONSOLE /console-api/dum-download ConsoleDumDownloadAction GET n USER PUBLIC
|
||||
CONSOLE /console-api/eppPassword ConsoleEppPasswordAction POST n USER PUBLIC
|
||||
CONSOLE /console-api/ote ConsoleOteAction GET,POST n USER PUBLIC
|
||||
CONSOLE /console-api/registrar ConsoleUpdateRegistrarAction POST n USER PUBLIC
|
||||
CONSOLE /console-api/registrars RegistrarsAction GET,POST n USER PUBLIC
|
||||
CONSOLE /console-api/registry-lock ConsoleRegistryLockAction GET,POST n USER PUBLIC
|
||||
CONSOLE /console-api/registry-lock-verify ConsoleRegistryLockVerifyAction GET n USER PUBLIC
|
||||
CONSOLE /console-api/settings/contacts ContactAction GET,POST n USER PUBLIC
|
||||
CONSOLE /console-api/settings/security SecurityAction POST n USER PUBLIC
|
||||
CONSOLE /console-api/settings/whois-fields WhoisRegistrarFieldsAction POST n USER PUBLIC
|
||||
CONSOLE /console-api/userdata ConsoleUserDataAction GET n USER PUBLIC
|
||||
CONSOLE /console-api/users ConsoleUsersAction GET,POST,DELETE n USER PUBLIC
|
||||
@@ -470,7 +470,7 @@
|
||||
);
|
||||
|
||||
create table "FeatureFlag" (
|
||||
feature_name text not null check (feature_name in ('TEST_FEATURE','MINIMUM_DATASET_CONTACTS_OPTIONAL','MINIMUM_DATASET_CONTACTS_PROHIBITED')),
|
||||
feature_name text not null check (feature_name in ('TEST_FEATURE','MINIMUM_DATASET_CONTACTS_OPTIONAL','MINIMUM_DATASET_CONTACTS_PROHIBITED','INCLUDE_PENDING_DELETE_DATE_FOR_DOMAINS')),
|
||||
status hstore not null,
|
||||
primary key (feature_name)
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user