1
0
mirror of https://github.com/google/nomulus synced 2026-01-30 17:42:20 +00:00

Compare commits

..

6 Commits

Author SHA1 Message Date
Shicong Huang
b9c40648d0 Add dual write for Registrar (#474)
* Add dual write for Registrar

* Use @AlsoLoad to set street field for Cloud SQL

* Change email body to use the new streetLine field

* Refactored the logic to handle street fields

* Simplify postLoad function

* Address comments

* Add a TODO to remove street

* Add test for onLoad and postLoad

* Rebase on master
2020-02-13 16:00:21 -05:00
sarahcaseybot
22a879e655 Add the schema and DAO for Locks in CloudSQL (#462)
* Add the schema and DAO for Locks in CloudSQL

* Addresses some comments

* Change number on flyway file

* Small changes

* More small changes

* Use checkArgumentNotNull instead of checkNotNull

* Address comments

* fix javadocs

* update persistence
2020-02-13 10:22:10 -05:00
Weimin Yu
62433c2238 Use JSON API for Maven Repo on GCS (#483)
* Use JSON API for Maven Repo on GCS

The url pattern https://storage.googleapis.com/{Bucket}/{Path}
uses the legacy XML API, which seems to be less robust than
the JSON API. We have observed connection resets after a few
thousand-file download bursts over 30 minutes.

This PR changes all urls to registry's Maven repo on GCS to
gcs://{Bucket}/{Path}. Gradle uses the JSON API for such urls.

TESTED=In Cloud Build with local change
2020-02-12 14:03:50 -05:00
Shicong Huang
90945bcc30 Add a test to verify persistence.xml (#482) 2020-02-12 12:39:03 -05:00
Weimin Yu
f134c4bf37 Use dependency cache in all Gradle tasks in GCB (#481)
* Use dependency cache in all Gradle tasks in GCB

Make the initial test and the final publishing steps use the shared
dependency cache.

Also make the initial test use the registry's own maven repo instead
of Maven Central.
2020-02-11 14:50:22 -05:00
gbrodman
44921c29d6 Clean up RegistryLockPostAction (#470)
* Clean up RegistryLockPostAction

* pocId -> userEmail when appropriate

* Merge remote-tracking branch 'origin/master' into lockPostImprovements

* Remove pocId
2020-02-11 08:43:44 -07:00
27 changed files with 973 additions and 127 deletions

View File

@@ -162,7 +162,10 @@ public abstract class ImmutableObject implements Cloneable {
// values.
Map<String, Object> result = new LinkedHashMap<>();
for (Entry<Field, Object> entry : ModelUtils.getFieldValues(o).entrySet()) {
result.put(entry.getKey().getName(), toMapRecursive(entry.getValue()));
Field field = entry.getKey();
if (!field.isAnnotationPresent(IgnoredInDiffableMap.class)) {
result.put(field.getName(), toMapRecursive(entry.getValue()));
}
}
return result;
} else if (o instanceof Map) {
@@ -191,6 +194,12 @@ public abstract class ImmutableObject implements Cloneable {
}
}
/** Marker to indicate that this filed should be ignored by {@link #toDiffableFieldMap}. */
@Documented
@Retention(RUNTIME)
@Target(FIELD)
protected @interface IgnoredInDiffableMap {}
/** Returns a map of all object fields (including sensitive data) that's used to produce diffs. */
@SuppressWarnings("unchecked")
public Map<String, Object> toDiffableFieldMap() {

View File

@@ -16,20 +16,24 @@ package google.registry.model.eppcommon;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.nullToEmpty;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.googlecode.objectify.annotation.AlsoLoad;
import com.googlecode.objectify.annotation.Ignore;
import com.googlecode.objectify.annotation.OnLoad;
import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.JsonMapBuilder;
import google.registry.model.Jsonifiable;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;
import javax.persistence.Embeddable;
import javax.persistence.MappedSuperclass;
import javax.persistence.PostLoad;
import javax.persistence.Transient;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlTransient;
@@ -53,15 +57,17 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
public class Address extends ImmutableObject implements Jsonifiable {
/** The schema validation will enforce that this has 3 lines at most. */
// TODO(shicong): Remove this field after migration. We need to figure out how to generate same
// XML from streetLine[1,2,3].
@XmlJavaTypeAdapter(NormalizedStringAdapter.class)
@Transient
List<String> street;
@Ignore String streetLine1;
@Ignore @XmlTransient @IgnoredInDiffableMap String streetLine1;
@Ignore String streetLine2;
@Ignore @XmlTransient @IgnoredInDiffableMap String streetLine2;
@Ignore String streetLine3;
@Ignore @XmlTransient @IgnoredInDiffableMap String streetLine3;
@XmlJavaTypeAdapter(NormalizedStringAdapter.class)
String city;
@@ -86,18 +92,6 @@ public class Address extends ImmutableObject implements Jsonifiable {
}
}
public String getStreetLine1() {
return streetLine1;
}
public String getStreetLine2() {
return streetLine2;
}
public String getStreetLine13() {
return streetLine3;
}
public String getCity() {
return city;
}
@@ -144,6 +138,9 @@ public class Address extends ImmutableObject implements Jsonifiable {
street == null || (!street.isEmpty() && street.size() <= 3),
"Street address must have [1-3] lines: %s", 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;
return this;
}
@@ -171,8 +168,14 @@ public class Address extends ImmutableObject implements Jsonifiable {
}
}
@OnLoad
void setStreetForCloudSql() {
/**
* Sets {@link #streetLine1}, {@link #streetLine2} and {@link #streetLine3} after loading the
* entity from Datastore.
*
* <p>This callback method is used by Objectify to set streetLine[1,2,3] fields as they are not
* persisted in the Datastore. TODO(shicong): Delete this method after database migration.
*/
void onLoad(@AlsoLoad("street") List<String> street) {
if (street == null || street.size() == 0) {
return;
}
@@ -180,4 +183,22 @@ public class Address extends ImmutableObject implements Jsonifiable {
streetLine2 = street.size() >= 2 ? street.get(1) : null;
streetLine3 = street.size() >= 3 ? street.get(2) : null;
}
/**
* Sets {@link #street} after loading the entity from Cloud SQL.
*
* <p>This callback method is used by Hibernate to set {@link #street} field as it is not
* persisted in Cloud SQL. We are doing this because the street list field is exposed by Address
* class and is used everywhere in our code base. Also, setting/reading a list of strings is more
* convenient.
*/
@PostLoad
void postLoad() {
street =
streetLine1 == null
? null
: Stream.of(streetLine1, streetLine2, streetLine3)
.filter(Objects::nonNull)
.collect(toImmutableList());
}
}

View File

@@ -29,20 +29,26 @@ public class CurrencyToBillingMapUserType extends MapUserType {
@Override
public Object toEntityTypeMap(Map<String, String> map) {
return map.entrySet().stream()
.collect(
toImmutableMap(
entry -> CurrencyUnit.of(entry.getKey()),
entry ->
new BillingAccountEntry(CurrencyUnit.of(entry.getKey()), entry.getValue())));
return map == null
? null
: map.entrySet().stream()
.collect(
toImmutableMap(
entry -> CurrencyUnit.of(entry.getKey()),
entry ->
new BillingAccountEntry(
CurrencyUnit.of(entry.getKey()), entry.getValue())));
}
@Override
public Map<String, String> toDbSupportedMap(Object map) {
return ((Map<CurrencyUnit, BillingAccountEntry>) map)
.entrySet().stream()
.collect(
toImmutableMap(
entry -> entry.getKey().getCode(), entry -> entry.getValue().getAccountId()));
return map == null
? null
: ((Map<CurrencyUnit, BillingAccountEntry>) map)
.entrySet().stream()
.collect(
toImmutableMap(
entry -> entry.getKey().getCode(),
entry -> entry.getValue().getAccountId()));
}
}

View File

@@ -0,0 +1,73 @@
// Copyright 2020 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.schema.registrar;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import google.registry.model.registrar.Registrar;
import java.util.Optional;
/** Data access object for {@link Registrar}. */
public class RegistrarDao {
private RegistrarDao() {}
/** Persists a new or updates an existing registrar in Cloud SQL. */
public static void saveNew(Registrar registrar) {
checkArgumentNotNull(registrar, "registrar must be specified");
jpaTm().transact(() -> jpaTm().getEntityManager().persist(registrar));
}
/** Updates an existing registrar in Cloud SQL, throws excpetion if it does not exist. */
public static void update(Registrar registrar) {
checkArgumentNotNull(registrar, "registrar must be specified");
jpaTm()
.transact(
() -> {
checkArgument(
checkExists(registrar.getClientId()),
"A registrar of this id does not exist: %s.",
registrar.getClientId());
jpaTm().getEntityManager().merge(registrar);
});
}
/** Returns whether the registrar of the given id exists. */
public static boolean checkExists(String clientId) {
checkArgumentNotNull(clientId, "clientId must be specified");
return jpaTm()
.transact(
() ->
jpaTm()
.getEntityManager()
.createQuery(
"SELECT 1 FROM Registrar WHERE clientIdentifier = :clientIdentifier",
Integer.class)
.setParameter("clientIdentifier", clientId)
.setMaxResults(1)
.getResultList()
.size()
> 0);
}
/** Loads the registrar by its id, returns empty if it doesn't exist. */
public static Optional<Registrar> load(String clientId) {
checkArgumentNotNull(clientId, "clientId must be specified");
return Optional.ofNullable(
jpaTm().transact(() -> jpaTm().getEntityManager().find(Registrar.class, clientId)));
}
}

View File

@@ -0,0 +1,132 @@
// Copyright 2020 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.schema.server;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import google.registry.model.ImmutableObject;
import google.registry.schema.server.Lock.LockId;
import google.registry.util.DateTimeUtils;
import java.io.Serializable;
import java.time.ZonedDateTime;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Table;
import org.joda.time.DateTime;
import org.joda.time.Duration;
/**
* A lock on some shared resource.
*
* <p>Locks are either specific to a tld or global to the entire system, in which case a tld of
* {@link GLOBAL} is used.
*
* <p>This uses a compound primary key as defined in {@link LockId}.
*/
@Entity
@Table
@IdClass(LockId.class)
public class Lock {
/** The resource name used to create the lock. */
@Column(nullable = false)
@Id
String resourceName;
/** The tld used to create the lock. */
@Column(nullable = false)
@Id
String tld;
/**
* Unique log ID of the request that owns this lock.
*
* <p>When that request is no longer running (is finished), the lock can be considered implicitly
* released.
*
* <p>See {@link RequestStatusCheckerImpl#getLogId} for details about how it's created in
* practice.
*/
@Column(nullable = false)
String requestLogId;
/** When the lock was acquired. Used for logging. */
@Column(nullable = false)
ZonedDateTime acquiredTime;
/** When the lock can be considered implicitly released. */
@Column(nullable = false)
ZonedDateTime expirationTime;
/** The scope of a lock that is not specific to a single tld. */
static final String GLOBAL = "GLOBAL";
/**
* Validate input and create a new {@link Lock} for the given resource name in the specified tld.
*/
private Lock(
String resourceName,
String tld,
String requestLogId,
DateTime acquiredTime,
Duration leaseLength) {
this.resourceName = checkArgumentNotNull(resourceName, "The resource name cannot be null");
this.tld = checkArgumentNotNull(tld, "The tld cannot be null. For a global lock, use GLOBAL");
this.requestLogId =
checkArgumentNotNull(requestLogId, "The requestLogId of the lock cannot be null");
this.acquiredTime =
DateTimeUtils.toZonedDateTime(
checkArgumentNotNull(acquiredTime, "The acquired time of the lock cannot be null"));
checkArgumentNotNull(leaseLength, "The lease length of the lock cannot be null");
this.expirationTime = DateTimeUtils.toZonedDateTime(acquiredTime.plus(leaseLength));
}
// Hibernate requires a default constructor.
private Lock() {}
/** Constructs a {@link Lock} object. */
public static Lock create(
String resourceName,
String tld,
String requestLogId,
DateTime acquiredTime,
Duration leaseLength) {
checkArgumentNotNull(
tld, "The tld cannot be null. To create a global lock, use the createGlobal method");
return new Lock(resourceName, tld, requestLogId, acquiredTime, leaseLength);
}
/** Constructs a {@link Lock} object with a {@link GLOBAL} scope. */
public static Lock createGlobal(
String resourceName, String requestLogId, DateTime acquiredTime, Duration leaseLength) {
return new Lock(resourceName, GLOBAL, requestLogId, acquiredTime, leaseLength);
}
static class LockId extends ImmutableObject implements Serializable {
String resourceName;
String tld;
private LockId() {}
LockId(String resourceName, String tld) {
this.resourceName = checkArgumentNotNull(resourceName, "The resource name cannot be null");
this.tld = tld;
}
}
}

View File

@@ -0,0 +1,76 @@
// Copyright 2020 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.schema.server;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.schema.server.Lock.GLOBAL;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import google.registry.schema.server.Lock.LockId;
import java.util.Optional;
/** Data access object class for {@link Lock}. */
public class LockDao {
/** Saves the {@link Lock} object to Cloud SQL. */
public static void saveNew(Lock lock) {
jpaTm()
.transact(
() -> {
jpaTm().getEntityManager().persist(lock);
});
}
/**
* Loads and returns a {@link Lock} object with the given resourceName and tld from Cloud SQL if
* it exists, else empty.
*/
public static Optional<Lock> load(String resourceName, String tld) {
checkArgumentNotNull(resourceName, "The resource name of the lock to load cannot be null");
checkArgumentNotNull(tld, "The tld of the lock to load cannot be null");
return Optional.ofNullable(
jpaTm()
.transact(
() -> jpaTm().getEntityManager().find(Lock.class, new LockId(resourceName, tld))));
}
/**
* Loads a global {@link Lock} object with the given resourceName from Cloud SQL if it exists,
* else empty.
*/
public static Optional<Lock> load(String resourceName) {
checkArgumentNotNull(resourceName, "The resource name of the lock to load cannot be null");
return Optional.ofNullable(
jpaTm()
.transact(
() ->
jpaTm().getEntityManager().find(Lock.class, new LockId(resourceName, GLOBAL))));
}
/**
* Deletes the given {@link Lock} object from Cloud SQL. This method is idempotent and will simply
* return if the lock has already been deleted.
*/
public static void delete(Lock lock) {
jpaTm()
.transact(
() -> {
Optional<Lock> loadedLock = load(lock.resourceName, lock.tld);
if (loadedLock.isPresent()) {
jpaTm().getEntityManager().remove(loadedLock.get());
}
});
}
}

View File

@@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Predicates.isNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
import static google.registry.util.RegistrarUtils.normalizeRegistrarName;
import static java.nio.charset.StandardCharsets.US_ASCII;
@@ -28,6 +29,7 @@ import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarAddress;
import google.registry.model.registry.Registry;
@@ -53,6 +55,8 @@ import org.joda.time.DateTime;
/** Shared base class for commands to create or update a {@link Registrar}. */
abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
static final FluentLogger logger = FluentLogger.forEnclosingClass();
@Parameter(
description = "Client identifier of the registrar account",
required = true)
@@ -458,4 +462,26 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
stageEntityChange(oldRegistrar, newRegistrar);
}
}
@Override
protected String execute() throws Exception {
// Save registrar to Datastore and output its response
logger.atInfo().log(super.execute());
String cloudSqlMessage;
try {
jpaTm()
.transact(
() ->
getChangedEntities().forEach(newEntity -> saveToCloudSql((Registrar) newEntity)));
cloudSqlMessage =
String.format("Updated %d entities in Cloud SQL.\n", getChangedEntities().size());
} catch (Throwable t) {
cloudSqlMessage = "Unexpected error saving registrar to Cloud SQL from nomulus tool command";
logger.atSevere().withCause(t).log(cloudSqlMessage);
}
return cloudSqlMessage;
}
abstract void saveToCloudSql(Registrar registrar);
}

View File

@@ -32,6 +32,7 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;
import google.registry.config.RegistryEnvironment;
import google.registry.model.registrar.Registrar;
import google.registry.schema.registrar.RegistrarDao;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -69,6 +70,11 @@ final class CreateRegistrarCommand extends CreateOrUpdateRegistrarCommand
registrarState = Optional.ofNullable(registrarState).orElse(ACTIVE);
}
@Override
void saveToCloudSql(Registrar registrar) {
RegistrarDao.saveNew(registrar);
}
@Nullable
@Override
Registrar getOldRegistrar(final String clientId) {

View File

@@ -20,6 +20,7 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.DatastoreServiceUtils.getNameOrId;
@@ -45,9 +46,9 @@ import javax.annotation.Nullable;
public abstract class MutatingCommand extends ConfirmingCommand implements CommandWithRemoteApi {
/**
* A mutation of a specific entity, represented by an old and a new version of the entity.
* Storing the old version is necessary to enable checking that the existing entity has not been
* modified when applying a mutation that was created outside the same transaction.
* A mutation of a specific entity, represented by an old and a new version of the entity. Storing
* the old version is necessary to enable checking that the existing entity has not been modified
* when applying a mutation that was created outside the same transaction.
*/
private static class EntityChange {
@@ -169,8 +170,8 @@ public abstract class MutatingCommand extends ConfirmingCommand implements Comma
}
/**
* Returns a set of lists of EntityChange actions to commit. Each list should be executed in
* order inside a single transaction.
* Returns a set of lists of EntityChange actions to commit. Each list should be executed in order
* inside a single transaction.
*/
private ImmutableSet<ImmutableList<EntityChange>> getCollatedEntityChangeBatches() {
ImmutableSet.Builder<ImmutableList<EntityChange>> batches = new ImmutableSet.Builder<>();
@@ -221,4 +222,11 @@ public abstract class MutatingCommand extends ConfirmingCommand implements Comma
? "No entity changes to apply."
: changedEntitiesMap.values().stream().map(Object::toString).collect(joining("\n"));
}
/** Returns the collection of the new entity in the {@link EntityChange}. */
protected ImmutableList<ImmutableObject> getChangedEntities() {
return changedEntitiesMap.values().stream()
.map(entityChange -> entityChange.newEntity)
.collect(toImmutableList());
}
}

View File

@@ -20,6 +20,7 @@ import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
import com.beust.jcommander.Parameters;
import google.registry.config.RegistryEnvironment;
import google.registry.model.registrar.Registrar;
import google.registry.schema.registrar.RegistrarDao;
import javax.annotation.Nullable;
/** Command to update a Registrar. */
@@ -49,4 +50,9 @@ final class UpdateRegistrarCommand extends CreateOrUpdateRegistrarCommand {
+ " contact.");
}
}
@Override
void saveToCloudSql(Registrar registrar) {
RegistrarDao.update(registrar);
}
}

View File

@@ -119,8 +119,9 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA
// handler, registrar-settings really only supports read and update.
String op = Optional.ofNullable((String) input.get(OP_PARAM)).orElse("read");
@SuppressWarnings("unchecked")
Map<String, ?> args = (Map<String, Object>)
Optional.<Object>ofNullable(input.get(ARGS_PARAM)).orElse(ImmutableMap.of());
Map<String, ?> args =
(Map<String, Object>)
Optional.<Object>ofNullable(input.get(ARGS_PARAM)).orElse(ImmutableMap.of());
logger.atInfo().log("Received request '%s' on registrar '%s' with args %s", op, clientId, args);
String status = "SUCCESS";
@@ -296,8 +297,7 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA
.ifPresent(builder::setEmailAddress);
builder.setPhoneNumber(
RegistrarFormFields.PHONE_NUMBER_FIELD.extractUntyped(args).orElse(null));
builder.setFaxNumber(
RegistrarFormFields.FAX_NUMBER_FIELD.extractUntyped(args).orElse(null));
builder.setFaxNumber(RegistrarFormFields.FAX_NUMBER_FIELD.extractUntyped(args).orElse(null));
builder.setLocalizedAddress(
RegistrarFormFields.L10N_ADDRESS_FIELD.extractUntyped(args).orElse(null));
@@ -357,9 +357,7 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA
* <p>On success, returns {@code builder.build()}.
*/
private Registrar checkNotChangedUnlessAllowed(
Registrar.Builder builder,
Registrar originalRegistrar,
Role allowedRole) {
Registrar.Builder builder, Registrar originalRegistrar, Role allowedRole) {
Registrar updatedRegistrar = builder.build();
if (updatedRegistrar.equals(originalRegistrar)) {
return updatedRegistrar;
@@ -374,9 +372,7 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA
}
Map<?, ?> diffs =
DiffUtils.deepDiff(
originalRegistrar.toDiffableFieldMap(),
updatedRegistrar.toDiffableFieldMap(),
true);
originalRegistrar.toDiffableFieldMap(), updatedRegistrar.toDiffableFieldMap(), true);
throw new ForbiddenException(
String.format("Unauthorized: only %s can change fields %s", allowedRole, diffs.keySet()));
}
@@ -400,9 +396,10 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA
Set<String> emails = new HashSet<>();
for (RegistrarContact contact : updatedContacts) {
if (!emails.add(contact.getEmailAddress())) {
throw new ContactRequirementException(String.format(
"One email address (%s) cannot be used for multiple contacts",
contact.getEmailAddress()));
throw new ContactRequirementException(
String.format(
"One email address (%s) cannot be used for multiple contacts",
contact.getEmailAddress()));
}
}
// Check that required contacts don't go away, once they are set.

