diff --git a/core/src/main/java/google/registry/model/registrar/RegistrarContact.java b/core/src/main/java/google/registry/model/registrar/RegistrarContact.java index 78a5f853d..2255522fe 100644 --- a/core/src/main/java/google/registry/model/registrar/RegistrarContact.java +++ b/core/src/main/java/google/registry/model/registrar/RegistrarContact.java @@ -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 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; diff --git a/core/src/main/java/google/registry/tools/RegistrarContactCommand.java b/core/src/main/java/google/registry/tools/RegistrarContactCommand.java index 7a74596ab..9012ad2ae 100644 --- a/core/src/main/java/google/registry/tools/RegistrarContactCommand.java +++ b/core/src/main/java/google/registry/tools/RegistrarContactCommand.java @@ -78,11 +78,15 @@ final class RegistrarContactCommand extends MutatingCommand { private List 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)); diff --git a/core/src/main/java/google/registry/ui/server/RegistrarFormFields.java b/core/src/main/java/google/registry/ui/server/RegistrarFormFields.java index 61ff2a12c..c37a1d88c 100644 --- a/core/src/main/java/google/registry/ui/server/RegistrarFormFields.java +++ b/core/src/main/java/google/registry/ui/server/RegistrarFormFields.java @@ -184,6 +184,11 @@ public final class RegistrarFormFields { .required() .build(); + public static final FormField REGISTRY_LOCK_EMAIL_ADDRESS_FIELD = + FormFields.EMAIL + .asBuilderNamed("registryLockEmailAddress") + .build(); + public static final FormField 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 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( diff --git a/core/src/main/java/google/registry/ui/server/registrar/RegistryLockGetAction.java b/core/src/main/java/google/registry/ui/server/registrar/RegistryLockGetAction.java index d52050d19..3ccdb999e 100644 --- a/core/src/main/java/google/registry/ui/server/registrar/RegistryLockGetAction.java +++ b/core/src/main/java/google/registry/ui/server/registrar/RegistryLockGetAction.java @@ -157,9 +157,11 @@ public final class RegistryLockGetAction implements JsonGetAction { Optional 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, diff --git a/core/src/main/java/google/registry/ui/server/registrar/RegistryLockPostAction.java b/core/src/main/java/google/registry/ui/server/registrar/RegistryLockPostAction.java index 376306091..eb42ca3d3 100644 --- a/core/src/main/java/google/registry/ui/server/registrar/RegistryLockPostAction.java +++ b/core/src/main/java/google/registry/ui/server/registrar/RegistryLockPostAction.java @@ -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. */ diff --git a/core/src/test/java/google/registry/testing/AppEngineRule.java b/core/src/test/java/google/registry/testing/AppEngineRule.java index 22d56d220..b6b7785a5 100644 --- a/core/src/test/java/google/registry/testing/AppEngineRule.java +++ b/core/src/test/java/google/registry/testing/AppEngineRule.java @@ -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") diff --git a/core/src/test/java/google/registry/tools/RegistrarContactCommandTest.java b/core/src/test/java/google/registry/tools/RegistrarContactCommandTest.java index 4d7b13b03..8fd847786 100644 --- a/core/src/test/java/google/registry/tools/RegistrarContactCommandTest.java +++ b/core/src/test/java/google/registry/tools/RegistrarContactCommandTest.java @@ -89,6 +89,7 @@ public class RegistrarContactCommandTest extends CommandTestCase 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 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 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 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 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 response = action.handleJsonRequest(lockRequest()); - assertSuccess(response, "lock", "Marla.Singer@crr.com"); + assertSuccess(response, "lock", "Marla.Singer.RegistryLock@crr.com"); } @Test diff --git a/core/src/test/java/google/registry/webdriver/RegistrarConsoleScreenshotTest.java b/core/src/test/java/google/registry/webdriver/RegistrarConsoleScreenshotTest.java index 2f1f9bba8..6ccb8b08b 100644 --- a/core/src/test/java/google/registry/webdriver/RegistrarConsoleScreenshotTest.java +++ b/core/src/test/java/google/registry/webdriver/RegistrarConsoleScreenshotTest.java @@ -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()); diff --git a/core/src/test/resources/google/registry/model/schema.txt b/core/src/test/resources/google/registry/model/schema.txt index f67fa50eb..3b1120dc0 100644 --- a/core/src/test/resources/google/registry/model/schema.txt +++ b/core/src/test/resources/google/registry/model/schema.txt @@ -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 types; diff --git a/core/src/test/resources/google/registry/ui/server/registrar/update_registrar_email.txt b/core/src/test/resources/google/registry/ui/server/registrar/update_registrar_email.txt index 98a14402a..3a78b4b7f 100644 --- a/core/src/test/resources/google/registry/ui/server/registrar/update_registrar_email.txt +++ b/core/src/test/resources/google/registry/ui/server/registrar/update_registrar_email.txt @@ -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} diff --git a/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLock_empty_page.png b/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLock_empty_page.png index 6be1c5d49..274bcfdb3 100644 Binary files a/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLock_empty_page.png and b/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLock_empty_page.png differ diff --git a/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLock_lockModal_page.png b/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLock_lockModal_page.png index df055cb66..4460cc2d0 100644 Binary files a/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLock_lockModal_page.png and b/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLock_lockModal_page.png differ diff --git a/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLock_nonEmpty_admin_page.png b/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLock_nonEmpty_admin_page.png index c79aa9ab0..adc80f8f5 100644 Binary files a/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLock_nonEmpty_admin_page.png and b/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLock_nonEmpty_admin_page.png differ diff --git a/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLock_nonEmpty_page.png b/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLock_nonEmpty_page.png index 57db44a73..c5c95ffdd 100644 Binary files a/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLock_nonEmpty_page.png and b/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLock_nonEmpty_page.png differ diff --git a/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLock_unlockModal_page.png b/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLock_unlockModal_page.png index 4151dbc03..d2bd491e5 100644 Binary files a/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLock_unlockModal_page.png and b/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLock_unlockModal_page.png differ diff --git a/db/src/main/resources/sql/flyway/V21__add_registry_lock_email_to_poc.sql b/db/src/main/resources/sql/flyway/V21__add_registry_lock_email_to_poc.sql new file mode 100644 index 000000000..642c3a647 --- /dev/null +++ b/db/src/main/resources/sql/flyway/V21__add_registry_lock_email_to_poc.sql @@ -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; diff --git a/db/src/main/resources/sql/schema/db-schema.sql.generated b/db/src/main/resources/sql/schema/db-schema.sql.generated index c3a12e586..ededa2754 100644 --- a/db/src/main/resources/sql/schema/db-schema.sql.generated +++ b/db/src/main/resources/sql/schema/db-schema.sql.generated @@ -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[], diff --git a/db/src/main/resources/sql/schema/nomulus.golden.sql b/db/src/main/resources/sql/schema/nomulus.golden.sql index 5186fa432..6f3b19399 100644 --- a/db/src/main/resources/sql/schema/nomulus.golden.sql +++ b/db/src/main/resources/sql/schema/nomulus.golden.sql @@ -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 );