mirror of
https://github.com/google/nomulus
synced 2025-12-23 14:25:44 +00:00
Add user email prefix to the console user create (#2623)
This commit is contained in:
@@ -11,31 +11,10 @@
|
|||||||
<mat-icon>arrow_back</mat-icon>
|
<mat-icon>arrow_back</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<form (ngSubmit)="saveEdit()">
|
<app-user-edit-form
|
||||||
<p>
|
[user]="userDetails()"
|
||||||
<mat-form-field appearance="outline">
|
(onEditComplete)="saveEdit($event)"
|
||||||
<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) {
|
} @else { @if(isNewUser) {
|
||||||
<h1 class="mat-headline-4">
|
<h1 class="mat-headline-4">
|
||||||
{{ userDetails().emailAddress + " successfully created" }}
|
{{ userDetails().emailAddress + " successfully created" }}
|
||||||
@@ -53,7 +32,7 @@
|
|||||||
mat-flat-button
|
mat-flat-button
|
||||||
color="primary"
|
color="primary"
|
||||||
aria-label="Edit User"
|
aria-label="Edit User"
|
||||||
(click)="userRole = userDetails().role; isEditing = true"
|
(click)="isEditing = true"
|
||||||
>
|
>
|
||||||
<mat-icon>edit</mat-icon>
|
<mat-icon>edit</mat-icon>
|
||||||
Edit
|
Edit
|
||||||
@@ -19,13 +19,14 @@ 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 { UsersService, roleToDescription } from './users.service';
|
import { UsersService, roleToDescription, User } from './users.service';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { UserEditFormComponent } from './userEditForm.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-user-edit',
|
selector: 'app-user-edit',
|
||||||
templateUrl: './userEdit.component.html',
|
templateUrl: './userDetails.component.html',
|
||||||
styleUrls: ['./userEdit.component.scss'],
|
styleUrls: ['./userDetails.component.scss'],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [
|
imports: [
|
||||||
FormsModule,
|
FormsModule,
|
||||||
@@ -33,15 +34,15 @@ import { FormsModule } from '@angular/forms';
|
|||||||
SnackBarModule,
|
SnackBarModule,
|
||||||
CommonModule,
|
CommonModule,
|
||||||
SelectedRegistrarModule,
|
SelectedRegistrarModule,
|
||||||
|
UserEditFormComponent,
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
})
|
})
|
||||||
export class UserEditComponent {
|
export class UserDetailsComponent {
|
||||||
isEditing = false;
|
isEditing = false;
|
||||||
isPasswordVisible = false;
|
isPasswordVisible = false;
|
||||||
isNewUser = false;
|
isNewUser = false;
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
userRole = '';
|
|
||||||
|
|
||||||
userDetails = computed(() => {
|
userDetails = computed(() => {
|
||||||
return this.usersService
|
return this.usersService
|
||||||
@@ -84,14 +85,9 @@ export class UserEditComponent {
|
|||||||
this.usersService.currentlyOpenUserEmail.set('');
|
this.usersService.currentlyOpenUserEmail.set('');
|
||||||
}
|
}
|
||||||
|
|
||||||
saveEdit() {
|
saveEdit(user: User) {
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
this.usersService
|
this.usersService.updateUser(user).subscribe({
|
||||||
.updateUser({
|
|
||||||
role: this.userRole,
|
|
||||||
emailAddress: this.userDetails().emailAddress,
|
|
||||||
})
|
|
||||||
.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;
|
||||||
39
console-webapp/src/app/users/userEditForm.component.html
Normal file
39
console-webapp/src/app/users/userEditForm.component.html
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<form (ngSubmit)="saveEdit($event)" #form>
|
||||||
|
<p *ngIf="isNew()">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label
|
||||||
|
>User name prefix:
|
||||||
|
<mat-icon
|
||||||
|
matTooltip="Prefix will be combined with registrar ID to create a unique user name - {prefix}.{registrarId}@registry.google"
|
||||||
|
>help_outline</mat-icon
|
||||||
|
></mat-label
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
minlength="3"
|
||||||
|
maxlength="3"
|
||||||
|
[required]="true"
|
||||||
|
[(ngModel)]="user().emailAddress"
|
||||||
|
[ngModelOptions]="{ standalone: true }"
|
||||||
|
/>
|
||||||
|
</mat-form-field>
|
||||||
|
</p>
|
||||||
|
<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)]="user().role" 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>
|
||||||
58
console-webapp/src/app/users/userEditForm.component.ts
Normal file
58
console-webapp/src/app/users/userEditForm.component.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
// 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,
|
||||||
|
ElementRef,
|
||||||
|
EventEmitter,
|
||||||
|
input,
|
||||||
|
Output,
|
||||||
|
ViewChild,
|
||||||
|
} from '@angular/core';
|
||||||
|
import { MaterialModule } from '../material.module';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { User } from './users.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-user-edit-form',
|
||||||
|
templateUrl: './userEditForm.component.html',
|
||||||
|
styleUrls: ['./userEditForm.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [FormsModule, MaterialModule, CommonModule],
|
||||||
|
providers: [],
|
||||||
|
})
|
||||||
|
export class UserEditFormComponent {
|
||||||
|
@ViewChild('form') form!: ElementRef;
|
||||||
|
isNew = input<boolean>(false);
|
||||||
|
user = input<User>(
|
||||||
|
{
|
||||||
|
emailAddress: '',
|
||||||
|
role: 'ACCOUNT_MANAGER',
|
||||||
|
},
|
||||||
|
// @ts-ignore - legit option, typescript fails to match it to a proper type
|
||||||
|
{ transform: (user: User) => structuredClone(user) }
|
||||||
|
);
|
||||||
|
|
||||||
|
@Output() onEditComplete = new EventEmitter<User>();
|
||||||
|
|
||||||
|
saveEdit(e: SubmitEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (this.form.nativeElement.checkValidity()) {
|
||||||
|
this.onEditComplete.emit(this.user());
|
||||||
|
} else {
|
||||||
|
this.form.nativeElement.reportValidity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,10 +4,8 @@
|
|||||||
<mat-spinner />
|
<mat-spinner />
|
||||||
</div>
|
</div>
|
||||||
} @else if(selectingExistingUser) {
|
} @else if(selectingExistingUser) {
|
||||||
|
|
||||||
<div class="console-app__users">
|
<div class="console-app__users">
|
||||||
<h1 class="mat-headline-4">Add existing user</h1>
|
<h1 class="mat-headline-4">Add existing user</h1>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<button
|
<button
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
@@ -61,6 +59,19 @@
|
|||||||
</div>
|
</div>
|
||||||
} @else if(usersService.currentlyOpenUserEmail()) {
|
} @else if(usersService.currentlyOpenUserEmail()) {
|
||||||
<app-user-edit></app-user-edit>
|
<app-user-edit></app-user-edit>
|
||||||
|
} @else if(isNew) {
|
||||||
|
<h1 class="mat-headline-4">New User Form</h1>
|
||||||
|
<div class="spacer"></div>
|
||||||
|
<p>
|
||||||
|
<button
|
||||||
|
mat-icon-button
|
||||||
|
aria-label="Back to users list"
|
||||||
|
(click)="isNew = false"
|
||||||
|
>
|
||||||
|
<mat-icon>arrow_back</mat-icon>
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
<app-user-edit-form [isNew]="true" (onEditComplete)="createNewUser($event)" />
|
||||||
} @else {
|
} @else {
|
||||||
<div class="console-app__users">
|
<div class="console-app__users">
|
||||||
<div class="console-app__users-header">
|
<div class="console-app__users-header">
|
||||||
@@ -79,11 +90,11 @@
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
mat-flat-button
|
mat-flat-button
|
||||||
(click)="createNewUser()"
|
(click)="isNew = true"
|
||||||
aria-label="Create new user"
|
aria-label="Create new user"
|
||||||
color="primary"
|
color="primary"
|
||||||
>
|
>
|
||||||
Create a Viewer User
|
Create New User
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,12 +20,13 @@ 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 { UserEditComponent } from './userEdit.component';
|
import { UserDetailsComponent } from './userDetails.component';
|
||||||
import { User, UsersService } from './users.service';
|
import { User, UsersService } from './users.service';
|
||||||
import { UserDataService } from '../shared/services/userData.service';
|
import { UserDataService } from '../shared/services/userData.service';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { UsersListComponent } from './usersList.component';
|
import { UsersListComponent } from './usersList.component';
|
||||||
import { MatSelectChange } from '@angular/material/select';
|
import { MatSelectChange } from '@angular/material/select';
|
||||||
|
import { UserEditFormComponent } from './userEditForm.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-users',
|
selector: 'app-users',
|
||||||
@@ -39,12 +40,14 @@ import { MatSelectChange } from '@angular/material/select';
|
|||||||
CommonModule,
|
CommonModule,
|
||||||
SelectedRegistrarModule,
|
SelectedRegistrarModule,
|
||||||
UsersListComponent,
|
UsersListComponent,
|
||||||
UserEditComponent,
|
UserEditFormComponent,
|
||||||
|
UserDetailsComponent,
|
||||||
],
|
],
|
||||||
providers: [UsersService],
|
providers: [UsersService],
|
||||||
})
|
})
|
||||||
export class UsersComponent {
|
export class UsersComponent {
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
|
isNew = false;
|
||||||
selectingExistingUser = false;
|
selectingExistingUser = false;
|
||||||
selectedRegistrarId = '';
|
selectedRegistrarId = '';
|
||||||
usersSelection: User[] = [];
|
usersSelection: User[] = [];
|
||||||
@@ -87,9 +90,9 @@ export class UsersComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
createNewUser() {
|
createNewUser(user: User) {
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
this.usersService.createOrAddNewUser(null).subscribe({
|
this.usersService.createOrAddNewUser(user).subscribe({
|
||||||
error: (err: HttpErrorResponse) => {
|
error: (err: HttpErrorResponse) => {
|
||||||
this._snackBar.open(err.error || err.message);
|
this._snackBar.open(err.error || err.message);
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
|
|||||||
@@ -60,9 +60,9 @@ export class UsersService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
createOrAddNewUser(maybeExistingUser: User | null) {
|
createOrAddNewUser(user: User) {
|
||||||
return this.backendService
|
return this.backendService
|
||||||
.createUser(this.registrarService.registrarId(), maybeExistingUser)
|
.createUser(this.registrarService.registrarId(), user)
|
||||||
.pipe(
|
.pipe(
|
||||||
tap((newUser: User) => {
|
tap((newUser: User) => {
|
||||||
if (newUser) {
|
if (newUser) {
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
|||||||
|
|
||||||
import com.google.api.services.directory.Directory;
|
import com.google.api.services.directory.Directory;
|
||||||
import com.google.api.services.directory.model.UserName;
|
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.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.gson.annotations.Expose;
|
import com.google.gson.annotations.Expose;
|
||||||
@@ -52,7 +51,6 @@ import java.util.Map;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.IntStream;
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
@@ -67,7 +65,6 @@ public class ConsoleUsersAction extends ConsoleApiAction {
|
|||||||
static final String PATH = "/console-api/users";
|
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 String registrarId;
|
private final String registrarId;
|
||||||
private final Directory directory;
|
private final Directory directory;
|
||||||
@@ -102,12 +99,7 @@ public class ConsoleUsersAction extends ConsoleApiAction {
|
|||||||
// Temporary flag while testing
|
// Temporary flag while testing
|
||||||
if (user.getUserRoles().isAdmin()) {
|
if (user.getUserRoles().isAdmin()) {
|
||||||
checkPermission(user, registrarId, ConsolePermission.MANAGE_USERS);
|
checkPermission(user, registrarId, ConsolePermission.MANAGE_USERS);
|
||||||
if (userData.isPresent()) { // Adding existing user to registrar
|
tm().transact(this::runPostInTransaction);
|
||||||
tm().transact(this::runAppendUserInTransaction);
|
|
||||||
} else { // Adding new user to registrar
|
|
||||||
tm().transact(this::runCreateInTransaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
consoleApiParams.response().setStatus(SC_FORBIDDEN);
|
consoleApiParams.response().setStatus(SC_FORBIDDEN);
|
||||||
}
|
}
|
||||||
@@ -152,10 +144,16 @@ public class ConsoleUsersAction extends ConsoleApiAction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void runAppendUserInTransaction() {
|
private void runPostInTransaction() throws IOException {
|
||||||
if (!isModifyingRequestValid(false)) {
|
validateRequestParams();
|
||||||
return;
|
if (!tm().exists(VKey.create(User.class, this.userData.get().emailAddress))) {
|
||||||
|
this.runCreate();
|
||||||
|
} else {
|
||||||
|
this.runAppendUserToExistingRegistrar();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runAppendUserToExistingRegistrar() {
|
||||||
ImmutableList<User> allRegistrarUsers = getAllRegistrarUsers(registrarId);
|
ImmutableList<User> allRegistrarUsers = getAllRegistrarUsers(registrarId);
|
||||||
if (allRegistrarUsers.size() >= 4) {
|
if (allRegistrarUsers.size() >= 4) {
|
||||||
throw new BadRequestException("Total users amount per registrar is limited to 4");
|
throw new BadRequestException("Total users amount per registrar is limited to 4");
|
||||||
@@ -164,20 +162,17 @@ public class ConsoleUsersAction extends ConsoleApiAction {
|
|||||||
updateUserRegistrarRoles(
|
updateUserRegistrarRoles(
|
||||||
this.userData.get().emailAddress,
|
this.userData.get().emailAddress,
|
||||||
registrarId,
|
registrarId,
|
||||||
RegistrarRole.valueOf(this.userData.get().role),
|
RegistrarRole.valueOf(this.userData.get().role));
|
||||||
false);
|
|
||||||
consoleApiParams.response().setStatus(SC_OK);
|
consoleApiParams.response().setStatus(SC_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void runDeleteInTransaction() throws IOException {
|
private void runDeleteInTransaction() throws IOException {
|
||||||
if (!isModifyingRequestValid(true)) {
|
if (!isModifyingRequestValid()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String email = this.userData.get().emailAddress;
|
String email = this.userData.get().emailAddress;
|
||||||
User updatedUser =
|
User updatedUser = updateUserRegistrarRoles(email, registrarId, null);
|
||||||
updateUserRegistrarRoles(
|
|
||||||
email, registrarId, RegistrarRole.valueOf(this.userData.get().role), true);
|
|
||||||
|
|
||||||
// User has no registrars assigned
|
// User has no registrars assigned
|
||||||
if (updatedUser.getUserRoles().getRegistrarRoles().size() == 0) {
|
if (updatedUser.getUserRoles().getRegistrarRoles().size() == 0) {
|
||||||
@@ -193,34 +188,33 @@ public class ConsoleUsersAction extends ConsoleApiAction {
|
|||||||
User.revokeIapPermission(email, maybeGroupEmailAddress, cloudTasksUtils, null, iamClient);
|
User.revokeIapPermission(email, maybeGroupEmailAddress, cloudTasksUtils, null, iamClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
consoleApiParams.response().setStatus(SC_OK);
|
consoleApiParams.response().setStatus(SC_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void runCreateInTransaction() throws IOException {
|
private void runCreate() throws IOException {
|
||||||
ImmutableList<User> allRegistrarUsers = getAllRegistrarUsers(registrarId);
|
ImmutableList<User> allRegistrarUsers = getAllRegistrarUsers(registrarId);
|
||||||
if (allRegistrarUsers.size() >= 4) {
|
if (allRegistrarUsers.size() >= 4) {
|
||||||
throw new BadRequestException("Total users amount per registrar is limited to 4");
|
throw new BadRequestException("Total users amount per registrar is limited to 4");
|
||||||
}
|
}
|
||||||
|
|
||||||
String nextAvailableEmail =
|
String newEmailPrefix = userData.get().emailAddress.trim();
|
||||||
IntStream.range(1, 5)
|
|
||||||
.mapToObj(i -> String.format("%s-user%s@%s", registrarId, i, gSuiteDomainName))
|
if (!newEmailPrefix.matches("^[a-zA-Z0-9]{3}$")) {
|
||||||
.filter(email -> tm().loadByKeyIfPresent(VKey.create(User.class, email)).isEmpty())
|
throw new BadRequestException("Email prefix is invalid");
|
||||||
.findFirst()
|
}
|
||||||
// Can only happen if registrar cycled through 20 users, which is unlikely
|
|
||||||
.orElseThrow(
|
String newEmail = String.format("%s.%s@%s", newEmailPrefix, registrarId, gSuiteDomainName);
|
||||||
() -> new BadRequestException("Failed to find available increment for new user"));
|
if (tm().loadByKeyIfPresent(VKey.create(User.class, newEmail)).isPresent()) {
|
||||||
|
throw new BadRequestException("Email prefix is not available");
|
||||||
|
}
|
||||||
|
|
||||||
com.google.api.services.directory.model.User newUser =
|
com.google.api.services.directory.model.User newUser =
|
||||||
new com.google.api.services.directory.model.User();
|
new com.google.api.services.directory.model.User();
|
||||||
|
|
||||||
newUser.setName(
|
newUser.setName(
|
||||||
new UserName()
|
new UserName().setFamilyName(registrarId).setGivenName(newEmailPrefix + "." + registrarId));
|
||||||
.setFamilyName(registrarId)
|
|
||||||
.setGivenName(EMAIL_SPLITTER.splitToList(nextAvailableEmail).get(0)));
|
|
||||||
newUser.setPassword(passwordGenerator.createString(PASSWORD_LENGTH));
|
newUser.setPassword(passwordGenerator.createString(PASSWORD_LENGTH));
|
||||||
newUser.setPrimaryEmail(nextAvailableEmail);
|
newUser.setPrimaryEmail(newEmail);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
directory.users().insert(newUser).execute();
|
directory.users().insert(newUser).execute();
|
||||||
@@ -234,11 +228,9 @@ public class ConsoleUsersAction extends ConsoleApiAction {
|
|||||||
.setRegistrarRoles(ImmutableMap.of(registrarId, ACCOUNT_MANAGER))
|
.setRegistrarRoles(ImmutableMap.of(registrarId, ACCOUNT_MANAGER))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
User.Builder builder =
|
User.Builder builder = new User.Builder().setUserRoles(userRoles).setEmailAddress(newEmail);
|
||||||
new User.Builder().setUserRoles(userRoles).setEmailAddress(newUser.getPrimaryEmail());
|
|
||||||
tm().put(builder.build());
|
tm().put(builder.build());
|
||||||
User.grantIapPermission(
|
User.grantIapPermission(newEmail, maybeGroupEmailAddress, cloudTasksUtils, null, iamClient);
|
||||||
nextAvailableEmail, maybeGroupEmailAddress, cloudTasksUtils, null, iamClient);
|
|
||||||
|
|
||||||
consoleApiParams.response().setStatus(SC_CREATED);
|
consoleApiParams.response().setStatus(SC_CREATED);
|
||||||
consoleApiParams
|
consoleApiParams
|
||||||
@@ -246,72 +238,69 @@ public class ConsoleUsersAction extends ConsoleApiAction {
|
|||||||
.setPayload(
|
.setPayload(
|
||||||
consoleApiParams
|
consoleApiParams
|
||||||
.gson()
|
.gson()
|
||||||
.toJson(
|
.toJson(new UserData(newEmail, ACCOUNT_MANAGER.toString(), newUser.getPassword())));
|
||||||
new UserData(
|
|
||||||
newUser.getPrimaryEmail(),
|
|
||||||
ACCOUNT_MANAGER.toString(),
|
|
||||||
newUser.getPassword())));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void runUpdateInTransaction() {
|
private void runUpdateInTransaction() {
|
||||||
if (!isModifyingRequestValid(true)) {
|
if (!isModifyingRequestValid()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateUserRegistrarRoles(
|
updateUserRegistrarRoles(
|
||||||
this.userData.get().emailAddress,
|
this.userData.get().emailAddress,
|
||||||
registrarId,
|
registrarId,
|
||||||
RegistrarRole.valueOf(this.userData.get().role),
|
RegistrarRole.valueOf(this.userData.get().role));
|
||||||
false);
|
|
||||||
consoleApiParams.response().setStatus(SC_OK);
|
consoleApiParams.response().setStatus(SC_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isModifyingRequestValid(boolean verifyAccess) {
|
private boolean isModifyingRequestValid() {
|
||||||
|
validateRequestParams();
|
||||||
|
User userToUpdate = verifyUserExists(userData.get().emailAddress);
|
||||||
|
return validateUserRegistrarAssociation(userToUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateRequestParams() {
|
||||||
if (userData.isEmpty()
|
if (userData.isEmpty()
|
||||||
|| isNullOrEmpty(userData.get().emailAddress)
|
|| isNullOrEmpty(userData.get().emailAddress)
|
||||||
|| isNullOrEmpty(userData.get().role)) {
|
|| isNullOrEmpty(userData.get().role)) {
|
||||||
throw new BadRequestException("User data is missing or incomplete");
|
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)) {
|
private User verifyUserExists(String email) {
|
||||||
|
return tm().loadByKeyIfPresent(VKey.create(User.class, email))
|
||||||
|
.orElseThrow(() -> new BadRequestException(String.format("User %s doesn't exist", email)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean validateUserRegistrarAssociation(User user) {
|
||||||
|
if (user.getUserRoles().getRegistrarRoles().containsKey(registrarId)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
setFailedResponse(
|
setFailedResponse(
|
||||||
String.format("Can't update user not associated with registrarId %s", registrarId),
|
String.format("Can't update user not associated with registrarId %s", registrarId),
|
||||||
SC_FORBIDDEN);
|
SC_FORBIDDEN);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private User updateUserRegistrarRoles(
|
private User updateUserRegistrarRoles(String email, String registrarId, RegistrarRole newRole) {
|
||||||
String email, String registrarId, RegistrarRole newRole, boolean isDelete) {
|
|
||||||
User userToUpdate = tm().loadByKeyIfPresent(VKey.create(User.class, email)).get();
|
|
||||||
Map<String, RegistrarRole> updatedRegistrarRoles;
|
Map<String, RegistrarRole> updatedRegistrarRoles;
|
||||||
if (isDelete) {
|
User user = verifyUserExists(email);
|
||||||
|
if (newRole == null) {
|
||||||
updatedRegistrarRoles =
|
updatedRegistrarRoles =
|
||||||
userToUpdate.getUserRoles().getRegistrarRoles().entrySet().stream()
|
user.getUserRoles().getRegistrarRoles().entrySet().stream()
|
||||||
.filter(entry -> !Objects.equals(entry.getKey(), registrarId))
|
.filter(entry -> !Objects.equals(entry.getKey(), registrarId))
|
||||||
.collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue));
|
.collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||||
} else {
|
} else {
|
||||||
updatedRegistrarRoles =
|
updatedRegistrarRoles =
|
||||||
ImmutableMap.<String, RegistrarRole>builder()
|
ImmutableMap.<String, RegistrarRole>builder()
|
||||||
.putAll(userToUpdate.getUserRoles().getRegistrarRoles())
|
.putAll(user.getUserRoles().getRegistrarRoles())
|
||||||
.put(registrarId, newRole)
|
.put(registrarId, newRole)
|
||||||
.buildKeepingLast();
|
.buildKeepingLast();
|
||||||
}
|
}
|
||||||
var updatedUser =
|
var updatedUser =
|
||||||
userToUpdate
|
user.asBuilder()
|
||||||
.asBuilder()
|
|
||||||
.setUserRoles(
|
.setUserRoles(
|
||||||
userToUpdate
|
user.getUserRoles().asBuilder().setRegistrarRoles(updatedRegistrarRoles).build())
|
||||||
.getUserRoles()
|
|
||||||
.asBuilder()
|
|
||||||
.setRegistrarRoles(updatedRegistrarRoles)
|
|
||||||
.build())
|
|
||||||
.build();
|
.build();
|
||||||
tm().put(updatedUser);
|
tm().put(updatedUser);
|
||||||
return updatedUser;
|
return updatedUser;
|
||||||
|
|||||||
@@ -159,6 +159,24 @@ class ConsoleUsersActionTest {
|
|||||||
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN);
|
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFailure_invalidPrefix() throws IOException {
|
||||||
|
User user = DatabaseHelper.createAdminUser("email@email.com");
|
||||||
|
AuthResult authResult = AuthResult.createUser(user);
|
||||||
|
ConsoleUsersAction action =
|
||||||
|
createAction(
|
||||||
|
Optional.of(ConsoleApiParamsUtils.createFake(authResult)),
|
||||||
|
Optional.of("POST"),
|
||||||
|
Optional.of(new UserData("a@d", RegistrarRole.ACCOUNT_MANAGER.toString(), null)));
|
||||||
|
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("Email prefix is invalid");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSuccess_createsUser() throws IOException {
|
void testSuccess_createsUser() throws IOException {
|
||||||
User user = DatabaseHelper.createAdminUser("email@email.com");
|
User user = DatabaseHelper.createAdminUser("email@email.com");
|
||||||
@@ -167,7 +185,7 @@ class ConsoleUsersActionTest {
|
|||||||
createAction(
|
createAction(
|
||||||
Optional.of(ConsoleApiParamsUtils.createFake(authResult)),
|
Optional.of(ConsoleApiParamsUtils.createFake(authResult)),
|
||||||
Optional.of("POST"),
|
Optional.of("POST"),
|
||||||
Optional.empty());
|
Optional.of(new UserData("lol", 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.insert(any(com.google.api.services.directory.model.User.class))).thenReturn(insert);
|
when(users.insert(any(com.google.api.services.directory.model.User.class))).thenReturn(insert);
|
||||||
@@ -176,7 +194,7 @@ class ConsoleUsersActionTest {
|
|||||||
assertThat(response.getStatus()).isEqualTo(SC_CREATED);
|
assertThat(response.getStatus()).isEqualTo(SC_CREATED);
|
||||||
assertThat(response.getPayload())
|
assertThat(response.getPayload())
|
||||||
.contains(
|
.contains(
|
||||||
"{\"emailAddress\":\"TheRegistrar-user1@email.com\",\"role\":\"ACCOUNT_MANAGER\",\"password\":\"abcdefghijklmnop\"}");
|
"{\"emailAddress\":\"lol.TheRegistrar@email.com\",\"role\":\"ACCOUNT_MANAGER\",\"password\":\"abcdefghijklmnop\"}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -319,7 +337,8 @@ class ConsoleUsersActionTest {
|
|||||||
createAction(
|
createAction(
|
||||||
Optional.of(ConsoleApiParamsUtils.createFake(authResult)),
|
Optional.of(ConsoleApiParamsUtils.createFake(authResult)),
|
||||||
Optional.of("POST"),
|
Optional.of("POST"),
|
||||||
Optional.empty());
|
Optional.of(
|
||||||
|
new UserData("test3@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.insert(any(com.google.api.services.directory.model.User.class))).thenReturn(insert);
|
when(users.insert(any(com.google.api.services.directory.model.User.class))).thenReturn(insert);
|
||||||
|
|||||||
Reference in New Issue
Block a user