Change pkey of User to emailAddress (#2505)
CodeQL / Analyze (java) (push) Failing after 1m22s
CodeQL / Analyze (javascript) (push) Failing after 1m13s
CodeQL / Analyze (python) (push) Failing after 51s
Dependency Submission / dependency-submission (push) Successful in 2m11s

Originally, we though that User entities were going to have mutable
email addresses, and thus would require a non-changing primary key. This
proved to not be the case. It'll simplify the User loading/saving code
if we just do everything by email address.

Obviously this doesn't change much functionality, but it prepares us for
removing the id field down the line once the changes propagate.
This commit is contained in:
gbrodman
2024-07-29 22:27:06 +00:00
committed by GitHub
parent 53a7d1b66c
commit d33571dde3
12 changed files with 869 additions and 874 deletions
@@ -14,9 +14,11 @@
package google.registry.model.console;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.tools.server.UpdateUserGroupAction.GROUP_UPDATE_QUEUE;
import com.google.cloud.tasks.v2.Task;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.flogger.FluentLogger;
import google.registry.batch.CloudTasksUtils;
@@ -27,25 +29,25 @@ import google.registry.tools.server.UpdateUserGroupAction;
import google.registry.tools.server.UpdateUserGroupAction.Mode;
import google.registry.util.RegistryEnvironment;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.Table;
/** A console user, either a registry employee or a registrar partner. */
@Embeddable
@Entity
@Table(indexes = {@Index(columnList = "emailAddress", name = "user_email_address_idx")})
@Table
public class User extends UserBase {
public static final String IAP_SECURED_WEB_APP_USER_ROLE = "roles/iap.httpsResourceAccessor";
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@VisibleForTesting public static final AtomicLong ID_GENERATOR_FOR_TESTING = new AtomicLong();
/**
* Grants the user permission to pass IAP.
*
@@ -114,13 +116,18 @@ public class User extends UserBase {
}
@Override
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Access(AccessType.PROPERTY)
public Long getId() {
return super.getId();
}
@Id
@Override
@Access(AccessType.PROPERTY)
public String getEmailAddress() {
return super.getEmailAddress();
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
@@ -139,5 +146,20 @@ public class User extends UserBase {
public Builder(User user) {
super(user);
}
@Override
public User build() {
// Sets the ID temporarily until we can get rid of the non-null constraint (and the field)
if (getInstance().getId() == null || getInstance().getId().equals(0L)) {
// In tests, we cannot guarantee that the database is fully set up -- so don't use it to
// generate a new long
if (RegistryEnvironment.get() == RegistryEnvironment.UNITTEST) {
getInstance().setId(ID_GENERATOR_FOR_TESTING.getAndIncrement());
} else {
getInstance().setId(tm().reTransact(tm()::allocateId));
}
}
return super.build();
}
}
}
@@ -53,8 +53,7 @@ public class UserBase extends UpdateAutoTimestampEntity implements Buildable {
@Transient private Long id;
/** Email address of the user in question. */
@Column(nullable = false)
String emailAddress;
@Transient String emailAddress;
/** Optional external email address to use for registry lock confirmation emails. */
@Column String registryLockEmailAddress;
@@ -90,6 +89,10 @@ public class UserBase extends UpdateAutoTimestampEntity implements Buildable {
this.id = id;
}
void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
public String getEmailAddress() {
return emailAddress;
}
@@ -14,7 +14,6 @@
package google.registry.model.console;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import java.util.Optional;
@@ -32,23 +31,8 @@ public class UserDao {
.findFirst());
}
/** Saves the given user, checking that no existing user already exists with this email. */
/** Saves the given user, updating it if it already exists. */
public static void saveUser(User user) {
tm().transact(
() -> {
// Check for an existing user (the unique constraint protects us, but this gives a
// nicer exception)
Optional<User> maybeSavedUser = loadUser(user.getEmailAddress());
if (maybeSavedUser.isPresent()) {
User savedUser = maybeSavedUser.get();
checkArgument(
savedUser.getId().equals(user.getId()),
String.format(
"Attempted save of User with email address %s and ID %s, user with that"
+ " email already exists with ID %s",
user.getEmailAddress(), user.getId(), savedUser.getId()));
}
tm().put(user);
});
tm().transact(() -> tm().put(user));
}
}
@@ -42,6 +42,9 @@ public class UserUpdateHistory extends ConsoleUpdateHistory {
@Column(nullable = false, name = "userId")
Long id;
@Column(nullable = false, name = "emailAddress")
String emailAddress;
public UserBase getUser() {
return user;
}
@@ -49,6 +52,7 @@ public class UserUpdateHistory extends ConsoleUpdateHistory {
@PostLoad
void postLoad() {
user.setId(id);
user.setEmailAddress(emailAddress);
}
/** Creates a {@link VKey} instance for this entity. */
@@ -80,6 +84,7 @@ public class UserUpdateHistory extends ConsoleUpdateHistory {
public Builder setUser(User user) {
getInstance().user = user;
getInstance().id = user.getId();
getInstance().emailAddress = user.getEmailAddress();
return this;
}
}
@@ -17,7 +17,6 @@ package google.registry.model.console;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static org.junit.jupiter.api.Assertions.assertThrows;
import google.registry.model.EntityTestCase;
import org.junit.jupiter.api.Test;
@@ -64,7 +63,7 @@ public class UserDaoTest extends EntityTestCase {
}
@Test
void testFailure_sameEmail() {
void testSuccess_updateUser_sameEmail() {
User user1 =
new User.Builder()
.setEmailAddress("email@email.com")
@@ -73,12 +72,12 @@ public class UserDaoTest extends EntityTestCase {
User user2 =
new User.Builder()
.setEmailAddress("email@email.com")
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.SUPPORT_AGENT).build())
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build())
.build();
UserDao.saveUser(user1);
assertThrows(IllegalArgumentException.class, () -> UserDao.saveUser(user2));
UserDao.saveUser(user2);
assertAboutImmutableObjects()
.that(user1)
.isEqualExceptFields(UserDao.loadUser("email@email.com").get(), "id", "updateTimestamp");
.that(user2)
.isEqualExceptFields(UserDao.loadUser("email@email.com").get(), "updateTimestamp");
}
}
@@ -28,6 +28,7 @@ public class GetUserCommandTest extends CommandTestCase<GetUserCommand> {
@BeforeEach
void beforeEach() {
User.ID_GENERATOR_FOR_TESTING.set(0L);
UserDao.saveUser(
new User.Builder()
.setEmailAddress("johndoe@theregistrar.com")
@@ -52,7 +53,7 @@ public class GetUserCommandTest extends CommandTestCase<GetUserCommand> {
"""
User: {
emailAddress=fte@google.com
id=2
id=1
registryLockEmailAddress=null
registryLockPasswordHash=null
registryLockPasswordSalt=null
@@ -75,7 +76,7 @@ public class GetUserCommandTest extends CommandTestCase<GetUserCommand> {
"""
User: {
emailAddress=johndoe@theregistrar.com
id=1
id=0
registryLockEmailAddress=null
registryLockPasswordHash=null
registryLockPasswordSalt=null
@@ -90,7 +91,7 @@ public class GetUserCommandTest extends CommandTestCase<GetUserCommand> {
}
User: {
emailAddress=fte@google.com
id=2
id=1
registryLockEmailAddress=null
registryLockPasswordHash=null
registryLockPasswordSalt=null
@@ -113,7 +114,7 @@ public class GetUserCommandTest extends CommandTestCase<GetUserCommand> {
"""
User: {
emailAddress=johndoe@theregistrar.com
id=1
id=0
registryLockEmailAddress=null
registryLockPasswordHash=null
registryLockPasswordSalt=null
File diff suppressed because it is too large Load Diff
@@ -257,15 +257,15 @@ td.section {
<tbody>
<tr>
<td class="property_name">generated by</td>
<td class="property_value">SchemaCrawler 16.21.2</td>
<td class="property_value">SchemaCrawler 16.21.4</td>
</tr>
<tr>
<td class="property_name">generated on</td>
<td class="property_value">2024-06-20 20:07:00</td>
<td class="property_value">2024-07-25 18:42:05</td>
</tr>
<tr>
<td class="property_name">last flyway file</td>
<td id="lastFlywayFile" class="property_value">V173__create_feature_flag_table.sql</td>
<td id="lastFlywayFile" class="property_value">V174__user_pkey.sql</td>
</tr>
</tbody>
</table>
@@ -278,9 +278,9 @@ td.section {
</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-9412 4738,-9412 4738,4 -4,4" />
<text text-anchor="start" x="4494" y="-29.8" font-family="Helvetica,sans-Serif" font-size="14.00">generated by</text>
<text text-anchor="start" x="4577" y="-29.8" font-family="Helvetica,sans-Serif" font-size="14.00">SchemaCrawler 16.21.2</text>
<text text-anchor="start" x="4577" y="-29.8" font-family="Helvetica,sans-Serif" font-size="14.00">SchemaCrawler 16.21.4</text>
<text text-anchor="start" x="4493" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">generated on</text>
<text text-anchor="start" x="4577" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">2024-06-20 20:07:00</text>
<text text-anchor="start" x="4577" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">2024-07-25 18:42:05</text>
<polygon fill="none" stroke="#888888" points="4490,-4 4490,-44 4726,-44 4726,-4 4490,-4" /> <!-- allocationtoken_a08ccbef -->
<g id="node1" class="node">
<title>
@@ -2071,12 +2071,12 @@ td.section {
<text text-anchor="start" x="713" y="-504.3" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">public."User"</text>
<polygon fill="#e9c2f2" stroke="transparent" points="901,-497.5 901,-516.5 1011,-516.5 1011,-497.5 901,-497.5" />
<text text-anchor="start" x="972" y="-503.3" font-family="Helvetica,sans-Serif" font-size="14.00">[table]</text>
<text text-anchor="start" x="713" y="-485.3" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">id</text>
<text text-anchor="start" x="713" y="-484.3" font-family="Helvetica,sans-Serif" font-size="14.00">id</text>
<text text-anchor="start" x="895" y="-484.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="903" y="-484.3" font-family="Helvetica,sans-Serif" font-size="14.00">bigserial not null</text>
<text text-anchor="start" x="903" y="-484.3" font-family="Helvetica,sans-Serif" font-size="14.00">bigserial</text>
<text text-anchor="start" x="895" y="-465.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="903" y="-465.3" font-family="Helvetica,sans-Serif" font-size="14.00">auto-incremented</text>
<text text-anchor="start" x="713" y="-446.3" font-family="Helvetica,sans-Serif" font-size="14.00">email_address</text>
<text text-anchor="start" x="713" y="-447.3" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">email_address</text>
<text text-anchor="start" x="895" y="-446.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="903" y="-446.3" font-family="Helvetica,sans-Serif" font-size="14.00">text not null</text>
<text text-anchor="start" x="713" y="-427.3" font-family="Helvetica,sans-Serif" font-size="14.00">registry_lock_password_hash</text>
@@ -13113,8 +13113,8 @@ td.section {
<tbody>
<tr>
<td class="spacer"></td>
<td class="minwidth"><b><i>id</i></b></td>
<td class="minwidth">bigserial not null</td>
<td class="minwidth">id</td>
<td class="minwidth">bigserial</td>
</tr>
<tr>
<td class="spacer"></td>
@@ -13123,7 +13123,7 @@ td.section {
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth">email_address</td>
<td class="minwidth"><b><i>email_address</i></b></td>
<td class="minwidth">text not null</td>
</tr>
<tr>
@@ -13176,7 +13176,7 @@ td.section {
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth">id</td>
<td class="minwidth">email_address</td>
<td class="minwidth"></td>
</tr>
<tr>
@@ -13260,7 +13260,7 @@ td.section {
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth">id</td>
<td class="minwidth">email_address</td>
<td class="minwidth">ascending</td>
</tr>
<tr>
@@ -13275,18 +13275,6 @@ td.section {
<td class="minwidth">email_address</td>
<td class="minwidth">ascending</td>
</tr>
<tr>
<td colspan="3"></td>
</tr>
<tr>
<td colspan="2" class="name">user_email_address_idx</td>
<td class="description right">[non-unique index]</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth">email_address</td>
<td class="minwidth">ascending</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
+1
View File
@@ -171,3 +171,4 @@ V170__user_update_registry_lock_email.sql
V171__drop_create_billing_cost_columns_from_tld.sql
V172__allocation_token_renewal_price.sql
V173__create_feature_flag_table.sql
V174__user_pkey.sql
@@ -0,0 +1,18 @@
-- Copyright 2024 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 "User" DROP CONSTRAINT "User_pkey";
ALTER TABLE "User" ADD CONSTRAINT "User_pkey" PRIMARY KEY (email_address);
ALTER TABLE "User" ALTER COLUMN id DROP NOT NULL;
DROP INDEX user_email_address_idx;
@@ -885,16 +885,16 @@
);
create table "User" (
id bigserial not null,
email_address text not null,
update_timestamp timestamptz,
email_address text not null,
registry_lock_email_address text,
registry_lock_password_hash text,
registry_lock_password_salt text,
global_role text not null,
is_admin boolean not null,
registrar_roles hstore,
primary key (id)
id int8,
primary key (email_address)
);
create table "UserUpdateHistory" (
@@ -904,8 +904,8 @@
history_request_body text,
history_type text not null,
history_url text not null,
user_id int8 not null,
email_address text not null,
user_id int8 not null,
registry_lock_email_address text,
registry_lock_password_hash text,
registry_lock_password_salt text,
@@ -1016,17 +1016,13 @@ create index reservedlist_name_idx on "ReservedList" (name);
create index spec11threatmatch_registrar_id_idx on "Spec11ThreatMatch" (registrar_id);
create index spec11threatmatch_tld_idx on "Spec11ThreatMatch" (tld);
create index spec11threatmatch_check_date_idx on "Spec11ThreatMatch" (check_date);
create index user_email_address_idx on "User" (email_address);
alter table if exists "User"
add constraint UK_seshmd8qn3lcffsjw5j8wnbnd unique (email_address);
create index IDXbjacjlm8ianc4kxxvamnu94k5 on "UserUpdateHistory" (history_acting_user);
create index IDX5yqacw829y5bm6f7eajsq1cts on "UserUpdateHistory" (email_address);
alter table if exists "ConsoleEppActionHistory"
add constraint FKb686b9os2nsjpv930npa4r3b4
foreign key (history_acting_user)
references "User" (email_address);
references "User";
alter table if exists "DelegationSignerData"
add constraint FKtr24j9v14ph2mfuw2gsmt12kq
@@ -1066,12 +1062,12 @@ create index IDX5yqacw829y5bm6f7eajsq1cts on "UserUpdateHistory" (email_address)
alter table if exists "RegistrarPocUpdateHistory"
add constraint FKftpbwctxtkc1i0njc0tdcaa2g
foreign key (history_acting_user)
references "User" (email_address);
references "User";
alter table if exists "RegistrarUpdateHistory"
add constraint FKsr7w342s7x5s5jvdti2axqeq8
foreign key (history_acting_user)
references "User" (email_address);
references "User";
alter table if exists "RegistryLock"
add constraint FK2lhcwpxlnqijr96irylrh1707
@@ -1086,4 +1082,4 @@ create index IDX5yqacw829y5bm6f7eajsq1cts on "UserUpdateHistory" (email_address)
alter table if exists "UserUpdateHistory"
add constraint FK1s7bopbl3pwrhv3jkkofnv3o0
foreign key (history_acting_user)
references "User" (email_address);
references "User";
@@ -1317,7 +1317,7 @@ CREATE TABLE public."TmchCrl" (
--
CREATE TABLE public."User" (
id bigint NOT NULL,
id bigint,
email_address text NOT NULL,
registry_lock_password_hash text,
registry_lock_password_salt text,
@@ -1833,7 +1833,7 @@ ALTER TABLE ONLY public."UserUpdateHistory"
--
ALTER TABLE ONLY public."User"
ADD CONSTRAINT "User_pkey" PRIMARY KEY (id);
ADD CONSTRAINT "User_pkey" PRIMARY KEY (email_address);
--
@@ -2547,13 +2547,6 @@ CREATE INDEX spec11threatmatch_registrar_id_idx ON public."Spec11ThreatMatch" US
CREATE INDEX spec11threatmatch_tld_idx ON public."Spec11ThreatMatch" USING btree (tld);
--
-- Name: user_email_address_idx; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX user_email_address_idx ON public."User" USING btree (email_address);
--
-- Name: UserUpdateHistory fk1s7bopbl3pwrhv3jkkofnv3o0; Type: FK CONSTRAINT; Schema: public; Owner: -
--