View File

@@ -21,7 +21,6 @@ import static google.registry.security.JsonResponseHelper.Status.ERROR;
import static google.registry.security.JsonResponseHelper.Status.SUCCESS;
import static google.registry.ui.server.registrar.RegistrarConsoleModule.PARAM_CLIENT_ID;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
@@ -34,6 +33,7 @@ import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarContact;
import google.registry.request.Action;
import google.registry.request.Action.Method;
import google.registry.request.HttpException.ForbiddenException;
import google.registry.request.JsonActionRunner;
import google.registry.request.auth.Auth;
import google.registry.request.auth.AuthResult;
@@ -123,10 +123,16 @@ public class RegistryLockPostAction implements Runnable, JsonActionRunner.JsonAc
!Strings.isNullOrEmpty(postInput.fullyQualifiedDomainName),
"Missing key for fullyQualifiedDomainName");
checkNotNull(postInput.isLock, "Missing key for isLock");
checkArgumentPresent(authResult.userAuthInfo(), "User is not logged in");
UserAuthInfo userAuthInfo =
authResult
.userAuthInfo()
.orElseThrow(() -> new ForbiddenException("User is not logged in"));
boolean isAdmin = authResult.userAuthInfo().get().isUserAdmin();
verifyRegistryLockPassword(postInput);
boolean isAdmin = userAuthInfo.isUserAdmin();
String userEmail = userAuthInfo.user().getEmail();
if (!isAdmin) {
verifyRegistryLockPassword(postInput, userEmail);
}
jpaTm()
.transact(
() -> {
@@ -135,12 +141,12 @@ public class RegistryLockPostAction implements Runnable, JsonActionRunner.JsonAc
? domainLockUtils.createRegistryLockRequest(
postInput.fullyQualifiedDomainName,
postInput.clientId,
postInput.pocId,
userEmail,
isAdmin,
clock)
: domainLockUtils.createRegistryUnlockRequest(
postInput.fullyQualifiedDomainName, postInput.clientId, isAdmin, clock);
sendVerificationEmail(registryLock, postInput.isLock);
sendVerificationEmail(registryLock, userEmail, postInput.isLock);
});
String action = postInput.isLock ? "lock" : "unlock";
return JsonResponseHelper.create(SUCCESS, String.format("Successful %s", action));
@@ -152,7 +158,7 @@ public class RegistryLockPostAction implements Runnable, JsonActionRunner.JsonAc
}
}
private void sendVerificationEmail(RegistryLock lock, boolean isLock) {
private void sendVerificationEmail(RegistryLock lock, String userEmail, boolean isLock) {
try {
String url =
new URIBuilder()
@@ -165,8 +171,7 @@ public class RegistryLockPostAction implements Runnable, JsonActionRunner.JsonAc
.toString();
String body = String.format(VERIFICATION_EMAIL_TEMPLATE, lock.getDomainName(), url);
ImmutableList<InternetAddress> recipients =
ImmutableList.of(
new InternetAddress(authResult.userAuthInfo().get().user().getEmail(), true));
ImmutableList.of(new InternetAddress(userEmail, true));
String action = isLock ? "lock" : "unlock";
sendEmailService.sendEmail(
EmailMessage.newBuilder()
@@ -180,30 +185,25 @@ public class RegistryLockPostAction implements Runnable, JsonActionRunner.JsonAc
}
}
private void verifyRegistryLockPassword(RegistryLockPostInput postInput)
private void verifyRegistryLockPassword(RegistryLockPostInput postInput, String userEmail)
throws RegistrarAccessDeniedException {
// Verify that the user can access the registrar and that the user is either an admin or has
// Verify that the user can access the registrar and that the user has
// registry lock enabled and provided a correct password
checkArgument(authResult.userAuthInfo().isPresent(), "Auth result not present");
Registrar registrar = registrarAccessor.getRegistrar(postInput.clientId);
checkArgument(
registrar.isRegistryLockAllowed(), "Registry lock not allowed for this registrar");
UserAuthInfo userAuthInfo = authResult.userAuthInfo().get();
if (!userAuthInfo.isUserAdmin()) {
checkArgument(!Strings.isNullOrEmpty(postInput.pocId), "Missing key for pocId");
checkArgument(!Strings.isNullOrEmpty(postInput.password), "Missing key for password");
RegistrarContact registrarContact =
registrar.getContacts().stream()
.filter(contact -> contact.getEmailAddress().equals(postInput.pocId))
.findFirst()
.orElseThrow(
() ->
new IllegalArgumentException(
String.format("Unknown registrar POC ID %s", postInput.pocId)));
checkArgument(
registrarContact.verifyRegistryLockPassword(postInput.password),
"Incorrect registry lock password for contact");
}
checkArgument(!Strings.isNullOrEmpty(postInput.password), "Missing key for password");
RegistrarContact registrarContact =
registrar.getContacts().stream()
.filter(contact -> contact.getEmailAddress().equals(userEmail))
.findFirst()
.orElseThrow(
() ->
new IllegalArgumentException(
String.format("Unknown user email %s", userEmail)));
checkArgument(
registrarContact.verifyRegistryLockPassword(postInput.password),
"Incorrect registry lock password for contact");
}
/** Value class that represents the expected input body from the UI request. */
@@ -211,7 +211,6 @@ public class RegistryLockPostAction implements Runnable, JsonActionRunner.JsonAc
private String clientId;
private String fullyQualifiedDomainName;
private Boolean isLock;
private String pocId;
private String password;
}
}

