1
0
mirror of https://github.com/google/nomulus synced 2026-01-05 13:07:04 +00:00

Create Users when setting up OT&E and Production registrars (#2488)

This commit is contained in:
Lai Jiang
2024-07-03 14:31:07 -04:00
committed by GitHub
parent 54c5a9450d
commit d86c002132
24 changed files with 705 additions and 312 deletions

View File

@@ -96,6 +96,14 @@
<max-retry-duration>3600s</max-retry-duration> <max-retry-duration>3600s</max-retry-duration>
</queue> </queue>
<!-- Queue for tasks that update membership in the console user group. -->
<queue>
<name>console-user-group-update</name>
<max-dispatches-per-second>1</max-dispatches-per-second>
<max-concurrent-dispatches>1</max-concurrent-dispatches>
<max-retry-duration>3600s</max-retry-duration>
</queue>
<!-- Queue for infrequent cron tasks (i.e. hourly or less often) that should retry three times on failure. --> <!-- Queue for infrequent cron tasks (i.e. hourly or less often) that should retry three times on failure. -->
<queue> <queue>
<name>retryable-cron-tasks</name> <name>retryable-cron-tasks</name>

View File

@@ -17,6 +17,7 @@ package google.registry.model;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static google.registry.model.tld.Tld.TldState.GENERAL_AVAILABILITY; 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.model.tld.Tld.TldState.START_DATE_SUNRISE;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
@@ -28,19 +29,28 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.google.common.collect.Streams; 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.pricing.StaticPremiumListPricingEngine;
import google.registry.model.registrar.Registrar; import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarAddress; import google.registry.model.registrar.RegistrarAddress;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.model.tld.Tld; import google.registry.model.tld.Tld;
import google.registry.model.tld.Tld.TldState; import google.registry.model.tld.Tld.TldState;
import google.registry.model.tld.Tld.TldType; import google.registry.model.tld.Tld.TldType;
import google.registry.model.tld.label.PremiumList; import google.registry.model.tld.label.PremiumList;
import google.registry.model.tld.label.PremiumListDao; import google.registry.model.tld.label.PremiumListDao;
import google.registry.persistence.VKey; import google.registry.persistence.VKey;
import google.registry.tools.IamClient;
import google.registry.util.CidrAddressBlock; import google.registry.util.CidrAddressBlock;
import google.registry.util.RegistryEnvironment; import google.registry.util.RegistryEnvironment;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -75,8 +85,8 @@ public final class OteAccountBuilder {
* Validation regex for registrar base client IDs (3-14 lowercase alphanumeric characters). * Validation regex for registrar base client IDs (3-14 lowercase alphanumeric characters).
* *
* <p>The base client ID is appended with numbers to create four different test registrar accounts * <p>The base client ID is appended with numbers to create four different test registrar accounts
* (e.g. reg-1, reg-3, reg-4, reg-5). Registrar client IDs are of type clIDType in eppcom.xsd * (e.g., reg-1, reg-3, reg-4, reg-5). Registrar client IDs are of type clIDType in eppcom.xsd
* which is limited to 16 characters, hence the limit of 14 here to account for the dash and * that is limited to 16 characters, hence the limit of 14 here to account for the dash and
* numbers. * numbers.
* *
* <p>The base client ID is also used to generate the OT&E TLDs, hence the restriction to * <p>The base client ID is also used to generate the OT&E TLDs, hence the restriction to
@@ -113,7 +123,7 @@ public final class OteAccountBuilder {
* The default billing account map applied to all OT&amp;E registrars. * The default billing account map applied to all OT&amp;E registrars.
* *
* <p>This contains dummy values for USD and JPY so that OT&amp;E registrars can be granted access * <p>This contains dummy values for USD and JPY so that OT&amp;E registrars can be granted access
* to all existing TLDs in sandbox. Note that OT&amp;E is only on sandbox and thus these dummy * to all existing TLDs in sandbox. Note that OT&amp;E is only on sandbox, and thus these dummy
* values will never be used in production (the only environment where real invoicing takes * values will never be used in production (the only environment where real invoicing takes
* place). * place).
*/ */
@@ -124,7 +134,7 @@ public final class OteAccountBuilder {
private final Tld sunriseTld; private final Tld sunriseTld;
private final Tld gaTld; private final Tld gaTld;
private final Tld eapTld; private final Tld eapTld;
private final ImmutableList.Builder<RegistrarPoc> contactsBuilder = new ImmutableList.Builder<>(); private final List<User> users = new ArrayList<>();
private ImmutableList<Registrar> registrars; private ImmutableList<Registrar> registrars;
private boolean replaceExisting = false; private boolean replaceExisting = false;
@@ -172,16 +182,28 @@ public final class OteAccountBuilder {
} }
/** /**
* Adds a RegistrarContact with Web Console access. * Adds a {@link User} with Web Console access.
* *
* <p>NOTE: can be called more than once, adding multiple contacts. Each contact will have access * <p>NOTE: can be called more than once, adding multiple users. Each user will have access to all
* to all OT&amp;E Registrars. * OT&amp;E Registrars.
* *
* @param email the contact/login email that will have web-console access to all the Registrars. * @param email the login email that will have web-console access to all the Registrars. Must be
* Must be from "our G Suite domain". * from "our Google Workspace domain".
*/ */
public OteAccountBuilder addContact(String email) { public OteAccountBuilder addUser(String email) {
registrars.forEach(registrar -> contactsBuilder.add(createRegistrarContact(email, registrar))); users.add(
new User.Builder()
.setEmailAddress(email)
.setUserRoles(
new UserRoles.Builder()
.setRegistrarRoles(
registrars.stream()
.collect(
toImmutableMap(
Registrar::getRegistrarId,
registrar -> RegistrarRole.ACCOUNT_MANAGER)))
.build())
.build());
return this; return this;
} }
@@ -217,7 +239,7 @@ public final class OteAccountBuilder {
return transformRegistrars(builder -> builder.setClientCertificate(asciiCert, now)); return transformRegistrars(builder -> builder.setClientCertificate(asciiCert, now));
} }
/** Sets the IP allow list to all the OT&amp;E Registrars. */ /** Sets the IP allowlist to all the OT&amp;E Registrars. */
public OteAccountBuilder setIpAllowList(Collection<String> ipAllowList) { public OteAccountBuilder setIpAllowList(Collection<String> ipAllowList) {
ImmutableList<CidrAddressBlock> ipAddressAllowList = ImmutableList<CidrAddressBlock> ipAddressAllowList =
ipAllowList.stream().map(CidrAddressBlock::create).collect(toImmutableList()); ipAllowList.stream().map(CidrAddressBlock::create).collect(toImmutableList());
@@ -237,18 +259,37 @@ public final class OteAccountBuilder {
} }
/** /**
* Return map from the OT&amp;E registrarIds we will create to the new TLDs they will have access * Return the map from the OT&amp;E registrarIds we will create to the new TLDs they will have
* to. * access to.
*/ */
public ImmutableMap<String, String> getRegistrarIdToTldMap() { public ImmutableMap<String, String> getRegistrarIdToTldMap() {
return registrarIdToTld; return registrarIdToTld;
} }
/** Grants the users permission to pass IAP. */
public void grantIapPermission(
Optional<String> groupEmailAddress, CloudTasksUtils cloudTasksUtils, IamClient iamClient) {
for (User user : users) {
User.grantIapPermission(
user.getEmailAddress(), groupEmailAddress, cloudTasksUtils, iamClient);
}
}
/** Saves all the OT&amp;E entities we created. */ /** Saves all the OT&amp;E entities we created. */
private void saveAllEntities() { private void saveAllEntities() {
// use ImmutableObject instead of Registry so that the Key generation doesn't break
ImmutableList<Tld> registries = ImmutableList.of(sunriseTld, gaTld, eapTld); ImmutableList<Tld> registries = ImmutableList.of(sunriseTld, gaTld, eapTld);
ImmutableList<RegistrarPoc> contacts = contactsBuilder.build(); 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( tm().transact(
() -> { () -> {
@@ -256,8 +297,7 @@ public final class OteAccountBuilder {
ImmutableList<VKey<? extends ImmutableObject>> keys = ImmutableList<VKey<? extends ImmutableObject>> keys =
Streams.concat( Streams.concat(
registries.stream().map(tld -> Tld.createVKey(tld.getTldStr())), registries.stream().map(tld -> Tld.createVKey(tld.getTldStr())),
registrars.stream().map(Registrar::createVKey), registrars.stream().map(Registrar::createVKey))
contacts.stream().map(RegistrarPoc::createVKey))
.collect(toImmutableList()); .collect(toImmutableList());
ImmutableMap<VKey<? extends ImmutableObject>, ImmutableObject> existingObjects = ImmutableMap<VKey<? extends ImmutableObject>, ImmutableObject> existingObjects =
tm().loadByKeysIfPresent(keys); tm().loadByKeysIfPresent(keys);
@@ -275,8 +315,18 @@ public final class OteAccountBuilder {
registrars = registrars.stream().map(this::addAllowedTld).collect(toImmutableList()); registrars = registrars.stream().map(this::addAllowedTld).collect(toImmutableList());
// and we can save the registrars and contacts! // and we can save the registrars and contacts!
tm().putAll(registrars); tm().putAll(registrars);
tm().putAll(contacts);
}); });
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);
}
} }
private Registrar addAllowedTld(Registrar registrar) { private Registrar addAllowedTld(Registrar registrar) {
@@ -336,15 +386,6 @@ public final class OteAccountBuilder {
.build(); .build();
} }
private static RegistrarPoc createRegistrarContact(String email, Registrar registrar) {
return new RegistrarPoc.Builder()
.setRegistrar(registrar)
.setName(email)
.setEmailAddress(email)
.setLoginEmailAddress(email)
.build();
}
/** Returns the registrar IDs of the OT&amp;E, with the TLDs each has access to. */ /** Returns the registrar IDs of the OT&amp;E, with the TLDs each has access to. */
public static ImmutableMap<String, String> createRegistrarIdToTldMap(String baseRegistrarId) { public static ImmutableMap<String, String> createRegistrarIdToTldMap(String baseRegistrarId) {
checkArgument( checkArgument(

View File

@@ -14,7 +14,19 @@
package google.registry.model.console; package google.registry.model.console;
import static google.registry.tools.server.UpdateUserGroupAction.GROUP_UPDATE_QUEUE;
import com.google.cloud.tasks.v2.Task;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.flogger.FluentLogger;
import google.registry.batch.CloudTasksUtils;
import google.registry.persistence.VKey; import google.registry.persistence.VKey;
import google.registry.request.Action.Service;
import google.registry.tools.IamClient;
import google.registry.tools.server.UpdateUserGroupAction;
import google.registry.tools.server.UpdateUserGroupAction.Mode;
import google.registry.util.RegistryEnvironment;
import java.util.Optional;
import javax.persistence.Access; import javax.persistence.Access;
import javax.persistence.AccessType; import javax.persistence.AccessType;
import javax.persistence.Embeddable; import javax.persistence.Embeddable;
@@ -31,6 +43,76 @@ import javax.persistence.Table;
@Table(indexes = {@Index(columnList = "emailAddress", name = "user_email_address_idx")}) @Table(indexes = {@Index(columnList = "emailAddress", name = "user_email_address_idx")})
public class User extends UserBase { public class User extends UserBase {
public static final String IAP_SECURED_WEB_APP_USER_ROLE = "roles/iap.httpsResourceAccessor";
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
/**
* Grants the user permission to pass IAP.
*
* <p>Depending on if a console user group is set up, the permission is granted either
* individually or via group membership.
*/
public static void grantIapPermission(
String emailAddress,
Optional<String> groupEmailAddress,
CloudTasksUtils cloudTasksUtils,
IamClient iamClient) {
if (RegistryEnvironment.isInTestServer()) {
return;
}
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);
}
}
/**
* Revoke the user's permission to pass IAP.
*
* <p>Depending on if a console user group is set up, the permission is revoked either
* individually or via group membership.
*/
public static void revokeIapPermission(
String emailAddress,
Optional<String> groupEmailAddress,
CloudTasksUtils cloudTasksUtils,
IamClient iamClient) {
if (RegistryEnvironment.isInTestServer()) {
return;
}
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);
}
}
private static void modifyGroupMembershipAsync(
String userEmailAddress,
String groupEmailAddress,
CloudTasksUtils cloudTasksUtils,
Mode mode) {
Task task =
cloudTasksUtils.createPostTask(
UpdateUserGroupAction.PATH,
Service.TOOLS,
ImmutableMultimap.of(
"userEmailAddress",
userEmailAddress,
"groupEmailAddress",
groupEmailAddress,
"groupUpdateMode",
mode.name()));
cloudTasksUtils.enqueue(GROUP_UPDATE_QUEUE, task);
}
@Override @Override
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)

View File

@@ -15,30 +15,25 @@
package google.registry.tools; package google.registry.tools;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.model.console.User.grantIapPermission;
import com.beust.jcommander.Parameters; import com.beust.jcommander.Parameters;
import com.google.common.collect.ImmutableMap; import google.registry.batch.CloudTasksUtils;
import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType;
import google.registry.config.RegistryConfig.Config; import google.registry.config.RegistryConfig.Config;
import google.registry.model.console.User; import google.registry.model.console.User;
import google.registry.model.console.UserDao; import google.registry.model.console.UserDao;
import google.registry.tools.server.UpdateUserGroupAction;
import java.util.Optional; import java.util.Optional;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
/** Command to create a new User. */ /** Command to create a new User. */
@Parameters(separators = " =", commandDescription = "Update a user account") @Parameters(separators = " =", commandDescription = "Update a user account")
public class CreateUserCommand extends CreateOrUpdateUserCommand implements CommandWithConnection { public class CreateUserCommand extends CreateOrUpdateUserCommand {
static final String IAP_SECURED_WEB_APP_USER_ROLE = "roles/iap.httpsResourceAccessor";
static final FluentLogger logger = FluentLogger.forEnclosingClass();
private ServiceConnection connection;
@Inject IamClient iamClient; @Inject IamClient iamClient;
@Inject CloudTasksUtils cloudTasksUtils;
@Inject @Inject
@Config("gSuiteConsoleUserGroupEmailAddress") @Config("gSuiteConsoleUserGroupEmailAddress")
Optional<String> maybeGroupEmailAddress; Optional<String> maybeGroupEmailAddress;
@@ -53,29 +48,7 @@ public class CreateUserCommand extends CreateOrUpdateUserCommand implements Comm
@Override @Override
protected String execute() throws Exception { protected String execute() throws Exception {
String ret = super.execute(); String ret = super.execute();
String groupEmailAddress = maybeGroupEmailAddress.orElse(null); grantIapPermission(email, maybeGroupEmailAddress, cloudTasksUtils, iamClient);
if (groupEmailAddress != null) {
logger.atInfo().log("Adding %s to group %s", email, groupEmailAddress);
connection.sendPostRequest(
UpdateUserGroupAction.PATH,
ImmutableMap.of(
"userEmailAddress",
email,
"groupEmailAddress",
groupEmailAddress,
"groupUpdateMode",
"ADD"),
MediaType.PLAIN_TEXT_UTF_8,
new byte[0]);
} else {
logger.atInfo().log("Granting IAP role to user %s", email);
iamClient.addBinding(email, IAP_SECURED_WEB_APP_USER_ROLE);
}
return ret; return ret;
} }
@Override
public void setConnection(ServiceConnection connection) {
this.connection = connection;
}
} }

View File

@@ -15,32 +15,27 @@
package google.registry.tools; package google.registry.tools;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.tools.CreateUserCommand.IAP_SECURED_WEB_APP_USER_ROLE;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import static google.registry.util.PreconditionsUtils.checkArgumentPresent; import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters; import com.beust.jcommander.Parameters;
import com.google.common.collect.ImmutableMap; import google.registry.batch.CloudTasksUtils;
import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType;
import google.registry.config.RegistryConfig.Config; import google.registry.config.RegistryConfig.Config;
import google.registry.model.console.User; import google.registry.model.console.User;
import google.registry.model.console.UserDao; import google.registry.model.console.UserDao;
import google.registry.tools.server.UpdateUserGroupAction;
import java.util.Optional; import java.util.Optional;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
/** Deletes a {@link User}. */ /** Deletes a {@link User}. */
@Parameters(separators = " =", commandDescription = "Delete a user account") @Parameters(separators = " =", commandDescription = "Delete a user account")
public class DeleteUserCommand extends ConfirmingCommand implements CommandWithConnection { public class DeleteUserCommand extends ConfirmingCommand {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private ServiceConnection connection;
@Inject IamClient iamClient; @Inject IamClient iamClient;
@Inject CloudTasksUtils cloudTasksUtils;
@Inject @Inject
@Config("gSuiteConsoleUserGroupEmailAddress") @Config("gSuiteConsoleUserGroupEmailAddress")
Optional<String> maybeGroupEmailAddress; Optional<String> maybeGroupEmailAddress;
@@ -49,11 +44,6 @@ public class DeleteUserCommand extends ConfirmingCommand implements CommandWithC
@Parameter(names = "--email", description = "Email address of the user", required = true) @Parameter(names = "--email", description = "Email address of the user", required = true)
String email; String email;
@Override
public void setConnection(ServiceConnection connection) {
this.connection = connection;
}
@Override @Override
protected String prompt() { protected String prompt() {
checkArgumentNotNull(email, "Email must be provided"); checkArgumentNotNull(email, "Email must be provided");
@@ -69,24 +59,7 @@ public class DeleteUserCommand extends ConfirmingCommand implements CommandWithC
checkArgumentPresent(optionalUser, "Email no longer corresponds to a valid user"); checkArgumentPresent(optionalUser, "Email no longer corresponds to a valid user");
tm().delete(optionalUser.get()); tm().delete(optionalUser.get());
}); });
String groupEmailAddress = maybeGroupEmailAddress.orElse(null); User.revokeIapPermission(email, maybeGroupEmailAddress, cloudTasksUtils, iamClient);
if (groupEmailAddress != null) {
logger.atInfo().log("Removing %s from group %s", email, groupEmailAddress);
connection.sendPostRequest(
UpdateUserGroupAction.PATH,
ImmutableMap.of(
"userEmailAddress",
email,
"groupEmailAddress",
groupEmailAddress,
"groupUpdateMode",
"REMOVE"),
MediaType.PLAIN_TEXT_UTF_8,
new byte[0]);
} else {
logger.atInfo().log("Removing IAP role from user %s", email);
iamClient.removeBinding(email, IAP_SECURED_WEB_APP_USER_ROLE);
}
return String.format("Deleted user with email %s", email); return String.format("Deleted user with email %s", email);
} }
} }

View File

@@ -20,7 +20,7 @@ import com.google.api.services.cloudresourcemanager.model.GetIamPolicyRequest;
import com.google.api.services.cloudresourcemanager.model.Policy; import com.google.api.services.cloudresourcemanager.model.Policy;
import com.google.api.services.cloudresourcemanager.model.SetIamPolicyRequest; import com.google.api.services.cloudresourcemanager.model.SetIamPolicyRequest;
import com.google.common.base.Ascii; import com.google.common.base.Ascii;
import google.registry.config.CredentialModule.LocalCredential; import google.registry.config.CredentialModule.ApplicationDefaultCredential;
import google.registry.config.RegistryConfig.Config; import google.registry.config.RegistryConfig.Config;
import google.registry.util.GoogleCredentialsBundle; import google.registry.util.GoogleCredentialsBundle;
import java.io.IOException; import java.io.IOException;
@@ -38,7 +38,7 @@ public class IamClient {
@Inject @Inject
public IamClient( public IamClient(
@LocalCredential GoogleCredentialsBundle credentialsBundle, @ApplicationDefaultCredential GoogleCredentialsBundle credentialsBundle,
@Config("projectId") String projectId) { @Config("projectId") String projectId) {
this( this(
new CloudResourceManager.Builder( new CloudResourceManager.Builder(

View File

@@ -17,7 +17,7 @@ package google.registry.tools;
import com.google.api.services.dataflow.Dataflow; import com.google.api.services.dataflow.Dataflow;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import google.registry.config.CredentialModule.LocalCredential; import google.registry.config.CredentialModule.ApplicationDefaultCredential;
import google.registry.config.RegistryConfig.Config; import google.registry.config.RegistryConfig.Config;
import google.registry.util.GoogleCredentialsBundle; import google.registry.util.GoogleCredentialsBundle;
@@ -27,7 +27,7 @@ public class RegistryToolDataflowModule {
@Provides @Provides
static Dataflow provideDataflow( static Dataflow provideDataflow(
@LocalCredential GoogleCredentialsBundle credentialsBundle, @ApplicationDefaultCredential GoogleCredentialsBundle credentialsBundle,
@Config("projectId") String projectId) { @Config("projectId") String projectId) {
return new Dataflow.Builder( return new Dataflow.Builder(
credentialsBundle.getHttpTransport(), credentialsBundle.getHttpTransport(),

View File

@@ -22,6 +22,8 @@ import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters; import com.beust.jcommander.Parameters;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.io.MoreFiles; import com.google.common.io.MoreFiles;
import google.registry.batch.CloudTasksUtils;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.OteAccountBuilder; import google.registry.model.OteAccountBuilder;
import google.registry.tools.params.PathParameter; import google.registry.tools.params.PathParameter;
import google.registry.util.Clock; import google.registry.util.Clock;
@@ -30,6 +32,7 @@ import google.registry.util.StringGenerator;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
@@ -47,7 +50,7 @@ final class SetupOteCommand extends ConfirmingCommand {
@Parameter( @Parameter(
names = {"-a", "--ip_allow_list"}, names = {"-a", "--ip_allow_list"},
description = "Comma-separated list of IP addreses or CIDR ranges.", description = "Comma-separated list of IP addresses or CIDR ranges.",
required = true) required = true)
private List<String> ipAllowList = new ArrayList<>(); private List<String> ipAllowList = new ArrayList<>();
@@ -55,7 +58,7 @@ final class SetupOteCommand extends ConfirmingCommand {
names = {"--email"}, names = {"--email"},
description = description =
"The registrar's account to use for console access. " "The registrar's account to use for console access. "
+ "Must be on the registry's G Suite domain.", + "Must be on the registry's Google Workspace domain.",
required = true) required = true)
private String email; private String email;
@@ -76,6 +79,14 @@ final class SetupOteCommand extends ConfirmingCommand {
@Inject Clock clock; @Inject Clock clock;
@Inject CloudTasksUtils cloudTasksUtils;
@Inject IamClient iamClient;
@Inject
@Config("gSuiteConsoleUserGroupEmailAddress")
Optional<String> maybeGroupEmailAddress;
OteAccountBuilder oteAccountBuilder; OteAccountBuilder oteAccountBuilder;
String password; String password;
@@ -87,7 +98,7 @@ final class SetupOteCommand extends ConfirmingCommand {
password = passwordGenerator.createString(PASSWORD_LENGTH); password = passwordGenerator.createString(PASSWORD_LENGTH);
oteAccountBuilder = oteAccountBuilder =
OteAccountBuilder.forRegistrarId(registrar) OteAccountBuilder.forRegistrarId(registrar)
.addContact(email) .addUser(email)
.setPassword(password) .setPassword(password)
.setIpAllowList(ipAllowList) .setIpAllowList(ipAllowList)
.setReplaceExisting(overwrite); .setReplaceExisting(overwrite);
@@ -114,8 +125,11 @@ final class SetupOteCommand extends ConfirmingCommand {
&& RegistryEnvironment.get() != RegistryEnvironment.UNITTEST) { && RegistryEnvironment.get() != RegistryEnvironment.UNITTEST) {
builder.append( builder.append(
String.format( String.format(
"\n\nWARNING: Running against %s environment. Are " """
+ "you sure you didn\'t mean to run this against sandbox (e.g. \"-e SANDBOX\")?",
WARNING: Running against %s environment. Are \
you sure you didn't mean to run this against sandbox (e.g. "-e SANDBOX")?""",
RegistryEnvironment.get())); RegistryEnvironment.get()));
} }
@@ -125,15 +139,15 @@ final class SetupOteCommand extends ConfirmingCommand {
@Override @Override
public String execute() { public String execute() {
ImmutableMap<String, String> clientIdToTld = oteAccountBuilder.buildAndPersist(); ImmutableMap<String, String> clientIdToTld = oteAccountBuilder.buildAndPersist();
oteAccountBuilder.grantIapPermission(maybeGroupEmailAddress, cloudTasksUtils, iamClient);
StringBuilder output = new StringBuilder(); StringBuilder output = new StringBuilder();
output.append("Copy these usernames/passwords back into the onboarding bug:\n\n"); output.append("Copy these usernames/passwords back into the onboarding bug:\n\n");
clientIdToTld.forEach( clientIdToTld.forEach(
(clientId, tld) -> { (clientId, tld) ->
output.append( output.append(
String.format("Login: %s\nPassword: %s\nTLD: %s\n\n", clientId, password, tld)); String.format("Login: %s\nPassword: %s\nTLD: %s\n\n", clientId, password, tld)));
});
return output.toString(); return output.toString();
} }

View File

@@ -34,6 +34,7 @@ import javax.inject.Inject;
public class UpdateUserGroupAction implements Runnable { public class UpdateUserGroupAction implements Runnable {
public static final String PATH = "/_dr/admin/updateUserGroup"; public static final String PATH = "/_dr/admin/updateUserGroup";
public static final String GROUP_UPDATE_QUEUE = "console-user-group-update";
private static final FluentLogger logger = FluentLogger.forEnclosingClass(); private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -53,7 +54,7 @@ public class UpdateUserGroupAction implements Runnable {
@Inject @Inject
UpdateUserGroupAction() {} UpdateUserGroupAction() {}
enum Mode { public enum Mode {
ADD, ADD,
REMOVE REMOVE
} }

View File

@@ -24,6 +24,8 @@ import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.flogger.FluentLogger; import com.google.common.flogger.FluentLogger;
import com.google.template.soy.tofu.SoyTofu; import com.google.template.soy.tofu.SoyTofu;
import google.registry.batch.CloudTasksUtils;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.OteAccountBuilder; import google.registry.model.OteAccountBuilder;
import google.registry.request.Action; import google.registry.request.Action;
import google.registry.request.Action.Method; import google.registry.request.Action.Method;
@@ -31,6 +33,7 @@ import google.registry.request.HttpException.BadRequestException;
import google.registry.request.Parameter; import google.registry.request.Parameter;
import google.registry.request.auth.Auth; import google.registry.request.auth.Auth;
import google.registry.request.auth.AuthenticatedRegistrarAccessor; import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.tools.IamClient;
import google.registry.ui.server.SendEmailUtils; import google.registry.ui.server.SendEmailUtils;
import google.registry.ui.server.SoyTemplateUtils; import google.registry.ui.server.SoyTemplateUtils;
import google.registry.ui.soy.registrar.OteSetupConsoleSoyInfo; import google.registry.ui.soy.registrar.OteSetupConsoleSoyInfo;
@@ -87,6 +90,14 @@ public final class ConsoleOteSetupAction extends HtmlAction {
@Parameter("password") @Parameter("password")
Optional<String> optionalPassword; Optional<String> optionalPassword;
@Inject CloudTasksUtils cloudTasksUtils;
@Inject IamClient iamClient;
@Inject
@Config("gSuiteConsoleUserGroupEmailAddress")
Optional<String> maybeGroupEmailAddress;
@Inject @Inject
ConsoleOteSetupAction() {} ConsoleOteSetupAction() {}
@@ -107,16 +118,11 @@ public final class ConsoleOteSetupAction extends HtmlAction {
return; return;
} }
switch (method) { switch (method) {
case POST -> { case POST -> runPost(data);
runPost(data); case GET -> runGet(data);
} default ->
case GET -> { throw new BadRequestException(
runGet(data); String.format("Action cannot be called with method %s", method));
}
default -> {
throw new BadRequestException(
String.format("Action cannot be called with method %s", method));
}
} }
} }
@@ -133,11 +139,13 @@ public final class ConsoleOteSetupAction extends HtmlAction {
data.put("contactEmail", email.get()); data.put("contactEmail", email.get());
String password = optionalPassword.orElse(passwordGenerator.createString(PASSWORD_LENGTH)); String password = optionalPassword.orElse(passwordGenerator.createString(PASSWORD_LENGTH));
ImmutableMap<String, String> clientIdToTld = OteAccountBuilder oteAccountBuilder =
OteAccountBuilder.forRegistrarId(clientId.get()) OteAccountBuilder.forRegistrarId(clientId.get())
.addContact(email.get()) .addUser(email.get())
.setPassword(password) .setPassword(password);
.buildAndPersist(); ImmutableMap<String, String> clientIdToTld = oteAccountBuilder.buildAndPersist();
oteAccountBuilder.grantIapPermission(maybeGroupEmailAddress, cloudTasksUtils, iamClient);
sendExternalUpdates(clientIdToTld); sendExternalUpdates(clientIdToTld);

View File

@@ -26,10 +26,15 @@ import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.flogger.FluentLogger; import com.google.common.flogger.FluentLogger;
import com.google.template.soy.tofu.SoyTofu; import com.google.template.soy.tofu.SoyTofu;
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.Registrar;
import google.registry.model.registrar.RegistrarAddress; import google.registry.model.registrar.RegistrarAddress;
import google.registry.model.registrar.RegistrarBase.State; import google.registry.model.registrar.RegistrarBase.State;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.request.Action; import google.registry.request.Action;
import google.registry.request.Action.Method; import google.registry.request.Action.Method;
import google.registry.request.Action.Service; import google.registry.request.Action.Service;
@@ -37,6 +42,7 @@ import google.registry.request.HttpException.BadRequestException;
import google.registry.request.Parameter; import google.registry.request.Parameter;
import google.registry.request.auth.Auth; import google.registry.request.auth.Auth;
import google.registry.request.auth.AuthenticatedRegistrarAccessor; import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.tools.IamClient;
import google.registry.ui.server.SendEmailUtils; import google.registry.ui.server.SendEmailUtils;
import google.registry.ui.server.SoyTemplateUtils; import google.registry.ui.server.SoyTemplateUtils;
import google.registry.ui.soy.registrar.AnalyticsSoyInfo; import google.registry.ui.soy.registrar.AnalyticsSoyInfo;
@@ -95,6 +101,14 @@ public final class ConsoleRegistrarCreatorAction extends HtmlAction {
@Parameter("consoleName") @Parameter("consoleName")
Optional<String> name; Optional<String> name;
@Inject CloudTasksUtils cloudTasksUtils;
@Inject IamClient iamClient;
@Inject
@Config("gSuiteConsoleUserGroupEmailAddress")
Optional<String> maybeGroupEmailAddress;
@Inject @Parameter("billingAccount") Optional<String> billingAccount; @Inject @Parameter("billingAccount") Optional<String> billingAccount;
@Inject @Parameter("ianaId") Optional<Integer> ianaId; @Inject @Parameter("ianaId") Optional<Integer> ianaId;
@Inject @Parameter("referralEmail") Optional<String> referralEmail; @Inject @Parameter("referralEmail") Optional<String> referralEmail;
@@ -129,16 +143,11 @@ public final class ConsoleRegistrarCreatorAction extends HtmlAction {
return; return;
} }
switch (method) { switch (method) {
case POST -> { case POST -> runPost(data);
runPost(data); case GET -> runGet(data);
} default ->
case GET -> { throw new BadRequestException(
runGet(data); String.format("Action cannot be called with method %s", method));
}
default -> {
throw new BadRequestException(
String.format("Action cannot be called with method %s", method));
}
} }
} }
@@ -169,7 +178,8 @@ public final class ConsoleRegistrarCreatorAction extends HtmlAction {
list)) list))
.collect( .collect(
toImmutableMap( toImmutableMap(
list -> CurrencyUnit.of(Ascii.toUpperCase(list.get(0))), list -> list.get(1))); list -> CurrencyUnit.of(Ascii.toUpperCase(list.getFirst())),
list -> list.get(1)));
} catch (Throwable e) { } catch (Throwable e) {
throw new RuntimeException("Error parsing billing accounts - " + e.getMessage(), e); throw new RuntimeException("Error parsing billing accounts - " + e.getMessage(), e);
} }
@@ -233,12 +243,15 @@ public final class ConsoleRegistrarCreatorAction extends HtmlAction {
.setZip(optionalZip.orElse(null)) .setZip(optionalZip.orElse(null))
.build()) .build())
.build(); .build();
RegistrarPoc contact = User user =
new RegistrarPoc.Builder() new User.Builder()
.setRegistrar(registrar)
.setName(consoleUserEmail.get())
.setEmailAddress(consoleUserEmail.get()) .setEmailAddress(consoleUserEmail.get())
.setLoginEmailAddress(consoleUserEmail.get()) .setUserRoles(
new UserRoles.Builder()
.setRegistrarRoles(
ImmutableMap.of(
registrar.getRegistrarId(), RegistrarRole.ACCOUNT_MANAGER))
.build())
.build(); .build();
tm().transact( tm().transact(
() -> { () -> {
@@ -246,8 +259,11 @@ public final class ConsoleRegistrarCreatorAction extends HtmlAction {
Registrar.loadByRegistrarId(registrar.getRegistrarId()).isEmpty(), Registrar.loadByRegistrarId(registrar.getRegistrarId()).isEmpty(),
"Registrar with client ID %s already exists", "Registrar with client ID %s already exists",
registrar.getRegistrarId()); registrar.getRegistrarId());
tm().putAll(registrar, contact); tm().put(registrar);
}); });
UserDao.saveUser(user);
User.grantIapPermission(
user.getEmailAddress(), maybeGroupEmailAddress, cloudTasksUtils, iamClient);
data.put("password", password); data.put("password", password);
data.put("passcode", phonePasscode); data.put("passcode", phonePasscode);

View File

@@ -88,22 +88,6 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA
static final String ARGS_PARAM = "args"; static final String ARGS_PARAM = "args";
static final String ID_PARAM = "id"; static final String ID_PARAM = "id";
/**
* Allows task enqueueing to be disabled when executing registrar console test cases.
*
* <p>The existing workflow in UI test cases triggers task enqueueing, which was not an issue with
* Task Queue since it's a native App Engine feature simulated by the App Engine SDK's
* environment. However, with Cloud Tasks, the server enqueues and fails to deliver to the actual
* Cloud Tasks endpoint due to lack of permission.
*
* <p>One way to allow enqueuing in backend test and avoid enqueuing in UI test is to disable
* enqueuing when the test server starts and enable enqueueing once the test server stops. This
* can be done by utilizing a ThreadLocal<Boolean> variable isInTestDriver, which is set to false
* by default. Enqueuing is allowed only if the value of isInTestDriver is false. It's set to true
* in start() and set to false in stop() inside TestDriver.java, a class used in testing.
*/
private static final ThreadLocal<Boolean> isInTestDriver = ThreadLocal.withInitial(() -> false);
@Inject JsonActionRunner jsonActionRunner; @Inject JsonActionRunner jsonActionRunner;
@Inject RegistrarConsoleMetrics registrarConsoleMetrics; @Inject RegistrarConsoleMetrics registrarConsoleMetrics;
@Inject SendEmailUtils sendEmailUtils; @Inject SendEmailUtils sendEmailUtils;
@@ -118,14 +102,6 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA
return contact.getPhoneNumber() != null; return contact.getPhoneNumber() != null;
} }
public static void setIsInTestDriverToFalse() {
isInTestDriver.set(false);
}
public static void setIsInTestDriverToTrue() {
isInTestDriver.set(true);
}
@Override @Override
public void run() { public void run() {
jsonActionRunner.run(this); jsonActionRunner.run(this);
@@ -623,7 +599,7 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA
if (CollectionUtils.difference(changedKeys, "lastUpdateTime").isEmpty()) { if (CollectionUtils.difference(changedKeys, "lastUpdateTime").isEmpty()) {
return; return;
} }
if (!isInTestDriver.get()) { if (!RegistryEnvironment.isInTestServer()) {
// Enqueues a sync registrar sheet task if enqueuing is not triggered by console tests and // Enqueues a sync registrar sheet task if enqueuing is not triggered by console tests and
// there's an update besides the lastUpdateTime // there's an update besides the lastUpdateTime
cloudTasksUtils.enqueue( cloudTasksUtils.enqueue(

View File

@@ -15,6 +15,7 @@
package google.registry.model; package google.registry.model;
import static com.google.common.truth.Truth.assertThat; 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.model.tld.Tld.TldState.GENERAL_AVAILABILITY; 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.model.tld.Tld.TldState.START_DATE_SUNRISE;
import static google.registry.persistence.transaction.JpaTransactionManagerExtension.makeRegistrar1; import static google.registry.persistence.transaction.JpaTransactionManagerExtension.makeRegistrar1;
@@ -26,16 +27,28 @@ import static google.registry.testing.DatabaseHelper.persistSimpleResource;
import static google.registry.util.DateTimeUtils.START_OF_TIME; import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.joda.money.CurrencyUnit.USD; import static org.joda.money.CurrencyUnit.USD;
import static org.junit.jupiter.api.Assertions.assertThrows; 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 com.google.cloud.tasks.v2.HttpMethod;
import com.google.common.collect.ImmutableList; 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.registrar.Registrar;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.model.tld.Tld; import google.registry.model.tld.Tld;
import google.registry.model.tld.Tld.TldState; import google.registry.model.tld.Tld.TldState;
import google.registry.persistence.transaction.JpaTestExtensions; import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension; import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.CloudTasksHelper.TaskMatcher;
import google.registry.tools.IamClient;
import google.registry.util.CidrAddressBlock; import google.registry.util.CidrAddressBlock;
import google.registry.util.SystemClock; import google.registry.util.SystemClock;
import java.util.Optional;
import javax.annotation.Nullable;
import org.joda.money.Money; import org.joda.money.Money;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.DateTimeZone; import org.joda.time.DateTimeZone;
@@ -50,6 +63,9 @@ public final class OteAccountBuilderTest {
final JpaIntegrationTestExtension jpa = final JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().buildIntegrationTestExtension(); new JpaTestExtensions.Builder().buildIntegrationTestExtension();
private final CloudTasksHelper cloudTasksHelper = new CloudTasksHelper();
private final IamClient iamClient = mock(IamClient.class);
@Test @Test
void testGetRegistrarToTldMap() { void testGetRegistrarToTldMap() {
assertThat(OteAccountBuilder.forRegistrarId("myclientid").getRegistrarIdToTldMap()) assertThat(OteAccountBuilder.forRegistrarId("myclientid").getRegistrarIdToTldMap())
@@ -89,23 +105,63 @@ public final class OteAccountBuilderTest {
assertThat(registrar.getAllowedTlds()).containsExactly(tld); assertThat(registrar.getAllowedTlds()).containsExactly(tld);
} }
private static void assertContactExists(String registrarId, String email) { public static void verifyUser(String registrarId, String email) {
Registrar registrar = Registrar.loadByRegistrarId(registrarId).get(); Optional<User> maybeUser = UserDao.loadUser(email);
assertThat(registrar.getContacts().stream().map(RegistrarPoc::getEmailAddress)).contains(email); assertThat(maybeUser).isPresent();
RegistrarPoc contact = assertThat(maybeUser.get().getUserRoles().getRegistrarRoles().get(registrarId))
registrar.getContacts().stream() .isEqualTo(RegistrarRole.ACCOUNT_MANAGER);
.filter(c -> email.equals(c.getEmailAddress())) }
.findAny()
.get(); public static void verifyIapPermission(
assertThat(contact.getEmailAddress()).isEqualTo(email); @Nullable String emailAddress,
assertThat(contact.getLoginEmailAddress()).isEqualTo(email); Optional<String> maybeGroupEmailAddress,
CloudTasksHelper cloudTasksHelper,
IamClient iamClient) {
if (emailAddress == null) {
cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
verifyNoInteractions(iamClient);
} else {
String groupEmailAddress = maybeGroupEmailAddress.orElse(null);
if (groupEmailAddress == null) {
cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
verify(iamClient).addBinding(emailAddress, IAP_SECURED_WEB_APP_USER_ROLE);
} else {
cloudTasksHelper.assertTasksEnqueued(
"console-user-group-update",
new TaskMatcher()
.service("TOOLS")
.method(HttpMethod.POST)
.path("/_dr/admin/updateUserGroup")
.param("userEmailAddress", emailAddress)
.param("groupEmailAddress", groupEmailAddress)
.param("groupUpdateMode", "ADD"));
verifyNoInteractions(iamClient);
}
}
}
@Test
void testUpdateUserGroup() {
CloudTasksUtils cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
OteAccountBuilder.forRegistrarId("myclientid")
.addUser("email@example.com")
.grantIapPermission(Optional.of("console@example.com"), cloudTasksUtils, iamClient);
verifyIapPermission(
"email@example.com", Optional.of("console@example.com"), cloudTasksHelper, iamClient);
}
@Test
void testGrantIndividualPermission() {
CloudTasksUtils cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
OteAccountBuilder.forRegistrarId("myclientid")
.addUser("email@example.com")
.grantIapPermission(Optional.empty(), cloudTasksUtils, iamClient);
verifyIapPermission("email@example.com", Optional.empty(), cloudTasksHelper, iamClient);
} }
@Test @Test
void testCreateOteEntities_success() { void testCreateOteEntities_success() {
OteAccountBuilder.forRegistrarId("myclientid") OteAccountBuilder.forRegistrarId("myclientid").addUser("email@example.com").buildAndPersist();
.addContact("email@example.com")
.buildAndPersist();
assertTldExists("myclientid-sunrise", START_DATE_SUNRISE, Money.zero(USD)); assertTldExists("myclientid-sunrise", START_DATE_SUNRISE, Money.zero(USD));
assertTldExists("myclientid-ga", GENERAL_AVAILABILITY, Money.zero(USD)); assertTldExists("myclientid-ga", GENERAL_AVAILABILITY, Money.zero(USD));
@@ -114,18 +170,18 @@ public final class OteAccountBuilderTest {
assertRegistrarExists("myclientid-3", "myclientid-ga"); assertRegistrarExists("myclientid-3", "myclientid-ga");
assertRegistrarExists("myclientid-4", "myclientid-ga"); assertRegistrarExists("myclientid-4", "myclientid-ga");
assertRegistrarExists("myclientid-5", "myclientid-eap"); assertRegistrarExists("myclientid-5", "myclientid-eap");
assertContactExists("myclientid-1", "email@example.com"); verifyUser("myclientid-1", "email@example.com");
assertContactExists("myclientid-3", "email@example.com"); verifyUser("myclientid-3", "email@example.com");
assertContactExists("myclientid-4", "email@example.com"); verifyUser("myclientid-4", "email@example.com");
assertContactExists("myclientid-5", "email@example.com"); verifyUser("myclientid-5", "email@example.com");
} }
@Test @Test
void testCreateOteEntities_multipleContacts_success() { void testCreateOteEntities_multipleContacts_success() {
OteAccountBuilder.forRegistrarId("myclientid") OteAccountBuilder.forRegistrarId("myclientid")
.addContact("email@example.com") .addUser("email@example.com")
.addContact("other@example.com") .addUser("other@example.com")
.addContact("someone@example.com") .addUser("someone@example.com")
.buildAndPersist(); .buildAndPersist();
assertTldExists("myclientid-sunrise", START_DATE_SUNRISE, Money.zero(USD)); assertTldExists("myclientid-sunrise", START_DATE_SUNRISE, Money.zero(USD));
@@ -135,18 +191,18 @@ public final class OteAccountBuilderTest {
assertRegistrarExists("myclientid-3", "myclientid-ga"); assertRegistrarExists("myclientid-3", "myclientid-ga");
assertRegistrarExists("myclientid-4", "myclientid-ga"); assertRegistrarExists("myclientid-4", "myclientid-ga");
assertRegistrarExists("myclientid-5", "myclientid-eap"); assertRegistrarExists("myclientid-5", "myclientid-eap");
assertContactExists("myclientid-1", "email@example.com"); verifyUser("myclientid-1", "email@example.com");
assertContactExists("myclientid-3", "email@example.com"); verifyUser("myclientid-3", "email@example.com");
assertContactExists("myclientid-4", "email@example.com"); verifyUser("myclientid-4", "email@example.com");
assertContactExists("myclientid-5", "email@example.com"); verifyUser("myclientid-5", "email@example.com");
assertContactExists("myclientid-1", "other@example.com"); verifyUser("myclientid-1", "other@example.com");
assertContactExists("myclientid-3", "other@example.com"); verifyUser("myclientid-3", "other@example.com");
assertContactExists("myclientid-4", "other@example.com"); verifyUser("myclientid-4", "other@example.com");
assertContactExists("myclientid-5", "other@example.com"); verifyUser("myclientid-5", "other@example.com");
assertContactExists("myclientid-1", "someone@example.com"); verifyUser("myclientid-1", "someone@example.com");
assertContactExists("myclientid-3", "someone@example.com"); verifyUser("myclientid-3", "someone@example.com");
assertContactExists("myclientid-4", "someone@example.com"); verifyUser("myclientid-4", "someone@example.com");
assertContactExists("myclientid-5", "someone@example.com"); verifyUser("myclientid-5", "someone@example.com");
} }
@Test @Test
@@ -223,7 +279,7 @@ public final class OteAccountBuilderTest {
OteAccountBuilder oteSetupHelper = OteAccountBuilder.forRegistrarId("myclientid"); OteAccountBuilder oteSetupHelper = OteAccountBuilder.forRegistrarId("myclientid");
IllegalStateException thrown = IllegalStateException thrown =
assertThrows(IllegalStateException.class, () -> oteSetupHelper.buildAndPersist()); assertThrows(IllegalStateException.class, oteSetupHelper::buildAndPersist);
assertThat(thrown) assertThat(thrown)
.hasMessageThat() .hasMessageThat()
.contains("Found existing object(s) conflicting with OT&E objects"); .contains("Found existing object(s) conflicting with OT&E objects");
@@ -236,7 +292,7 @@ public final class OteAccountBuilderTest {
OteAccountBuilder oteSetupHelper = OteAccountBuilder.forRegistrarId("myclientid"); OteAccountBuilder oteSetupHelper = OteAccountBuilder.forRegistrarId("myclientid");
IllegalStateException thrown = IllegalStateException thrown =
assertThrows(IllegalStateException.class, () -> oteSetupHelper.buildAndPersist()); assertThrows(IllegalStateException.class, oteSetupHelper::buildAndPersist);
assertThat(thrown) assertThat(thrown)
.hasMessageThat() .hasMessageThat()
.contains("Found existing object(s) conflicting with OT&E objects"); .contains("Found existing object(s) conflicting with OT&E objects");
@@ -261,7 +317,7 @@ public final class OteAccountBuilderTest {
void testCreateOteEntities_doubleCreation_actuallyReplaces() { void testCreateOteEntities_doubleCreation_actuallyReplaces() {
OteAccountBuilder.forRegistrarId("myclientid") OteAccountBuilder.forRegistrarId("myclientid")
.setPassword("oldPassword") .setPassword("oldPassword")
.addContact("email@example.com") .addUser("email@example.com")
.buildAndPersist(); .buildAndPersist();
assertThat(Registrar.loadByRegistrarId("myclientid-3").get().verifyPassword("oldPassword")) assertThat(Registrar.loadByRegistrarId("myclientid-3").get().verifyPassword("oldPassword"))
@@ -269,7 +325,7 @@ public final class OteAccountBuilderTest {
OteAccountBuilder.forRegistrarId("myclientid") OteAccountBuilder.forRegistrarId("myclientid")
.setPassword("newPassword") .setPassword("newPassword")
.addContact("email@example.com") .addUser("email@example.com")
.setReplaceExisting(true) .setReplaceExisting(true)
.buildAndPersist(); .buildAndPersist();
@@ -281,19 +337,17 @@ public final class OteAccountBuilderTest {
@Test @Test
void testCreateOteEntities_doubleCreation_keepsOldContacts() { void testCreateOteEntities_doubleCreation_keepsOldContacts() {
OteAccountBuilder.forRegistrarId("myclientid") OteAccountBuilder.forRegistrarId("myclientid").addUser("email@example.com").buildAndPersist();
.addContact("email@example.com")
.buildAndPersist();
assertContactExists("myclientid-3", "email@example.com"); verifyUser("myclientid-3", "email@example.com");
OteAccountBuilder.forRegistrarId("myclientid") OteAccountBuilder.forRegistrarId("myclientid")
.addContact("other@example.com") .addUser("other@example.com")
.setReplaceExisting(true) .setReplaceExisting(true)
.buildAndPersist(); .buildAndPersist();
assertContactExists("myclientid-3", "other@example.com"); verifyUser("myclientid-3", "other@example.com");
assertContactExists("myclientid-3", "email@example.com"); verifyUser("myclientid-3", "email@example.com");
} }
@Test @Test

View File

@@ -79,9 +79,7 @@ public final class OteStatsTestHelper {
public static void setupIncompleteOte(String baseClientId) throws IOException { public static void setupIncompleteOte(String baseClientId) throws IOException {
createTld("tld"); createTld("tld");
persistPremiumList("default_sandbox_list", USD, "sandbox,USD 1000"); persistPremiumList("default_sandbox_list", USD, "sandbox,USD 1000");
OteAccountBuilder.forRegistrarId(baseClientId) OteAccountBuilder.forRegistrarId(baseClientId).buildAndPersist();
.addContact("email@example.com")
.buildAndPersist();
String oteAccount1 = String.format("%s-1", baseClientId); String oteAccount1 = String.format("%s-1", baseClientId);
DateTime now = DateTime.now(DateTimeZone.UTC); DateTime now = DateTime.now(DateTimeZone.UTC);
persistResource( persistResource(

View File

@@ -16,11 +16,21 @@ package google.registry.model.console;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects; import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.model.console.User.IAP_SECURED_WEB_APP_USER_ROLE;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static org.junit.jupiter.api.Assertions.assertThrows; 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.verifyNoMoreInteractions;
import com.google.cloud.tasks.v2.HttpMethod;
import google.registry.batch.CloudTasksUtils;
import google.registry.model.EntityTestCase; import google.registry.model.EntityTestCase;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.CloudTasksHelper.TaskMatcher;
import google.registry.testing.DatabaseHelper; import google.registry.testing.DatabaseHelper;
import google.registry.tools.IamClient;
import java.util.Optional;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
/** Tests for {@link User}. */ /** Tests for {@link User}. */
@@ -104,4 +114,56 @@ public class UserTest extends EntityTestCase {
assertThat(user.hasRegistryLockPassword()).isFalse(); assertThat(user.hasRegistryLockPassword()).isFalse();
assertThat(user.verifyRegistryLockPassword("foobar")).isFalse(); assertThat(user.verifyRegistryLockPassword("foobar")).isFalse();
} }
@Test
void testGrantIapPermission() {
CloudTasksHelper cloudTasksHelper = new CloudTasksHelper();
IamClient iamClient = mock(IamClient.class);
CloudTasksUtils cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
// Individual permission.
User.grantIapPermission("email@example.com", Optional.empty(), cloudTasksUtils, 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);
cloudTasksHelper.assertTasksEnqueued(
"console-user-group-update",
new TaskMatcher()
.service("TOOLS")
.method(HttpMethod.POST)
.path("/_dr/admin/updateUserGroup")
.param("userEmailAddress", "email@example.com")
.param("groupEmailAddress", "console@example.com")
.param("groupUpdateMode", "ADD"));
verifyNoMoreInteractions(iamClient);
}
@Test
void testRevokeIapPermission() {
CloudTasksHelper cloudTasksHelper = new CloudTasksHelper();
IamClient iamClient = mock(IamClient.class);
CloudTasksUtils cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
// Individual permission.
User.revokeIapPermission("email@example.com", Optional.empty(), cloudTasksUtils, 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);
cloudTasksHelper.assertTasksEnqueued(
"console-user-group-update",
new TaskMatcher()
.service("TOOLS")
.method(HttpMethod.POST)
.path("/_dr/admin/updateUserGroup")
.param("userEmailAddress", "email@example.com")
.param("groupEmailAddress", "console@example.com")
.param("groupUpdateMode", "REMOVE"));
verifyNoMoreInteractions(iamClient);
}
} }

View File

@@ -135,7 +135,7 @@ public final class RegistryTestServerMain {
final RegistryTestServer server = new RegistryTestServer(address); final RegistryTestServer server = new RegistryTestServer(address);
System.out.printf("%sLoading SQL fixtures and User service...%s\n", BLUE, RESET); System.out.printf("%sLoading SQL fixtures setting User for authentication...%s\n", BLUE, RESET);
UserRoles userRoles = UserRoles userRoles =
new UserRoles.Builder().setIsAdmin(loginIsAdmin).setGlobalRole(GlobalRole.FTE).build(); new UserRoles.Builder().setIsAdmin(loginIsAdmin).setGlobalRole(GlobalRole.FTE).build();
User user = User user =
@@ -151,7 +151,7 @@ public final class RegistryTestServerMain {
for (Fixture fixture : fixtures) { for (Fixture fixture : fixtures) {
fixture.load(); fixture.load();
} }
System.out.printf("%sStarting Jetty6 HTTP Server...%s\n", BLUE, RESET); System.out.printf("%sStarting Jetty HTTP Server...%s\n", BLUE, RESET);
server.start(); server.start();
System.out.printf("%sListening on: %s%s\n", PURPLE, server.getUrl("/"), RESET); System.out.printf("%sListening on: %s%s\n", PURPLE, server.getUrl("/"), RESET);
try { try {

View File

@@ -24,7 +24,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.net.HostAndPort; import com.google.common.net.HostAndPort;
import com.google.common.util.concurrent.SimpleTimeLimiter; import com.google.common.util.concurrent.SimpleTimeLimiter;
import google.registry.ui.server.registrar.RegistrarSettingsAction; import google.registry.util.RegistryEnvironment;
import google.registry.util.UrlChecker; import google.registry.util.UrlChecker;
import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServlet;
import java.net.MalformedURLException; import java.net.MalformedURLException;
@@ -91,7 +91,7 @@ public final class TestServer {
/** Starts the HTTP server in a new thread and returns once it's online. */ /** Starts the HTTP server in a new thread and returns once it's online. */
public void start() { public void start() {
try { try {
RegistrarSettingsAction.setIsInTestDriverToTrue(); RegistryEnvironment.setIsInTestDriver(true);
server.start(); server.start();
} catch (Exception e) { } catch (Exception e) {
throwIfUnchecked(e); throwIfUnchecked(e);
@@ -129,7 +129,7 @@ public final class TestServer {
.callWithTimeout( .callWithTimeout(
() -> { () -> {
server.stop(); server.stop();
RegistrarSettingsAction.setIsInTestDriverToFalse(); RegistryEnvironment.setIsInTestDriver(false);
return null; return null;
}, },
SHUTDOWN_TIMEOUT_MS, SHUTDOWN_TIMEOUT_MS,

View File

@@ -80,12 +80,12 @@ import org.joda.time.DateTime;
* to the same test task container that the original instance pushes to, so that we can make * to the same test task container that the original instance pushes to, so that we can make
* assertions on them by accessing the original instance. We cannot make the test task container * assertions on them by accessing the original instance. We cannot make the test task container
* itself static because we do not want tasks enqueued in previous tests to interfere with latter * itself static because we do not want tasks enqueued in previous tests to interfere with latter
* tests, when they run on the same JVM (and therefore share the same static class members). To * tests when they run on the same JVM (and therefore share the same static class members). To solve
* solve this we put the test container in a static map whose keys are the instance IDs. An * this, we put the test container in a static map whose keys are the instance IDs. An explicitly
* explicitly created new {@link CloudTasksHelper} (as would be created for a new test method) would * created new {@link CloudTasksHelper} (as would be created for a new test method) would have a new
* have a new ID allocated to it, and therefore stores its tasks in a distinct container. A * ID allocated to it, and therefore stores its tasks in a distinct container. A deserialized {@link
* deserialized {@link CloudTasksHelper}, on the other hand, will have the same instance ID and * CloudTasksHelper}, on the other hand, will have the same instance ID and share the same test
* share the same test class container with its progenitor. * class container with its progenitor.
*/ */
public class CloudTasksHelper implements Serializable { public class CloudTasksHelper implements Serializable {
@@ -131,7 +131,7 @@ public class CloudTasksHelper implements Serializable {
*/ */
public void assertTasksEnqueuedWithProperty( public void assertTasksEnqueuedWithProperty(
String queueName, Function<Task, String> propertyGetter, String... expectedTaskProperties) { String queueName, Function<Task, String> propertyGetter, String... expectedTaskProperties) {
// Ordering is irrelevant but duplicates should be considered independently. // Ordering is irrelevant, but duplicates should be considered independently.
assertThat(getTestTasksFor(queueName).stream().map(propertyGetter)) assertThat(getTestTasksFor(queueName).stream().map(propertyGetter))
.containsExactly((Object[]) expectedTaskProperties); .containsExactly((Object[]) expectedTaskProperties);
} }
@@ -285,7 +285,7 @@ public class CloudTasksHelper implements Serializable {
}); });
headers = headerBuilder.build(); headers = headerBuilder.build();
ImmutableMultimap.Builder<String, String> paramBuilder = new ImmutableMultimap.Builder<>(); ImmutableMultimap.Builder<String, String> paramBuilder = new ImmutableMultimap.Builder<>();
// Note that UriParameters.parse() does not throw an IAE on a bad query string (e.g. one // Note that UriParameters.parse() does not throw an IAE on a bad query string (e.g., one
// where parameters are not properly URL-encoded); it always does a best-effort parse. // where parameters are not properly URL-encoded); it always does a best-effort parse.
if (method == HttpMethod.GET && uri.getQuery() != null) { if (method == HttpMethod.GET && uri.getQuery() != null) {
paramBuilder.putAll(UriParameters.parse(uri.getQuery())); paramBuilder.putAll(UriParameters.parse(uri.getQuery()));
@@ -382,7 +382,7 @@ public class CloudTasksHelper implements Serializable {
* the same contract as {@link #equals}, since it will ignore null fields. * the same contract as {@link #equals}, since it will ignore null fields.
* *
* <p>Match fails if any headers or params expected on the TaskMatcher are not found on the * <p>Match fails if any headers or params expected on the TaskMatcher are not found on the
* Task. Note that the inverse is not true (i.e. there may be extra headers on the Task). * Task. Note that the inverse is not true (i.e., there may be extra headers on the Task).
* *
* <p>Schedule time by default is Timestamp.getDefaultInstance() or null. * <p>Schedule time by default is Timestamp.getDefaultInstance() or null.
*/ */

View File

@@ -15,20 +15,22 @@
package google.registry.tools; package google.registry.tools;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static google.registry.tools.CreateUserCommand.IAP_SECURED_WEB_APP_USER_ROLE; import static google.registry.model.console.User.IAP_SECURED_WEB_APP_USER_ROLE;
import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions;
import com.google.cloud.tasks.v2.HttpMethod;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.net.MediaType;
import google.registry.model.console.GlobalRole; import google.registry.model.console.GlobalRole;
import google.registry.model.console.RegistrarRole; import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User; import google.registry.model.console.User;
import google.registry.model.console.UserDao; 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.testing.DatabaseHelper;
import java.util.Optional; import java.util.Optional;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@@ -38,13 +40,13 @@ import org.junit.jupiter.api.Test;
public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> { public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
private final IamClient iamClient = mock(IamClient.class); private final IamClient iamClient = mock(IamClient.class);
private final ServiceConnection connection = mock(ServiceConnection.class); private final CloudTasksHelper cloudTasksHelper = new CloudTasksHelper();
@BeforeEach @BeforeEach
void beforeEach() { void beforeEach() {
command.iamClient = iamClient; command.iamClient = iamClient;
command.maybeGroupEmailAddress = Optional.empty(); command.maybeGroupEmailAddress = Optional.empty();
command.setConnection(connection); command.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
} }
@Test @Test
@@ -57,7 +59,7 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
assertThat(onlyUser.getUserRoles().getRegistrarRoles()).isEmpty(); assertThat(onlyUser.getUserRoles().getRegistrarRoles()).isEmpty();
verify(iamClient).addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE); verify(iamClient).addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE);
verifyNoMoreInteractions(iamClient); verifyNoMoreInteractions(iamClient);
verifyNoInteractions(connection); cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
} }
@Test @Test
@@ -69,20 +71,16 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
assertThat(onlyUser.getUserRoles().isAdmin()).isFalse(); assertThat(onlyUser.getUserRoles().isAdmin()).isFalse();
assertThat(onlyUser.getUserRoles().getGlobalRole()).isEqualTo(GlobalRole.NONE); assertThat(onlyUser.getUserRoles().getGlobalRole()).isEqualTo(GlobalRole.NONE);
assertThat(onlyUser.getUserRoles().getRegistrarRoles()).isEmpty(); assertThat(onlyUser.getUserRoles().getRegistrarRoles()).isEmpty();
verify(connection) cloudTasksHelper.assertTasksEnqueued(
.sendPostRequest( "console-user-group-update",
"/_dr/admin/updateUserGroup", new TaskMatcher()
ImmutableMap.of( .method(HttpMethod.POST)
"userEmailAddress", .service("TOOLS")
"user@example.test", .path("/_dr/admin/updateUserGroup")
"groupEmailAddress", .param("userEmailAddress", "user@example.test")
"group@example.test", .param("groupEmailAddress", "group@example.test")
"groupUpdateMode", .param("groupUpdateMode", "ADD"));
"ADD"),
MediaType.PLAIN_TEXT_UTF_8,
new byte[0]);
verifyNoInteractions(iamClient); verifyNoInteractions(iamClient);
verifyNoMoreInteractions(connection);
} }
@Test @Test
@@ -102,7 +100,7 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().isAdmin()).isTrue(); assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().isAdmin()).isTrue();
verify(iamClient).addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE); verify(iamClient).addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE);
verifyNoMoreInteractions(iamClient); verifyNoMoreInteractions(iamClient);
verifyNoInteractions(connection); cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
} }
@Test @Test
@@ -112,7 +110,7 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
.isEqualTo(GlobalRole.FTE); .isEqualTo(GlobalRole.FTE);
verify(iamClient).addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE); verify(iamClient).addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE);
verifyNoMoreInteractions(iamClient); verifyNoMoreInteractions(iamClient);
verifyNoInteractions(connection); cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
} }
@Test @Test
@@ -131,7 +129,7 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
RegistrarRole.PRIMARY_CONTACT)); RegistrarRole.PRIMARY_CONTACT));
verify(iamClient).addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE); verify(iamClient).addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE);
verifyNoMoreInteractions(iamClient); verifyNoMoreInteractions(iamClient);
verifyNoInteractions(connection); cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
} }
@Test @Test
@@ -146,7 +144,7 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
.hasMessageThat() .hasMessageThat()
.isEqualTo("A user with email user@example.test already exists"); .isEqualTo("A user with email user@example.test already exists");
verifyNoMoreInteractions(iamClient); verifyNoMoreInteractions(iamClient);
verifyNoInteractions(connection); cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
} }
@Test @Test

View File

@@ -15,16 +15,17 @@
package google.registry.tools; package google.registry.tools;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static google.registry.tools.CreateUserCommand.IAP_SECURED_WEB_APP_USER_ROLE; import static google.registry.model.console.User.IAP_SECURED_WEB_APP_USER_ROLE;
import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions;
import com.google.common.collect.ImmutableMap; import com.google.cloud.tasks.v2.HttpMethod;
import com.google.common.net.MediaType;
import google.registry.model.console.UserDao; 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.testing.DatabaseHelper;
import java.util.Optional; import java.util.Optional;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@@ -34,13 +35,13 @@ import org.junit.jupiter.api.Test;
public class DeleteUserCommandTest extends CommandTestCase<DeleteUserCommand> { public class DeleteUserCommandTest extends CommandTestCase<DeleteUserCommand> {
private final IamClient iamClient = mock(IamClient.class); private final IamClient iamClient = mock(IamClient.class);
private final ServiceConnection connection = mock(ServiceConnection.class); private final CloudTasksHelper cloudTasksHelper = new CloudTasksHelper();
@BeforeEach @BeforeEach
void beforeEach() { void beforeEach() {
command.iamClient = iamClient; command.iamClient = iamClient;
command.setConnection(connection);
command.maybeGroupEmailAddress = Optional.empty(); command.maybeGroupEmailAddress = Optional.empty();
command.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
} }
@Test @Test
@@ -51,7 +52,7 @@ public class DeleteUserCommandTest extends CommandTestCase<DeleteUserCommand> {
assertThat(UserDao.loadUser("email@example.test")).isEmpty(); assertThat(UserDao.loadUser("email@example.test")).isEmpty();
verify(iamClient).removeBinding("email@example.test", IAP_SECURED_WEB_APP_USER_ROLE); verify(iamClient).removeBinding("email@example.test", IAP_SECURED_WEB_APP_USER_ROLE);
verifyNoMoreInteractions(iamClient); verifyNoMoreInteractions(iamClient);
verifyNoInteractions(connection); cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
} }
@Test @Test
@@ -61,20 +62,16 @@ public class DeleteUserCommandTest extends CommandTestCase<DeleteUserCommand> {
assertThat(UserDao.loadUser("email@example.test")).isPresent(); assertThat(UserDao.loadUser("email@example.test")).isPresent();
runCommandForced("--email", "email@example.test"); runCommandForced("--email", "email@example.test");
assertThat(UserDao.loadUser("email@example.test")).isEmpty(); assertThat(UserDao.loadUser("email@example.test")).isEmpty();
verify(connection) cloudTasksHelper.assertTasksEnqueued(
.sendPostRequest( "console-user-group-update",
"/_dr/admin/updateUserGroup", new TaskMatcher()
ImmutableMap.of( .method(HttpMethod.POST)
"userEmailAddress", .service("TOOLS")
"email@example.test", .path("/_dr/admin/updateUserGroup")
"groupEmailAddress", .param("userEmailAddress", "email@example.test")
"group@example.test", .param("groupEmailAddress", "group@example.test")
"groupUpdateMode", .param("groupUpdateMode", "REMOVE"));
"REMOVE"),
MediaType.PLAIN_TEXT_UTF_8,
new byte[0]);
verifyNoInteractions(iamClient); verifyNoInteractions(iamClient);
verifyNoMoreInteractions(connection);
} }
@Test @Test
@@ -86,6 +83,6 @@ public class DeleteUserCommandTest extends CommandTestCase<DeleteUserCommand> {
.hasMessageThat() .hasMessageThat()
.isEqualTo("Email does not correspond to a valid user"); .isEqualTo("Email does not correspond to a valid user");
verifyNoInteractions(iamClient); verifyNoInteractions(iamClient);
verifyNoInteractions(connection); cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
} }
} }

View File

@@ -15,6 +15,8 @@
package google.registry.tools; package google.registry.tools;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.OteAccountBuilderTest.verifyUser;
import static google.registry.model.console.User.IAP_SECURED_WEB_APP_USER_ROLE;
import static google.registry.model.registrar.RegistrarBase.State.ACTIVE; import static google.registry.model.registrar.RegistrarBase.State.ACTIVE;
import static google.registry.model.tld.Tld.TldState.GENERAL_AVAILABILITY; 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.model.tld.Tld.TldState.START_DATE_SUNRISE;
@@ -26,19 +28,30 @@ import static google.registry.testing.DatabaseHelper.persistResource;
import static org.joda.money.CurrencyUnit.USD; import static org.joda.money.CurrencyUnit.USD;
import static org.joda.time.DateTimeZone.UTC; import static org.joda.time.DateTimeZone.UTC;
import static org.junit.jupiter.api.Assertions.assertThrows; 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 com.beust.jcommander.ParameterException; import com.beust.jcommander.ParameterException;
import com.google.cloud.tasks.v2.HttpMethod;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap; 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.registrar.Registrar;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.model.tld.Tld; import google.registry.model.tld.Tld;
import google.registry.model.tld.Tld.TldState; import google.registry.model.tld.Tld.TldState;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.CloudTasksHelper.TaskMatcher;
import google.registry.testing.DeterministicStringGenerator; import google.registry.testing.DeterministicStringGenerator;
import google.registry.testing.FakeClock; import google.registry.testing.FakeClock;
import google.registry.util.CidrAddressBlock; import google.registry.util.CidrAddressBlock;
import java.security.cert.CertificateParsingException; import java.security.cert.CertificateParsingException;
import java.util.Optional;
import javax.annotation.Nullable;
import org.joda.money.Money; import org.joda.money.Money;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.Duration; import org.joda.time.Duration;
@@ -50,22 +63,24 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
private static final String PASSWORD = "abcdefghijklmnop"; private static final String PASSWORD = "abcdefghijklmnop";
private DeterministicStringGenerator passwordGenerator = private final IamClient iamClient = mock(IamClient.class);
private final CloudTasksHelper cloudTasksHelper = new CloudTasksHelper();
private final DeterministicStringGenerator passwordGenerator =
new DeterministicStringGenerator("abcdefghijklmnopqrstuvwxyz"); new DeterministicStringGenerator("abcdefghijklmnopqrstuvwxyz");
@BeforeEach @BeforeEach
void beforeEach() { void beforeEach() {
command.passwordGenerator = passwordGenerator; command.passwordGenerator = passwordGenerator;
command.clock = new FakeClock(DateTime.parse("2018-07-07TZ")); command.clock = new FakeClock(DateTime.parse("2018-07-07TZ"));
command.maybeGroupEmailAddress = Optional.of("group@example.com");
command.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
command.iamClient = iamClient;
persistPremiumList("default_sandbox_list", USD, "sandbox,USD 1000"); persistPremiumList("default_sandbox_list", USD, "sandbox,USD 1000");
} }
/** Verify TLD creation. */ /** Verify TLD creation. */
private void verifyTldCreation( private void verifyTldCreation(
String tldName, String tldName, String roidSuffix, TldState tldState, boolean isEarlyAccess) {
String roidSuffix,
TldState tldState,
boolean isEarlyAccess) {
Tld registry = Tld.get(tldName); Tld registry = Tld.get(tldName);
assertThat(registry).isNotNull(); assertThat(registry).isNotNull();
assertThat(registry.getRoidSuffix()).isEqualTo(roidSuffix); assertThat(registry.getRoidSuffix()).isEqualTo(roidSuffix);
@@ -107,13 +122,28 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
assertThat(registrar.getClientCertificateHash()).hasValue(SAMPLE_CERT_HASH); assertThat(registrar.getClientCertificateHash()).hasValue(SAMPLE_CERT_HASH);
} }
private void verifyRegistrarContactCreation(String registrarName, String email) { private void verifyIapPermission(@Nullable String emailAddress) {
ImmutableSet<RegistrarPoc> registrarPocs = loadRegistrar(registrarName).getContacts(); if (emailAddress == null) {
assertThat(registrarPocs).hasSize(1); cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
RegistrarPoc registrarPoc = registrarPocs.stream().findAny().get(); verifyNoInteractions(iamClient);
assertThat(registrarPoc.getEmailAddress()).isEqualTo(email); } else {
assertThat(registrarPoc.getName()).isEqualTo(email); String groupEmailAddress = command.maybeGroupEmailAddress.orElse(null);
assertThat(registrarPoc.getLoginEmailAddress()).isEqualTo(email); if (groupEmailAddress == null) {
cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
verify(iamClient).addBinding(emailAddress, IAP_SECURED_WEB_APP_USER_ROLE);
} else {
cloudTasksHelper.assertTasksEnqueued(
"console-user-group-update",
new TaskMatcher()
.service("TOOLS")
.method(HttpMethod.POST)
.path("/_dr/admin/updateUserGroup")
.param("userEmailAddress", emailAddress)
.param("groupEmailAddress", groupEmailAddress)
.param("groupUpdateMode", "ADD"));
verifyNoInteractions(iamClient);
}
}
} }
@Test @Test
@@ -136,10 +166,12 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
verifyRegistrarCreation("blobio-4", "blobio-ga", PASSWORD, ipAddress); verifyRegistrarCreation("blobio-4", "blobio-ga", PASSWORD, ipAddress);
verifyRegistrarCreation("blobio-5", "blobio-eap", PASSWORD, ipAddress); verifyRegistrarCreation("blobio-5", "blobio-eap", PASSWORD, ipAddress);
verifyRegistrarContactCreation("blobio-1", "contact@email.com"); verifyUser("blobio-1", "contact@email.com");
verifyRegistrarContactCreation("blobio-3", "contact@email.com"); verifyUser("blobio-3", "contact@email.com");
verifyRegistrarContactCreation("blobio-4", "contact@email.com"); verifyUser("blobio-4", "contact@email.com");
verifyRegistrarContactCreation("blobio-5", "contact@email.com"); verifyUser("blobio-5", "contact@email.com");
verifyIapPermission("contact@email.com");
} }
@Test @Test
@@ -162,10 +194,12 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
verifyRegistrarCreation("abc-4", "abc-ga", PASSWORD, ipAddress); verifyRegistrarCreation("abc-4", "abc-ga", PASSWORD, ipAddress);
verifyRegistrarCreation("abc-5", "abc-eap", PASSWORD, ipAddress); verifyRegistrarCreation("abc-5", "abc-eap", PASSWORD, ipAddress);
verifyRegistrarContactCreation("abc-1", "abc@email.com"); verifyUser("abc-1", "abc@email.com");
verifyRegistrarContactCreation("abc-3", "abc@email.com"); verifyUser("abc-3", "abc@email.com");
verifyRegistrarContactCreation("abc-4", "abc@email.com"); verifyUser("abc-4", "abc@email.com");
verifyRegistrarContactCreation("abc-5", "abc@email.com"); verifyUser("abc-5", "abc@email.com");
verifyIapPermission("abc@email.com");
} }
@Test @Test
@@ -180,19 +214,20 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
verifyTldCreation("blobio-ga", "BLOBIOG2", GENERAL_AVAILABILITY, false); verifyTldCreation("blobio-ga", "BLOBIOG2", GENERAL_AVAILABILITY, false);
verifyTldCreation("blobio-eap", "BLOBIOE3", GENERAL_AVAILABILITY, true); verifyTldCreation("blobio-eap", "BLOBIOE3", GENERAL_AVAILABILITY, true);
ImmutableList<CidrAddressBlock> ipAddresses = ImmutableList.of( ImmutableList<CidrAddressBlock> ipAddresses =
CidrAddressBlock.create("1.1.1.1"), ImmutableList.of(CidrAddressBlock.create("1.1.1.1"), CidrAddressBlock.create("2.2.2.2"));
CidrAddressBlock.create("2.2.2.2"));
verifyRegistrarCreation("blobio-1", "blobio-sunrise", PASSWORD, ipAddresses); verifyRegistrarCreation("blobio-1", "blobio-sunrise", PASSWORD, ipAddresses);
verifyRegistrarCreation("blobio-3", "blobio-ga", PASSWORD, ipAddresses); verifyRegistrarCreation("blobio-3", "blobio-ga", PASSWORD, ipAddresses);
verifyRegistrarCreation("blobio-4", "blobio-ga", PASSWORD, ipAddresses); verifyRegistrarCreation("blobio-4", "blobio-ga", PASSWORD, ipAddresses);
verifyRegistrarCreation("blobio-5", "blobio-eap", PASSWORD, ipAddresses); verifyRegistrarCreation("blobio-5", "blobio-eap", PASSWORD, ipAddresses);
verifyRegistrarContactCreation("blobio-1", "contact@email.com"); verifyUser("blobio-1", "contact@email.com");
verifyRegistrarContactCreation("blobio-3", "contact@email.com"); verifyUser("blobio-3", "contact@email.com");
verifyRegistrarContactCreation("blobio-4", "contact@email.com"); verifyUser("blobio-4", "contact@email.com");
verifyRegistrarContactCreation("blobio-5", "contact@email.com"); verifyUser("blobio-5", "contact@email.com");
verifyIapPermission("contact@email.com");
} }
@Test @Test
@@ -206,6 +241,7 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
"--email=contact@email.com", "--email=contact@email.com",
"--certfile=" + getCertFilename())); "--certfile=" + getCertFilename()));
assertThat(thrown).hasMessageThat().contains("option is required: [-a | --ip_allow_list]"); assertThat(thrown).hasMessageThat().contains("option is required: [-a | --ip_allow_list]");
verifyIapPermission(null);
} }
@Test @Test
@@ -219,6 +255,7 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
"--email=contact@email.com", "--email=contact@email.com",
"--certfile=" + getCertFilename())); "--certfile=" + getCertFilename()));
assertThat(thrown).hasMessageThat().contains("option is required: [-r | --registrar]"); assertThat(thrown).hasMessageThat().contains("option is required: [-r | --registrar]");
verifyIapPermission(null);
} }
@Test @Test
@@ -232,6 +269,7 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
assertThat(thrown) assertThat(thrown)
.hasMessageThat() .hasMessageThat()
.contains("Must specify exactly one client certificate file."); .contains("Must specify exactly one client certificate file.");
verifyIapPermission(null);
} }
@Test @Test
@@ -245,6 +283,36 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
"--certfile=" + getCertFilename(), "--certfile=" + getCertFilename(),
"--registrar=blobio")); "--registrar=blobio"));
assertThat(thrown).hasMessageThat().contains("option is required: [--email]"); assertThat(thrown).hasMessageThat().contains("option is required: [--email]");
verifyIapPermission(null);
}
@Test
void testSuccess_noConsoleUserGroup() throws Exception {
command.maybeGroupEmailAddress = Optional.empty();
runCommandForced(
"--ip_allow_list=1.1.1.1",
"--registrar=blobio",
"--email=contact@email.com",
"--certfile=" + getCertFilename());
verifyTldCreation("blobio-sunrise", "BLOBIOS0", START_DATE_SUNRISE, false);
verifyTldCreation("blobio-ga", "BLOBIOG2", GENERAL_AVAILABILITY, false);
verifyTldCreation("blobio-eap", "BLOBIOE3", GENERAL_AVAILABILITY, true);
ImmutableList<CidrAddressBlock> ipAddress =
ImmutableList.of(CidrAddressBlock.create("1.1.1.1"));
verifyRegistrarCreation("blobio-1", "blobio-sunrise", PASSWORD, ipAddress);
verifyRegistrarCreation("blobio-3", "blobio-ga", PASSWORD, ipAddress);
verifyRegistrarCreation("blobio-4", "blobio-ga", PASSWORD, ipAddress);
verifyRegistrarCreation("blobio-5", "blobio-eap", PASSWORD, ipAddress);
verifyUser("blobio-1", "contact@email.com");
verifyUser("blobio-3", "contact@email.com");
verifyUser("blobio-4", "contact@email.com");
verifyUser("blobio-5", "contact@email.com");
verifyIapPermission("contact@email.com");
} }
@Test @Test
@@ -259,6 +327,7 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
"--email=contact@email.com", "--email=contact@email.com",
"--certfile=/dev/null")); "--certfile=/dev/null"));
assertThat(thrown).hasMessageThat().contains("No X509Certificate found"); assertThat(thrown).hasMessageThat().contains("No X509Certificate found");
verifyIapPermission(null);
} }
@Test @Test
@@ -273,6 +342,7 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
"--email=contact@email.com", "--email=contact@email.com",
"--certfile=" + getCertFilename())); "--certfile=" + getCertFilename()));
assertThat(thrown).hasMessageThat().contains("Invalid registrar name: 3blo-bio"); assertThat(thrown).hasMessageThat().contains("Invalid registrar name: 3blo-bio");
verifyIapPermission(null);
} }
@Test @Test
@@ -287,6 +357,7 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
"--email=contact@email.com", "--email=contact@email.com",
"--certfile=" + getCertFilename())); "--certfile=" + getCertFilename()));
assertThat(thrown).hasMessageThat().contains("Invalid registrar name: bl"); assertThat(thrown).hasMessageThat().contains("Invalid registrar name: bl");
verifyIapPermission(null);
} }
@Test @Test
@@ -301,6 +372,7 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
"--email=contact@email.com", "--email=contact@email.com",
"--certfile=" + getCertFilename())); "--certfile=" + getCertFilename()));
assertThat(thrown).hasMessageThat().contains("Invalid registrar name: blobiotoooolong"); assertThat(thrown).hasMessageThat().contains("Invalid registrar name: blobiotoooolong");
verifyIapPermission(null);
} }
@Test @Test
@@ -315,6 +387,7 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
"--email=contact@email.com", "--email=contact@email.com",
"--certfile=" + getCertFilename())); "--certfile=" + getCertFilename()));
assertThat(thrown).hasMessageThat().contains("Invalid registrar name: blo#bio"); assertThat(thrown).hasMessageThat().contains("Invalid registrar name: blo#bio");
verifyIapPermission(null);
} }
@Test @Test
@@ -330,6 +403,7 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
"--email=contact@email.com", "--email=contact@email.com",
"--certfile=" + getCertFilename())); "--certfile=" + getCertFilename()));
assertThat(thrown).hasMessageThat().contains("VKey<Tld>(sql:blobio-sunrise)"); assertThat(thrown).hasMessageThat().contains("VKey<Tld>(sql:blobio-sunrise)");
verifyIapPermission(null);
} }
@Test @Test
@@ -345,6 +419,8 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
verifyTldCreation("blobio-sunrise", "BLOBIOS0", START_DATE_SUNRISE, false); verifyTldCreation("blobio-sunrise", "BLOBIOS0", START_DATE_SUNRISE, false);
verifyTldCreation("blobio-ga", "BLOBIOG2", GENERAL_AVAILABILITY, false); verifyTldCreation("blobio-ga", "BLOBIOG2", GENERAL_AVAILABILITY, false);
verifyIapPermission("contact@email.com");
} }
@Test @Test
@@ -366,6 +442,28 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
"--email=contact@email.com", "--email=contact@email.com",
"--certfile=" + getCertFilename())); "--certfile=" + getCertFilename()));
assertThat(thrown).hasMessageThat().contains("VKey<Registrar>(sql:blobio-1)"); assertThat(thrown).hasMessageThat().contains("VKey<Registrar>(sql:blobio-1)");
verifyIapPermission(null);
}
@Test
void testFailure_userExists() {
User user =
new User.Builder()
.setEmailAddress("contact@email.com")
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build())
.build();
UserDao.saveUser(user);
IllegalStateException thrown =
assertThrows(
IllegalStateException.class,
() ->
runCommandForced(
"--ip_allow_list=1.1.1.1",
"--registrar=blobio",
"--email=contact@email.com",
"--certfile=" + getCertFilename()));
assertThat(thrown).hasMessageThat().contains("Found existing users: {contact@email.com");
verifyIapPermission(null);
} }
@Test @Test
@@ -385,10 +483,46 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
"--email=contact@email.com", "--email=contact@email.com",
"--certfile=" + getCertFilename()); "--certfile=" + getCertFilename());
ImmutableList<CidrAddressBlock> ipAddress = ImmutableList.of( ImmutableList<CidrAddressBlock> ipAddress =
CidrAddressBlock.create("1.1.1.1")); ImmutableList.of(CidrAddressBlock.create("1.1.1.1"));
verifyRegistrarCreation("blobio-1", "blobio-sunrise", PASSWORD, ipAddress); verifyRegistrarCreation("blobio-1", "blobio-sunrise", PASSWORD, ipAddress);
verifyRegistrarCreation("blobio-3", "blobio-ga", PASSWORD, ipAddress); verifyRegistrarCreation("blobio-3", "blobio-ga", PASSWORD, ipAddress);
verifyIapPermission("contact@email.com");
}
@Test
void testSuccess_userExists_replaceExisting() throws Exception {
User user =
new User.Builder()
.setEmailAddress("contact@email.com")
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build())
.build();
UserDao.saveUser(user);
runCommandForced(
"--overwrite",
"--ip_allow_list=1.1.1.1",
"--registrar=blobio",
"--email=contact@email.com",
"--certfile=" + getCertFilename());
ImmutableList<CidrAddressBlock> ipAddress =
ImmutableList.of(CidrAddressBlock.create("1.1.1.1"));
verifyRegistrarCreation("blobio-1", "blobio-sunrise", PASSWORD, ipAddress);
verifyRegistrarCreation("blobio-3", "blobio-ga", PASSWORD, ipAddress);
verifyUser("blobio-1", "contact@email.com");
verifyUser("blobio-3", "contact@email.com");
verifyUser("blobio-4", "contact@email.com");
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())
.isEqualTo(GlobalRole.NONE);
verifyIapPermission("contact@email.com");
} }
} }

