1
0
mirror of https://github.com/google/nomulus synced 2026-07-19 22:42:23 +00:00

Compare commits

..

13 Commits

Author SHA1 Message Date
Pavlo Tkach d0d28cc7e6 Fix console contact delete button not working (#2528) 2024-08-09 16:42:39 +00:00
Pavlo Tkach 2d1260c01b Allow updating icannReferralEmail through the new console ui (#2525) 2024-08-07 16:28:08 +00:00
Pavlo Tkach 06da6a2cc6 Make ContactActionTest deterministic for stop fail under new Hibernate (#2524) 2024-08-07 13:37:13 +00:00
Lai Jiang 858a22f82e Delete a duplicate resource file (#2522)
It already exists under the resources folder.
2024-08-06 18:42:29 +00:00
gbrodman 3c126ddfd4 Remove ID field from User in Java classes and remove UserDao (#2517)
This is the first step in the field removal (second will be removing the
column from SQL once this is deployed).

There's no point in using a UserDao versus just doing the standard
loading-from-DB that we do everywhere else. No need to special-case it.
2024-08-05 20:36:17 +00:00
gbrodman 2b98e6f177 Add deprecation message to old console (#2516) 2024-08-02 15:59:08 +00:00
gbrodman 20036b6a74 Fix wording on registry lock verification (#2518) 2024-08-01 20:17:46 +00:00
Lai Jiang 396cbd6bd3 Remove login_email_address from RegistrarPoc (part 2) (#2510)
Remove the field from the schema.
2024-08-01 17:07:03 +00:00
Lai Jiang 71ea16ff69 Call Workspace Groups API directly from nomulus tool (#2515)
When creating/deleting users, we need to add/remove the emails in
question to/from the console email group (if it exists). This used to be
done synchronously by calling the Groups API directly from the nomulus
tool. However #2488 made it so that in all cases where group membership
is modified, a Cloud Tasks task is created to execute the change on
the server side asynchronously (because there are multiple places where
this change needs to be done, and it is easier to make it all happen on the
server side).

Alas, as it turns out, Cloud Tasks tasks need to be created with a
service account's credential (which is trivially done on the server side
because the ADC is a service account). Nomulus command runs with a user
credential, and we need to grant the relevant user permission to
masquerade as a service account, in order to enqueue tasks from the
nomulus tool. It is therefore easier to just revert to the old behavior.
2024-08-01 15:29:57 +00:00
Pavlo Tkach 45331be166 Add redirect to the new console from the old console for tech support (#2514) 2024-07-31 17:16:12 +00:00
gbrodman beb7c14adb Drop not-null constraint on UserUpdateHistory:user_id (#2513)
Dependency Submission / dependency-submission (push) Successful in 3m55s
CodeQL / Analyze (java) (push) Failing after 3m42s
CodeQL / Analyze (javascript) (push) Failing after 52s
CodeQL / Analyze (python) (push) Failing after 50s
This is nullable now that we're switching from using an ID field to
using the email address as the primary identifier.
2024-07-30 19:19:29 +00:00
gbrodman d33571dde3 Change pkey of User to emailAddress (#2505)
CodeQL / Analyze (java) (push) Failing after 1m22s
CodeQL / Analyze (javascript) (push) Failing after 1m13s
CodeQL / Analyze (python) (push) Failing after 51s
Dependency Submission / dependency-submission (push) Successful in 2m11s
Originally, we though that User entities were going to have mutable
email addresses, and thus would require a non-changing primary key. This
proved to not be the case. It'll simplify the User loading/saving code
if we just do everything by email address.

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

* Run feature flag query in a transaction

---------

Co-authored-by: ptkach <ptkach@google.com>
2024-07-29 21:55:12 +00:00
94 changed files with 1302 additions and 1226 deletions
@@ -18,7 +18,11 @@
<mat-icon>edit</mat-icon>
Edit
</button>
<button mat-icon-button aria-label="Delete Contact">
<button
mat-icon-button
aria-label="Delete Contact"
(click)="deleteContact()"
>
<mat-icon>delete</mat-icon>
</button>
}
@@ -50,6 +50,9 @@ export class ContactDetailsComponent {
error: (err: HttpErrorResponse) => {
this._snackBar.open(err.error);
},
complete: () => {
this.goBack();
},
});
}
}
@@ -132,6 +132,18 @@
/>
</mat-form-field>
@if((userDataService.userData()?.globalRole || 'NONE') !== "NONE") {
<mat-form-field appearance="outline">
<mat-label>ICANN Referral Email: </mat-label>
<input
matInput
type="text"
[(ngModel)]="registrarInEdit.icannReferralEmail"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
}
<button mat-flat-button color="primary" type="submit">Save</button>
</form>
</div>
@@ -19,6 +19,7 @@ import {
Registrar,
RegistrarService,
} from 'src/app/registrar/registrar.service';
import { UserDataService } from 'src/app/shared/services/userData.service';
import { WhoisService } from './whois.service';
@Component({
@@ -30,6 +31,7 @@ export default class WhoisEditComponent {
registrarInEdit: Registrar | undefined;
constructor(
public userDataService: UserDataService,
public whoisService: WhoisService,
public registrarService: RegistrarService,
private _snackBar: MatSnackBar
@@ -77,6 +77,7 @@ public class CloudTasksUtils implements Serializable {
@Config("projectId") String projectId,
@Config("locationId") String locationId,
@Config("oauthClientId") String oauthClientId,
// Note that this has to be a service account, due to limitations of the Cloud Tasks API.
@ApplicationDefaultCredential GoogleCredentialsBundle credential,
SerializableCloudTasksClient client) {
this.retrier = retrier;
@@ -1,17 +0,0 @@
<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%</domain:name>
</domain:delete>
</delete>
<extension>
<metadata:metadata xmlns:metadata="urn:google:params:xml:ns:metadata-1.0">
<metadata:reason>Non-renewing domain has reached expiration date.</metadata:reason>
<metadata:requestedByRegistrar>false</metadata:requestedByRegistrar>
</metadata:metadata>
</extension>
<clTRID>ABC-12345</clTRID>
</command>
</epp>
@@ -32,7 +32,6 @@ import com.google.common.collect.Streams;
import google.registry.batch.CloudTasksUtils;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.model.console.UserRoles;
import google.registry.model.pricing.StaticPremiumListPricingEngine;
import google.registry.model.registrar.Registrar;
@@ -48,9 +47,7 @@ import google.registry.util.CidrAddressBlock;
import google.registry.util.RegistryEnvironment;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.regex.Pattern;
@@ -271,29 +268,21 @@ public final class OteAccountBuilder {
Optional<String> groupEmailAddress, CloudTasksUtils cloudTasksUtils, IamClient iamClient) {
for (User user : users) {
User.grantIapPermission(
user.getEmailAddress(), groupEmailAddress, cloudTasksUtils, iamClient);
user.getEmailAddress(), groupEmailAddress, cloudTasksUtils, null, iamClient);
}
}
/** Saves all the OT&amp;E entities we created. */
private void saveAllEntities() {
ImmutableList<Tld> registries = ImmutableList.of(sunriseTld, gaTld, eapTld);
Map<String, User> existingUsers = new HashMap<>();
users.forEach(
user ->
UserDao.loadUser(user.getEmailAddress())
.ifPresent(
existingUser ->
existingUsers.put(existingUser.getEmailAddress(), existingUser)));
if (!replaceExisting) {
checkState(existingUsers.isEmpty(), "Found existing users: %s", existingUsers);
}
tm().transact(
() -> {
if (!replaceExisting) {
ImmutableMap<String, User> existingUsers =
tm().loadByEntitiesIfPresent(users).stream()
.collect(toImmutableMap(User::getEmailAddress, u -> u));
checkState(existingUsers.isEmpty(), "Found existing users: %s", existingUsers);
ImmutableList<VKey<? extends ImmutableObject>> keys =
Streams.concat(
registries.stream().map(tld -> Tld.createVKey(tld.getTldStr())),
@@ -317,16 +306,7 @@ public final class OteAccountBuilder {
tm().putAll(registrars);
});
for (User user : users) {
String email = user.getEmailAddress();
if (existingUsers.containsKey(email)) {
// Note that other roles for the existing user are reset. We do this instead of simply
// saving the new user is that UserDao does not allow us to save the new user with the same
// email as the existing user.
user = existingUsers.get(email).asBuilder().setUserRoles(user.getUserRoles()).build();
}
UserDao.saveUser(user);
}
tm().transact(() -> tm().putAll(ImmutableList.copyOf(users)));
}
private Registrar addAllowedTld(Registrar registrar) {
@@ -63,7 +63,8 @@ public class FeatureFlag extends ImmutableObject implements Buildable {
public enum FeatureName {
TEST_FEATURE,
MINIMUM_DATASET_CONTACTS_OPTIONAL,
MINIMUM_DATASET_CONTACTS_PROHIBITED
MINIMUM_DATASET_CONTACTS_PROHIBITED,
NEW_CONSOLE
}
/** The name of the flag/feature. */
@@ -14,33 +14,36 @@
package google.registry.model.console;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.tools.server.UpdateUserGroupAction.GROUP_UPDATE_QUEUE;
import com.google.cloud.tasks.v2.Task;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType;
import google.registry.batch.CloudTasksUtils;
import google.registry.persistence.VKey;
import google.registry.request.Action.Service;
import google.registry.tools.IamClient;
import google.registry.tools.ServiceConnection;
import google.registry.tools.server.UpdateUserGroupAction;
import google.registry.tools.server.UpdateUserGroupAction.Mode;
import google.registry.util.RegistryEnvironment;
import java.io.IOException;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.Table;
/** A console user, either a registry employee or a registrar partner. */
@Embeddable
@Entity
@Table(indexes = {@Index(columnList = "emailAddress", name = "user_email_address_idx")})
@Table
public class User extends UserBase {
public static final String IAP_SECURED_WEB_APP_USER_ROLE = "roles/iap.httpsResourceAccessor";
@@ -55,18 +58,30 @@ public class User extends UserBase {
public static void grantIapPermission(
String emailAddress,
Optional<String> groupEmailAddress,
CloudTasksUtils cloudTasksUtils,
@Nullable CloudTasksUtils cloudTasksUtils,
@Nullable ServiceConnection connection,
IamClient iamClient) {
if (RegistryEnvironment.isInTestServer()) {
return;
}
checkArgument(
cloudTasksUtils != null || connection != null,
"At least one of cloudTasksUtils or connection must be set");
checkArgument(
cloudTasksUtils == null || connection == null,
"Only one of cloudTasksUtils or connection can be set");
if (groupEmailAddress.isEmpty()) {
logger.atInfo().log("Granting IAP role to user %s", emailAddress);
iamClient.addBinding(emailAddress, IAP_SECURED_WEB_APP_USER_ROLE);
} else {
logger.atInfo().log("Adding %s to group %s", emailAddress, groupEmailAddress.get());
modifyGroupMembershipAsync(
emailAddress, groupEmailAddress.get(), cloudTasksUtils, UpdateUserGroupAction.Mode.ADD);
if (cloudTasksUtils != null) {
modifyGroupMembershipAsync(
emailAddress, groupEmailAddress.get(), cloudTasksUtils, UpdateUserGroupAction.Mode.ADD);
} else {
modifyGroupMembershipSync(
emailAddress, groupEmailAddress.get(), connection, UpdateUserGroupAction.Mode.ADD);
}
}
}
@@ -79,18 +94,29 @@ public class User extends UserBase {
public static void revokeIapPermission(
String emailAddress,
Optional<String> groupEmailAddress,
CloudTasksUtils cloudTasksUtils,
@Nullable CloudTasksUtils cloudTasksUtils,
@Nullable ServiceConnection connection,
IamClient iamClient) {
if (RegistryEnvironment.isInTestServer()) {
return;
}
checkArgument(
cloudTasksUtils != null || connection != null,
"At least one of cloudTasksUtils or connection must be set");
checkArgument(
cloudTasksUtils == null || connection == null,
"Only one of cloudTasksUtils or connection can be set");
if (groupEmailAddress.isEmpty()) {
logger.atInfo().log("Removing IAP role from user %s", emailAddress);
iamClient.removeBinding(emailAddress, IAP_SECURED_WEB_APP_USER_ROLE);
} else {
logger.atInfo().log("Removing %s from group %s", emailAddress, groupEmailAddress.get());
modifyGroupMembershipAsync(
emailAddress, groupEmailAddress.get(), cloudTasksUtils, Mode.REMOVE);
if (cloudTasksUtils != null) {
modifyGroupMembershipAsync(
emailAddress, groupEmailAddress.get(), cloudTasksUtils, Mode.REMOVE);
} else {
modifyGroupMembershipSync(emailAddress, groupEmailAddress.get(), connection, Mode.REMOVE);
}
}
}
@@ -113,12 +139,30 @@ public class User extends UserBase {
cloudTasksUtils.enqueue(GROUP_UPDATE_QUEUE, task);
}
@Override
private static void modifyGroupMembershipSync(
String userEmailAddress, String groupEmailAddress, ServiceConnection connection, Mode mode) {
try {
connection.sendPostRequest(
UpdateUserGroupAction.PATH,
ImmutableMap.of(
"userEmailAddress",
userEmailAddress,
"groupEmailAddress",
groupEmailAddress,
"groupUpdateMode",
mode.name()),
MediaType.PLAIN_TEXT_UTF_8,
new byte[0]);
} catch (IOException e) {
throw new RuntimeException("Cannot send request to server", e);
}
}
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Override
@Access(AccessType.PROPERTY)
public Long getId() {
return super.getId();
public String getEmailAddress() {
return super.getEmailAddress();
}
@Override
@@ -128,7 +172,7 @@ public class User extends UserBase {
@Override
public VKey<User> createVKey() {
return VKey.create(User.class, getId());
return VKey.create(User.class, getEmailAddress());
}
/** Builder for constructing immutable {@link User} objects. */
@@ -49,12 +49,8 @@ public class UserBase extends UpdateAutoTimestampEntity implements Buildable {
private static final long serialVersionUID = 6936728603828566721L;
/** Autogenerated unique ID of this user. */
@Transient private Long id;
/** Email address of the user in question. */
@Column(nullable = false)
String emailAddress;
@Transient String emailAddress;
/** Optional external email address to use for registry lock confirmation emails. */
@Column String registryLockEmailAddress;
@@ -72,22 +68,17 @@ public class UserBase extends UpdateAutoTimestampEntity implements Buildable {
/** Randomly generated hash salt. */
String registryLockPasswordSalt;
public Long getId() {
return id;
}
/**
* Sets the user ID.
* Sets the user email address.
*
* <p>This should only be used for restoring the user id of an object being loaded in a PostLoad
* method (effectively, when it is still under construction by Hibernate). In all other cases, the
* object should be regarded as immutable and changes should go through a Builder.
* <p>This should only be used for restoring an object being loaded in a PostLoad method
* (effectively, when it is still under construction by Hibernate). In all other cases, the object
* should be regarded as immutable and changes should go through a Builder.
*
* <p>In addition to this special case use, this method must exist to satisfy Hibernate.
*/
@SuppressWarnings("unused")
void setId(Long id) {
this.id = id;
void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
public String getEmailAddress() {
@@ -1,54 +0,0 @@
// Copyright 2022 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.model.console;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import java.util.Optional;
/** Data access object for {@link User} objects to simplify saving and retrieval. */
public class UserDao {
/** Retrieves the one user with this email address if it exists. */
public static Optional<User> loadUser(String emailAddress) {
return tm().transact(
() ->
tm().query("FROM User WHERE emailAddress = :emailAddress", User.class)
.setParameter("emailAddress", emailAddress)
.getResultStream()
.findFirst());
}
/** Saves the given user, checking that no existing user already exists with this email. */
public static void saveUser(User user) {
tm().transact(
() -> {
// Check for an existing user (the unique constraint protects us, but this gives a
// nicer exception)
Optional<User> maybeSavedUser = loadUser(user.getEmailAddress());
if (maybeSavedUser.isPresent()) {
User savedUser = maybeSavedUser.get();
checkArgument(
savedUser.getId().equals(user.getId()),
String.format(
"Attempted save of User with email address %s and ID %s, user with that"
+ " email already exists with ID %s",
user.getEmailAddress(), user.getId(), savedUser.getId()));
}
tm().put(user);
});
}
}
@@ -38,9 +38,8 @@ public class UserUpdateHistory extends ConsoleUpdateHistory {
UserBase user;
// This field exists so that it's populated in the SQL table
@Column(nullable = false, name = "userId")
Long id;
@Column(nullable = false, name = "emailAddress")
String emailAddress;
public UserBase getUser() {
return user;
@@ -48,7 +47,7 @@ public class UserUpdateHistory extends ConsoleUpdateHistory {
@PostLoad
void postLoad() {
user.setId(id);
user.setEmailAddress(emailAddress);
}
/** Creates a {@link VKey} instance for this entity. */
@@ -79,7 +78,7 @@ public class UserUpdateHistory extends ConsoleUpdateHistory {
public Builder setUser(User user) {
getInstance().user = user;
getInstance().id = user.getId();
getInstance().emailAddress = user.getEmailAddress();
return this;
}
}
@@ -15,6 +15,7 @@
package google.registry.request.auth;
import static com.google.common.base.Preconditions.checkState;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.api.client.json.webtoken.JsonWebSignature;
import com.google.auth.oauth2.TokenVerifier;
@@ -23,7 +24,7 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.persistence.VKey;
import google.registry.request.auth.AuthModule.IapOidc;
import google.registry.request.auth.AuthModule.RegularOidc;
import google.registry.request.auth.AuthModule.RegularOidcFallback;
@@ -117,7 +118,8 @@ public abstract class OidcTokenAuthenticationMechanism implements Authentication
logger.atWarning().log("No email address from the OIDC token:\n%s", token.getPayload());
return AuthResult.NOT_AUTHENTICATED;
}
Optional<User> maybeUser = UserDao.loadUser(email);
Optional<User> maybeUser =
tm().transact(() -> tm().loadByKeyIfPresent(VKey.create(User.class, email)));
if (maybeUser.isPresent()) {
return AuthResult.createUser(maybeUser.get());
}
@@ -22,7 +22,6 @@ import com.google.common.collect.ImmutableMap;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.model.console.UserRoles;
import google.registry.tools.params.KeyValueMapParameter.StringToRegistrarRoleMap;
import java.util.Optional;
@@ -95,8 +94,6 @@ public abstract class CreateOrUpdateUserCommand extends ConfirmingCommand {
builder.setRegistryLockEmailAddress(registryLockEmailAddress);
}
}
User newUser = builder.build();
UserDao.saveUser(newUser);
tm().put(builder.build());
}
}
@@ -16,24 +16,24 @@ package google.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.model.console.User.grantIapPermission;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.beust.jcommander.Parameters;
import google.registry.batch.CloudTasksUtils;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.persistence.VKey;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.inject.Inject;
/** Command to create a new User. */
@Parameters(separators = " =", commandDescription = "Update a user account")
public class CreateUserCommand extends CreateOrUpdateUserCommand {
public class CreateUserCommand extends CreateOrUpdateUserCommand implements CommandWithConnection {
private ServiceConnection connection;
@Inject IamClient iamClient;
@Inject CloudTasksUtils cloudTasksUtils;
@Inject
@Config("gSuiteConsoleUserGroupEmailAddress")
Optional<String> maybeGroupEmailAddress;
@@ -41,14 +41,20 @@ public class CreateUserCommand extends CreateOrUpdateUserCommand {
@Nullable
@Override
User getExistingUser(String email) {
checkArgument(UserDao.loadUser(email).isEmpty(), "A user with email %s already exists", email);
checkArgument(
!tm().exists(VKey.create(User.class, email)), "A user with email %s already exists", email);
return null;
}
@Override
protected String execute() throws Exception {
String ret = super.execute();
grantIapPermission(email, maybeGroupEmailAddress, cloudTasksUtils, iamClient);
grantIapPermission(email, maybeGroupEmailAddress, null, connection, iamClient);
return ret;
}
@Override
public void setConnection(ServiceConnection connection) {
this.connection = connection;
}
}
@@ -14,28 +14,27 @@
package google.registry.tools;
import static com.google.api.client.util.Preconditions.checkArgument;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import google.registry.batch.CloudTasksUtils;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.persistence.VKey;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.inject.Inject;
/** Deletes a {@link User}. */
@Parameters(separators = " =", commandDescription = "Delete a user account")
public class DeleteUserCommand extends ConfirmingCommand {
public class DeleteUserCommand extends ConfirmingCommand implements CommandWithConnection {
private ServiceConnection connection;
@Inject IamClient iamClient;
@Inject CloudTasksUtils cloudTasksUtils;
@Inject
@Config("gSuiteConsoleUserGroupEmailAddress")
Optional<String> maybeGroupEmailAddress;
@@ -47,7 +46,9 @@ public class DeleteUserCommand extends ConfirmingCommand {
@Override
protected String prompt() {
checkArgumentNotNull(email, "Email must be provided");
checkArgumentPresent(UserDao.loadUser(email), "Email does not correspond to a valid user");
checkArgument(
tm().transact(() -> tm().exists(VKey.create(User.class, email))),
"Email does not correspond to a valid user");
return String.format("Delete user with email %s?", email);
}
@@ -55,11 +56,16 @@ public class DeleteUserCommand extends ConfirmingCommand {
protected String execute() throws Exception {
tm().transact(
() -> {
Optional<User> optionalUser = UserDao.loadUser(email);
checkArgumentPresent(optionalUser, "Email no longer corresponds to a valid user");
tm().delete(optionalUser.get());
VKey<User> key = VKey.create(User.class, email);
checkArgument(tm().exists(key), "Email no longer corresponds to a valid user");
tm().delete(key);
});
User.revokeIapPermission(email, maybeGroupEmailAddress, cloudTasksUtils, iamClient);
User.revokeIapPermission(email, maybeGroupEmailAddress, null, connection, iamClient);
return String.format("Deleted user with email %s", email);
}
@Override
public void setConnection(ServiceConnection connection) {
this.connection = connection;
}
}
@@ -14,10 +14,12 @@
package google.registry.tools;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.persistence.VKey;
import java.util.List;
/** Command to display one or more users. */
@@ -29,11 +31,14 @@ public class GetUserCommand implements Command {
@Override
public void run() throws Exception {
for (String emailAddress : mainParameters) {
System.out.println(
UserDao.loadUser(emailAddress)
.map(User::toString)
.orElse(String.format("No user with email address %s", emailAddress)));
}
tm().transact(
() -> {
for (String emailAddress : mainParameters) {
System.out.println(
tm().loadByKeyIfPresent(VKey.create(User.class, emailAddress))
.map(User::toString)
.orElse(String.format("No user with email address %s", emailAddress)));
}
});
}
}
@@ -14,20 +14,20 @@
package google.registry.tools;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
import com.beust.jcommander.Parameters;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import javax.annotation.Nullable;
import google.registry.persistence.VKey;
/** Updates a user, assuming that the user in question already exists. */
@Parameters(separators = " =", commandDescription = "Update a user account")
public class UpdateUserCommand extends CreateOrUpdateUserCommand {
@Nullable
@Override
User getExistingUser(String email) {
return checkArgumentPresent(UserDao.loadUser(email), "User %s not found", email);
return checkArgumentPresent(
tm().loadByKeyIfPresent(VKey.create(User.class, email)), "User %s not found", email);
}
}
@@ -16,6 +16,9 @@ package google.registry.ui.server.console;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.model.common.FeatureFlag.FeatureName.NEW_CONSOLE;
import static google.registry.model.common.FeatureFlag.isActiveNow;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.Action.Method.GET;
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
@@ -88,7 +91,10 @@ public abstract class ConsoleApiAction implements Runnable {
!GlobalRole.NONE.equals(userRoles.getGlobalRole())
|| userRoles.hasPermission(
registryAdminClientId, ConsolePermission.VIEW_REGISTRAR_DETAILS);
if (RegistryEnvironment.get() != RegistryEnvironment.UNITTEST && !hasGlobalOrTestingRole) {
if (!hasGlobalOrTestingRole
&& RegistryEnvironment.get() != RegistryEnvironment.UNITTEST
&& tm().transact(() -> !isActiveNow(NEW_CONSOLE))) {
try {
consoleApiParams.response().sendRedirect(ConsoleUiAction.PATH);
return;
@@ -60,7 +60,7 @@ public class ConsoleRegistryLockVerifyAction extends ConsoleApiAction {
RegistryLock lock =
domainLockUtils.verifyVerificationCode(lockVerificationCode, user.getUserRoles().isAdmin());
RegistryLockAction action =
lock.getLockCompletionTime().isPresent()
lock.getUnlockCompletionTime().isPresent()
? RegistryLockAction.UNLOCKED
: RegistryLockAction.LOCKED;
RegistryLockVerificationResponse lockResponse =
@@ -17,6 +17,7 @@ package google.registry.ui.server.console.settings;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.Action.Method.POST;
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
@@ -30,6 +31,7 @@ import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.request.auth.AuthenticatedRegistrarAccessor.RegistrarAccessDeniedException;
import google.registry.ui.server.console.ConsoleApiAction;
import google.registry.ui.server.registrar.ConsoleApiParams;
import java.util.Objects;
import java.util.Optional;
import javax.inject.Inject;
@@ -65,10 +67,10 @@ public class WhoisRegistrarFieldsAction extends ConsoleApiAction {
checkArgument(registrar.isPresent(), "'registrar' parameter is not present");
checkPermission(
user, registrar.get().getRegistrarId(), ConsolePermission.EDIT_REGISTRAR_DETAILS);
tm().transact(() -> loadAndModifyRegistrar(registrar.get()));
tm().transact(() -> loadAndModifyRegistrar(registrar.get(), user));
}
private void loadAndModifyRegistrar(Registrar providedRegistrar) {
private void loadAndModifyRegistrar(Registrar providedRegistrar, User user) {
Registrar savedRegistrar;
try {
// reload to make sure the object has all the correct fields
@@ -78,6 +80,15 @@ public class WhoisRegistrarFieldsAction extends ConsoleApiAction {
return;
}
// icannReferralEmail can't be updated by partners, only by global users with
// EDIT_REGISTRAR_DETAILS permission
if (!Objects.equals(
savedRegistrar.getIcannReferralEmail(), providedRegistrar.getIcannReferralEmail())
&& !user.getUserRoles().hasGlobalPermission(ConsolePermission.EDIT_REGISTRAR_DETAILS)) {
setFailedResponse(
"Icann Referral Email update is not permitted for this user", SC_BAD_REQUEST);
}
Registrar newRegistrar =
savedRegistrar
.asBuilder()
@@ -86,6 +97,8 @@ public class WhoisRegistrarFieldsAction extends ConsoleApiAction {
.setLocalizedAddress(providedRegistrar.getLocalizedAddress())
.setPhoneNumber(providedRegistrar.getPhoneNumber())
.setFaxNumber(providedRegistrar.getFaxNumber())
.setIcannReferralEmail(providedRegistrar.getIcannReferralEmail())
.setEmailAddress(providedRegistrar.getEmailAddress())
.build();
tm().put(newRegistrar);
sendExternalUpdatesIfNecessary(
@@ -30,7 +30,6 @@ import google.registry.batch.CloudTasksUtils;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.model.console.UserRoles;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarAddress;
@@ -260,10 +259,10 @@ public final class ConsoleRegistrarCreatorAction extends HtmlAction {
"Registrar with client ID %s already exists",
registrar.getRegistrarId());
tm().put(registrar);
tm().put(user);
});
UserDao.saveUser(user);
User.grantIapPermission(
user.getEmailAddress(), maybeGroupEmailAddress, cloudTasksUtils, iamClient);
user.getEmailAddress(), maybeGroupEmailAddress, cloudTasksUtils, null, iamClient);
data.put("password", password);
data.put("passcode", phonePasscode);
@@ -14,10 +14,13 @@
package google.registry.ui.server.registrar;
import static com.google.common.base.Strings.isNullOrEmpty;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.auth.AuthenticatedRegistrarAccessor.Role.ADMIN;
import static google.registry.request.auth.AuthenticatedRegistrarAccessor.Role.OWNER;
import static google.registry.ui.server.SoyTemplateUtils.CSS_RENAMING_MAP_SUPPLIER;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static jakarta.servlet.http.HttpServletResponse.SC_PERMANENT_REDIRECT;
import static jakarta.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
import com.google.common.base.Supplier;
@@ -26,6 +29,8 @@ import com.google.common.flogger.FluentLogger;
import com.google.template.soy.data.SoyMapData;
import com.google.template.soy.tofu.SoyTofu;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.common.FeatureFlag;
import google.registry.model.console.GlobalRole;
import google.registry.request.Action;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
@@ -35,8 +40,10 @@ import google.registry.request.auth.AuthenticatedRegistrarAccessor.Role;
import google.registry.ui.server.SoyTemplateUtils;
import google.registry.ui.soy.registrar.ConsoleSoyInfo;
import google.registry.util.RegistryEnvironment;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
import javax.inject.Inject;
/** Action that serves Registrar Console single HTML page (SPA). */
@@ -100,6 +107,7 @@ public final class ConsoleUiAction extends HtmlAction {
soyMapData.put("announcementsEmail", announcementsEmail);
soyMapData.put("supportPhoneNumber", supportPhoneNumber);
soyMapData.put("technicalDocsUrl", technicalDocsUrl);
if (!enabled) {
response.setStatus(SC_SERVICE_UNAVAILABLE);
response.setPayload(
@@ -111,9 +119,35 @@ public final class ConsoleUiAction extends HtmlAction {
.render());
return;
}
// Set permanent redirect to the new console for tech support
if (isNullOrEmpty(req.getParameter("redirect"))
&& Stream.of(GlobalRole.SUPPORT_LEAD, GlobalRole.SUPPORT_AGENT)
.anyMatch(
globalRole ->
globalRole.equals(authResult.user().get().getUserRoles().getGlobalRole()))) {
response.setStatus(SC_PERMANENT_REDIRECT);
try {
response.sendRedirect("/console");
return;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
ImmutableSetMultimap<String, Role> roleMap = registrarAccessor.getAllRegistrarIdsWithRoles();
soyMapData.put("allClientIds", roleMap.keySet());
soyMapData.put("environment", RegistryEnvironment.get().toString());
boolean newConsole =
tm().transact(
() ->
FeatureFlag.getUncached(FeatureFlag.FeatureName.NEW_CONSOLE)
.map(
flag ->
flag.getStatus(tm().getTransactionTime())
.equals(FeatureFlag.FeatureStatus.ACTIVE))
.orElse(false));
soyMapData.put("includeDeprecationWarning", newConsole);
// We set the initial value to the value that will show if guessClientId throws.
String clientId = "<null>";
try {
@@ -261,7 +261,7 @@ li.kd-menulistitem {
top: 136px;
left: 173px;
bottom: 0;
width: 100%;
width: calc(100% - 173px);
margin: 0;
padding: 25px 0 1em 0;
overflow-y: scroll !important;
@@ -296,3 +296,31 @@ li.kd-menulistitem {
.reg-select {
margin-left: 23px;
}
div#reg-deprecation-warning {
position: fixed;
top: 64px;
width: 100%;
left: 0;
text-align: center;
background-color: #ba2a1a;
font-size: 0.6svw;
}
div#reg-deprecation-warning h1 {
color: #ffffff;
}
div#reg-deprecation-warning a {
color: #add8e6;
}
div#reg-accessing-as-role {
margin-top: 40px;
}
@media screen and (max-width: 1600px) {
div#reg-deprecation-warning {
font-size: 0.85vw;
}
}
@@ -36,6 +36,7 @@
{@param technicalDocsUrl: string}
{@param environment: string}
{@param analyticsConfig: [googleAnalyticsId: string|null]}
{@param includeDeprecationWarning: bool}
{call registry.soy.console.header}
{param app: 'registrar' /}
@@ -45,7 +46,7 @@
{call registry.soy.console.googlebar data="all" /}
<div id="reg-app" lang="en-US">
<div id="reg-appbar" class="{css('kd-appbar')}">
<div class="{css('kd-description')}">
<div id="reg-accessing-as-role" class="{css('kd-description')}">
Accessing <span class="{css('kd-value')}">{$clientId}</span> as{sp}
{if $isOwner}<span class="{css('kd-value')}">Owner</span>{/if}
{if $isAdmin}<span class="{css('kd-value')}">Admin</span>{/if}
@@ -53,6 +54,13 @@
{sp}(Switch registrar: {call .clientIdSelect_ data="all" /})
{/if}
</div>
{if $includeDeprecationWarning}
<div id="reg-deprecation-warning">
<h1>Note: this console is deprecated and will be deactivated in October 2024.
Please use the <a href="/console">new console</a> instead.
</h1>
</div>
{/if}
<div id="reg-app-buttons" class="{css('kd-buttonbar')} {css('left')}"></div>
</div>
{call .navbar_ data="all" /}
@@ -22,6 +22,7 @@ import static google.registry.persistence.transaction.JpaTransactionManagerExten
import static google.registry.testing.CertificateSamples.SAMPLE_CERT;
import static google.registry.testing.CertificateSamples.SAMPLE_CERT_HASH;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.loadByKeyIfPresent;
import static google.registry.testing.DatabaseHelper.persistPremiumList;
import static google.registry.testing.DatabaseHelper.persistSimpleResource;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
@@ -36,10 +37,10 @@ import com.google.common.collect.ImmutableList;
import google.registry.batch.CloudTasksUtils;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.model.registrar.Registrar;
import google.registry.model.tld.Tld;
import google.registry.model.tld.Tld.TldState;
import google.registry.persistence.VKey;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.testing.CloudTasksHelper;
@@ -106,7 +107,7 @@ public final class OteAccountBuilderTest {
}
public static void verifyUser(String registrarId, String email) {
Optional<User> maybeUser = UserDao.loadUser(email);
Optional<User> maybeUser = loadByKeyIfPresent(VKey.create(User.class, email));
assertThat(maybeUser).isPresent();
assertThat(maybeUser.get().getUserRoles().getRegistrarRoles().get(registrarId))
.isEqualTo(RegistrarRole.ACCOUNT_MANAGER);
@@ -1,84 +0,0 @@
// Copyright 2022 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.model.console;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static org.junit.jupiter.api.Assertions.assertThrows;
import google.registry.model.EntityTestCase;
import org.junit.jupiter.api.Test;
/** Tests for {@link UserDao}. */
public class UserDaoTest extends EntityTestCase {
@Test
void testSuccess_saveAndRetrieve() {
User user1 =
new User.Builder()
.setEmailAddress("email@email.com")
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.SUPPORT_AGENT).build())
.build();
User user2 =
new User.Builder()
.setEmailAddress("foo@bar.com")
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.SUPPORT_AGENT).build())
.build();
UserDao.saveUser(user1);
UserDao.saveUser(user2);
assertAboutImmutableObjects()
.that(user1)
.isEqualExceptFields(UserDao.loadUser("email@email.com").get(), "id", "updateTimestamp");
assertAboutImmutableObjects()
.that(user2)
.isEqualExceptFields(UserDao.loadUser("foo@bar.com").get(), "id", "updateTimestamp");
}
@Test
void testSuccess_absentUser() {
User user =
new User.Builder()
.setEmailAddress("email@email.com")
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.SUPPORT_AGENT).build())
.build();
UserDao.saveUser(user);
User fromDb = UserDao.loadUser("email@email.com").get();
// nonexistent one should never exist
assertThat(UserDao.loadUser("nonexistent@email.com")).isEmpty();
// now try deleting the one that does exist
tm().transact(() -> tm().delete(fromDb));
assertThat(UserDao.loadUser("email@email.com")).isEmpty();
}
@Test
void testFailure_sameEmail() {
User user1 =
new User.Builder()
.setEmailAddress("email@email.com")
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.SUPPORT_AGENT).build())
.build();
User user2 =
new User.Builder()
.setEmailAddress("email@email.com")
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.SUPPORT_AGENT).build())
.build();
UserDao.saveUser(user1);
assertThrows(IllegalArgumentException.class, () -> UserDao.saveUser(user2));
assertAboutImmutableObjects()
.that(user1)
.isEqualExceptFields(UserDao.loadUser("email@email.com").get(), "id", "updateTimestamp");
}
}
@@ -21,15 +21,20 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import com.google.cloud.tasks.v2.HttpMethod;
import com.google.common.collect.ImmutableMap;
import com.google.common.net.MediaType;
import google.registry.batch.CloudTasksUtils;
import google.registry.model.EntityTestCase;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.CloudTasksHelper.TaskMatcher;
import google.registry.testing.DatabaseHelper;
import google.registry.tools.IamClient;
import google.registry.tools.ServiceConnection;
import google.registry.tools.server.UpdateUserGroupAction;
import java.util.Optional;
import org.junit.jupiter.api.Test;
@@ -57,6 +62,34 @@ public class UserTest extends EntityTestCase {
});
}
@Test
void testFailure_asyncAndSyncModeConflict() {
assertThrows(
IllegalArgumentException.class,
() -> User.grantIapPermission("email@example.com", Optional.empty(), null, null, null));
assertThrows(
IllegalArgumentException.class,
() ->
User.grantIapPermission(
"email@example.com",
Optional.empty(),
mock(CloudTasksUtils.class),
mock(ServiceConnection.class),
null));
assertThrows(
IllegalArgumentException.class,
() -> User.revokeIapPermission("email@example.com", Optional.empty(), null, null, null));
assertThrows(
IllegalArgumentException.class,
() ->
User.revokeIapPermission(
"email@example.com",
Optional.empty(),
mock(CloudTasksUtils.class),
mock(ServiceConnection.class),
null));
}
@Test
void testFailure_badInputs() {
User.Builder builder = new User.Builder();
@@ -116,19 +149,20 @@ public class UserTest extends EntityTestCase {
}
@Test
void testGrantIapPermission() {
void testGrantIapPermissionAsync() {
CloudTasksHelper cloudTasksHelper = new CloudTasksHelper();
IamClient iamClient = mock(IamClient.class);
CloudTasksUtils cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
// Individual permission.
User.grantIapPermission("email@example.com", Optional.empty(), cloudTasksUtils, iamClient);
User.grantIapPermission(
"email@example.com", Optional.empty(), cloudTasksUtils, null, iamClient);
cloudTasksHelper.assertNoTasksEnqueued();
verify(iamClient).addBinding("email@example.com", IAP_SECURED_WEB_APP_USER_ROLE);
// Group membership.
User.grantIapPermission(
"email@example.com", Optional.of("console@example.com"), cloudTasksUtils, iamClient);
"email@example.com", Optional.of("console@example.com"), cloudTasksUtils, null, iamClient);
cloudTasksHelper.assertTasksEnqueued(
"console-user-group-update",
new TaskMatcher()
@@ -142,19 +176,49 @@ public class UserTest extends EntityTestCase {
}
@Test
void testRevokeIapPermission() {
void testGrantIapPermissionSync() throws Exception {
ServiceConnection connection = mock(ServiceConnection.class);
IamClient iamClient = mock(IamClient.class);
// Individual permission.
User.grantIapPermission("email@example.com", Optional.empty(), null, connection, iamClient);
verifyNoInteractions(connection);
verify(iamClient).addBinding("email@example.com", IAP_SECURED_WEB_APP_USER_ROLE);
// Group membership.
User.grantIapPermission(
"email@example.com", Optional.of("console@example.com"), null, connection, iamClient);
verify(connection)
.sendPostRequest(
UpdateUserGroupAction.PATH,
ImmutableMap.of(
"userEmailAddress",
"email@example.com",
"groupEmailAddress",
"console@example.com",
"groupUpdateMode",
"ADD"),
MediaType.PLAIN_TEXT_UTF_8,
new byte[0]);
verifyNoMoreInteractions(iamClient);
verifyNoMoreInteractions(connection);
}
@Test
void testRevokeIapPermissionAsync() {
CloudTasksHelper cloudTasksHelper = new CloudTasksHelper();
IamClient iamClient = mock(IamClient.class);
CloudTasksUtils cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
// Individual permission.
User.revokeIapPermission("email@example.com", Optional.empty(), cloudTasksUtils, iamClient);
User.revokeIapPermission(
"email@example.com", Optional.empty(), cloudTasksUtils, null, iamClient);
cloudTasksHelper.assertNoTasksEnqueued();
verify(iamClient).removeBinding("email@example.com", IAP_SECURED_WEB_APP_USER_ROLE);
// Group membership.
User.revokeIapPermission(
"email@example.com", Optional.of("console@example.com"), cloudTasksUtils, iamClient);
"email@example.com", Optional.of("console@example.com"), cloudTasksUtils, null, iamClient);
cloudTasksHelper.assertTasksEnqueued(
"console-user-group-update",
new TaskMatcher()
@@ -166,4 +230,33 @@ public class UserTest extends EntityTestCase {
.param("groupUpdateMode", "REMOVE"));
verifyNoMoreInteractions(iamClient);
}
@Test
void testRevokeIapPermissionSync() throws Exception {
ServiceConnection connection = mock(ServiceConnection.class);
IamClient iamClient = mock(IamClient.class);
// Individual permission.
User.revokeIapPermission("email@example.com", Optional.empty(), null, connection, iamClient);
verifyNoInteractions(connection);
verify(iamClient).removeBinding("email@example.com", IAP_SECURED_WEB_APP_USER_ROLE);
// Group membership.
User.revokeIapPermission(
"email@example.com", Optional.of("console@example.com"), null, connection, iamClient);
verify(connection)
.sendPostRequest(
UpdateUserGroupAction.PATH,
ImmutableMap.of(
"userEmailAddress",
"email@example.com",
"groupEmailAddress",
"console@example.com",
"groupUpdateMode",
"REMOVE"),
MediaType.PLAIN_TEXT_UTF_8,
new byte[0]);
verifyNoMoreInteractions(iamClient);
verifyNoMoreInteractions(connection);
}
}
@@ -73,7 +73,6 @@ import google.registry.model.billing.BillingRecurrence;
import google.registry.model.common.DnsRefreshRequest;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.model.console.UserRoles;
import google.registry.model.contact.Contact;
import google.registry.model.contact.ContactAuthInfo;
@@ -1014,20 +1013,33 @@ public final class DatabaseHelper {
return tm().transact(() -> tm().loadByEntity(resource));
}
public static User loadExistingUser(String emailAddress) {
return loadByKey(VKey.create(User.class, emailAddress));
}
/** Persists an admin {@link User} with the given email address if it doesn't already exist. */
public static User createAdminUser(String emailAddress) {
Optional<User> existingUser = UserDao.loadUser(emailAddress);
if (existingUser.isPresent()) {
return existingUser.get();
}
User user =
new User.Builder()
.setEmailAddress(emailAddress)
.setUserRoles(
new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).setIsAdmin(true).build())
.build();
tm().transact(() -> tm().put(user));
return UserDao.loadUser(emailAddress).get();
// Reload the user to pick up the update time
return loadByEntity(
tm().transact(
() -> {
VKey<User> key = VKey.create(User.class, emailAddress);
Optional<User> existingUser = tm().loadByKeyIfPresent(key);
if (existingUser.isPresent()) {
return existingUser.get();
}
User user =
new User.Builder()
.setEmailAddress(emailAddress)
.setUserRoles(
new UserRoles.Builder()
.setGlobalRole(GlobalRole.FTE)
.setIsAdmin(true)
.build())
.build();
tm().put(user);
return user;
}));
}
/** Returns all the history entries that are parented off the given EppResource. */
@@ -16,22 +16,21 @@ package google.registry.tools;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.console.User.IAP_SECURED_WEB_APP_USER_ROLE;
import static google.registry.testing.DatabaseHelper.loadExistingUser;
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import com.google.cloud.tasks.v2.HttpMethod;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.net.MediaType;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.CloudTasksHelper.TaskMatcher;
import google.registry.testing.DatabaseHelper;
import google.registry.tools.server.UpdateUserGroupAction;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -40,13 +39,13 @@ import org.junit.jupiter.api.Test;
public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
private final IamClient iamClient = mock(IamClient.class);
private final CloudTasksHelper cloudTasksHelper = new CloudTasksHelper();
private final ServiceConnection connection = mock(ServiceConnection.class);
@BeforeEach
void beforeEach() {
command.iamClient = iamClient;
command.maybeGroupEmailAddress = Optional.empty();
command.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
command.setConnection(connection);
}
@Test
@@ -59,7 +58,7 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
assertThat(onlyUser.getUserRoles().getRegistrarRoles()).isEmpty();
verify(iamClient).addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE);
verifyNoMoreInteractions(iamClient);
cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
verifyNoInteractions(connection);
}
@Test
@@ -71,16 +70,20 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
assertThat(onlyUser.getUserRoles().isAdmin()).isFalse();
assertThat(onlyUser.getUserRoles().getGlobalRole()).isEqualTo(GlobalRole.NONE);
assertThat(onlyUser.getUserRoles().getRegistrarRoles()).isEmpty();
cloudTasksHelper.assertTasksEnqueued(
"console-user-group-update",
new TaskMatcher()
.method(HttpMethod.POST)
.service("TOOLS")
.path("/_dr/admin/updateUserGroup")
.param("userEmailAddress", "user@example.test")
.param("groupEmailAddress", "group@example.test")
.param("groupUpdateMode", "ADD"));
verify(connection)
.sendPostRequest(
UpdateUserGroupAction.PATH,
ImmutableMap.of(
"userEmailAddress",
"user@example.test",
"groupEmailAddress",
"group@example.test",
"groupUpdateMode",
"ADD"),
MediaType.PLAIN_TEXT_UTF_8,
new byte[0]);
verifyNoInteractions(iamClient);
verifyNoMoreInteractions(connection);
}
@Test
@@ -90,27 +93,27 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
"user@example.test",
"--registry_lock_email_address",
"registrylockemail@otherexample.test");
assertThat(UserDao.loadUser("user@example.test").get().getRegistryLockEmailAddress())
assertThat(loadExistingUser("user@example.test").getRegistryLockEmailAddress())
.hasValue("registrylockemail@otherexample.test");
}
@Test
void testSuccess_admin() throws Exception {
runCommandForced("--email", "user@example.test", "--admin", "true");
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().isAdmin()).isTrue();
assertThat(loadExistingUser("user@example.test").getUserRoles().isAdmin()).isTrue();
verify(iamClient).addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE);
verifyNoMoreInteractions(iamClient);
cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
verifyNoInteractions(connection);
}
@Test
void testSuccess_globalRole() throws Exception {
runCommandForced("--email", "user@example.test", "--global_role", "FTE");
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getGlobalRole())
assertThat(loadExistingUser("user@example.test").getUserRoles().getGlobalRole())
.isEqualTo(GlobalRole.FTE);
verify(iamClient).addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE);
verifyNoMoreInteractions(iamClient);
cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
verifyNoInteractions(connection);
}
@Test
@@ -120,7 +123,7 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
"user@example.test",
"--registrar_roles",
"TheRegistrar=ACCOUNT_MANAGER,NewRegistrar=PRIMARY_CONTACT");
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getRegistrarRoles())
assertThat(loadExistingUser("user@example.test").getUserRoles().getRegistrarRoles())
.isEqualTo(
ImmutableMap.of(
"TheRegistrar",
@@ -129,7 +132,7 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
RegistrarRole.PRIMARY_CONTACT));
verify(iamClient).addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE);
verifyNoMoreInteractions(iamClient);
cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
verifyNoInteractions(connection);
}
@Test
@@ -144,7 +147,7 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
.hasMessageThat()
.isEqualTo("A user with email user@example.test already exists");
verifyNoMoreInteractions(iamClient);
cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
verifyNoInteractions(connection);
}
@Test
@@ -22,11 +22,12 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import com.google.cloud.tasks.v2.HttpMethod;
import google.registry.model.console.UserDao;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.CloudTasksHelper.TaskMatcher;
import com.google.common.collect.ImmutableMap;
import com.google.common.net.MediaType;
import google.registry.model.console.User;
import google.registry.persistence.VKey;
import google.registry.testing.DatabaseHelper;
import google.registry.tools.server.UpdateUserGroupAction;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -35,43 +36,49 @@ import org.junit.jupiter.api.Test;
public class DeleteUserCommandTest extends CommandTestCase<DeleteUserCommand> {
private final IamClient iamClient = mock(IamClient.class);
private final CloudTasksHelper cloudTasksHelper = new CloudTasksHelper();
private final ServiceConnection connection = mock(ServiceConnection.class);
@BeforeEach
void beforeEach() {
command.iamClient = iamClient;
command.maybeGroupEmailAddress = Optional.empty();
command.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
command.setConnection(connection);
}
@Test
void testSuccess_deletesUser() throws Exception {
DatabaseHelper.createAdminUser("email@example.test");
assertThat(UserDao.loadUser("email@example.test")).isPresent();
VKey<User> key = VKey.create(User.class, "email@example.test");
assertThat(DatabaseHelper.loadByKeyIfPresent(key)).isPresent();
runCommandForced("--email", "email@example.test");
assertThat(UserDao.loadUser("email@example.test")).isEmpty();
assertThat(DatabaseHelper.loadByKeyIfPresent(key)).isEmpty();
verify(iamClient).removeBinding("email@example.test", IAP_SECURED_WEB_APP_USER_ROLE);
verifyNoMoreInteractions(iamClient);
cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
verifyNoInteractions(connection);
}
@Test
void testSuccess_deletesUser_removeFromGroup() throws Exception {
command.maybeGroupEmailAddress = Optional.of("group@example.test");
DatabaseHelper.createAdminUser("email@example.test");
assertThat(UserDao.loadUser("email@example.test")).isPresent();
VKey<User> key = VKey.create(User.class, "email@example.test");
assertThat(DatabaseHelper.loadByKeyIfPresent(key)).isPresent();
runCommandForced("--email", "email@example.test");
assertThat(UserDao.loadUser("email@example.test")).isEmpty();
cloudTasksHelper.assertTasksEnqueued(
"console-user-group-update",
new TaskMatcher()
.method(HttpMethod.POST)
.service("TOOLS")
.path("/_dr/admin/updateUserGroup")
.param("userEmailAddress", "email@example.test")
.param("groupEmailAddress", "group@example.test")
.param("groupUpdateMode", "REMOVE"));
assertThat(DatabaseHelper.loadByKeyIfPresent(key)).isEmpty();
verify(connection)
.sendPostRequest(
UpdateUserGroupAction.PATH,
ImmutableMap.of(
"userEmailAddress",
"email@example.test",
"groupEmailAddress",
"group@example.test",
"groupUpdateMode",
"REMOVE"),
MediaType.PLAIN_TEXT_UTF_8,
new byte[0]);
verifyNoInteractions(iamClient);
verifyNoMoreInteractions(connection);
}
@Test
@@ -83,6 +90,6 @@ public class DeleteUserCommandTest extends CommandTestCase<DeleteUserCommand> {
.hasMessageThat()
.isEqualTo("Email does not correspond to a valid user");
verifyNoInteractions(iamClient);
cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
verifyNoInteractions(connection);
}
}
@@ -14,12 +14,13 @@
package google.registry.tools;
import com.google.common.collect.ImmutableMap;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.model.console.UserRoles;
import google.registry.testing.DatabaseHelper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -28,7 +29,7 @@ public class GetUserCommandTest extends CommandTestCase<GetUserCommand> {
@BeforeEach
void beforeEach() {
UserDao.saveUser(
DatabaseHelper.putInDb(
new User.Builder()
.setEmailAddress("johndoe@theregistrar.com")
.setUserRoles(
@@ -36,8 +37,7 @@ public class GetUserCommandTest extends CommandTestCase<GetUserCommand> {
.setRegistrarRoles(
ImmutableMap.of("TheRegistrar", RegistrarRole.PRIMARY_CONTACT))
.build())
.build());
UserDao.saveUser(
.build(),
new User.Builder()
.setEmailAddress("fte@google.com")
.setUserRoles(
@@ -52,7 +52,6 @@ public class GetUserCommandTest extends CommandTestCase<GetUserCommand> {
"""
User: {
emailAddress=fte@google.com
id=2
registryLockEmailAddress=null
registryLockPasswordHash=null
registryLockPasswordSalt=null
@@ -75,7 +74,6 @@ public class GetUserCommandTest extends CommandTestCase<GetUserCommand> {
"""
User: {
emailAddress=johndoe@theregistrar.com
id=1
registryLockEmailAddress=null
registryLockPasswordHash=null
registryLockPasswordSalt=null
@@ -90,7 +88,6 @@ public class GetUserCommandTest extends CommandTestCase<GetUserCommand> {
}
User: {
emailAddress=fte@google.com
id=2
registryLockEmailAddress=null
registryLockPasswordHash=null
registryLockPasswordSalt=null
@@ -113,7 +110,6 @@ public class GetUserCommandTest extends CommandTestCase<GetUserCommand> {
"""
User: {
emailAddress=johndoe@theregistrar.com
id=1
registryLockEmailAddress=null
registryLockPasswordHash=null
registryLockPasswordSalt=null
@@ -22,9 +22,11 @@ import static google.registry.model.tld.Tld.TldState.GENERAL_AVAILABILITY;
import static google.registry.model.tld.Tld.TldState.START_DATE_SUNRISE;
import static google.registry.testing.CertificateSamples.SAMPLE_CERT_HASH;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.loadExistingUser;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
import static google.registry.testing.DatabaseHelper.persistPremiumList;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.DatabaseHelper.putInDb;
import static org.joda.money.CurrencyUnit.USD;
import static org.joda.time.DateTimeZone.UTC;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -39,7 +41,6 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.model.console.UserRoles;
import google.registry.model.registrar.Registrar;
import google.registry.model.tld.Tld;
@@ -447,12 +448,11 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
@Test
void testFailure_userExists() {
User user =
putInDb(
new User.Builder()
.setEmailAddress("contact@email.com")
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build())
.build();
UserDao.saveUser(user);
.build());
IllegalStateException thrown =
assertThrows(
IllegalStateException.class,
@@ -494,12 +494,11 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
@Test
void testSuccess_userExists_replaceExisting() throws Exception {
User user =
putInDb(
new User.Builder()
.setEmailAddress("contact@email.com")
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build())
.build();
UserDao.saveUser(user);
.build());
runCommandForced(
"--overwrite",
@@ -520,7 +519,7 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
verifyUser("blobio-5", "contact@email.com");
// verify that the role is completely replaced, e.g., the global role is gone.
assertThat(UserDao.loadUser("contact@email.com").get().getUserRoles().getGlobalRole())
assertThat(loadExistingUser("contact@email.com").getUserRoles().getGlobalRole())
.isEqualTo(GlobalRole.NONE);
verifyIapPermission("contact@email.com");
@@ -15,13 +15,14 @@
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.putInDb;
import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableMap;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.model.console.UserRoles;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -31,7 +32,7 @@ public class UpdateUserCommandTest extends CommandTestCase<UpdateUserCommand> {
@BeforeEach
void beforeEach() throws Exception {
UserDao.saveUser(
putInDb(
new User.Builder()
.setEmailAddress("user@example.test")
.setUserRoles(new UserRoles.Builder().build())
@@ -45,41 +46,39 @@ public class UpdateUserCommandTest extends CommandTestCase<UpdateUserCommand> {
"user@example.test",
"--registry_lock_email_address",
"registrylockemail@otherexample.test");
assertThat(UserDao.loadUser("user@example.test").get().getRegistryLockEmailAddress())
assertThat(loadExistingUser("user@example.test").getRegistryLockEmailAddress())
.hasValue("registrylockemail@otherexample.test");
}
@Test
void testSuccess_removeRegistryLockEmail() throws Exception {
UserDao.saveUser(
UserDao.loadUser("user@example.test")
.get()
putInDb(
loadExistingUser("user@example.test")
.asBuilder()
.setRegistryLockEmailAddress("registrylock@otherexample.test")
.build());
runCommandForced("--email", "user@example.test", "--registry_lock_email_address", "");
assertThat(UserDao.loadUser("user@example.test").get().getRegistryLockEmailAddress()).isEmpty();
assertThat(loadExistingUser("user@example.test").getRegistryLockEmailAddress()).isEmpty();
}
@Test
void testSuccess_admin() throws Exception {
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().isAdmin()).isFalse();
assertThat(loadExistingUser("user@example.test").getUserRoles().isAdmin()).isFalse();
runCommandForced("--email", "user@example.test", "--admin", "true");
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().isAdmin()).isTrue();
assertThat(loadExistingUser("user@example.test").getUserRoles().isAdmin()).isTrue();
runCommandForced("--email", "user@example.test", "--admin", "false");
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().isAdmin()).isFalse();
assertThat(loadExistingUser("user@example.test").getUserRoles().isAdmin()).isFalse();
}
@Test
void testSuccess_registrarRoles() throws Exception {
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getRegistrarRoles())
.isEmpty();
assertThat(loadExistingUser("user@example.test").getUserRoles().getRegistrarRoles()).isEmpty();
runCommandForced(
"--email",
"user@example.test",
"--registrar_roles",
"TheRegistrar=ACCOUNT_MANAGER,NewRegistrar=PRIMARY_CONTACT");
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getRegistrarRoles())
assertThat(loadExistingUser("user@example.test").getUserRoles().getRegistrarRoles())
.isEqualTo(
ImmutableMap.of(
"TheRegistrar",
@@ -87,16 +86,15 @@ public class UpdateUserCommandTest extends CommandTestCase<UpdateUserCommand> {
"NewRegistrar",
RegistrarRole.PRIMARY_CONTACT));
runCommandForced("--email", "user@example.test", "--registrar_roles", "");
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getRegistrarRoles())
.isEmpty();
assertThat(loadExistingUser("user@example.test").getUserRoles().getRegistrarRoles()).isEmpty();
}
@Test
void testSuccess_globalRole() throws Exception {
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getGlobalRole())
assertThat(loadExistingUser("user@example.test").getUserRoles().getGlobalRole())
.isEqualTo(GlobalRole.NONE);
runCommandForced("--email", "user@example.test", "--global_role", "FTE");
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getGlobalRole())
assertThat(loadExistingUser("user@example.test").getUserRoles().getGlobalRole())
.isEqualTo(GlobalRole.FTE);
}
@@ -90,7 +90,7 @@ public class ConsoleRegistryLockVerifyActionTest {
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
assertThat(response.getPayload())
.isEqualTo(
"{\"action\":\"unlocked\",\"domainName\":\"example.test\",\"registrarId\":\"TheRegistrar\"}");
"{\"action\":\"locked\",\"domainName\":\"example.test\",\"registrarId\":\"TheRegistrar\"}");
assertThat(loadByEntity(defaultDomain).getStatusValues())
.containsAtLeastElementsIn(REGISTRY_LOCK_STATUSES);
}
@@ -123,7 +123,7 @@ public class ConsoleRegistryLockVerifyActionTest {
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
assertThat(response.getPayload())
.isEqualTo(
"{\"action\":\"unlocked\",\"domainName\":\"example.test\",\"registrarId\":\"TheRegistrar\"}");
"{\"action\":\"locked\",\"domainName\":\"example.test\",\"registrarId\":\"TheRegistrar\"}");
assertThat(loadByEntity(defaultDomain).getStatusValues())
.containsAtLeastElementsIn(REGISTRY_LOCK_STATUSES);
}
@@ -17,7 +17,6 @@ package google.registry.ui.server.console.settings;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.registrar.RegistrarPocBase.Type.ADMIN;
import static google.registry.model.registrar.RegistrarPocBase.Type.WHOIS;
import static google.registry.testing.DatabaseHelper.createAdminUser;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.loadAllOf;
@@ -67,7 +66,7 @@ class ContactActionTest {
+ "\"emailAddress\":\"test.registrar1@example.com\","
+ "\"registrarId\":\"registrarId\","
+ "\"phoneNumber\":\"+1.9999999999\",\"faxNumber\":\"+1.9999999991\","
+ "\"types\":[\"WHOIS\",\"ADMIN\"],\"visibleInWhoisAsAdmin\":true,"
+ "\"types\":[\"ADMIN\"],\"visibleInWhoisAsAdmin\":true,"
+ "\"visibleInWhoisAsTech\":false,\"visibleInDomainWhoisAsAbuse\":false}";
private static String jsonRegistrar2 =
@@ -75,7 +74,7 @@ class ContactActionTest {
+ "\"emailAddress\":\"test.registrar2@example.com\","
+ "\"registrarId\":\"registrarId\","
+ "\"phoneNumber\":\"+1.1234567890\",\"faxNumber\":\"+1.1234567891\","
+ "\"types\":[\"WHOIS\",\"ADMIN\"],\"visibleInWhoisAsAdmin\":true,"
+ "\"types\":[\"ADMIN\"],\"visibleInWhoisAsAdmin\":true,"
+ "\"visibleInWhoisAsTech\":false,\"visibleInDomainWhoisAsAbuse\":false}";
private Registrar testRegistrar;
@@ -97,7 +96,7 @@ class ContactActionTest {
.setEmailAddress("test.registrar1@example.com")
.setPhoneNumber("+1.9999999999")
.setFaxNumber("+1.9999999991")
.setTypes(ImmutableSet.of(WHOIS, ADMIN))
.setTypes(ImmutableSet.of(ADMIN))
.setVisibleInWhoisAsAdmin(true)
.setVisibleInWhoisAsTech(false)
.setVisibleInDomainWhoisAsAbuse(false)
@@ -218,22 +217,22 @@ class ContactActionTest {
+ " {name=Test Registrar 1,"
+ " emailAddress=test.registrar1@example.com, registrarId=registrarId,"
+ " registryLockEmailAddress=null, phoneNumber=+1.9999999999,"
+ " faxNumber=+1.9999999991, types=[ADMIN, WHOIS],"
+ " faxNumber=+1.9999999991, types=[ADMIN],"
+ " visibleInWhoisAsAdmin=true, visibleInWhoisAsTech=false,"
+ " visibleInDomainWhoisAsAbuse=false,"
+ " allowedToSetRegistryLockPassword=false}\n"
+ " REMOVED:\n"
+ " {name=Test Registrar 1, emailAddress=incorrect@email.com,"
+ " registrarId=registrarId, registryLockEmailAddress=null,"
+ " phoneNumber=+1.9999999999, faxNumber=+1.9999999991, types=[WHOIS,"
+ " ADMIN], visibleInWhoisAsAdmin=true,"
+ " phoneNumber=+1.9999999999, faxNumber=+1.9999999991, types=[ADMIN],"
+ " visibleInWhoisAsAdmin=true,"
+ " visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false,"
+ " allowedToSetRegistryLockPassword=false}\n"
+ " FINAL CONTENTS:\n"
+ " {name=Test Registrar 1,"
+ " emailAddress=test.registrar1@example.com, registrarId=registrarId,"
+ " registryLockEmailAddress=null, phoneNumber=+1.9999999999,"
+ " faxNumber=+1.9999999991, types=[ADMIN, WHOIS],"
+ " faxNumber=+1.9999999991, types=[ADMIN],"
+ " visibleInWhoisAsAdmin=true, visibleInWhoisAsTech=false,"
+ " visibleInDomainWhoisAsAbuse=false,"
+ " allowedToSetRegistryLockPassword=false}\n")
@@ -104,6 +104,8 @@ public class WhoisRegistrarFieldsActionTest {
ImmutableMap.of(
"whoisServer",
"whois.nic.google",
"icannReferralEmail",
"lol@sloth.test",
"phoneNumber",
"+1.4155552671",
"faxNumber",
@@ -29,6 +29,8 @@ import static google.registry.tools.LockOrUnlockDomainCommand.REGISTRY_LOCK_STAT
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedMap;
import google.registry.model.common.FeatureFlag;
import google.registry.model.console.RegistrarRole;
import google.registry.model.domain.Domain;
import google.registry.model.domain.RegistryLock;
@@ -110,15 +112,6 @@ class RegistrarConsoleScreenshotTest extends WebDriverTestCase {
driver.diffPage("page");
}
/** Admins shouldn't have the "add" button */
@RetryingTest(3)
void settingsContact_asAdmin() throws Throwable {
server.setIsAdmin(true);
driver.get(server.getUrl("/registrar?clientId=NewRegistrar#contact-settings"));
driver.waitForDisplayedElement(By.tagName("h1"));
driver.diffPage("page");
}
@RetryingTest(3)
void settingsContactItem() throws Throwable {
driver.get(server.getUrl("/registrar#contact-settings/johndoe@theregistrar.com"));
@@ -518,6 +511,18 @@ class RegistrarConsoleScreenshotTest extends WebDriverTestCase {
driver.diffPage("page");
}
@RetryingTest(3)
void deprecationWarning_active() throws Throwable {
persistResource(
new FeatureFlag.Builder()
.setFeatureName(FeatureFlag.FeatureName.NEW_CONSOLE)
.setStatusMap(ImmutableSortedMap.of(START_OF_TIME, FeatureFlag.FeatureStatus.ACTIVE))
.build());
driver.get(server.getUrl("/registrar"));
driver.waitForDisplayedElement(By.tagName("h1"));
driver.diffPage("page");
}
private static void createDomainAndSaveLock() {
createTld("tld");
Domain domain = persistActiveDomain("example.tld");
Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 207 KiB

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 KiB

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

File diff suppressed because it is too large Load Diff
@@ -257,15 +257,15 @@ td.section {
<tbody>
<tr>
<td class="property_name">generated by</td>
<td class="property_value">SchemaCrawler 16.21.2</td>
<td class="property_value">SchemaCrawler 16.21.4</td>
</tr>
<tr>
<td class="property_name">generated on</td>
<td class="property_value">2024-06-20 20:07:00</td>
<td class="property_value">2024-08-01 19:19:23</td>
</tr>
<tr>
<td class="property_name">last flyway file</td>
<td id="lastFlywayFile" class="property_value">V173__create_feature_flag_table.sql</td>
<td id="lastFlywayFile" class="property_value">V176__drop_login_email_address_column_from_registrar_poc.sql</td>
</tr>
</tbody>
</table>
@@ -278,9 +278,9 @@ td.section {
</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-9412 4738,-9412 4738,4 -4,4" />
<text text-anchor="start" x="4494" y="-29.8" font-family="Helvetica,sans-Serif" font-size="14.00">generated by</text>
<text text-anchor="start" x="4577" y="-29.8" font-family="Helvetica,sans-Serif" font-size="14.00">SchemaCrawler 16.21.2</text>
<text text-anchor="start" x="4577" y="-29.8" font-family="Helvetica,sans-Serif" font-size="14.00">SchemaCrawler 16.21.4</text>
<text text-anchor="start" x="4493" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">generated on</text>
<text text-anchor="start" x="4577" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">2024-06-20 20:07:00</text>
<text text-anchor="start" x="4577" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">2024-08-01 19:19:23</text>
<polygon fill="none" stroke="#888888" points="4490,-4 4490,-44 4726,-44 4726,-4 4490,-4" /> <!-- allocationtoken_a08ccbef -->
<g id="node1" class="node">
<title>
@@ -2071,12 +2071,12 @@ td.section {
<text text-anchor="start" x="713" y="-504.3" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">public."User"</text>
<polygon fill="#e9c2f2" stroke="transparent" points="901,-497.5 901,-516.5 1011,-516.5 1011,-497.5 901,-497.5" />
<text text-anchor="start" x="972" y="-503.3" font-family="Helvetica,sans-Serif" font-size="14.00">[table]</text>
<text text-anchor="start" x="713" y="-485.3" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">id</text>
<text text-anchor="start" x="713" y="-484.3" font-family="Helvetica,sans-Serif" font-size="14.00">id</text>
<text text-anchor="start" x="895" y="-484.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="903" y="-484.3" font-family="Helvetica,sans-Serif" font-size="14.00">bigserial not null</text>
<text text-anchor="start" x="903" y="-484.3" font-family="Helvetica,sans-Serif" font-size="14.00">bigserial</text>
<text text-anchor="start" x="895" y="-465.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="903" y="-465.3" font-family="Helvetica,sans-Serif" font-size="14.00">auto-incremented</text>
<text text-anchor="start" x="713" y="-446.3" font-family="Helvetica,sans-Serif" font-size="14.00">email_address</text>
<text text-anchor="start" x="713" y="-447.3" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">email_address</text>
<text text-anchor="start" x="895" y="-446.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="903" y="-446.3" font-family="Helvetica,sans-Serif" font-size="14.00">text not null</text>
<text text-anchor="start" x="713" y="-427.3" font-family="Helvetica,sans-Serif" font-size="14.00">registry_lock_password_hash</text>
@@ -3425,61 +3425,58 @@ td.section {
<title>
registrarpoc_ab47054d
</title>
<polygon fill="#e9c2f2" stroke="transparent" points="697,-2093.5 697,-2112.5 948,-2112.5 948,-2093.5 697,-2093.5" />
<text text-anchor="start" x="699" y="-2100.3" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">public."RegistrarPoc"</text>
<polygon fill="#e9c2f2" stroke="transparent" points="948,-2093.5 948,-2112.5 1025,-2112.5 1025,-2093.5 948,-2093.5" />
<text text-anchor="start" x="986" y="-2099.3" font-family="Helvetica,sans-Serif" font-size="14.00">[table]</text>
<text text-anchor="start" x="699" y="-2081.3" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">email_address</text>
<text text-anchor="start" x="942" y="-2080.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="950" y="-2080.3" font-family="Helvetica,sans-Serif" font-size="14.00">text not null</text>
<text text-anchor="start" x="699" y="-2061.3" font-family="Helvetica,sans-Serif" font-size="14.00">allowed_to_set_registry_lock_password</text>
<text text-anchor="start" x="942" y="-2061.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="950" y="-2061.3" font-family="Helvetica,sans-Serif" font-size="14.00">bool not null</text>
<text text-anchor="start" x="699" y="-2042.3" font-family="Helvetica,sans-Serif" font-size="14.00">fax_number</text>
<text text-anchor="start" x="942" y="-2042.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="950" y="-2042.3" font-family="Helvetica,sans-Serif" font-size="14.00">text</text>
<text text-anchor="start" x="699" y="-2023.3" font-family="Helvetica,sans-Serif" font-size="14.00">name</text>
<text text-anchor="start" x="942" y="-2023.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="950" y="-2023.3" font-family="Helvetica,sans-Serif" font-size="14.00">text</text>
<text text-anchor="start" x="699" y="-2004.3" font-family="Helvetica,sans-Serif" font-size="14.00">phone_number</text>
<text text-anchor="start" x="942" y="-2004.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="950" y="-2004.3" font-family="Helvetica,sans-Serif" font-size="14.00">text</text>
<text text-anchor="start" x="699" y="-1985.3" font-family="Helvetica,sans-Serif" font-size="14.00">registry_lock_password_hash</text>
<text text-anchor="start" x="942" y="-1985.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="950" y="-1985.3" font-family="Helvetica,sans-Serif" font-size="14.00">text</text>
<text text-anchor="start" x="699" y="-1966.3" font-family="Helvetica,sans-Serif" font-size="14.00">registry_lock_password_salt</text>
<text text-anchor="start" x="942" y="-1966.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="950" y="-1966.3" font-family="Helvetica,sans-Serif" font-size="14.00">text</text>
<text text-anchor="start" x="699" y="-1947.3" font-family="Helvetica,sans-Serif" font-size="14.00">types</text>
<text text-anchor="start" x="942" y="-1947.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="950" y="-1947.3" font-family="Helvetica,sans-Serif" font-size="14.00">_text</text>
<text text-anchor="start" x="699" y="-1928.3" font-family="Helvetica,sans-Serif" font-size="14.00">visible_in_domain_whois_as_abuse</text>
<text text-anchor="start" x="942" y="-1928.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="950" y="-1928.3" font-family="Helvetica,sans-Serif" font-size="14.00">bool not null</text>
<text text-anchor="start" x="699" y="-1909.3" font-family="Helvetica,sans-Serif" font-size="14.00">visible_in_whois_as_admin</text>
<text text-anchor="start" x="942" y="-1909.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="950" y="-1909.3" font-family="Helvetica,sans-Serif" font-size="14.00">bool not null</text>
<text text-anchor="start" x="699" y="-1890.3" font-family="Helvetica,sans-Serif" font-size="14.00">visible_in_whois_as_tech</text>
<text text-anchor="start" x="942" y="-1890.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="950" y="-1890.3" font-family="Helvetica,sans-Serif" font-size="14.00">bool not null</text>
<text text-anchor="start" x="699" y="-1871.3" font-family="Helvetica,sans-Serif" font-size="14.00">registry_lock_email_address</text>
<text text-anchor="start" x="942" y="-1871.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="950" y="-1871.3" font-family="Helvetica,sans-Serif" font-size="14.00">text</text>
<text text-anchor="start" x="699" y="-1853.3" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">registrar_id</text>
<text text-anchor="start" x="942" y="-1852.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="950" y="-1852.3" font-family="Helvetica,sans-Serif" font-size="14.00">text not null</text>
<text text-anchor="start" x="699" y="-1833.3" font-family="Helvetica,sans-Serif" font-size="14.00">login_email_address</text>
<text text-anchor="start" x="942" y="-1833.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="950" y="-1833.3" font-family="Helvetica,sans-Serif" font-size="14.00">text</text>
<polygon fill="none" stroke="#888888" points="696,-1827 696,-2114 1026,-2114 1026,-1827 696,-1827" />
<polygon fill="#e9c2f2" stroke="transparent" points="697,-2094.5 697,-2113.5 948,-2113.5 948,-2094.5 697,-2094.5" />
<text text-anchor="start" x="699" y="-2101.3" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">public."RegistrarPoc"</text>
<polygon fill="#e9c2f2" stroke="transparent" points="948,-2094.5 948,-2113.5 1025,-2113.5 1025,-2094.5 948,-2094.5" />
<text text-anchor="start" x="986" y="-2100.3" font-family="Helvetica,sans-Serif" font-size="14.00">[table]</text>
<text text-anchor="start" x="699" y="-2082.3" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">email_address</text>
<text text-anchor="start" x="942" y="-2081.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="950" y="-2081.3" font-family="Helvetica,sans-Serif" font-size="14.00">text not null</text>
<text text-anchor="start" x="699" y="-2062.3" font-family="Helvetica,sans-Serif" font-size="14.00">allowed_to_set_registry_lock_password</text>
<text text-anchor="start" x="942" y="-2062.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="950" y="-2062.3" font-family="Helvetica,sans-Serif" font-size="14.00">bool not null</text>
<text text-anchor="start" x="699" y="-2043.3" font-family="Helvetica,sans-Serif" font-size="14.00">fax_number</text>
<text text-anchor="start" x="942" y="-2043.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="950" y="-2043.3" font-family="Helvetica,sans-Serif" font-size="14.00">text</text>
<text text-anchor="start" x="699" y="-2024.3" font-family="Helvetica,sans-Serif" font-size="14.00">name</text>
<text text-anchor="start" x="942" y="-2024.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="950" y="-2024.3" font-family="Helvetica,sans-Serif" font-size="14.00">text</text>
<text text-anchor="start" x="699" y="-2005.3" font-family="Helvetica,sans-Serif" font-size="14.00">phone_number</text>
<text text-anchor="start" x="942" y="-2005.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="950" y="-2005.3" font-family="Helvetica,sans-Serif" font-size="14.00">text</text>
<text text-anchor="start" x="699" y="-1986.3" font-family="Helvetica,sans-Serif" font-size="14.00">registry_lock_password_hash</text>
<text text-anchor="start" x="942" y="-1986.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="950" y="-1986.3" font-family="Helvetica,sans-Serif" font-size="14.00">text</text>
<text text-anchor="start" x="699" y="-1967.3" font-family="Helvetica,sans-Serif" font-size="14.00">registry_lock_password_salt</text>
<text text-anchor="start" x="942" y="-1967.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="950" y="-1967.3" font-family="Helvetica,sans-Serif" font-size="14.00">text</text>
<text text-anchor="start" x="699" y="-1948.3" font-family="Helvetica,sans-Serif" font-size="14.00">types</text>
<text text-anchor="start" x="942" y="-1948.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="950" y="-1948.3" font-family="Helvetica,sans-Serif" font-size="14.00">_text</text>
<text text-anchor="start" x="699" y="-1929.3" font-family="Helvetica,sans-Serif" font-size="14.00">visible_in_domain_whois_as_abuse</text>
<text text-anchor="start" x="942" y="-1929.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="950" y="-1929.3" font-family="Helvetica,sans-Serif" font-size="14.00">bool not null</text>
<text text-anchor="start" x="699" y="-1910.3" font-family="Helvetica,sans-Serif" font-size="14.00">visible_in_whois_as_admin</text>
<text text-anchor="start" x="942" y="-1910.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="950" y="-1910.3" font-family="Helvetica,sans-Serif" font-size="14.00">bool not null</text>
<text text-anchor="start" x="699" y="-1891.3" font-family="Helvetica,sans-Serif" font-size="14.00">visible_in_whois_as_tech</text>
<text text-anchor="start" x="942" y="-1891.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="950" y="-1891.3" font-family="Helvetica,sans-Serif" font-size="14.00">bool not null</text>
<text text-anchor="start" x="699" y="-1872.3" font-family="Helvetica,sans-Serif" font-size="14.00">registry_lock_email_address</text>
<text text-anchor="start" x="942" y="-1872.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="950" y="-1872.3" font-family="Helvetica,sans-Serif" font-size="14.00">text</text>
<text text-anchor="start" x="699" y="-1854.3" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">registrar_id</text>
<text text-anchor="start" x="942" y="-1853.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="950" y="-1853.3" font-family="Helvetica,sans-Serif" font-size="14.00">text not null</text>
<polygon fill="none" stroke="#888888" points="696,-1846.5 696,-2114.5 1026,-2114.5 1026,-1846.5 696,-1846.5" />
</g> <!-- registrarpoc_ab47054d&#45;&gt;registrar_6e1503e3 -->
<g id="edge76" class="edge">
<title>
registrarpoc_ab47054d:w-&gt;registrar_6e1503e3:e
</title>
<path fill="none" stroke="black" d="M677.75,-1856.09C540.68,-1865.23 521.94,-1979.48 470,-2117.5 454.68,-2158.21 486.76,-5152.06 448.04,-5342.06" />
<polygon fill="black" stroke="black" points="686.01,-1855.82 696.15,-1860 691,-1855.66 696,-1855.5 696,-1855.5 696,-1855.5 691,-1855.66 695.85,-1851 686.01,-1855.82 686.01,-1855.82" />
<ellipse fill="none" stroke="black" cx="682.01" cy="-1855.95" rx="4" ry="4" />
<path fill="none" stroke="black" d="M677.79,-1857.09C540.99,-1866.18 521.95,-1979.83 470,-2117.5 454.64,-2158.19 486.76,-5152.06 448.04,-5342.06" />
<polygon fill="black" stroke="black" points="686.01,-1856.82 696.14,-1861 691,-1856.66 696,-1856.5 696,-1856.5 696,-1856.5 691,-1856.66 695.86,-1852 686.01,-1856.82 686.01,-1856.82" />
<ellipse fill="none" stroke="black" cx="682.01" cy="-1856.95" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="448.99,-5352.55 439.8,-5348.61 440.58,-5346.78 449.78,-5350.71 448.99,-5352.55" />
<polyline fill="none" stroke="black" points="444,-5351.5 445.97,-5346.9 " />
<polygon fill="black" stroke="black" points="450.96,-5347.95 441.76,-5344.02 442.55,-5342.18 451.74,-5346.11 450.96,-5347.95" />
@@ -3659,14 +3656,14 @@ td.section {
<title>
registrarupdatehistory_8a38bed4:w-&gt;registrar_6e1503e3:e
</title>
<path fill="none" stroke="black" d="M1332.75,-685.31C1306.86,-790.03 1336.07,-1380.67 1316,-1412.5 1089.82,-1771.16 691.21,-1491.76 470,-1853.5 445.42,-1893.69 489.75,-5145.35 448.15,-5342.19" />
<polygon fill="black" stroke="black" points="1336.95,-678.13 1345.88,-671.77 1339.48,-673.82 1342,-669.5 1342,-669.5 1342,-669.5 1339.48,-673.82 1338.12,-667.23 1336.95,-678.13 1336.95,-678.13" />
<ellipse fill="none" stroke="black" cx="1334.93" cy="-681.58" rx="4" ry="4" />
<path fill="none" stroke="black" d="M1332.73,-685.18C1306.41,-790.38 1336.49,-1390.29 1316,-1422.5 1089.39,-1778.71 691.77,-1495.25 470,-1854.5 445.26,-1894.58 489.73,-5145.4 448.15,-5342.19" />
<polygon fill="black" stroke="black" points="1336.91,-678.11 1345.87,-671.79 1339.45,-673.8 1342,-669.5 1342,-669.5 1342,-669.5 1339.45,-673.8 1338.13,-667.21 1336.91,-678.11 1336.91,-678.11" />
<ellipse fill="none" stroke="black" cx="1334.87" cy="-681.55" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="448.97,-5352.62 439.84,-5348.55 440.65,-5346.72 449.79,-5350.8 448.97,-5352.62" />
<polyline fill="none" stroke="black" points="444,-5351.5 446.04,-5346.93 " />
<polygon fill="black" stroke="black" points="451.01,-5348.06 441.88,-5343.98 442.69,-5342.16 451.82,-5346.23 451.01,-5348.06" />
<polyline fill="none" stroke="black" points="446.04,-5346.93 448.07,-5342.37 " />
<text text-anchor="start" x="760" y="-1674.3" font-family="Helvetica,sans-Serif" font-size="14.00">fkregistrarupdatehistoryregistrarid</text>
<text text-anchor="start" x="760" y="-1680.3" font-family="Helvetica,sans-Serif" font-size="14.00">fkregistrarupdatehistoryregistrarid</text>
</g> <!-- registrarupdatehistory_8a38bed4&#45;&gt;user_f2216f01 -->
<g id="edge87" class="edge">
<title>
@@ -3758,26 +3755,26 @@ td.section {
<title>
registrarpocupdatehistory_31e5d9aa:w-&gt;registrarpoc_ab47054d:e
</title>
<path fill="none" stroke="black" d="M1352.71,-1972C1210.95,-1979.97 1182.3,-2079.86 1036.26,-2084.34" />
<path fill="none" stroke="black" d="M1352.7,-1972.01C1210.84,-1980.05 1182.43,-2080.82 1036.26,-2085.34" />
<polygon fill="black" stroke="black" points="1361,-1971.78 1371.12,-1976 1366,-1971.64 1371,-1971.5 1371,-1971.5 1371,-1971.5 1366,-1971.64 1370.88,-1967 1361,-1971.78 1361,-1971.78" />
<ellipse fill="none" stroke="black" cx="1357.01" cy="-1971.89" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="1027.08,-2089.48 1026.92,-2079.49 1028.92,-2079.46 1029.08,-2089.45 1027.08,-2089.48" />
<polyline fill="none" stroke="black" points="1026,-2084.5 1031,-2084.42 " />
<polygon fill="black" stroke="black" points="1032.08,-2089.41 1031.92,-2079.41 1033.92,-2079.38 1034.08,-2089.38 1032.08,-2089.41" />
<polyline fill="none" stroke="black" points="1031,-2084.42 1036,-2084.35 " />
<text text-anchor="start" x="1062" y="-2087.3" font-family="Helvetica,sans-Serif" font-size="14.00">fkregistrarpocupdatehistoryemailaddress</text>
<polygon fill="black" stroke="black" points="1027.08,-2090.48 1026.92,-2080.49 1028.92,-2080.45 1029.08,-2090.45 1027.08,-2090.48" />
<polyline fill="none" stroke="black" points="1026,-2085.5 1031,-2085.42 " />
<polygon fill="black" stroke="black" points="1032.08,-2090.41 1031.92,-2080.41 1033.92,-2080.38 1034.08,-2090.38 1032.08,-2090.41" />
<polyline fill="none" stroke="black" points="1031,-2085.42 1036,-2085.35 " />
<text text-anchor="start" x="1062" y="-2088.3" font-family="Helvetica,sans-Serif" font-size="14.00">fkregistrarpocupdatehistoryemailaddress</text>
</g> <!-- registrarpocupdatehistory_31e5d9aa&#45;&gt;registrarpoc_ab47054d -->
<g id="edge79" class="edge">
<title>
registrarpocupdatehistory_31e5d9aa:w-&gt;registrarpoc_ab47054d:e
</title>
<path fill="none" stroke="black" d="M1352.93,-1952.07C1212.68,-1945.23 1180.48,-1859.48 1036.13,-1855.63" />
<path fill="none" stroke="black" d="M1352.94,-1952.07C1212.77,-1945.3 1180.37,-1860.44 1036.12,-1856.63" />
<polygon fill="black" stroke="black" points="1361,-1952.26 1370.89,-1957 1366,-1952.38 1371,-1952.5 1371,-1952.5 1371,-1952.5 1366,-1952.38 1371.11,-1948 1361,-1952.26 1361,-1952.26" />
<ellipse fill="none" stroke="black" cx="1357" cy="-1952.17" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="1026.93,-1860.51 1027.07,-1850.51 1029.07,-1850.54 1028.93,-1860.54 1026.93,-1860.51" />
<polyline fill="none" stroke="black" points="1026,-1855.5 1031,-1855.57 " />
<polygon fill="black" stroke="black" points="1031.93,-1860.58 1032.07,-1850.58 1034.07,-1850.61 1033.93,-1860.61 1031.93,-1860.58" />
<polyline fill="none" stroke="black" points="1031,-1855.57 1036,-1855.63 " />
<polygon fill="black" stroke="black" points="1026.93,-1861.51 1027.07,-1851.51 1029.07,-1851.54 1028.93,-1861.54 1026.93,-1861.51" />
<polyline fill="none" stroke="black" points="1026,-1856.5 1031,-1856.57 " />
<polygon fill="black" stroke="black" points="1031.93,-1861.58 1032.06,-1851.58 1034.06,-1851.6 1033.93,-1861.6 1031.93,-1861.58" />
<polyline fill="none" stroke="black" points="1031,-1856.57 1036,-1856.63 " />
<text text-anchor="start" x="1062" y="-1951.3" font-family="Helvetica,sans-Serif" font-size="14.00">fkregistrarpocupdatehistoryemailaddress</text>
</g> <!-- registrarpocupdatehistory_31e5d9aa&#45;&gt;user_f2216f01 -->
<g id="edge86" class="edge">
@@ -4065,7 +4062,7 @@ td.section {
<text text-anchor="start" x="1594" y="-485.3" font-family="Helvetica,sans-Serif" font-size="14.00">text not null</text>
<text text-anchor="start" x="1404" y="-466.3" font-family="Helvetica,sans-Serif" font-size="14.00">user_id</text>
<text text-anchor="start" x="1586" y="-466.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="1594" y="-466.3" font-family="Helvetica,sans-Serif" font-size="14.00">int8 not null</text>
<text text-anchor="start" x="1594" y="-466.3" font-family="Helvetica,sans-Serif" font-size="14.00">int8</text>
<text text-anchor="start" x="1404" y="-447.3" font-family="Helvetica,sans-Serif" font-size="14.00">email_address</text>
<text text-anchor="start" x="1586" y="-447.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="1594" y="-447.3" font-family="Helvetica,sans-Serif" font-size="14.00">text not null</text>
@@ -11362,11 +11359,6 @@ td.section {
<td class="minwidth"><b><i>registrar_id</i></b></td>
<td class="minwidth">text not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth">login_email_address</td>
<td class="minwidth">text</td>
</tr>
<tr>
<td colspan="3"></td>
</tr>
@@ -11434,18 +11426,6 @@ td.section {
<tr>
<td colspan="3"></td>
</tr>
<tr>
<td colspan="2" class="name">registrarpoc_login_email_idx</td>
<td class="description right">[non-unique index]</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth">login_email_address</td>
<td class="minwidth">ascending</td>
</tr>
<tr>
<td colspan="3"></td>
</tr>
<tr>
<td colspan="2" class="name">"RegistrarPoc_pkey"</td>
<td class="description right">[unique index]</td>
@@ -13113,8 +13093,8 @@ td.section {
<tbody>
<tr>
<td class="spacer"></td>
<td class="minwidth"><b><i>id</i></b></td>
<td class="minwidth">bigserial not null</td>
<td class="minwidth">id</td>
<td class="minwidth">bigserial</td>
</tr>
<tr>
<td class="spacer"></td>
@@ -13123,7 +13103,7 @@ td.section {
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth">email_address</td>
<td class="minwidth"><b><i>email_address</i></b></td>
<td class="minwidth">text not null</td>
</tr>
<tr>
@@ -13176,7 +13156,7 @@ td.section {
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth">id</td>
<td class="minwidth">email_address</td>
<td class="minwidth"></td>
</tr>
<tr>
@@ -13260,7 +13240,7 @@ td.section {
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth">id</td>
<td class="minwidth">email_address</td>
<td class="minwidth">ascending</td>
</tr>
<tr>
@@ -13275,18 +13255,6 @@ td.section {
<td class="minwidth">email_address</td>
<td class="minwidth">ascending</td>
</tr>
<tr>
<td colspan="3"></td>
</tr>
<tr>
<td colspan="2" class="name">user_email_address_idx</td>
<td class="description right">[non-unique index]</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth">email_address</td>
<td class="minwidth">ascending</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
@@ -13328,7 +13296,7 @@ td.section {
<tr>
<td class="spacer"></td>
<td class="minwidth">user_id</td>
<td class="minwidth">int8 not null</td>
<td class="minwidth">int8</td>
</tr>
<tr>
<td class="spacer"></td>
+3
View File
@@ -171,3 +171,6 @@ V170__user_update_registry_lock_email.sql
V171__drop_create_billing_cost_columns_from_tld.sql
V172__allocation_token_renewal_price.sql
V173__create_feature_flag_table.sql
V174__user_pkey.sql
V175__user_update_history_id.sql
V176__drop_login_email_address_column_from_registrar_poc.sql
@@ -0,0 +1,18 @@
-- Copyright 2024 The Nomulus Authors. All Rights Reserved.
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
ALTER TABLE "User" DROP CONSTRAINT "User_pkey";
ALTER TABLE "User" ADD CONSTRAINT "User_pkey" PRIMARY KEY (email_address);
ALTER TABLE "User" ALTER COLUMN id DROP NOT NULL;
DROP INDEX user_email_address_idx;
@@ -0,0 +1,15 @@
-- Copyright 2024 The Nomulus Authors. All Rights Reserved.
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
ALTER TABLE "UserUpdateHistory" ALTER COLUMN user_id DROP NOT NULL;
@@ -0,0 +1,15 @@
-- Copyright 2024 The Nomulus Authors. All Rights Reserved.
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
alter table "RegistrarPoc" drop column login_email_address;
@@ -885,16 +885,15 @@
);
create table "User" (
id bigserial not null,
email_address text not null,
update_timestamp timestamptz,
email_address text not null,
registry_lock_email_address text,
registry_lock_password_hash text,
registry_lock_password_salt text,
global_role text not null,
is_admin boolean not null,
registrar_roles hstore,
primary key (id)
primary key (email_address)
);
create table "UserUpdateHistory" (
@@ -904,7 +903,6 @@
history_request_body text,
history_type text not null,
history_url text not null,
user_id int8 not null,
email_address text not null,
registry_lock_email_address text,
registry_lock_password_hash text,
@@ -1016,17 +1014,13 @@ create index reservedlist_name_idx on "ReservedList" (name);
create index spec11threatmatch_registrar_id_idx on "Spec11ThreatMatch" (registrar_id);
create index spec11threatmatch_tld_idx on "Spec11ThreatMatch" (tld);
create index spec11threatmatch_check_date_idx on "Spec11ThreatMatch" (check_date);
create index user_email_address_idx on "User" (email_address);
alter table if exists "User"
add constraint UK_seshmd8qn3lcffsjw5j8wnbnd unique (email_address);
create index IDXbjacjlm8ianc4kxxvamnu94k5 on "UserUpdateHistory" (history_acting_user);
create index IDX5yqacw829y5bm6f7eajsq1cts on "UserUpdateHistory" (email_address);
alter table if exists "ConsoleEppActionHistory"
add constraint FKb686b9os2nsjpv930npa4r3b4
foreign key (history_acting_user)
references "User" (email_address);
references "User";
alter table if exists "DelegationSignerData"
add constraint FKtr24j9v14ph2mfuw2gsmt12kq
@@ -1066,12 +1060,12 @@ create index IDX5yqacw829y5bm6f7eajsq1cts on "UserUpdateHistory" (email_address)
alter table if exists "RegistrarPocUpdateHistory"
add constraint FKftpbwctxtkc1i0njc0tdcaa2g
foreign key (history_acting_user)
references "User" (email_address);
references "User";
alter table if exists "RegistrarUpdateHistory"
add constraint FKsr7w342s7x5s5jvdti2axqeq8
foreign key (history_acting_user)
references "User" (email_address);
references "User";
alter table if exists "RegistryLock"
add constraint FK2lhcwpxlnqijr96irylrh1707
@@ -1086,4 +1080,4 @@ create index IDX5yqacw829y5bm6f7eajsq1cts on "UserUpdateHistory" (email_address)
alter table if exists "UserUpdateHistory"
add constraint FK1s7bopbl3pwrhv3jkkofnv3o0
foreign key (history_acting_user)
references "User" (email_address);
references "User";
@@ -986,8 +986,7 @@ CREATE TABLE public."RegistrarPoc" (
visible_in_whois_as_admin boolean NOT NULL,
visible_in_whois_as_tech boolean NOT NULL,
registry_lock_email_address text,
registrar_id text NOT NULL,
login_email_address text
registrar_id text NOT NULL
);
@@ -1317,7 +1316,7 @@ CREATE TABLE public."TmchCrl" (
--
CREATE TABLE public."User" (
id bigint NOT NULL,
id bigint,
email_address text NOT NULL,
registry_lock_password_hash text,
registry_lock_password_salt text,
@@ -1340,7 +1339,7 @@ CREATE TABLE public."UserUpdateHistory" (
history_request_body text,
history_type text NOT NULL,
history_url text NOT NULL,
user_id bigint NOT NULL,
user_id bigint,
email_address text NOT NULL,
registry_lock_password_hash text,
registry_lock_password_salt text,
@@ -1833,7 +1832,7 @@ ALTER TABLE ONLY public."UserUpdateHistory"
--
ALTER TABLE ONLY public."User"
ADD CONSTRAINT "User_pkey" PRIMARY KEY (id);
ADD CONSTRAINT "User_pkey" PRIMARY KEY (email_address);
--
@@ -2512,13 +2511,6 @@ CREATE INDEX registrar_iana_identifier_idx ON public."Registrar" USING btree (ia
CREATE INDEX registrar_name_idx ON public."Registrar" USING btree (registrar_name);
--
-- Name: registrarpoc_login_email_idx; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX registrarpoc_login_email_idx ON public."RegistrarPoc" USING btree (login_email_address);
--
-- Name: reservedlist_name_idx; Type: INDEX; Schema: public; Owner: -
--
@@ -2547,13 +2539,6 @@ CREATE INDEX spec11threatmatch_registrar_id_idx ON public."Spec11ThreatMatch" US
CREATE INDEX spec11threatmatch_tld_idx ON public."Spec11ThreatMatch" USING btree (tld);
--
-- Name: user_email_address_idx; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX user_email_address_idx ON public."User" USING btree (email_address);
--
-- Name: UserUpdateHistory fk1s7bopbl3pwrhv3jkkofnv3o0; Type: FK CONSTRAINT; Schema: public; Owner: -
--