mirror of
https://github.com/google/nomulus
synced 2026-07-26 18:12:38 +00:00
Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc44e095d4 | ||
|
|
355e8ba423 | ||
|
|
dca5a010d5 | ||
|
|
147e7dabcb | ||
|
|
0b8025d3db | ||
|
|
9c4a558d7b | ||
|
|
7347dbf762 | ||
|
|
53b6044ff8 | ||
|
|
12ab7f47e2 | ||
|
|
d3a1d0709e | ||
|
|
f0fae9898b |
@@ -61,6 +61,14 @@
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"optimization": {
|
||||
"scripts": true,
|
||||
"styles": {
|
||||
"minify": true,
|
||||
"inlineCritical": false
|
||||
},
|
||||
"fonts": true
|
||||
},
|
||||
"outputHashing": "all"
|
||||
},
|
||||
"sandbox": {
|
||||
@@ -82,6 +90,14 @@
|
||||
"with": "src/environments/environment.sandbox.ts"
|
||||
}
|
||||
],
|
||||
"optimization": {
|
||||
"scripts": true,
|
||||
"styles": {
|
||||
"minify": true,
|
||||
"inlineCritical": false
|
||||
},
|
||||
"fonts": true
|
||||
},
|
||||
"outputHashing": "all"
|
||||
},
|
||||
"crash": {
|
||||
|
||||
@@ -165,6 +165,12 @@ public final class RegistryConfig {
|
||||
return config.misc.isEmailSendingEnabled;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("consoleIapServiceId")
|
||||
public static Optional<String> provideConsoleIapServiceId(RegistryConfigSettings config) {
|
||||
return Optional.ofNullable(config.registrarConsole.consoleIapServiceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* The e-mail address for general support. Used in the "contact-us" section of the registrar
|
||||
* console.
|
||||
|
||||
@@ -187,6 +187,7 @@ public class RegistryConfigSettings {
|
||||
|
||||
/** Configuration for the web-based registrar console. */
|
||||
public static class RegistrarConsole {
|
||||
public String consoleIapServiceId;
|
||||
public String dumFileName;
|
||||
public String supportPhoneNumber;
|
||||
public String supportEmailAddress;
|
||||
|
||||
@@ -390,6 +390,11 @@ rde:
|
||||
sshIdentityEmailAddress: rde@example.com
|
||||
|
||||
registrarConsole:
|
||||
# IAP service ID of the HTTP(s) load balancer that serves the console-api backend.
|
||||
# Users created in the console will be bound only to this service to limit access.
|
||||
# This is only applicable to non-group-based auth (if there is no consoleUserGroupEmailAddress)
|
||||
consoleIapServiceId: null
|
||||
|
||||
# DUM download file name, excluding the extension
|
||||
dumFileName: dum_file_name
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import static google.registry.flows.ResourceFlowUtils.verifyResourceDoesNotExist
|
||||
import static google.registry.flows.host.HostFlowUtils.lookupSuperordinateDomain;
|
||||
import static google.registry.flows.host.HostFlowUtils.validateHostName;
|
||||
import static google.registry.flows.host.HostFlowUtils.verifySuperordinateDomainNotInPendingDelete;
|
||||
import static google.registry.flows.host.HostFlowUtils.verifySuperordinateDomainNotServerUpdateProhibited;
|
||||
import static google.registry.flows.host.HostFlowUtils.verifySuperordinateDomainOwnership;
|
||||
import static google.registry.model.EppResourceUtils.createRepoId;
|
||||
import static google.registry.model.reporting.HistoryEntry.Type.HOST_CREATE;
|
||||
@@ -73,6 +74,7 @@ import java.util.Optional;
|
||||
* @error {@link HostFlowUtils.HostNameNotPunyCodedException}
|
||||
* @error {@link HostFlowUtils.SuperordinateDomainDoesNotExistException}
|
||||
* @error {@link HostFlowUtils.SuperordinateDomainInPendingDeleteException}
|
||||
* @error {@link HostFlowUtils.SuperordinateDomainServerUpdateProhibitedException}
|
||||
* @error {@link SubordinateHostMustHaveIpException}
|
||||
* @error {@link UnexpectedExternalHostIpException}
|
||||
*/
|
||||
@@ -107,6 +109,7 @@ public final class HostCreateFlow implements MutatingFlow {
|
||||
Optional<Domain> superordinateDomain =
|
||||
lookupSuperordinateDomain(validateHostName(targetId), now);
|
||||
verifySuperordinateDomainNotInPendingDelete(superordinateDomain.orElse(null));
|
||||
verifySuperordinateDomainNotServerUpdateProhibited(superordinateDomain.orElse(null));
|
||||
verifySuperordinateDomainOwnership(registrarId, superordinateDomain.orElse(null));
|
||||
boolean willBeSubordinate = superordinateDomain.isPresent();
|
||||
boolean hasIpAddresses = !isNullOrEmpty(command.getInetAddresses());
|
||||
|
||||
@@ -22,6 +22,7 @@ import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyNoDisallowedStatuses;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
|
||||
import static google.registry.flows.host.HostFlowUtils.validateHostName;
|
||||
import static google.registry.flows.host.HostFlowUtils.verifySuperordinateDomainNotServerUpdateProhibited;
|
||||
import static google.registry.model.eppoutput.Result.Code.SUCCESS;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
@@ -33,7 +34,7 @@ import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
@@ -62,6 +63,7 @@ import java.time.Instant;
|
||||
* @error {@link HostFlowUtils.HostNameNotLowerCaseException}
|
||||
* @error {@link HostFlowUtils.HostNameNotNormalizedException}
|
||||
* @error {@link HostFlowUtils.HostNameNotPunyCodedException}
|
||||
* @error {@link HostFlowUtils.SuperordinateDomainServerUpdateProhibitedException}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.HOST_DELETE)
|
||||
public final class HostDeleteFlow implements MutatingFlow {
|
||||
@@ -90,12 +92,15 @@ public final class HostDeleteFlow implements MutatingFlow {
|
||||
if (!isSuperuser) {
|
||||
verifyNoDisallowedStatuses(existingHost, DELETE_PROHIBITED_STATUSES);
|
||||
// Hosts transfer with their superordinate domains, so for hosts with a superordinate domain,
|
||||
// the client id, needs to be read off of it.
|
||||
EppResource owningResource =
|
||||
existingHost.isSubordinate()
|
||||
? tm().loadByKey(existingHost.getSuperordinateDomain()).cloneProjectedAtTime(now)
|
||||
: existingHost;
|
||||
verifyResourceOwnership(registrarId, owningResource);
|
||||
// the client id needs to be read off of it.
|
||||
if (existingHost.isSubordinate()) {
|
||||
Domain superordinateDomain =
|
||||
tm().loadByKey(existingHost.getSuperordinateDomain()).cloneProjectedAtTime(now);
|
||||
verifyResourceOwnership(registrarId, superordinateDomain);
|
||||
verifySuperordinateDomainNotServerUpdateProhibited(superordinateDomain);
|
||||
} else {
|
||||
verifyResourceOwnership(registrarId, existingHost);
|
||||
}
|
||||
}
|
||||
Host newHost = existingHost.asBuilder().setStatusValues(null).setDeletionTime(now).build();
|
||||
if (existingHost.isSubordinate()) {
|
||||
|
||||
@@ -174,6 +174,31 @@ public class HostFlowUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the superordinate domain does not have {@code serverUpdateProhibited} status.
|
||||
*
|
||||
* <p>Subordinate hosts publish glue A/AAAA records in the TLD zone on behalf of their
|
||||
* superordinate domain. If the superordinate domain is registry-locked (carries {@link
|
||||
* StatusValue#SERVER_UPDATE_PROHIBITED}), non-superuser modifications to its subordinate hosts
|
||||
* must be rejected as well; otherwise a compromised sponsoring registrar could hijack DNS for a
|
||||
* locked domain by swapping the glue addresses of its in-bailiwick nameservers.
|
||||
*/
|
||||
static void verifySuperordinateDomainNotServerUpdateProhibited(Domain superordinateDomain)
|
||||
throws EppException {
|
||||
if ((superordinateDomain != null)
|
||||
&& superordinateDomain.getStatusValues().contains(StatusValue.SERVER_UPDATE_PROHIBITED)) {
|
||||
throw new SuperordinateDomainServerUpdateProhibitedException();
|
||||
}
|
||||
}
|
||||
|
||||
/** Superordinate domain for this hostname has serverUpdateProhibited status. */
|
||||
static class SuperordinateDomainServerUpdateProhibitedException
|
||||
extends StatusProhibitsOperationException {
|
||||
public SuperordinateDomainServerUpdateProhibitedException() {
|
||||
super("Superordinate domain for this hostname has serverUpdateProhibited status");
|
||||
}
|
||||
}
|
||||
|
||||
/** Host names are limited to 253 characters. */
|
||||
static class HostNameTooLongException extends ParameterValueRangeErrorException {
|
||||
public HostNameTooLongException() {
|
||||
|
||||
@@ -28,6 +28,7 @@ import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
|
||||
import static google.registry.flows.host.HostFlowUtils.lookupSuperordinateDomain;
|
||||
import static google.registry.flows.host.HostFlowUtils.validateHostName;
|
||||
import static google.registry.flows.host.HostFlowUtils.verifySuperordinateDomainNotInPendingDelete;
|
||||
import static google.registry.flows.host.HostFlowUtils.verifySuperordinateDomainNotServerUpdateProhibited;
|
||||
import static google.registry.flows.host.HostFlowUtils.verifySuperordinateDomainOwnership;
|
||||
import static google.registry.model.reporting.HistoryEntry.Type.HOST_UPDATE;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
@@ -98,6 +99,7 @@ import java.util.Optional;
|
||||
* @error {@link HostFlowUtils.InvalidHostNameException}
|
||||
* @error {@link HostFlowUtils.SuperordinateDomainDoesNotExistException}
|
||||
* @error {@link HostFlowUtils.SuperordinateDomainInPendingDeleteException}
|
||||
* @error {@link HostFlowUtils.SuperordinateDomainServerUpdateProhibitedException}
|
||||
* @error {@link CannotAddIpToExternalHostException}
|
||||
* @error {@link CannotRemoveSubordinateHostLastIpException}
|
||||
* @error {@link CannotRenameExternalHostException}
|
||||
@@ -152,7 +154,12 @@ public final class HostUpdateFlow implements MutatingFlow {
|
||||
verifySuperordinateDomainNotInPendingDelete(newSuperordinateDomain.orElse(null));
|
||||
EppResource owningResource = firstNonNull(oldSuperordinateDomain, existingHost);
|
||||
verifyUpdateAllowed(
|
||||
command, existingHost, newSuperordinateDomain.orElse(null), owningResource, isHostRename);
|
||||
command,
|
||||
existingHost,
|
||||
oldSuperordinateDomain,
|
||||
newSuperordinateDomain.orElse(null),
|
||||
owningResource,
|
||||
isHostRename);
|
||||
if (isHostRename && ForeignKeyUtils.loadKey(Host.class, newHostName, now).isPresent()) {
|
||||
throw new HostAlreadyExistsException(newHostName);
|
||||
}
|
||||
@@ -211,30 +218,38 @@ public final class HostUpdateFlow implements MutatingFlow {
|
||||
private void verifyUpdateAllowed(
|
||||
Update command,
|
||||
Host existingHost,
|
||||
Domain oldSuperordinateDomain,
|
||||
Domain newSuperordinateDomain,
|
||||
EppResource owningResource,
|
||||
boolean isHostRename)
|
||||
throws EppException {
|
||||
if (!isSuperuser) {
|
||||
// Verify that the host belongs to this registrar, either directly or because it is currently
|
||||
// subordinate to a domain owned by this registrar.
|
||||
verifyResourceOwnership(registrarId, owningResource);
|
||||
if (isHostRename && !existingHost.isSubordinate()) {
|
||||
throw new CannotRenameExternalHostException();
|
||||
}
|
||||
// Verify that the new superordinate domain belongs to this registrar.
|
||||
verifySuperordinateDomainOwnership(registrarId, newSuperordinateDomain);
|
||||
ImmutableSet<StatusValue> statusesToAdd = command.getInnerAdd().getStatusValues();
|
||||
ImmutableSet<StatusValue> statusesToRemove = command.getInnerRemove().getStatusValues();
|
||||
// If the resource is marked with clientUpdateProhibited, and this update does not clear that
|
||||
// status, then the update must be disallowed.
|
||||
if (existingHost.getStatusValues().contains(StatusValue.CLIENT_UPDATE_PROHIBITED)
|
||||
&& !statusesToRemove.contains(StatusValue.CLIENT_UPDATE_PROHIBITED)) {
|
||||
throw new ResourceHasClientUpdateProhibitedException();
|
||||
}
|
||||
verifyAllStatusesAreClientSettable(union(statusesToAdd, statusesToRemove));
|
||||
}
|
||||
verifyNoDisallowedStatuses(existingHost, DISALLOWED_STATUSES);
|
||||
if (isSuperuser) {
|
||||
return;
|
||||
}
|
||||
// Verify that the host belongs to this registrar, either directly or because it is currently
|
||||
// subordinate to a domain owned by this registrar.
|
||||
verifyResourceOwnership(registrarId, owningResource);
|
||||
if (isHostRename && !existingHost.isSubordinate()) {
|
||||
throw new CannotRenameExternalHostException();
|
||||
}
|
||||
// Verify that the new superordinate domain belongs to this registrar.
|
||||
verifySuperordinateDomainOwnership(registrarId, newSuperordinateDomain);
|
||||
// Subordinate hosts inherit the registry-lock protection of their superordinate domain: if
|
||||
// either the current or the target superordinate domain carries SERVER_UPDATE_PROHIBITED,
|
||||
// non-superuser updates (including glue IP changes and renames) must be rejected so that a
|
||||
// compromised sponsoring registrar cannot hijack DNS for a registry-locked domain.
|
||||
verifySuperordinateDomainNotServerUpdateProhibited(oldSuperordinateDomain);
|
||||
verifySuperordinateDomainNotServerUpdateProhibited(newSuperordinateDomain);
|
||||
ImmutableSet<StatusValue> statusesToAdd = command.getInnerAdd().getStatusValues();
|
||||
ImmutableSet<StatusValue> statusesToRemove = command.getInnerRemove().getStatusValues();
|
||||
// If the resource is marked with clientUpdateProhibited, and this update does not clear that
|
||||
// status, then the update must be disallowed.
|
||||
if (existingHost.getStatusValues().contains(StatusValue.CLIENT_UPDATE_PROHIBITED)
|
||||
&& !statusesToRemove.contains(StatusValue.CLIENT_UPDATE_PROHIBITED)) {
|
||||
throw new ResourceHasClientUpdateProhibitedException();
|
||||
}
|
||||
verifyAllStatusesAreClientSettable(union(statusesToAdd, statusesToRemove));
|
||||
}
|
||||
|
||||
private static void verifyHasIpsIffIsExternal(Update command, Host existingHost, Host newHost)
|
||||
|
||||
@@ -197,8 +197,7 @@ public final class OteAccountBuilder {
|
||||
registrars.stream()
|
||||
.collect(
|
||||
toImmutableMap(
|
||||
Registrar::getRegistrarId,
|
||||
registrar -> RegistrarRole.ACCOUNT_MANAGER)))
|
||||
Registrar::getRegistrarId, _ -> RegistrarRole.ACCOUNT_MANAGER)))
|
||||
.build())
|
||||
.build());
|
||||
return this;
|
||||
@@ -265,10 +264,18 @@ public final class OteAccountBuilder {
|
||||
|
||||
/** Grants the users permission to pass IAP. */
|
||||
public void grantIapPermission(
|
||||
Optional<String> groupEmailAddress, CloudTasksUtils cloudTasksUtils, IamClient iamClient) {
|
||||
Optional<String> groupEmailAddress,
|
||||
Optional<String> consoleIapServiceId,
|
||||
CloudTasksUtils cloudTasksUtils,
|
||||
IamClient iamClient) {
|
||||
for (User user : users) {
|
||||
User.grantIapPermission(
|
||||
user.getEmailAddress(), groupEmailAddress, cloudTasksUtils, null, iamClient);
|
||||
user.getEmailAddress(),
|
||||
groupEmailAddress,
|
||||
consoleIapServiceId,
|
||||
cloudTasksUtils,
|
||||
null,
|
||||
iamClient);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -86,6 +86,7 @@ public class User extends UpdateAutoTimestampEntity implements Buildable {
|
||||
public static void grantIapPermission(
|
||||
String emailAddress,
|
||||
Optional<String> groupEmailAddress,
|
||||
Optional<String> consoleIapServiceId,
|
||||
@Nullable CloudTasksUtils cloudTasksUtils,
|
||||
@Nullable ServiceConnection connection,
|
||||
IamClient iamClient) {
|
||||
@@ -93,14 +94,14 @@ public class User extends UpdateAutoTimestampEntity implements Buildable {
|
||||
return;
|
||||
}
|
||||
checkArgument(
|
||||
cloudTasksUtils != null || connection != null,
|
||||
"At least one of cloudTasksUtils or connection must be set");
|
||||
cloudTasksUtils == null ^ connection == null,
|
||||
"Precisely one of cloudTasksUtils or connection can be set");
|
||||
checkArgument(
|
||||
cloudTasksUtils == null || connection == null,
|
||||
"Only one of cloudTasksUtils or connection can be set");
|
||||
groupEmailAddress.isPresent() || consoleIapServiceId.isPresent(),
|
||||
"At least one of groupEmailAddress or consoleIapServiceId must be present");
|
||||
if (groupEmailAddress.isEmpty()) {
|
||||
logger.atInfo().log("Granting IAP role to user %s", emailAddress);
|
||||
iamClient.addBinding(emailAddress, IAP_SECURED_WEB_APP_USER_ROLE);
|
||||
iamClient.addBinding(emailAddress, IAP_SECURED_WEB_APP_USER_ROLE, consoleIapServiceId.get());
|
||||
} else {
|
||||
logger.atInfo().log("Adding %s to group %s", emailAddress, groupEmailAddress.get());
|
||||
if (cloudTasksUtils != null) {
|
||||
@@ -129,11 +130,8 @@ public class User extends UpdateAutoTimestampEntity implements Buildable {
|
||||
return;
|
||||
}
|
||||
checkArgument(
|
||||
cloudTasksUtils != null || connection != null,
|
||||
"At least one of cloudTasksUtils or connection must be set");
|
||||
checkArgument(
|
||||
cloudTasksUtils == null || connection == null,
|
||||
"Only one of cloudTasksUtils or connection can be set");
|
||||
cloudTasksUtils == null ^ connection == null,
|
||||
"Precisely one of cloudTasksUtils or connection can be set");
|
||||
if (groupEmailAddress.isEmpty()) {
|
||||
logger.atInfo().log("Removing IAP role from user %s", emailAddress);
|
||||
iamClient.removeBinding(emailAddress, IAP_SECURED_WEB_APP_USER_ROLE);
|
||||
|
||||
@@ -49,6 +49,10 @@ import java.util.stream.Stream;
|
||||
* also matches the "addrType" type from <a
|
||||
* href="http://tools.ietf.org/html/draft-lozano-tmch-smd">Mark and Signed Mark Objects Mapping</a>.
|
||||
*
|
||||
* <p>The lengths of the fields are limited to match the constraints defined in XSD schemas like
|
||||
* {@code rde-registrar.xsd}. Specifically, {@code zip} is limited to 16 characters and {@code
|
||||
* city}, {@code state}, and each {@code street} line are limited to 255 characters.
|
||||
*
|
||||
* @see google.registry.model.mark.MarkAddress
|
||||
* @see google.registry.model.registrar.RegistrarAddress
|
||||
*/
|
||||
@@ -169,11 +173,20 @@ public class Address extends ImmutableObject
|
||||
street == null || (!street.isEmpty() && street.size() <= 3),
|
||||
"Street address must have [1-3] lines: %s",
|
||||
street);
|
||||
//noinspection ConstantConditions
|
||||
if (street != null) {
|
||||
checkArgument(
|
||||
street.stream().noneMatch(String::isEmpty),
|
||||
"Street address cannot contain empty string: %s",
|
||||
street);
|
||||
checkArgument(
|
||||
street.stream().allMatch(s -> s.length() <= 255),
|
||||
"Street address lines cannot be longer than 255 characters");
|
||||
}
|
||||
checkArgument(
|
||||
street == null || street.stream().noneMatch(String::isEmpty),
|
||||
"Street address cannot contain empty string: %s",
|
||||
street);
|
||||
city == null || city.length() <= 255, "City cannot be longer than 255 characters");
|
||||
checkArgument(
|
||||
state == null || state.length() <= 255, "State cannot be longer than 255 characters");
|
||||
checkArgument(zip == null || zip.length() <= 16, "Zip cannot be longer than 16 characters");
|
||||
checkArgument(
|
||||
countryCode == null || countryCode.length() == 2,
|
||||
"Country code should be a 2 character string");
|
||||
@@ -196,9 +209,9 @@ public class Address extends ImmutableObject
|
||||
|
||||
public Builder<T> setStreet(ImmutableList<String> street) {
|
||||
getInstance().street = street;
|
||||
getInstance().streetLine1 = street.get(0);
|
||||
getInstance().streetLine2 = street.size() >= 2 ? street.get(1) : null;
|
||||
getInstance().streetLine3 = street.size() == 3 ? street.get(2) : null;
|
||||
getInstance().streetLine1 = (street != null && street.size() >= 1) ? street.get(0) : null;
|
||||
getInstance().streetLine2 = (street != null && street.size() >= 2) ? street.get(1) : null;
|
||||
getInstance().streetLine3 = (street != null && street.size() == 3) ? street.get(2) : null;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -631,7 +631,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link RegistrarPoc} that is the RDAP abuse contact for this registrar, or empty if
|
||||
* Returns a {@link RegistrarPoc} that is the RDAP abuse contact for this registrar, or empty if
|
||||
* one does not exist.
|
||||
*/
|
||||
public Optional<RegistrarPoc> getRdapAbuseContact() {
|
||||
|
||||
@@ -88,7 +88,8 @@ public class BulkDomainTransferCommand extends ConfirmingCommand implements Comm
|
||||
|
||||
@Parameter(
|
||||
names = {"--registrar_request"},
|
||||
description = "Whether the change was requested by a registrar.")
|
||||
description = "Whether the change was requested by a registrar.",
|
||||
arity = 1)
|
||||
private boolean requestedByRegistrar = false;
|
||||
|
||||
@Parameter(
|
||||
|
||||
@@ -38,6 +38,10 @@ public class CreateUserCommand extends CreateOrUpdateUserCommand implements Comm
|
||||
@Config("gSuiteConsoleUserGroupEmailAddress")
|
||||
Optional<String> maybeGroupEmailAddress;
|
||||
|
||||
@Inject
|
||||
@Config("consoleIapServiceId")
|
||||
Optional<String> consoleIapServiceId;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
User getExistingUser(String email) {
|
||||
@@ -49,7 +53,8 @@ public class CreateUserCommand extends CreateOrUpdateUserCommand implements Comm
|
||||
@Override
|
||||
protected String execute() throws Exception {
|
||||
String ret = super.execute();
|
||||
grantIapPermission(email, maybeGroupEmailAddress, null, connection, iamClient);
|
||||
grantIapPermission(
|
||||
email, maybeGroupEmailAddress, consoleIapServiceId, null, connection, iamClient);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,8 @@ final class DeleteDomainCommand extends MutatingEppToolCommand {
|
||||
|
||||
@Parameter(
|
||||
names = {"--registrar_request"},
|
||||
description = "Whether the change was requested by a registrar.")
|
||||
description = "Whether the change was requested by a registrar.",
|
||||
arity = 1)
|
||||
private boolean requestedByRegistrar = false;
|
||||
|
||||
@Override
|
||||
|
||||
@@ -20,6 +20,8 @@ import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainAuthInfo;
|
||||
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
|
||||
import google.registry.persistence.transaction.QueryComposer.Comparator;
|
||||
import google.registry.util.DomainNameUtils;
|
||||
import java.util.List;
|
||||
@@ -32,6 +34,12 @@ final class GetDomainCommand extends GetEppResourceCommand {
|
||||
@Parameter(names = "--show_deleted", description = "Include deleted domains in the print out")
|
||||
private boolean showDeleted = false;
|
||||
|
||||
@Parameter(
|
||||
names = "--show_authcode",
|
||||
description = "Include domain authentication code in output",
|
||||
arity = 1)
|
||||
private boolean showAuthcode = false;
|
||||
|
||||
@Parameter(
|
||||
description = "Fully qualified domain name(s)",
|
||||
required = true)
|
||||
@@ -51,14 +59,26 @@ final class GetDomainCommand extends GetEppResourceCommand {
|
||||
.stream()
|
||||
.forEach(
|
||||
d -> {
|
||||
printResource("Domain", canonicalDomain, Optional.of(d));
|
||||
printResource(
|
||||
"Domain", canonicalDomain, Optional.of(censorAuthcode(d)));
|
||||
}));
|
||||
} else {
|
||||
printResource(
|
||||
"Domain",
|
||||
canonicalDomain,
|
||||
ForeignKeyUtils.loadResource(Domain.class, canonicalDomain, readTimestamp));
|
||||
ForeignKeyUtils.loadResource(Domain.class, canonicalDomain, readTimestamp)
|
||||
.map(this::censorAuthcode));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Domain censorAuthcode(Domain domain) {
|
||||
if (showAuthcode || domain.getAuthInfo() == null) {
|
||||
return domain;
|
||||
}
|
||||
return domain
|
||||
.asBuilder()
|
||||
.setAuthInfo(DomainAuthInfo.create(PasswordAuth.create("[REDACTED]")))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ package google.registry.tools;
|
||||
|
||||
import com.google.api.services.cloudresourcemanager.CloudResourceManager;
|
||||
import com.google.api.services.cloudresourcemanager.model.Binding;
|
||||
import com.google.api.services.cloudresourcemanager.model.Expr;
|
||||
import com.google.api.services.cloudresourcemanager.model.GetIamPolicyRequest;
|
||||
import com.google.api.services.cloudresourcemanager.model.Policy;
|
||||
import com.google.api.services.cloudresourcemanager.model.SetIamPolicyRequest;
|
||||
@@ -31,7 +32,10 @@ import java.util.Optional;
|
||||
|
||||
@Singleton
|
||||
public class IamClient {
|
||||
|
||||
private static final String MEMBER_FORMAT = "user:%s";
|
||||
private static final String IAP_ACCESS_EXPRESSION_FORMAT =
|
||||
"resource.name == 'projects/%s/iap_web/compute/services/%s'";
|
||||
|
||||
private final CloudResourceManager resourceManager;
|
||||
private final String projectId;
|
||||
@@ -60,7 +64,7 @@ public class IamClient {
|
||||
*
|
||||
* <p>No-op if the role is already bound to the account.
|
||||
*/
|
||||
public void addBinding(String account, String role) {
|
||||
public void addBinding(String account, String role, String consoleIapServiceId) {
|
||||
String member = String.format(MEMBER_FORMAT, account);
|
||||
Policy policy = getPolicy();
|
||||
Binding binding =
|
||||
@@ -69,7 +73,21 @@ public class IamClient {
|
||||
.findFirst()
|
||||
.orElseGet(
|
||||
() -> {
|
||||
Binding newBinding = new Binding().setRole(role).setMembers(new ArrayList<>());
|
||||
Binding newBinding =
|
||||
new Binding()
|
||||
.setRole(role)
|
||||
.setMembers(new ArrayList<>())
|
||||
.setCondition(
|
||||
new Expr()
|
||||
.setTitle("Registrar Console IAP access")
|
||||
.setDescription(
|
||||
"Restrict IAP access only to the Registrar Console HTTP(s)"
|
||||
+ " load balancer")
|
||||
.setExpression(
|
||||
String.format(
|
||||
IAP_ACCESS_EXPRESSION_FORMAT,
|
||||
projectId,
|
||||
consoleIapServiceId)));
|
||||
policy.getBindings().add(newBinding);
|
||||
return newBinding;
|
||||
});
|
||||
|
||||
@@ -86,6 +86,10 @@ final class SetupOteCommand extends ConfirmingCommand {
|
||||
@Config("gSuiteConsoleUserGroupEmailAddress")
|
||||
Optional<String> maybeGroupEmailAddress;
|
||||
|
||||
@Inject
|
||||
@Config("consoleIapServiceId")
|
||||
Optional<String> consoleIapServiceId;
|
||||
|
||||
OteAccountBuilder oteAccountBuilder;
|
||||
String password;
|
||||
|
||||
@@ -138,7 +142,8 @@ you sure you didn't mean to run this against sandbox (e.g. "-e SANDBOX")?\
|
||||
@Override
|
||||
public String execute() {
|
||||
ImmutableMap<String, String> clientIdToTld = oteAccountBuilder.buildAndPersist();
|
||||
oteAccountBuilder.grantIapPermission(maybeGroupEmailAddress, cloudTasksUtils, iamClient);
|
||||
oteAccountBuilder.grantIapPermission(
|
||||
maybeGroupEmailAddress, consoleIapServiceId, cloudTasksUtils, iamClient);
|
||||
|
||||
StringBuilder output = new StringBuilder();
|
||||
|
||||
|
||||
@@ -127,6 +127,14 @@ public abstract class ConsoleApiAction implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
protected static void checkGlobalPermission(User user, ConsolePermission permission) {
|
||||
if (!user.getUserRoles().hasGlobalPermission(permission)) {
|
||||
throw new ConsolePermissionForbiddenException(
|
||||
String.format(
|
||||
"User %s does not have global permission %s", user.getEmailAddress(), permission));
|
||||
}
|
||||
}
|
||||
|
||||
protected void postHandler(User user) {
|
||||
throw new UnsupportedOperationException("Console API POST handler not implemented");
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ public class ConsoleOteAction extends ConsoleApiAction {
|
||||
private final StringGenerator passwordGenerator;
|
||||
private final Optional<OteCreateData> oteCreateData;
|
||||
private final Optional<String> maybeGroupEmailAddress;
|
||||
private final Optional<String> consoleIapServiceId;
|
||||
private final IamClient iamClient;
|
||||
private final String registrarId;
|
||||
|
||||
@@ -71,12 +72,14 @@ public class ConsoleOteAction extends ConsoleApiAction {
|
||||
IamClient iamClient,
|
||||
@Parameter("registrarId") String registrarId, // Get request param
|
||||
@Config("gSuiteConsoleUserGroupEmailAddress") Optional<String> maybeGroupEmailAddress,
|
||||
@Config("consoleIapServiceId") Optional<String> consoleIapServiceId,
|
||||
@Named("base58StringGenerator") StringGenerator passwordGenerator,
|
||||
@Parameter("oteCreateData") Optional<OteCreateData> oteCreateData) {
|
||||
super(consoleApiParams);
|
||||
this.passwordGenerator = passwordGenerator;
|
||||
this.oteCreateData = oteCreateData;
|
||||
this.maybeGroupEmailAddress = maybeGroupEmailAddress;
|
||||
this.consoleIapServiceId = consoleIapServiceId;
|
||||
this.iamClient = iamClient;
|
||||
this.registrarId = registrarId;
|
||||
}
|
||||
@@ -105,7 +108,8 @@ public class ConsoleOteAction extends ConsoleApiAction {
|
||||
.setPassword(password);
|
||||
|
||||
ImmutableMap<String, String> registrarIdToTld = oteAccountBuilder.buildAndPersist();
|
||||
oteAccountBuilder.grantIapPermission(maybeGroupEmailAddress, cloudTasksUtils, iamClient);
|
||||
oteAccountBuilder.grantIapPermission(
|
||||
maybeGroupEmailAddress, consoleIapServiceId, cloudTasksUtils, iamClient);
|
||||
consoleApiParams.response().setStatus(SC_OK);
|
||||
consoleApiParams
|
||||
.response()
|
||||
|
||||
+31
-26
@@ -15,6 +15,7 @@
|
||||
package google.registry.ui.server.console;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.request.Action.Method.POST;
|
||||
import static google.registry.util.DateTimeUtils.START_INSTANT;
|
||||
@@ -38,7 +39,6 @@ import google.registry.util.RegistryEnvironment;
|
||||
import jakarta.inject.Inject;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Action(
|
||||
service = Service.CONSOLE,
|
||||
@@ -69,22 +69,23 @@ public class ConsoleUpdateRegistrarAction extends ConsoleApiAction {
|
||||
|
||||
tm().transact(
|
||||
() -> {
|
||||
Optional<Registrar> existingRegistrar =
|
||||
Registrar.loadByRegistrarId(registrarParam.getRegistrarId());
|
||||
checkArgument(
|
||||
!existingRegistrar.isEmpty(),
|
||||
"Registrar with registrarId %s doesn't exists",
|
||||
registrarParam.getRegistrarId());
|
||||
Registrar existingRegistrar =
|
||||
Registrar.loadByRegistrarId(registrarParam.getRegistrarId())
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new IllegalArgumentException(
|
||||
String.format(
|
||||
"Registrar %s does not exist",
|
||||
registrarParam.getRegistrarId())));
|
||||
|
||||
// Only allow modifying allowed TLDs if we're in a non-PRODUCTION environment, if the
|
||||
// registrar is not REAL, or the registrar has a RDAP abuse contact set.
|
||||
if (!registrarParam.getAllowedTlds().isEmpty()) {
|
||||
boolean isRealRegistrar =
|
||||
Registrar.Type.REAL.equals(existingRegistrar.get().getType());
|
||||
boolean isRealRegistrar = Registrar.Type.REAL.equals(existingRegistrar.getType());
|
||||
if (RegistryEnvironment.PRODUCTION.equals(RegistryEnvironment.get())
|
||||
&& isRealRegistrar) {
|
||||
checkArgumentPresent(
|
||||
existingRegistrar.get().getRdapAbuseContact(),
|
||||
existingRegistrar.getRdapAbuseContact(),
|
||||
"Cannot modify allowed TLDs if there is no RDAP abuse contact set. Please"
|
||||
+ " use the \"nomulus registrar_contact\" command on this registrar to"
|
||||
+ " set a RDAP abuse contact.");
|
||||
@@ -103,20 +104,27 @@ public class ConsoleUpdateRegistrarAction extends ConsoleApiAction {
|
||||
|
||||
var updatedRegistrarBuilder =
|
||||
existingRegistrar
|
||||
.get()
|
||||
.asBuilder()
|
||||
.setLastPocVerificationDate(newLastPocVerificationDate);
|
||||
|
||||
if (user.getUserRoles()
|
||||
.hasGlobalPermission(ConsolePermission.EDIT_REGISTRAR_DETAILS)) {
|
||||
updatedRegistrarBuilder =
|
||||
updatedRegistrarBuilder
|
||||
.setAllowedTlds(
|
||||
registrarParam.getAllowedTlds().stream()
|
||||
.map(DomainNameUtils::canonicalizeHostname)
|
||||
.collect(Collectors.toSet()))
|
||||
.setRegistryLockAllowed(registrarParam.isRegistryLockAllowed())
|
||||
.setLastPocVerificationDate(newLastPocVerificationDate);
|
||||
if (!registrarParam.getAllowedTlds().equals(existingRegistrar.getAllowedTlds())) {
|
||||
// The global permission EDIT_REGISTRAR_DETAILS signifies a support agent who *does*
|
||||
// have the ability to edit allowed TLDs. See support docs 2.19: "Enabling TLD
|
||||
// Access in Production"
|
||||
checkGlobalPermission(user, ConsolePermission.EDIT_REGISTRAR_DETAILS);
|
||||
updatedRegistrarBuilder.setAllowedTlds(
|
||||
registrarParam.getAllowedTlds().stream()
|
||||
.map(DomainNameUtils::canonicalizeHostname)
|
||||
.collect(toImmutableSet()));
|
||||
}
|
||||
|
||||
if (registrarParam.isRegistryLockAllowed()
|
||||
!= existingRegistrar.isRegistryLockAllowed()) {
|
||||
// Enabling registry lock requires a support lead or FTE, which maps to
|
||||
// MANAGE_REGISTRARS. See support docs 2.33: "Registry Lock Onboarding Process"
|
||||
checkGlobalPermission(user, ConsolePermission.MANAGE_REGISTRARS);
|
||||
updatedRegistrarBuilder.setRegistryLockAllowed(
|
||||
registrarParam.isRegistryLockAllowed());
|
||||
}
|
||||
|
||||
var updatedRegistrar = updatedRegistrarBuilder.build();
|
||||
@@ -126,14 +134,11 @@ public class ConsoleUpdateRegistrarAction extends ConsoleApiAction {
|
||||
.setType(ConsoleUpdateHistory.Type.REGISTRAR_UPDATE)
|
||||
.setDescription(updatedRegistrar.getRegistrarId()));
|
||||
|
||||
logConsoleChangesIfNecessary(updatedRegistrar, existingRegistrar.get());
|
||||
logConsoleChangesIfNecessary(updatedRegistrar, existingRegistrar);
|
||||
|
||||
sendExternalUpdatesIfNecessary(
|
||||
EmailInfo.create(
|
||||
existingRegistrar.get(),
|
||||
updatedRegistrar,
|
||||
ImmutableSet.of(),
|
||||
ImmutableSet.of()));
|
||||
existingRegistrar, updatedRegistrar, ImmutableSet.of(), ImmutableSet.of()));
|
||||
});
|
||||
|
||||
consoleApiParams.response().setStatus(SC_OK);
|
||||
|
||||
@@ -79,6 +79,7 @@ public class ConsoleUsersAction extends ConsoleApiAction {
|
||||
private final StringGenerator passwordGenerator;
|
||||
private final Optional<UserData> userData;
|
||||
private final Optional<String> maybeGroupEmailAddress;
|
||||
private final Optional<String> consoleIapServiceId;
|
||||
private final IamClient iamClient;
|
||||
private final String gSuiteDomainName;
|
||||
|
||||
@@ -89,6 +90,7 @@ public class ConsoleUsersAction extends ConsoleApiAction {
|
||||
IamClient iamClient,
|
||||
@Config("gSuiteDomainName") String gSuiteDomainName,
|
||||
@Config("gSuiteConsoleUserGroupEmailAddress") Optional<String> maybeGroupEmailAddress,
|
||||
@Config("consoleIapServiceId") Optional<String> consoleIapServiceId,
|
||||
@Named("base58StringGenerator") StringGenerator passwordGenerator,
|
||||
@Parameter("userData") Optional<UserData> userData,
|
||||
@Parameter("registrarId") String registrarId) {
|
||||
@@ -98,6 +100,7 @@ public class ConsoleUsersAction extends ConsoleApiAction {
|
||||
this.passwordGenerator = passwordGenerator;
|
||||
this.userData = userData;
|
||||
this.maybeGroupEmailAddress = maybeGroupEmailAddress;
|
||||
this.consoleIapServiceId = consoleIapServiceId;
|
||||
this.iamClient = iamClient;
|
||||
this.gSuiteDomainName = gSuiteDomainName;
|
||||
}
|
||||
@@ -256,7 +259,8 @@ public class ConsoleUsersAction extends ConsoleApiAction {
|
||||
|
||||
User.Builder builder = new User.Builder().setUserRoles(userRoles).setEmailAddress(newEmail);
|
||||
tm().put(builder.build());
|
||||
User.grantIapPermission(newEmail, maybeGroupEmailAddress, cloudTasksUtils, null, iamClient);
|
||||
User.grantIapPermission(
|
||||
newEmail, maybeGroupEmailAddress, consoleIapServiceId, cloudTasksUtils, null, iamClient);
|
||||
sendConfirmationEmail(registrarId, newEmail, "Created user");
|
||||
consoleApiParams.response().setStatus(SC_CREATED);
|
||||
consoleApiParams
|
||||
|
||||
@@ -228,17 +228,23 @@ public class ContactAction extends ConsoleApiAction {
|
||||
|
||||
enforcePrimaryContactRestrictions(oldContactsByType, newContactsByType);
|
||||
ensurePhoneNumberNotRemovedForContactTypes(oldContactsByType, newContactsByType, Type.TECH);
|
||||
Optional<RegistrarPoc> domainRdapAbuseContact =
|
||||
getDomainRdapVisibleAbuseContact(updatedContacts);
|
||||
// If the new set has a domain RDAP abuse contact, it must have a phone number.
|
||||
if (domainRdapAbuseContact.isPresent()
|
||||
&& domainRdapAbuseContact.get().getPhoneNumber() == null) {
|
||||
throw new ContactRequirementException(
|
||||
"The abuse contact visible in domain RDAP query must have a phone number");
|
||||
}
|
||||
|
||||
ImmutableSet<RegistrarPoc> abusePocs =
|
||||
updatedContacts.stream()
|
||||
.filter(RegistrarPoc::getVisibleInDomainRdapAsAbuse)
|
||||
.collect(toImmutableSet());
|
||||
|
||||
// All abuse POCs must have a phone number attached
|
||||
abusePocs.forEach(
|
||||
poc -> {
|
||||
if (poc.getPhoneNumber() == null) {
|
||||
throw new ContactRequirementException(
|
||||
"The abuse contact visible in domain RDAP query must have a phone number");
|
||||
}
|
||||
});
|
||||
// If there was a domain RDAP abuse contact in the old set, the new set must have one.
|
||||
if (getDomainRdapVisibleAbuseContact(existingContacts).isPresent()
|
||||
&& domainRdapAbuseContact.isEmpty()) {
|
||||
if (existingContacts.stream().anyMatch(RegistrarPoc::getVisibleInDomainRdapAsAbuse)
|
||||
&& abusePocs.isEmpty()) {
|
||||
throw new ContactRequirementException(
|
||||
"An abuse contact visible in domain RDAP query must be designated");
|
||||
}
|
||||
@@ -255,26 +261,14 @@ public class ContactAction extends ConsoleApiAction {
|
||||
newContactsByType.get(Type.ADMIN).stream()
|
||||
.map(RegistrarPoc::getEmailAddress)
|
||||
.collect(toImmutableSet());
|
||||
if (!newAdminEmails.containsAll(oldAdminEmails)) {
|
||||
throw new ContactRequirementException(
|
||||
"Cannot remove or change the email address of primary contacts");
|
||||
// ADMIN (primary) PoC emails are used for EPP password resets and potentially other
|
||||
// security-sensitive tasks. A user with only EDIT_REGISTRAR_DETAILS should not be able to
|
||||
// change this.
|
||||
if (!newAdminEmails.equals(oldAdminEmails)) {
|
||||
throw new ContactRequirementException("Cannot alter the set of primary contacts");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the registrar contact whose phone number and email address is visible in domain RDAP
|
||||
* query as abuse contact (if any).
|
||||
*
|
||||
* <p>Frontend processing ensures that only one contact can be set as abuse contact in domain RDAP
|
||||
* record.
|
||||
*
|
||||
* <p>Therefore, it is possible to return inside the loop once one such contact is found.
|
||||
*/
|
||||
private static Optional<RegistrarPoc> getDomainRdapVisibleAbuseContact(
|
||||
Set<RegistrarPoc> contacts) {
|
||||
return contacts.stream().filter(RegistrarPoc::getVisibleInDomainRdapAsAbuse).findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that for each given registrar type, a phone number is present after update, if there was
|
||||
* one before.
|
||||
|
||||
@@ -51,6 +51,7 @@ import google.registry.flows.host.HostFlowUtils.InvalidHostNameException;
|
||||
import google.registry.flows.host.HostFlowUtils.IpAddressNotRoutableException;
|
||||
import google.registry.flows.host.HostFlowUtils.SuperordinateDomainDoesNotExistException;
|
||||
import google.registry.flows.host.HostFlowUtils.SuperordinateDomainInPendingDeleteException;
|
||||
import google.registry.flows.host.HostFlowUtils.SuperordinateDomainServerUpdateProhibitedException;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
@@ -413,6 +414,22 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, Host> {
|
||||
.marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_superordinateDomainIsServerUpdateProhibited() {
|
||||
createTld("tld");
|
||||
persistResource(
|
||||
DatabaseHelper.newDomain("example.tld")
|
||||
.asBuilder()
|
||||
.setStatusValues(ImmutableSet.of(StatusValue.SERVER_UPDATE_PROHIBITED))
|
||||
.build());
|
||||
setEppHostCreateInputWithIps("ns1.example.tld");
|
||||
SuperordinateDomainServerUpdateProhibitedException thrown =
|
||||
assertThrows(SuperordinateDomainServerUpdateProhibitedException.class, this::runFlow);
|
||||
assertThat(thrown)
|
||||
.hasMessageThat()
|
||||
.contains("Superordinate domain for this hostname has serverUpdateProhibited status");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIcannActivityReportField_getsLogged() throws Exception {
|
||||
runFlow();
|
||||
|
||||
@@ -42,6 +42,7 @@ import google.registry.flows.exceptions.ResourceToDeleteIsReferencedException;
|
||||
import google.registry.flows.host.HostFlowUtils.HostNameNotLowerCaseException;
|
||||
import google.registry.flows.host.HostFlowUtils.HostNameNotNormalizedException;
|
||||
import google.registry.flows.host.HostFlowUtils.HostNameNotPunyCodedException;
|
||||
import google.registry.flows.host.HostFlowUtils.SuperordinateDomainServerUpdateProhibitedException;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.GracePeriod;
|
||||
import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||
@@ -360,6 +361,25 @@ class HostDeleteFlowTest extends ResourceFlowTestCase<HostDeleteFlow, Host> {
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_superordinateDomainIsServerUpdateProhibited() {
|
||||
createTld("tld");
|
||||
Domain domain =
|
||||
persistResource(
|
||||
DatabaseHelper.newDomain("example.tld")
|
||||
.asBuilder()
|
||||
.setStatusValues(ImmutableSet.of(StatusValue.SERVER_UPDATE_PROHIBITED))
|
||||
.build());
|
||||
persistResource(
|
||||
newHost("ns1.example.tld").asBuilder().setSuperordinateDomain(domain.createVKey()).build());
|
||||
clock.advanceOneMilli();
|
||||
SuperordinateDomainServerUpdateProhibitedException thrown =
|
||||
assertThrows(SuperordinateDomainServerUpdateProhibitedException.class, this::runFlow);
|
||||
assertThat(thrown)
|
||||
.hasMessageThat()
|
||||
.contains("Superordinate domain for this hostname has serverUpdateProhibited status");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIcannActivityReportField_getsLogged() throws Exception {
|
||||
persistActiveHost("ns1.example.tld");
|
||||
|
||||
@@ -68,6 +68,7 @@ import google.registry.flows.host.HostFlowUtils.HostNameTooShallowException;
|
||||
import google.registry.flows.host.HostFlowUtils.IpAddressNotRoutableException;
|
||||
import google.registry.flows.host.HostFlowUtils.SuperordinateDomainDoesNotExistException;
|
||||
import google.registry.flows.host.HostFlowUtils.SuperordinateDomainInPendingDeleteException;
|
||||
import google.registry.flows.host.HostFlowUtils.SuperordinateDomainServerUpdateProhibitedException;
|
||||
import google.registry.flows.host.HostUpdateFlow.CannotAddIpToExternalHostException;
|
||||
import google.registry.flows.host.HostUpdateFlow.CannotRemoveSubordinateHostLastIpException;
|
||||
import google.registry.flows.host.HostUpdateFlow.CannotRenameExternalHostException;
|
||||
@@ -889,6 +890,30 @@ class HostUpdateFlowTest extends ResourceFlowTestCase<HostUpdateFlow, Host> {
|
||||
.contains("Superordinate domain for this hostname is in pending delete");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_superordinateDomainIsServerUpdateProhibited() throws Exception {
|
||||
setEppHostUpdateInput(
|
||||
"ns1.example.tld",
|
||||
"ns2.example.tld",
|
||||
"<host:addr ip=\"v4\">192.0.2.22</host:addr>",
|
||||
"<host:addr ip=\"v6\">1080:0:0:0:8:800:200C:417A</host:addr>");
|
||||
createTld("tld");
|
||||
Domain domain =
|
||||
persistResource(
|
||||
DatabaseHelper.newDomain("example.tld")
|
||||
.asBuilder()
|
||||
.setSubordinateHosts(ImmutableSet.of(oldHostName()))
|
||||
.setStatusValues(ImmutableSet.of(StatusValue.SERVER_UPDATE_PROHIBITED))
|
||||
.build());
|
||||
persistActiveSubordinateHost(oldHostName(), domain);
|
||||
clock.advanceOneMilli();
|
||||
SuperordinateDomainServerUpdateProhibitedException thrown =
|
||||
assertThrows(SuperordinateDomainServerUpdateProhibitedException.class, this::runFlow);
|
||||
assertThat(thrown)
|
||||
.hasMessageThat()
|
||||
.contains("Superordinate domain for this hostname has serverUpdateProhibited status");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_neverExisted() throws Exception {
|
||||
ResourceDoesNotExistException thrown =
|
||||
|
||||
@@ -116,6 +116,7 @@ public final class OteAccountBuilderTest {
|
||||
public static void verifyIapPermission(
|
||||
@Nullable String emailAddress,
|
||||
Optional<String> maybeGroupEmailAddress,
|
||||
Optional<String> consoleIapServiceId,
|
||||
CloudTasksHelper cloudTasksHelper,
|
||||
IamClient iamClient) {
|
||||
if (emailAddress == null) {
|
||||
@@ -125,7 +126,8 @@ public final class OteAccountBuilderTest {
|
||||
String groupEmailAddress = maybeGroupEmailAddress.orElse(null);
|
||||
if (groupEmailAddress == null) {
|
||||
cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
|
||||
verify(iamClient).addBinding(emailAddress, IAP_SECURED_WEB_APP_USER_ROLE);
|
||||
verify(iamClient)
|
||||
.addBinding(emailAddress, IAP_SECURED_WEB_APP_USER_ROLE, consoleIapServiceId.get());
|
||||
} else {
|
||||
cloudTasksHelper.assertTasksEnqueued(
|
||||
"console-user-group-update",
|
||||
@@ -146,9 +148,32 @@ public final class OteAccountBuilderTest {
|
||||
CloudTasksUtils cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
|
||||
OteAccountBuilder.forRegistrarId("myclientid")
|
||||
.addUser("email@example.com")
|
||||
.grantIapPermission(Optional.of("console@example.com"), cloudTasksUtils, iamClient);
|
||||
.grantIapPermission(
|
||||
Optional.of("console@example.com"),
|
||||
Optional.of("consoleIapServiceId"),
|
||||
cloudTasksUtils,
|
||||
iamClient);
|
||||
verifyIapPermission(
|
||||
"email@example.com", Optional.of("console@example.com"), cloudTasksHelper, iamClient);
|
||||
"email@example.com",
|
||||
Optional.of("console@example.com"),
|
||||
Optional.of("consoleIapServiceId"),
|
||||
cloudTasksHelper,
|
||||
iamClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdateUserGroup_worksWithoutIapServiceId() {
|
||||
CloudTasksUtils cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
|
||||
OteAccountBuilder.forRegistrarId("myclientid")
|
||||
.addUser("email@example.com")
|
||||
.grantIapPermission(
|
||||
Optional.of("console@example.com"), Optional.empty(), cloudTasksUtils, iamClient);
|
||||
verifyIapPermission(
|
||||
"email@example.com",
|
||||
Optional.of("console@example.com"),
|
||||
Optional.empty(),
|
||||
cloudTasksHelper,
|
||||
iamClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -156,8 +181,14 @@ public final class OteAccountBuilderTest {
|
||||
CloudTasksUtils cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
|
||||
OteAccountBuilder.forRegistrarId("myclientid")
|
||||
.addUser("email@example.com")
|
||||
.grantIapPermission(Optional.empty(), cloudTasksUtils, iamClient);
|
||||
verifyIapPermission("email@example.com", Optional.empty(), cloudTasksHelper, iamClient);
|
||||
.grantIapPermission(
|
||||
Optional.empty(), Optional.of("consoleIapServiceId"), cloudTasksUtils, iamClient);
|
||||
verifyIapPermission(
|
||||
"email@example.com",
|
||||
Optional.empty(),
|
||||
Optional.of("consoleIapServiceId"),
|
||||
cloudTasksHelper,
|
||||
iamClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -66,13 +66,21 @@ public class UserTest extends EntityTestCase {
|
||||
void testFailure_asyncAndSyncModeConflict() {
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> User.grantIapPermission("email@example.com", Optional.empty(), null, null, null));
|
||||
() ->
|
||||
User.grantIapPermission(
|
||||
"email@example.com",
|
||||
Optional.empty(),
|
||||
Optional.of("consoleIapServiceId"),
|
||||
null,
|
||||
null,
|
||||
null));
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() ->
|
||||
User.grantIapPermission(
|
||||
"email@example.com",
|
||||
Optional.empty(),
|
||||
Optional.of("consoleIapServiceId"),
|
||||
mock(CloudTasksUtils.class),
|
||||
mock(ServiceConnection.class),
|
||||
null));
|
||||
@@ -90,6 +98,24 @@ public class UserTest extends EntityTestCase {
|
||||
null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGrantIapPermission_neitherGroupNorIapServiceId_fails() {
|
||||
IllegalArgumentException e =
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() ->
|
||||
User.grantIapPermission(
|
||||
"email@example.com",
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
mock(CloudTasksUtils.class),
|
||||
null,
|
||||
mock(IamClient.class)));
|
||||
assertThat(e)
|
||||
.hasMessageThat()
|
||||
.isEqualTo("At least one of groupEmailAddress or consoleIapServiceId must be present");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_badInputs() {
|
||||
User.Builder builder = new User.Builder();
|
||||
@@ -156,13 +182,33 @@ public class UserTest extends EntityTestCase {
|
||||
|
||||
// Individual permission.
|
||||
User.grantIapPermission(
|
||||
"email@example.com", Optional.empty(), cloudTasksUtils, null, iamClient);
|
||||
"email@example.com",
|
||||
Optional.empty(),
|
||||
Optional.of("consoleIapServiceId"),
|
||||
cloudTasksUtils,
|
||||
null,
|
||||
iamClient);
|
||||
cloudTasksHelper.assertNoTasksEnqueued();
|
||||
verify(iamClient).addBinding("email@example.com", IAP_SECURED_WEB_APP_USER_ROLE);
|
||||
verify(iamClient)
|
||||
.addBinding("email@example.com", IAP_SECURED_WEB_APP_USER_ROLE, "consoleIapServiceId");
|
||||
|
||||
// Group membership.
|
||||
User.grantIapPermission(
|
||||
"email@example.com", Optional.of("console@example.com"), cloudTasksUtils, null, iamClient);
|
||||
"email@example.com",
|
||||
Optional.of("console@example.com"),
|
||||
Optional.empty(),
|
||||
cloudTasksUtils,
|
||||
null,
|
||||
iamClient);
|
||||
// Group membership takes precedence over individual membership
|
||||
User.grantIapPermission(
|
||||
"groupemail@example.com",
|
||||
Optional.of("console@example.com"),
|
||||
Optional.of("consoleIapServiceId"),
|
||||
cloudTasksUtils,
|
||||
null,
|
||||
iamClient);
|
||||
|
||||
cloudTasksHelper.assertTasksEnqueued(
|
||||
"console-user-group-update",
|
||||
new TaskMatcher()
|
||||
@@ -171,6 +217,13 @@ public class UserTest extends EntityTestCase {
|
||||
.path("/_dr/admin/updateUserGroup")
|
||||
.param("userEmailAddress", "email@example.com")
|
||||
.param("groupEmailAddress", "console@example.com")
|
||||
.param("groupUpdateMode", "ADD"),
|
||||
new TaskMatcher()
|
||||
.service("BACKEND")
|
||||
.method(HttpMethod.POST)
|
||||
.path("/_dr/admin/updateUserGroup")
|
||||
.param("userEmailAddress", "groupemail@example.com")
|
||||
.param("groupEmailAddress", "console@example.com")
|
||||
.param("groupUpdateMode", "ADD"));
|
||||
verifyNoMoreInteractions(iamClient);
|
||||
}
|
||||
@@ -181,13 +234,25 @@ public class UserTest extends EntityTestCase {
|
||||
IamClient iamClient = mock(IamClient.class);
|
||||
|
||||
// Individual permission.
|
||||
User.grantIapPermission("email@example.com", Optional.empty(), null, connection, iamClient);
|
||||
User.grantIapPermission(
|
||||
"email@example.com",
|
||||
Optional.empty(),
|
||||
Optional.of("consoleIapServiceId"),
|
||||
null,
|
||||
connection,
|
||||
iamClient);
|
||||
verifyNoInteractions(connection);
|
||||
verify(iamClient).addBinding("email@example.com", IAP_SECURED_WEB_APP_USER_ROLE);
|
||||
verify(iamClient)
|
||||
.addBinding("email@example.com", IAP_SECURED_WEB_APP_USER_ROLE, "consoleIapServiceId");
|
||||
|
||||
// Group membership.
|
||||
User.grantIapPermission(
|
||||
"email@example.com", Optional.of("console@example.com"), null, connection, iamClient);
|
||||
"email@example.com",
|
||||
Optional.of("console@example.com"),
|
||||
Optional.empty(),
|
||||
null,
|
||||
connection,
|
||||
iamClient);
|
||||
verify(connection)
|
||||
.sendPostRequest(
|
||||
UpdateUserGroupAction.PATH,
|
||||
|
||||
@@ -109,6 +109,32 @@ class AddressTest {
|
||||
assertThrows(IllegalArgumentException.class, () -> createAddress("line1", "", "line3"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_streetLineTooLong() {
|
||||
assertThrows(IllegalArgumentException.class, () -> createAddress("a".repeat(256)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_cityTooLong() {
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> createAddress("line1").asBuilder().setCity("a".repeat(256)).build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_stateTooLong() {
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> createAddress("line1").asBuilder().setState("a".repeat(256)).build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_zipTooLong() {
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> createAddress("line1").asBuilder().setZip("12345678901234567").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_pojoToAndFromXml() throws Exception {
|
||||
JAXBContext jaxbContext = JAXBContext.newInstance(TestEntity.class);
|
||||
|
||||
@@ -165,4 +165,32 @@ public class BulkDomainTransferCommandTest extends CommandTestCase<BulkDomainTra
|
||||
.isEqualTo(
|
||||
"Must specify exactly one input method, either --domains or --domain_names_file");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_registrarRequestExplicit() throws Exception {
|
||||
runCommandForced(
|
||||
"--gaining_registrar_id",
|
||||
"NewRegistrar",
|
||||
"--losing_registrar_id",
|
||||
"TheRegistrar",
|
||||
"--reason",
|
||||
"someReason",
|
||||
"--domains",
|
||||
"foo.tld,bar.tld",
|
||||
"--registrar_request=false");
|
||||
verify(connection)
|
||||
.sendPostRequest(
|
||||
"/_dr/task/bulkDomainTransfer",
|
||||
ImmutableMap.of(
|
||||
"gainingRegistrarId",
|
||||
"NewRegistrar",
|
||||
"losingRegistrarId",
|
||||
"TheRegistrar",
|
||||
"requestedByRegistrar",
|
||||
false,
|
||||
"reason",
|
||||
"someReason"),
|
||||
MediaType.PLAIN_TEXT_UTF_8,
|
||||
"[\"foo.tld\",\"bar.tld\"]".getBytes(UTF_8));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
|
||||
void beforeEach() {
|
||||
command.iamClient = iamClient;
|
||||
command.maybeGroupEmailAddress = Optional.empty();
|
||||
command.consoleIapServiceId = Optional.of("consoleIapServiceId");
|
||||
command.setConnection(connection);
|
||||
}
|
||||
|
||||
@@ -56,7 +57,8 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
|
||||
assertThat(onlyUser.getUserRoles().isAdmin()).isFalse();
|
||||
assertThat(onlyUser.getUserRoles().getGlobalRole()).isEqualTo(GlobalRole.NONE);
|
||||
assertThat(onlyUser.getUserRoles().getRegistrarRoles()).isEmpty();
|
||||
verify(iamClient).addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE);
|
||||
verify(iamClient)
|
||||
.addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE, "consoleIapServiceId");
|
||||
verifyNoMoreInteractions(iamClient);
|
||||
verifyNoInteractions(connection);
|
||||
}
|
||||
@@ -86,6 +88,41 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
|
||||
verifyNoMoreInteractions(connection);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_addToGroup_noIapServiceId() throws Exception {
|
||||
command.maybeGroupEmailAddress = Optional.of("group@example.test");
|
||||
command.consoleIapServiceId = Optional.empty();
|
||||
runCommandForced("--email", "user@example.test");
|
||||
User onlyUser = Iterables.getOnlyElement(DatabaseHelper.loadAllOf(User.class));
|
||||
assertThat(onlyUser.getEmailAddress()).isEqualTo("user@example.test");
|
||||
verify(connection)
|
||||
.sendPostRequest(
|
||||
UpdateUserGroupAction.PATH,
|
||||
ImmutableMap.of(
|
||||
"userEmailAddress",
|
||||
"user@example.test",
|
||||
"groupEmailAddress",
|
||||
"group@example.test",
|
||||
"groupUpdateMode",
|
||||
"ADD"),
|
||||
MediaType.PLAIN_TEXT_UTF_8,
|
||||
new byte[0]);
|
||||
verifyNoInteractions(iamClient);
|
||||
verifyNoMoreInteractions(connection);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_neitherGroupNorIapServiceId() {
|
||||
command.maybeGroupEmailAddress = Optional.empty();
|
||||
command.consoleIapServiceId = Optional.empty();
|
||||
IllegalArgumentException e =
|
||||
assertThrows(
|
||||
IllegalArgumentException.class, () -> runCommandForced("--email", "user@example.test"));
|
||||
assertThat(e)
|
||||
.hasMessageThat()
|
||||
.isEqualTo("At least one of groupEmailAddress or consoleIapServiceId must be present");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_registryLock() throws Exception {
|
||||
runCommandForced(
|
||||
@@ -107,7 +144,8 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
|
||||
void testSuccess_admin() throws Exception {
|
||||
runCommandForced("--email", "user@example.test", "--admin", "true");
|
||||
assertThat(loadExistingUser("user@example.test").getUserRoles().isAdmin()).isTrue();
|
||||
verify(iamClient).addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE);
|
||||
verify(iamClient)
|
||||
.addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE, "consoleIapServiceId");
|
||||
verifyNoMoreInteractions(iamClient);
|
||||
verifyNoInteractions(connection);
|
||||
}
|
||||
@@ -117,7 +155,8 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
|
||||
runCommandForced("--email", "user@example.test", "--global_role", "FTE");
|
||||
assertThat(loadExistingUser("user@example.test").getUserRoles().getGlobalRole())
|
||||
.isEqualTo(GlobalRole.FTE);
|
||||
verify(iamClient).addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE);
|
||||
verify(iamClient)
|
||||
.addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE, "consoleIapServiceId");
|
||||
verifyNoMoreInteractions(iamClient);
|
||||
verifyNoInteractions(connection);
|
||||
}
|
||||
@@ -136,7 +175,8 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
|
||||
RegistrarRole.ACCOUNT_MANAGER,
|
||||
"NewRegistrar",
|
||||
RegistrarRole.PRIMARY_CONTACT));
|
||||
verify(iamClient).addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE);
|
||||
verify(iamClient)
|
||||
.addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE, "consoleIapServiceId");
|
||||
verifyNoMoreInteractions(iamClient);
|
||||
verifyNoInteractions(connection);
|
||||
}
|
||||
@@ -144,7 +184,8 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
|
||||
@Test
|
||||
void testFailure_alreadyExists() throws Exception {
|
||||
runCommandForced("--email", "user@example.test");
|
||||
verify(iamClient).addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE);
|
||||
verify(iamClient)
|
||||
.addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE, "consoleIapServiceId");
|
||||
verifyNoMoreInteractions(iamClient);
|
||||
assertThat(
|
||||
assertThrows(
|
||||
|
||||
@@ -53,10 +53,20 @@ class DeleteDomainCommandTest extends EppToolCommandTestCase<DeleteDomainCommand
|
||||
"--client=NewRegistrar",
|
||||
"--domain_name=example.tld",
|
||||
"--reason=Test",
|
||||
"--registrar_request");
|
||||
"--registrar_request=true");
|
||||
eppVerifier.verifySent("domain_delete_by_registrar.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_requestedByRegistrarExplicitFalse() throws Exception {
|
||||
runCommandForced(
|
||||
"--client=NewRegistrar",
|
||||
"--domain_name=example.tld",
|
||||
"--reason=Test",
|
||||
"--registrar_request=false");
|
||||
eppVerifier.verifySent("domain_delete.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_noReason() {
|
||||
assertThrows(
|
||||
|
||||
@@ -24,6 +24,8 @@ import static google.registry.util.DateTimeUtils.plusMonths;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import com.beust.jcommander.ParameterException;
|
||||
import google.registry.model.domain.DomainAuthInfo;
|
||||
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -136,4 +138,28 @@ class GetDomainCommandTest extends CommandTestCase<GetDomainCommand> {
|
||||
// Deleted
|
||||
assertInStdout("Websafe key: kind:Domain@sql:rO0ABXQABTMtVExE");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_censorsAuthcodeByDefault() throws Exception {
|
||||
persistResource(
|
||||
DatabaseHelper.newDomain("example.tld")
|
||||
.asBuilder()
|
||||
.setAuthInfo(DomainAuthInfo.create(PasswordAuth.create("secret123")))
|
||||
.build());
|
||||
runCommand("example.tld");
|
||||
assertInStdout("value=[REDACTED]");
|
||||
assertNotInStdout("secret123");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_showAuthcode() throws Exception {
|
||||
persistResource(
|
||||
DatabaseHelper.newDomain("example.tld")
|
||||
.asBuilder()
|
||||
.setAuthInfo(DomainAuthInfo.create(PasswordAuth.create("secret123")))
|
||||
.build());
|
||||
runCommand("example.tld", "--show_authcode=true");
|
||||
assertInStdout("value=secret123");
|
||||
assertNotInStdout("[REDACTED]");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.mockito.ArgumentCaptor;
|
||||
/** Unit tests for {@link IamClient}. */
|
||||
public class IamClientTest {
|
||||
private final CloudResourceManager resourceManager = mock(CloudResourceManager.class);
|
||||
private final String consoleIapServiceId = "123456789";
|
||||
private final String projectId = "my-project";
|
||||
private final String account = "test@example.test";
|
||||
private final String role = "roles/fakeRole";
|
||||
@@ -89,11 +90,17 @@ public class IamClientTest {
|
||||
void testSuccess_addBinding_noMatchedBindingExists() throws Exception {
|
||||
setupRequests();
|
||||
assertThat(bindings.size()).isEqualTo(1);
|
||||
client.addBinding(account, role);
|
||||
client.addBinding(account, role, consoleIapServiceId);
|
||||
assertThat(bindings.size()).isEqualTo(2);
|
||||
Binding binding = bindings.get(1);
|
||||
assertThat(binding.getRole()).isEqualTo(role);
|
||||
assertThat(binding.getMembers()).containsExactly("user:" + account);
|
||||
assertThat(binding.getCondition()).isNotNull();
|
||||
assertThat(binding.getCondition().getTitle()).isEqualTo("Registrar Console IAP access");
|
||||
assertThat(binding.getCondition().getDescription())
|
||||
.isEqualTo("Restrict IAP access only to the Registrar Console HTTP(s) load balancer");
|
||||
assertThat(binding.getCondition().getExpression())
|
||||
.isEqualTo("resource.name == 'projects/my-project/iap_web/compute/services/123456789'");
|
||||
verifySetPolicyRequest();
|
||||
}
|
||||
|
||||
@@ -107,7 +114,7 @@ public class IamClientTest {
|
||||
when(matchedBinding.getMembers()).thenReturn(existingMembers);
|
||||
bindings.add(matchedBinding);
|
||||
assertThat(bindings.size()).isEqualTo(2);
|
||||
client.addBinding(account, role);
|
||||
client.addBinding(account, role, consoleIapServiceId);
|
||||
assertThat(bindings.size()).isEqualTo(2);
|
||||
assertThat(existingMembers)
|
||||
.containsExactly("serviceAccount:service@example.test", "user:" + account);
|
||||
@@ -125,7 +132,7 @@ public class IamClientTest {
|
||||
when(matchedBinding.getMembers()).thenReturn(existingMembers);
|
||||
bindings.add(matchedBinding);
|
||||
assertThat(bindings.size()).isEqualTo(2);
|
||||
client.addBinding(account, role);
|
||||
client.addBinding(account, role, consoleIapServiceId);
|
||||
assertThat(bindings.size()).isEqualTo(2);
|
||||
assertThat(existingMembers)
|
||||
.containsExactly("serviceAccount:service@example.test", "user:" + account);
|
||||
|
||||
@@ -73,6 +73,7 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
|
||||
command.clock = fakeClock;
|
||||
command.passwordGenerator = passwordGenerator;
|
||||
command.maybeGroupEmailAddress = Optional.of("group@example.com");
|
||||
command.consoleIapServiceId = Optional.of("consoleIapServiceId");
|
||||
command.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
|
||||
command.iamClient = iamClient;
|
||||
persistPremiumList("default_sandbox_list", USD, "sandbox,USD 1000");
|
||||
@@ -129,7 +130,9 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
|
||||
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);
|
||||
verify(iamClient)
|
||||
.addBinding(
|
||||
emailAddress, IAP_SECURED_WEB_APP_USER_ROLE, command.consoleIapServiceId.get());
|
||||
} else {
|
||||
cloudTasksHelper.assertTasksEnqueued(
|
||||
"console-user-group-update",
|
||||
|
||||
@@ -144,6 +144,7 @@ class ConsoleOteActionTest extends ConsoleActionBaseTestCase {
|
||||
verifyIapPermission(
|
||||
"contact@registry.example",
|
||||
Optional.of("someRandomString@email.test"),
|
||||
Optional.of("consoleIapServiceId"),
|
||||
cloudTasksHelper,
|
||||
iamClient);
|
||||
}
|
||||
@@ -216,6 +217,7 @@ class ConsoleOteActionTest extends ConsoleActionBaseTestCase {
|
||||
iamClient,
|
||||
registrarId,
|
||||
maybeGroupEmailAddress,
|
||||
Optional.of("consoleIapServiceId"),
|
||||
passwordGenerator,
|
||||
oteCreateData);
|
||||
}
|
||||
|
||||
+114
-12
@@ -20,6 +20,7 @@ import static google.registry.testing.DatabaseHelper.createTlds;
|
||||
import static google.registry.testing.DatabaseHelper.loadSingleton;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@@ -37,6 +38,7 @@ import google.registry.request.Action;
|
||||
import google.registry.request.RequestModule;
|
||||
import google.registry.request.auth.AuthResult;
|
||||
import google.registry.testing.ConsoleApiParamsUtils;
|
||||
import google.registry.testing.FakeResponse;
|
||||
import google.registry.testing.SystemPropertyExtension;
|
||||
import google.registry.util.EmailMessage;
|
||||
import google.registry.util.RegistryEnvironment;
|
||||
@@ -55,8 +57,6 @@ class ConsoleUpdateRegistrarActionTest extends ConsoleActionBaseTestCase {
|
||||
|
||||
private Registrar registrar;
|
||||
|
||||
private User user;
|
||||
|
||||
private static String registrarPostData =
|
||||
"{\"registrarId\":\"%s\",\"allowedTlds\":[%s],\"registryLockAllowed\":%s,"
|
||||
+ " \"lastPocVerificationDate\":%s }";
|
||||
@@ -76,12 +76,6 @@ class ConsoleUpdateRegistrarActionTest extends ConsoleActionBaseTestCase {
|
||||
.setAllowedTlds(ImmutableSet.of())
|
||||
.setRegistryLockAllowed(false)
|
||||
.build());
|
||||
user =
|
||||
persistResource(
|
||||
new User.Builder()
|
||||
.setEmailAddress("user@registrarId.com")
|
||||
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build())
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -209,15 +203,123 @@ class ConsoleUpdateRegistrarActionTest extends ConsoleActionBaseTestCase {
|
||||
.build());
|
||||
}
|
||||
|
||||
private ConsoleApiParams createParams() {
|
||||
AuthResult authResult = AuthResult.createUser(user);
|
||||
return ConsoleApiParamsUtils.createFake(authResult);
|
||||
@Test
|
||||
void testFailure_registrarNotFound() throws IOException {
|
||||
var action =
|
||||
createAction(
|
||||
String.format(
|
||||
registrarPostData,
|
||||
"nonexistent",
|
||||
"app, dev",
|
||||
false,
|
||||
"\"2023-12-12T00:00:00.000Z\""));
|
||||
action.run();
|
||||
assertThat(response.getStatus()).isEqualTo(SC_FORBIDDEN);
|
||||
}
|
||||
|
||||
private ConsoleUpdateRegistrarAction createAction(String requestData) throws IOException {
|
||||
@Test
|
||||
void testSuccess_supportAgentCanUpdateAllowedTlds() throws IOException {
|
||||
User supportAgentUser =
|
||||
persistResource(
|
||||
new User.Builder()
|
||||
.setEmailAddress("agent@registrarId.com")
|
||||
.setUserRoles(
|
||||
new UserRoles.Builder().setGlobalRole(GlobalRole.SUPPORT_AGENT).build())
|
||||
.build());
|
||||
var action =
|
||||
createAction(
|
||||
supportAgentUser,
|
||||
String.format(
|
||||
registrarPostData,
|
||||
"TheRegistrar",
|
||||
"app, dev",
|
||||
false,
|
||||
"\"2023-12-12T00:00:00.000Z\""));
|
||||
action.run();
|
||||
Registrar newRegistrar = Registrar.loadByRegistrarId("TheRegistrar").get();
|
||||
assertThat(newRegistrar.getAllowedTlds()).containsExactly("app", "dev");
|
||||
assertThat(response.getStatus()).isEqualTo(SC_OK);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_supportAgentCannotUpdateRegistryLock() throws IOException {
|
||||
User supportAgentUser =
|
||||
persistResource(
|
||||
new User.Builder()
|
||||
.setEmailAddress("agent@registrarId.com")
|
||||
.setUserRoles(
|
||||
new UserRoles.Builder().setGlobalRole(GlobalRole.SUPPORT_AGENT).build())
|
||||
.build());
|
||||
var action =
|
||||
createAction(
|
||||
supportAgentUser,
|
||||
String.format(
|
||||
registrarPostData,
|
||||
"TheRegistrar",
|
||||
"app, dev",
|
||||
true,
|
||||
"\"2023-12-12T00:00:00.000Z\""));
|
||||
action.run();
|
||||
assertThat(response.getStatus()).isEqualTo(SC_FORBIDDEN);
|
||||
Registrar unupdatedRegistrar = Registrar.loadByRegistrarId("TheRegistrar").get();
|
||||
assertThat(unupdatedRegistrar.isRegistryLockAllowed()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_userWithoutEditRegistrarDetailsCannotUpdateAllowedTlds() throws IOException {
|
||||
User normalUser =
|
||||
persistResource(
|
||||
new User.Builder()
|
||||
.setEmailAddress("normal@registrarId.com")
|
||||
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.NONE).build())
|
||||
.build());
|
||||
var action =
|
||||
createAction(
|
||||
normalUser,
|
||||
String.format(
|
||||
registrarPostData,
|
||||
"TheRegistrar",
|
||||
"app, dev",
|
||||
false,
|
||||
"\"2023-12-12T00:00:00.000Z\""));
|
||||
action.run();
|
||||
assertThat(response.getStatus()).isEqualTo(SC_FORBIDDEN);
|
||||
Registrar unupdatedRegistrar = Registrar.loadByRegistrarId("TheRegistrar").get();
|
||||
assertThat(unupdatedRegistrar.getAllowedTlds()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_supportLeadCanUpdateRegistryLock() throws IOException {
|
||||
User supportLeadUser =
|
||||
persistResource(
|
||||
new User.Builder()
|
||||
.setEmailAddress("lead@registrarId.com")
|
||||
.setUserRoles(
|
||||
new UserRoles.Builder().setGlobalRole(GlobalRole.SUPPORT_LEAD).build())
|
||||
.build());
|
||||
var action =
|
||||
createAction(
|
||||
supportLeadUser,
|
||||
String.format(
|
||||
registrarPostData, "TheRegistrar", "", true, "\"2023-12-12T00:00:00.000Z\""));
|
||||
action.run();
|
||||
Registrar newRegistrar = Registrar.loadByRegistrarId("TheRegistrar").get();
|
||||
assertThat(newRegistrar.isRegistryLockAllowed()).isTrue();
|
||||
assertThat(response.getStatus()).isEqualTo(SC_OK);
|
||||
}
|
||||
|
||||
private ConsoleUpdateRegistrarAction createAction(User actionUser, String requestData)
|
||||
throws IOException {
|
||||
AuthResult authResult = AuthResult.createUser(actionUser);
|
||||
consoleApiParams = ConsoleApiParamsUtils.createFake(authResult);
|
||||
response = (FakeResponse) consoleApiParams.response();
|
||||
when(consoleApiParams.request().getMethod()).thenReturn(Action.Method.POST.toString());
|
||||
Optional<Registrar> maybeRegistrarUpdateData =
|
||||
ConsoleModule.provideRegistrar(GSON, RequestModule.provideJsonBody(requestData, GSON));
|
||||
return new ConsoleUpdateRegistrarAction(consoleApiParams, maybeRegistrarUpdateData);
|
||||
}
|
||||
|
||||
private ConsoleUpdateRegistrarAction createAction(String requestData) throws IOException {
|
||||
return createAction(fteUser, requestData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -663,6 +663,7 @@ class ConsoleUsersActionTest extends ConsoleActionBaseTestCase {
|
||||
iamClient,
|
||||
"email.com",
|
||||
Optional.of("someRandomString"),
|
||||
Optional.of("consoleIapServiceId"),
|
||||
passwordGenerator,
|
||||
userData,
|
||||
"TheRegistrar");
|
||||
|
||||
+17
-4
@@ -343,10 +343,23 @@ visibleInRdapAsTech=true, visibleInDomainRdapAsAbuse=false}
|
||||
testRegistrar.getRegistrarId(),
|
||||
adminPoc.asBuilder().setEmailAddress("testemail@example.com").build());
|
||||
action.run();
|
||||
FakeResponse fakeResponse = response;
|
||||
assertThat(fakeResponse.getStatus()).isEqualTo(400);
|
||||
assertThat(fakeResponse.getPayload())
|
||||
.isEqualTo("Cannot remove or change the email address of primary contacts");
|
||||
assertThat(response.getStatus()).isEqualTo(SC_BAD_REQUEST);
|
||||
assertThat(response.getPayload()).isEqualTo("Cannot alter the set of primary contacts");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_addsAdminEmail() throws Exception {
|
||||
RegistrarPoc newAdminPoc =
|
||||
adminPoc
|
||||
.asBuilder()
|
||||
.setName("New Admin Contact")
|
||||
.setEmailAddress("new.admin@example.com")
|
||||
.build();
|
||||
ContactAction action =
|
||||
createAction(Action.Method.POST, fteUser, testRegistrar.getRegistrarId(), newAdminPoc);
|
||||
action.run();
|
||||
assertThat(response.getStatus()).isEqualTo(SC_BAD_REQUEST);
|
||||
assertThat(response.getPayload()).isEqualTo("Cannot alter the set of primary contacts");
|
||||
}
|
||||
|
||||
private ContactAction createAction(
|
||||
|
||||
+14
-1
@@ -19,6 +19,7 @@ import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableO
|
||||
import static google.registry.testing.DatabaseHelper.loadSingleton;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@@ -67,7 +68,8 @@ public class RdapRegistrarFieldsActionTest extends ConsoleActionBaseTestCase {
|
||||
"url",
|
||||
"\"http://my.fake.url\"",
|
||||
"localizedAddress",
|
||||
"{\"street\": [\"123 Example Boulevard\"], \"city\": \"Williamsburg\", \"state\":"
|
||||
"{\"street\": [\"123 Example Boulevard\"], \"city\":"
|
||||
+ " \"Williamsburg\", \"state\":"
|
||||
+ " \"NY\", \"zip\": \"11201\", \"countryCode\": \"US\"}"));
|
||||
|
||||
@Test
|
||||
@@ -132,6 +134,17 @@ public class RdapRegistrarFieldsActionTest extends ConsoleActionBaseTestCase {
|
||||
assertThat(DatabaseHelper.loadByEntity(newRegistrar)).isEqualTo(newRegistrar);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_zipTooLong() throws Exception {
|
||||
uiRegistrarMap.put(
|
||||
"localizedAddress",
|
||||
"{\"street\": [\"123 Fake St\"], \"city\": \"Fakeville\", \"state\":"
|
||||
+ " \"NL\", \"zip\": \"12345678901234567\", \"countryCode\": \"CA\"}");
|
||||
IllegalArgumentException exception =
|
||||
assertThrows(IllegalArgumentException.class, this::createAction);
|
||||
assertThat(exception).hasMessageThat().contains("Zip cannot be longer than 16 characters");
|
||||
}
|
||||
|
||||
private RdapRegistrarFieldsAction createAction() throws IOException {
|
||||
return createAction(fteUser);
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
<init-param>
|
||||
<param-name>headerConfig</param-name>
|
||||
<param-value>
|
||||
set Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; object-src 'none'; base-uri 'self';
|
||||
set X-Content-Type-Options: nosniff
|
||||
set Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; object-src 'none'; base-uri 'self';,
|
||||
set X-Content-Type-Options: nosniff,
|
||||
set X-Frame-Options: DENY
|
||||
</param-value>
|
||||
</init-param>
|
||||
|
||||
@@ -269,7 +269,7 @@ steps:
|
||||
if gcloud storage objects describe gs://${gcs_loc}/${gradle_bin}
|
||||
then
|
||||
local_md5=$(md5sum ${gradle_bin} | awk '{print $1}')
|
||||
remote_md5=$(gcloud storage hash -h gs://${gcs_loc}/${gradle_bin} | grep md5 | awk '{print $3}')
|
||||
remote_md5=$(gcloud storage hash --hex gs://${gcs_loc}/${gradle_bin} | grep md5 | awk '{print $2}')
|
||||
if [[ ${local_md5} != ${remote_md5} ]]
|
||||
then
|
||||
echo "ERROR: MD5 hash mismatch for ${gradle_bin}! Local (official): ${local_md5}, Remote (GCS cache): ${remote_md5}. Aborting build." >&2
|
||||
|
||||
Reference in New Issue
Block a user