View File

@@ -15,11 +15,14 @@
package google.registry.ui.server.registrar; package google.registry.ui.server.registrar;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.OteAccountBuilderTest.verifyIapPermission;
import static google.registry.model.OteAccountBuilderTest.verifyUser;
import static google.registry.model.registrar.Registrar.loadByRegistrarId; import static google.registry.model.registrar.Registrar.loadByRegistrarId;
import static google.registry.testing.DatabaseHelper.persistPremiumList; import static google.registry.testing.DatabaseHelper.persistPremiumList;
import static jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; import static jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
import static org.joda.money.CurrencyUnit.USD; import static org.joda.money.CurrencyUnit.USD;
import static org.junit.jupiter.api.Assertions.assertThrows; 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.verify;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@@ -35,10 +38,12 @@ import google.registry.request.Action.Method;
import google.registry.request.auth.AuthResult; import google.registry.request.auth.AuthResult;
import google.registry.request.auth.AuthenticatedRegistrarAccessor; import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.security.XsrfTokenManager; import google.registry.security.XsrfTokenManager;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.DeterministicStringGenerator; import google.registry.testing.DeterministicStringGenerator;
import google.registry.testing.FakeClock; import google.registry.testing.FakeClock;
import google.registry.testing.FakeResponse; import google.registry.testing.FakeResponse;
import google.registry.testing.SystemPropertyExtension; import google.registry.testing.SystemPropertyExtension;
import google.registry.tools.IamClient;
import google.registry.ui.server.SendEmailUtils; import google.registry.ui.server.SendEmailUtils;
import google.registry.util.EmailMessage; import google.registry.util.EmailMessage;
import google.registry.util.RegistryEnvironment; import google.registry.util.RegistryEnvironment;
@@ -65,6 +70,8 @@ public final class ConsoleOteSetupActionTest {
@Order(value = Integer.MAX_VALUE) @Order(value = Integer.MAX_VALUE)
final SystemPropertyExtension systemPropertyExtension = new SystemPropertyExtension(); final SystemPropertyExtension systemPropertyExtension = new SystemPropertyExtension();
private final IamClient iamClient = mock(IamClient.class);
private final CloudTasksHelper cloudTasksHelper = new CloudTasksHelper();
private final FakeResponse response = new FakeResponse(); private final FakeResponse response = new FakeResponse();
private final ConsoleOteSetupAction action = new ConsoleOteSetupAction(); private final ConsoleOteSetupAction action = new ConsoleOteSetupAction();
private final User user = private final User user =
@@ -100,6 +107,9 @@ public final class ConsoleOteSetupActionTest {
action.optionalPassword = Optional.empty(); action.optionalPassword = Optional.empty();
action.passwordGenerator = new DeterministicStringGenerator("abcdefghijklmnopqrstuvwxyz"); action.passwordGenerator = new DeterministicStringGenerator("abcdefghijklmnopqrstuvwxyz");
action.maybeGroupEmailAddress = Optional.of("group@example.com");
action.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
action.iamClient = iamClient;
} }
@Test @Test
@@ -142,9 +152,7 @@ public final class ConsoleOteSetupActionTest {
// checking that all the entities are there or that they have the correct values. // checking that all the entities are there or that they have the correct values.
assertThat(loadByRegistrarId("myclientid-3")).isPresent(); assertThat(loadByRegistrarId("myclientid-3")).isPresent();
assertThat(Tld.get("myclientid-ga")).isNotNull(); assertThat(Tld.get("myclientid-ga")).isNotNull();
assertThat( verifyUser("myclientid-5", "contact@registry.example");
loadByRegistrarId("myclientid-5").get().getContacts().asList().get(0).getEmailAddress())
.isEqualTo("contact@registry.example");
assertThat(response.getPayload()) assertThat(response.getPayload())
.contains("<h1>OT&E successfully created for registrar myclientid!</h1>"); .contains("<h1>OT&E successfully created for registrar myclientid!</h1>");
ArgumentCaptor<EmailMessage> contentCaptor = ArgumentCaptor.forClass(EmailMessage.class); ArgumentCaptor<EmailMessage> contentCaptor = ArgumentCaptor.forClass(EmailMessage.class);
@@ -163,6 +171,14 @@ public final class ConsoleOteSetupActionTest {
Gave user contact@registry.example web access to these Registrars Gave user contact@registry.example web access to these Registrars
"""); """);
assertThat(response.getPayload()).contains("gtag('config', 'sampleId')"); assertThat(response.getPayload()).contains("gtag('config', 'sampleId')");
verifyIapPermission(
"contact@registry.example", action.maybeGroupEmailAddress, cloudTasksHelper, iamClient);
}
@Test
void testPost_authorized_noConsoleGroup() {
action.maybeGroupEmailAddress = Optional.empty();
testPost_authorized();
} }
@Test @Test

