mirror of
https://github.com/google/nomulus
synced 2026-05-18 05:41:51 +00:00
Compare commits
11 Commits
nomulus-20
...
proxy-2024
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa377733be | ||
|
|
21950f7d82 | ||
|
|
e66aee0416 | ||
|
|
c7e1fc17d2 | ||
|
|
0c0b0df36e | ||
|
|
304f0002b4 | ||
|
|
15cf3e1bc0 | ||
|
|
eeed166310 | ||
|
|
e54075fea3 | ||
|
|
78cc1b2937 | ||
|
|
35f95bbbe4 |
@@ -166,12 +166,26 @@ export class BackendService {
|
||||
.pipe(catchError((err) => this.errorCatcher<User[]>(err)));
|
||||
}
|
||||
|
||||
createUser(registrarId: string): Observable<User> {
|
||||
createUser(registrarId: string, maybeUser: User | null): Observable<User> {
|
||||
return this.http
|
||||
.post<User>(`/console-api/users?registrarId=${registrarId}`, {})
|
||||
.post<User>(`/console-api/users?registrarId=${registrarId}`, maybeUser)
|
||||
.pipe(catchError((err) => this.errorCatcher<User>(err)));
|
||||
}
|
||||
|
||||
deleteUser(registrarId: string, user: User): Observable<any> {
|
||||
return this.http
|
||||
.delete<any>(`/console-api/users?registrarId=${registrarId}`, {
|
||||
body: JSON.stringify(user),
|
||||
})
|
||||
.pipe(catchError((err) => this.errorCatcher<any>(err)));
|
||||
}
|
||||
|
||||
updateUser(registrarId: string, updatedUser: User): Observable<any> {
|
||||
return this.http
|
||||
.put<User>(`/console-api/users?registrarId=${registrarId}`, updatedUser)
|
||||
.pipe(catchError((err) => this.errorCatcher<any>(err)));
|
||||
}
|
||||
|
||||
getUserData(): Observable<UserData> {
|
||||
return this.http
|
||||
.get<UserData>('/console-api/userdata')
|
||||
|
||||
@@ -27,6 +27,7 @@ export interface UserData {
|
||||
supportEmail: string;
|
||||
supportPhoneNumber: string;
|
||||
technicalDocsUrl: string;
|
||||
userRoles?: Map<string, string>;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
|
||||
125
console-webapp/src/app/users/userEdit.component.html
Normal file
125
console-webapp/src/app/users/userEdit.component.html
Normal file
@@ -0,0 +1,125 @@
|
||||
<div class="console-app__user-details">
|
||||
@if(isEditing) {
|
||||
<h1 class="mat-headline-4">Editing {{ userDetails().emailAddress }}</h1>
|
||||
<mat-divider></mat-divider>
|
||||
<div class="console-app__user-details-controls">
|
||||
<button
|
||||
mat-icon-button
|
||||
aria-label="Back to view user"
|
||||
(click)="isEditing = false"
|
||||
>
|
||||
<mat-icon>arrow_back</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<form (ngSubmit)="saveEdit()">
|
||||
<p>
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label
|
||||
>User Role:
|
||||
<mat-icon
|
||||
matTooltip="Viewer role doesn't allow making updates; Editor role allows updates, like Contacts delete or SSL certificate change"
|
||||
>help_outline</mat-icon
|
||||
></mat-label
|
||||
>
|
||||
<mat-select [(ngModel)]="userRole" name="userRole">
|
||||
<mat-option value="PRIMARY_CONTACT">Editor</mat-option>
|
||||
<mat-option value="ACCOUNT_MANAGER">Viewer</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</p>
|
||||
<button
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
aria-label="Save user"
|
||||
type="submit"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</form>
|
||||
} @else { @if(isNewUser) {
|
||||
<h1 class="mat-headline-4">
|
||||
{{ userDetails().emailAddress + " successfully created" }}
|
||||
</h1>
|
||||
} @else {
|
||||
<h1 class="mat-headline-4">User details</h1>
|
||||
<mat-divider></mat-divider>
|
||||
<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-flat-button
|
||||
color="primary"
|
||||
aria-label="Edit User"
|
||||
(click)="userRole = userDetails().role; isEditing = true"
|
||||
>
|
||||
<mat-icon>edit</mat-icon>
|
||||
Edit
|
||||
</button>
|
||||
<button
|
||||
mat-icon-button
|
||||
aria-label="Delete User"
|
||||
(click)="deleteUser()"
|
||||
[disabled]="isLoading"
|
||||
>
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="isNewUser" class="console-app__user-details-save-password">
|
||||
<mat-icon>priority_high</mat-icon>
|
||||
Please save the password. For your security, we do not store passwords in a
|
||||
recoverable format.
|
||||
</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>
|
||||
39
console-webapp/src/app/users/userEdit.component.scss
Normal file
39
console-webapp/src/app/users/userEdit.component.scss
Normal file
@@ -0,0 +1,39 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
&-save-password {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 15px 10px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 10px;
|
||||
}
|
||||
max-width: 616px;
|
||||
}
|
||||
}
|
||||
105
console-webapp/src/app/users/userEdit.component.ts
Normal file
105
console-webapp/src/app/users/userEdit.component.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
// 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, computed } 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 { UsersService, roleToDescription } from './users.service';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'app-user-edit',
|
||||
templateUrl: './userEdit.component.html',
|
||||
styleUrls: ['./userEdit.component.scss'],
|
||||
standalone: true,
|
||||
imports: [
|
||||
FormsModule,
|
||||
MaterialModule,
|
||||
SnackBarModule,
|
||||
CommonModule,
|
||||
SelectedRegistrarModule,
|
||||
],
|
||||
providers: [],
|
||||
})
|
||||
export class UserEditComponent {
|
||||
isEditing = false;
|
||||
isPasswordVisible = false;
|
||||
isNewUser = false;
|
||||
isLoading = false;
|
||||
userRole = '';
|
||||
|
||||
userDetails = computed(() => {
|
||||
return this.usersService
|
||||
.users()
|
||||
.filter(
|
||||
(u) => u.emailAddress === this.usersService.currentlyOpenUserEmail()
|
||||
)[0];
|
||||
});
|
||||
|
||||
constructor(
|
||||
protected registrarService: RegistrarService,
|
||||
protected usersService: UsersService,
|
||||
private _snackBar: MatSnackBar
|
||||
) {
|
||||
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()).subscribe({
|
||||
error: (err) => {
|
||||
this._snackBar.open(err.error || err.message);
|
||||
this.isLoading = false;
|
||||
},
|
||||
complete: () => {
|
||||
this.isLoading = false;
|
||||
this.goBack();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
goBack() {
|
||||
this.usersService.currentlyOpenUserEmail.set('');
|
||||
}
|
||||
|
||||
saveEdit() {
|
||||
this.isLoading = true;
|
||||
this.usersService
|
||||
.updateUser({
|
||||
role: this.userRole,
|
||||
emailAddress: this.userDetails().emailAddress,
|
||||
})
|
||||
.subscribe({
|
||||
error: (err) => {
|
||||
this._snackBar.open(err.error || err.message);
|
||||
this.isLoading = false;
|
||||
},
|
||||
complete: () => {
|
||||
this.isLoading = false;
|
||||
this.isEditing = false;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,43 +1,96 @@
|
||||
<app-selected-registrar-wrapper>
|
||||
@if (!isLoading) {
|
||||
@if(isLoading) {
|
||||
<div class="console-app__users-spinner">
|
||||
<mat-spinner />
|
||||
</div>
|
||||
} @else if(selectingExistingUser) {
|
||||
|
||||
<div class="console-app__users">
|
||||
<h1 class="mat-headline-4">Add existing user</h1>
|
||||
|
||||
<p>
|
||||
<button
|
||||
mat-icon-button
|
||||
aria-label="Back to users list"
|
||||
(click)="selectingExistingUser = false"
|
||||
>
|
||||
<mat-icon>arrow_back</mat-icon>
|
||||
</button>
|
||||
</p>
|
||||
<h1>Select registrar from which to add a new user</h1>
|
||||
<p>
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Registrar</mat-label>
|
||||
<mat-select
|
||||
[(ngModel)]="selectedRegistrarId"
|
||||
name="selectedRegistrarId"
|
||||
(selectionChange)="onRegistrarSelectionChange($event)"
|
||||
>
|
||||
@for (registrar of registrarService.registrars(); track registrar) {
|
||||
<mat-option [value]="registrar.registrarId">{{
|
||||
registrar.registrarId
|
||||
}}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</p>
|
||||
@if(usersSelection.length) {
|
||||
<app-users-list
|
||||
[users]="usersSelection"
|
||||
(onSelect)="existingUserSelected($event)"
|
||||
/>
|
||||
<p class="console-app__users-add-existing">
|
||||
<button
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
aria-label="Add user"
|
||||
(click)="submitExistingUser()"
|
||||
[disabled]="!selectedExistingUser"
|
||||
>
|
||||
Add user
|
||||
</button>
|
||||
<button
|
||||
mat-stroked-button
|
||||
aria-label="Cancel adding existing user"
|
||||
(click)="selectingExistingUser = false"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</p>
|
||||
}
|
||||
</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>
|
||||
<div class="spacer"></div>
|
||||
<button
|
||||
mat-flat-button
|
||||
(click)="createNewUser()"
|
||||
aria-label="Create new user"
|
||||
color="primary"
|
||||
>
|
||||
Create a Viewer User
|
||||
</button>
|
||||
<div class="console-app__users-header-buttons">
|
||||
<button
|
||||
class="console-app__users-header-add"
|
||||
mat-stroked-button
|
||||
(click)="addExistingUser()"
|
||||
aria-label="Create new user"
|
||||
color="primary"
|
||||
>
|
||||
<mat-icon>add</mat-icon>
|
||||
Add existing user
|
||||
</button>
|
||||
<button
|
||||
mat-flat-button
|
||||
(click)="createNewUser()"
|
||||
aria-label="Create new user"
|
||||
color="primary"
|
||||
>
|
||||
Create a Viewer User
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<mat-table
|
||||
[dataSource]="dataSource"
|
||||
class="mat-elevation-z0"
|
||||
class="console-app__users-table"
|
||||
matSort
|
||||
>
|
||||
<ng-container
|
||||
*ngFor="let column of columns"
|
||||
[matColumnDef]="column.columnDef"
|
||||
>
|
||||
<mat-header-cell *matHeaderCellDef>
|
||||
{{ column.header }}
|
||||
</mat-header-cell>
|
||||
<mat-cell
|
||||
*matCellDef="let row"
|
||||
[innerHTML]="column.cell(row)"
|
||||
></mat-cell>
|
||||
</ng-container>
|
||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
|
||||
</mat-table>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="console-app__users-spinner">
|
||||
<mat-spinner />
|
||||
<app-users-list
|
||||
[users]="usersService.users()"
|
||||
(onSelect)="openDetails($event)"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</app-selected-registrar-wrapper>
|
||||
|
||||
@@ -13,26 +13,37 @@
|
||||
// limitations under the License.
|
||||
|
||||
.console-app {
|
||||
&__users {
|
||||
max-width: 1024px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
&__users-spinner {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
$min-width: 756px;
|
||||
$max-width: 1024px;
|
||||
|
||||
&__users-table {
|
||||
min-width: $min-width !important;
|
||||
max-width: $max-width;
|
||||
}
|
||||
|
||||
&__users-new {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
&__users-add-existing {
|
||||
margin-top: 20px;
|
||||
> button {
|
||||
margin-right: 15px;
|
||||
}
|
||||
}
|
||||
&__users-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
&-buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
button {
|
||||
margin: 0 15px 15px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,36 +14,18 @@
|
||||
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { Component, effect, ViewChild } from '@angular/core';
|
||||
import { Component, effect } from '@angular/core';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { MatSort } from '@angular/material/sort';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { SelectedRegistrarModule } from '../app.module';
|
||||
import { MaterialModule } from '../material.module';
|
||||
import { RegistrarService } from '../registrar/registrar.service';
|
||||
import { SnackBarModule } from '../snackbar.module';
|
||||
import { UserEditComponent } from './userEdit.component';
|
||||
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';
|
||||
};
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
columnDef: 'emailAddress',
|
||||
header: 'User email',
|
||||
cell: (record: User) => `${record.emailAddress || ''}`,
|
||||
},
|
||||
{
|
||||
columnDef: 'role',
|
||||
header: 'User role',
|
||||
cell: (record: User) => `${roleToDescription(record.role)}`,
|
||||
},
|
||||
];
|
||||
import { UserDataService } from '../shared/services/userData.service';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { UsersListComponent } from './usersList.component';
|
||||
import { MatSelectChange } from '@angular/material/select';
|
||||
|
||||
@Component({
|
||||
selector: 'app-users',
|
||||
@@ -51,40 +33,45 @@ export const columns = [
|
||||
styleUrls: ['./users.component.scss'],
|
||||
standalone: true,
|
||||
imports: [
|
||||
FormsModule,
|
||||
MaterialModule,
|
||||
SnackBarModule,
|
||||
CommonModule,
|
||||
SelectedRegistrarModule,
|
||||
UsersListComponent,
|
||||
UserEditComponent,
|
||||
],
|
||||
providers: [UsersService],
|
||||
})
|
||||
export class UsersComponent {
|
||||
dataSource: MatTableDataSource<User>;
|
||||
columns = columns;
|
||||
displayedColumns = this.columns.map((c) => c.columnDef);
|
||||
isLoading = false;
|
||||
|
||||
@ViewChild(MatSort) sort!: MatSort;
|
||||
selectingExistingUser = false;
|
||||
selectedRegistrarId = '';
|
||||
usersSelection: User[] = [];
|
||||
selectedExistingUser: User | undefined;
|
||||
|
||||
constructor(
|
||||
protected registrarService: RegistrarService,
|
||||
protected usersService: UsersService,
|
||||
private userDataService: UserDataService,
|
||||
private _snackBar: MatSnackBar
|
||||
) {
|
||||
this.dataSource = new MatTableDataSource<User>(usersService.users());
|
||||
|
||||
effect(() => {
|
||||
if (registrarService.registrarId()) {
|
||||
this.loadUsers();
|
||||
}
|
||||
});
|
||||
effect(() => {
|
||||
this.dataSource.data = usersService.users();
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.dataSource.sort = this.sort;
|
||||
addExistingUser() {
|
||||
this.selectingExistingUser = true;
|
||||
this.selectedRegistrarId = '';
|
||||
this.usersSelection = [];
|
||||
this.selectedExistingUser = undefined;
|
||||
}
|
||||
|
||||
existingUserSelected(user: User) {
|
||||
this.selectedExistingUser = user;
|
||||
}
|
||||
|
||||
loadUsers() {
|
||||
@@ -92,6 +79,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;
|
||||
@@ -101,22 +89,50 @@ 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,
|
||||
}
|
||||
);
|
||||
},
|
||||
this.usersService.createOrAddNewUser(null).subscribe({
|
||||
error: (err: HttpErrorResponse) => {
|
||||
this._snackBar.open(err.error || err.message);
|
||||
this.isLoading = false;
|
||||
},
|
||||
complete: () => {
|
||||
this.isLoading = false;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
openDetails(user: User) {
|
||||
this.usersService.currentlyOpenUserEmail.set(user.emailAddress);
|
||||
}
|
||||
|
||||
onRegistrarSelectionChange(e: MatSelectChange) {
|
||||
if (e.value) {
|
||||
this.usersService.fetchUsersForRegistrar(e.value).subscribe({
|
||||
error: (err) => {
|
||||
this._snackBar.open(err.error || err.message);
|
||||
},
|
||||
next: (users) => {
|
||||
this.usersSelection = users;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
submitExistingUser() {
|
||||
this.isLoading = true;
|
||||
if (this.selectedExistingUser) {
|
||||
this.usersService
|
||||
.createOrAddNewUser(this.selectedExistingUser)
|
||||
.subscribe({
|
||||
error: (err) => {
|
||||
this._snackBar.open(err.error || err.message);
|
||||
this.isLoading = false;
|
||||
},
|
||||
complete: () => {
|
||||
this.isLoading = false;
|
||||
this.selectingExistingUser = false;
|
||||
this.loadUsers();
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,29 +13,43 @@
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable, signal } from '@angular/core';
|
||||
import { tap } from 'rxjs';
|
||||
import { switchMap, 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 === '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,
|
||||
private registrarService: RegistrarService
|
||||
) {}
|
||||
|
||||
fetchUsersForRegistrar(registrarId: string) {
|
||||
return this.backendService.getUsers(registrarId);
|
||||
}
|
||||
|
||||
fetchUsers() {
|
||||
return this.backendService
|
||||
.getUsers(this.registrarService.registrarId())
|
||||
@@ -46,13 +60,29 @@ export class UsersService {
|
||||
);
|
||||
}
|
||||
|
||||
createNewUser() {
|
||||
createOrAddNewUser(maybeExistingUser: User | null) {
|
||||
return this.backendService
|
||||
.createUser(this.registrarService.registrarId())
|
||||
.createUser(this.registrarService.registrarId(), maybeExistingUser)
|
||||
.pipe(
|
||||
tap((newUser: User) => {
|
||||
this.users.set([...this.users(), newUser]);
|
||||
if (newUser) {
|
||||
this.users.set([...this.users(), newUser]);
|
||||
this.currentlyOpenUserEmail.set(newUser.emailAddress);
|
||||
this.isNewUser = true;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
deleteUser(user: User) {
|
||||
return this.backendService
|
||||
.deleteUser(this.registrarService.registrarId(), user)
|
||||
.pipe(switchMap((_) => this.fetchUsers()));
|
||||
}
|
||||
|
||||
updateUser(updatedUser: User) {
|
||||
return this.backendService
|
||||
.updateUser(this.registrarService.registrarId(), updatedUser)
|
||||
.pipe(switchMap((_) => this.fetchUsers()));
|
||||
}
|
||||
}
|
||||
|
||||
24
console-webapp/src/app/users/usersList.component.html
Normal file
24
console-webapp/src/app/users/usersList.component.html
Normal file
@@ -0,0 +1,24 @@
|
||||
<div class="console-app__users-table-wrapper">
|
||||
<mat-table
|
||||
[dataSource]="dataSource"
|
||||
class="mat-elevation-z0"
|
||||
class="console-app__users-table"
|
||||
matSort
|
||||
>
|
||||
<ng-container
|
||||
*ngFor="let column of columns"
|
||||
[matColumnDef]="column.columnDef"
|
||||
>
|
||||
<mat-header-cell *matHeaderCellDef>
|
||||
{{ column.header }}
|
||||
</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row" [innerHTML]="column.cell(row)"></mat-cell>
|
||||
</ng-container>
|
||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||
<mat-row
|
||||
*matRowDef="let row; columns: displayedColumns"
|
||||
[class.rowSelected]="isRowSelected(row)"
|
||||
(click)="onClick(row)"
|
||||
></mat-row>
|
||||
</mat-table>
|
||||
</div>
|
||||
14
console-webapp/src/app/users/usersList.component.scss
Normal file
14
console-webapp/src/app/users/usersList.component.scss
Normal file
@@ -0,0 +1,14 @@
|
||||
.console-app {
|
||||
&__users-table {
|
||||
min-width: 616px;
|
||||
.rowSelected {
|
||||
background-color: var(--light-highlight);
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
&__users-table-wrapper {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
78
console-webapp/src/app/users/usersList.component.ts
Normal file
78
console-webapp/src/app/users/usersList.component.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
// 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,
|
||||
effect,
|
||||
EventEmitter,
|
||||
input,
|
||||
Output,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import { MaterialModule } from '../material.module';
|
||||
import { User, roleToDescription } from './users.service';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { MatSort } from '@angular/material/sort';
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
columnDef: 'emailAddress',
|
||||
header: 'User email',
|
||||
cell: (record: User) => `${record.emailAddress || ''}`,
|
||||
},
|
||||
{
|
||||
columnDef: 'role',
|
||||
header: 'User role',
|
||||
cell: (record: User) => `${roleToDescription(record.role)}`,
|
||||
},
|
||||
];
|
||||
|
||||
@Component({
|
||||
selector: 'app-users-list',
|
||||
templateUrl: './usersList.component.html',
|
||||
styleUrls: ['./usersList.component.scss'],
|
||||
standalone: true,
|
||||
imports: [MaterialModule, CommonModule],
|
||||
providers: [],
|
||||
})
|
||||
export class UsersListComponent {
|
||||
columns = columns;
|
||||
displayedColumns = this.columns.map((c) => c.columnDef);
|
||||
dataSource: MatTableDataSource<User>;
|
||||
selectedRow!: User;
|
||||
users = input<User[]>([]);
|
||||
@Output() onSelect = new EventEmitter<User>();
|
||||
@ViewChild(MatSort) sort!: MatSort;
|
||||
|
||||
constructor() {
|
||||
this.dataSource = new MatTableDataSource<User>(this.users());
|
||||
effect(() => {
|
||||
this.dataSource.data = this.users();
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.dataSource.sort = this.sort;
|
||||
}
|
||||
|
||||
onClick(row: User) {
|
||||
this.selectedRow = row;
|
||||
this.onSelect.emit(row);
|
||||
}
|
||||
|
||||
isRowSelected(row: User) {
|
||||
return row === this.selectedRow;
|
||||
}
|
||||
}
|
||||
@@ -1743,6 +1743,17 @@ public final class RegistryConfig {
|
||||
CONFIG_SETTINGS.get().registryPolicy.tieredPricingPromotionRegistrarIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set of registrars for which we do not send poll messages on standard domain deletion.
|
||||
*
|
||||
* <p>For these registrars we won't send a poll message in order to avoid database contention. See
|
||||
* b/379331882 for more details.
|
||||
*/
|
||||
public static ImmutableSet<String> getNoPollMessageOnDeletionRegistrarIds() {
|
||||
return ImmutableSet.copyOf(
|
||||
CONFIG_SETTINGS.get().registryPolicy.noPollMessageOnDeletionRegistrarIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Memoizes loading of the {@link RegistryConfigSettings} POJO.
|
||||
*
|
||||
|
||||
@@ -115,6 +115,7 @@ public class RegistryConfigSettings {
|
||||
public boolean requireSslCertificates;
|
||||
public double sunriseDomainCreateDiscount;
|
||||
public Set<String> tieredPricingPromotionRegistrarIds;
|
||||
public Set<String> noPollMessageOnDeletionRegistrarIds;
|
||||
}
|
||||
|
||||
/** Configuration for Hibernate. */
|
||||
|
||||
@@ -220,6 +220,9 @@ registryPolicy:
|
||||
# In addition, we will return the non-promotional (i.e. incorrect) price on
|
||||
# domain create requests.
|
||||
tieredPricingPromotionRegistrarIds: []
|
||||
# List of registrars for which we won't send poll message on standard domain
|
||||
# deletions.
|
||||
noPollMessageOnDeletionRegistrarIds: []
|
||||
|
||||
hibernate:
|
||||
# If set to false, calls to tm().transact() cannot be nested. If set to true,
|
||||
|
||||
@@ -11,6 +11,8 @@ registryPolicy:
|
||||
Line 2 is this 1.
|
||||
tieredPricingPromotionRegistrarIds:
|
||||
- NewRegistrar
|
||||
noPollMessageOnDeletionRegistrarIds:
|
||||
- NewRegistrar
|
||||
|
||||
caching:
|
||||
singletonCacheRefreshSeconds: 0
|
||||
|
||||
@@ -105,7 +105,7 @@ public final class ExtensionManager {
|
||||
}
|
||||
|
||||
private static final ImmutableSet<EppRequestSource> ALLOWED_METADATA_EPP_REQUEST_SOURCES =
|
||||
ImmutableSet.of(EppRequestSource.TOOL, EppRequestSource.BACKEND);
|
||||
ImmutableSet.of(EppRequestSource.BACKEND, EppRequestSource.CONSOLE, EppRequestSource.TOOL);
|
||||
|
||||
private void checkForRestrictedExtensions(
|
||||
ImmutableSet<Class<? extends CommandExtension>> suppliedExtensions)
|
||||
|
||||
@@ -75,7 +75,8 @@ public class FlowRunner {
|
||||
flowReporter.recordToLogs();
|
||||
}
|
||||
eppMetricBuilder.setCommandNameFromFlow(flowClass.getSimpleName());
|
||||
if (!isTransactional) {
|
||||
// We may already be in a transaction, e.g., when invoked by DeleteExpiredDomainsAction.
|
||||
if (!isTransactional || jpaTransactionManager.inTransaction()) {
|
||||
return EppOutput.create(flowProvider.get().run());
|
||||
}
|
||||
try {
|
||||
|
||||
@@ -44,7 +44,9 @@ import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.batch.AsyncTaskEnqueuer;
|
||||
import google.registry.config.RegistryConfig;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.EppException.AssociationProhibitsOperationException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
@@ -117,6 +119,8 @@ import org.joda.time.Duration;
|
||||
@ReportingSpec(ActivityReportField.DOMAIN_DELETE)
|
||||
public final class DomainDeleteFlow implements MutatingFlow {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
private static final ImmutableSet<StatusValue> DISALLOWED_STATUSES = ImmutableSet.of(
|
||||
StatusValue.CLIENT_DELETE_PROHIBITED,
|
||||
StatusValue.PENDING_DELETE,
|
||||
@@ -212,10 +216,17 @@ public final class DomainDeleteFlow implements MutatingFlow {
|
||||
// superuser (i.e. the registrar didn't request this delete and thus should be notified even if
|
||||
// it is synchronous).
|
||||
if (durationUntilDelete.isLongerThan(Duration.ZERO) || isSuperuser) {
|
||||
PollMessage.OneTime deletePollMessage =
|
||||
createDeletePollMessage(existingDomain, domainHistoryId, deletionTime);
|
||||
entitiesToSave.add(deletePollMessage);
|
||||
builder.setDeletePollMessage(deletePollMessage.createVKey());
|
||||
if (RegistryConfig.getNoPollMessageOnDeletionRegistrarIds()
|
||||
.contains(existingDomain.getCurrentSponsorRegistrarId())) {
|
||||
logger.atInfo().log(
|
||||
"Skipping poll message on domain deletion for registrar %s due to configuration",
|
||||
existingDomain.getCurrentSponsorRegistrarId());
|
||||
} else {
|
||||
PollMessage.OneTime deletePollMessage =
|
||||
createDeletePollMessage(existingDomain, domainHistoryId, deletionTime);
|
||||
entitiesToSave.add(deletePollMessage);
|
||||
builder.setDeletePollMessage(deletePollMessage.createVKey());
|
||||
}
|
||||
}
|
||||
|
||||
// Send a second poll message immediately if the domain is being deleted asynchronously by a
|
||||
|
||||
@@ -183,7 +183,9 @@ public final class DomainRestoreRequestFlow implements MutatingFlow {
|
||||
DomainHistory domainHistory = buildDomainHistory(newDomain, now);
|
||||
entitiesToSave.add(newDomain, domainHistory, autorenewEvent, autorenewPollMessage);
|
||||
tm().putAll(entitiesToSave.build());
|
||||
tm().delete(existingDomain.getDeletePollMessage());
|
||||
if (existingDomain.getDeletePollMessage() != null) {
|
||||
tm().delete(existingDomain.getDeletePollMessage());
|
||||
}
|
||||
requestDomainDnsRefresh(existingDomain.getDomainName());
|
||||
return responseBuilder
|
||||
.setExtensions(createResponseExtensions(feesAndCredits, feeUpdate, isExpired))
|
||||
|
||||
@@ -37,6 +37,8 @@ import google.registry.model.poll.MessageQueueInfo;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.poll.PollMessageExternalKeyConverter;
|
||||
import google.registry.model.poll.PollMessageExternalKeyConverter.PollMessageExternalKeyParseException;
|
||||
import google.registry.persistence.IsolationLevel;
|
||||
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
@@ -55,6 +57,7 @@ import org.joda.time.DateTime;
|
||||
* @error {@link PollAckFlow.MissingMessageIdException}
|
||||
* @error {@link PollAckFlow.NotAuthorizedToAckMessageException}
|
||||
*/
|
||||
@IsolationLevel(value = TransactionIsolationLevel.TRANSACTION_READ_COMMITTED)
|
||||
public final class PollAckFlow implements MutatingFlow {
|
||||
|
||||
@Inject ExtensionManager extensionManager;
|
||||
|
||||
@@ -32,6 +32,8 @@ import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.poll.MessageQueueInfo;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.poll.PollMessageExternalKeyConverter;
|
||||
import google.registry.persistence.IsolationLevel;
|
||||
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
import org.joda.time.DateTime;
|
||||
@@ -47,6 +49,7 @@ import org.joda.time.DateTime;
|
||||
*
|
||||
* @error {@link PollRequestFlow.UnexpectedMessageIdException}
|
||||
*/
|
||||
@IsolationLevel(value = TransactionIsolationLevel.TRANSACTION_READ_COMMITTED)
|
||||
public final class PollRequestFlow implements TransactionalFlow {
|
||||
|
||||
@Inject ExtensionManager extensionManager;
|
||||
|
||||
@@ -33,7 +33,9 @@ public @interface Action {
|
||||
enum Method {
|
||||
GET,
|
||||
HEAD,
|
||||
POST
|
||||
POST,
|
||||
PUT,
|
||||
DELETE
|
||||
}
|
||||
|
||||
interface Service {
|
||||
|
||||
@@ -36,9 +36,9 @@ import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* An authenticam mechanism that verifies the OIDC token.
|
||||
* An authentication mechanism that verifies the OIDC token.
|
||||
*
|
||||
* <p>Currently, two flavors are supported: one that checkes for the OIDC token as a regular bearer
|
||||
* <p>Currently, two flavors are supported: one that checks for the OIDC token as a regular bearer
|
||||
* token, and another that checks for the OIDC token passed by IAP. In both cases, the {@link
|
||||
* AuthResult} with the highest {@link AuthLevel} possible is returned. So, if the email address for
|
||||
* which the token is minted exists both as a {@link User} and as a service account, the returned
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
package google.registry.tools;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
@@ -41,6 +42,15 @@ public abstract class CreateOrUpdateUserCommand extends ConfirmingCommand {
|
||||
+ " to remove the field.")
|
||||
private String registryLockEmailAddress;
|
||||
|
||||
@Nullable
|
||||
@Parameter(
|
||||
names = "--registry_lock_password",
|
||||
description =
|
||||
"Sets the registry lock password for this user, or removes it (allowing the user to"
|
||||
+ " re-set it). Do not set the password explicitly unless in exceptional"
|
||||
+ " circumstances.")
|
||||
private String registryLockPassword;
|
||||
|
||||
@Nullable
|
||||
@Parameter(
|
||||
names = "--admin",
|
||||
@@ -94,6 +104,25 @@ public abstract class CreateOrUpdateUserCommand extends ConfirmingCommand {
|
||||
builder.setRegistryLockEmailAddress(registryLockEmailAddress);
|
||||
}
|
||||
}
|
||||
// Ditto the registry lock password
|
||||
if (registryLockPassword != null) {
|
||||
if (registryLockEmailAddress != null) {
|
||||
// Edge case, make sure we're not removing an email and setting a password at the same time
|
||||
checkArgument(
|
||||
!registryLockEmailAddress.isEmpty(),
|
||||
"Cannot set/remove registry lock password on a user without a registry lock email"
|
||||
+ " address");
|
||||
} else {
|
||||
checkArgument(
|
||||
user != null && user.getRegistryLockEmailAddress().isPresent(),
|
||||
"Cannot set/remove registry lock password on a user without a registry lock email"
|
||||
+ " address");
|
||||
}
|
||||
builder.removeRegistryLockPassword();
|
||||
if (!registryLockPassword.isEmpty()) {
|
||||
builder.setRegistryLockPassword(registryLockPassword);
|
||||
}
|
||||
}
|
||||
tm().put(builder.build());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,12 @@ 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.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.HEAD;
|
||||
import static google.registry.request.Action.Method.POST;
|
||||
import static google.registry.request.Action.Method.PUT;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
|
||||
@@ -33,6 +37,7 @@ import google.registry.batch.CloudTasksUtils;
|
||||
import google.registry.config.RegistryConfig;
|
||||
import google.registry.export.sheet.SyncRegistrarsSheetAction;
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
import google.registry.model.console.ConsoleUpdateHistory;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
@@ -75,13 +80,21 @@ 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 if (requestMethod.equals(PUT.toString())) {
|
||||
putHandler(user);
|
||||
} else {
|
||||
postHandler(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ConsolePermissionForbiddenException e) {
|
||||
@@ -109,10 +122,22 @@ public abstract class ConsoleApiAction implements Runnable {
|
||||
throw new UnsupportedOperationException("Console API POST handler not implemented");
|
||||
}
|
||||
|
||||
protected void putHandler(User user) {
|
||||
throw new UnsupportedOperationException("Console API PUT handler not implemented");
|
||||
}
|
||||
|
||||
protected void getHandler(User user) {
|
||||
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);
|
||||
@@ -237,6 +262,14 @@ public abstract class ConsoleApiAction implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
protected void finishAndPersistConsoleUpdateHistory(ConsoleUpdateHistory.Builder<?, ?> builder) {
|
||||
builder.setActingUser(consoleApiParams.authResult().user().get());
|
||||
builder.setUrl(consoleApiParams.request().getRequestURI());
|
||||
builder.setMethod(consoleApiParams.request().getMethod());
|
||||
builder.setModificationTime(tm().getTransactionTime());
|
||||
tm().put(builder.build());
|
||||
}
|
||||
|
||||
/** Specialized exception class used for failure when a user doesn't have the right permission. */
|
||||
private static class ConsolePermissionForbiddenException extends RuntimeException {
|
||||
private ConsolePermissionForbiddenException(String message) {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
package google.registry.ui.server.console;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.AuthResult;
|
||||
import google.registry.security.XsrfTokenManager;
|
||||
@@ -26,13 +27,16 @@ public record ConsoleApiParams(
|
||||
Response response,
|
||||
AuthResult authResult,
|
||||
SendEmailUtils sendEmailUtils,
|
||||
XsrfTokenManager xsrfTokenManager) {
|
||||
XsrfTokenManager xsrfTokenManager,
|
||||
Gson gson) {
|
||||
public static ConsoleApiParams create(
|
||||
HttpServletRequest request,
|
||||
Response response,
|
||||
AuthResult authResult,
|
||||
SendEmailUtils sendEmailUtils,
|
||||
XsrfTokenManager xsrfTokenManager) {
|
||||
return new ConsoleApiParams(request, response, authResult, sendEmailUtils, xsrfTokenManager);
|
||||
XsrfTokenManager xsrfTokenManager,
|
||||
Gson gson) {
|
||||
return new ConsoleApiParams(
|
||||
request, response, authResult, sendEmailUtils, xsrfTokenManager, gson);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
// 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.
|
||||
|
||||
package google.registry.ui.server.console;
|
||||
|
||||
import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.escape.Escaper;
|
||||
import com.google.common.xml.XmlEscapers;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.annotations.Expose;
|
||||
import google.registry.flows.EppController;
|
||||
import google.registry.flows.EppRequestSource;
|
||||
import google.registry.flows.PasswordOnlyTransportCredentials;
|
||||
import google.registry.flows.StatelessRequestSessionMetadata;
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.eppcommon.ProtocolDefinition;
|
||||
import google.registry.model.eppoutput.EppOutput;
|
||||
import google.registry.model.eppoutput.Result;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.OptionalJsonPayload;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.auth.Auth;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Console endpoint to perform the same action to a list of domains.
|
||||
*
|
||||
* <p>All requests must include the {@link BulkAction} to perform as well as a {@link
|
||||
* BulkDomainList} of domains on which to apply the action. The remaining contents of the request
|
||||
* body depend on the type of action -- some requests may require more data than others.
|
||||
*/
|
||||
@Action(
|
||||
service = Action.GaeService.DEFAULT,
|
||||
gkeService = Action.GkeService.CONSOLE,
|
||||
path = ConsoleBulkDomainAction.PATH,
|
||||
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
|
||||
public class ConsoleBulkDomainAction extends ConsoleApiAction {
|
||||
|
||||
public static final String PATH = "/console-api/bulk-domain";
|
||||
|
||||
private static Escaper XML_ESCAPER = XmlEscapers.xmlContentEscaper();
|
||||
|
||||
public enum BulkAction {
|
||||
DELETE,
|
||||
SUSPEND
|
||||
}
|
||||
|
||||
/** All requests must include at least a list of domain names on which to perform the action. */
|
||||
public record BulkDomainList(@Expose List<String> domainList) {}
|
||||
|
||||
public record BulkDomainDeleteRequest(@Expose String reason) {}
|
||||
|
||||
public record BulkDomainSuspendRequest(@Expose String reason) {}
|
||||
|
||||
private static final String DOMAIN_DELETE_XML =
|
||||
"""
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
|
||||
<command>
|
||||
<delete>
|
||||
<domain:delete
|
||||
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
|
||||
<domain:name>%DOMAIN_NAME%</domain:name>
|
||||
</domain:delete>
|
||||
</delete>
|
||||
<extension>
|
||||
<metadata:metadata xmlns:metadata="urn:google:params:xml:ns:metadata-1.0">
|
||||
<metadata:reason>%REASON%</metadata:reason>
|
||||
<metadata:requestedByRegistrar>true</metadata:requestedByRegistrar>
|
||||
</metadata:metadata>
|
||||
</extension>
|
||||
<clTRID>RegistryConsole</clTRID>
|
||||
</command>
|
||||
</epp>""";
|
||||
|
||||
private static final String DOMAIN_SUSPEND_XML =
|
||||
"""
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<epp
|
||||
xmlns="urn:ietf:params:xml:ns:epp-1.0">
|
||||
<command>
|
||||
<update>
|
||||
<domain:update
|
||||
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
|
||||
<domain:name>%DOMAIN_NAME%</domain:name>
|
||||
<domain:add>
|
||||
<domain:status s="serverDeleteProhibited" lang="en"></domain:status>
|
||||
<domain:status s="serverHold" lang="en"></domain:status>
|
||||
<domain:status s="serverRenewProhibited" lang="en"></domain:status>
|
||||
<domain:status s="serverTransferProhibited" lang="en"></domain:status>
|
||||
<domain:status s="serverUpdateProhibited" lang="en"></domain:status>
|
||||
</domain:add>
|
||||
<domain:rem></domain:rem>
|
||||
</domain:update>
|
||||
</update>
|
||||
<extension>
|
||||
<metadata:metadata
|
||||
xmlns:metadata="urn:google:params:xml:ns:metadata-1.0">
|
||||
<metadata:reason>Console suspension: %REASON%</metadata:reason>
|
||||
<metadata:requestedByRegistrar>false</metadata:requestedByRegistrar>
|
||||
</metadata:metadata>
|
||||
</extension>
|
||||
<clTRID>RegistryTool</clTRID>
|
||||
</command>
|
||||
</epp>""";
|
||||
|
||||
private final EppController eppController;
|
||||
private final String registrarId;
|
||||
private final String bulkDomainAction;
|
||||
private final Optional<JsonElement> optionalJsonPayload;
|
||||
|
||||
@Inject
|
||||
public ConsoleBulkDomainAction(
|
||||
ConsoleApiParams consoleApiParams,
|
||||
EppController eppController,
|
||||
@Parameter("registrarId") String registrarId,
|
||||
@Parameter("bulkDomainAction") String bulkDomainAction,
|
||||
@OptionalJsonPayload Optional<JsonElement> optionalJsonPayload) {
|
||||
super(consoleApiParams);
|
||||
this.eppController = eppController;
|
||||
this.registrarId = registrarId;
|
||||
this.bulkDomainAction = bulkDomainAction;
|
||||
this.optionalJsonPayload = optionalJsonPayload;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void postHandler(User user) {
|
||||
BulkAction bulkAction = BulkAction.valueOf(bulkDomainAction);
|
||||
JsonElement jsonPayload =
|
||||
optionalJsonPayload.orElseThrow(
|
||||
() -> new IllegalArgumentException("Bulk action payload must be present"));
|
||||
BulkDomainList domainList = consoleApiParams.gson().fromJson(jsonPayload, BulkDomainList.class);
|
||||
checkPermission(user, registrarId, ConsolePermission.EXECUTE_EPP_COMMANDS);
|
||||
ImmutableMap<String, ConsoleEppOutput> result =
|
||||
switch (bulkAction) {
|
||||
case DELETE -> handleBulkDelete(jsonPayload, domainList, user);
|
||||
case SUSPEND -> handleBulkSuspend(jsonPayload, domainList, user);
|
||||
};
|
||||
// Front end should parse situations where only some commands worked
|
||||
consoleApiParams.response().setPayload(consoleApiParams.gson().toJson(result));
|
||||
consoleApiParams.response().setStatus(SC_OK);
|
||||
}
|
||||
|
||||
private ImmutableMap<String, ConsoleEppOutput> handleBulkDelete(
|
||||
JsonElement jsonPayload, BulkDomainList domainList, User user) {
|
||||
String reason =
|
||||
consoleApiParams.gson().fromJson(jsonPayload, BulkDomainDeleteRequest.class).reason;
|
||||
return runCommandOverDomains(
|
||||
domainList,
|
||||
DOMAIN_DELETE_XML,
|
||||
new ImmutableMap.Builder<String, String>().put("REASON", reason),
|
||||
user);
|
||||
}
|
||||
|
||||
private ImmutableMap<String, ConsoleEppOutput> handleBulkSuspend(
|
||||
JsonElement jsonPayload, BulkDomainList domainList, User user) {
|
||||
String reason =
|
||||
consoleApiParams.gson().fromJson(jsonPayload, BulkDomainSuspendRequest.class).reason;
|
||||
return runCommandOverDomains(
|
||||
domainList,
|
||||
DOMAIN_SUSPEND_XML,
|
||||
new ImmutableMap.Builder<String, String>().put("REASON", reason),
|
||||
user);
|
||||
}
|
||||
|
||||
/** Runs the provided XML template and substitutions over a provided list of domains. */
|
||||
private ImmutableMap<String, ConsoleEppOutput> runCommandOverDomains(
|
||||
BulkDomainList domainList,
|
||||
String xmlTemplate,
|
||||
ImmutableMap.Builder<String, String> replacements,
|
||||
User user) {
|
||||
return domainList.domainList.stream()
|
||||
.collect(
|
||||
toImmutableMap(
|
||||
d -> d,
|
||||
d ->
|
||||
executeEpp(
|
||||
fillSubstitutions(xmlTemplate, replacements.put("DOMAIN_NAME", d)), user)));
|
||||
}
|
||||
|
||||
private ConsoleEppOutput executeEpp(String xml, User user) {
|
||||
return ConsoleEppOutput.fromEppOutput(
|
||||
eppController.handleEppCommand(
|
||||
new StatelessRequestSessionMetadata(
|
||||
registrarId, ProtocolDefinition.getVisibleServiceExtensionUris()),
|
||||
new PasswordOnlyTransportCredentials(),
|
||||
EppRequestSource.CONSOLE,
|
||||
false,
|
||||
user.getUserRoles().isAdmin(),
|
||||
xml.getBytes(UTF_8)));
|
||||
}
|
||||
|
||||
/** Fills the provided XML template with the replacement values, including escaping the values. */
|
||||
private String fillSubstitutions(
|
||||
String xmlTemplate, ImmutableMap.Builder<String, String> replacements) {
|
||||
String xml = xmlTemplate;
|
||||
for (Map.Entry<String, String> entry : replacements.buildKeepingLast().entrySet()) {
|
||||
xml = xml.replaceAll("%" + entry.getKey() + "%", XML_ESCAPER.escape(entry.getValue()));
|
||||
}
|
||||
return xml;
|
||||
}
|
||||
|
||||
public record ConsoleEppOutput(@Expose String message, @Expose int responseCode) {
|
||||
static ConsoleEppOutput fromEppOutput(EppOutput eppOutput) {
|
||||
Result result = eppOutput.getResponse().getResult();
|
||||
return new ConsoleEppOutput(result.getMsg(), result.getCode().code);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_NOT_FOUND;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import google.registry.model.EppResourceUtils;
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
import google.registry.model.console.User;
|
||||
@@ -41,17 +40,14 @@ public class ConsoleDomainGetAction extends ConsoleApiAction {
|
||||
|
||||
public static final String PATH = "/console-api/domain";
|
||||
|
||||
private final Gson gson;
|
||||
private final String paramDomain;
|
||||
|
||||
@Inject
|
||||
public ConsoleDomainGetAction(
|
||||
ConsoleApiParams consoleApiParams,
|
||||
Gson gson,
|
||||
@Parameter("consoleDomain") String paramDomain) {
|
||||
super(consoleApiParams);
|
||||
this.paramDomain = paramDomain;
|
||||
this.gson = gson;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -72,6 +68,6 @@ public class ConsoleDomainGetAction extends ConsoleApiAction {
|
||||
return;
|
||||
}
|
||||
consoleApiParams.response().setStatus(SC_OK);
|
||||
consoleApiParams.response().setPayload(gson.toJson(domain));
|
||||
consoleApiParams.response().setPayload(consoleApiParams.gson().toJson(domain));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.annotations.Expose;
|
||||
import google.registry.model.CreateAutoTimestamp;
|
||||
import google.registry.model.console.User;
|
||||
@@ -55,7 +54,6 @@ public class ConsoleDomainListAction extends ConsoleApiAction {
|
||||
private static final String SEARCH_TERM_QUERY = " AND LOWER(domainName) LIKE :searchTerm";
|
||||
private static final String ORDER_BY_STATEMENT = " ORDER BY creationTime DESC";
|
||||
|
||||
private final Gson gson;
|
||||
private final String registrarId;
|
||||
private final Optional<DateTime> checkpointTime;
|
||||
private final int pageNumber;
|
||||
@@ -66,7 +64,6 @@ public class ConsoleDomainListAction extends ConsoleApiAction {
|
||||
@Inject
|
||||
public ConsoleDomainListAction(
|
||||
ConsoleApiParams consoleApiParams,
|
||||
Gson gson,
|
||||
@Parameter("registrarId") String registrarId,
|
||||
@Parameter("checkpointTime") Optional<DateTime> checkpointTime,
|
||||
@Parameter("pageNumber") Optional<Integer> pageNumber,
|
||||
@@ -74,7 +71,6 @@ public class ConsoleDomainListAction extends ConsoleApiAction {
|
||||
@Parameter("totalResults") Optional<Long> totalResults,
|
||||
@Parameter("searchTerm") Optional<String> searchTerm) {
|
||||
super(consoleApiParams);
|
||||
this.gson = gson;
|
||||
this.registrarId = registrarId;
|
||||
this.checkpointTime = checkpointTime;
|
||||
this.pageNumber = pageNumber.orElse(0);
|
||||
@@ -120,7 +116,10 @@ public class ConsoleDomainListAction extends ConsoleApiAction {
|
||||
|
||||
consoleApiParams
|
||||
.response()
|
||||
.setPayload(gson.toJson(new DomainListResult(domains, checkpoint, actualTotalResults)));
|
||||
.setPayload(
|
||||
consoleApiParams
|
||||
.gson()
|
||||
.toJson(new DomainListResult(domains, checkpoint, actualTotalResults)));
|
||||
consoleApiParams.response().setStatus(SC_OK);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ import com.google.common.collect.ImmutableSet;
|
||||
import com.google.gson.annotations.Expose;
|
||||
import google.registry.flows.EppException.AuthenticationErrorException;
|
||||
import google.registry.flows.PasswordOnlyTransportCredentials;
|
||||
import google.registry.model.console.ConsoleUpdateHistory;
|
||||
import google.registry.model.console.RegistrarUpdateHistory;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.request.Action;
|
||||
@@ -53,7 +55,6 @@ public class ConsoleEppPasswordAction extends ConsoleApiAction {
|
||||
private final PasswordOnlyTransportCredentials credentials =
|
||||
new PasswordOnlyTransportCredentials();
|
||||
private final AuthenticatedRegistrarAccessor registrarAccessor;
|
||||
|
||||
private final Optional<EppPasswordData> eppPasswordChangeRequest;
|
||||
|
||||
@Inject
|
||||
@@ -106,6 +107,14 @@ public class ConsoleEppPasswordAction extends ConsoleApiAction {
|
||||
Registrar updatedRegistrar =
|
||||
registrar.asBuilder().setPassword(eppRequestBody.newPassword()).build();
|
||||
tm().put(updatedRegistrar);
|
||||
EppPasswordData sanitizedData =
|
||||
new EppPasswordData(
|
||||
eppRequestBody.registrarId, "********", "••••••••", "••••••••");
|
||||
finishAndPersistConsoleUpdateHistory(
|
||||
new RegistrarUpdateHistory.Builder()
|
||||
.setType(ConsoleUpdateHistory.Type.REGISTRAR_UPDATE)
|
||||
.setRegistrar(updatedRegistrar)
|
||||
.setRequestBody(consoleApiParams.gson().toJson(sanitizedData)));
|
||||
sendExternalUpdates(
|
||||
ImmutableMap.of("password", new DiffUtils.DiffPair("********", "••••••••")),
|
||||
registrar,
|
||||
|
||||
@@ -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.UserData;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
@@ -52,8 +53,10 @@ public final class ConsoleModule {
|
||||
Response response,
|
||||
AuthResult authResult,
|
||||
SendEmailUtils sendEmailUtils,
|
||||
XsrfTokenManager xsrfTokenManager) {
|
||||
return ConsoleApiParams.create(request, response, authResult, sendEmailUtils, xsrfTokenManager);
|
||||
XsrfTokenManager xsrfTokenManager,
|
||||
Gson gson) {
|
||||
return ConsoleApiParams.create(
|
||||
request, response, authResult, sendEmailUtils, xsrfTokenManager, gson);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -238,6 +241,12 @@ public final class ConsoleModule {
|
||||
return extractOptionalParameter(req, "searchTerm");
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter("bulkDomainAction")
|
||||
public static String provideBulkDomainAction(HttpServletRequest req) {
|
||||
return extractRequiredParameter(req, "bulkDomainAction");
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter("eppPasswordChangeRequest")
|
||||
public static Optional<EppPasswordData> provideEppPasswordChangeRequest(
|
||||
@@ -245,6 +254,13 @@ public final class ConsoleModule {
|
||||
return payload.map(s -> gson.fromJson(s, EppPasswordData.class));
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter("userData")
|
||||
public static Optional<UserData> provideUserData(
|
||||
Gson gson, @OptionalJsonPayload Optional<JsonElement> payload) {
|
||||
return payload.map(s -> gson.fromJson(s, UserData.class));
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter("oteCreateData")
|
||||
public static Optional<OteCreateData> provideOteCreateData(
|
||||
|
||||
@@ -27,7 +27,6 @@ import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
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.OteAccountBuilder;
|
||||
@@ -62,7 +61,6 @@ public class ConsoleOteAction extends ConsoleApiAction {
|
||||
private static final String STAT_TYPE_DESCRIPTION_PARAM = "description";
|
||||
private static final String STAT_TYPE_REQUIREMENT_PARAM = "requirement";
|
||||
private static final String STAT_TYPE_TIMES_PERFORMED_PARAM = "timesPerformed";
|
||||
private final Gson gson;
|
||||
private final StringGenerator passwordGenerator;
|
||||
private final Optional<OteCreateData> oteCreateData;
|
||||
private final Optional<String> maybeGroupEmailAddress;
|
||||
@@ -72,14 +70,12 @@ public class ConsoleOteAction extends ConsoleApiAction {
|
||||
@Inject
|
||||
public ConsoleOteAction(
|
||||
ConsoleApiParams consoleApiParams,
|
||||
Gson gson,
|
||||
IamClient iamClient,
|
||||
@Parameter("registrarId") String registrarId, // Get request param
|
||||
@Config("gSuiteConsoleUserGroupEmailAddress") Optional<String> maybeGroupEmailAddress,
|
||||
@Named("base58StringGenerator") StringGenerator passwordGenerator,
|
||||
@Parameter("oteCreateData") Optional<OteCreateData> oteCreateData) {
|
||||
super(consoleApiParams);
|
||||
this.gson = gson;
|
||||
this.passwordGenerator = passwordGenerator;
|
||||
this.oteCreateData = oteCreateData;
|
||||
this.maybeGroupEmailAddress = maybeGroupEmailAddress;
|
||||
@@ -116,8 +112,13 @@ public class ConsoleOteAction extends ConsoleApiAction {
|
||||
consoleApiParams
|
||||
.response()
|
||||
.setPayload(
|
||||
gson.toJson(
|
||||
ImmutableMap.builder().putAll(registrarIdToTld).put("password", password).build()));
|
||||
consoleApiParams
|
||||
.gson()
|
||||
.toJson(
|
||||
ImmutableMap.builder()
|
||||
.putAll(registrarIdToTld)
|
||||
.put("password", password)
|
||||
.build()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -153,7 +154,7 @@ public class ConsoleOteAction extends ConsoleApiAction {
|
||||
convertSingleRequirement(statType, oteStats.getCount(statType)))
|
||||
.collect(toImmutableList());
|
||||
consoleApiParams.response().setStatus(SC_OK);
|
||||
consoleApiParams.response().setPayload(gson.toJson(stats));
|
||||
consoleApiParams.response().setPayload(consoleApiParams.gson().toJson(stats));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.annotations.Expose;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.domain.DomainFlowUtils;
|
||||
@@ -72,7 +71,6 @@ public class ConsoleRegistryLockAction extends ConsoleApiAction {
|
||||
|
||||
private final DomainLockUtils domainLockUtils;
|
||||
private final GmailClient gmailClient;
|
||||
private final Gson gson;
|
||||
private final Optional<ConsoleRegistryLockPostInput> optionalPostInput;
|
||||
private final String registrarId;
|
||||
|
||||
@@ -81,14 +79,12 @@ public class ConsoleRegistryLockAction extends ConsoleApiAction {
|
||||
ConsoleApiParams consoleApiParams,
|
||||
DomainLockUtils domainLockUtils,
|
||||
GmailClient gmailClient,
|
||||
Gson gson,
|
||||
@Parameter("consoleRegistryLockPostInput")
|
||||
Optional<ConsoleRegistryLockPostInput> optionalPostInput,
|
||||
@Parameter("registrarId") String registrarId) {
|
||||
super(consoleApiParams);
|
||||
this.domainLockUtils = domainLockUtils;
|
||||
this.gmailClient = gmailClient;
|
||||
this.gson = gson;
|
||||
this.optionalPostInput = optionalPostInput;
|
||||
this.registrarId = registrarId;
|
||||
}
|
||||
@@ -96,7 +92,7 @@ public class ConsoleRegistryLockAction extends ConsoleApiAction {
|
||||
@Override
|
||||
protected void getHandler(User user) {
|
||||
checkPermission(user, registrarId, ConsolePermission.REGISTRY_LOCK);
|
||||
consoleApiParams.response().setPayload(gson.toJson(getLockedDomains()));
|
||||
consoleApiParams.response().setPayload(consoleApiParams.gson().toJson(getLockedDomains()));
|
||||
consoleApiParams.response().setStatus(SC_OK);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ package google.registry.ui.server.console;
|
||||
import static google.registry.request.Action.Method.GET;
|
||||
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.annotations.Expose;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.domain.RegistryLock;
|
||||
@@ -42,18 +41,15 @@ public class ConsoleRegistryLockVerifyAction extends ConsoleApiAction {
|
||||
static final String PATH = "/console-api/registry-lock-verify";
|
||||
|
||||
private final DomainLockUtils domainLockUtils;
|
||||
private final Gson gson;
|
||||
private final String lockVerificationCode;
|
||||
|
||||
@Inject
|
||||
public ConsoleRegistryLockVerifyAction(
|
||||
ConsoleApiParams consoleApiParams,
|
||||
DomainLockUtils domainLockUtils,
|
||||
Gson gson,
|
||||
@Parameter("lockVerificationCode") String lockVerificationCode) {
|
||||
super(consoleApiParams);
|
||||
this.domainLockUtils = domainLockUtils;
|
||||
this.gson = gson;
|
||||
this.lockVerificationCode = lockVerificationCode;
|
||||
}
|
||||
|
||||
@@ -68,7 +64,7 @@ public class ConsoleRegistryLockVerifyAction extends ConsoleApiAction {
|
||||
RegistryLockVerificationResponse lockResponse =
|
||||
new RegistryLockVerificationResponse(
|
||||
Ascii.toLowerCase(action.toString()), lock.getDomainName(), lock.getRegistrarId());
|
||||
consoleApiParams.response().setPayload(gson.toJson(lockResponse));
|
||||
consoleApiParams.response().setPayload(consoleApiParams.gson().toJson(lockResponse));
|
||||
consoleApiParams.response().setStatus(HttpServletResponse.SC_OK);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ import static org.apache.http.HttpStatus.SC_OK;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
import google.registry.model.console.ConsoleUpdateHistory;
|
||||
import google.registry.model.console.RegistrarUpdateHistory;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.request.Action;
|
||||
@@ -99,6 +101,11 @@ public class ConsoleUpdateRegistrarAction extends ConsoleApiAction {
|
||||
.build();
|
||||
|
||||
tm().put(updatedRegistrar);
|
||||
finishAndPersistConsoleUpdateHistory(
|
||||
new RegistrarUpdateHistory.Builder()
|
||||
.setType(ConsoleUpdateHistory.Type.REGISTRAR_UPDATE)
|
||||
.setRegistrar(updatedRegistrar)
|
||||
.setRequestBody(consoleApiParams.gson().toJson(registrarParam)));
|
||||
sendExternalUpdatesIfNecessary(
|
||||
EmailInfo.create(
|
||||
existingRegistrar.get(),
|
||||
|
||||
@@ -82,6 +82,8 @@ public class ConsoleUserDataAction extends ConsoleApiAction {
|
||||
// auth checks.
|
||||
"isAdmin", user.getUserRoles().isAdmin(),
|
||||
"globalRole", user.getUserRoles().getGlobalRole(),
|
||||
// registrar-specific roles
|
||||
"userRoles", user.getUserRoles().getRegistrarRoles(),
|
||||
// Include static contact resources in this call to minimize round trips
|
||||
"productName", productName,
|
||||
"supportEmail", supportEmail,
|
||||
|
||||
@@ -14,11 +14,15 @@
|
||||
|
||||
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 google.registry.request.Action.Method.PUT;
|
||||
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;
|
||||
@@ -28,8 +32,10 @@ import com.google.api.services.directory.model.UserName;
|
||||
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.RegistrarRole;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.console.UserRoles;
|
||||
import google.registry.persistence.VKey;
|
||||
@@ -38,11 +44,16 @@ 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.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.IntStream;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
@@ -50,36 +61,40 @@ import javax.inject.Named;
|
||||
service = Action.GaeService.DEFAULT,
|
||||
gkeService = GkeService.CONSOLE,
|
||||
path = ConsoleUsersAction.PATH,
|
||||
method = {GET, POST},
|
||||
method = {GET, POST, DELETE, PUT},
|
||||
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 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<UserData> userData;
|
||||
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("userData") Optional<UserData> userData,
|
||||
@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.userData = userData;
|
||||
this.maybeGroupEmailAddress = maybeGroupEmailAddress;
|
||||
this.iamClient = iamClient;
|
||||
this.gSuiteDomainName = gSuiteDomainName;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -87,7 +102,23 @@ public class ConsoleUsersAction extends ConsoleApiAction {
|
||||
// Temporary flag while testing
|
||||
if (user.getUserRoles().isAdmin()) {
|
||||
checkPermission(user, registrarId, ConsolePermission.MANAGE_USERS);
|
||||
tm().transact(() -> runInTransaction(user));
|
||||
if (userData.isPresent()) { // Adding existing user to registrar
|
||||
tm().transact(this::runAppendUserInTransaction);
|
||||
} else { // Adding new user to registrar
|
||||
tm().transact(this::runCreateInTransaction);
|
||||
}
|
||||
|
||||
} else {
|
||||
consoleApiParams.response().setStatus(SC_FORBIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void putHandler(User user) {
|
||||
// Temporary flag while testing
|
||||
if (user.getUserRoles().isAdmin()) {
|
||||
checkPermission(user, registrarId, ConsolePermission.MANAGE_USERS);
|
||||
tm().transact(this::runUpdateInTransaction);
|
||||
} else {
|
||||
consoleApiParams.response().setStatus(SC_FORBIDDEN);
|
||||
}
|
||||
@@ -96,40 +127,98 @@ public class ConsoleUsersAction extends ConsoleApiAction {
|
||||
@Override
|
||||
protected void getHandler(User user) {
|
||||
checkPermission(user, registrarId, ConsolePermission.MANAGE_USERS);
|
||||
List<ImmutableMap> users =
|
||||
getAllUsers().stream()
|
||||
.filter(u -> u.getUserRoles().getRegistrarRoles().containsKey(registrarId))
|
||||
List<UserData> users =
|
||||
getAllRegistrarUsers(registrarId).stream()
|
||||
.map(
|
||||
u ->
|
||||
ImmutableMap.of(
|
||||
"emailAddress",
|
||||
new UserData(
|
||||
u.getEmailAddress(),
|
||||
"role",
|
||||
u.getUserRoles().getRegistrarRoles().get(registrarId)))
|
||||
u.getUserRoles().getRegistrarRoles().get(registrarId).toString(),
|
||||
null))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
consoleApiParams.response().setPayload(gson.toJson(users));
|
||||
consoleApiParams.response().setPayload(consoleApiParams.gson().toJson(users));
|
||||
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(this::runDeleteInTransaction);
|
||||
} else {
|
||||
consoleApiParams.response().setStatus(SC_FORBIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
private void runAppendUserInTransaction() {
|
||||
if (!isModifyingRequestValid(false)) {
|
||||
return;
|
||||
}
|
||||
ImmutableList<User> allRegistrarUsers = getAllRegistrarUsers(registrarId);
|
||||
if (allRegistrarUsers.size() >= 4)
|
||||
throw new BadRequestException("Total users amount per registrar is limited to 4");
|
||||
|
||||
updateUserRegistrarRoles(
|
||||
this.userData.get().emailAddress,
|
||||
registrarId,
|
||||
RegistrarRole.valueOf(this.userData.get().role),
|
||||
false);
|
||||
consoleApiParams.response().setStatus(SC_OK);
|
||||
}
|
||||
|
||||
private void runDeleteInTransaction() throws IOException {
|
||||
if (!isModifyingRequestValid(true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String email = this.userData.get().emailAddress;
|
||||
User updatedUser =
|
||||
updateUserRegistrarRoles(
|
||||
email, registrarId, RegistrarRole.valueOf(this.userData.get().role), true);
|
||||
|
||||
// User has no registrars assigned
|
||||
if (updatedUser.getUserRoles().getRegistrarRoles().size() == 0) {
|
||||
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,26 +235,94 @@ 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(
|
||||
gson.toJson(
|
||||
ImmutableMap.of(
|
||||
"password",
|
||||
newUser.getPassword(),
|
||||
"emailAddress",
|
||||
newUser.getPrimaryEmail(),
|
||||
"role",
|
||||
ACCOUNT_MANAGER)));
|
||||
consoleApiParams
|
||||
.gson()
|
||||
.toJson(
|
||||
new UserData(
|
||||
newUser.getPrimaryEmail(),
|
||||
ACCOUNT_MANAGER.toString(),
|
||||
newUser.getPassword())));
|
||||
}
|
||||
|
||||
private ImmutableList<User> getAllUsers() {
|
||||
private void runUpdateInTransaction() {
|
||||
if (!isModifyingRequestValid(true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateUserRegistrarRoles(
|
||||
this.userData.get().emailAddress,
|
||||
registrarId,
|
||||
RegistrarRole.valueOf(this.userData.get().role),
|
||||
false);
|
||||
consoleApiParams.response().setStatus(SC_OK);
|
||||
}
|
||||
|
||||
private boolean isModifyingRequestValid(boolean verifyAccess) {
|
||||
if (userData.isEmpty()
|
||||
|| isNullOrEmpty(userData.get().emailAddress)
|
||||
|| isNullOrEmpty(userData.get().role)) {
|
||||
throw new BadRequestException("User data is missing or incomplete");
|
||||
}
|
||||
String email = userData.get().emailAddress;
|
||||
User userToUpdate =
|
||||
tm().loadByKeyIfPresent(VKey.create(User.class, email))
|
||||
.orElseThrow(
|
||||
() -> new BadRequestException(String.format("User %s doesn't exist", email)));
|
||||
|
||||
if (verifyAccess && !userToUpdate.getUserRoles().getRegistrarRoles().containsKey(registrarId)) {
|
||||
setFailedResponse(
|
||||
String.format("Can't update user not associated with registrarId %s", registrarId),
|
||||
SC_FORBIDDEN);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private User updateUserRegistrarRoles(
|
||||
String email, String registrarId, RegistrarRole newRole, boolean isDelete) {
|
||||
User userToUpdate = tm().loadByKeyIfPresent(VKey.create(User.class, email)).get();
|
||||
Map<String, RegistrarRole> updatedRegistrarRoles;
|
||||
if (isDelete) {
|
||||
updatedRegistrarRoles =
|
||||
userToUpdate.getUserRoles().getRegistrarRoles().entrySet().stream()
|
||||
.filter(entry -> !Objects.equals(entry.getKey(), registrarId))
|
||||
.collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
} else {
|
||||
updatedRegistrarRoles =
|
||||
ImmutableMap.<String, RegistrarRole>builder()
|
||||
.putAll(userToUpdate.getUserRoles().getRegistrarRoles())
|
||||
.put(registrarId, newRole)
|
||||
.buildKeepingLast();
|
||||
}
|
||||
var updatedUser =
|
||||
userToUpdate
|
||||
.asBuilder()
|
||||
.setUserRoles(
|
||||
userToUpdate
|
||||
.getUserRoles()
|
||||
.asBuilder()
|
||||
.setRegistrarRoles(updatedRegistrarRoles)
|
||||
.build())
|
||||
.build();
|
||||
tm().put(updatedUser);
|
||||
return updatedUser;
|
||||
}
|
||||
|
||||
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 UserData(
|
||||
@Expose String emailAddress, @Expose String role, @Expose @Nullable String password) {}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,9 @@ import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Streams;
|
||||
import com.google.gson.Gson;
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
import google.registry.model.console.ConsoleUpdateHistory;
|
||||
import google.registry.model.console.RegistrarUpdateHistory;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarBase;
|
||||
@@ -62,7 +63,6 @@ public class RegistrarsAction extends ConsoleApiAction {
|
||||
WHERE registrar_id in :registrarIds
|
||||
""";
|
||||
static final String PATH = "/console-api/registrars";
|
||||
private final Gson gson;
|
||||
private final Optional<Registrar> registrar;
|
||||
private final StringGenerator passwordGenerator;
|
||||
private final StringGenerator passcodeGenerator;
|
||||
@@ -70,12 +70,10 @@ public class RegistrarsAction extends ConsoleApiAction {
|
||||
@Inject
|
||||
public RegistrarsAction(
|
||||
ConsoleApiParams consoleApiParams,
|
||||
Gson gson,
|
||||
@Parameter("registrar") Optional<Registrar> registrar,
|
||||
@Named("base58StringGenerator") StringGenerator passwordGenerator,
|
||||
@Named("digitOnlyStringGenerator") StringGenerator passcodeGenerator) {
|
||||
super(consoleApiParams);
|
||||
this.gson = gson;
|
||||
this.registrar = registrar;
|
||||
this.passcodeGenerator = passcodeGenerator;
|
||||
this.passwordGenerator = passwordGenerator;
|
||||
@@ -88,7 +86,7 @@ public class RegistrarsAction extends ConsoleApiAction {
|
||||
Streams.stream(Registrar.loadAll())
|
||||
.filter(r -> allowedRegistrarTypes.contains(r.getType()))
|
||||
.collect(ImmutableList.toImmutableList());
|
||||
consoleApiParams.response().setPayload(gson.toJson(registrars));
|
||||
consoleApiParams.response().setPayload(consoleApiParams.gson().toJson(registrars));
|
||||
consoleApiParams.response().setStatus(SC_OK);
|
||||
} else if (user.getUserRoles().getRegistrarRoles().values().stream()
|
||||
.anyMatch(role -> role.hasPermission(ConsolePermission.VIEW_REGISTRAR_DETAILS))) {
|
||||
@@ -106,7 +104,7 @@ public class RegistrarsAction extends ConsoleApiAction {
|
||||
.setParameter("registrarIds", accessibleRegistrarIds)
|
||||
.getResultList());
|
||||
|
||||
consoleApiParams.response().setPayload(gson.toJson(registrars));
|
||||
consoleApiParams.response().setPayload(consoleApiParams.gson().toJson(registrars));
|
||||
consoleApiParams.response().setStatus(SC_OK);
|
||||
} else {
|
||||
consoleApiParams.response().setStatus(SC_FORBIDDEN);
|
||||
@@ -175,6 +173,11 @@ public class RegistrarsAction extends ConsoleApiAction {
|
||||
"Registrar with registrarId %s already exists",
|
||||
registrar.getRegistrarId());
|
||||
tm().putAll(registrar, contact);
|
||||
finishAndPersistConsoleUpdateHistory(
|
||||
new RegistrarUpdateHistory.Builder()
|
||||
.setType(ConsoleUpdateHistory.Type.REGISTRAR_UPDATE)
|
||||
.setRegistrar(registrar)
|
||||
.setRequestBody(consoleApiParams.gson().toJson(registrar)));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.gson.Gson;
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
@@ -60,18 +59,15 @@ import javax.inject.Inject;
|
||||
public class ContactAction extends ConsoleApiAction {
|
||||
static final String PATH = "/console-api/settings/contacts";
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
private final Gson gson;
|
||||
private final Optional<ImmutableSet<RegistrarPoc>> contacts;
|
||||
private final String registrarId;
|
||||
|
||||
@Inject
|
||||
public ContactAction(
|
||||
ConsoleApiParams consoleApiParams,
|
||||
Gson gson,
|
||||
@Parameter("registrarId") String registrarId,
|
||||
@Parameter("contacts") Optional<ImmutableSet<RegistrarPoc>> contacts) {
|
||||
super(consoleApiParams);
|
||||
this.gson = gson;
|
||||
this.registrarId = registrarId;
|
||||
this.contacts = contacts;
|
||||
}
|
||||
@@ -90,7 +86,7 @@ public class ContactAction extends ConsoleApiAction {
|
||||
.collect(toImmutableList()));
|
||||
|
||||
consoleApiParams.response().setStatus(SC_OK);
|
||||
consoleApiParams.response().setPayload(gson.toJson(am));
|
||||
consoleApiParams.response().setPayload(consoleApiParams.gson().toJson(am));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -25,6 +25,8 @@ import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.flows.certs.CertificateChecker;
|
||||
import google.registry.flows.certs.CertificateChecker.InsecureCertificateException;
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
import google.registry.model.console.ConsoleUpdateHistory;
|
||||
import google.registry.model.console.RegistrarUpdateHistory;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.request.Action;
|
||||
@@ -117,6 +119,11 @@ public class SecurityAction extends ConsoleApiAction {
|
||||
|
||||
Registrar updatedRegistrar = updatedRegistrarBuilder.build();
|
||||
tm().put(updatedRegistrar);
|
||||
finishAndPersistConsoleUpdateHistory(
|
||||
new RegistrarUpdateHistory.Builder()
|
||||
.setType(ConsoleUpdateHistory.Type.REGISTRAR_UPDATE)
|
||||
.setRegistrar(updatedRegistrar)
|
||||
.setRequestBody(consoleApiParams.gson().toJson(registrar.get())));
|
||||
|
||||
sendExternalUpdatesIfNecessary(
|
||||
EmailInfo.create(savedRegistrar, updatedRegistrar, ImmutableSet.of(), ImmutableSet.of()));
|
||||
|
||||
@@ -22,6 +22,8 @@ import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
import google.registry.model.console.ConsoleUpdateHistory;
|
||||
import google.registry.model.console.RegistrarUpdateHistory;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.request.Action;
|
||||
@@ -104,6 +106,11 @@ public class WhoisRegistrarFieldsAction extends ConsoleApiAction {
|
||||
.setEmailAddress(providedRegistrar.getEmailAddress())
|
||||
.build();
|
||||
tm().put(newRegistrar);
|
||||
finishAndPersistConsoleUpdateHistory(
|
||||
new RegistrarUpdateHistory.Builder()
|
||||
.setType(ConsoleUpdateHistory.Type.REGISTRAR_UPDATE)
|
||||
.setRegistrar(newRegistrar)
|
||||
.setRequestBody(consoleApiParams.gson().toJson(registrar.get())));
|
||||
sendExternalUpdatesIfNecessary(
|
||||
EmailInfo.create(
|
||||
savedRegistrar,
|
||||
|
||||
@@ -121,7 +121,7 @@ class ExtensionManagerTest {
|
||||
void testMetadataExtension_forbiddenWhenNotToolSource() {
|
||||
ExtensionManager manager =
|
||||
new TestInstanceBuilder()
|
||||
.setEppRequestSource(EppRequestSource.CONSOLE)
|
||||
.setEppRequestSource(EppRequestSource.TLS)
|
||||
.setDeclaredUris()
|
||||
.setSuppliedExtensions(MetadataExtension.class)
|
||||
.build();
|
||||
|
||||
@@ -1247,4 +1247,15 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
|
||||
clock.advanceOneMilli();
|
||||
runFlowAssertResponse(loadFile("domain_delete_response_fee_free_grace.xml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_skipsPollMessage_whenConfigured() throws Exception {
|
||||
setUpSuccessfulTest();
|
||||
domain =
|
||||
persistResource(
|
||||
domain.asBuilder().setPersistedCurrentSponsorRegistrarId("NewRegistrar").build());
|
||||
setRegistrarIdForFlow("NewRegistrar");
|
||||
runFlowAssertResponse(loadFile("domain_delete_response_pending.xml"));
|
||||
assertPollMessages();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ import google.registry.model.reporting.DomainTransactionRecord;
|
||||
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -103,12 +104,12 @@ class DomainRestoreRequestFlowTest extends ResourceFlowTestCase<DomainRestoreReq
|
||||
setEppInput("domain_update_restore_request.xml", ImmutableMap.of("DOMAIN", "example.tld"));
|
||||
}
|
||||
|
||||
void persistPendingDeleteDomain() throws Exception {
|
||||
Domain persistPendingDeleteDomain() throws Exception {
|
||||
// The domain is now past what had been its expiration date at the time of deletion.
|
||||
persistPendingDeleteDomain(clock.nowUtc().minusDays(5));
|
||||
return persistPendingDeleteDomain(clock.nowUtc().minusDays(5));
|
||||
}
|
||||
|
||||
void persistPendingDeleteDomain(DateTime expirationTime) throws Exception {
|
||||
Domain persistPendingDeleteDomain(DateTime expirationTime) throws Exception {
|
||||
Domain domain = persistResource(DatabaseHelper.newDomain(getUniqueIdFromCommand()));
|
||||
HistoryEntry historyEntry =
|
||||
persistResource(
|
||||
@@ -118,29 +119,31 @@ class DomainRestoreRequestFlowTest extends ResourceFlowTestCase<DomainRestoreReq
|
||||
.setRegistrarId(domain.getCurrentSponsorRegistrarId())
|
||||
.setDomain(domain)
|
||||
.build());
|
||||
persistResource(
|
||||
domain
|
||||
.asBuilder()
|
||||
.setRegistrationExpirationTime(expirationTime)
|
||||
.setDeletionTime(clock.nowUtc().plusDays(35))
|
||||
.addGracePeriod(
|
||||
GracePeriod.create(
|
||||
GracePeriodStatus.REDEMPTION,
|
||||
domain.getRepoId(),
|
||||
clock.nowUtc().plusDays(1),
|
||||
"TheRegistrar",
|
||||
null))
|
||||
.setStatusValues(ImmutableSet.of(StatusValue.PENDING_DELETE))
|
||||
.setDeletePollMessage(
|
||||
persistResource(
|
||||
new PollMessage.OneTime.Builder()
|
||||
.setRegistrarId("TheRegistrar")
|
||||
.setEventTime(clock.nowUtc().plusDays(5))
|
||||
.setHistoryEntry(historyEntry)
|
||||
.build())
|
||||
.createVKey())
|
||||
.build());
|
||||
domain =
|
||||
persistResource(
|
||||
domain
|
||||
.asBuilder()
|
||||
.setRegistrationExpirationTime(expirationTime)
|
||||
.setDeletionTime(clock.nowUtc().plusDays(35))
|
||||
.addGracePeriod(
|
||||
GracePeriod.create(
|
||||
GracePeriodStatus.REDEMPTION,
|
||||
domain.getRepoId(),
|
||||
clock.nowUtc().plusDays(1),
|
||||
"TheRegistrar",
|
||||
null))
|
||||
.setStatusValues(ImmutableSet.of(StatusValue.PENDING_DELETE))
|
||||
.setDeletePollMessage(
|
||||
persistResource(
|
||||
new PollMessage.OneTime.Builder()
|
||||
.setRegistrarId("TheRegistrar")
|
||||
.setEventTime(clock.nowUtc().plusDays(5))
|
||||
.setHistoryEntry(historyEntry)
|
||||
.build())
|
||||
.createVKey())
|
||||
.build());
|
||||
clock.advanceOneMilli();
|
||||
return domain;
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -491,6 +494,15 @@ class DomainRestoreRequestFlowTest extends ResourceFlowTestCase<DomainRestoreReq
|
||||
loadFile("domain_update_restore_request_response_premium.xml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_worksWithoutPollMessage() throws Exception {
|
||||
Domain domain = persistPendingDeleteDomain();
|
||||
VKey<PollMessage.OneTime> deletePollMessage = domain.getDeletePollMessage();
|
||||
persistResource(domain.asBuilder().setDeletePollMessage(null).build());
|
||||
DatabaseHelper.deleteByKey(deletePollMessage);
|
||||
runFlowAssertResponse(loadFile("generic_success_response.xml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_doesNotExist() throws Exception {
|
||||
ResourceDoesNotExistException thrown =
|
||||
|
||||
@@ -20,6 +20,7 @@ import static org.mockito.Mockito.when;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import google.registry.groups.GmailClient;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.request.RequestModule;
|
||||
import google.registry.request.auth.AuthResult;
|
||||
import google.registry.security.XsrfTokenManager;
|
||||
import google.registry.ui.server.SendEmailUtils;
|
||||
@@ -45,7 +46,13 @@ public final class ConsoleApiParamsUtils {
|
||||
xsrfTokenManager.generateToken(
|
||||
authResult.user().map(User::getEmailAddress).orElse("")))
|
||||
});
|
||||
when(request.getRequestURI()).thenReturn("/console/fake-url");
|
||||
return ConsoleApiParams.create(
|
||||
request, new FakeResponse(), authResult, sendEmailUtils, xsrfTokenManager);
|
||||
request,
|
||||
new FakeResponse(),
|
||||
authResult,
|
||||
sendEmailUtils,
|
||||
xsrfTokenManager,
|
||||
RequestModule.provideGson());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1168,6 +1168,10 @@ public final class DatabaseHelper {
|
||||
tm().transact(() -> tm().delete(resource));
|
||||
}
|
||||
|
||||
public static void deleteByKey(VKey<?> key) {
|
||||
tm().transact(() -> tm().delete(key));
|
||||
}
|
||||
|
||||
/** Force the create and update timestamps to get written into the resource. */
|
||||
public static <R> R cloneAndSetAutoTimestamps(final R resource) {
|
||||
// We have to separate the read and write operation into different transactions otherwise JPA
|
||||
@@ -1294,6 +1298,11 @@ public final class DatabaseHelper {
|
||||
return tm().transact(() -> tm().loadByEntitiesIfPresent(entities));
|
||||
}
|
||||
|
||||
/** Loads the only instance of this particular class, or empty if none exists. */
|
||||
public static <T> Optional<T> loadSingleton(Class<T> clazz) {
|
||||
return tm().transact(() -> tm().loadSingleton(clazz));
|
||||
}
|
||||
|
||||
/** Returns whether or not the given entity exists in Cloud SQL. */
|
||||
public static boolean existsInDb(ImmutableObject object) {
|
||||
return tm().transact(() -> tm().exists(object));
|
||||
|
||||
@@ -91,10 +91,16 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
|
||||
runCommandForced(
|
||||
"--email",
|
||||
"user@example.test",
|
||||
"--registrar_roles",
|
||||
"TheRegistrar=PRIMARY_CONTACT",
|
||||
"--registry_lock_email_address",
|
||||
"registrylockemail@otherexample.test");
|
||||
assertThat(loadExistingUser("user@example.test").getRegistryLockEmailAddress())
|
||||
.hasValue("registrylockemail@otherexample.test");
|
||||
"registrylockemail@otherexample.test",
|
||||
"--registry_lock_password",
|
||||
"password");
|
||||
User user = loadExistingUser("user@example.test");
|
||||
assertThat(user.getRegistryLockEmailAddress()).hasValue("registrylockemail@otherexample.test");
|
||||
assertThat(user.verifyRegistryLockPassword("password")).isTrue();
|
||||
assertThat(user.verifyRegistryLockPassword("foobar")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -174,4 +180,18 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
|
||||
.hasMessageThat()
|
||||
.isEqualTo("Provided email this is not valid is not a valid email address");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_registryLockPassword_withoutEmail() {
|
||||
assertThat(
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() ->
|
||||
runCommandForced(
|
||||
"--email", "user@example.test", "--registry_lock_password", "password")))
|
||||
.hasMessageThat()
|
||||
.isEqualTo(
|
||||
"Cannot set/remove registry lock password on a user without a registry lock email"
|
||||
+ " address");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ package google.registry.tools;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.testing.DatabaseHelper.loadExistingUser;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.DatabaseHelper.putInDb;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
@@ -98,6 +99,44 @@ public class UpdateUserCommandTest extends CommandTestCase<UpdateUserCommand> {
|
||||
.isEqualTo(GlobalRole.FTE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_removePassword() throws Exception {
|
||||
// Empty password value removes the password
|
||||
persistResource(
|
||||
loadExistingUser("user@example.test")
|
||||
.asBuilder()
|
||||
.setUserRoles(
|
||||
new UserRoles.Builder()
|
||||
.setRegistrarRoles(ImmutableMap.of("TheRegistrar", RegistrarRole.TECH_CONTACT))
|
||||
.build())
|
||||
.setRegistryLockEmailAddress("registrylock@example.test")
|
||||
.setRegistryLockPassword("password")
|
||||
.build());
|
||||
assertThat(loadExistingUser("user@example.test").hasRegistryLockPassword()).isTrue();
|
||||
runCommandForced("--email", "user@example.test", "--registry_lock_password", "");
|
||||
assertThat(loadExistingUser("user@example.test").hasRegistryLockPassword()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_setsPassword() throws Exception {
|
||||
persistResource(
|
||||
loadExistingUser("user@example.test")
|
||||
.asBuilder()
|
||||
.setUserRoles(
|
||||
new UserRoles.Builder()
|
||||
.setRegistrarRoles(ImmutableMap.of("TheRegistrar", RegistrarRole.TECH_CONTACT))
|
||||
.build())
|
||||
.setRegistryLockEmailAddress("registrylock@example.test")
|
||||
.setRegistryLockPassword("password")
|
||||
.build());
|
||||
assertThat(loadExistingUser("user@example.test").verifyRegistryLockPassword("password"))
|
||||
.isTrue();
|
||||
runCommandForced("--email", "user@example.test", "--registry_lock_password", "foobar");
|
||||
assertThat(loadExistingUser("user@example.test").verifyRegistryLockPassword("password"))
|
||||
.isFalse();
|
||||
assertThat(loadExistingUser("user@example.test").verifyRegistryLockPassword("foobar")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_doesntExist() {
|
||||
assertThat(
|
||||
@@ -122,4 +161,18 @@ public class UpdateUserCommandTest extends CommandTestCase<UpdateUserCommand> {
|
||||
.hasMessageThat()
|
||||
.isEqualTo("Provided email this is not valid is not a valid email address");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_setPassword_noEmail() {
|
||||
assertThat(
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() ->
|
||||
runCommandForced(
|
||||
"--email", "user@example.test", "--registry_lock_password", "foobar")))
|
||||
.hasMessageThat()
|
||||
.isEqualTo(
|
||||
"Cannot set/remove registry lock password on a user without a registry lock email"
|
||||
+ " address");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
// 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.
|
||||
|
||||
package google.registry.ui.server.console;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_OPTIONAL;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureStatus.INACTIVE;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.loadByEntity;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveContact;
|
||||
import static google.registry.testing.DatabaseHelper.persistDomainWithDependentResources;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import google.registry.flows.DaggerEppTestComponent;
|
||||
import google.registry.flows.EppController;
|
||||
import google.registry.flows.EppTestComponent;
|
||||
import google.registry.model.common.FeatureFlag;
|
||||
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.model.domain.Domain;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.request.auth.AuthResult;
|
||||
import google.registry.testing.ConsoleApiParamsUtils;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.FakeResponse;
|
||||
import google.registry.tools.GsonUtils;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
/** Tests for {@link ConsoleBulkDomainAction}. */
|
||||
public class ConsoleBulkDomainActionTest {
|
||||
|
||||
private static final Gson GSON = GsonUtils.provideGson();
|
||||
|
||||
private final FakeClock clock = new FakeClock(DateTime.parse("2024-05-13T00:00:00.000Z"));
|
||||
|
||||
@RegisterExtension
|
||||
final JpaTestExtensions.JpaIntegrationTestExtension jpa =
|
||||
new JpaTestExtensions.Builder().withClock(clock).buildIntegrationTestExtension();
|
||||
|
||||
private EppController eppController;
|
||||
private FakeResponse fakeResponse;
|
||||
private Domain domain;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
persistResource(
|
||||
new FeatureFlag()
|
||||
.asBuilder()
|
||||
.setFeatureName(MINIMUM_DATASET_CONTACTS_OPTIONAL)
|
||||
.setStatusMap(ImmutableSortedMap.of(START_OF_TIME, INACTIVE))
|
||||
.build());
|
||||
eppController =
|
||||
DaggerEppTestComponent.builder()
|
||||
.fakesAndMocksModule(EppTestComponent.FakesAndMocksModule.create(clock))
|
||||
.build()
|
||||
.startRequest()
|
||||
.eppController();
|
||||
createTld("tld");
|
||||
domain =
|
||||
persistDomainWithDependentResources(
|
||||
"example",
|
||||
"tld",
|
||||
persistActiveContact("contact1234"),
|
||||
clock.nowUtc(),
|
||||
clock.nowUtc().minusMonths(1),
|
||||
clock.nowUtc().plusMonths(11));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_delete() {
|
||||
ConsoleBulkDomainAction action =
|
||||
createAction(
|
||||
"DELETE",
|
||||
GSON.toJsonTree(
|
||||
ImmutableMap.of("domainList", ImmutableList.of("example.tld"), "reason", "test")));
|
||||
action.run();
|
||||
assertThat(fakeResponse.getStatus()).isEqualTo(SC_OK);
|
||||
assertThat(fakeResponse.getPayload())
|
||||
.isEqualTo(
|
||||
"{\"example.tld\":{\"message\":\"Command completed"
|
||||
+ " successfully\",\"responseCode\":1000}}");
|
||||
assertThat(loadByEntity(domain).getDeletionTime()).isEqualTo(clock.nowUtc());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_suspend() throws Exception {
|
||||
User adminUser =
|
||||
persistResource(
|
||||
new User.Builder()
|
||||
.setEmailAddress("email@email.com")
|
||||
.setUserRoles(
|
||||
new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).setIsAdmin(true).build())
|
||||
.build());
|
||||
ConsoleBulkDomainAction action =
|
||||
createAction(
|
||||
"SUSPEND",
|
||||
GSON.toJsonTree(
|
||||
ImmutableMap.of("domainList", ImmutableList.of("example.tld"), "reason", "test")),
|
||||
adminUser);
|
||||
action.run();
|
||||
assertThat(fakeResponse.getStatus()).isEqualTo(SC_OK);
|
||||
assertThat(fakeResponse.getPayload())
|
||||
.isEqualTo(
|
||||
"{\"example.tld\":{\"message\":\"Command completed"
|
||||
+ " successfully\",\"responseCode\":1000}}");
|
||||
assertThat(loadByEntity(domain).getStatusValues())
|
||||
.containsAtLeast(
|
||||
StatusValue.SERVER_RENEW_PROHIBITED,
|
||||
StatusValue.SERVER_TRANSFER_PROHIBITED,
|
||||
StatusValue.SERVER_UPDATE_PROHIBITED,
|
||||
StatusValue.SERVER_DELETE_PROHIBITED,
|
||||
StatusValue.SERVER_HOLD);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHalfSuccess_halfNonexistent() throws Exception {
|
||||
ConsoleBulkDomainAction action =
|
||||
createAction(
|
||||
"DELETE",
|
||||
GSON.toJsonTree(
|
||||
ImmutableMap.of(
|
||||
"domainList",
|
||||
ImmutableList.of("example.tld", "nonexistent.tld"),
|
||||
"reason",
|
||||
"test")));
|
||||
action.run();
|
||||
assertThat(fakeResponse.getStatus()).isEqualTo(SC_OK);
|
||||
assertThat(fakeResponse.getPayload())
|
||||
.isEqualTo(
|
||||
"{\"example.tld\":{\"message\":\"Command completed"
|
||||
+ " successfully\",\"responseCode\":1000},\"nonexistent.tld\":{\"message\":\"The"
|
||||
+ " domain with given ID (nonexistent.tld) doesn\\u0027t"
|
||||
+ " exist.\",\"responseCode\":2303}}");
|
||||
assertThat(loadByEntity(domain).getDeletionTime()).isEqualTo(clock.nowUtc());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_badActionString() {
|
||||
ConsoleBulkDomainAction action = createAction("bad", null);
|
||||
action.run();
|
||||
assertThat(fakeResponse.getStatus()).isEqualTo(SC_BAD_REQUEST);
|
||||
assertThat(fakeResponse.getPayload())
|
||||
.isEqualTo(
|
||||
"No enum constant"
|
||||
+ " google.registry.ui.server.console.ConsoleBulkDomainAction.BulkAction.bad");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_emptyBody() {
|
||||
ConsoleBulkDomainAction action = createAction("DELETE", null);
|
||||
action.run();
|
||||
assertThat(fakeResponse.getStatus()).isEqualTo(SC_BAD_REQUEST);
|
||||
assertThat(fakeResponse.getPayload()).isEqualTo("Bulk action payload must be present");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_noPermission() {
|
||||
JsonElement payload =
|
||||
GSON.toJsonTree(ImmutableMap.of("domainList", ImmutableList.of("domain.tld")));
|
||||
ConsoleBulkDomainAction action =
|
||||
createAction(
|
||||
"DELETE",
|
||||
payload,
|
||||
new User.Builder()
|
||||
.setEmailAddress("foobar@theregistrar.com")
|
||||
.setUserRoles(
|
||||
new UserRoles.Builder()
|
||||
.setRegistrarRoles(
|
||||
ImmutableMap.of("TheRegistrar", RegistrarRole.ACCOUNT_MANAGER))
|
||||
.build())
|
||||
.build());
|
||||
action.run();
|
||||
assertThat(fakeResponse.getStatus()).isEqualTo(SC_FORBIDDEN);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_suspend_nonAdmin() {
|
||||
ConsoleBulkDomainAction action =
|
||||
createAction(
|
||||
"SUSPEND",
|
||||
GSON.toJsonTree(
|
||||
ImmutableMap.of("domainList", ImmutableList.of("example.tld"), "reason", "test")));
|
||||
action.run();
|
||||
assertThat(fakeResponse.getStatus()).isEqualTo(SC_OK);
|
||||
Map<String, ConsoleBulkDomainAction.ConsoleEppOutput> payload =
|
||||
GSON.fromJson(fakeResponse.getPayload(), new TypeToken<>() {});
|
||||
assertThat(payload).containsKey("example.tld");
|
||||
assertThat(payload.get("example.tld").responseCode()).isEqualTo(2004);
|
||||
assertThat(payload.get("example.tld").message()).contains("cannot be set by clients");
|
||||
assertThat(loadByEntity(domain)).isEqualTo(domain);
|
||||
}
|
||||
|
||||
private ConsoleBulkDomainAction createAction(String action, JsonElement payload) {
|
||||
User user =
|
||||
persistResource(
|
||||
new User.Builder()
|
||||
.setEmailAddress("email@email.com")
|
||||
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build())
|
||||
.build());
|
||||
return createAction(action, payload, user);
|
||||
}
|
||||
|
||||
private ConsoleBulkDomainAction createAction(String action, JsonElement payload, User user) {
|
||||
AuthResult authResult = AuthResult.createUser(user);
|
||||
ConsoleApiParams params = ConsoleApiParamsUtils.createFake(authResult);
|
||||
when(params.request().getMethod()).thenReturn("POST");
|
||||
fakeResponse = (FakeResponse) params.response();
|
||||
return new ConsoleBulkDomainAction(
|
||||
params, eppController, "TheRegistrar", action, Optional.ofNullable(payload));
|
||||
}
|
||||
}
|
||||
@@ -22,13 +22,11 @@ import static jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gson.Gson;
|
||||
import google.registry.model.console.RegistrarRole;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.console.UserRoles;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.RequestModule;
|
||||
import google.registry.request.auth.AuthResult;
|
||||
import google.registry.testing.ConsoleApiParamsUtils;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
@@ -40,7 +38,6 @@ import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
/** Tests for {@link google.registry.ui.server.console.ConsoleDomainGetAction}. */
|
||||
public class ConsoleDomainGetActionTest {
|
||||
|
||||
private static final Gson GSON = RequestModule.provideGson();
|
||||
private ConsoleApiParams consoleApiParams;
|
||||
|
||||
@RegisterExtension
|
||||
@@ -124,6 +121,6 @@ public class ConsoleDomainGetActionTest {
|
||||
private ConsoleDomainGetAction createAction(AuthResult authResult, String domain) {
|
||||
consoleApiParams = ConsoleApiParamsUtils.createFake(authResult);
|
||||
when(consoleApiParams.request().getMethod()).thenReturn(Action.Method.GET.toString());
|
||||
return new ConsoleDomainGetAction(consoleApiParams, GSON, domain);
|
||||
return new ConsoleDomainGetAction(consoleApiParams, domain);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,7 +264,6 @@ public class ConsoleDomainListActionTest {
|
||||
when(consoleApiParams.request().getMethod()).thenReturn(Action.Method.GET.toString());
|
||||
return new ConsoleDomainListAction(
|
||||
consoleApiParams,
|
||||
GSON,
|
||||
registrarId,
|
||||
Optional.ofNullable(checkpointTime),
|
||||
Optional.ofNullable(pageNumber),
|
||||
|
||||
@@ -21,7 +21,6 @@ import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.gson.Gson;
|
||||
import google.registry.model.console.GlobalRole;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.console.UserRoles;
|
||||
@@ -32,7 +31,6 @@ import google.registry.testing.ConsoleApiParamsUtils;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.FakeResponse;
|
||||
import google.registry.tools.GsonUtils;
|
||||
import java.io.IOException;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -41,8 +39,6 @@ import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
class ConsoleDumDownloadActionTest {
|
||||
|
||||
private static final Gson GSON = GsonUtils.provideGson();
|
||||
|
||||
private final FakeClock clock = new FakeClock(DateTime.parse("2024-04-15T00:00:00.000Z"));
|
||||
|
||||
private ConsoleApiParams consoleApiParams;
|
||||
|
||||
@@ -17,6 +17,7 @@ package google.registry.ui.server.console;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.request.auth.AuthenticatedRegistrarAccessor.Role.OWNER;
|
||||
import static google.registry.testing.DatabaseHelper.loadRegistrar;
|
||||
import static google.registry.testing.DatabaseHelper.loadSingleton;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
|
||||
@@ -32,6 +33,7 @@ import com.google.common.collect.ImmutableSetMultimap;
|
||||
import com.google.gson.Gson;
|
||||
import google.registry.flows.PasswordOnlyTransportCredentials;
|
||||
import google.registry.model.console.GlobalRole;
|
||||
import google.registry.model.console.RegistrarUpdateHistory;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.console.UserRoles;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
@@ -41,6 +43,7 @@ import google.registry.request.RequestModule;
|
||||
import google.registry.request.auth.AuthResult;
|
||||
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
|
||||
import google.registry.testing.ConsoleApiParamsUtils;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
import google.registry.testing.FakeResponse;
|
||||
import google.registry.tools.GsonUtils;
|
||||
import google.registry.ui.server.console.ConsoleEppPasswordAction.EppPasswordData;
|
||||
@@ -139,6 +142,10 @@ class ConsoleEppPasswordActionTest {
|
||||
() -> {
|
||||
credentials.validate(loadRegistrar("TheRegistrar"), "randomPassword");
|
||||
});
|
||||
assertThat(loadSingleton(RegistrarUpdateHistory.class).get().getRequestBody())
|
||||
.isEqualTo(
|
||||
"{\"registrarId\":\"TheRegistrar\",\"oldPassword\":\"********\",\"newPassword\":"
|
||||
+ "\"••••••••\",\"newPasswordRepeat\":\"••••••••\"}");
|
||||
}
|
||||
|
||||
private ConsoleEppPasswordAction createAction(
|
||||
@@ -150,6 +157,7 @@ class ConsoleEppPasswordActionTest {
|
||||
.setEmailAddress("email@email.com")
|
||||
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build())
|
||||
.build();
|
||||
DatabaseHelper.putInDb(user);
|
||||
|
||||
AuthResult authResult = AuthResult.createUser(user);
|
||||
consoleApiParams = ConsoleApiParamsUtils.createFake(authResult);
|
||||
|
||||
@@ -245,7 +245,6 @@ class ConsoleOteActionTest {
|
||||
when(consoleApiParams.request().getMethod()).thenReturn(method.toString());
|
||||
return new ConsoleOteAction(
|
||||
consoleApiParams,
|
||||
GSON,
|
||||
iamClient,
|
||||
registrarId,
|
||||
maybeGroupEmailAddress,
|
||||
|
||||
@@ -34,7 +34,6 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gson.Gson;
|
||||
import google.registry.groups.GmailClient;
|
||||
import google.registry.model.console.GlobalRole;
|
||||
import google.registry.model.console.RegistrarRole;
|
||||
@@ -44,7 +43,6 @@ import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.RegistryLock;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
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;
|
||||
@@ -83,8 +81,6 @@ public class ConsoleRegistryLockActionTest {
|
||||
https://registrarconsole.tld/console/#/registry-lock-verify?lockVerificationCode=\
|
||||
123456789ABCDEFGHJKLMNPQRSTUVWXY""";
|
||||
|
||||
private static final Gson GSON = RequestModule.provideGson();
|
||||
|
||||
private final FakeClock fakeClock = new FakeClock(DateTime.parse("2024-04-18T12:00:00.000Z"));
|
||||
|
||||
@RegisterExtension
|
||||
@@ -128,10 +124,10 @@ public class ConsoleRegistryLockActionTest {
|
||||
assertThat(response.getPayload())
|
||||
.isEqualTo(
|
||||
"""
|
||||
[{"domainName":"example.test","registrarPocId":"johndoe@theregistrar.com","lockRequestTime":\
|
||||
{"creationTime":"2024-04-18T12:00:00.000Z"},"unlockRequestTime":"null","lockCompletionTime":\
|
||||
"2024-04-18T12:00:00.000Z","unlockCompletionTime":"null","isSuperuser":false}]\
|
||||
""");
|
||||
[{"domainName":"example.test","registrarPocId":"johndoe@theregistrar.com","lockRequestTime":\
|
||||
{"creationTime":"2024-04-18T12:00:00.000Z"},"unlockRequestTime":"null","lockCompletionTime":\
|
||||
"2024-04-18T12:00:00.000Z","unlockCompletionTime":"null","isSuperuser":false}]\
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -222,25 +218,25 @@ public class ConsoleRegistryLockActionTest {
|
||||
assertThat(response.getPayload())
|
||||
.isEqualTo(
|
||||
"""
|
||||
[{"domainName":"adminexample.test","lockRequestTime":{"creationTime":"2024-04-19T12:00:00.001Z"},\
|
||||
"unlockRequestTime":"null","lockCompletionTime":"2024-04-19T12:00:00.001Z","unlockCompletionTime":\
|
||||
"null","isSuperuser":true},\
|
||||
\
|
||||
{"domainName":"example.test","registrarPocId":"johndoe@theregistrar.com","lockRequestTime":\
|
||||
{"creationTime":"2024-04-19T12:00:00.001Z"},"unlockRequestTime":"null","lockCompletionTime":\
|
||||
"2024-04-19T12:00:00.000Z","unlockCompletionTime":"null","isSuperuser":false},\
|
||||
\
|
||||
{"domainName":"expiredunlock.test","registrarPocId":"johndoe@theregistrar.com","lockRequestTime":\
|
||||
{"creationTime":"2024-04-18T12:00:00.000Z"},"unlockRequestTime":"2024-04-18T12:00:00.000Z",\
|
||||
"lockCompletionTime":"2024-04-18T12:00:00.000Z","unlockCompletionTime":"null","isSuperuser":false},\
|
||||
\
|
||||
{"domainName":"incompleteunlock.test","registrarPocId":"johndoe@theregistrar.com","lockRequestTime":\
|
||||
{"creationTime":"2024-04-19T12:00:00.001Z"},"unlockRequestTime":"2024-04-19T12:00:00.001Z",\
|
||||
"lockCompletionTime":"2024-04-19T12:00:00.001Z","unlockCompletionTime":"null","isSuperuser":false},\
|
||||
\
|
||||
{"domainName":"pending.test","registrarPocId":"johndoe@theregistrar.com","lockRequestTime":\
|
||||
{"creationTime":"2024-04-19T12:00:00.001Z"},"unlockRequestTime":"null","lockCompletionTime":"null",\
|
||||
"unlockCompletionTime":"null","isSuperuser":false}]""");
|
||||
[{"domainName":"adminexample.test","lockRequestTime":{"creationTime":"2024-04-19T12:00:00.001Z"},\
|
||||
"unlockRequestTime":"null","lockCompletionTime":"2024-04-19T12:00:00.001Z","unlockCompletionTime":\
|
||||
"null","isSuperuser":true},\
|
||||
\
|
||||
{"domainName":"example.test","registrarPocId":"johndoe@theregistrar.com","lockRequestTime":\
|
||||
{"creationTime":"2024-04-19T12:00:00.001Z"},"unlockRequestTime":"null","lockCompletionTime":\
|
||||
"2024-04-19T12:00:00.000Z","unlockCompletionTime":"null","isSuperuser":false},\
|
||||
\
|
||||
{"domainName":"expiredunlock.test","registrarPocId":"johndoe@theregistrar.com","lockRequestTime":\
|
||||
{"creationTime":"2024-04-18T12:00:00.000Z"},"unlockRequestTime":"2024-04-18T12:00:00.000Z",\
|
||||
"lockCompletionTime":"2024-04-18T12:00:00.000Z","unlockCompletionTime":"null","isSuperuser":false},\
|
||||
\
|
||||
{"domainName":"incompleteunlock.test","registrarPocId":"johndoe@theregistrar.com","lockRequestTime":\
|
||||
{"creationTime":"2024-04-19T12:00:00.001Z"},"unlockRequestTime":"2024-04-19T12:00:00.001Z",\
|
||||
"lockCompletionTime":"2024-04-19T12:00:00.001Z","unlockCompletionTime":"null","isSuperuser":false},\
|
||||
\
|
||||
{"domainName":"pending.test","registrarPocId":"johndoe@theregistrar.com","lockRequestTime":\
|
||||
{"creationTime":"2024-04-19T12:00:00.001Z"},"unlockRequestTime":"null","lockCompletionTime":"null",\
|
||||
"unlockCompletionTime":"null","isSuperuser":false}]""");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -508,7 +504,7 @@ public class ConsoleRegistryLockActionTest {
|
||||
new CloudTasksHelper(fakeClock).getTestCloudTasksUtils());
|
||||
response = (FakeResponse) params.response();
|
||||
return new ConsoleRegistryLockAction(
|
||||
params, domainLockUtils, gmailClient, GSON, optionalPostInput, "TheRegistrar");
|
||||
params, domainLockUtils, gmailClient, optionalPostInput, "TheRegistrar");
|
||||
}
|
||||
|
||||
private ConsoleApiParams createParams() {
|
||||
|
||||
@@ -24,7 +24,6 @@ import static google.registry.tools.LockOrUnlockDomainCommand.REGISTRY_LOCK_STAT
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gson.Gson;
|
||||
import google.registry.model.console.RegistrarRole;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.console.UserRoles;
|
||||
@@ -32,7 +31,6 @@ import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.RegistryLock;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
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;
|
||||
@@ -51,7 +49,6 @@ import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
public class ConsoleRegistryLockVerifyActionTest {
|
||||
|
||||
private static final String DEFAULT_CODE = "123456789ABCDEFGHJKLMNPQRSTUUUUU";
|
||||
private static final Gson GSON = RequestModule.provideGson();
|
||||
private final FakeClock fakeClock = new FakeClock();
|
||||
|
||||
@RegisterExtension
|
||||
@@ -214,6 +211,6 @@ public class ConsoleRegistryLockVerifyActionTest {
|
||||
"adminreg",
|
||||
new CloudTasksHelper(fakeClock).getTestCloudTasksUtils());
|
||||
response = (FakeResponse) params.response();
|
||||
return new ConsoleRegistryLockVerifyAction(params, domainLockUtils, GSON, verificationCode);
|
||||
return new ConsoleRegistryLockVerifyAction(params, domainLockUtils, verificationCode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,10 @@
|
||||
package google.registry.ui.server.console;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
|
||||
import static google.registry.model.registrar.RegistrarPocBase.Type.WHOIS;
|
||||
import static google.registry.testing.DatabaseHelper.createTlds;
|
||||
import static google.registry.testing.DatabaseHelper.loadSingleton;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
@@ -29,6 +31,7 @@ import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.gson.Gson;
|
||||
import google.registry.model.console.GlobalRole;
|
||||
import google.registry.model.console.RegistrarUpdateHistory;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.console.UserRoles;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
@@ -85,10 +88,11 @@ class ConsoleUpdateRegistrarActionTest {
|
||||
.setRegistryLockAllowed(false)
|
||||
.build());
|
||||
user =
|
||||
new User.Builder()
|
||||
.setEmailAddress("user@registrarId.com")
|
||||
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build())
|
||||
.build();
|
||||
persistResource(
|
||||
new User.Builder()
|
||||
.setEmailAddress("user@registrarId.com")
|
||||
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build())
|
||||
.build());
|
||||
consoleApiParams = createParams();
|
||||
}
|
||||
|
||||
@@ -104,6 +108,9 @@ class ConsoleUpdateRegistrarActionTest {
|
||||
assertThat(newRegistrar.getAllowedTlds()).containsExactly("app", "dev");
|
||||
assertThat(newRegistrar.isRegistryLockAllowed()).isFalse();
|
||||
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_OK);
|
||||
assertAboutImmutableObjects()
|
||||
.that(newRegistrar)
|
||||
.hasFieldsEqualTo(loadSingleton(RegistrarUpdateHistory.class).get().getRegistrar());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -20,6 +20,7 @@ import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gson.Gson;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
@@ -71,6 +72,8 @@ class ConsoleUserDataActionTest {
|
||||
GSON.fromJson(((FakeResponse) consoleApiParams.response()).getPayload(), Map.class);
|
||||
assertThat(jsonObject)
|
||||
.containsExactly(
|
||||
"userRoles",
|
||||
ImmutableMap.of(),
|
||||
"isAdmin",
|
||||
true,
|
||||
"technicalDocsUrl",
|
||||
|
||||
@@ -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.UserData;
|
||||
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,243 @@ 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\"}");
|
||||
"{\"emailAddress\":\"TheRegistrar-user1@email.com\",\"role\":\"ACCOUNT_MANAGER\",\"password\":\"abcdefghijklmnop\"}");
|
||||
}
|
||||
|
||||
@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 UserData("test3@test.com", RegistrarRole.ACCOUNT_MANAGER.toString(), null)));
|
||||
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 update 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 UserData("email-1@email.com", RegistrarRole.ACCOUNT_MANAGER.toString(), null)));
|
||||
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 UserData("test2@test.com", RegistrarRole.ACCOUNT_MANAGER.toString(), null)));
|
||||
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 testSuccess_removesRole() 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.persistResource(
|
||||
new User.Builder()
|
||||
.setEmailAddress("test4@test.com")
|
||||
.setUserRoles(
|
||||
new UserRoles()
|
||||
.asBuilder()
|
||||
.setRegistrarRoles(
|
||||
ImmutableMap.of(
|
||||
"TheRegistrar",
|
||||
RegistrarRole.PRIMARY_CONTACT,
|
||||
"SomeRegistrar",
|
||||
RegistrarRole.PRIMARY_CONTACT))
|
||||
.build())
|
||||
.build());
|
||||
|
||||
ConsoleUsersAction action =
|
||||
createAction(
|
||||
Optional.of(ConsoleApiParamsUtils.createFake(authResult)),
|
||||
Optional.of("DELETE"),
|
||||
Optional.of(
|
||||
new UserData("test4@test.com", RegistrarRole.ACCOUNT_MANAGER.toString(), null)));
|
||||
|
||||
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);
|
||||
Optional<User> actualUser =
|
||||
DatabaseHelper.loadByKeyIfPresent(VKey.create(User.class, "test4@test.com"));
|
||||
assertThat(actualUser).isPresent();
|
||||
assertThat(actualUser.get().getUserRoles().getRegistrarRoles().containsKey("TheRegistrar"))
|
||||
.isFalse();
|
||||
}
|
||||
|
||||
@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");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_updatesUserRole() 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());
|
||||
|
||||
assertThat(
|
||||
DatabaseHelper.loadByKey(VKey.create(User.class, "test2@test.com"))
|
||||
.getUserRoles()
|
||||
.getRegistrarRoles()
|
||||
.get("TheRegistrar"))
|
||||
.isEqualTo(RegistrarRole.PRIMARY_CONTACT);
|
||||
ConsoleUsersAction action =
|
||||
createAction(
|
||||
Optional.of(ConsoleApiParamsUtils.createFake(authResult)),
|
||||
Optional.of("PUT"),
|
||||
Optional.of(
|
||||
new UserData("test2@test.com", RegistrarRole.ACCOUNT_MANAGER.toString(), null)));
|
||||
action.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
|
||||
action.run();
|
||||
var response = ((FakeResponse) consoleApiParams.response());
|
||||
assertThat(response.getStatus()).isEqualTo(SC_OK);
|
||||
assertThat(
|
||||
DatabaseHelper.loadByKey(VKey.create(User.class, "test2@test.com"))
|
||||
.getUserRoles()
|
||||
.getRegistrarRoles()
|
||||
.get("TheRegistrar"))
|
||||
.isEqualTo(RegistrarRole.ACCOUNT_MANAGER);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_noPermissionToUpdateUser() 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("PUT"),
|
||||
Optional.of(
|
||||
new UserData("test3@test.com", RegistrarRole.ACCOUNT_MANAGER.toString(), null)));
|
||||
action.run();
|
||||
var response = ((FakeResponse) consoleApiParams.response());
|
||||
assertThat(response.getStatus()).isEqualTo(SC_FORBIDDEN);
|
||||
assertThat(response.getPayload())
|
||||
.contains("Can't update user not associated with registrarId TheRegistrar");
|
||||
}
|
||||
|
||||
private ConsoleUsersAction createAction(
|
||||
Optional<ConsoleApiParams> maybeConsoleApiParams, Optional<String> method)
|
||||
Optional<ConsoleApiParams> maybeConsoleApiParams,
|
||||
Optional<String> method,
|
||||
Optional<UserData> userData)
|
||||
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,
|
||||
directory,
|
||||
iamClient,
|
||||
"email.com",
|
||||
Optional.of("someRandomString"),
|
||||
passwordGenerator,
|
||||
userData,
|
||||
"TheRegistrar");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,10 @@
|
||||
package google.registry.ui.server.console;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
|
||||
import static google.registry.testing.DatabaseHelper.loadAllOf;
|
||||
import static google.registry.testing.DatabaseHelper.loadRegistrar;
|
||||
import static google.registry.testing.DatabaseHelper.loadSingleton;
|
||||
import static google.registry.testing.DatabaseHelper.persistNewRegistrar;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.SqlHelper.saveRegistrar;
|
||||
@@ -30,6 +32,7 @@ import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gson.Gson;
|
||||
import google.registry.model.console.GlobalRole;
|
||||
import google.registry.model.console.RegistrarRole;
|
||||
import google.registry.model.console.RegistrarUpdateHistory;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.console.UserRoles;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
@@ -183,6 +186,9 @@ class RegistrarsActionTest {
|
||||
.findAny()
|
||||
.isPresent())
|
||||
.isTrue();
|
||||
assertAboutImmutableObjects()
|
||||
.that(r)
|
||||
.isEqualExceptFields(loadSingleton(RegistrarUpdateHistory.class).get().getRegistrar());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -225,10 +231,8 @@ class RegistrarsActionTest {
|
||||
}
|
||||
|
||||
private User createUser(UserRoles userRoles) {
|
||||
return new User.Builder()
|
||||
.setEmailAddress("email@email.com")
|
||||
.setUserRoles(userRoles)
|
||||
.build();
|
||||
return persistResource(
|
||||
new User.Builder().setEmailAddress("email@email.com").setUserRoles(userRoles).build());
|
||||
}
|
||||
|
||||
private RegistrarsAction createAction(Action.Method method, AuthResult authResult) {
|
||||
@@ -236,7 +240,7 @@ class RegistrarsActionTest {
|
||||
when(consoleApiParams.request().getMethod()).thenReturn(method.toString());
|
||||
if (method.equals(Action.Method.GET)) {
|
||||
return new RegistrarsAction(
|
||||
consoleApiParams, GSON, Optional.ofNullable(null), passwordGenerator, passcodeGenerator);
|
||||
consoleApiParams, Optional.ofNullable(null), passwordGenerator, passcodeGenerator);
|
||||
} else {
|
||||
try {
|
||||
doReturn(new BufferedReader(new StringReader(registrarParamMap.toString())))
|
||||
@@ -245,7 +249,6 @@ class RegistrarsActionTest {
|
||||
} catch (IOException e) {
|
||||
return new RegistrarsAction(
|
||||
consoleApiParams,
|
||||
GSON,
|
||||
Optional.ofNullable(null),
|
||||
passwordGenerator,
|
||||
passcodeGenerator);
|
||||
@@ -254,7 +257,7 @@ class RegistrarsActionTest {
|
||||
ConsoleModule.provideRegistrar(
|
||||
GSON, RequestModule.provideJsonBody(consoleApiParams.request(), GSON));
|
||||
return new RegistrarsAction(
|
||||
consoleApiParams, GSON, maybeRegistrar, passwordGenerator, passcodeGenerator);
|
||||
consoleApiParams, maybeRegistrar, passwordGenerator, passcodeGenerator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -480,10 +480,10 @@ class ContactActionTest {
|
||||
consoleApiParams = ConsoleApiParamsUtils.createFake(authResult);
|
||||
when(consoleApiParams.request().getMethod()).thenReturn(method.toString());
|
||||
if (method.equals(Action.Method.GET)) {
|
||||
return new ContactAction(consoleApiParams, GSON, registrarId, Optional.empty());
|
||||
return new ContactAction(consoleApiParams, registrarId, Optional.empty());
|
||||
} else {
|
||||
return new ContactAction(
|
||||
consoleApiParams, GSON, registrarId, Optional.of(ImmutableSet.copyOf(contacts)));
|
||||
consoleApiParams, registrarId, Optional.of(ImmutableSet.copyOf(contacts)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,10 @@
|
||||
package google.registry.ui.server.console.settings;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
|
||||
import static google.registry.testing.CertificateSamples.SAMPLE_CERT2;
|
||||
import static google.registry.testing.DatabaseHelper.loadRegistrar;
|
||||
import static google.registry.testing.DatabaseHelper.loadSingleton;
|
||||
import static google.registry.testing.SqlHelper.saveRegistrar;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
@@ -28,6 +30,7 @@ import com.google.common.collect.ImmutableSetMultimap;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import com.google.gson.Gson;
|
||||
import google.registry.flows.certs.CertificateChecker;
|
||||
import google.registry.model.console.RegistrarUpdateHistory;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.request.Action;
|
||||
@@ -98,6 +101,9 @@ class SecurityActionTest {
|
||||
.isEqualTo("GNd6ZP8/n91t9UTnpxR8aH7aAW4+CpvufYx9ViGbcMY");
|
||||
assertThat(r.getIpAddressAllowList().get(0).getIp()).isEqualTo("192.168.1.1");
|
||||
assertThat(r.getIpAddressAllowList().get(0).getNetmask()).isEqualTo(32);
|
||||
assertAboutImmutableObjects()
|
||||
.that(loadSingleton(RegistrarUpdateHistory.class).get().getRegistrar())
|
||||
.hasFieldsEqualTo(r);
|
||||
}
|
||||
|
||||
private SecurityAction createAction(AuthResult authResult, String registrarId)
|
||||
|
||||
@@ -16,6 +16,7 @@ package google.registry.ui.server.console.settings;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
|
||||
import static google.registry.testing.DatabaseHelper.loadSingleton;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
@@ -27,6 +28,7 @@ import com.google.common.collect.ImmutableSetMultimap;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.gson.Gson;
|
||||
import google.registry.model.console.RegistrarRole;
|
||||
import google.registry.model.console.RegistrarUpdateHistory;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.console.UserRoles;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
@@ -129,6 +131,9 @@ public class WhoisRegistrarFieldsActionTest {
|
||||
.that(newRegistrar)
|
||||
.isEqualExceptFields(
|
||||
oldRegistrar, "whoisServer", "url", "localizedAddress", "phoneNumber", "faxNumber");
|
||||
assertAboutImmutableObjects()
|
||||
.that(loadSingleton(RegistrarUpdateHistory.class).get().getRegistrar())
|
||||
.hasFieldsEqualTo(newRegistrar);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -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,PUT 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,PUT n USER PUBLIC
|
||||
@@ -56,7 +56,7 @@
|
||||
</rdeHeader:count>
|
||||
</rdeHeader:header>
|
||||
|
||||
<!-- Domian: example1.test -->
|
||||
<!-- Domain: example1.test -->
|
||||
<rdeDom:domain>
|
||||
<rdeDom:name>example1.test</rdeDom:name>
|
||||
<rdeDom:roid>Dexample1-TEST</rdeDom:roid>
|
||||
@@ -74,7 +74,7 @@
|
||||
<rdeDom:exDate>2015-04-03T22:00:00.0Z</rdeDom:exDate>
|
||||
</rdeDom:domain>
|
||||
|
||||
<!-- Domian: example2.test -->
|
||||
<!-- Domain: example2.test -->
|
||||
<rdeDom:domain>
|
||||
<rdeDom:name>example2.test</rdeDom:name>
|
||||
<rdeDom:roid>Dexample2-TEST</rdeDom:roid>
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
</rdeHeader:count>
|
||||
</rdeHeader:header>
|
||||
|
||||
<!-- Domian: example1.test -->
|
||||
<!-- Domain: example1.test -->
|
||||
<rdeDom:domain>
|
||||
<rdeDom:name>example1.test</rdeDom:name>
|
||||
<rdeDom:roid>Dexample1-TEST</rdeDom:roid>
|
||||
@@ -74,7 +74,7 @@
|
||||
<rdeDom:exDate>2015-04-03T22:00:00.0Z</rdeDom:exDate>
|
||||
</rdeDom:domain>
|
||||
|
||||
<!-- Domian: example2.test -->
|
||||
<!-- Domain: example2.test -->
|
||||
<rdeDom:domain>
|
||||
<rdeDom:name>example2.test</rdeDom:name>
|
||||
<rdeDom:roid>Dexample2-TEST</rdeDom:roid>
|
||||
|
||||
Reference in New Issue
Block a user