View File

@@ -25,17 +25,12 @@
<class>google.registry.schema.domain.RegistryLock</class>
<class>google.registry.schema.tmch.ClaimsList</class>
<class>google.registry.schema.cursor.Cursor</class>
<class>google.registry.model.transfer.BaseTransferObject</class>
<class>google.registry.schema.server.Lock</class>
<class>google.registry.schema.tld.PremiumList</class>
<class>google.registry.schema.tld.PremiumEntry</class>
<class>google.registry.schema.tld.ReservedList</class>
<class>google.registry.model.domain.secdns.DelegationSignerData</class>
<class>google.registry.model.domain.DesignatedContact</class>
<class>google.registry.model.domain.DomainBase</class>
<class>google.registry.model.domain.GracePeriod</class>
<class>org.joda.time.Period</class>
<class>google.registry.model.transfer.TransferData</class>
<class>google.registry.model.eppcommon.Trid</class>
<!-- Customized type converters -->
<class>google.registry.persistence.BloomFilterConverter</class>

View File

@@ -0,0 +1,79 @@
// Copyright 2020 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.eppcommon;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Tests for {@link Address}. */
@RunWith(JUnit4.class)
public class AddressTest {
@Test
public void onLoad_setsIndividualStreetLinesSuccessfully() {
Address address = new Address();
address.onLoad(ImmutableList.of("line1", "line2", "line3"));
assertThat(address.streetLine1).isEqualTo("line1");
assertThat(address.streetLine2).isEqualTo("line2");
assertThat(address.streetLine3).isEqualTo("line3");
}
@Test
public void onLoad_setsOnlyNonNullStreetLines() {
Address address = new Address();
address.onLoad(ImmutableList.of("line1", "line2"));
assertThat(address.streetLine1).isEqualTo("line1");
assertThat(address.streetLine2).isEqualTo("line2");
assertThat(address.streetLine3).isNull();
}
@Test
public void onLoad_doNothingIfInputIsNull() {
Address address = new Address();
address.onLoad(null);
assertThat(address.streetLine1).isNull();
assertThat(address.streetLine2).isNull();
assertThat(address.streetLine3).isNull();
}
@Test
public void postLoad_setsStreetListSuccessfully() {
Address address = new Address();
address.streetLine1 = "line1";
address.streetLine2 = "line2";
address.streetLine3 = "line3";
address.postLoad();
assertThat(address.street).containsExactly("line1", "line2", "line3");
}
@Test
public void postLoad_setsOnlyNonNullStreetLines() {
Address address = new Address();
address.streetLine1 = "line1";
address.streetLine2 = "line2";
address.postLoad();
assertThat(address.street).containsExactly("line1", "line2");
}
@Test
public void postLoad_doNothingIfInputIsNull() {
Address address = new Address();
address.postLoad();
assertThat(address.street).isNull();
}
}