View File

@@ -15,10 +15,13 @@
package google.registry.ui.server.registrar; package google.registry.ui.server.registrar;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.OteAccountBuilderTest.verifyIapPermission;
import static google.registry.model.OteAccountBuilderTest.verifyUser;
import static google.registry.model.registrar.Registrar.loadByRegistrarId; import static google.registry.model.registrar.Registrar.loadByRegistrarId;
import static google.registry.testing.DatabaseHelper.persistPremiumList; import static google.registry.testing.DatabaseHelper.persistPremiumList;
import static jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; import static jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
import static org.joda.money.CurrencyUnit.USD; import static org.joda.money.CurrencyUnit.USD;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@@ -29,17 +32,18 @@ import google.registry.model.console.User;
import google.registry.model.console.UserRoles; import google.registry.model.console.UserRoles;
import google.registry.model.registrar.Registrar; import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarAddress; import google.registry.model.registrar.RegistrarAddress;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.persistence.transaction.JpaTestExtensions; import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension; import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.request.Action.Method; import google.registry.request.Action.Method;
import google.registry.request.auth.AuthResult; import google.registry.request.auth.AuthResult;
import google.registry.request.auth.AuthenticatedRegistrarAccessor; import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.security.XsrfTokenManager; import google.registry.security.XsrfTokenManager;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.DeterministicStringGenerator; import google.registry.testing.DeterministicStringGenerator;
import google.registry.testing.FakeClock; import google.registry.testing.FakeClock;
import google.registry.testing.FakeResponse; import google.registry.testing.FakeResponse;
import google.registry.testing.SystemPropertyExtension; import google.registry.testing.SystemPropertyExtension;
import google.registry.tools.IamClient;
import google.registry.ui.server.SendEmailUtils; import google.registry.ui.server.SendEmailUtils;
import google.registry.util.EmailMessage; import google.registry.util.EmailMessage;
import google.registry.util.RegistryEnvironment; import google.registry.util.RegistryEnvironment;
@@ -73,6 +77,8 @@ final class ConsoleRegistrarCreatorActionTest {
.setEmailAddress("marla.singer@example.com") .setEmailAddress("marla.singer@example.com")
.setUserRoles(new UserRoles()) .setUserRoles(new UserRoles())
.build(); .build();
private final CloudTasksHelper cloudTasksHelper = new CloudTasksHelper();
private final IamClient iamClient = mock(IamClient.class);
@Mock HttpServletRequest request; @Mock HttpServletRequest request;
@Mock GmailClient gmailClient; @Mock GmailClient gmailClient;
@@ -119,6 +125,10 @@ final class ConsoleRegistrarCreatorActionTest {
action.passcodeGenerator = new DeterministicStringGenerator("314159265"); action.passcodeGenerator = new DeterministicStringGenerator("314159265");
action.analyticsConfig = ImmutableMap.of("googleAnalyticsId", "sampleId"); action.analyticsConfig = ImmutableMap.of("googleAnalyticsId", "sampleId");
action.maybeGroupEmailAddress = Optional.of("group@example.com");
action.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
action.iamClient = iamClient;
} }
@Test @Test
@@ -152,8 +162,7 @@ final class ConsoleRegistrarCreatorActionTest {
assertThat(response.getPayload()).contains("gtag('config', 'sampleId')"); assertThat(response.getPayload()).contains("gtag('config', 'sampleId')");
} }
@Test void runTestPost_authorized_minimalAddress() {
void testPost_authorized_minimalAddress() {
action.clientId = Optional.of("myclientid"); action.clientId = Optional.of("myclientid");
action.name = Optional.of("registrar name"); action.name = Optional.of("registrar name");
action.billingAccount = Optional.of("USD=billing-account"); action.billingAccount = Optional.of("USD=billing-account");
@@ -209,14 +218,22 @@ final class ConsoleRegistrarCreatorActionTest {
.setCountryCode("CC") .setCountryCode("CC")
.build()); .build());
assertThat(registrar.getContacts()) verifyUser("myclientid", "myclientid@registry.example");
.containsExactly( }
new RegistrarPoc.Builder()
.setRegistrar(registrar) @Test
.setName("myclientid@registry.example") void testPost_authorized_minimalAddress_consoleUserGroup() {
.setEmailAddress("myclientid@registry.example") runTestPost_authorized_minimalAddress();
.setLoginEmailAddress("myclientid@registry.example") verifyIapPermission(
.build()); "myclientid@registry.example", action.maybeGroupEmailAddress, cloudTasksHelper, iamClient);
}
@Test
void testPost_authorized_minimalAddress_NoConsoleUserGroup() {
action.maybeGroupEmailAddress = Optional.empty();
runTestPost_authorized_minimalAddress();
verifyIapPermission(
"myclientid@registry.example", action.maybeGroupEmailAddress, cloudTasksHelper, iamClient);
} }
@Test @Test

