1
0
mirror of https://github.com/google/nomulus synced 2025-12-23 06:15:42 +00:00

Add console user role update and minor fixes to delete (#2610)

This commit is contained in:
Pavlo Tkach
2024-11-15 13:36:10 -05:00
committed by GitHub
parent e54075fea3
commit eeed166310
12 changed files with 380 additions and 183 deletions

View File

@@ -172,14 +172,20 @@ export class BackendService {
.pipe(catchError((err) => this.errorCatcher<User>(err))); .pipe(catchError((err) => this.errorCatcher<User>(err)));
} }
deleteUser(registrarId: string, emailAddress: string): Observable<any> { deleteUser(registrarId: string, user: User): Observable<any> {
return this.http return this.http
.delete<any>(`/console-api/users?registrarId=${registrarId}`, { .delete<any>(`/console-api/users?registrarId=${registrarId}`, {
body: JSON.stringify({ emailAddress }), body: JSON.stringify(user),
}) })
.pipe(catchError((err) => this.errorCatcher<any>(err))); .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> { getUserData(): Observable<UserData> {
return this.http return this.http
.get<UserData>('/console-api/userdata') .get<UserData>('/console-api/userdata')

View File

@@ -1,32 +1,77 @@
<div class="console-app__user-details"> <div class="console-app__user-details">
@if(isNewUser) { @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"> <h1 class="mat-headline-4">
{{ userDetails.emailAddress + " succesfully created" }} {{ userDetails().emailAddress + " successfully created" }}
</h1> </h1>
} @else { } @else {
<h1 class="mat-headline-4">User details</h1> <h1 class="mat-headline-4">User details</h1>
}
<mat-divider></mat-divider> <mat-divider></mat-divider>
<div> <div class="console-app__user-details-controls">
<div class="console-app__user-details-controls"> <button mat-icon-button aria-label="Back to users list" (click)="goBack()">
<button <mat-icon>arrow_back</mat-icon>
mat-icon-button </button>
aria-label="Back to users list" <div class="spacer"></div>
(click)="goBack()" <button
> mat-flat-button
<mat-icon>arrow_back</mat-icon> color="primary"
</button> aria-label="Edit User"
<div class="spacer"></div> (click)="userRole = userDetails().role; isEditing = true"
<button >
mat-icon-button <mat-icon>edit</mat-icon>
aria-label="Delete User" Edit
(click)="deleteUser()" </button>
[disabled]="isLoading" <button
> mat-icon-button
<mat-icon>delete</mat-icon> aria-label="Delete User"
</button> (click)="deleteUser()"
</div> [disabled]="isLoading"
>
<mat-icon>delete</mat-icon>
</button>
</div> </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"> <p *ngIf="isLoading">
<mat-progress-bar mode="query"></mat-progress-bar> <mat-progress-bar mode="query"></mat-progress-bar>
</p> </p>
@@ -41,17 +86,17 @@
<mat-list-item role="listitem"> <mat-list-item role="listitem">
<span class="console-app__list-key">User email</span> <span class="console-app__list-key">User email</span>
<span class="console-app__list-value">{{ <span class="console-app__list-value">{{
userDetails.emailAddress userDetails().emailAddress
}}</span> }}</span>
</mat-list-item> </mat-list-item>
<mat-divider></mat-divider> <mat-divider></mat-divider>
<mat-list-item role="listitem"> <mat-list-item role="listitem">
<span class="console-app__list-key">User role</span> <span class="console-app__list-key">User role</span>
<span class="console-app__list-value">{{ <span class="console-app__list-value">{{
roleToDescription(userDetails.role) roleToDescription(userDetails().role)
}}</span> }}</span>
</mat-list-item> </mat-list-item>
@if (userDetails.password) { @if (userDetails().password) {
<mat-divider></mat-divider> <mat-divider></mat-divider>
<mat-list-item role="listitem"> <mat-list-item role="listitem">
<span class="console-app__list-key">Password</span> <span class="console-app__list-key">Password</span>
@@ -60,7 +105,7 @@
> >
<input <input
[type]="isPasswordVisible ? 'text' : 'password'" [type]="isPasswordVisible ? 'text' : 'password'"
[value]="userDetails.password" [value]="userDetails().password"
disabled disabled
/> />
<button <button
@@ -76,4 +121,5 @@
</mat-list> </mat-list>
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
}}
</div> </div>

View File

@@ -25,6 +25,15 @@
background: transparent; 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; max-width: 616px;
} }
} }