View File

@@ -0,0 +1,67 @@
// Copyright 2020 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.persistence;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.truth.Expect;
import java.util.Collections;
import javax.persistence.AttributeConverter;
import javax.persistence.Entity;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests to verify persistence.xml is valid. */
@RunWith(JUnit4.class)
public class PersistenceXmlTest {
@ClassRule public static final Expect expect = Expect.create();
@Test
public void verifyClassTags_containOnlyRequiredClasses() {
ImmutableList<Class> managedClassed = PersistenceXmlUtility.getManagedClasses();
ImmutableList<Class> unnecessaryClasses =
managedClassed.stream()
.filter(
clazz ->
!clazz.isAnnotationPresent(Entity.class)
&& !AttributeConverter.class.isAssignableFrom(clazz))
.collect(toImmutableList());
ImmutableSet<Class> duplicateClasses =
managedClassed.stream()
.filter(clazz -> Collections.frequency(managedClassed, clazz) > 1)
.collect(toImmutableSet());
expect
.withMessage("Found duplicate <class> tags defined in persistence.xml.")
.that(duplicateClasses)
.isEmpty();
expect
.withMessage(
"Found unnecessary <class> tags defined in persistence.xml. Only entity class and"
+ " implementation of AttributeConverter are required to be added in"
+ " persistence.xml.")
.that(unnecessaryClasses)
.isEmpty();
}
}