View File

@@ -14,6 +14,8 @@
package google.registry.util; package google.registry.util;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.base.Ascii; import com.google.common.base.Ascii;
/** Registry environments. */ /** Registry environments. */
@@ -59,6 +61,20 @@ public enum RegistryEnvironment {
private static final boolean ON_JETTY = private static final boolean ON_JETTY =
Boolean.parseBoolean(System.getProperty(JETTY_PROPERTY, "false")); Boolean.parseBoolean(System.getProperty(JETTY_PROPERTY, "false"));
/**
* A thread local boolean that can be set in tests to indicate some code is running in a local
* test server.
*
* <p>Certain API calls (like calls to Cloud Tasks) are hard to stub when they run in the test
* server because the test server does not allow arbitrary injection of dependencies. Instead,
* code running in the server can check this value and decide whether to skip these API calls.
*
* <p>The value is set to false by default and can only be set to true in unit test environment.
* It is set to {@code true} in {@code start()} and {@code false} in {@code stop()} in {@code
* TestServer}.
*/
private static final ThreadLocal<Boolean> IN_TEST_SERVER = ThreadLocal.withInitial(() -> false);
/** Sets this enum as the name of the registry environment. */ /** Sets this enum as the name of the registry environment. */
public RegistryEnvironment setup() { public RegistryEnvironment setup() {
return setup(SystemPropertySetter.PRODUCTION_IMPL); return setup(SystemPropertySetter.PRODUCTION_IMPL);
@@ -81,4 +97,13 @@ public enum RegistryEnvironment {
public static boolean isOnJetty() { public static boolean isOnJetty() {
return ON_JETTY; return ON_JETTY;
} }
public static void setIsInTestDriver(boolean value) {
checkState(RegistryEnvironment.get() == RegistryEnvironment.UNITTEST);
IN_TEST_SERVER.set(value);
}
public static boolean isInTestServer() {
return IN_TEST_SERVER.get();
}
} }