View File

@@ -13,13 +13,14 @@
// limitations under the License. // limitations under the License.
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Component } from '@angular/core'; import { Component, computed } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar'; import { MatSnackBar } from '@angular/material/snack-bar';
import { SelectedRegistrarModule } from '../app.module'; import { SelectedRegistrarModule } from '../app.module';
import { MaterialModule } from '../material.module'; import { MaterialModule } from '../material.module';
import { RegistrarService } from '../registrar/registrar.service'; import { RegistrarService } from '../registrar/registrar.service';
import { SnackBarModule } from '../snackbar.module'; import { SnackBarModule } from '../snackbar.module';
import { User, UsersService, roleToDescription } from './users.service'; import { User, UsersService, roleToDescription } from './users.service';
import { FormsModule } from '@angular/forms';
@Component({ @Component({
selector: 'app-user-edit', selector: 'app-user-edit',
@@ -27,6 +28,7 @@ import { User, UsersService, roleToDescription } from './users.service';
styleUrls: ['./userEdit.component.scss'], styleUrls: ['./userEdit.component.scss'],
standalone: true, standalone: true,
imports: [ imports: [
FormsModule,
MaterialModule, MaterialModule,
SnackBarModule, SnackBarModule,
CommonModule, CommonModule,
@@ -35,22 +37,25 @@ import { User, UsersService, roleToDescription } from './users.service';
providers: [], providers: [],
}) })
export class UserEditComponent { export class UserEditComponent {
inEdit = false; isEditing = false;
isPasswordVisible = false; isPasswordVisible = false;
isNewUser = false; isNewUser = false;
isLoading = false; isLoading = false;
userDetails: User; userRole = '';
userDetails = computed(() => {
return this.usersService
.users()
.filter(
(u) => u.emailAddress === this.usersService.currentlyOpenUserEmail()
)[0];
});
constructor( constructor(
protected registrarService: RegistrarService, protected registrarService: RegistrarService,
protected usersService: UsersService, protected usersService: UsersService,
private _snackBar: MatSnackBar private _snackBar: MatSnackBar
) { ) {
this.userDetails = this.usersService
.users()
.filter(
(u) => u.emailAddress === this.usersService.currentlyOpenUserEmail()
)[0];
if (this.usersService.isNewUser) { if (this.usersService.isNewUser) {
this.isNewUser = true; this.isNewUser = true;
this.usersService.isNewUser = false; this.usersService.isNewUser = false;
@@ -63,7 +68,7 @@ export class UserEditComponent {
deleteUser() { deleteUser() {
this.isLoading = true; this.isLoading = true;
this.usersService.deleteUser(this.userDetails.emailAddress).subscribe({ this.usersService.deleteUser(this.userDetails()).subscribe({
error: (err) => { error: (err) => {
this._snackBar.open(err.error || err.message); this._snackBar.open(err.error || err.message);
this.isLoading = false; this.isLoading = false;
@@ -78,4 +83,23 @@ export class UserEditComponent {
goBack() { goBack() {
this.usersService.currentlyOpenUserEmail.set(''); 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;
},
});
}
} }

View File

@@ -13,13 +13,13 @@
// limitations under the License. // limitations under the License.
import { Injectable, signal } from '@angular/core'; import { Injectable, signal } from '@angular/core';
import { tap } from 'rxjs'; import { switchMap, tap } from 'rxjs';
import { RegistrarService } from '../registrar/registrar.service'; import { RegistrarService } from '../registrar/registrar.service';
import { BackendService } from '../shared/services/backend.service'; import { BackendService } from '../shared/services/backend.service';
export const roleToDescription = (role: string) => { export const roleToDescription = (role: string) => {
if (!role) return 'N/A'; if (!role) return 'N/A';
else if (role.toLowerCase().startsWith('account_manager')) { else if (role === 'ACCOUNT_MANAGER') {
return 'Viewer'; return 'Viewer';
} }
return 'Editor'; return 'Editor';
@@ -68,9 +68,15 @@ export class UsersService {
); );
} }
deleteUser(emailAddress: string) { deleteUser(user: User) {
return this.backendService return this.backendService
.deleteUser(this.registrarService.registrarId(), emailAddress) .deleteUser(this.registrarService.registrarId(), user)
.pipe(tap((_) => this.fetchUsers())); .pipe(switchMap((_) => this.fetchUsers()));
}
updateUser(updatedUser: User) {
return this.backendService
.updateUser(this.registrarService.registrarId(), updatedUser)
.pipe(switchMap((_) => this.fetchUsers()));
} }
} }

View File

@@ -34,6 +34,7 @@ public @interface Action {
GET, GET,
HEAD, HEAD,
POST, POST,
PUT,
DELETE DELETE
} }

View File

@@ -20,6 +20,7 @@ import static google.registry.request.Action.Method.DELETE;
import static google.registry.request.Action.Method.GET; import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.HEAD; import static google.registry.request.Action.Method.HEAD;
import static google.registry.request.Action.Method.POST; 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_BAD_REQUEST;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN; 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_INTERNAL_SERVER_ERROR;
@@ -87,6 +88,8 @@ public abstract class ConsoleApiAction implements Runnable {
if (verifyXSRF(user)) { if (verifyXSRF(user)) {
if (requestMethod.equals(DELETE.toString())) { if (requestMethod.equals(DELETE.toString())) {
deleteHandler(user); deleteHandler(user);
} else if (requestMethod.equals(PUT.toString())) {
putHandler(user);
} else { } else {
postHandler(user); postHandler(user);
} }
@@ -117,6 +120,10 @@ public abstract class ConsoleApiAction implements Runnable {
throw new UnsupportedOperationException("Console API POST handler not implemented"); 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) { protected void getHandler(User user) {
throw new UnsupportedOperationException("Console API GET handler not implemented"); throw new UnsupportedOperationException("Console API GET handler not implemented");
} }

View File

@@ -36,7 +36,7 @@ import google.registry.ui.server.SendEmailUtils;
import google.registry.ui.server.console.ConsoleEppPasswordAction.EppPasswordData; import google.registry.ui.server.console.ConsoleEppPasswordAction.EppPasswordData;
import google.registry.ui.server.console.ConsoleOteAction.OteCreateData; import google.registry.ui.server.console.ConsoleOteAction.OteCreateData;
import google.registry.ui.server.console.ConsoleRegistryLockAction.ConsoleRegistryLockPostInput; import google.registry.ui.server.console.ConsoleRegistryLockAction.ConsoleRegistryLockPostInput;
import google.registry.ui.server.console.ConsoleUsersAction.UserDeleteData; import google.registry.ui.server.console.ConsoleUsersAction.UserData;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import java.util.Optional; import java.util.Optional;
import org.joda.time.DateTime; import org.joda.time.DateTime;
@@ -247,10 +247,10 @@ public final class ConsoleModule {
} }
@Provides @Provides
@Parameter("userDeleteData") @Parameter("userData")
public static Optional<UserDeleteData> provideUserDeleteData( public static Optional<UserData> provideUserData(
Gson gson, @OptionalJsonPayload Optional<JsonElement> payload) { Gson gson, @OptionalJsonPayload Optional<JsonElement> payload) {
return payload.map(s -> gson.fromJson(s, UserDeleteData.class)); return payload.map(s -> gson.fromJson(s, UserData.class));
} }
@Provides @Provides

View File

@@ -21,6 +21,7 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
import static google.registry.request.Action.Method.DELETE; import static google.registry.request.Action.Method.DELETE;
import static google.registry.request.Action.Method.GET; import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.POST; 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_CREATED;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN; 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_INTERNAL_SERVER_ERROR;
@@ -35,6 +36,7 @@ import com.google.gson.Gson;
import com.google.gson.annotations.Expose; import com.google.gson.annotations.Expose;
import google.registry.config.RegistryConfig.Config; import google.registry.config.RegistryConfig.Config;
import google.registry.model.console.ConsolePermission; import google.registry.model.console.ConsolePermission;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User; import google.registry.model.console.User;
import google.registry.model.console.UserRoles; import google.registry.model.console.UserRoles;
import google.registry.persistence.VKey; import google.registry.persistence.VKey;
@@ -50,6 +52,7 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
@@ -57,7 +60,7 @@ import javax.inject.Named;
service = Action.GaeService.DEFAULT, service = Action.GaeService.DEFAULT,
gkeService = GkeService.CONSOLE, gkeService = GkeService.CONSOLE,
path = ConsoleUsersAction.PATH, path = ConsoleUsersAction.PATH,
method = {GET, POST, DELETE}, method = {GET, POST, DELETE, PUT},
auth = Auth.AUTH_PUBLIC_LOGGED_IN) auth = Auth.AUTH_PUBLIC_LOGGED_IN)
public class ConsoleUsersAction extends ConsoleApiAction { public class ConsoleUsersAction extends ConsoleApiAction {
static final String PATH = "/console-api/users"; static final String PATH = "/console-api/users";
@@ -68,7 +71,7 @@ public class ConsoleUsersAction extends ConsoleApiAction {
private final String registrarId; private final String registrarId;
private final Directory directory; private final Directory directory;
private final StringGenerator passwordGenerator; private final StringGenerator passwordGenerator;
private final Optional<UserDeleteData> userDeleteData; private final Optional<UserData> userData;
private final Optional<String> maybeGroupEmailAddress; private final Optional<String> maybeGroupEmailAddress;
private final IamClient iamClient; private final IamClient iamClient;
private final String gSuiteDomainName; private final String gSuiteDomainName;
@@ -82,14 +85,14 @@ public class ConsoleUsersAction extends ConsoleApiAction {
@Config("gSuiteDomainName") String gSuiteDomainName, @Config("gSuiteDomainName") String gSuiteDomainName,
@Config("gSuiteConsoleUserGroupEmailAddress") Optional<String> maybeGroupEmailAddress, @Config("gSuiteConsoleUserGroupEmailAddress") Optional<String> maybeGroupEmailAddress,
@Named("base58StringGenerator") StringGenerator passwordGenerator, @Named("base58StringGenerator") StringGenerator passwordGenerator,
@Parameter("userDeleteData") Optional<UserDeleteData> userDeleteData, @Parameter("userData") Optional<UserData> userData,
@Parameter("registrarId") String registrarId) { @Parameter("registrarId") String registrarId) {
super(consoleApiParams); super(consoleApiParams);
this.gson = gson; this.gson = gson;
this.registrarId = registrarId; this.registrarId = registrarId;
this.directory = directory; this.directory = directory;
this.passwordGenerator = passwordGenerator; this.passwordGenerator = passwordGenerator;
this.userDeleteData = userDeleteData; this.userData = userData;
this.maybeGroupEmailAddress = maybeGroupEmailAddress; this.maybeGroupEmailAddress = maybeGroupEmailAddress;
this.iamClient = iamClient; this.iamClient = iamClient;
this.gSuiteDomainName = gSuiteDomainName; this.gSuiteDomainName = gSuiteDomainName;
@@ -106,18 +109,28 @@ public class ConsoleUsersAction extends ConsoleApiAction {
} }
} }
@Override
protected void putHandler(User user) {
// Temporary flag while testing
if (user.getUserRoles().isAdmin()) {
checkPermission(user, registrarId, ConsolePermission.MANAGE_USERS);
tm().transact(() -> runUpdateInTransaction());
} else {
consoleApiParams.response().setStatus(SC_FORBIDDEN);
}
}
@Override @Override
protected void getHandler(User user) { protected void getHandler(User user) {
checkPermission(user, registrarId, ConsolePermission.MANAGE_USERS); checkPermission(user, registrarId, ConsolePermission.MANAGE_USERS);
List<ImmutableMap> users = List<UserData> users =
getAllRegistrarUsers(registrarId).stream() getAllRegistrarUsers(registrarId).stream()
.map( .map(
u -> u ->
ImmutableMap.of( new UserData(
"emailAddress",
u.getEmailAddress(), u.getEmailAddress(),
"role", u.getUserRoles().getRegistrarRoles().get(registrarId).toString(),
u.getUserRoles().getRegistrarRoles().get(registrarId))) null))
.collect(Collectors.toList()); .collect(Collectors.toList());
consoleApiParams.response().setPayload(gson.toJson(users)); consoleApiParams.response().setPayload(gson.toJson(users));
@@ -136,22 +149,10 @@ public class ConsoleUsersAction extends ConsoleApiAction {
} }
private void runDeleteInTransaction() throws IOException { private void runDeleteInTransaction() throws IOException {
if (userDeleteData.isEmpty() || isNullOrEmpty(userDeleteData.get().userEmail)) { if (!isModifyingRequestValid()) {
throw new BadRequestException("Missing user data param");
}
String email = userDeleteData.get().userEmail;
User userToDelete =
tm().loadByKeyIfPresent(VKey.create(User.class, email))
.orElseThrow(
() -> new BadRequestException(String.format("User %s doesn't exist", email)));
if (!userToDelete.getUserRoles().getRegistrarRoles().containsKey(registrarId)) {
setFailedResponse(
String.format("Can't delete user not associated with registrarId %s", registrarId),
SC_FORBIDDEN);
return; return;
} }
String email = this.userData.get().emailAddress;
try { try {
directory.users().delete(email).execute(); directory.users().delete(email).execute();
} catch (IOException e) { } catch (IOException e) {
@@ -213,13 +214,50 @@ public class ConsoleUsersAction extends ConsoleApiAction {
.response() .response()
.setPayload( .setPayload(
gson.toJson( gson.toJson(
ImmutableMap.of( new UserData(
"password", newUser.getPrimaryEmail(), ACCOUNT_MANAGER.toString(), newUser.getPassword())));
newUser.getPassword(), }
"emailAddress",
newUser.getPrimaryEmail(), private void runUpdateInTransaction() {
"role", if (!isModifyingRequestValid()) {
ACCOUNT_MANAGER))); return;
}
UserData userData = this.userData.get();
UserRoles userRoles =
new UserRoles.Builder()
.setRegistrarRoles(ImmutableMap.of(registrarId, RegistrarRole.valueOf(userData.role)))
.build();
User updatedUser =
tm().loadByKeyIfPresent(VKey.create(User.class, userData.emailAddress))
.get()
.asBuilder()
.setUserRoles(userRoles)
.build();
tm().put(updatedUser);
consoleApiParams.response().setStatus(SC_OK);
}
private boolean isModifyingRequestValid() {
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 (!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 ImmutableList<User> getAllRegistrarUsers(String registrarId) { private ImmutableList<User> getAllRegistrarUsers(String registrarId) {
@@ -230,5 +268,6 @@ public class ConsoleUsersAction extends ConsoleApiAction {
.collect(toImmutableList())); .collect(toImmutableList()));
} }
public record UserDeleteData(@Expose String userEmail) {} public record UserData(
@Expose String emailAddress, @Expose String role, @Expose @Nullable String password) {}
} }

View File

@@ -44,7 +44,7 @@ import google.registry.testing.DatabaseHelper;
import google.registry.testing.DeterministicStringGenerator; import google.registry.testing.DeterministicStringGenerator;
import google.registry.testing.FakeResponse; import google.registry.testing.FakeResponse;
import google.registry.tools.IamClient; import google.registry.tools.IamClient;
import google.registry.ui.server.console.ConsoleUsersAction.UserDeleteData; import google.registry.ui.server.console.ConsoleUsersAction.UserData;
import google.registry.util.StringGenerator; import google.registry.util.StringGenerator;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
@@ -176,7 +176,7 @@ class ConsoleUsersActionTest {
assertThat(response.getStatus()).isEqualTo(SC_CREATED); assertThat(response.getStatus()).isEqualTo(SC_CREATED);
assertThat(response.getPayload()) assertThat(response.getPayload())
.contains( .contains(
"{\"password\":\"abcdefghijklmnop\",\"emailAddress\":\"TheRegistrar-user1@email.com\",\"role\":\"ACCOUNT_MANAGER\"}"); "{\"emailAddress\":\"TheRegistrar-user1@email.com\",\"role\":\"ACCOUNT_MANAGER\",\"password\":\"abcdefghijklmnop\"}");
} }
@Test @Test
@@ -192,14 +192,15 @@ class ConsoleUsersActionTest {
createAction( createAction(
Optional.of(ConsoleApiParamsUtils.createFake(authResult)), Optional.of(ConsoleApiParamsUtils.createFake(authResult)),
Optional.of("DELETE"), Optional.of("DELETE"),
Optional.of(new UserDeleteData("test3@test.com"))); Optional.of(
new UserData("test3@test.com", RegistrarRole.ACCOUNT_MANAGER.toString(), null)));
when(directory.users()).thenReturn(users); when(directory.users()).thenReturn(users);
when(users.delete(any(String.class))).thenReturn(delete); when(users.delete(any(String.class))).thenReturn(delete);
action.run(); action.run();
var response = ((FakeResponse) consoleApiParams.response()); var response = ((FakeResponse) consoleApiParams.response());
assertThat(response.getStatus()).isEqualTo(SC_FORBIDDEN); assertThat(response.getStatus()).isEqualTo(SC_FORBIDDEN);
assertThat(response.getPayload()) assertThat(response.getPayload())
.contains("Can't delete user not associated with registrarId TheRegistrar"); .contains("Can't update user not associated with registrarId TheRegistrar");
} }
@Test @Test
@@ -210,7 +211,8 @@ class ConsoleUsersActionTest {
createAction( createAction(
Optional.of(ConsoleApiParamsUtils.createFake(authResult)), Optional.of(ConsoleApiParamsUtils.createFake(authResult)),
Optional.of("DELETE"), Optional.of("DELETE"),
Optional.of(new UserDeleteData("email-1@email.com"))); Optional.of(
new UserData("email-1@email.com", RegistrarRole.ACCOUNT_MANAGER.toString(), null)));
when(directory.users()).thenReturn(users); when(directory.users()).thenReturn(users);
when(users.delete(any(String.class))).thenReturn(delete); when(users.delete(any(String.class))).thenReturn(delete);
action.run(); action.run();
@@ -232,7 +234,8 @@ class ConsoleUsersActionTest {
createAction( createAction(
Optional.of(ConsoleApiParamsUtils.createFake(authResult)), Optional.of(ConsoleApiParamsUtils.createFake(authResult)),
Optional.of("DELETE"), Optional.of("DELETE"),
Optional.of(new UserDeleteData("test2@test.com"))); Optional.of(
new UserData("test2@test.com", RegistrarRole.ACCOUNT_MANAGER.toString(), null)));
action.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils(); action.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
when(directory.users()).thenReturn(users); when(directory.users()).thenReturn(users);
when(users.delete(any(String.class))).thenReturn(delete); when(users.delete(any(String.class))).thenReturn(delete);
@@ -282,10 +285,66 @@ class ConsoleUsersActionTest {
assertThat(response.getPayload()).contains("Total users amount per registrar is limited to 4"); 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( private ConsoleUsersAction createAction(
Optional<ConsoleApiParams> maybeConsoleApiParams, Optional<ConsoleApiParams> maybeConsoleApiParams,
Optional<String> method, Optional<String> method,
Optional<UserDeleteData> userDeleteData) Optional<UserData> userData)
throws IOException { throws IOException {
consoleApiParams = consoleApiParams =
maybeConsoleApiParams.orElseGet( maybeConsoleApiParams.orElseGet(
@@ -299,7 +358,7 @@ class ConsoleUsersActionTest {
"email.com", "email.com",
Optional.of("someRandomString"), Optional.of("someRandomString"),
passwordGenerator, passwordGenerator,
userDeleteData, userData,
"TheRegistrar"); "TheRegistrar");
} }
} }

View File

@@ -1,16 +1,16 @@
SERVICE PATH CLASS METHODS OK MIN USER_POLICY SERVICE PATH CLASS METHODS OK MIN USER_POLICY
FRONTEND /_dr/epp EppTlsAction POST n APP ADMIN FRONTEND /_dr/epp EppTlsAction POST n APP ADMIN
CONSOLE /console-api/domain ConsoleDomainGetAction GET n USER PUBLIC CONSOLE /console-api/domain ConsoleDomainGetAction GET n USER PUBLIC
CONSOLE /console-api/domain-list ConsoleDomainListAction 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/dum-download ConsoleDumDownloadAction GET n USER PUBLIC
CONSOLE /console-api/eppPassword ConsoleEppPasswordAction POST 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/ote ConsoleOteAction GET,POST n USER PUBLIC
CONSOLE /console-api/registrar ConsoleUpdateRegistrarAction 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/registrars RegistrarsAction GET,POST n USER PUBLIC
CONSOLE /console-api/registry-lock ConsoleRegistryLockAction 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/registry-lock-verify ConsoleRegistryLockVerifyAction GET n USER PUBLIC
CONSOLE /console-api/settings/contacts ContactAction GET,POST 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/security SecurityAction POST n USER PUBLIC
CONSOLE /console-api/settings/whois-fields WhoisRegistrarFieldsAction 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/userdata ConsoleUserDataAction GET n USER PUBLIC
CONSOLE /console-api/users ConsoleUsersAction GET,POST,DELETE n USER PUBLIC CONSOLE /console-api/users ConsoleUsersAction GET,POST,DELETE,PUT n USER PUBLIC

View File

@@ -1,82 +1,82 @@
SERVICE PATH CLASS METHODS OK MIN USER_POLICY SERVICE PATH CLASS METHODS OK MIN USER_POLICY
FRONTEND /_dr/epp EppTlsAction POST n APP ADMIN FRONTEND /_dr/epp EppTlsAction POST n APP ADMIN
BACKEND /_dr/admin/createGroups CreateGroupsAction 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/domains ListDomainsAction GET,POST n APP ADMIN
BACKEND /_dr/admin/list/hosts ListHostsAction 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/premiumLists ListPremiumListsAction GET,POST n APP ADMIN
BACKEND /_dr/admin/list/registrars ListRegistrarsAction 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/reservedLists ListReservedListsAction GET,POST n APP ADMIN
BACKEND /_dr/admin/list/tlds ListTldsAction 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/updateUserGroup UpdateUserGroupAction POST n APP ADMIN
BACKEND /_dr/admin/verifyOte VerifyOteAction POST n APP ADMIN BACKEND /_dr/admin/verifyOte VerifyOteAction POST n APP ADMIN
BACKEND /_dr/cron/fanout TldFanoutAction GET y APP ADMIN BACKEND /_dr/cron/fanout TldFanoutAction GET y APP ADMIN
BACKEND /_dr/epptool EppToolAction POST n APP ADMIN BACKEND /_dr/epptool EppToolAction POST n APP ADMIN
BACKEND /_dr/loadtest LoadTestAction POST y APP ADMIN BACKEND /_dr/loadtest LoadTestAction POST y APP ADMIN
BACKEND /_dr/task/brdaCopy BrdaCopyAction 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/bsaDownload BsaDownloadAction GET,POST n APP ADMIN
BACKEND /_dr/task/bsaRefresh BsaRefreshAction 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/bsaValidate BsaValidateAction GET,POST n APP ADMIN
BACKEND /_dr/task/copyDetailReports CopyDetailReportsAction 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/deleteExpiredDomains DeleteExpiredDomainsAction GET n APP ADMIN
BACKEND /_dr/task/deleteLoadTestData DeleteLoadTestDataAction POST n APP ADMIN BACKEND /_dr/task/deleteLoadTestData DeleteLoadTestDataAction POST n APP ADMIN
BACKEND /_dr/task/deleteProberData DeleteProberDataAction 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/dnsRefresh RefreshDnsAction GET y APP ADMIN
BACKEND /_dr/task/executeCannedScript CannedScriptExecutionAction POST,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/expandBillingRecurrences ExpandBillingRecurrencesAction GET n APP ADMIN
BACKEND /_dr/task/exportDomainLists ExportDomainListsAction POST n APP ADMIN BACKEND /_dr/task/exportDomainLists ExportDomainListsAction POST n APP ADMIN
BACKEND /_dr/task/exportPremiumTerms ExportPremiumTermsAction 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/exportReservedTerms ExportReservedTermsAction POST n APP ADMIN
BACKEND /_dr/task/generateInvoices GenerateInvoicesAction 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/generateSpec11 GenerateSpec11ReportAction POST n APP ADMIN
BACKEND /_dr/task/generateZoneFiles GenerateZoneFilesAction 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/icannReportingStaging IcannReportingStagingAction POST n APP ADMIN
BACKEND /_dr/task/icannReportingUpload IcannReportingUploadAction 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/nordnUpload NordnUploadAction POST y APP ADMIN
BACKEND /_dr/task/nordnVerify NordnVerifyAction 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/publishDnsUpdates PublishDnsUpdatesAction POST y APP ADMIN
BACKEND /_dr/task/publishInvoices PublishInvoicesAction POST n APP ADMIN BACKEND /_dr/task/publishInvoices PublishInvoicesAction POST n APP ADMIN
BACKEND /_dr/task/publishSpec11 PublishSpec11ReportAction 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/rdeReport RdeReportAction POST n APP ADMIN
BACKEND /_dr/task/rdeStaging RdeStagingAction GET,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/rdeUpload RdeUploadAction POST n APP ADMIN
BACKEND /_dr/task/readDnsRefreshRequests ReadDnsRefreshRequestsAction POST y APP ADMIN BACKEND /_dr/task/readDnsRefreshRequests ReadDnsRefreshRequestsAction POST y APP ADMIN
BACKEND /_dr/task/refreshDnsForAllDomains RefreshDnsForAllDomainsAction GET n APP ADMIN BACKEND /_dr/task/refreshDnsForAllDomains RefreshDnsForAllDomainsAction GET n APP ADMIN
BACKEND /_dr/task/refreshDnsOnHostRename RefreshDnsOnHostRenameAction POST n APP ADMIN BACKEND /_dr/task/refreshDnsOnHostRename RefreshDnsOnHostRenameAction POST n APP ADMIN
BACKEND /_dr/task/relockDomain RelockDomainAction POST y APP ADMIN BACKEND /_dr/task/relockDomain RelockDomainAction POST y APP ADMIN
BACKEND /_dr/task/resaveAllEppResourcesPipeline ResaveAllEppResourcesPipelineAction GET n APP ADMIN BACKEND /_dr/task/resaveAllEppResourcesPipeline ResaveAllEppResourcesPipelineAction GET n APP ADMIN
BACKEND /_dr/task/resaveEntity ResaveEntityAction POST n APP ADMIN BACKEND /_dr/task/resaveEntity ResaveEntityAction POST n APP ADMIN
BACKEND /_dr/task/sendExpiringCertificateNotificationEmail SendExpiringCertificateNotificationEmailAction GET n APP ADMIN BACKEND /_dr/task/sendExpiringCertificateNotificationEmail SendExpiringCertificateNotificationEmailAction GET n APP ADMIN
BACKEND /_dr/task/syncGroupMembers SyncGroupMembersAction POST n APP ADMIN BACKEND /_dr/task/syncGroupMembers SyncGroupMembersAction POST n APP ADMIN
BACKEND /_dr/task/syncRegistrarsSheet SyncRegistrarsSheetAction 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/tmchCrl TmchCrlAction POST y APP ADMIN
BACKEND /_dr/task/tmchDnl TmchDnlAction 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/tmchSmdrl TmchSmdrlAction POST y APP ADMIN
BACKEND /_dr/task/updateRegistrarRdapBaseUrls UpdateRegistrarRdapBaseUrlsAction GET 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/uploadBsaUnavailableNames UploadBsaUnavailableDomainsAction GET,POST n APP ADMIN
BACKEND /_dr/task/wipeOutContactHistoryPii WipeOutContactHistoryPiiAction GET n APP ADMIN BACKEND /_dr/task/wipeOutContactHistoryPii WipeOutContactHistoryPiiAction GET n APP ADMIN
PUBAPI /_dr/whois WhoisAction POST n APP ADMIN PUBAPI /_dr/whois WhoisAction POST n APP ADMIN
PUBAPI /check CheckApiAction GET n NONE PUBLIC PUBAPI /check CheckApiAction GET n NONE PUBLIC
PUBAPI /rdap/autnum/(*) RdapAutnumAction GET,HEAD n NONE PUBLIC PUBAPI /rdap/autnum/(*) RdapAutnumAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/domain/(*) RdapDomainAction 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/domains RdapDomainSearchAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/entities RdapEntitySearchAction 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/entity/(*) RdapEntityAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/help(*) RdapHelpAction 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/ip/(*) RdapIpAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/nameserver/(*) RdapNameserverAction GET,HEAD n NONE PUBLIC PUBAPI /rdap/nameserver/(*) RdapNameserverAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/nameservers RdapNameserverSearchAction GET,HEAD n NONE PUBLIC PUBAPI /rdap/nameservers RdapNameserverSearchAction GET,HEAD n NONE PUBLIC
PUBAPI /whois/(*) WhoisHttpAction GET n NONE PUBLIC PUBAPI /whois/(*) WhoisHttpAction GET n NONE PUBLIC
CONSOLE /console-api/domain ConsoleDomainGetAction GET n USER PUBLIC CONSOLE /console-api/domain ConsoleDomainGetAction GET n USER PUBLIC
CONSOLE /console-api/domain-list ConsoleDomainListAction 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/dum-download ConsoleDumDownloadAction GET n USER PUBLIC
CONSOLE /console-api/eppPassword ConsoleEppPasswordAction POST 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/ote ConsoleOteAction GET,POST n USER PUBLIC
CONSOLE /console-api/registrar ConsoleUpdateRegistrarAction 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/registrars RegistrarsAction GET,POST n USER PUBLIC
CONSOLE /console-api/registry-lock ConsoleRegistryLockAction 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/registry-lock-verify ConsoleRegistryLockVerifyAction GET n USER PUBLIC
CONSOLE /console-api/settings/contacts ContactAction GET,POST 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/security SecurityAction POST n USER PUBLIC
CONSOLE /console-api/settings/whois-fields WhoisRegistrarFieldsAction 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/userdata ConsoleUserDataAction GET n USER PUBLIC
CONSOLE /console-api/users ConsoleUsersAction GET,POST,DELETE n USER PUBLIC CONSOLE /console-api/users ConsoleUsersAction GET,POST,DELETE,PUT n USER PUBLIC