View File

@@ -19,13 +19,17 @@ import google.registry.model.domain.DomainBaseSqlTest;
import google.registry.model.registry.RegistryLockDaoTest;
import google.registry.persistence.transaction.JpaEntityCoverage;
import google.registry.schema.cursor.CursorDaoTest;
import google.registry.schema.registrar.RegistrarDaoTest;
import google.registry.schema.server.LockDaoTest;
import google.registry.schema.tld.PremiumListDaoTest;
import google.registry.schema.tld.ReservedListDaoTest;
import google.registry.schema.tmch.ClaimsListDaoTest;
import google.registry.tools.CreateRegistrarCommandTest;
import google.registry.tools.CreateReservedListCommandTest;
import google.registry.tools.DomainLockUtilsTest;
import google.registry.tools.LockDomainCommandTest;
import google.registry.tools.UnlockDomainCommandTest;
import google.registry.tools.UpdateRegistrarCommandTest;
import google.registry.tools.UpdateReservedListCommandTest;
import google.registry.tools.server.CreatePremiumListActionTest;
import google.registry.tools.server.UpdatePremiumListActionTest;
@@ -53,18 +57,22 @@ import org.junit.runners.Suite.SuiteClasses;
@SuiteClasses({
ClaimsListDaoTest.class,
CreatePremiumListActionTest.class,
CreateRegistrarCommandTest.class,
CreateReservedListCommandTest.class,
CursorDaoTest.class,
DomainLockUtilsTest.class,
LockDaoTest.class,
LockDomainCommandTest.class,
DomainBaseSqlTest.class,
PremiumListDaoTest.class,
RegistrarDaoTest.class,
RegistryLockDaoTest.class,
RegistryLockGetActionTest.class,
RegistryLockVerifyActionTest.class,
ReservedListDaoTest.class,
UnlockDomainCommandTest.class,
UpdatePremiumListActionTest.class,
UpdateRegistrarCommandTest.class,
UpdateReservedListCommandTest.class
})
public class SqlIntegrationTestSuite {

View File

@@ -0,0 +1,104 @@
// Copyright 2020 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.schema.registrar;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableList;
import google.registry.model.EntityTestCase;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarAddress;
import google.registry.persistence.transaction.JpaTestRules;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageRule;
import google.registry.testing.FakeClock;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link RegistrarDao}. */
@RunWith(JUnit4.class)
public class RegistrarDaoTest extends EntityTestCase {
private final FakeClock fakeClock = new FakeClock();
@Rule
public final JpaIntegrationWithCoverageRule jpaRule =
new JpaTestRules.Builder().withClock(fakeClock).buildIntegrationWithCoverageRule();
private Registrar testRegistrar;
@Before
public void setUp() {
testRegistrar =
new Registrar.Builder()
.setType(Registrar.Type.TEST)
.setClientId("registrarId")
.setRegistrarName("registrarName")
.setLocalizedAddress(
new RegistrarAddress.Builder()
.setStreet(ImmutableList.of("123 Example Boulevard."))
.setCity("Williamsburg")
.setState("NY")
.setZip("11211")
.setCountryCode("US")
.build())
.build();
}
@Test
public void saveNew_worksSuccessfully() {
assertThat(RegistrarDao.checkExists("registrarId")).isFalse();
RegistrarDao.saveNew(testRegistrar);
assertThat(RegistrarDao.checkExists("registrarId")).isTrue();
}
@Test
public void update_worksSuccessfully() {
RegistrarDao.saveNew(testRegistrar);
Registrar persisted = RegistrarDao.load("registrarId").get();
assertThat(persisted.getRegistrarName()).isEqualTo("registrarName");
RegistrarDao.update(persisted.asBuilder().setRegistrarName("changedRegistrarName").build());
persisted = RegistrarDao.load("registrarId").get();
assertThat(persisted.getRegistrarName()).isEqualTo("changedRegistrarName");
}
@Test
public void update_throwsExceptionWhenEntityDoesNotExist() {
assertThat(RegistrarDao.checkExists("registrarId")).isFalse();
assertThrows(IllegalArgumentException.class, () -> RegistrarDao.update(testRegistrar));
}
@Test
public void load_worksSuccessfully() {
assertThat(RegistrarDao.checkExists("registrarId")).isFalse();
RegistrarDao.saveNew(testRegistrar);
Registrar persisted = RegistrarDao.load("registrarId").get();
assertThat(persisted.getClientId()).isEqualTo("registrarId");
assertThat(persisted.getRegistrarName()).isEqualTo("registrarName");
assertThat(persisted.getCreationTime()).isEqualTo(fakeClock.nowUtc());
assertThat(persisted.getLocalizedAddress())
.isEqualTo(
new RegistrarAddress.Builder()
.setStreet(ImmutableList.of("123 Example Boulevard."))
.setCity("Williamsburg")
.setState("NY")
.setZip("11211")
.setCountryCode("US")
.build());
}
}

