1
0
mirror of https://github.com/google/nomulus synced 2026-07-17 21:42:24 +00:00

Compare commits

...

7 Commits

Author SHA1 Message Date
Shicong Huang fa9400ebc5 Set postgres package back to runtime dependency (#522) 2020-03-20 15:43:30 -04:00
gbrodman 519a85af85 Add a registryLockEmailAddress field to RegistrarConctact objects (#523)
* Add a registryLockEmailAddress field to RegistrarConctact objects

Because we need to manage the login email, it should be on an account
that we manage. However, for registry lock, we would want to send the
verification emails to a separate email address that the user can use.

As a result, we will use a second field for a user-accessible registry
lock email address. This must be set on the contact when enabling
registry lock for this contact.

* Responses to CR

* derp
2020-03-20 14:12:00 -04:00
sarahcaseybot b2df127dc4 Add lock dual read (#517)
* Add lock dual read

* small changes
2020-03-20 14:11:00 -04:00
gbrodman b21042bda9 Fix the test server (#521)
* Fix the test server

This rule isn't necessary any more since we merged the SQL-starting rule
into the AppEngineRule logic. Furthermore, it actually causes the test
server to crash because we try to drop-and-create the DB twice, the
second time while the first instance is still connected.
2020-03-19 11:05:51 -04:00
Lai Jiang 36378f6b10 Upgrade to Gradle 6.2.2 (#518) 2020-03-18 21:38:37 -04:00
Shicong Huang d01f1f7604 Make jpaTm for nomulus tool use local credential (#515)
* Make jpaTm for nomulus tool use local credential

* Remove unused methods in RegistryToolEnvironment

* Fix order of annotations

* Remove unused method in PersistenceComponent

* Move the creation of credential to the module

* Move creadential creation to AuthModule

* Add a TODO
2020-03-17 20:16:42 -04:00
gbrodman e9610636e4 Add a relockDuration to the RegistryLock SQL object (#514)
* Add a relockDuration to the RegistryLock SQL object

This is the length of time after an unlock that we will re-lock the
domain in question.

* Sort by domain name for stability

Note: this is likely not the best solution for the UI but we can iterate
on this.

* Add nullable

* Add a converter for Duration
2020-03-16 17:44:25 -04:00
58 changed files with 731 additions and 297 deletions
+2 -1
View File
@@ -197,7 +197,8 @@ dependencies {
compile deps['com.google.appengine:appengine-remote-api']
compile deps['com.google.auth:google-auth-library-credentials']
compile deps['com.google.auth:google-auth-library-oauth2-http']
compile deps['com.google.cloud.sql:postgres-socket-factory']
compile deps['com.google.cloud.sql:jdbc-socket-factory-core']
runtimeOnly deps['com.google.cloud.sql:postgres-socket-factory']
compile deps['com.google.code.gson:gson']
compile deps['com.google.auto.value:auto-value-annotations']
compile deps['com.google.code.findbugs:jsr305']
@@ -75,7 +75,6 @@ com.google.cloud.bigdataoss:util:1.9.16
com.google.cloud.bigtable:bigtable-client-core:1.8.0
com.google.cloud.datastore:datastore-v1-proto-client:1.6.0
com.google.cloud.sql:jdbc-socket-factory-core:1.0.12
com.google.cloud.sql:postgres-socket-factory:1.0.12
com.google.cloud:google-cloud-bigquerystorage:0.79.0-alpha
com.google.cloud:google-cloud-bigtable-admin:0.73.0-alpha
com.google.cloud:google-cloud-bigtable:0.73.0-alpha
@@ -75,7 +75,6 @@ com.google.cloud.bigdataoss:util:1.9.16
com.google.cloud.bigtable:bigtable-client-core:1.8.0
com.google.cloud.datastore:datastore-v1-proto-client:1.6.0
com.google.cloud.sql:jdbc-socket-factory-core:1.0.12
com.google.cloud.sql:postgres-socket-factory:1.0.12
com.google.cloud:google-cloud-bigquerystorage:0.79.0-alpha
com.google.cloud:google-cloud-bigtable-admin:0.73.0-alpha
com.google.cloud:google-cloud-bigtable:0.73.0-alpha
@@ -75,7 +75,6 @@ com.google.cloud.bigdataoss:util:1.9.16
com.google.cloud.bigtable:bigtable-client-core:1.8.0
com.google.cloud.datastore:datastore-v1-proto-client:1.6.0
com.google.cloud.sql:jdbc-socket-factory-core:1.0.12
com.google.cloud.sql:postgres-socket-factory:1.0.12
com.google.cloud:google-cloud-bigquerystorage:0.79.0-alpha
com.google.cloud:google-cloud-bigtable-admin:0.73.0-alpha
com.google.cloud:google-cloud-bigtable:0.73.0-alpha
@@ -75,7 +75,6 @@ com.google.cloud.bigdataoss:util:1.9.16
com.google.cloud.bigtable:bigtable-client-core:1.8.0
com.google.cloud.datastore:datastore-v1-proto-client:1.6.0
com.google.cloud.sql:jdbc-socket-factory-core:1.0.12
com.google.cloud.sql:postgres-socket-factory:1.0.12
com.google.cloud:google-cloud-bigquerystorage:0.79.0-alpha
com.google.cloud:google-cloud-bigtable-admin:0.73.0-alpha
com.google.cloud:google-cloud-bigtable:0.73.0-alpha
@@ -75,7 +75,6 @@ com.google.cloud.bigdataoss:util:1.9.16
com.google.cloud.bigtable:bigtable-client-core:1.8.0
com.google.cloud.datastore:datastore-v1-proto-client:1.6.0
com.google.cloud.sql:jdbc-socket-factory-core:1.0.12
com.google.cloud.sql:postgres-socket-factory:1.0.12
com.google.cloud:google-cloud-bigquerystorage:0.79.0-alpha
com.google.cloud:google-cloud-bigtable-admin:0.73.0-alpha
com.google.cloud:google-cloud-bigtable:0.73.0-alpha
@@ -75,7 +75,6 @@ com.google.cloud.bigdataoss:util:1.9.16
com.google.cloud.bigtable:bigtable-client-core:1.8.0
com.google.cloud.datastore:datastore-v1-proto-client:1.6.0
com.google.cloud.sql:jdbc-socket-factory-core:1.0.12
com.google.cloud.sql:postgres-socket-factory:1.0.12
com.google.cloud:google-cloud-bigquerystorage:0.79.0-alpha
com.google.cloud:google-cloud-bigtable-admin:0.73.0-alpha
com.google.cloud:google-cloud-bigtable:0.73.0-alpha
@@ -75,7 +75,6 @@ com.google.cloud.bigdataoss:util:1.9.16
com.google.cloud.bigtable:bigtable-client-core:1.8.0
com.google.cloud.datastore:datastore-v1-proto-client:1.6.0
com.google.cloud.sql:jdbc-socket-factory-core:1.0.12
com.google.cloud.sql:postgres-socket-factory:1.0.12
com.google.cloud:google-cloud-bigquerystorage:0.79.0-alpha
com.google.cloud:google-cloud-bigtable-admin:0.73.0-alpha
com.google.cloud:google-cloud-bigtable:0.73.0-alpha
@@ -76,7 +76,6 @@ com.google.cloud.bigdataoss:util:1.9.16
com.google.cloud.bigtable:bigtable-client-core:1.8.0
com.google.cloud.datastore:datastore-v1-proto-client:1.6.0
com.google.cloud.sql:jdbc-socket-factory-core:1.0.12
com.google.cloud.sql:postgres-socket-factory:1.0.12
com.google.cloud:google-cloud-bigquerystorage:0.79.0-alpha
com.google.cloud:google-cloud-bigtable-admin:0.73.0-alpha
com.google.cloud:google-cloud-bigtable:0.73.0-alpha
@@ -76,7 +76,6 @@ com.google.cloud.bigdataoss:util:1.9.16
com.google.cloud.bigtable:bigtable-client-core:1.8.0
com.google.cloud.datastore:datastore-v1-proto-client:1.6.0
com.google.cloud.sql:jdbc-socket-factory-core:1.0.12
com.google.cloud.sql:postgres-socket-factory:1.0.12
com.google.cloud:google-cloud-bigquerystorage:0.79.0-alpha
com.google.cloud:google-cloud-bigtable-admin:0.73.0-alpha
com.google.cloud:google-cloud-bigtable:0.73.0-alpha
@@ -44,7 +44,9 @@ import google.registry.model.Jsonifiable;
import google.registry.model.annotations.ReportedOn;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nullable;
import javax.persistence.Column;
import javax.persistence.Table;
import javax.persistence.Transient;
@@ -112,6 +114,9 @@ public class RegistrarContact extends ImmutableObject implements Jsonifiable {
@Column(nullable = false)
String emailAddress;
/** External email address of this contact used for registry lock confirmations. */
String registryLockEmailAddress;
/** The voice number of the contact. */
String phoneNumber;
@@ -212,6 +217,10 @@ public class RegistrarContact extends ImmutableObject implements Jsonifiable {
return emailAddress;
}
public Optional<String> getRegistryLockEmailAddress() {
return Optional.ofNullable(registryLockEmailAddress);
}
public String getPhoneNumber() {
return phoneNumber;
}
@@ -318,6 +327,7 @@ public class RegistrarContact extends ImmutableObject implements Jsonifiable {
return new JsonMapBuilder()
.put("name", name)
.put("emailAddress", emailAddress)
.put("registryLockEmailAddress", registryLockEmailAddress)
.put("phoneNumber", phoneNumber)
.put("faxNumber", faxNumber)
.put("types", getTypes().stream().map(Object::toString).collect(joining(",")))
@@ -352,6 +362,14 @@ public class RegistrarContact extends ImmutableObject implements Jsonifiable {
public RegistrarContact build() {
checkNotNull(getInstance().parent, "Registrar parent cannot be null");
checkValidEmail(getInstance().emailAddress);
// Check allowedToSetRegistryLockPassword here because if we want to allow the user to set
// a registry lock password, we must also set up the correct registry lock email concurrently
// or beforehand.
if (getInstance().allowedToSetRegistryLockPassword) {
checkArgument(
!isNullOrEmpty(getInstance().registryLockEmailAddress),
"Registry lock email must not be null if allowing registry lock access");
}
return cloneEmptyToNull(super.build());
}
@@ -365,6 +383,11 @@ public class RegistrarContact extends ImmutableObject implements Jsonifiable {
return this;
}
public Builder setRegistryLockEmailAddress(@Nullable String registryLockEmailAddress) {
getInstance().registryLockEmailAddress = registryLockEmailAddress;
return this;
}
public Builder setPhoneNumber(String phoneNumber) {
getInstance().phoneNumber = phoneNumber;
return this;
@@ -52,8 +52,10 @@ public final class RegistryLockDao {
jpaTm()
.getEntityManager()
.createQuery(
"SELECT lock FROM RegistryLock lock WHERE lock.registrarId = :registrarId"
+ " AND lock.unlockCompletionTimestamp IS NULL",
"SELECT lock FROM RegistryLock lock"
+ " WHERE lock.registrarId = :registrarId"
+ " AND lock.unlockCompletionTimestamp IS NULL"
+ " ORDER BY lock.domainName ASC",
RegistryLock.class)
.setParameter("registrarId", registrarId)
.getResultList());
@@ -78,6 +78,18 @@ public class Lock extends ImmutableObject implements Serializable {
/** When the lock can be considered implicitly released. */
DateTime expirationTime;
public String getRequestLogId() {
return requestLogId;
}
public DateTime getExpirationTime() {
return expirationTime;
}
public DateTime getAcquiredTime() {
return acquiredTime;
}
/** When was the lock acquired. Used for logging. */
DateTime acquiredTime;
@@ -89,10 +101,10 @@ public class Lock extends ImmutableObject implements Serializable {
String tld;
/**
* Create a new {@link Lock} for the given resource name in the specified tld (which can be
* null for cross-tld locks).
* Create a new {@link Lock} for the given resource name in the specified tld (which can be null
* for cross-tld locks).
*/
private static Lock create(
public static Lock create(
String resourceName,
@Nullable String tld,
String requestLogId,
@@ -185,6 +197,18 @@ public class Lock extends ImmutableObject implements Serializable {
// Checking if an unexpired lock still exists - if so, the lock can't be acquired.
Lock lock = ofy().load().type(Lock.class).id(lockId).now();
try {
jpaTm()
.transact(
() -> {
Optional<google.registry.schema.server.Lock> cloudSqlLockOptional =
LockDao.load(resourceName, tld);
LockDao.compare(Optional.ofNullable(lock), cloudSqlLockOptional);
});
} catch (Exception e) {
logger.atSevere().withCause(e).log(
"Issue loading and comparing lock from Cloud SQL");
}
if (lock != null) {
logger.atInfo().log(
"Loaded existing lock: %s for request: %s", lock.lockId, lock.requestLogId);
@@ -221,12 +245,7 @@ public class Lock extends ImmutableObject implements Serializable {
requestStatusChecker.getLogId(),
now,
leaseLength);
// cloudSqlLock should not already exist in Cloud SQL, but call delete
// just in case
// TODO: Remove this delete once dual read is added
LockDao.delete(
resourceName, Optional.ofNullable(tld).orElse("GLOBAL"));
LockDao.saveNew(cloudSqlLock);
LockDao.save(cloudSqlLock);
});
} catch (Exception e) {
logger.atSevere().withCause(e).log(
@@ -251,6 +270,18 @@ public class Lock extends ImmutableObject implements Serializable {
// this can happen if release() is called around the expiration time and the lock
// expires underneath us.
Lock loadedLock = ofy().load().type(Lock.class).id(lockId).now();
try {
jpaTm()
.transact(
() -> {
Optional<google.registry.schema.server.Lock> cloudSqlLockOptional =
LockDao.load(resourceName, tld);
LockDao.compare(Optional.ofNullable(loadedLock), cloudSqlLockOptional);
});
} catch (Exception e) {
logger.atSevere().withCause(e).log(
"Issue loading and comparing lock from Cloud SQL");
}
if (Lock.this.equals(loadedLock)) {
// Use noBackupOfy() so that we don't create a commit log entry for deleting the
// lock.
@@ -19,7 +19,6 @@ import google.registry.config.CredentialModule;
import google.registry.config.RegistryConfig.ConfigModule;
import google.registry.keyring.kms.KmsModule;
import google.registry.persistence.PersistenceModule.AppEngineJpaTm;
import google.registry.persistence.PersistenceModule.NomulusToolJpaTm;
import google.registry.persistence.transaction.JpaTransactionManager;
import google.registry.util.UtilsModule;
import javax.inject.Singleton;
@@ -39,7 +38,4 @@ public interface PersistenceComponent {
@AppEngineJpaTm
JpaTransactionManager appEngineJpaTransactionManager();
@NomulusToolJpaTm
JpaTransactionManager nomulusToolJpaTransactionManager();
}
@@ -22,6 +22,7 @@ import static google.registry.config.RegistryConfig.getHibernateHikariMaximumPoo
import static google.registry.config.RegistryConfig.getHibernateHikariMinimumIdle;
import static google.registry.config.RegistryConfig.getHibernateLogSqlQueries;
import com.google.api.client.auth.oauth2.Credential;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
@@ -29,8 +30,10 @@ import dagger.Module;
import dagger.Provides;
import google.registry.config.RegistryConfig.Config;
import google.registry.keyring.kms.KmsKeyring;
import google.registry.persistence.transaction.CloudSqlCredentialSupplier;
import google.registry.persistence.transaction.JpaTransactionManager;
import google.registry.persistence.transaction.JpaTransactionManagerImpl;
import google.registry.tools.AuthModule.CloudSqlClientCredential;
import google.registry.util.Clock;
import java.lang.annotation.Documented;
import java.util.HashMap;
@@ -118,7 +121,9 @@ public class PersistenceModule {
@Config("toolsCloudSqlUsername") String username,
KmsKeyring kmsKeyring,
@PartialCloudSqlConfigs ImmutableMap<String, String> cloudSqlConfigs,
@CloudSqlClientCredential Credential credential,
Clock clock) {
CloudSqlCredentialSupplier.setupCredentialSupplier(credential);
HashMap<String, String> overrides = Maps.newHashMap(cloudSqlConfigs);
overrides.put(Environment.USER, username);
overrides.put(Environment.PASS, kmsKeyring.getToolsCloudSqlPassword());
@@ -158,7 +163,7 @@ public class PersistenceModule {
/** Dagger qualifier for {@link JpaTransactionManager} used for Nomulus tool. */
@Qualifier
@Documented
@interface NomulusToolJpaTm {}
public @interface NomulusToolJpaTm {}
/** Dagger qualifier for the partial Cloud SQL configs. */
@Qualifier
@@ -0,0 +1,37 @@
// 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.converter;
import javax.annotation.Nullable;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import org.joda.time.Duration;
/** JPA converter to for storing/retrieving {@link org.joda.time.DateTime} objects. */
@Converter(autoApply = true)
public class DurationConverter implements AttributeConverter<Duration, Long> {
@Override
@Nullable
public Long convertToDatabaseColumn(@Nullable Duration duration) {
return duration == null ? null : duration.getMillis();
}
@Override
@Nullable
public Duration convertToEntityAttribute(@Nullable Long dbData) {
return dbData == null ? null : new Duration(dbData);
}
}
@@ -0,0 +1,35 @@
// 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.transaction;
import com.google.api.client.auth.oauth2.Credential;
import com.google.cloud.sql.CredentialFactory;
/** Supplier class to provide {@link Credential} for Cloud SQL library. */
public class CloudSqlCredentialSupplier implements CredentialFactory {
private static Credential credential;
/** Initialize the supplier with given credential json and scopes. */
public static void setupCredentialSupplier(Credential credential) {
System.setProperty(
CredentialFactory.CREDENTIAL_FACTORY_PROPERTY, CloudSqlCredentialSupplier.class.getName());
CloudSqlCredentialSupplier.credential = credential;
}
@Override
public Credential create() {
return credential;
}
}
@@ -16,11 +16,9 @@ package google.registry.persistence.transaction;
import com.google.appengine.api.utils.SystemProperty;
import com.google.appengine.api.utils.SystemProperty.Environment.Value;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Suppliers;
import google.registry.model.ofy.DatastoreTransactionManager;
import google.registry.persistence.DaggerPersistenceComponent;
import google.registry.tools.RegistryToolEnvironment;
import google.registry.util.NonFinalForTesting;
import java.util.function.Supplier;
@@ -38,11 +36,10 @@ public class TransactionManagerFactory {
private TransactionManagerFactory() {}
private static JpaTransactionManager createJpaTransactionManager() {
// If we are running a nomulus command, jpaTm will be injected in RegistryCli.java
// by calling setJpaTm().
if (isInAppEngine()) {
return DaggerPersistenceComponent.create().appEngineJpaTransactionManager();
} else if (RegistryToolEnvironment.isInRegistryTool()
&& RegistryToolEnvironment.isJpaTmEnabled()) {
return DaggerPersistenceComponent.create().nomulusToolJpaTransactionManager();
} else {
return DummyJpaTransactionManager.create();
}
@@ -78,8 +75,8 @@ public class TransactionManagerFactory {
return jpaTm.get();
}
@VisibleForTesting
static void setJpaTmForTesting(JpaTransactionManager newJpaTm) {
/** Sets the return of {@link #jpaTm()} to the given instance of {@link JpaTransactionManager}. */
public static void setJpaTm(JpaTransactionManager newJpaTm) {
jpaTm = Suppliers.ofInstance(newJpaTm);
}
}
@@ -26,6 +26,7 @@ import google.registry.model.UpdateAutoTimestamp;
import google.registry.util.DateTimeUtils;
import java.time.ZonedDateTime;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
@@ -37,6 +38,7 @@ import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import org.joda.time.DateTime;
import org.joda.time.Duration;
/**
* Represents a registry lock/unlock object, meaning that the domain is locked on the registry
@@ -131,6 +133,9 @@ public final class RegistryLock extends ImmutableObject implements Buildable {
@JoinColumn(name = "relockRevisionId", referencedColumnName = "revisionId")
private RegistryLock relock;
/** The duration after which we will re-lock this domain after it is unlocked. */
private Duration relockDuration;
/** Time that this entity was last updated. */
private UpdateAutoTimestamp lastUpdateTimestamp;
@@ -197,6 +202,11 @@ public final class RegistryLock extends ImmutableObject implements Buildable {
return relock;
}
/** The duration after which we will re-lock this domain after it is unlocked. */
public Optional<Duration> getRelockDuration() {
return Optional.ofNullable(relockDuration);
}
public boolean isLocked() {
return lockCompletionTimestamp != null && unlockCompletionTimestamp == null;
}
@@ -289,5 +299,10 @@ public final class RegistryLock extends ImmutableObject implements Buildable {
getInstance().relock = relock;
return this;
}
public Builder setRelockDuration(@Nullable Duration relockDuration) {
getInstance().relockDuration = relockDuration;
return this;
}
}
}
@@ -18,18 +18,22 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
import static google.registry.schema.server.Lock.GLOBAL;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.google.common.flogger.FluentLogger;
import google.registry.schema.server.Lock.LockId;
import google.registry.util.DateTimeUtils;
import java.util.Optional;
/** Data access object class for {@link Lock}. */
public class LockDao {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
/** Saves the {@link Lock} object to Cloud SQL. */
public static void saveNew(Lock lock) {
public static void save(Lock lock) {
jpaTm()
.transact(
() -> {
jpaTm().getEntityManager().persist(lock);
jpaTm().getEntityManager().merge(lock);
});
}
@@ -76,4 +80,57 @@ public class LockDao {
public static void delete(String resourceName) {
delete(resourceName, GLOBAL);
}
/**
* Compares a {@link google.registry.model.server.Lock} object with a {@link Lock} object, logging
* a warning if there are any differences.
*/
public static void compare(
Optional<google.registry.model.server.Lock> datastoreLockOptional,
Optional<Lock> cloudSqlLockOptional) {
if (!datastoreLockOptional.isPresent()) {
cloudSqlLockOptional.ifPresent(
value ->
logger.atWarning().log(
String.format(
"Cloud SQL lock for %s with tld %s should be null",
value.resourceName, value.tld)));
return;
}
google.registry.schema.server.Lock cloudSqlLock;
google.registry.model.server.Lock datastoreLock = datastoreLockOptional.get();
if (cloudSqlLockOptional.isPresent()) {
cloudSqlLock = cloudSqlLockOptional.get();
if (!datastoreLock.getRequestLogId().equals(cloudSqlLock.requestLogId)) {
logger.atWarning().log(
String.format(
"Datastore lock requestLogId of %s does not equal Cloud SQL lock requestLogId of"
+ " %s",
datastoreLock.getRequestLogId(), cloudSqlLock.requestLogId));
}
if (!datastoreLock
.getAcquiredTime()
.equals(DateTimeUtils.toJodaDateTime(cloudSqlLock.acquiredTime))) {
logger.atWarning().log(
String.format(
"Datastore lock acquiredTime of %s does not equal Cloud SQL lock acquiredTime of"
+ " %s",
datastoreLock.getAcquiredTime(),
DateTimeUtils.toJodaDateTime(cloudSqlLock.acquiredTime)));
}
if (!datastoreLock
.getExpirationTime()
.equals(DateTimeUtils.toJodaDateTime(cloudSqlLock.expirationTime))) {
logger.atWarning().log(
String.format(
"Datastore lock expirationTime of %s does not equal Cloud SQL lock expirationTime"
+ " of %s",
datastoreLock.getExpirationTime(),
DateTimeUtils.toJodaDateTime(cloudSqlLock.expirationTime)));
}
} else {
logger.atWarning().log(
String.format("Datastore lock: %s was not found in Cloud SQL", datastoreLock));
}
}
}
@@ -20,6 +20,7 @@ import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets.Details;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.util.store.AbstractDataStoreFactory;
@@ -42,6 +43,7 @@ import google.registry.util.GoogleCredentialsBundle;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -92,6 +94,26 @@ public class AuthModule {
}
}
// TODO(b/138195359): Deprecate this credential once Cloud SQL socket library uses the new auth
// library.
@Provides
@CloudSqlClientCredential
public static Credential providesLocalCredentialForCloudSqlClient(
@LocalCredentialJson String credentialJson,
@Config("localCredentialOauthScopes") ImmutableList<String> credentialScopes) {
try {
GoogleCredential credential =
GoogleCredential.fromStream(new ByteArrayInputStream(credentialJson.getBytes(UTF_8)));
if (credential.createScopedRequired()) {
credential = credential.createScoped(credentialScopes);
}
return credential;
} catch (IOException e) {
throw new UncheckedIOException(
"Error occurred while creating a GoogleCredential for Cloud SQL client", e);
}
}
@Provides
public static GoogleAuthorizationCodeFlow provideAuthorizationCodeFlow(
JsonFactory jsonFactory,
@@ -184,6 +206,11 @@ public class AuthModule {
@Retention(RetentionPolicy.RUNTIME)
private @interface StoredCredential {}
/** Dagger qualifier for {@link Credential} used by the Cloud SQL client in the nomulus tool. */
@Qualifier
@Documented
public @interface CloudSqlClientCredential {}
/** Dagger qualifier for the credential qualifier consisting of client and scopes. */
@Qualifier
@Documented
@@ -78,11 +78,15 @@ final class RegistrarContactCommand extends MutatingCommand {
private List<String> contactTypeNames;
@Nullable
@Parameter(
names = "--email",
description = "Contact email address.")
@Parameter(names = "--email", description = "Contact email address.")
String email;
@Nullable
@Parameter(
names = "--registry_lock_email",
description = "Email address used for registry lock confirmation emails")
String registryLockEmail;
@Nullable
@Parameter(
names = "--phone",
@@ -247,6 +251,9 @@ final class RegistrarContactCommand extends MutatingCommand {
builder.setParent(registrar);
builder.setName(name);
builder.setEmailAddress(email);
if (!isNullOrEmpty(registryLockEmail)) {
builder.setRegistryLockEmailAddress(registryLockEmail);
}
if (phone != null) {
builder.setPhoneNumber(phone.orElse(null));
}
@@ -277,14 +284,14 @@ final class RegistrarContactCommand extends MutatingCommand {
private RegistrarContact updateContact(RegistrarContact contact, Registrar registrar) {
checkNotNull(registrar);
checkNotNull(email, "--email is required when --mode=UPDATE");
RegistrarContact.Builder builder = contact.asBuilder();
builder.setParent(registrar);
checkArgument(!isNullOrEmpty(email), "--email is required when --mode=UPDATE");
RegistrarContact.Builder builder =
contact.asBuilder().setEmailAddress(email).setParent(registrar);
if (!isNullOrEmpty(name)) {
builder.setName(name);
}
if (!isNullOrEmpty(email)) {
builder.setEmailAddress(email);
if (!isNullOrEmpty(registryLockEmail)) {
builder.setRegistryLockEmailAddress(registryLockEmail);
}
if (phone != null) {
builder.setPhoneNumber(phone.orElse(null));
@@ -31,6 +31,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import google.registry.config.RegistryConfig;
import google.registry.model.ofy.ObjectifyService;
import google.registry.persistence.transaction.TransactionManagerFactory;
import google.registry.tools.AuthModule.LoginRequiredException;
import google.registry.tools.params.ParameterFactory;
import java.io.ByteArrayInputStream;
@@ -237,7 +238,7 @@ final class RegistryCli implements AutoCloseable, CommandRunner {
// Enable Cloud SQL for command that needs remote API as they will very likely use
// Cloud SQL after the database migration. Note that the DB password is stored in Datastore
// and it is already initialized above.
RegistryToolEnvironment.enableJpaTm();
TransactionManagerFactory.setJpaTm(component.nomulusToolJpaTransactionManager());
}
command.run();
@@ -27,6 +27,9 @@ import google.registry.keyring.KeyringModule;
import google.registry.keyring.api.DummyKeyringModule;
import google.registry.keyring.api.KeyModule;
import google.registry.keyring.kms.KmsModule;
import google.registry.persistence.PersistenceModule;
import google.registry.persistence.PersistenceModule.NomulusToolJpaTm;
import google.registry.persistence.transaction.JpaTransactionManager;
import google.registry.rde.RdeModule;
import google.registry.request.Modules.DatastoreServiceModule;
import google.registry.request.Modules.Jackson2Module;
@@ -63,6 +66,7 @@ import javax.inject.Singleton;
KeyringModule.class,
KmsModule.class,
LocalCredentialModule.class,
PersistenceModule.class,
RdeModule.class,
RequestFactoryModule.class,
URLFetchServiceModule.class,
@@ -70,7 +74,7 @@ import javax.inject.Singleton;
UserServiceModule.class,
UtilsModule.class,
VoidDnsWriterModule.class,
WhoisModule.class,
WhoisModule.class
})
interface RegistryToolComponent {
void inject(AckPollMessagesCommand command);
@@ -117,6 +121,9 @@ interface RegistryToolComponent {
@LocalCredentialJson
String googleCredentialJson();
@NomulusToolJpaTm
JpaTransactionManager nomulusToolJpaTransactionManager();
@Component.Builder
interface Builder {
@BindsInstance
@@ -23,7 +23,6 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import google.registry.config.RegistryEnvironment;
import google.registry.config.SystemPropertySetter;
import google.registry.persistence.transaction.TransactionManagerFactory;
/** Enum of production environments, used for the {@code --environment} flag. */
public enum RegistryToolEnvironment {
@@ -40,7 +39,6 @@ public enum RegistryToolEnvironment {
private static final ImmutableList<String> FLAGS = ImmutableList.of("-e", "--environment");
private static RegistryToolEnvironment instance;
private static boolean isJpaTmEnabled = false;
private final RegistryEnvironment actualEnvironment;
private final ImmutableMap<String, String> extraProperties;
@@ -100,26 +98,6 @@ public enum RegistryToolEnvironment {
}
}
/** Returns true if the RegistryToolEnvironment is set up. */
public static boolean isInRegistryTool() {
return instance != null;
}
/**
* Sets the flag to indicate that the running command needs JpaTransactionManager to be enabled.
*/
public static void enableJpaTm() {
isJpaTmEnabled = true;
}
/**
* Returns true if the JpaTransactionManager is enabled. Note that JpaTm is actually enabled in
* {@link TransactionManagerFactory} by reading this flag.
*/
public static boolean isJpaTmEnabled() {
return isJpaTmEnabled;
}
/** Extracts value from command-line arguments associated with any {@code flags}. */
private static String getFlagValue(String[] args, Iterable<String> flags) {
for (String flag : flags) {
@@ -184,6 +184,11 @@ public final class RegistrarFormFields {
.required()
.build();
public static final FormField<String, String> REGISTRY_LOCK_EMAIL_ADDRESS_FIELD =
FormFields.EMAIL
.asBuilderNamed("registryLockEmailAddress")
.build();
public static final FormField<Boolean, Boolean> CONTACT_VISIBLE_IN_WHOIS_AS_ADMIN_FIELD =
FormField.named("visibleInWhoisAsAdmin", Boolean.class)
.build();
@@ -377,6 +382,8 @@ public final class RegistrarFormFields {
RegistrarContact.Builder builder, Map<String, ?> args) {
builder.setName(CONTACT_NAME_FIELD.extractUntyped(args).orElse(null));
builder.setEmailAddress(CONTACT_EMAIL_ADDRESS_FIELD.extractUntyped(args).orElse(null));
builder.setRegistryLockEmailAddress(
REGISTRY_LOCK_EMAIL_ADDRESS_FIELD.extractUntyped(args).orElse(null));
builder.setVisibleInWhoisAsAdmin(
CONTACT_VISIBLE_IN_WHOIS_AS_ADMIN_FIELD.extractUntyped(args).orElse(false));
builder.setVisibleInWhoisAsTech(
@@ -157,9 +157,11 @@ public final class RegistryLockGetAction implements JsonGetAction {
Optional<RegistrarContact> contactOptional = getContactMatchingLogin(user, registrar);
boolean isRegistryLockAllowed =
isAdmin || contactOptional.map(RegistrarContact::isRegistryLockAllowed).orElse(false);
// Use the contact email if it's present, else use the login email
// Use the contact's registry lock email if it's present, else use the login email (for admins)
String relevantEmail =
contactOptional.map(RegistrarContact::getEmailAddress).orElse(user.getEmail());
contactOptional
.map(contact -> contact.getRegistryLockEmailAddress().get())
.orElse(user.getEmail());
return ImmutableMap.of(
LOCK_ENABLED_FOR_CONTACT_PARAM,
isRegistryLockAllowed,
@@ -202,7 +202,14 @@ public class RegistryLockPostAction implements Runnable, JsonActionRunner.JsonAc
checkArgument(
registrarContact.verifyRegistryLockPassword(postInput.password),
"Incorrect registry lock password for contact");
return registrarContact.getEmailAddress();
return registrarContact
.getRegistryLockEmailAddress()
.orElseThrow(
() ->
new IllegalStateException(
String.format(
"Contact %s had no registry lock email address",
registrarContact.getEmailAddress())));
}
/** Value class that represents the expected input body from the UI request. */
@@ -38,6 +38,7 @@
<class>google.registry.persistence.converter.CreateAutoTimestampConverter</class>
<class>google.registry.persistence.converter.CurrencyUnitConverter</class>
<class>google.registry.persistence.converter.DateTimeConverter</class>
<class>google.registry.persistence.converter.DurationConverter</class>
<class>google.registry.persistence.converter.RegistrarPocSetConverter</class>
<class>google.registry.persistence.converter.StatusValueSetConverter</class>
<class>google.registry.persistence.converter.StringListConverter</class>
@@ -30,6 +30,7 @@ import google.registry.schema.domain.RegistryLock;
import google.registry.testing.AppEngineRule;
import google.registry.testing.FakeClock;
import java.util.Optional;
import org.joda.time.Duration;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -88,12 +89,14 @@ public final class RegistryLockDaoTest {
.setLockCompletionTimestamp(fakeClock.nowUtc())
.setUnlockRequestTimestamp(fakeClock.nowUtc())
.setUnlockCompletionTimestamp(fakeClock.nowUtc())
.setRelockDuration(Duration.standardHours(6))
.build());
RegistryLock fromDatabase = getRegistryLockByVerificationCode(lock.getVerificationCode()).get();
assertThat(fromDatabase.getUnlockRequestTimestamp()).isEqualTo(Optional.of(fakeClock.nowUtc()));
assertThat(fromDatabase.getUnlockCompletionTimestamp())
.isEqualTo(Optional.of(fakeClock.nowUtc()));
assertThat(fromDatabase.isLocked()).isFalse();
assertThat(fromDatabase.getRelockDuration().get()).isEqualTo(Duration.standardHours(6));
}
@Test
@@ -11,6 +11,7 @@
// 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.converter;
import static com.google.common.truth.Truth.assertThat;
@@ -0,0 +1,80 @@
// 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.converter;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import google.registry.model.ImmutableObject;
import google.registry.persistence.transaction.JpaTestRules;
import google.registry.persistence.transaction.JpaTestRules.JpaUnitTestRule;
import java.math.BigInteger;
import javax.persistence.Entity;
import javax.persistence.Id;
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 DurationConverter}. */
@RunWith(JUnit4.class)
public class DurationConverterTest {
@Rule
public final JpaUnitTestRule jpaRule =
new JpaTestRules.Builder().withEntityClass(TestEntity.class).buildUnitTestRule();
private final DurationConverter converter = new DurationConverter();
@Test
public void testNulls() {
assertThat(converter.convertToDatabaseColumn(null)).isNull();
assertThat(converter.convertToEntityAttribute(null)).isNull();
}
@Test
public void testRoundTrip() {
TestEntity entity = new TestEntity(Duration.standardDays(6));
jpaTm().transact(() -> jpaTm().getEntityManager().persist(entity));
assertThat(
jpaTm()
.transact(
() ->
jpaTm()
.getEntityManager()
.createNativeQuery(
"SELECT duration FROM \"TestEntity\" WHERE name = 'id'")
.getResultList()))
.containsExactly(BigInteger.valueOf(Duration.standardDays(6).getMillis()));
TestEntity persisted =
jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "id"));
assertThat(persisted.duration).isEqualTo(Duration.standardDays(6));
}
@Entity(name = "TestEntity") // Override entity name to avoid the nested class reference.
public static class TestEntity extends ImmutableObject {
@Id String name = "id";
Duration duration;
public TestEntity() {}
TestEntity(Duration duration) {
this.duration = duration;
}
}
}
@@ -133,12 +133,12 @@ abstract class JpaTransactionManagerRule extends ExternalResource {
extraEntityClasses);
JpaTransactionManagerImpl txnManager = new JpaTransactionManagerImpl(emf, clock);
cachedTm = TransactionManagerFactory.jpaTm();
TransactionManagerFactory.setJpaTmForTesting(txnManager);
TransactionManagerFactory.setJpaTm(txnManager);
}
@Override
public void after() {
TransactionManagerFactory.setJpaTmForTesting(cachedTm);
TransactionManagerFactory.setJpaTm(cachedTm);
if (emf != null) {
emf.close();
emf = null;
@@ -15,13 +15,15 @@
package google.registry.schema.server;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import static google.registry.testing.LogsSubject.assertAboutLogs;
import com.google.common.testing.TestLogHandler;
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 java.util.logging.Level;
import java.util.logging.Logger;
import org.joda.time.Duration;
import org.junit.Rule;
import org.junit.Test;
@@ -33,6 +35,8 @@ import org.junit.runners.JUnit4;
public class LockDaoTest {
private final FakeClock fakeClock = new FakeClock();
private final TestLogHandler logHandler = new TestLogHandler();
private final Logger loggerToIntercept = Logger.getLogger(LockDao.class.getCanonicalName());
@Rule
public final JpaIntegrationWithCoverageRule jpaRule =
@@ -42,29 +46,28 @@ public class LockDaoTest {
public void save_worksSuccessfully() {
Lock lock =
Lock.create("testResource", "tld", "testLogId", fakeClock.nowUtc(), Duration.millis(2));
LockDao.saveNew(lock);
LockDao.save(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() {
public void save_succeedsWhenLockAlreadyExists() {
Lock lock =
Lock.create("testResource", "tld", "testLogId", fakeClock.nowUtc(), Duration.millis(2));
LockDao.saveNew(lock);
LockDao.save(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");
LockDao.save(lock2);
assertThat(LockDao.load("testResource", "tld").get().requestLogId).isEqualTo("testLogId2");
}
@Test
public void save_worksSuccesfullyGlobalLock() {
Lock lock =
Lock.createGlobal("testResource", "testLogId", fakeClock.nowUtc(), Duration.millis(2));
LockDao.saveNew(lock);
LockDao.save(lock);
Optional<Lock> returnedLock = LockDao.load("testResource");
assertThat(returnedLock.get().expirationTime).isEqualTo(lock.expirationTime);
assertThat(returnedLock.get().requestLogId).isEqualTo(lock.requestLogId);
@@ -74,7 +77,7 @@ public class LockDaoTest {
public void load_worksSuccessfully() {
Lock lock =
Lock.create("testResource", "tld", "testLogId", fakeClock.nowUtc(), Duration.millis(2));
LockDao.saveNew(lock);
LockDao.save(lock);
Optional<Lock> returnedLock = LockDao.load("testResource", "tld");
assertThat(returnedLock.get().expirationTime).isEqualTo(lock.expirationTime);
assertThat(returnedLock.get().requestLogId).isEqualTo(lock.requestLogId);
@@ -84,7 +87,7 @@ public class LockDaoTest {
public void load_worksSuccessfullyGlobalLock() {
Lock lock =
Lock.createGlobal("testResource", "testLogId", fakeClock.nowUtc(), Duration.millis(2));
LockDao.saveNew(lock);
LockDao.save(lock);
Optional<Lock> returnedLock = LockDao.load("testResource");
assertThat(returnedLock.get().expirationTime).isEqualTo(lock.expirationTime);
assertThat(returnedLock.get().requestLogId).isEqualTo(lock.requestLogId);
@@ -100,7 +103,7 @@ public class LockDaoTest {
public void delete_worksSuccesfully() {
Lock lock =
Lock.create("testResource", "tld", "testLogId", fakeClock.nowUtc(), Duration.millis(2));
LockDao.saveNew(lock);
LockDao.save(lock);
Optional<Lock> returnedLock = LockDao.load("testResource", "tld");
assertThat(returnedLock.get().expirationTime).isEqualTo(lock.expirationTime);
LockDao.delete("testResource", "tld");
@@ -112,7 +115,7 @@ public class LockDaoTest {
public void delete_worksSuccessfullyGlobalLock() {
Lock lock =
Lock.createGlobal("testResource", "testLogId", fakeClock.nowUtc(), Duration.millis(2));
LockDao.saveNew(lock);
LockDao.save(lock);
Optional<Lock> returnedLock = LockDao.load("testResource");
assertThat(returnedLock.get().expirationTime).isEqualTo(lock.expirationTime);
LockDao.delete("testResource");
@@ -124,4 +127,63 @@ public class LockDaoTest {
public void delete_succeedsLockDoesntExist() {
LockDao.delete("testResource");
}
@Test
public void compare_logsWarningWhenCloudSqlLockMissing() {
loggerToIntercept.addHandler(logHandler);
google.registry.model.server.Lock datastoreLock =
google.registry.model.server.Lock.create(
"resourceName", "tld", "id", fakeClock.nowUtc(), Duration.millis(2));
LockDao.compare(Optional.of(datastoreLock), Optional.empty());
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(
Level.WARNING,
String.format("Datastore lock: %s was not found in Cloud SQL", datastoreLock));
}
@Test
public void compare_logsWarningWhenCloudSqlLockExistsWhenItShouldNot() {
loggerToIntercept.addHandler(logHandler);
Lock lock =
Lock.createGlobal("testResource", "testLogId", fakeClock.nowUtc(), Duration.millis(2));
LockDao.compare(Optional.ofNullable(null), Optional.of(lock));
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(
Level.WARNING,
String.format("Cloud SQL lock for testResource with tld GLOBAL should be null"));
}
@Test
public void compare_logsWarningWhenLocksDontMatch() {
loggerToIntercept.addHandler(logHandler);
Lock cloudSqlLock =
Lock.create("testResource", "tld", "testLogId", fakeClock.nowUtc(), Duration.millis(2));
google.registry.model.server.Lock datastoreLock =
google.registry.model.server.Lock.create(
"testResource", "tld", "wrong", fakeClock.nowUtc().minusDays(1), Duration.millis(3));
LockDao.compare(Optional.of(datastoreLock), Optional.of(cloudSqlLock));
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(
Level.WARNING,
String.format(
"Datastore lock requestLogId of wrong does not equal Cloud SQL lock requestLogId"
+ " of testLogId"));
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(
Level.WARNING,
String.format(
"Datastore lock acquiredTime of 1969-12-31T00:00:00.000Z does not equal Cloud SQL"
+ " lock acquiredTime of 1970-01-01T00:00:00.000Z"));
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(
Level.WARNING,
String.format(
"Datastore lock expirationTime of 1969-12-31T00:00:00.003Z does not equal Cloud"
+ " SQL lock expirationTime of 1970-01-01T00:00:00.002Z"));
}
}
@@ -19,7 +19,6 @@ import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.collect.ImmutableList;
import com.google.common.net.HostAndPort;
import google.registry.persistence.transaction.JpaTestRules;
import google.registry.testing.AppEngineRule;
import google.registry.testing.UserInfo;
import google.registry.tools.params.HostAndPortParameter;
@@ -156,23 +155,18 @@ public final class RegistryTestServerMain {
}
};
Statement withAppEngine =
AppEngineRule.builder()
.withDatastoreAndCloudSql()
.withUrlFetch()
.withTaskQueue()
.withLocalModules()
.withUserService(
loginIsAdmin
? UserInfo.createAdmin(loginEmail, loginUserId)
: UserInfo.create(loginEmail, loginUserId))
.build()
.apply(runner, Description.EMPTY);
System.out.printf("%sLoading SQL fixtures and AppEngineRule...%s\n", BLUE, RESET);
new JpaTestRules.Builder()
.buildIntegrationTestRule()
.apply(withAppEngine, Description.EMPTY)
AppEngineRule.builder()
.withDatastoreAndCloudSql()
.withUrlFetch()
.withTaskQueue()
.withLocalModules()
.withUserService(
loginIsAdmin
? UserInfo.createAdmin(loginEmail, loginUserId)
: UserInfo.create(loginEmail, loginUserId))
.build()
.apply(runner, Description.EMPTY)
.evaluate();
}
@@ -247,6 +247,7 @@ public final class AppEngineRule extends ExternalResource {
.setParent(makeRegistrar2())
.setName("Marla Singer")
.setEmailAddress("Marla.Singer@crr.com")
.setRegistryLockEmailAddress("Marla.Singer.RegistryLock@crr.com")
.setPhoneNumber("+1.2128675309")
.setTypes(ImmutableSet.of(RegistrarContact.Type.TECH))
.setGaeUserId("12345")
@@ -89,6 +89,7 @@ public class RegistrarContactCommandTest extends CommandTestCase<RegistrarContac
"--mode=UPDATE",
"--name=Judith Registrar",
"--email=judith.doe@example.com",
"--registry_lock_email=judith.doe@external.com",
"--phone=+1.2125650000",
"--fax=+1.2125650001",
"--contact_type=WHOIS",
@@ -102,6 +103,7 @@ public class RegistrarContactCommandTest extends CommandTestCase<RegistrarContac
.setParent(registrar)
.setName("Judith Registrar")
.setEmailAddress("judith.doe@example.com")
.setRegistryLockEmailAddress("judith.doe@external.com")
.setPhoneNumber("+1.2125650000")
.setFaxNumber("+1.2125650001")
.setTypes(ImmutableSet.of(WHOIS))
@@ -292,6 +294,7 @@ public class RegistrarContactCommandTest extends CommandTestCase<RegistrarContac
"--mode=CREATE",
"--name=Jim Doe",
"--email=jim.doe@example.com",
"--registry_lock_email=jim.doe@external.com",
"--contact_type=ADMIN,ABUSE",
"--visible_in_whois_as_admin=true",
"--visible_in_whois_as_tech=false",
@@ -303,6 +306,7 @@ public class RegistrarContactCommandTest extends CommandTestCase<RegistrarContac
.setParent(registrar)
.setName("Jim Doe")
.setEmailAddress("jim.doe@example.com")
.setRegistryLockEmailAddress("jim.doe@external.com")
.setTypes(ImmutableSet.of(ADMIN, ABUSE))
.setVisibleInWhoisAsAdmin(true)
.setVisibleInWhoisAsTech(false)
@@ -374,6 +378,7 @@ public class RegistrarContactCommandTest extends CommandTestCase<RegistrarContac
"--mode=CREATE",
"--name=Jim Doe",
"--email=jim.doe@example.com",
"--registry_lock_email=jim.doe.registry.lock@example.com",
"--allowed_to_set_registry_lock_password=true",
"NewRegistrar");
RegistrarContact registrarContact = loadRegistrar("NewRegistrar").getContacts().asList().get(1);
@@ -392,12 +397,30 @@ public class RegistrarContactCommandTest extends CommandTestCase<RegistrarContac
.setEmailAddress("jim.doe@example.com")
.build());
assertThat(registrarContact.isAllowedToSetRegistryLockPassword()).isFalse();
// First, try (and fail) to set the password directly
assertThrows(
IllegalArgumentException.class,
() -> registrarContact.asBuilder().setRegistryLockPassword("foo"));
// Next, try (and fail) to allow registry lock without a registry lock email
assertThat(
assertThrows(
IllegalArgumentException.class,
() ->
runCommandForced(
"--mode=UPDATE",
"--email=jim.doe@example.com",
"--allowed_to_set_registry_lock_password=true",
"NewRegistrar")))
.hasMessageThat()
.isEqualTo("Registry lock email must not be null if allowing registry lock access");
// Next, include the email and it should succeed
runCommandForced(
"--mode=UPDATE",
"--email=jim.doe@example.com",
"--registry_lock_email=jim.doe.registry.lock@example.com",
"--allowed_to_set_registry_lock_password=true",
"NewRegistrar");
RegistrarContact newContact = reloadResource(registrarContact);
@@ -415,6 +438,7 @@ public class RegistrarContactCommandTest extends CommandTestCase<RegistrarContac
.setParent(registrar)
.setName("Jim Doe")
.setEmailAddress("jim.doe@example.com")
.setRegistryLockEmailAddress("jim.doe.registry.lock@example.com")
.setAllowedToSetRegistryLockPassword(true)
.setRegistryLockPassword("hi")
.build());
@@ -237,6 +237,7 @@ public class ContactSettingsTest extends RegistrarSettingsActionTestCase {
AppEngineRule.makeRegistrarContact2()
.asBuilder()
.setEmailAddress("someotheremail@example.com")
.setRegistryLockEmailAddress("someotherexample@example.com")
.setAllowedToSetRegistryLockPassword(true)
.build()
.toJsonMap(),
@@ -100,6 +100,7 @@ public abstract class RegistrarSettingsActionTestCase {
.setParent(loadRegistrar(CLIENT_ID))
.setName("Jian-Yang")
.setEmailAddress("jyang@bachman.accelerator")
.setRegistryLockEmailAddress("jyang.registrylock@bachman.accelerator")
.setPhoneNumber("+1.1234567890")
.setTypes(ImmutableSet.of(RegistrarContact.Type.TECH))
.build());
@@ -182,16 +182,16 @@ public final class RegistryLockGetActionTest {
"lockEnabledForContact",
true,
"email",
"Marla.Singer@crr.com",
"Marla.Singer.RegistryLock@crr.com",
"clientId",
"TheRegistrar",
"locks",
ImmutableList.of(
new ImmutableMap.Builder<>()
.put("fullyQualifiedDomainName", "expiredunlock.test")
.put("lockedTime", "2000-06-08T22:00:00.000Z")
.put("lockedBy", "johndoe@theregistrar.com")
.put("userCanUnlock", true)
.put("fullyQualifiedDomainName", "adminexample.test")
.put("lockedTime", "2000-06-09T22:00:00.001Z")
.put("lockedBy", "admin")
.put("userCanUnlock", false)
.put("isLockPending", false)
.put("isUnlockPending", false)
.build(),
@@ -204,19 +204,11 @@ public final class RegistryLockGetActionTest {
.put("isUnlockPending", false)
.build(),
new ImmutableMap.Builder<>()
.put("fullyQualifiedDomainName", "adminexample.test")
.put("lockedTime", "2000-06-09T22:00:00.001Z")
.put("lockedBy", "admin")
.put("userCanUnlock", false)
.put("isLockPending", false)
.put("isUnlockPending", false)
.build(),
new ImmutableMap.Builder<>()
.put("fullyQualifiedDomainName", "pending.test")
.put("lockedTime", "")
.put("fullyQualifiedDomainName", "expiredunlock.test")
.put("lockedTime", "2000-06-08T22:00:00.000Z")
.put("lockedBy", "johndoe@theregistrar.com")
.put("userCanUnlock", true)
.put("isLockPending", true)
.put("isLockPending", false)
.put("isUnlockPending", false)
.build(),
new ImmutableMap.Builder<>()
@@ -226,6 +218,14 @@ public final class RegistryLockGetActionTest {
.put("userCanUnlock", true)
.put("isLockPending", false)
.put("isUnlockPending", true)
.build(),
new ImmutableMap.Builder<>()
.put("fullyQualifiedDomainName", "pending.test")
.put("lockedTime", "")
.put("lockedBy", "johndoe@theregistrar.com")
.put("userCanUnlock", true)
.put("isLockPending", true)
.put("isUnlockPending", false)
.build()))));
}
@@ -276,7 +276,7 @@ public final class RegistryLockGetActionTest {
"lockEnabledForContact",
false,
"email",
"Marla.Singer@crr.com",
"Marla.Singer.RegistryLock@crr.com",
"clientId",
"TheRegistrar",
"locks",
@@ -304,7 +304,7 @@ public final class RegistryLockGetActionTest {
"lockEnabledForContact",
true,
"email",
"Marla.Singer@crr.com",
"Marla.Singer.RegistryLock@crr.com",
"clientId",
"TheRegistrar",
"locks",
@@ -327,7 +327,7 @@ public final class RegistryLockGetActionTest {
"lockEnabledForContact",
true,
"email",
"Marla.Singer@crr.com",
"Marla.Singer.RegistryLock@crr.com",
"clientId",
"TheRegistrar",
"locks",
@@ -105,7 +105,7 @@ public final class RegistryLockPostActionTest {
@Test
public void testSuccess_lock() throws Exception {
Map<String, ?> response = action.handleJsonRequest(lockRequest());
assertSuccess(response, "lock", "Marla.Singer@crr.com");
assertSuccess(response, "lock", "Marla.Singer.RegistryLock@crr.com");
}
@Test
@@ -113,7 +113,7 @@ public final class RegistryLockPostActionTest {
saveRegistryLock(createLock().asBuilder().setLockCompletionTimestamp(clock.nowUtc()).build());
persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build());
Map<String, ?> response = action.handleJsonRequest(unlockRequest());
assertSuccess(response, "unlock", "Marla.Singer@crr.com");
assertSuccess(response, "unlock", "Marla.Singer.RegistryLock@crr.com");
}
@Test
@@ -142,7 +142,7 @@ public final class RegistryLockPostActionTest {
createAction(
AuthResult.create(AuthLevel.USER, UserAuthInfo.create(userWithLockPermission, false)));
Map<String, ?> response = action.handleJsonRequest(lockRequest());
assertSuccess(response, "lock", "Marla.Singer@crr.com");
assertSuccess(response, "lock", "Marla.Singer.RegistryLock@crr.com");
}
@Test
@@ -309,7 +309,7 @@ public final class RegistryLockPostActionTest {
.build());
Map<String, ?> response = action.handleJsonRequest(lockRequest());
assertSuccess(response, "lock", "Marla.Singer@crr.com");
assertSuccess(response, "lock", "Marla.Singer.RegistryLock@crr.com");
}
@Test
@@ -319,7 +319,7 @@ public final class RegistryLockPostActionTest {
previousLock = getRegistryLockByVerificationCode(verificationCode).get();
clock.setTo(previousLock.getLockRequestTimestamp().plusHours(2));
Map<String, ?> response = action.handleJsonRequest(lockRequest());
assertSuccess(response, "lock", "Marla.Singer@crr.com");
assertSuccess(response, "lock", "Marla.Singer.RegistryLock@crr.com");
}
@Test
@@ -153,6 +153,7 @@ public class RegistrarConsoleScreenshotTest extends WebDriverTestCase {
persistResource(
makeRegistrarContact2()
.asBuilder()
.setRegistryLockEmailAddress("johndoe.registrylock@example.com")
.setAllowedToSetRegistryLockPassword(true)
.build());
persistResource(makeRegistrar2().asBuilder().setRegistryLockAllowed(true).build());
@@ -477,6 +477,7 @@ class google.registry.model.registrar.RegistrarContact {
java.lang.String gaeUserId;
java.lang.String name;
java.lang.String phoneNumber;
java.lang.String registryLockEmailAddress;
java.lang.String registryLockPasswordHash;
java.lang.String registryLockPasswordSalt;
java.util.Set<google.registry.model.registrar.RegistrarContact$Type> types;
@@ -11,10 +11,10 @@ emailAddress: the.registrar@example.com -> thase@the.registrar
url: http://my.fake.url -> http://my.new.url
contacts:
ADDED:
{parent=Key<?>(EntityGroupRoot("cross-tld")/Registrar("TheRegistrar")), name=Extra Terrestrial, emailAddress=etphonehome@example.com, phoneNumber=+1.2345678901, faxNumber=null, types=[ADMIN, BILLING, TECH, WHOIS], gaeUserId=null, visibleInWhoisAsAdmin=true, visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false, allowedToSetRegistryLockPassword=false}
{parent=Key<?>(EntityGroupRoot("cross-tld")/Registrar("TheRegistrar")), name=Extra Terrestrial, emailAddress=etphonehome@example.com, registryLockEmailAddress=null, phoneNumber=+1.2345678901, faxNumber=null, types=[ADMIN, BILLING, TECH, WHOIS], gaeUserId=null, visibleInWhoisAsAdmin=true, visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false, allowedToSetRegistryLockPassword=false}
REMOVED:
{parent=Key<?>(EntityGroupRoot("cross-tld")/Registrar("TheRegistrar")), name=Marla Singer, emailAddress=Marla.Singer@crr.com, phoneNumber=+1.2128675309, faxNumber=null, types=[TECH], gaeUserId=12345, visibleInWhoisAsAdmin=false, visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false, allowedToSetRegistryLockPassword=false},
{parent=Key<?>(EntityGroupRoot("cross-tld")/Registrar("TheRegistrar")), name=John Doe, emailAddress=johndoe@theregistrar.com, phoneNumber=+1.1234567890, faxNumber=null, types=[ADMIN], gaeUserId=31337, visibleInWhoisAsAdmin=false, visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false, allowedToSetRegistryLockPassword=false},
{parent=Key<?>(EntityGroupRoot("cross-tld")/Registrar("TheRegistrar")), name=Jian-Yang, emailAddress=jyang@bachman.accelerator, phoneNumber=+1.1234567890, faxNumber=null, types=[TECH], gaeUserId=null, visibleInWhoisAsAdmin=false, visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false, allowedToSetRegistryLockPassword=false}
{parent=Key<?>(EntityGroupRoot("cross-tld")/Registrar("TheRegistrar")), name=Marla Singer, emailAddress=Marla.Singer@crr.com, registryLockEmailAddress=Marla.Singer.RegistryLock@crr.com, phoneNumber=+1.2128675309, faxNumber=null, types=[TECH], gaeUserId=12345, visibleInWhoisAsAdmin=false, visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false, allowedToSetRegistryLockPassword=false},
{parent=Key<?>(EntityGroupRoot("cross-tld")/Registrar("TheRegistrar")), name=John Doe, emailAddress=johndoe@theregistrar.com, registryLockEmailAddress=null, phoneNumber=+1.1234567890, faxNumber=null, types=[ADMIN], gaeUserId=31337, visibleInWhoisAsAdmin=false, visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false, allowedToSetRegistryLockPassword=false},
{parent=Key<?>(EntityGroupRoot("cross-tld")/Registrar("TheRegistrar")), name=Jian-Yang, emailAddress=jyang@bachman.accelerator, registryLockEmailAddress=jyang.registrylock@bachman.accelerator, phoneNumber=+1.1234567890, faxNumber=null, types=[TECH], gaeUserId=null, visibleInWhoisAsAdmin=false, visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false, allowedToSetRegistryLockPassword=false}
FINAL CONTENTS:
{parent=Key<?>(EntityGroupRoot("cross-tld")/Registrar("TheRegistrar")), name=Extra Terrestrial, emailAddress=etphonehome@example.com, phoneNumber=+1.2345678901, faxNumber=null, types=[ADMIN, BILLING, TECH, WHOIS], gaeUserId=null, visibleInWhoisAsAdmin=true, visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false, allowedToSetRegistryLockPassword=false}
{parent=Key<?>(EntityGroupRoot("cross-tld")/Registrar("TheRegistrar")), name=Extra Terrestrial, emailAddress=etphonehome@example.com, registryLockEmailAddress=null, phoneNumber=+1.2345678901, faxNumber=null, types=[ADMIN, BILLING, TECH, WHOIS], gaeUserId=null, visibleInWhoisAsAdmin=true, visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false, allowedToSetRegistryLockPassword=false}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

@@ -0,0 +1,15 @@
-- 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.
ALTER TABLE "RegistryLock" ADD COLUMN relock_duration bigint;
@@ -0,0 +1,15 @@
-- 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.
ALTER TABLE "RegistrarPoc" ADD COLUMN registry_lock_email_address text;
@@ -158,6 +158,7 @@
gae_user_id text,
name text,
phone_number text,
registry_lock_email_address text,
registry_lock_password_hash text,
registry_lock_password_salt text,
types text[],
@@ -176,6 +177,7 @@
lock_request_timestamp timestamptz not null,
registrar_id text not null,
registrar_poc_id text,
relock_duration int8,
repo_id text not null,
unlock_completion_timestamp timestamptz,
unlock_request_timestamp timestamptz,
@@ -241,7 +241,8 @@ CREATE TABLE public."RegistrarPoc" (
types text[],
visible_in_domain_whois_as_abuse boolean NOT NULL,
visible_in_whois_as_admin boolean NOT NULL,
visible_in_whois_as_tech boolean NOT NULL
visible_in_whois_as_tech boolean NOT NULL,
registry_lock_email_address text
);
@@ -262,7 +263,8 @@ CREATE TABLE public."RegistryLock" (
unlock_request_timestamp timestamp with time zone,
unlock_completion_timestamp timestamp with time zone,
last_update_timestamp timestamp with time zone,
relock_revision_id bigint
relock_revision_id bigint,
relock_duration bigint
);
+1
View File
@@ -43,6 +43,7 @@ ext {
'com.google.auto.value:auto-value-annotations:1.6.3',
'com.google.auto.value:auto-value:1.6.3',
'com.google.closure-stylesheets:closure-stylesheets:1.5.0',
'com.google.cloud.sql:jdbc-socket-factory-core:1.0.12',
'com.google.cloud.sql:postgres-socket-factory:1.0.12',
'com.google.cloud:google-cloud-core:1.59.0',
'com.google.cloud:google-cloud-storage:1.59.0',
Binary file not shown.
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.2-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+3
View File
@@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+163 -163
View File
@@ -10,7 +10,7 @@
"integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
"dev": true,
"requires": {
"mime-types": "~2.1.24",
"mime-types": "2.1.24",
"negotiator": "0.6.2"
}
},
@@ -26,7 +26,7 @@
"integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==",
"dev": true,
"requires": {
"es6-promisify": "^5.0.0"
"es6-promisify": "5.0.0"
}
},
"anymatch": {
@@ -35,8 +35,8 @@
"integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==",
"dev": true,
"requires": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
"normalize-path": "3.0.0",
"picomatch": "2.1.1"
}
},
"arraybuffer.slice": {
@@ -51,7 +51,7 @@
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
"dev": true,
"requires": {
"lodash": "^4.17.14"
"lodash": "4.17.15"
}
},
"async-limiter": {
@@ -118,15 +118,15 @@
"dev": true,
"requires": {
"bytes": "3.1.0",
"content-type": "~1.0.4",
"content-type": "1.0.4",
"debug": "2.6.9",
"depd": "~1.1.2",
"depd": "1.1.2",
"http-errors": "1.7.2",
"iconv-lite": "0.4.24",
"on-finished": "~2.3.0",
"on-finished": "2.3.0",
"qs": "6.7.0",
"raw-body": "2.4.0",
"type-is": "~1.6.17"
"type-is": "1.6.18"
}
},
"brace-expansion": {
@@ -135,7 +135,7 @@
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0",
"balanced-match": "1.0.0",
"concat-map": "0.0.1"
}
},
@@ -145,7 +145,7 @@
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dev": true,
"requires": {
"fill-range": "^7.0.1"
"fill-range": "7.0.1"
}
},
"buffer-alloc": {
@@ -154,8 +154,8 @@
"integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==",
"dev": true,
"requires": {
"buffer-alloc-unsafe": "^1.1.0",
"buffer-fill": "^1.0.0"
"buffer-alloc-unsafe": "1.1.0",
"buffer-fill": "1.0.0"
}
},
"buffer-alloc-unsafe": {
@@ -194,14 +194,14 @@
"integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==",
"dev": true,
"requires": {
"anymatch": "~3.1.1",
"braces": "~3.0.2",
"fsevents": "~2.1.1",
"glob-parent": "~5.1.0",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.2.0"
"anymatch": "3.1.1",
"braces": "3.0.2",
"fsevents": "2.1.2",
"glob-parent": "5.1.0",
"is-binary-path": "2.1.0",
"is-glob": "4.0.1",
"normalize-path": "3.0.0",
"readdirp": "3.2.0"
}
},
"colors": {
@@ -240,10 +240,10 @@
"integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
"dev": true,
"requires": {
"buffer-from": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^2.2.2",
"typedarray": "^0.0.6"
"buffer-from": "1.1.1",
"inherits": "2.0.3",
"readable-stream": "2.3.6",
"typedarray": "0.0.6"
}
},
"connect": {
@@ -254,7 +254,7 @@
"requires": {
"debug": "2.6.9",
"finalhandler": "1.1.2",
"parseurl": "~1.3.3",
"parseurl": "1.3.3",
"utils-merge": "1.0.1"
}
},
@@ -315,10 +315,10 @@
"integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=",
"dev": true,
"requires": {
"custom-event": "~1.0.0",
"ent": "~2.2.0",
"extend": "^3.0.0",
"void-elements": "^2.0.0"
"custom-event": "1.0.1",
"ent": "2.2.0",
"extend": "3.0.2",
"void-elements": "2.0.1"
}
},
"ee-first": {
@@ -339,12 +339,12 @@
"integrity": "sha512-+VlKzHzMhaU+GsCIg4AoXF1UdDFjHHwMmMKqMJNDNLlUlejz58FCy4LBqB2YVJskHGYl06BatYWKP2TVdVXE5w==",
"dev": true,
"requires": {
"accepts": "~1.3.4",
"accepts": "1.3.7",
"base64id": "1.0.0",
"cookie": "0.3.1",
"debug": "~3.1.0",
"engine.io-parser": "~2.1.0",
"ws": "~3.3.1"
"debug": "3.1.0",
"engine.io-parser": "2.1.3",
"ws": "3.3.3"
},
"dependencies": {
"debug": {
@@ -366,14 +366,14 @@
"requires": {
"component-emitter": "1.2.1",
"component-inherit": "0.0.3",
"debug": "~3.1.0",
"engine.io-parser": "~2.1.1",
"debug": "3.1.0",
"engine.io-parser": "2.1.3",
"has-cors": "1.1.0",
"indexof": "0.0.1",
"parseqs": "0.0.5",
"parseuri": "0.0.5",
"ws": "~3.3.1",
"xmlhttprequest-ssl": "~1.5.4",
"ws": "3.3.3",
"xmlhttprequest-ssl": "1.5.5",
"yeast": "0.1.2"
},
"dependencies": {
@@ -395,10 +395,10 @@
"dev": true,
"requires": {
"after": "0.8.2",
"arraybuffer.slice": "~0.0.7",
"arraybuffer.slice": "0.0.7",
"base64-arraybuffer": "0.1.5",
"blob": "0.0.5",
"has-binary2": "~1.0.2"
"has-binary2": "1.0.3"
}
},
"ent": {
@@ -419,7 +419,7 @@
"integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=",
"dev": true,
"requires": {
"es6-promise": "^4.0.3"
"es6-promise": "4.2.8"
}
},
"escape-html": {
@@ -458,7 +458,7 @@
"integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=",
"dev": true,
"requires": {
"pend": "~1.2.0"
"pend": "1.2.0"
}
},
"fill-range": {
@@ -467,7 +467,7 @@
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dev": true,
"requires": {
"to-regex-range": "^5.0.1"
"to-regex-range": "5.0.1"
}
},
"finalhandler": {
@@ -477,12 +477,12 @@
"dev": true,
"requires": {
"debug": "2.6.9",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"on-finished": "~2.3.0",
"parseurl": "~1.3.3",
"statuses": "~1.5.0",
"unpipe": "~1.0.0"
"encodeurl": "1.0.2",
"escape-html": "1.0.3",
"on-finished": "2.3.0",
"parseurl": "1.3.3",
"statuses": "1.5.0",
"unpipe": "1.0.0"
}
},
"flatted": {
@@ -497,7 +497,7 @@
"integrity": "sha512-CRcPzsSIbXyVDl0QI01muNDu69S8trU4jArW9LpOt2WtC6LyUJetcIrmfHsRBx7/Jb6GHJUiuqyYxPooFfNt6A==",
"dev": true,
"requires": {
"debug": "^3.0.0"
"debug": "3.2.6"
},
"dependencies": {
"debug": {
@@ -506,7 +506,7 @@
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
"dev": true,
"requires": {
"ms": "^2.1.1"
"ms": "2.1.2"
}
},
"ms": {
@@ -523,7 +523,7 @@
"integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=",
"dev": true,
"requires": {
"null-check": "^1.0.0"
"null-check": "1.0.0"
}
},
"fs-extra": {
@@ -532,9 +532,9 @@
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
"dev": true,
"requires": {
"graceful-fs": "^4.1.2",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
"graceful-fs": "4.2.3",
"jsonfile": "4.0.0",
"universalify": "0.1.2"
}
},
"fs.realpath": {
@@ -556,12 +556,12 @@
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
"fs.realpath": "1.0.0",
"inflight": "1.0.6",
"inherits": "2.0.3",
"minimatch": "3.0.4",
"once": "1.4.0",
"path-is-absolute": "1.0.1"
}
},
"glob-parent": {
@@ -570,7 +570,7 @@
"integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==",
"dev": true,
"requires": {
"is-glob": "^4.0.1"
"is-glob": "4.0.1"
}
},
"google-closure-library": {
@@ -605,10 +605,10 @@
"integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
"dev": true,
"requires": {
"depd": "~1.1.2",
"depd": "1.1.2",
"inherits": "2.0.3",
"setprototypeof": "1.1.1",
"statuses": ">= 1.5.0 < 2",
"statuses": "1.5.0",
"toidentifier": "1.0.0"
}
},
@@ -618,9 +618,9 @@
"integrity": "sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==",
"dev": true,
"requires": {
"eventemitter3": "^4.0.0",
"follow-redirects": "^1.0.0",
"requires-port": "^1.0.0"
"eventemitter3": "4.0.0",
"follow-redirects": "1.9.0",
"requires-port": "1.0.0"
}
},
"https-proxy-agent": {
@@ -629,8 +629,8 @@
"integrity": "sha512-+ML2Rbh6DAuee7d07tYGEKOEi2voWPUGan+ExdPbPW6Z3svq+JCqr0v8WmKPOkz1vOVykPCBSuobe7G8GJUtVg==",
"dev": true,
"requires": {
"agent-base": "^4.3.0",
"debug": "^3.1.0"
"agent-base": "4.3.0",
"debug": "3.2.6"
},
"dependencies": {
"debug": {
@@ -639,7 +639,7 @@
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
"dev": true,
"requires": {
"ms": "^2.1.1"
"ms": "2.1.2"
}
},
"ms": {
@@ -656,7 +656,7 @@
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"dev": true,
"requires": {
"safer-buffer": ">= 2.1.2 < 3"
"safer-buffer": "2.1.2"
}
},
"indexof": {
@@ -671,8 +671,8 @@
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
"once": "1.4.0",
"wrappy": "1.0.2"
}
},
"inherits": {
@@ -687,7 +687,7 @@
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"requires": {
"binary-extensions": "^2.0.0"
"binary-extensions": "2.0.0"
}
},
"is-extglob": {
@@ -702,7 +702,7 @@
"integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
"dev": true,
"requires": {
"is-extglob": "^2.1.1"
"is-extglob": "2.1.1"
}
},
"is-number": {
@@ -723,7 +723,7 @@
"integrity": "sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==",
"dev": true,
"requires": {
"buffer-alloc": "^1.2.0"
"buffer-alloc": "1.2.0"
}
},
"isexe": {
@@ -744,7 +744,7 @@
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"dev": true,
"requires": {
"graceful-fs": "^4.1.6"
"graceful-fs": "4.2.3"
}
},
"karma": {
@@ -753,30 +753,30 @@
"integrity": "sha512-L5SIaXEYqzrh6b1wqYC42tNsFMx2PWuxky84pK9coK09MvmL7mxii3G3bZBh/0rvD27lqDd0le9jyhzvwif73A==",
"dev": true,
"requires": {
"bluebird": "^3.3.0",
"body-parser": "^1.16.1",
"braces": "^3.0.2",
"chokidar": "^3.0.0",
"colors": "^1.1.0",
"connect": "^3.6.0",
"di": "^0.0.1",
"dom-serialize": "^2.2.0",
"flatted": "^2.0.0",
"glob": "^7.1.1",
"graceful-fs": "^4.1.2",
"http-proxy": "^1.13.0",
"isbinaryfile": "^3.0.0",
"lodash": "^4.17.14",
"log4js": "^4.0.0",
"mime": "^2.3.1",
"minimatch": "^3.0.2",
"optimist": "^0.6.1",
"qjobs": "^1.1.4",
"range-parser": "^1.2.0",
"rimraf": "^2.6.0",
"safe-buffer": "^5.0.1",
"bluebird": "3.7.1",
"body-parser": "1.19.0",
"braces": "3.0.2",
"chokidar": "3.3.0",
"colors": "1.4.0",
"connect": "3.7.0",
"di": "0.0.1",
"dom-serialize": "2.2.1",
"flatted": "2.0.1",
"glob": "7.1.6",
"graceful-fs": "4.2.3",
"http-proxy": "1.18.0",
"isbinaryfile": "3.0.3",
"lodash": "4.17.15",
"log4js": "4.5.1",
"mime": "2.4.4",
"minimatch": "3.0.4",
"optimist": "0.6.1",
"qjobs": "1.2.0",
"range-parser": "1.2.1",
"rimraf": "2.7.1",
"safe-buffer": "5.2.0",
"socket.io": "2.1.1",
"source-map": "^0.6.1",
"source-map": "0.6.1",
"tmp": "0.0.33",
"useragent": "2.3.0"
}
@@ -787,8 +787,8 @@
"integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==",
"dev": true,
"requires": {
"fs-access": "^1.0.0",
"which": "^1.2.1"
"fs-access": "1.0.1",
"which": "1.3.1"
}
},
"karma-closure": {
@@ -803,7 +803,7 @@
"integrity": "sha512-iuC0hmr9b+SNn1DaUD2QEYtUxkS1J+bSJSn7ejdEexs7P8EYvA1CWkEdrDQ+8jVH3AgWlCNwjYsT1chjcNW9lA==",
"dev": true,
"requires": {
"jasmine-core": "^3.3"
"jasmine-core": "3.4.0"
}
},
"lodash": {
@@ -818,11 +818,11 @@
"integrity": "sha512-EEEgFcE9bLgaYUKuozyFfytQM2wDHtXn4tAN41pkaxpNjAykv11GVdeI4tHtmPWW4Xrgh9R/2d7XYghDVjbKKw==",
"dev": true,
"requires": {
"date-format": "^2.0.0",
"debug": "^4.1.1",
"flatted": "^2.0.0",
"rfdc": "^1.1.4",
"streamroller": "^1.0.6"
"date-format": "2.1.0",
"debug": "4.1.1",
"flatted": "2.0.1",
"rfdc": "1.1.4",
"streamroller": "1.0.6"
},
"dependencies": {
"debug": {
@@ -831,7 +831,7 @@
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"dev": true,
"requires": {
"ms": "^2.1.1"
"ms": "2.1.2"
}
},
"ms": {
@@ -848,8 +848,8 @@
"integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
"dev": true,
"requires": {
"pseudomap": "^1.0.2",
"yallist": "^2.1.2"
"pseudomap": "1.0.2",
"yallist": "2.1.2"
}
},
"media-typer": {
@@ -885,7 +885,7 @@
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
"brace-expansion": "1.1.11"
}
},
"minimist": {
@@ -956,7 +956,7 @@
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
"wrappy": "1"
"wrappy": "1.0.2"
}
},
"optimist": {
@@ -965,8 +965,8 @@
"integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
"dev": true,
"requires": {
"minimist": "~0.0.1",
"wordwrap": "~0.0.2"
"minimist": "0.0.10",
"wordwrap": "0.0.3"
}
},
"os-tmpdir": {
@@ -981,7 +981,7 @@
"integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=",
"dev": true,
"requires": {
"better-assert": "~1.0.0"
"better-assert": "1.0.2"
}
},
"parseuri": {
@@ -990,7 +990,7 @@
"integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=",
"dev": true,
"requires": {
"better-assert": "~1.0.0"
"better-assert": "1.0.2"
}
},
"parseurl": {
@@ -1047,14 +1047,14 @@
"integrity": "sha512-t3MmTWzQxPRP71teU6l0jX47PHXlc4Z52sQv4LJQSZLq1ttkKS2yGM3gaI57uQwZkNaoGd0+HPPMELZkcyhlqA==",
"dev": true,
"requires": {
"debug": "^4.1.0",
"extract-zip": "^1.6.6",
"https-proxy-agent": "^3.0.0",
"mime": "^2.0.3",
"progress": "^2.0.1",
"proxy-from-env": "^1.0.0",
"rimraf": "^2.6.1",
"ws": "^6.1.0"
"debug": "4.1.1",
"extract-zip": "1.6.7",
"https-proxy-agent": "3.0.1",
"mime": "2.4.4",
"progress": "2.0.3",
"proxy-from-env": "1.0.0",
"rimraf": "2.7.1",
"ws": "6.2.1"
},
"dependencies": {
"debug": {
@@ -1063,7 +1063,7 @@
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"dev": true,
"requires": {
"ms": "^2.1.1"
"ms": "2.1.2"
}
},
"ms": {
@@ -1078,7 +1078,7 @@
"integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==",
"dev": true,
"requires": {
"async-limiter": "~1.0.0"
"async-limiter": "1.0.1"
}
}
}
@@ -1119,13 +1119,13 @@
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"dev": true,
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
"core-util-is": "1.0.2",
"inherits": "2.0.3",
"isarray": "1.0.0",
"process-nextick-args": "2.0.1",
"safe-buffer": "5.1.2",
"string_decoder": "1.1.1",
"util-deprecate": "1.0.2"
},
"dependencies": {
"isarray": {
@@ -1148,7 +1148,7 @@
"integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==",
"dev": true,
"requires": {
"picomatch": "^2.0.4"
"picomatch": "2.1.1"
}
},
"requires-port": {
@@ -1169,7 +1169,7 @@
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"dev": true,
"requires": {
"glob": "^7.1.3"
"glob": "7.1.6"
}
},
"safe-buffer": {
@@ -1196,12 +1196,12 @@
"integrity": "sha512-rORqq9c+7W0DAK3cleWNSyfv/qKXV99hV4tZe+gGLfBECw3XEhBy7x85F3wypA9688LKjtwO9pX9L33/xQI8yA==",
"dev": true,
"requires": {
"debug": "~3.1.0",
"engine.io": "~3.2.0",
"has-binary2": "~1.0.2",
"socket.io-adapter": "~1.1.0",
"debug": "3.1.0",
"engine.io": "3.2.1",
"has-binary2": "1.0.3",
"socket.io-adapter": "1.1.1",
"socket.io-client": "2.1.1",
"socket.io-parser": "~3.2.0"
"socket.io-parser": "3.2.0"
},
"dependencies": {
"debug": {
@@ -1231,15 +1231,15 @@
"base64-arraybuffer": "0.1.5",
"component-bind": "1.0.0",
"component-emitter": "1.2.1",
"debug": "~3.1.0",
"engine.io-client": "~3.2.0",
"has-binary2": "~1.0.2",
"debug": "3.1.0",
"engine.io-client": "3.2.1",
"has-binary2": "1.0.3",
"has-cors": "1.1.0",
"indexof": "0.0.1",
"object-component": "0.0.3",
"parseqs": "0.0.5",
"parseuri": "0.0.5",
"socket.io-parser": "~3.2.0",
"socket.io-parser": "3.2.0",
"to-array": "0.1.4"
},
"dependencies": {
@@ -1261,7 +1261,7 @@
"dev": true,
"requires": {
"component-emitter": "1.2.1",
"debug": "~3.1.0",
"debug": "3.1.0",
"isarray": "2.0.1"
},
"dependencies": {
@@ -1294,11 +1294,11 @@
"integrity": "sha512-3QC47Mhv3/aZNFpDDVO44qQb9gwB9QggMEE0sQmkTAwBVYdBRWISdsywlkfm5II1Q5y/pmrHflti/IgmIzdDBg==",
"dev": true,
"requires": {
"async": "^2.6.2",
"date-format": "^2.0.0",
"debug": "^3.2.6",
"fs-extra": "^7.0.1",
"lodash": "^4.17.14"
"async": "2.6.3",
"date-format": "2.1.0",
"debug": "3.2.6",
"fs-extra": "7.0.1",
"lodash": "4.17.15"
},
"dependencies": {
"debug": {
@@ -1307,7 +1307,7 @@
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
"dev": true,
"requires": {
"ms": "^2.1.1"
"ms": "2.1.2"
}
},
"ms": {
@@ -1324,7 +1324,7 @@
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
"safe-buffer": "~5.1.0"
"safe-buffer": "5.1.2"
},
"dependencies": {
"safe-buffer": {
@@ -1341,7 +1341,7 @@
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
"dev": true,
"requires": {
"os-tmpdir": "~1.0.2"
"os-tmpdir": "1.0.2"
}
},
"to-array": {
@@ -1356,7 +1356,7 @@
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"requires": {
"is-number": "^7.0.0"
"is-number": "7.0.0"
}
},
"toidentifier": {
@@ -1372,7 +1372,7 @@
"dev": true,
"requires": {
"media-typer": "0.3.0",
"mime-types": "~2.1.24"
"mime-types": "2.1.24"
}
},
"typedarray": {
@@ -1405,8 +1405,8 @@
"integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==",
"dev": true,
"requires": {
"lru-cache": "4.1.x",
"tmp": "0.0.x"
"lru-cache": "4.1.5",
"tmp": "0.0.33"
}
},
"util-deprecate": {
@@ -1433,7 +1433,7 @@
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
"dev": true,
"requires": {
"isexe": "^2.0.0"
"isexe": "2.0.0"
}
},
"wordwrap": {
@@ -1454,9 +1454,9 @@
"integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==",
"dev": true,
"requires": {
"async-limiter": "~1.0.0",
"safe-buffer": "~5.1.0",
"ultron": "~1.1.0"
"async-limiter": "1.0.1",
"safe-buffer": "5.1.2",
"ultron": "1.1.1"
},
"dependencies": {
"safe-buffer": {
@@ -1485,7 +1485,7 @@
"integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=",
"dev": true,
"requires": {
"fd-slicer": "~1.0.1"
"fd-slicer": "1.0.1"
}
},
"yeast": {