1
0
mirror of https://github.com/google/nomulus synced 2026-06-09 16:33:02 +00:00

Compare commits

...

4 Commits

Author SHA1 Message Date
Ben McIlwain b602aac09a Make all contacts nullable in the data model (#2490)
This doesn't yet allow them to be absent in EPP flows, but it should make the
code not break if they happen to be null in the database. This is a follow-up to
PR #2477, which ends up being a bit easier because whereas the registrant is
used in more parts of the codebase, the other contact types (admin, technical,
billing) are really only used in RDE, WHOIS, and RDAP, and because they were
already being used as a collection anyway, the handling for if that collection
contains fewer elements or is empty happened to already be mostly correct.
2024-07-03 21:42:20 +00:00
Lai Jiang d86c002132 Create Users when setting up OT&E and Production registrars (#2488) 2024-07-03 18:31:07 +00:00
gbrodman 54c5a9450d Add RegistrationBehavior.NONPREMIUM_CREATE (#2481)
When using this token (which must be tied to a particular domain), the
first year price (and only the first year price, i.e. the creation
price) will be the standard price for this TLD. Future years (i.e.
renewals) will continue at the normal premium price.
2024-06-26 20:01:32 +00:00
gbrodman 0f0097c15c Wait to load domain list until a registrar is selected (#2485)
This isn't the worst thing in the world but it does result in a bad
request to the server otherwise, and log/error spam. So, only load the
domains list if we have a registrar selected.
2024-06-25 18:39:53 +00:00
42 changed files with 1331 additions and 397 deletions
@@ -60,8 +60,9 @@ export class DomainListComponent {
effect(() => {
this.pageNumber = 0;
this.totalResults = 0;
this.reloadData();
this.registrarService.registrarId();
if (this.registrarService.registrarId()) {
this.reloadData();
}
});
}
@@ -466,13 +466,10 @@ public class RdePipeline implements Serializable {
// Contacts and hosts are only deposited in RDE, not BRDA.
if (pendingDeposit.mode() == RdeMode.FULL) {
HashSet<Serializable> contacts = new HashSet<>();
contacts.add(domain.getAdminContact().getKey());
contacts.add(domain.getTechContact().getKey());
domain.getRegistrant().ifPresent(r -> contacts.add(r.getKey()));
// Billing contact is not mandatory.
if (domain.getBillingContact() != null) {
contacts.add(domain.getBillingContact().getKey());
}
domain.getAdminContact().ifPresent(c -> contacts.add(c.getKey()));
domain.getTechContact().ifPresent(c -> contacts.add(c.getKey()));
domain.getRegistrant().ifPresent(c -> contacts.add(c.getKey()));
domain.getBillingContact().ifPresent(c -> contacts.add(c.getKey()));
referencedContactCounter.inc(contacts.size());
contacts.forEach(
contactRepoId ->
@@ -96,6 +96,14 @@
<max-retry-duration>3600s</max-retry-duration>
</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>
<name>retryable-cron-tasks</name>
@@ -85,6 +85,15 @@ public final class DomainPricingLogic {
createFee = Fee.create(zeroInCurrency(currency), FeeType.CREATE, false);
} else {
DomainPrices domainPrices = getPricesForDomainName(domainName, dateTime);
if (allocationToken.isPresent()
&& allocationToken
.get()
.getRegistrationBehavior()
.equals(RegistrationBehavior.NONPREMIUM_CREATE)) {
domainPrices =
DomainPrices.create(
false, tld.getCreateBillingCost(dateTime), domainPrices.getRenewCost());
}
Money domainCreateCost =
getDomainCreateCostWithDiscount(domainPrices, years, allocationToken);
// Apply a sunrise discount if configured and applicable
@@ -17,6 +17,7 @@ package google.registry.model;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
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.START_DATE_SUNRISE;
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.Sets;
import com.google.common.collect.Streams;
import google.registry.batch.CloudTasksUtils;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.model.console.UserRoles;
import google.registry.model.pricing.StaticPremiumListPricingEngine;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarAddress;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.model.tld.Tld;
import google.registry.model.tld.Tld.TldState;
import google.registry.model.tld.Tld.TldType;
import google.registry.model.tld.label.PremiumList;
import google.registry.model.tld.label.PremiumListDao;
import google.registry.persistence.VKey;
import google.registry.tools.IamClient;
import google.registry.util.CidrAddressBlock;
import google.registry.util.RegistryEnvironment;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.regex.Pattern;
@@ -75,8 +85,8 @@ public final class OteAccountBuilder {
* 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
* (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
* (e.g., reg-1, reg-3, reg-4, reg-5). Registrar client IDs are of type clIDType in eppcom.xsd
* that is limited to 16 characters, hence the limit of 14 here to account for the dash and
* numbers.
*
* <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.
*
* <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
* place).
*/
@@ -124,7 +134,7 @@ public final class OteAccountBuilder {
private final Tld sunriseTld;
private final Tld gaTld;
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 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
* to all OT&amp;E Registrars.
* <p>NOTE: can be called more than once, adding multiple users. Each user will have access to all
* OT&amp;E Registrars.
*
* @param email the contact/login email that will have web-console access to all the Registrars.
* Must be from "our G Suite domain".
* @param email the login email that will have web-console access to all the Registrars. Must be
* from "our Google Workspace domain".
*/
public OteAccountBuilder addContact(String email) {
registrars.forEach(registrar -> contactsBuilder.add(createRegistrarContact(email, registrar)));
public OteAccountBuilder addUser(String email) {
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;
}
@@ -217,7 +239,7 @@ public final class OteAccountBuilder {
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) {
ImmutableList<CidrAddressBlock> ipAddressAllowList =
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
* to.
* Return the map from the OT&amp;E registrarIds we will create to the new TLDs they will have
* access to.
*/
public ImmutableMap<String, String> getRegistrarIdToTldMap() {
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. */
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<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(
() -> {
@@ -256,8 +297,7 @@ public final class OteAccountBuilder {
ImmutableList<VKey<? extends ImmutableObject>> keys =
Streams.concat(
registries.stream().map(tld -> Tld.createVKey(tld.getTldStr())),
registrars.stream().map(Registrar::createVKey),
contacts.stream().map(RegistrarPoc::createVKey))
registrars.stream().map(Registrar::createVKey))
.collect(toImmutableList());
ImmutableMap<VKey<? extends ImmutableObject>, ImmutableObject> existingObjects =
tm().loadByKeysIfPresent(keys);
@@ -275,8 +315,18 @@ public final class OteAccountBuilder {
registrars = registrars.stream().map(this::addAllowedTld).collect(toImmutableList());
// and we can save the registrars and contacts!
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) {
@@ -336,15 +386,6 @@ public final class OteAccountBuilder {
.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. */
public static ImmutableMap<String, String> createRegistrarIdToTldMap(String baseRegistrarId) {
checkArgument(
@@ -14,7 +14,19 @@
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.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.AccessType;
import javax.persistence.Embeddable;
@@ -31,6 +43,76 @@ import javax.persistence.Table;
@Table(indexes = {@Index(columnList = "emailAddress", name = "user_email_address_idx")})
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
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -46,6 +46,7 @@ import google.registry.model.EppResource;
import google.registry.model.EppResource.ResourceWithTransferData;
import google.registry.model.billing.BillingRecurrence;
import google.registry.model.contact.Contact;
import google.registry.model.domain.DesignatedContact.Type;
import google.registry.model.domain.launch.LaunchNotice;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.domain.secdns.DomainDsData;
@@ -131,10 +132,10 @@ public class DomainBase extends EppResource
@Expose @Transient Set<VKey<Host>> nsHosts;
/** Contacts. */
@Expose VKey<Contact> adminContact;
@Expose @Nullable VKey<Contact> adminContact;
@Expose VKey<Contact> billingContact;
@Expose VKey<Contact> techContact;
@Expose @Nullable VKey<Contact> billingContact;
@Expose @Nullable VKey<Contact> techContact;
@Expose @Nullable VKey<Contact> registrantContact;
/** Authorization info (aka transfer secret) of the domain. */
@@ -589,24 +590,32 @@ public class DomainBase extends EppResource
return Optional.ofNullable(registrantContact);
}
public VKey<Contact> getAdminContact() {
return adminContact;
public Optional<VKey<Contact>> getAdminContact() {
return Optional.ofNullable(adminContact);
}
public VKey<Contact> getBillingContact() {
return billingContact;
public Optional<VKey<Contact>> getBillingContact() {
return Optional.ofNullable(billingContact);
}
public VKey<Contact> getTechContact() {
return techContact;
public Optional<VKey<Contact>> getTechContact() {
return Optional.ofNullable(techContact);
}
/** Associated contacts for the domain (other than registrant). */
/**
* Associated contacts for the domain (other than registrant).
*
* <p>Note: This can be an empty set if no contacts are present for the domain.
*/
public ImmutableSet<DesignatedContact> getContacts() {
return getAllContacts(false);
}
/** Gets all associated contacts for the domain, including the registrant. */
/**
* Gets all associated contacts for the domain, including the registrant.
*
* <p>Note: This can be an empty set if no contacts are present for the domain.
*/
public ImmutableSet<DesignatedContact> getAllContacts() {
return getAllContacts(true);
}
@@ -615,7 +624,11 @@ public class DomainBase extends EppResource
return authInfo;
}
/** Returns all referenced contacts from this domain. */
/**
* Returns all referenced contacts from this domain.
*
* <p>Note: This can be an empty set if no contacts are present for the domain.
*/
public ImmutableSet<VKey<Contact>> getReferencedContacts() {
return nullToEmptyImmutableCopy(getAllContacts(true)).stream()
.map(DesignatedContact::getContactKey)
@@ -625,18 +638,12 @@ public class DomainBase extends EppResource
private ImmutableSet<DesignatedContact> getAllContacts(boolean includeRegistrant) {
ImmutableSet.Builder<DesignatedContact> builder = new ImmutableSet.Builder<>();
if (includeRegistrant && registrantContact != null) {
builder.add(DesignatedContact.create(DesignatedContact.Type.REGISTRANT, registrantContact));
}
if (adminContact != null) {
builder.add(DesignatedContact.create(DesignatedContact.Type.ADMIN, adminContact));
}
if (billingContact != null) {
builder.add(DesignatedContact.create(DesignatedContact.Type.BILLING, billingContact));
}
if (techContact != null) {
builder.add(DesignatedContact.create(DesignatedContact.Type.TECH, techContact));
if (includeRegistrant) {
getRegistrant().ifPresent(c -> builder.add(DesignatedContact.create(Type.REGISTRANT, c)));
}
getAdminContact().ifPresent(c -> builder.add(DesignatedContact.create(Type.ADMIN, c)));
getBillingContact().ifPresent(c -> builder.add(DesignatedContact.create(Type.BILLING, c)));
getTechContact().ifPresent(c -> builder.add(DesignatedContact.create(Type.TECH, c)));
return builder.build();
}
@@ -652,11 +659,13 @@ public class DomainBase extends EppResource
*/
void setContactFields(Set<DesignatedContact> contacts, boolean includeRegistrant) {
// Set the individual contact fields.
billingContact = techContact = adminContact = null;
billingContact = null;
techContact = null;
adminContact = null;
if (includeRegistrant) {
registrantContact = null;
}
HashSet<DesignatedContact.Type> contactsDiscovered = new HashSet<>();
HashSet<Type> contactsDiscovered = new HashSet<>();
for (DesignatedContact contact : contacts) {
checkArgument(
!contactsDiscovered.contains(contact.getType()),
@@ -687,7 +696,7 @@ public class DomainBase extends EppResource
/** Predicate to determine if a given {@link DesignatedContact} is the registrant. */
static final Predicate<DesignatedContact> IS_REGISTRANT =
(DesignatedContact contact) -> DesignatedContact.Type.REGISTRANT.equals(contact.type);
(DesignatedContact contact) -> Type.REGISTRANT.equals(contact.type);
/** An override of {@link EppResource#asBuilder} with tighter typing. */
@Override
@@ -115,7 +115,17 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda
*/
BYPASS_TLD_STATE,
/** Bypasses most checks and creates the domain as an anchor tenant, with all that implies. */
ANCHOR_TENANT
ANCHOR_TENANT,
/**
* Bypasses the premium list to use the standard creation price. Does not affect the renewal
* price.
*
* <p>This cannot be specified along with a discount fraction, and any renewals (automatic or
* otherwise) will use the premium price for the domain if one exists.
*
* <p>Tokens with this behavior must be tied to a single particular domain.
*/
NONPREMIUM_CREATE
}
/** Type of the token that indicates how and where it should be used. */
@@ -404,6 +414,17 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda
checkArgumentNotNull(
getInstance().domainName, "ANCHOR_TENANT tokens must be tied to a domain");
}
if (getInstance().registrationBehavior.equals(RegistrationBehavior.NONPREMIUM_CREATE)) {
checkArgument(
getInstance().discountFraction == 0.0,
"NONPREMIUM_CREATE tokens cannot apply a discount");
checkArgumentNotNull(
getInstance().domainName, "NONPREMIUM_CREATE tokens must be tied to a domain");
checkArgument(
getInstance().allowedEppActions == null
|| getInstance().allowedEppActions.contains(CommandName.CREATE),
"NONPREMIUM_CREATE tokens must allow for CREATE actions");
}
if (getInstance().domainName != null) {
try {
DomainFlowUtils.validateDomainName(getInstance().domainName);
@@ -46,7 +46,7 @@ public interface PremiumPricingEngine {
private Money createCost;
private Money renewCost;
static DomainPrices create(boolean isPremium, Money createCost, Money renewCost) {
public static DomainPrices create(boolean isPremium, Money createCost, Money renewCost) {
DomainPrices instance = new DomainPrices();
instance.isPremium = isPremium;
instance.createCost = createCost;
@@ -381,8 +381,11 @@ public class RdapJsonFormatter {
() ->
ImmutableSet.copyOf(replicaTm().loadByKeys(domain.getNameservers()).values()));
// Load the registrant and other contacts and add them to the data.
ImmutableSet<VKey<Contact>> contacts = domain.getReferencedContacts();
ImmutableMap<VKey<? extends Contact>, Contact> loadedContacts =
replicaTm().transact(() -> replicaTm().loadByKeysIfPresent(domain.getReferencedContacts()));
contacts.isEmpty()
? ImmutableMap.of()
: replicaTm().transact(() -> replicaTm().loadByKeysIfPresent(contacts));
// RDAP Response Profile 2.7.1, 2.7.3 - we MUST have the contacts. 2.7.4 discusses redaction of
// fields we don't want to show (as opposed to not having contacts at all) because of GDPR etc.
@@ -544,7 +547,8 @@ public class RdapJsonFormatter {
// TODO(mcilwain): Once the RDAP profile is fully updated for minimum registration data set,
// we will want to not include non-existent contacts at all, rather than
// pretending they exist and just showing REDACTED info. This is especially
// important for authorized flows, where you wouldn't expect to see redaction.
// important for authorized flows, where you wouldn't expect to see redaction
// (although no one actually has access to authorized flows yet).
boolean isAuthorized =
contact.isPresent()
&& rdapAuthorization.isAuthorizedForRegistrar(
@@ -15,30 +15,25 @@
package google.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.model.console.User.grantIapPermission;
import com.beust.jcommander.Parameters;
import com.google.common.collect.ImmutableMap;
import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType;
import google.registry.batch.CloudTasksUtils;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.tools.server.UpdateUserGroupAction;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.inject.Inject;
/** Command to create a new User. */
@Parameters(separators = " =", commandDescription = "Update a user account")
public class CreateUserCommand extends CreateOrUpdateUserCommand implements CommandWithConnection {
static final String IAP_SECURED_WEB_APP_USER_ROLE = "roles/iap.httpsResourceAccessor";
static final FluentLogger logger = FluentLogger.forEnclosingClass();
private ServiceConnection connection;
public class CreateUserCommand extends CreateOrUpdateUserCommand {
@Inject IamClient iamClient;
@Inject CloudTasksUtils cloudTasksUtils;
@Inject
@Config("gSuiteConsoleUserGroupEmailAddress")
Optional<String> maybeGroupEmailAddress;
@@ -53,29 +48,7 @@ public class CreateUserCommand extends CreateOrUpdateUserCommand implements Comm
@Override
protected String execute() throws Exception {
String ret = super.execute();
String groupEmailAddress = maybeGroupEmailAddress.orElse(null);
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);
}
grantIapPermission(email, maybeGroupEmailAddress, cloudTasksUtils, iamClient);
return ret;
}
@Override
public void setConnection(ServiceConnection connection) {
this.connection = connection;
}
}
@@ -15,32 +15,27 @@
package google.registry.tools;
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.checkArgumentPresent;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.collect.ImmutableMap;
import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType;
import google.registry.batch.CloudTasksUtils;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.tools.server.UpdateUserGroupAction;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.inject.Inject;
/** Deletes a {@link User}. */
@Parameters(separators = " =", commandDescription = "Delete a user account")
public class DeleteUserCommand extends ConfirmingCommand implements CommandWithConnection {
public class DeleteUserCommand extends ConfirmingCommand {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private ServiceConnection connection;
@Inject IamClient iamClient;
@Inject CloudTasksUtils cloudTasksUtils;
@Inject
@Config("gSuiteConsoleUserGroupEmailAddress")
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)
String email;
@Override
public void setConnection(ServiceConnection connection) {
this.connection = connection;
}
@Override
protected String prompt() {
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");
tm().delete(optionalUser.get());
});
String groupEmailAddress = maybeGroupEmailAddress.orElse(null);
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);
}
User.revokeIapPermission(email, maybeGroupEmailAddress, cloudTasksUtils, iamClient);
return String.format("Deleted user with email %s", email);
}
}
@@ -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.SetIamPolicyRequest;
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.util.GoogleCredentialsBundle;
import java.io.IOException;
@@ -38,7 +38,7 @@ public class IamClient {
@Inject
public IamClient(
@LocalCredential GoogleCredentialsBundle credentialsBundle,
@ApplicationDefaultCredential GoogleCredentialsBundle credentialsBundle,
@Config("projectId") String projectId) {
this(
new CloudResourceManager.Builder(
@@ -17,7 +17,7 @@ package google.registry.tools;
import com.google.api.services.dataflow.Dataflow;
import dagger.Module;
import dagger.Provides;
import google.registry.config.CredentialModule.LocalCredential;
import google.registry.config.CredentialModule.ApplicationDefaultCredential;
import google.registry.config.RegistryConfig.Config;
import google.registry.util.GoogleCredentialsBundle;
@@ -27,7 +27,7 @@ public class RegistryToolDataflowModule {
@Provides
static Dataflow provideDataflow(
@LocalCredential GoogleCredentialsBundle credentialsBundle,
@ApplicationDefaultCredential GoogleCredentialsBundle credentialsBundle,
@Config("projectId") String projectId) {
return new Dataflow.Builder(
credentialsBundle.getHttpTransport(),
@@ -22,6 +22,8 @@ import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.collect.ImmutableMap;
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.tools.params.PathParameter;
import google.registry.util.Clock;
@@ -30,6 +32,7 @@ import google.registry.util.StringGenerator;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import javax.inject.Inject;
import javax.inject.Named;
@@ -47,7 +50,7 @@ final class SetupOteCommand extends ConfirmingCommand {
@Parameter(
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)
private List<String> ipAllowList = new ArrayList<>();
@@ -55,7 +58,7 @@ final class SetupOteCommand extends ConfirmingCommand {
names = {"--email"},
description =
"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)
private String email;
@@ -76,6 +79,14 @@ final class SetupOteCommand extends ConfirmingCommand {
@Inject Clock clock;
@Inject CloudTasksUtils cloudTasksUtils;
@Inject IamClient iamClient;
@Inject
@Config("gSuiteConsoleUserGroupEmailAddress")
Optional<String> maybeGroupEmailAddress;
OteAccountBuilder oteAccountBuilder;
String password;
@@ -87,7 +98,7 @@ final class SetupOteCommand extends ConfirmingCommand {
password = passwordGenerator.createString(PASSWORD_LENGTH);
oteAccountBuilder =
OteAccountBuilder.forRegistrarId(registrar)
.addContact(email)
.addUser(email)
.setPassword(password)
.setIpAllowList(ipAllowList)
.setReplaceExisting(overwrite);
@@ -114,8 +125,11 @@ final class SetupOteCommand extends ConfirmingCommand {
&& RegistryEnvironment.get() != RegistryEnvironment.UNITTEST) {
builder.append(
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()));
}
@@ -125,15 +139,15 @@ final class SetupOteCommand extends ConfirmingCommand {
@Override
public String execute() {
ImmutableMap<String, String> clientIdToTld = oteAccountBuilder.buildAndPersist();
oteAccountBuilder.grantIapPermission(maybeGroupEmailAddress, cloudTasksUtils, iamClient);
StringBuilder output = new StringBuilder();
output.append("Copy these usernames/passwords back into the onboarding bug:\n\n");
clientIdToTld.forEach(
(clientId, tld) -> {
output.append(
String.format("Login: %s\nPassword: %s\nTLD: %s\n\n", clientId, password, tld));
});
(clientId, tld) ->
output.append(
String.format("Login: %s\nPassword: %s\nTLD: %s\n\n", clientId, password, tld)));
return output.toString();
}
@@ -34,6 +34,7 @@ import javax.inject.Inject;
public class UpdateUserGroupAction implements Runnable {
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();
@@ -53,7 +54,7 @@ public class UpdateUserGroupAction implements Runnable {
@Inject
UpdateUserGroupAction() {}
enum Mode {
public enum Mode {
ADD,
REMOVE
}
@@ -24,6 +24,8 @@ import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableMap;
import com.google.common.flogger.FluentLogger;
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.request.Action;
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.auth.Auth;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.tools.IamClient;
import google.registry.ui.server.SendEmailUtils;
import google.registry.ui.server.SoyTemplateUtils;
import google.registry.ui.soy.registrar.OteSetupConsoleSoyInfo;
@@ -87,6 +90,14 @@ public final class ConsoleOteSetupAction extends HtmlAction {
@Parameter("password")
Optional<String> optionalPassword;
@Inject CloudTasksUtils cloudTasksUtils;
@Inject IamClient iamClient;
@Inject
@Config("gSuiteConsoleUserGroupEmailAddress")
Optional<String> maybeGroupEmailAddress;
@Inject
ConsoleOteSetupAction() {}
@@ -107,16 +118,11 @@ public final class ConsoleOteSetupAction extends HtmlAction {
return;
}
switch (method) {
case POST -> {
runPost(data);
}
case GET -> {
runGet(data);
}
default -> {
throw new BadRequestException(
String.format("Action cannot be called with method %s", method));
}
case POST -> runPost(data);
case GET -> runGet(data);
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());
String password = optionalPassword.orElse(passwordGenerator.createString(PASSWORD_LENGTH));
ImmutableMap<String, String> clientIdToTld =
OteAccountBuilder oteAccountBuilder =
OteAccountBuilder.forRegistrarId(clientId.get())
.addContact(email.get())
.setPassword(password)
.buildAndPersist();
.addUser(email.get())
.setPassword(password);
ImmutableMap<String, String> clientIdToTld = oteAccountBuilder.buildAndPersist();
oteAccountBuilder.grantIapPermission(maybeGroupEmailAddress, cloudTasksUtils, iamClient);
sendExternalUpdates(clientIdToTld);
@@ -26,10 +26,15 @@ import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;
import com.google.common.flogger.FluentLogger;
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.RegistrarAddress;
import google.registry.model.registrar.RegistrarBase.State;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.request.Action;
import google.registry.request.Action.Method;
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.auth.Auth;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.tools.IamClient;
import google.registry.ui.server.SendEmailUtils;
import google.registry.ui.server.SoyTemplateUtils;
import google.registry.ui.soy.registrar.AnalyticsSoyInfo;
@@ -95,6 +101,14 @@ public final class ConsoleRegistrarCreatorAction extends HtmlAction {
@Parameter("consoleName")
Optional<String> name;
@Inject CloudTasksUtils cloudTasksUtils;
@Inject IamClient iamClient;
@Inject
@Config("gSuiteConsoleUserGroupEmailAddress")
Optional<String> maybeGroupEmailAddress;
@Inject @Parameter("billingAccount") Optional<String> billingAccount;
@Inject @Parameter("ianaId") Optional<Integer> ianaId;
@Inject @Parameter("referralEmail") Optional<String> referralEmail;
@@ -129,16 +143,11 @@ public final class ConsoleRegistrarCreatorAction extends HtmlAction {
return;
}
switch (method) {
case POST -> {
runPost(data);
}
case GET -> {
runGet(data);
}
default -> {
throw new BadRequestException(
String.format("Action cannot be called with method %s", method));
}
case POST -> runPost(data);
case GET -> runGet(data);
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))
.collect(
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) {
throw new RuntimeException("Error parsing billing accounts - " + e.getMessage(), e);
}
@@ -233,12 +243,15 @@ public final class ConsoleRegistrarCreatorAction extends HtmlAction {
.setZip(optionalZip.orElse(null))
.build())
.build();
RegistrarPoc contact =
new RegistrarPoc.Builder()
.setRegistrar(registrar)
.setName(consoleUserEmail.get())
User user =
new User.Builder()
.setEmailAddress(consoleUserEmail.get())
.setLoginEmailAddress(consoleUserEmail.get())
.setUserRoles(
new UserRoles.Builder()
.setRegistrarRoles(
ImmutableMap.of(
registrar.getRegistrarId(), RegistrarRole.ACCOUNT_MANAGER))
.build())
.build();
tm().transact(
() -> {
@@ -246,8 +259,11 @@ public final class ConsoleRegistrarCreatorAction extends HtmlAction {
Registrar.loadByRegistrarId(registrar.getRegistrarId()).isEmpty(),
"Registrar with client ID %s already exists",
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("passcode", phonePasscode);
@@ -88,22 +88,6 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA
static final String ARGS_PARAM = "args";
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 RegistrarConsoleMetrics registrarConsoleMetrics;
@Inject SendEmailUtils sendEmailUtils;
@@ -118,14 +102,6 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA
return contact.getPhoneNumber() != null;
}
public static void setIsInTestDriverToFalse() {
isInTestDriver.set(false);
}
public static void setIsInTestDriverToTrue() {
isInTestDriver.set(true);
}
@Override
public void run() {
jsonActionRunner.run(this);
@@ -623,7 +599,7 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA
if (CollectionUtils.difference(changedKeys, "lastUpdateTime").isEmpty()) {
return;
}
if (!isInTestDriver.get()) {
if (!RegistryEnvironment.isInTestServer()) {
// Enqueues a sync registrar sheet task if enqueuing is not triggered by console tests and
// there's an update besides the lastUpdateTime
cloudTasksUtils.enqueue(
@@ -3519,6 +3519,22 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
assertSuccessfulCreate("tld", ImmutableSet.of(), token);
}
@Test
void testSuccess_nonpremiumCreateToken() throws Exception {
createTld("example");
persistContactsAndHosts();
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(SINGLE_USE)
.setRegistrationBehavior(RegistrationBehavior.NONPREMIUM_CREATE)
.setDomainName("rich.example")
.build());
setEppInput(
"domain_create_premium_allocationtoken.xml", ImmutableMap.of("YEARS", "1", "FEE", "13.00"));
runFlowAssertResponse(loadFile("domain_create_nonpremium_token_response.xml"));
}
@Test
void testFailure_quietPeriod_defaultTokenPresent() throws Exception {
persistResource(
@@ -235,6 +235,19 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, Domain> {
doSuccessfulTest("domain_info_response_no_registrant.xml", false);
}
@Test
void testSuccess_noContacts() throws Exception {
persistTestEntities(false);
domain =
persistResource(
domain
.asBuilder()
.setRegistrant(Optional.empty())
.setContacts(ImmutableSet.of())
.build());
doSuccessfulTest("domain_info_response_no_contacts.xml", false);
}
@Test
void testSuccess_clTridNotSpecified() throws Exception {
setEppInput("domain_info_no_cltrid.xml");
@@ -55,6 +55,7 @@ import google.registry.testing.DatabaseHelper;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeHttpSession;
import google.registry.util.Clock;
import java.math.BigDecimal;
import java.util.Optional;
import org.joda.money.Money;
import org.joda.time.DateTime;
@@ -146,7 +147,7 @@ public class DomainPricingLogicTest {
new FeesAndCredits.Builder()
.setCurrency(USD)
// (13 + 11) * 0.85 == 20.40
.addFeeOrCredit(Fee.create(Money.of(USD, 20.4).getAmount(), CREATE, false))
.addFeeOrCredit(Fee.create(new BigDecimal("20.40"), CREATE, false))
.build());
}
@@ -159,7 +160,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 10).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("10.00"), RENEW, false))
.build());
}
@@ -172,7 +173,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 50).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("50.00"), RENEW, false))
.build());
}
@@ -185,7 +186,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 100).getAmount(), RENEW, true))
.addFeeOrCredit(Fee.create(new BigDecimal("100.00"), RENEW, true))
.build());
}
@@ -198,7 +199,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 500).getAmount(), RENEW, true))
.addFeeOrCredit(Fee.create(new BigDecimal("500.00"), RENEW, true))
.build());
}
@@ -215,7 +216,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 100).getAmount(), RENEW, true))
.addFeeOrCredit(Fee.create(new BigDecimal("100.00"), RENEW, true))
.build());
}
@@ -241,7 +242,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 50).getAmount(), RENEW, true))
.addFeeOrCredit(Fee.create(new BigDecimal("50.00"), RENEW, true))
.build());
}
@@ -281,7 +282,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 500).getAmount(), RENEW, true))
.addFeeOrCredit(Fee.create(new BigDecimal("500.00"), RENEW, true))
.build());
}
@@ -308,7 +309,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 400).getAmount(), RENEW, true))
.addFeeOrCredit(Fee.create(new BigDecimal("400.00"), RENEW, true))
.build());
}
@@ -350,7 +351,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 10).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("10.00"), RENEW, false))
.build());
}
@@ -376,7 +377,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 5).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("5.00"), RENEW, false))
.build());
}
@@ -394,7 +395,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 50).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("50.00"), RENEW, false))
.build());
}
@@ -421,7 +422,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 40).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("40.00"), RENEW, false))
.build());
}
@@ -439,7 +440,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 10).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("10.00"), RENEW, false))
.build());
}
@@ -466,7 +467,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 5).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("5.00"), RENEW, false))
.build());
}
@@ -484,7 +485,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 50).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("50.00"), RENEW, false))
.build());
}
@@ -512,7 +513,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 40).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("40.00"), RENEW, false))
.build());
}
@@ -530,7 +531,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 10).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("10.00"), RENEW, false))
.build());
}
@@ -548,7 +549,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 50).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("50.00"), RENEW, false))
.build());
}
@@ -567,7 +568,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 1).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("1.00"), RENEW, false))
.build());
}
@@ -597,7 +598,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 1).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("1.00"), RENEW, false))
.build());
}
@@ -628,7 +629,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 1).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("1.00"), RENEW, false))
.build());
assertThat(
Iterables.getLast(DatabaseHelper.loadAllOf(BillingRecurrence.class))
@@ -651,7 +652,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 5).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("5.00"), RENEW, false))
.build());
}
@@ -679,7 +680,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 5).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("5.00"), RENEW, false))
.build());
}
@@ -698,7 +699,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 17).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("17.00"), RENEW, false))
.build());
}
@@ -717,7 +718,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 85).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("85.00"), RENEW, false))
.build());
}
@@ -739,7 +740,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 10).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("10.00"), RENEW, false))
.build());
}
@@ -750,7 +751,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 100).getAmount(), RENEW, true))
.addFeeOrCredit(Fee.create(new BigDecimal("100.00"), RENEW, true))
.build());
}
@@ -765,7 +766,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 10).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("10.00"), RENEW, false))
.build());
}
@@ -780,7 +781,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 100).getAmount(), RENEW, true))
.addFeeOrCredit(Fee.create(new BigDecimal("100.00"), RENEW, true))
.build());
}
@@ -796,7 +797,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 10).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("10.00"), RENEW, false))
.build());
}
@@ -812,7 +813,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 10).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("10.00"), RENEW, false))
.build());
}
@@ -829,7 +830,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 1.23).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("1.23"), RENEW, false))
.build());
}
@@ -846,7 +847,61 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 1.23).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("1.23"), RENEW, false))
.build());
}
@Test
void testGetDomainCreatePrice_nonPremiumCreate_unaffectedRenewal() throws EppException {
AllocationToken allocationToken =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(SINGLE_USE)
.setDomainName("premium.example")
.setRegistrationBehavior(AllocationToken.RegistrationBehavior.NONPREMIUM_CREATE)
.build());
assertThat(
domainPricingLogic.getCreatePrice(
tld,
"premium.example",
clock.nowUtc(),
1,
false,
false,
Optional.of(allocationToken)))
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(new BigDecimal("13.00"), CREATE, false))
.build());
// Two-year create should be 13 (standard price) + 100 (premium price)
assertThat(
domainPricingLogic.getCreatePrice(
tld,
"premium.example",
clock.nowUtc(),
2,
false,
false,
Optional.of(allocationToken)))
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(new BigDecimal("113.00"), CREATE, false))
.build());
assertThat(
domainPricingLogic.getRenewPrice(
tld,
"premium.example",
clock.nowUtc(),
1,
persistDomainAndSetRecurrence("premium.example", DEFAULT, Optional.empty()),
Optional.of(allocationToken)))
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(new BigDecimal("100.00"), RENEW, true))
.build());
}
}
@@ -15,6 +15,7 @@
package google.registry.model;
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.START_DATE_SUNRISE;
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 org.joda.money.CurrencyUnit.USD;
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 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.RegistrarPoc;
import google.registry.model.tld.Tld;
import google.registry.model.tld.Tld.TldState;
import google.registry.persistence.transaction.JpaTestExtensions;
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.SystemClock;
import java.util.Optional;
import javax.annotation.Nullable;
import org.joda.money.Money;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
@@ -50,6 +63,9 @@ public final class OteAccountBuilderTest {
final JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
private final CloudTasksHelper cloudTasksHelper = new CloudTasksHelper();
private final IamClient iamClient = mock(IamClient.class);
@Test
void testGetRegistrarToTldMap() {
assertThat(OteAccountBuilder.forRegistrarId("myclientid").getRegistrarIdToTldMap())
@@ -89,23 +105,63 @@ public final class OteAccountBuilderTest {
assertThat(registrar.getAllowedTlds()).containsExactly(tld);
}
private static void assertContactExists(String registrarId, String email) {
Registrar registrar = Registrar.loadByRegistrarId(registrarId).get();
assertThat(registrar.getContacts().stream().map(RegistrarPoc::getEmailAddress)).contains(email);
RegistrarPoc contact =
registrar.getContacts().stream()
.filter(c -> email.equals(c.getEmailAddress()))
.findAny()
.get();
assertThat(contact.getEmailAddress()).isEqualTo(email);
assertThat(contact.getLoginEmailAddress()).isEqualTo(email);
public static void verifyUser(String registrarId, String email) {
Optional<User> maybeUser = UserDao.loadUser(email);
assertThat(maybeUser).isPresent();
assertThat(maybeUser.get().getUserRoles().getRegistrarRoles().get(registrarId))
.isEqualTo(RegistrarRole.ACCOUNT_MANAGER);
}
public static void verifyIapPermission(
@Nullable String emailAddress,
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
void testCreateOteEntities_success() {
OteAccountBuilder.forRegistrarId("myclientid")
.addContact("email@example.com")
.buildAndPersist();
OteAccountBuilder.forRegistrarId("myclientid").addUser("email@example.com").buildAndPersist();
assertTldExists("myclientid-sunrise", START_DATE_SUNRISE, 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-4", "myclientid-ga");
assertRegistrarExists("myclientid-5", "myclientid-eap");
assertContactExists("myclientid-1", "email@example.com");
assertContactExists("myclientid-3", "email@example.com");
assertContactExists("myclientid-4", "email@example.com");
assertContactExists("myclientid-5", "email@example.com");
verifyUser("myclientid-1", "email@example.com");
verifyUser("myclientid-3", "email@example.com");
verifyUser("myclientid-4", "email@example.com");
verifyUser("myclientid-5", "email@example.com");
}
@Test
void testCreateOteEntities_multipleContacts_success() {
OteAccountBuilder.forRegistrarId("myclientid")
.addContact("email@example.com")
.addContact("other@example.com")
.addContact("someone@example.com")
.addUser("email@example.com")
.addUser("other@example.com")
.addUser("someone@example.com")
.buildAndPersist();
assertTldExists("myclientid-sunrise", START_DATE_SUNRISE, Money.zero(USD));
@@ -135,18 +191,18 @@ public final class OteAccountBuilderTest {
assertRegistrarExists("myclientid-3", "myclientid-ga");
assertRegistrarExists("myclientid-4", "myclientid-ga");
assertRegistrarExists("myclientid-5", "myclientid-eap");
assertContactExists("myclientid-1", "email@example.com");
assertContactExists("myclientid-3", "email@example.com");
assertContactExists("myclientid-4", "email@example.com");
assertContactExists("myclientid-5", "email@example.com");
assertContactExists("myclientid-1", "other@example.com");
assertContactExists("myclientid-3", "other@example.com");
assertContactExists("myclientid-4", "other@example.com");
assertContactExists("myclientid-5", "other@example.com");
assertContactExists("myclientid-1", "someone@example.com");
assertContactExists("myclientid-3", "someone@example.com");
assertContactExists("myclientid-4", "someone@example.com");
assertContactExists("myclientid-5", "someone@example.com");
verifyUser("myclientid-1", "email@example.com");
verifyUser("myclientid-3", "email@example.com");
verifyUser("myclientid-4", "email@example.com");
verifyUser("myclientid-5", "email@example.com");
verifyUser("myclientid-1", "other@example.com");
verifyUser("myclientid-3", "other@example.com");
verifyUser("myclientid-4", "other@example.com");
verifyUser("myclientid-5", "other@example.com");
verifyUser("myclientid-1", "someone@example.com");
verifyUser("myclientid-3", "someone@example.com");
verifyUser("myclientid-4", "someone@example.com");
verifyUser("myclientid-5", "someone@example.com");
}
@Test
@@ -223,7 +279,7 @@ public final class OteAccountBuilderTest {
OteAccountBuilder oteSetupHelper = OteAccountBuilder.forRegistrarId("myclientid");
IllegalStateException thrown =
assertThrows(IllegalStateException.class, () -> oteSetupHelper.buildAndPersist());
assertThrows(IllegalStateException.class, oteSetupHelper::buildAndPersist);
assertThat(thrown)
.hasMessageThat()
.contains("Found existing object(s) conflicting with OT&E objects");
@@ -236,7 +292,7 @@ public final class OteAccountBuilderTest {
OteAccountBuilder oteSetupHelper = OteAccountBuilder.forRegistrarId("myclientid");
IllegalStateException thrown =
assertThrows(IllegalStateException.class, () -> oteSetupHelper.buildAndPersist());
assertThrows(IllegalStateException.class, oteSetupHelper::buildAndPersist);
assertThat(thrown)
.hasMessageThat()
.contains("Found existing object(s) conflicting with OT&E objects");
@@ -261,7 +317,7 @@ public final class OteAccountBuilderTest {
void testCreateOteEntities_doubleCreation_actuallyReplaces() {
OteAccountBuilder.forRegistrarId("myclientid")
.setPassword("oldPassword")
.addContact("email@example.com")
.addUser("email@example.com")
.buildAndPersist();
assertThat(Registrar.loadByRegistrarId("myclientid-3").get().verifyPassword("oldPassword"))
@@ -269,7 +325,7 @@ public final class OteAccountBuilderTest {
OteAccountBuilder.forRegistrarId("myclientid")
.setPassword("newPassword")
.addContact("email@example.com")
.addUser("email@example.com")
.setReplaceExisting(true)
.buildAndPersist();
@@ -281,19 +337,17 @@ public final class OteAccountBuilderTest {
@Test
void testCreateOteEntities_doubleCreation_keepsOldContacts() {
OteAccountBuilder.forRegistrarId("myclientid")
.addContact("email@example.com")
.buildAndPersist();
OteAccountBuilder.forRegistrarId("myclientid").addUser("email@example.com").buildAndPersist();
assertContactExists("myclientid-3", "email@example.com");
verifyUser("myclientid-3", "email@example.com");
OteAccountBuilder.forRegistrarId("myclientid")
.addContact("other@example.com")
.addUser("other@example.com")
.setReplaceExisting(true)
.buildAndPersist();
assertContactExists("myclientid-3", "other@example.com");
assertContactExists("myclientid-3", "email@example.com");
verifyUser("myclientid-3", "other@example.com");
verifyUser("myclientid-3", "email@example.com");
}
@Test
@@ -79,9 +79,7 @@ public final class OteStatsTestHelper {
public static void setupIncompleteOte(String baseClientId) throws IOException {
createTld("tld");
persistPremiumList("default_sandbox_list", USD, "sandbox,USD 1000");
OteAccountBuilder.forRegistrarId(baseClientId)
.addContact("email@example.com")
.buildAndPersist();
OteAccountBuilder.forRegistrarId(baseClientId).buildAndPersist();
String oteAccount1 = String.format("%s-1", baseClientId);
DateTime now = DateTime.now(DateTimeZone.UTC);
persistResource(
@@ -16,11 +16,21 @@ package google.registry.model.console;
import static com.google.common.truth.Truth.assertThat;
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 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.testing.CloudTasksHelper;
import google.registry.testing.CloudTasksHelper.TaskMatcher;
import google.registry.testing.DatabaseHelper;
import google.registry.tools.IamClient;
import java.util.Optional;
import org.junit.jupiter.api.Test;
/** Tests for {@link User}. */
@@ -104,4 +114,56 @@ public class UserTest extends EntityTestCase {
assertThat(user.hasRegistryLockPassword()).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);
}
}
@@ -1023,16 +1023,16 @@ public class DomainTest {
DesignatedContact.create(Type.TECH, contact4Key)),
true);
assertThat(domain.getRegistrant()).hasValue(contact1Key);
assertThat(domain.getAdminContact()).isEqualTo(contact2Key);
assertThat(domain.getBillingContact()).isEqualTo(contact3Key);
assertThat(domain.getTechContact()).isEqualTo(contact4Key);
assertThat(domain.getAdminContact()).hasValue(contact2Key);
assertThat(domain.getBillingContact()).hasValue(contact3Key);
assertThat(domain.getTechContact()).hasValue(contact4Key);
// Make sure everything gets nulled out.
domain.setContactFields(ImmutableSet.of(), true);
assertThat(domain.getRegistrant()).isEmpty();
assertThat(domain.getAdminContact()).isNull();
assertThat(domain.getBillingContact()).isNull();
assertThat(domain.getTechContact()).isNull();
assertThat(domain.getAdminContact()).isEmpty();
assertThat(domain.getBillingContact()).isEmpty();
assertThat(domain.getTechContact()).isEmpty();
// Make sure that changes don't affect the registrant unless requested.
domain.setContactFields(
@@ -1043,15 +1043,15 @@ public class DomainTest {
DesignatedContact.create(Type.TECH, contact4Key)),
false);
assertThat(domain.getRegistrant()).isEmpty();
assertThat(domain.getAdminContact()).isEqualTo(contact2Key);
assertThat(domain.getBillingContact()).isEqualTo(contact3Key);
assertThat(domain.getTechContact()).isEqualTo(contact4Key);
assertThat(domain.getAdminContact()).hasValue(contact2Key);
assertThat(domain.getBillingContact()).hasValue(contact3Key);
assertThat(domain.getTechContact()).hasValue(contact4Key);
domain = domain.asBuilder().setRegistrant(Optional.of(contact1Key)).build();
domain.setContactFields(ImmutableSet.of(), false);
assertThat(domain.getRegistrant()).hasValue(contact1Key);
assertThat(domain.getAdminContact()).isNull();
assertThat(domain.getBillingContact()).isNull();
assertThat(domain.getTechContact()).isNull();
assertThat(domain.getAdminContact()).isEmpty();
assertThat(domain.getBillingContact()).isEmpty();
assertThat(domain.getTechContact()).isEmpty();
}
@Test
@@ -564,6 +564,35 @@ public class AllocationTokenTest extends EntityTestCase {
.build();
}
@Test
void testFailures_badNonpremiumCreate() {
createTld("tld");
AllocationToken validToken =
new AllocationToken.Builder()
.setToken("abc")
.setTokenType(SINGLE_USE)
.setRegistrationBehavior(RegistrationBehavior.NONPREMIUM_CREATE)
.setDomainName("example.tld")
.build();
assertThrows(
IllegalArgumentException.class, () -> validToken.asBuilder().setDomainName(null).build());
assertThrows(
IllegalArgumentException.class,
() -> validToken.asBuilder().setDiscountFraction(0.5).build());
assertThrows(
IllegalArgumentException.class,
() ->
validToken
.asBuilder()
.setAllowedEppActions(
ImmutableSet.of(
CommandName.RENEW,
CommandName.TRANSFER,
CommandName.RESTORE,
CommandName.UPDATE))
.build());
}
private void assertBadInitialTransition(TokenStatus status) {
assertBadTransition(
ImmutableSortedMap.<DateTime, TokenStatus>naturalOrder()
@@ -27,6 +27,7 @@ import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrarPo
import static google.registry.testing.GsonSubject.assertAboutJson;
import static org.mockito.Mockito.verify;
import com.google.common.collect.ImmutableSet;
import com.google.gson.JsonObject;
import google.registry.model.contact.Contact;
import google.registry.model.domain.Domain;
@@ -292,6 +293,20 @@ class RdapDomainActionTest extends RdapActionBaseTestCase<RdapDomainAction> {
assertProperResponseForCatLol("cat.lol", "rdap_domain_no_contacts_with_remark.json");
}
@Test
void testValidDomain_notLoggedIn_contactsShowRedacted_whenNoContactsExist() {
// Even though the domain has no contacts, it still shows a full set of REDACTED fields through
// RDAP.
persistResource(
loadByForeignKey(Domain.class, "cat.lol", clock.nowUtc())
.get()
.asBuilder()
.setRegistrant(Optional.empty())
.setContacts(ImmutableSet.of())
.build());
assertProperResponseForCatLol("cat.lol", "rdap_domain_no_contacts_exist_with_remark.json");
}
@Test
void testValidDomain_loggedInAsOtherRegistrar_noContacts() {
login("idnregistrar");
@@ -135,7 +135,7 @@ public final class RegistryTestServerMain {
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 =
new UserRoles.Builder().setIsAdmin(loginIsAdmin).setGlobalRole(GlobalRole.FTE).build();
User user =
@@ -151,7 +151,7 @@ public final class RegistryTestServerMain {
for (Fixture fixture : fixtures) {
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();
System.out.printf("%sListening on: %s%s\n", PURPLE, server.getUrl("/"), RESET);
try {
@@ -24,7 +24,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.net.HostAndPort;
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 jakarta.servlet.http.HttpServlet;
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. */
public void start() {
try {
RegistrarSettingsAction.setIsInTestDriverToTrue();
RegistryEnvironment.setIsInTestDriver(true);
server.start();
} catch (Exception e) {
throwIfUnchecked(e);
@@ -129,7 +129,7 @@ public final class TestServer {
.callWithTimeout(
() -> {
server.stop();
RegistrarSettingsAction.setIsInTestDriverToFalse();
RegistryEnvironment.setIsInTestDriver(false);
return null;
},
SHUTDOWN_TIMEOUT_MS,
@@ -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
* 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
* tests, when they run on the same JVM (and therefore share the same static class members). To
* solve this we put the test container in a static map whose keys are the instance IDs. An
* explicitly created new {@link CloudTasksHelper} (as would be created for a new test method) would
* have a new ID allocated to it, and therefore stores its tasks in a distinct container. A
* deserialized {@link CloudTasksHelper}, on the other hand, will have the same instance ID and
* share the same test class container with its progenitor.
* tests when they run on the same JVM (and therefore share the same static class members). To solve
* this, we put the test container in a static map whose keys are the instance IDs. An explicitly
* created new {@link CloudTasksHelper} (as would be created for a new test method) would have a new
* ID allocated to it, and therefore stores its tasks in a distinct container. A deserialized {@link
* CloudTasksHelper}, on the other hand, will have the same instance ID and share the same test
* class container with its progenitor.
*/
public class CloudTasksHelper implements Serializable {
@@ -131,7 +131,7 @@ public class CloudTasksHelper implements Serializable {
*/
public void assertTasksEnqueuedWithProperty(
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))
.containsExactly((Object[]) expectedTaskProperties);
}
@@ -285,7 +285,7 @@ public class CloudTasksHelper implements Serializable {
});
headers = headerBuilder.build();
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.
if (method == HttpMethod.GET && uri.getQuery() != null) {
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.
*
* <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.
*/
@@ -15,20 +15,22 @@
package google.registry.tools;
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.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import com.google.cloud.tasks.v2.HttpMethod;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.net.MediaType;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.CloudTasksHelper.TaskMatcher;
import google.registry.testing.DatabaseHelper;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
@@ -38,13 +40,13 @@ import org.junit.jupiter.api.Test;
public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
private final IamClient iamClient = mock(IamClient.class);
private final ServiceConnection connection = mock(ServiceConnection.class);
private final CloudTasksHelper cloudTasksHelper = new CloudTasksHelper();
@BeforeEach
void beforeEach() {
command.iamClient = iamClient;
command.maybeGroupEmailAddress = Optional.empty();
command.setConnection(connection);
command.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
}
@Test
@@ -57,7 +59,7 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
assertThat(onlyUser.getUserRoles().getRegistrarRoles()).isEmpty();
verify(iamClient).addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE);
verifyNoMoreInteractions(iamClient);
verifyNoInteractions(connection);
cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
}
@Test
@@ -69,20 +71,16 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
assertThat(onlyUser.getUserRoles().isAdmin()).isFalse();
assertThat(onlyUser.getUserRoles().getGlobalRole()).isEqualTo(GlobalRole.NONE);
assertThat(onlyUser.getUserRoles().getRegistrarRoles()).isEmpty();
verify(connection)
.sendPostRequest(
"/_dr/admin/updateUserGroup",
ImmutableMap.of(
"userEmailAddress",
"user@example.test",
"groupEmailAddress",
"group@example.test",
"groupUpdateMode",
"ADD"),
MediaType.PLAIN_TEXT_UTF_8,
new byte[0]);
cloudTasksHelper.assertTasksEnqueued(
"console-user-group-update",
new TaskMatcher()
.method(HttpMethod.POST)
.service("TOOLS")
.path("/_dr/admin/updateUserGroup")
.param("userEmailAddress", "user@example.test")
.param("groupEmailAddress", "group@example.test")
.param("groupUpdateMode", "ADD"));
verifyNoInteractions(iamClient);
verifyNoMoreInteractions(connection);
}
@Test
@@ -102,7 +100,7 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().isAdmin()).isTrue();
verify(iamClient).addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE);
verifyNoMoreInteractions(iamClient);
verifyNoInteractions(connection);
cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
}
@Test
@@ -112,7 +110,7 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
.isEqualTo(GlobalRole.FTE);
verify(iamClient).addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE);
verifyNoMoreInteractions(iamClient);
verifyNoInteractions(connection);
cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
}
@Test
@@ -131,7 +129,7 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
RegistrarRole.PRIMARY_CONTACT));
verify(iamClient).addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE);
verifyNoMoreInteractions(iamClient);
verifyNoInteractions(connection);
cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
}
@Test
@@ -146,7 +144,7 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
.hasMessageThat()
.isEqualTo("A user with email user@example.test already exists");
verifyNoMoreInteractions(iamClient);
verifyNoInteractions(connection);
cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
}
@Test
@@ -15,16 +15,17 @@
package google.registry.tools;
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.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import com.google.common.collect.ImmutableMap;
import com.google.common.net.MediaType;
import com.google.cloud.tasks.v2.HttpMethod;
import google.registry.model.console.UserDao;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.CloudTasksHelper.TaskMatcher;
import google.registry.testing.DatabaseHelper;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
@@ -34,13 +35,13 @@ import org.junit.jupiter.api.Test;
public class DeleteUserCommandTest extends CommandTestCase<DeleteUserCommand> {
private final IamClient iamClient = mock(IamClient.class);
private final ServiceConnection connection = mock(ServiceConnection.class);
private final CloudTasksHelper cloudTasksHelper = new CloudTasksHelper();
@BeforeEach
void beforeEach() {
command.iamClient = iamClient;
command.setConnection(connection);
command.maybeGroupEmailAddress = Optional.empty();
command.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
}
@Test
@@ -51,7 +52,7 @@ public class DeleteUserCommandTest extends CommandTestCase<DeleteUserCommand> {
assertThat(UserDao.loadUser("email@example.test")).isEmpty();
verify(iamClient).removeBinding("email@example.test", IAP_SECURED_WEB_APP_USER_ROLE);
verifyNoMoreInteractions(iamClient);
verifyNoInteractions(connection);
cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
}
@Test
@@ -61,20 +62,16 @@ public class DeleteUserCommandTest extends CommandTestCase<DeleteUserCommand> {
assertThat(UserDao.loadUser("email@example.test")).isPresent();
runCommandForced("--email", "email@example.test");
assertThat(UserDao.loadUser("email@example.test")).isEmpty();
verify(connection)
.sendPostRequest(
"/_dr/admin/updateUserGroup",
ImmutableMap.of(
"userEmailAddress",
"email@example.test",
"groupEmailAddress",
"group@example.test",
"groupUpdateMode",
"REMOVE"),
MediaType.PLAIN_TEXT_UTF_8,
new byte[0]);
cloudTasksHelper.assertTasksEnqueued(
"console-user-group-update",
new TaskMatcher()
.method(HttpMethod.POST)
.service("TOOLS")
.path("/_dr/admin/updateUserGroup")
.param("userEmailAddress", "email@example.test")
.param("groupEmailAddress", "group@example.test")
.param("groupUpdateMode", "REMOVE"));
verifyNoInteractions(iamClient);
verifyNoMoreInteractions(connection);
}
@Test
@@ -86,6 +83,6 @@ public class DeleteUserCommandTest extends CommandTestCase<DeleteUserCommand> {
.hasMessageThat()
.isEqualTo("Email does not correspond to a valid user");
verifyNoInteractions(iamClient);
verifyNoInteractions(connection);
cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
}
}
@@ -15,6 +15,8 @@
package google.registry.tools;
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.tld.Tld.TldState.GENERAL_AVAILABILITY;
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.time.DateTimeZone.UTC;
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.google.cloud.tasks.v2.HttpMethod;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.model.console.UserRoles;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.model.tld.Tld;
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.FakeClock;
import google.registry.util.CidrAddressBlock;
import java.security.cert.CertificateParsingException;
import java.util.Optional;
import javax.annotation.Nullable;
import org.joda.money.Money;
import org.joda.time.DateTime;
import org.joda.time.Duration;
@@ -50,22 +63,24 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
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");
@BeforeEach
void beforeEach() {
command.passwordGenerator = passwordGenerator;
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");
}
/** Verify TLD creation. */
private void verifyTldCreation(
String tldName,
String roidSuffix,
TldState tldState,
boolean isEarlyAccess) {
String tldName, String roidSuffix, TldState tldState, boolean isEarlyAccess) {
Tld registry = Tld.get(tldName);
assertThat(registry).isNotNull();
assertThat(registry.getRoidSuffix()).isEqualTo(roidSuffix);
@@ -107,13 +122,28 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
assertThat(registrar.getClientCertificateHash()).hasValue(SAMPLE_CERT_HASH);
}
private void verifyRegistrarContactCreation(String registrarName, String email) {
ImmutableSet<RegistrarPoc> registrarPocs = loadRegistrar(registrarName).getContacts();
assertThat(registrarPocs).hasSize(1);
RegistrarPoc registrarPoc = registrarPocs.stream().findAny().get();
assertThat(registrarPoc.getEmailAddress()).isEqualTo(email);
assertThat(registrarPoc.getName()).isEqualTo(email);
assertThat(registrarPoc.getLoginEmailAddress()).isEqualTo(email);
private void verifyIapPermission(@Nullable String emailAddress) {
if (emailAddress == null) {
cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
verifyNoInteractions(iamClient);
} else {
String groupEmailAddress = command.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
@@ -136,10 +166,12 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
verifyRegistrarCreation("blobio-4", "blobio-ga", PASSWORD, ipAddress);
verifyRegistrarCreation("blobio-5", "blobio-eap", PASSWORD, ipAddress);
verifyRegistrarContactCreation("blobio-1", "contact@email.com");
verifyRegistrarContactCreation("blobio-3", "contact@email.com");
verifyRegistrarContactCreation("blobio-4", "contact@email.com");
verifyRegistrarContactCreation("blobio-5", "contact@email.com");
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
@@ -162,10 +194,12 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
verifyRegistrarCreation("abc-4", "abc-ga", PASSWORD, ipAddress);
verifyRegistrarCreation("abc-5", "abc-eap", PASSWORD, ipAddress);
verifyRegistrarContactCreation("abc-1", "abc@email.com");
verifyRegistrarContactCreation("abc-3", "abc@email.com");
verifyRegistrarContactCreation("abc-4", "abc@email.com");
verifyRegistrarContactCreation("abc-5", "abc@email.com");
verifyUser("abc-1", "abc@email.com");
verifyUser("abc-3", "abc@email.com");
verifyUser("abc-4", "abc@email.com");
verifyUser("abc-5", "abc@email.com");
verifyIapPermission("abc@email.com");
}
@Test
@@ -180,19 +214,20 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
verifyTldCreation("blobio-ga", "BLOBIOG2", GENERAL_AVAILABILITY, false);
verifyTldCreation("blobio-eap", "BLOBIOE3", GENERAL_AVAILABILITY, true);
ImmutableList<CidrAddressBlock> ipAddresses = ImmutableList.of(
CidrAddressBlock.create("1.1.1.1"),
CidrAddressBlock.create("2.2.2.2"));
ImmutableList<CidrAddressBlock> ipAddresses =
ImmutableList.of(CidrAddressBlock.create("1.1.1.1"), CidrAddressBlock.create("2.2.2.2"));
verifyRegistrarCreation("blobio-1", "blobio-sunrise", PASSWORD, ipAddresses);
verifyRegistrarCreation("blobio-3", "blobio-ga", PASSWORD, ipAddresses);
verifyRegistrarCreation("blobio-4", "blobio-ga", PASSWORD, ipAddresses);
verifyRegistrarCreation("blobio-5", "blobio-eap", PASSWORD, ipAddresses);
verifyRegistrarContactCreation("blobio-1", "contact@email.com");
verifyRegistrarContactCreation("blobio-3", "contact@email.com");
verifyRegistrarContactCreation("blobio-4", "contact@email.com");
verifyRegistrarContactCreation("blobio-5", "contact@email.com");
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
@@ -206,6 +241,7 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
"--email=contact@email.com",
"--certfile=" + getCertFilename()));
assertThat(thrown).hasMessageThat().contains("option is required: [-a | --ip_allow_list]");
verifyIapPermission(null);
}
@Test
@@ -219,6 +255,7 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
"--email=contact@email.com",
"--certfile=" + getCertFilename()));
assertThat(thrown).hasMessageThat().contains("option is required: [-r | --registrar]");
verifyIapPermission(null);
}
@Test
@@ -232,6 +269,7 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
assertThat(thrown)
.hasMessageThat()
.contains("Must specify exactly one client certificate file.");
verifyIapPermission(null);
}
@Test
@@ -245,6 +283,36 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
"--certfile=" + getCertFilename(),
"--registrar=blobio"));
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
@@ -259,6 +327,7 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
"--email=contact@email.com",
"--certfile=/dev/null"));
assertThat(thrown).hasMessageThat().contains("No X509Certificate found");
verifyIapPermission(null);
}
@Test
@@ -273,6 +342,7 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
"--email=contact@email.com",
"--certfile=" + getCertFilename()));
assertThat(thrown).hasMessageThat().contains("Invalid registrar name: 3blo-bio");
verifyIapPermission(null);
}
@Test
@@ -287,6 +357,7 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
"--email=contact@email.com",
"--certfile=" + getCertFilename()));
assertThat(thrown).hasMessageThat().contains("Invalid registrar name: bl");
verifyIapPermission(null);
}
@Test
@@ -301,6 +372,7 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
"--email=contact@email.com",
"--certfile=" + getCertFilename()));
assertThat(thrown).hasMessageThat().contains("Invalid registrar name: blobiotoooolong");
verifyIapPermission(null);
}
@Test
@@ -315,6 +387,7 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
"--email=contact@email.com",
"--certfile=" + getCertFilename()));
assertThat(thrown).hasMessageThat().contains("Invalid registrar name: blo#bio");
verifyIapPermission(null);
}
@Test
@@ -330,6 +403,7 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
"--email=contact@email.com",
"--certfile=" + getCertFilename()));
assertThat(thrown).hasMessageThat().contains("VKey<Tld>(sql:blobio-sunrise)");
verifyIapPermission(null);
}
@Test
@@ -345,6 +419,8 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
verifyTldCreation("blobio-sunrise", "BLOBIOS0", START_DATE_SUNRISE, false);
verifyTldCreation("blobio-ga", "BLOBIOG2", GENERAL_AVAILABILITY, false);
verifyIapPermission("contact@email.com");
}
@Test
@@ -366,6 +442,28 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
"--email=contact@email.com",
"--certfile=" + getCertFilename()));
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
@@ -385,10 +483,46 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
"--email=contact@email.com",
"--certfile=" + getCertFilename());
ImmutableList<CidrAddressBlock> ipAddress = ImmutableList.of(
CidrAddressBlock.create("1.1.1.1"));
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);
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");
}
}
@@ -15,11 +15,14 @@
package google.registry.ui.server.registrar;
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.testing.DatabaseHelper.persistPremiumList;
import static jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
import static org.joda.money.CurrencyUnit.USD;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
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.AuthenticatedRegistrarAccessor;
import google.registry.security.XsrfTokenManager;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.DeterministicStringGenerator;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeResponse;
import google.registry.testing.SystemPropertyExtension;
import google.registry.tools.IamClient;
import google.registry.ui.server.SendEmailUtils;
import google.registry.util.EmailMessage;
import google.registry.util.RegistryEnvironment;
@@ -65,6 +70,8 @@ public final class ConsoleOteSetupActionTest {
@Order(value = Integer.MAX_VALUE)
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 ConsoleOteSetupAction action = new ConsoleOteSetupAction();
private final User user =
@@ -100,6 +107,9 @@ public final class ConsoleOteSetupActionTest {
action.optionalPassword = Optional.empty();
action.passwordGenerator = new DeterministicStringGenerator("abcdefghijklmnopqrstuvwxyz");
action.maybeGroupEmailAddress = Optional.of("group@example.com");
action.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
action.iamClient = iamClient;
}
@Test
@@ -142,9 +152,7 @@ public final class ConsoleOteSetupActionTest {
// checking that all the entities are there or that they have the correct values.
assertThat(loadByRegistrarId("myclientid-3")).isPresent();
assertThat(Tld.get("myclientid-ga")).isNotNull();
assertThat(
loadByRegistrarId("myclientid-5").get().getContacts().asList().get(0).getEmailAddress())
.isEqualTo("contact@registry.example");
verifyUser("myclientid-5", "contact@registry.example");
assertThat(response.getPayload())
.contains("<h1>OT&E successfully created for registrar myclientid!</h1>");
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
""");
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
@@ -15,10 +15,13 @@
package google.registry.ui.server.registrar;
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.testing.DatabaseHelper.persistPremiumList;
import static jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
import static org.joda.money.CurrencyUnit.USD;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
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.registrar.Registrar;
import google.registry.model.registrar.RegistrarAddress;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.request.Action.Method;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.security.XsrfTokenManager;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.DeterministicStringGenerator;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeResponse;
import google.registry.testing.SystemPropertyExtension;
import google.registry.tools.IamClient;
import google.registry.ui.server.SendEmailUtils;
import google.registry.util.EmailMessage;
import google.registry.util.RegistryEnvironment;
@@ -73,6 +77,8 @@ final class ConsoleRegistrarCreatorActionTest {
.setEmailAddress("marla.singer@example.com")
.setUserRoles(new UserRoles())
.build();
private final CloudTasksHelper cloudTasksHelper = new CloudTasksHelper();
private final IamClient iamClient = mock(IamClient.class);
@Mock HttpServletRequest request;
@Mock GmailClient gmailClient;
@@ -119,6 +125,10 @@ final class ConsoleRegistrarCreatorActionTest {
action.passcodeGenerator = new DeterministicStringGenerator("314159265");
action.analyticsConfig = ImmutableMap.of("googleAnalyticsId", "sampleId");
action.maybeGroupEmailAddress = Optional.of("group@example.com");
action.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
action.iamClient = iamClient;
}
@Test
@@ -152,8 +162,7 @@ final class ConsoleRegistrarCreatorActionTest {
assertThat(response.getPayload()).contains("gtag('config', 'sampleId')");
}
@Test
void testPost_authorized_minimalAddress() {
void runTestPost_authorized_minimalAddress() {
action.clientId = Optional.of("myclientid");
action.name = Optional.of("registrar name");
action.billingAccount = Optional.of("USD=billing-account");
@@ -209,14 +218,22 @@ final class ConsoleRegistrarCreatorActionTest {
.setCountryCode("CC")
.build());
assertThat(registrar.getContacts())
.containsExactly(
new RegistrarPoc.Builder()
.setRegistrar(registrar)
.setName("myclientid@registry.example")
.setEmailAddress("myclientid@registry.example")
.setLoginEmailAddress("myclientid@registry.example")
.build());
verifyUser("myclientid", "myclientid@registry.example");
}
@Test
void testPost_authorized_minimalAddress_consoleUserGroup() {
runTestPost_authorized_minimalAddress();
verifyIapPermission(
"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
@@ -307,6 +307,25 @@ class DomainWhoisResponseTest {
.isEqualTo(WhoisResponseResults.create(loadFile("whois_domain_no_registrant.txt"), 1));
}
@Test
void getPlainTextOutputTest_noContacts() {
DomainWhoisResponse domainWhoisResponse =
new DomainWhoisResponse(
domain
.asBuilder()
.setRegistrant(Optional.empty())
.setContacts(ImmutableSet.of())
.build(),
false,
"Please contact registrar",
clock.nowUtc());
assertThat(
domainWhoisResponse.getResponse(
false,
"Doodle Disclaimer\nI exist so that carriage return\nin disclaimer can be tested."))
.isEqualTo(WhoisResponseResults.create(loadFile("whois_domain_no_contacts.txt"), 1));
}
@Test
void getPlainTextOutputTest_registrarAbuseInfoMissing() {
persistResource(abuseContact.asBuilder().setVisibleInDomainWhoisAsAbuse(false).build());
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp xmlns:domain="urn:ietf:params:xml:ns:domain-1.0" xmlns="urn:ietf:params:xml:ns:epp-1.0"
xmlns:fee12="urn:ietf:params:xml:ns:fee-0.12"
>
<response>
<result code="1000">
<msg>Command completed successfully</msg>
</result>
<resData>
<domain:creData>
<domain:name>rich.example</domain:name>
<domain:crDate>1999-04-03T22:00:00Z</domain:crDate>
<domain:exDate>2000-04-03T22:00:00Z</domain:exDate>
</domain:creData>
</resData>
<extension>
<fee12:creData>
<fee12:currency>USD</fee12:currency>
<fee12:fee description="create">13.00</fee12:fee>
</fee12:creData>
</extension>
<trID>
<clTRID>ABC-12345</clTRID>
<svTRID>server-trid</svTRID>
</trID>
</response>
</epp>
@@ -0,0 +1,35 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<response>
<result code="1000">
<msg>Command completed successfully</msg>
</result>
<resData>
<domain:infData
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>example.tld</domain:name>
<domain:roid>%ROID%</domain:roid>
<domain:status s="ok"/>
<domain:ns>
<domain:hostObj>ns1.example.tld</domain:hostObj>
<domain:hostObj>ns1.example.net</domain:hostObj>
</domain:ns>
<domain:host>ns1.example.tld</domain:host>
<domain:host>ns2.example.tld</domain:host>
<domain:clID>NewRegistrar</domain:clID>
<domain:crID>TheRegistrar</domain:crID>
<domain:crDate>1999-04-03T22:00:00.0Z</domain:crDate>
<domain:upID>NewRegistrar</domain:upID>
<domain:upDate>1999-12-03T09:00:00.0Z</domain:upDate>
<domain:exDate>2005-04-03T22:00:00.0Z</domain:exDate>
<domain:trDate>2000-04-08T09:00:00.0Z</domain:trDate>
<domain:authInfo>
<domain:pw>2fooBAR</domain:pw>
</domain:authInfo>
</domain:infData>
</resData>
<trID>
<clTRID>ABC-12345</clTRID>
<svTRID>server-trid</svTRID>
</trID>
</response>
</epp>
@@ -0,0 +1,263 @@
{
"rdapConformance": [
"rdap_level_0",
"icann_rdap_response_profile_0",
"icann_rdap_technical_implementation_guide_0"
],
"objectClassName": "domain",
"handle": "%DOMAIN_HANDLE_1%",
"ldhName": "%DOMAIN_PUNYCODE_NAME_1%",
"status": [
"client delete prohibited",
"client renew prohibited",
"client transfer prohibited",
"server update prohibited"
],
"links": [
{
"href": "https://example.tld/rdap/domain/%DOMAIN_PUNYCODE_NAME_1%",
"type": "application/rdap+json",
"rel": "self"
},
{
"href": "https://rdap.example.com/withSlash/domain/%DOMAIN_PUNYCODE_NAME_1%",
"type": "application/rdap+json",
"rel": "related"
},
{
"href": "https://rdap.example.com/withoutSlash/domain/%DOMAIN_PUNYCODE_NAME_1%",
"type": "application/rdap+json",
"rel": "related"
}
],
"events": [
{
"eventAction": "registration",
"eventActor": "TheRegistrar",
"eventDate": "1997-01-01T00:00:00.000Z"
},
{
"eventAction": "expiration",
"eventDate": "2110-10-08T00:44:59.000Z"
},
{
"eventAction": "last update of RDAP database",
"eventDate": "2000-01-01T00:00:00.000Z"
},
{
"eventAction": "last changed",
"eventDate": "2009-05-29T20:13:00.000Z"
}
],
"nameservers": [
{
"objectClassName": "nameserver",
"handle": "%NAMESERVER_HANDLE_1%",
"ldhName": "%NAMESERVER_NAME_1%",
"links": [
{
"href": "https://example.tld/rdap/nameserver/%NAMESERVER_NAME_1%",
"type": "application/rdap+json",
"rel": "self"
}
],
"remarks": [
{
"title": "Incomplete Data",
"type": "object truncated due to unexplainable reasons",
"description": ["Summary data only. For complete data, send a specific query for the object."]
}
]
},
{
"objectClassName": "nameserver",
"handle": "%NAMESERVER_HANDLE_2%",
"ldhName": "%NAMESERVER_NAME_2%",
"links": [
{
"href": "https://example.tld/rdap/nameserver/%NAMESERVER_NAME_2%",
"type": "application/rdap+json",
"rel": "self"
}
],
"remarks": [
{
"title": "Incomplete Data",
"type": "object truncated due to unexplainable reasons",
"description": ["Summary data only. For complete data, send a specific query for the object."]
}
]
}
],
"secureDNS" : {
"delegationSigned": true,
"zoneSigned":true,
"dsData":[
{"algorithm":2,"digest":"DEADFACE","digestType":3,"keyTag":1}
]
},
"entities": [
{
"objectClassName" : "entity",
"handle" : "1",
"roles" : ["registrar"],
"links" : [
{
"rel" : "self",
"href" : "https://example.tld/rdap/entity/1",
"type" : "application/rdap+json"
}
],
"publicIds" : [
{
"type" : "IANA Registrar ID",
"identifier" : "1"
}
],
"vcardArray" : [
"vcard",
[
["version", {}, "text", "4.0"],
["fn", {}, "text", "%REGISTRAR_FULL_NAME_1%"]
]
],
"entities" : [
{
"objectClassName":"entity",
"roles":["abuse"],
"status":["active"],
"vcardArray": [
"vcard",
[
["version",{},"text","4.0"],
["fn",{},"text","Jake Doe"],
["tel",{"type":["voice"]},"uri","tel:+1.2125551216"],
["tel",{"type":["fax"]},"uri","tel:+1.2125551216"],
["email",{},"text","jakedoe@example.com"]
]
]
}
],
"remarks": [
{
"title": "Incomplete Data",
"description": [
"Summary data only. For complete data, send a specific query for the object."
],
"type": "object truncated due to unexplainable reasons"
}
]
},
{
"objectClassName":"entity",
"handle":"",
"remarks":[
{
"title":"REDACTED FOR PRIVACY",
"type":"object redacted due to authorization",
"description":[
"Some of the data in this object has been removed.",
"Contact personal data is visible only to the owning registrar."
],
"links":[
{
"href":"https://github.com/google/nomulus/blob/master/docs/rdap.md#authentication",
"rel":"alternate",
"type":"text/html"
}
]
},
{
"title":"EMAIL REDACTED FOR PRIVACY",
"type":"object redacted due to authorization",
"description":[
"Please query the RDDS service of the Registrar of Record identifies in this output for information on how to contact the Registrant of the queried domain name."
]
}
],
"roles":["registrant"],
"vcardArray":[
"vcard",
[
["version", {}, "text", "4.0"],
["fn", {}, "text", ""]
]
]
},
{
"objectClassName": "entity",
"handle": "",
"roles":["administrative"],
"remarks": [
{
"title":"REDACTED FOR PRIVACY",
"type":"object redacted due to authorization",
"description": [
"Some of the data in this object has been removed.",
"Contact personal data is visible only to the owning registrar."
],
"links":[
{
"href":"https://github.com/google/nomulus/blob/master/docs/rdap.md#authentication",
"rel":"alternate",
"type":"text/html"
}
]
},
{
"title":"EMAIL REDACTED FOR PRIVACY",
"type":"object redacted due to authorization",
"description": [
"Please query the RDDS service of the Registrar of Record identifies in this output for information on how to contact the Registrant of the queried domain name."
]
}
],
"vcardArray":[
"vcard",
[
["version", {}, "text", "4.0"],
["fn", {}, "text", ""]
]
]
},
{
"objectClassName":"entity",
"handle":"",
"remarks":[
{
"title":"REDACTED FOR PRIVACY",
"type":"object redacted due to authorization",
"description":[
"Some of the data in this object has been removed.",
"Contact personal data is visible only to the owning registrar."
],
"links":[
{
"href":"https://github.com/google/nomulus/blob/master/docs/rdap.md#authentication",
"rel":"alternate",
"type":"text/html"
}
]
},
{
"description":[
"Please query the RDDS service of the Registrar of Record identifies in this output for information on how to contact the Registrant of the queried domain name."
],
"title":"EMAIL REDACTED FOR PRIVACY",
"type":"object redacted due to authorization"
}
],
"roles": ["technical"],
"vcardArray": [
"vcard",
[
["version", {}, "text", "4.0"],
["fn", {}, "text", ""]
]
]
}
]
}
@@ -0,0 +1,28 @@
Domain Name: example.tld
Registry Domain ID: 3-TLD
Registrar WHOIS Server: whois.nic.fakewhois.example
Registrar URL: http://my.fake.url
Updated Date: 2009-05-29T20:13:00Z
Creation Date: 2000-10-08T00:45:00Z
Registry Expiry Date: 2010-10-08T00:44:59Z
Registrar: New Registrar
Registrar IANA ID: 5555555
Registrar Abuse Contact Email: jakedoe@theregistrar.com
Registrar Abuse Contact Phone: +1.2125551216
Domain Status: addPeriod https://icann.org/epp#addPeriod
Domain Status: clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited
Domain Status: clientRenewProhibited https://icann.org/epp#clientRenewProhibited
Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
Domain Status: serverUpdateProhibited https://icann.org/epp#serverUpdateProhibited
Domain Status: transferPeriod https://icann.org/epp#transferPeriod
Name Server: ns01.exampleregistrar.tld
Name Server: ns02.exampleregistrar.tld
DNSSEC: signedDelegation
URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/
>>> Last update of WHOIS database: 2009-05-29T20:15:00Z <<<
For more information on Whois status codes, please visit https://icann.org/epp
Doodle Disclaimer
I exist so that carriage return
in disclaimer can be tested.
@@ -14,6 +14,8 @@
package google.registry.util;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.base.Ascii;
/** Registry environments. */
@@ -59,6 +61,20 @@ public enum RegistryEnvironment {
private static final boolean ON_JETTY =
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. */
public RegistryEnvironment setup() {
return setup(SystemPropertySetter.PRODUCTION_IMPL);
@@ -81,4 +97,13 @@ public enum RegistryEnvironment {
public static boolean isOnJetty() {
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();
}
}