View File

@@ -0,0 +1,129 @@
// Copyright 2020 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.schema.server;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import google.registry.persistence.transaction.JpaTestRules;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageRule;
import google.registry.testing.FakeClock;
import java.util.Optional;
import javax.persistence.RollbackException;
import org.joda.time.Duration;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link Lock}. */
@RunWith(JUnit4.class)
public class LockDaoTest {
private final FakeClock fakeClock = new FakeClock();
@Rule
public final JpaIntegrationWithCoverageRule jpaRule =
new JpaTestRules.Builder().withClock(fakeClock).buildIntegrationWithCoverageRule();
@Test
public void save_worksSuccessfully() {
Lock lock =
Lock.create("testResource", "tld", "testLogId", fakeClock.nowUtc(), Duration.millis(2));
LockDao.saveNew(lock);
Optional<Lock> returnedLock = LockDao.load("testResource", "tld");
assertThat(returnedLock.get().expirationTime).isEqualTo(lock.expirationTime);
assertThat(returnedLock.get().requestLogId).isEqualTo(lock.requestLogId);
}
@Test
public void save_failsWhenLockAlreadyExists() {
Lock lock =
Lock.create("testResource", "tld", "testLogId", fakeClock.nowUtc(), Duration.millis(2));
LockDao.saveNew(lock);
Lock lock2 =
Lock.create("testResource", "tld", "testLogId2", fakeClock.nowUtc(), Duration.millis(4));
RollbackException thrown = assertThrows(RollbackException.class, () -> LockDao.saveNew(lock2));
assertThat(thrown.getCause().getCause().getCause().getMessage())
.contains("duplicate key value violates unique constraint");
}
@Test
public void save_worksSuccesfullyGlobalLock() {
Lock lock =
Lock.createGlobal("testResource", "testLogId", fakeClock.nowUtc(), Duration.millis(2));
LockDao.saveNew(lock);
Optional<Lock> returnedLock = LockDao.load("testResource");
assertThat(returnedLock.get().expirationTime).isEqualTo(lock.expirationTime);
assertThat(returnedLock.get().requestLogId).isEqualTo(lock.requestLogId);
}
@Test
public void load_worksSuccessfully() {
Lock lock =
Lock.create("testResource", "tld", "testLogId", fakeClock.nowUtc(), Duration.millis(2));
LockDao.saveNew(lock);
Optional<Lock> returnedLock = LockDao.load("testResource", "tld");
assertThat(returnedLock.get().expirationTime).isEqualTo(lock.expirationTime);
assertThat(returnedLock.get().requestLogId).isEqualTo(lock.requestLogId);
}
@Test
public void load_worksSuccessfullyGlobalLock() {
Lock lock =
Lock.createGlobal("testResource", "testLogId", fakeClock.nowUtc(), Duration.millis(2));
LockDao.saveNew(lock);
Optional<Lock> returnedLock = LockDao.load("testResource");
assertThat(returnedLock.get().expirationTime).isEqualTo(lock.expirationTime);
assertThat(returnedLock.get().requestLogId).isEqualTo(lock.requestLogId);
}
@Test
public void load_worksSuccesfullyLockDoesNotExist() {
Optional<Lock> returnedLock = LockDao.load("testResource", "tld");
assertThat(returnedLock.isPresent()).isFalse();
}
@Test
public void delete_worksSuccesfully() {
Lock lock =
Lock.create("testResource", "tld", "testLogId", fakeClock.nowUtc(), Duration.millis(2));
LockDao.saveNew(lock);
Optional<Lock> returnedLock = LockDao.load("testResource", "tld");
assertThat(returnedLock.get().expirationTime).isEqualTo(lock.expirationTime);
LockDao.delete(lock);
returnedLock = LockDao.load("testResource", "tld");
assertThat(returnedLock.isPresent()).isFalse();
}
@Test
public void delete_worksSuccessfullyGlobalLock() {
Lock lock =
Lock.createGlobal("testResource", "testLogId", fakeClock.nowUtc(), Duration.millis(2));
LockDao.saveNew(lock);
Optional<Lock> returnedLock = LockDao.load("testResource");
assertThat(returnedLock.get().expirationTime).isEqualTo(lock.expirationTime);
LockDao.delete(lock);
returnedLock = LockDao.load("testResource");
assertThat(returnedLock.isPresent()).isFalse();
}
@Test
public void delete_succeedsLockDoesntExist() {
Lock lock =
Lock.createGlobal("testResource", "testLogId", fakeClock.nowUtc(), Duration.millis(2));
LockDao.delete(lock);
}
}

View File

@@ -33,12 +33,17 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Range;
import com.google.common.net.MediaType;
import google.registry.model.registrar.Registrar;
import google.registry.persistence.transaction.JpaTestRules;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageRule;
import google.registry.schema.registrar.RegistrarDao;
import google.registry.testing.CertificateSamples;
import google.registry.testing.FakeClock;
import java.io.IOException;
import java.util.Optional;
import org.joda.money.CurrencyUnit;
import org.joda.time.DateTime;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
@@ -46,6 +51,12 @@ import org.mockito.Mock;
/** Unit tests for {@link CreateRegistrarCommand}. */
public class CreateRegistrarCommandTest extends CommandTestCase<CreateRegistrarCommand> {
private final FakeClock fakeClock = new FakeClock();
@Rule
public final JpaIntegrationWithCoverageRule jpaRule =
new JpaTestRules.Builder().withClock(fakeClock).buildIntegrationWithCoverageRule();
@Mock private AppEngineConnection connection;
@Before
@@ -100,6 +111,28 @@ public class CreateRegistrarCommandTest extends CommandTestCase<CreateRegistrarC
eq(new byte[0]));
}
@Test
public void testSuccess_alsoSaveToCloudSql() throws Exception {
runCommandForced(
"--name=blobio",
"--password=\"some_password\"",
"--registrar_type=REAL",
"--iana_id=8",
"--passcode=01234",
"--icann_referral_email=foo@bar.test",
"--street=\"123 Fake St\"",
"--city Fakington",
"--state MA",
"--zip 00351",
"--cc US",
"clientz");
Optional<Registrar> registrar = Registrar.loadByClientId("clientz");
assertThat(registrar).isPresent();
assertThat(registrar.get().verifyPassword("some_password")).isTrue();
assertThat(RegistrarDao.checkExists("clientz")).isTrue();
}
@Test
public void testSuccess_quotedPassword() throws Exception {
runCommandForced(

View File

@@ -32,16 +32,36 @@ import com.google.common.collect.ImmutableSet;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.Registrar.State;
import google.registry.model.registrar.Registrar.Type;
import google.registry.persistence.transaction.JpaTestRules;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageRule;
import google.registry.schema.registrar.RegistrarDao;
import google.registry.testing.AppEngineRule;
import google.registry.testing.FakeClock;
import google.registry.util.CidrAddressBlock;
import java.util.Optional;
import org.joda.money.CurrencyUnit;
import org.joda.time.DateTime;
import org.junit.Rule;
import org.junit.Test;
/** Unit tests for {@link UpdateRegistrarCommand}. */
public class UpdateRegistrarCommandTest extends CommandTestCase<UpdateRegistrarCommand> {
private final FakeClock fakeClock = new FakeClock();
@Rule
public final JpaIntegrationWithCoverageRule jpaRule =
new JpaTestRules.Builder().withClock(fakeClock).buildIntegrationWithCoverageRule();
@Test
public void testSuccess_alsoUpdateInCloudSql() throws Exception {
assertThat(loadRegistrar("NewRegistrar").verifyPassword("some_password")).isFalse();
RegistrarDao.saveNew(loadRegistrar("NewRegistrar"));
runCommand("--password=some_password", "--force", "NewRegistrar");
assertThat(loadRegistrar("NewRegistrar").verifyPassword("some_password")).isTrue();
assertThat(RegistrarDao.load("NewRegistrar").get().verifyPassword("some_password")).isTrue();
}
@Test
public void testSuccess_password() throws Exception {
assertThat(loadRegistrar("NewRegistrar").verifyPassword("some_password")).isFalse();

View File

@@ -132,6 +132,7 @@ public final class RegistryLockPostActionTest {
createAction(
AuthResult.create(AuthLevel.USER, UserAuthInfo.create(userWithoutPermission, true)));
Map<String, ?> response = action.handleJsonRequest(unlockRequest());
// we should still email the admin user's email address
assertSuccess(response, "unlock", "johndoe@theregistrar.com");
}
@@ -171,7 +172,7 @@ public final class RegistryLockPostActionTest {
@Test
public void testSuccess_adminUser() throws Exception {
// Admin user should be able to lock/unlock regardless
// Admin user should be able to lock/unlock regardless -- and we use the admin user's email
action =
createAction(
AuthResult.create(AuthLevel.USER, UserAuthInfo.create(userWithoutPermission, true)));
@@ -179,6 +180,20 @@ public final class RegistryLockPostActionTest {
assertSuccess(response, "lock", "johndoe@theregistrar.com");
}
@Test
public void testSuccess_adminUser_doesNotRequirePassword() throws Exception {
action =
createAction(
AuthResult.create(AuthLevel.USER, UserAuthInfo.create(userWithoutPermission, true)));
Map<String, ?> response =
action.handleJsonRequest(
ImmutableMap.of(
"clientId", "TheRegistrar",
"fullyQualifiedDomainName", "example.tld",
"isLock", true));
assertSuccess(response, "lock", "johndoe@theregistrar.com");
}
@Test
public void testFailure_noInput() {
Map<String, ?> response = action.handleJsonRequest(null);
@@ -231,20 +246,21 @@ public final class RegistryLockPostActionTest {
ImmutableMap.of(
"clientId", "TheRegistrar",
"fullyQualifiedDomainName", "example.tld",
"isLock", true,
"pocId", "Marla.Singer@crr.com"));
"isLock", true));
assertFailureWithMessage(response, "Missing key for password");
}
@Test
public void testFailure_notEnabledForRegistrarContact() {
action =
createAction(
AuthResult.create(AuthLevel.USER, UserAuthInfo.create(userWithoutPermission, false)));
Map<String, ?> response =
action.handleJsonRequest(
ImmutableMap.of(
"clientId", "TheRegistrar",
"fullyQualifiedDomainName", "example.tld",
"isLock", true,
"pocId", "johndoe@theregistrar.com",
"password", "hi"));
assertFailureWithMessage(response, "Incorrect registry lock password for contact");
}
@@ -257,7 +273,6 @@ public final class RegistryLockPostActionTest {
"clientId", "TheRegistrar",
"fullyQualifiedDomainName", "example.tld",
"isLock", true,
"pocId", "Marla.Singer@crr.com",
"password", "badPassword"));
assertFailureWithMessage(response, "Incorrect registry lock password for contact");
}
@@ -270,36 +285,10 @@ public final class RegistryLockPostActionTest {
"clientId", "TheRegistrar",
"fullyQualifiedDomainName", "bad.tld",
"isLock", true,
"pocId", "Marla.Singer@crr.com",
"password", "hi"));
assertFailureWithMessage(response, "Unknown domain bad.tld");
}
@Test
public void testFailure_noPocId() {
Map<String, ?> response =
action.handleJsonRequest(
ImmutableMap.of(
"clientId", "TheRegistrar",
"fullyQualifiedDomainName", "bad.tld",
"isLock", true,
"password", "hi"));
assertFailureWithMessage(response, "Missing key for pocId");
}
@Test
public void testFailure_invalidPocId() {
Map<String, ?> response =
action.handleJsonRequest(
ImmutableMap.of(
"clientId", "TheRegistrar",
"fullyQualifiedDomainName", "bad.tld",
"isLock", true,
"pocId", "someotherpoc@crr.com",
"password", "hi"));
assertFailureWithMessage(response, "Unknown registrar POC ID someotherpoc@crr.com");
}
@Test
public void testSuccess_previousLockUnlocked() throws Exception {
RegistryLockDao.save(
@@ -357,7 +346,6 @@ public final class RegistryLockPostActionTest {
"isLock", lock,
"clientId", "TheRegistrar",
"fullyQualifiedDomainName", "example.tld",
"pocId", "Marla.Singer@crr.com",
"password", "hi");
}

View File

@@ -0,0 +1,22 @@
-- Copyright 2020 The Nomulus Authors. All Rights Reserved.
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
create table "Lock" (
resource_name text not null,
tld text not null,
acquired_time timestamptz not null,
expiration_time timestamptz not null,
request_log_id text not null,
primary key (resource_name, tld)
);

View File

@@ -77,6 +77,15 @@
primary key (id)
);
create table "Lock" (
resource_name text not null,
tld text not null,
acquired_time timestamptz not null,
expiration_time timestamptz not null,
request_log_id text not null,
primary key (resource_name, tld)
);
create table "PremiumEntry" (
revision_id int8 not null,
domain_label text not null,

View File

@@ -116,6 +116,19 @@ CREATE TABLE public."Domain" (
);
--
-- Name: Lock; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public."Lock" (
resource_name text NOT NULL,
tld text NOT NULL,
acquired_time timestamp with time zone NOT NULL,
expiration_time timestamp with time zone NOT NULL,
request_log_id text NOT NULL
);
--
-- Name: PremiumEntry; Type: TABLE; Schema: public; Owner: -
--
@@ -374,6 +387,14 @@ ALTER TABLE ONLY public."Domain"
ADD CONSTRAINT "Domain_pkey" PRIMARY KEY (repo_id);
--
-- Name: Lock Lock_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."Lock"
ADD CONSTRAINT "Lock_pkey" PRIMARY KEY (resource_name, tld);
--
-- Name: PremiumEntry PremiumEntry_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

View File

@@ -27,13 +27,14 @@ fi
environment="$1"
dest="$2"
gcs_prefix="storage.googleapis.com/domain-registry-maven-repository"
gcs_prefix="gcs://domain-registry-maven-repository"
# Let Gradle put its caches (dependency cache and build cache) in the source
# tree. This allows sharing of the caches between steps in a Cloud Build
# task. (See ./cloudbuild-nomulus.yaml, which calls this script in several
# steps). If left at their default location, the caches will be lost after
# each step.
# Note: must be consistent with value in ./cloudbuild-nomulus.yaml
export GRADLE_USER_HOME="./cloudbuild-caches"
if [ "${environment}" == tool ]
@@ -41,8 +42,8 @@ then
mkdir -p "${dest}"
./gradlew clean :core:buildToolImage \
-PmavenUrl=https://"${gcs_prefix}"/maven \
-PpluginsUrl=https://"${gcs_prefix}"/plugins
-PmavenUrl="${gcs_prefix}"/maven \
-PpluginsUrl="${gcs_prefix}"/plugins
mv core/build/libs/nomulus.jar "${dest}"
else
@@ -50,8 +51,8 @@ else
mkdir -p "${dest}"
./gradlew clean stage -Penvironment="${environment}" \
-PmavenUrl=https://"${gcs_prefix}"/maven \
-PpluginsUrl=https://"${gcs_prefix}"/plugins
-PmavenUrl="${gcs_prefix}"/maven \
-PpluginsUrl="${gcs_prefix}"/plugins
for service in default pubapi backend tools
do

View File

@@ -21,7 +21,15 @@ steps:
args: ['mkdir', 'nomulus']
# Run tests
- name: 'gcr.io/${PROJECT_ID}/builder:latest'
args: ['./gradlew', 'test', '-PskipDockerIncompatibleTests=true']
# Set home for Gradle caches. Must be consistent with last step below
# and ./build_nomulus_for_env.sh
env: [ 'GRADLE_USER_HOME=./cloudbuild-caches' ]
args: ['./gradlew',
'test',
'-PskipDockerIncompatibleTests=true',
'-PmavenUrl=gcs://domain-registry-maven-repository/maven',
'-PpluginsUrl=gcs://domain-registry-maven-repository/plugins'
]
# Build the tool binary and image.
- name: 'gcr.io/${PROJECT_ID}/builder:latest'
args: ['release/build_nomulus_for_env.sh', 'tool', 'output']
@@ -70,20 +78,23 @@ steps:
# server/schema compatibility tests.
- name: 'gcr.io/${PROJECT_ID}/builder:latest'
entrypoint: /bin/bash
# Set home for Gradle caches. Must be consistent with second step above
# and ./build_nomulus_for_env.sh
env: [ 'GRADLE_USER_HOME=./cloudbuild-caches' ]
args:
- -c
- |
set -e
./gradlew \
:db:publish \
-PmavenUrl=https://storage.googleapis.com/domain-registry-maven-repository/maven \
-PpluginsUrl=https://storage.googleapis.com/domain-registry-maven-repository/plugins \
-PmavenUrl=gcs://domain-registry-maven-repository/maven \
-PpluginsUrl=gcs://domain-registry-maven-repository/plugins \
-Ppublish_repo=gcs://${PROJECT_ID}-deployed-tags/maven \
-Pschema_version=${TAG_NAME}
./gradlew \
:core:publish \
-PmavenUrl=https://storage.googleapis.com/domain-registry-maven-repository/maven \
-PpluginsUrl=https://storage.googleapis.com/domain-registry-maven-repository/plugins \
-PmavenUrl=gcs://domain-registry-maven-repository/maven \
-PpluginsUrl=gcs://domain-registry-maven-repository/plugins \
-Ppublish_repo=gcs://${PROJECT_ID}-deployed-tags/maven \
-Pnomulus_version=${TAG_NAME}
# Upload schema jar for use by schema deployment.

View File

@@ -19,8 +19,8 @@ steps:
- ./gradlew
- :proxy:test
- :proxy:buildProxyImage
- -PmavenUrl=https://storage.googleapis.com/domain-registry-maven-repository/maven
- -PpluginsUrl=https://storage.googleapis.com/domain-registry-maven-repository/plugins
- -PmavenUrl=gcs://domain-registry-maven-repository/maven
- -PpluginsUrl=gcs://domain-registry-maven-repository/plugins
# Tag and push the image. We can't let Cloud Build's default processing do that for us
# because we need to push the image before we can sign it in the following step.
- name: 'gcr.io/${PROJECT_ID}/builder:latest'