diff --git a/core/src/main/java/google/registry/model/console/User.java b/core/src/main/java/google/registry/model/console/User.java index 5550307d6..6a1345199 100644 --- a/core/src/main/java/google/registry/model/console/User.java +++ b/core/src/main/java/google/registry/model/console/User.java @@ -24,20 +24,34 @@ import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; import google.registry.model.Buildable; import google.registry.model.ImmutableObject; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Index; +import javax.persistence.Table; /** A console user, either a registry employee or a registrar partner. */ +@Entity +@Table( + indexes = { + @Index(columnList = "gaiaId", name = "user_gaia_id_idx"), + @Index(columnList = "emailAddress", name = "user_email_address_idx") + }) public class User extends ImmutableObject implements Buildable { /** Autogenerated unique ID of this user. */ - private long id; + @Id private long id; /** GAIA ID associated with the user in question. */ + @Column(nullable = false) private String gaiaId; /** Email address of the user in question. */ + @Column(nullable = false) private String emailAddress; /** Roles (which grant permissions) associated with this user. */ + @Column(nullable = false) private UserRoles userRoles; /** diff --git a/core/src/main/java/google/registry/model/console/UserRoles.java b/core/src/main/java/google/registry/model/console/UserRoles.java index 882935bd5..e21d8616f 100644 --- a/core/src/main/java/google/registry/model/console/UserRoles.java +++ b/core/src/main/java/google/registry/model/console/UserRoles.java @@ -21,6 +21,10 @@ import com.google.common.collect.ImmutableMap; import google.registry.model.Buildable; import google.registry.model.ImmutableObject; import java.util.Map; +import javax.persistence.Column; +import javax.persistence.Embeddable; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; /** * Contains the global and per-registrar roles for a given user. @@ -28,15 +32,19 @@ import java.util.Map; *

See go/nomulus-console-authz for more * information. */ +@Embeddable public class UserRoles extends ImmutableObject implements Buildable { /** Whether the user is a global admin, who has access to everything. */ + @Column(nullable = false) private boolean isAdmin = false; /** * The global role (e.g. {@link GlobalRole#SUPPORT_AGENT}) that the user has across all * registrars. */ + @Enumerated(EnumType.STRING) + @Column(nullable = false) private GlobalRole globalRole = GlobalRole.NONE; /** Any per-registrar roles that this user may have. */ diff --git a/core/src/main/java/google/registry/persistence/converter/RegistrarToRoleConverter.java b/core/src/main/java/google/registry/persistence/converter/RegistrarToRoleConverter.java new file mode 100644 index 000000000..0ac303875 --- /dev/null +++ b/core/src/main/java/google/registry/persistence/converter/RegistrarToRoleConverter.java @@ -0,0 +1,50 @@ +// Copyright 2022 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 google.registry.model.console.RegistrarRole; +import java.util.Map; +import javax.persistence.Converter; + +/** JPA converter for storing / retrieving {@code Map} objects. */ +@Converter(autoApply = true) +public class RegistrarToRoleConverter + extends StringMapConverterBase> { + + @Override + protected String convertKeyToString(String key) { + return key; + } + + @Override + protected String convertValueToString(RegistrarRole value) { + return value.toString(); + } + + @Override + protected String convertStringToKey(String string) { + return string; + } + + @Override + protected RegistrarRole convertStringToValue(String string) { + return RegistrarRole.valueOf(string); + } + + @Override + protected Map convertMapToDerivedType(Map map) { + return map; + } +} diff --git a/core/src/main/resources/META-INF/persistence.xml b/core/src/main/resources/META-INF/persistence.xml index ccbe28e1a..1a790641f 100644 --- a/core/src/main/resources/META-INF/persistence.xml +++ b/core/src/main/resources/META-INF/persistence.xml @@ -43,6 +43,7 @@ google.registry.model.billing.BillingEvent$Recurring google.registry.model.common.Cursor google.registry.model.common.DatabaseMigrationStateSchedule + google.registry.model.console.User google.registry.model.contact.ContactHistory google.registry.model.contact.ContactResource google.registry.model.domain.Domain @@ -91,6 +92,7 @@ google.registry.persistence.converter.LocalDateConverter google.registry.persistence.converter.PostalInfoChoiceListConverter google.registry.persistence.converter.RegistrarPocSetConverter + google.registry.persistence.converter.RegistrarToRoleConverter google.registry.persistence.converter.Spec11ThreatMatchThreatTypeSetConverter google.registry.persistence.converter.StatusValueSetConverter google.registry.persistence.converter.StringListConverter diff --git a/core/src/test/java/google/registry/model/console/UserTest.java b/core/src/test/java/google/registry/model/console/UserTest.java index ac4fb0403..fe03f1458 100644 --- a/core/src/test/java/google/registry/model/console/UserTest.java +++ b/core/src/test/java/google/registry/model/console/UserTest.java @@ -15,12 +15,71 @@ package google.registry.model.console; import static com.google.common.truth.Truth.assertThat; +import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; +import static google.registry.testing.DatabaseHelper.persistResource; import static org.junit.jupiter.api.Assertions.assertThrows; +import google.registry.model.EntityTestCase; import org.junit.jupiter.api.Test; /** Tests for {@link User}. */ -public class UserTest { +public class UserTest extends EntityTestCase { + + UserTest() { + super(JpaEntityCoverageCheck.ENABLED); + } + + @Test + void testPersistence_lookupByEmail() { + User user = + new User.Builder() + .setGaiaId("gaiaId") + .setEmailAddress("email@email.com") + .setUserRoles( + new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).setIsAdmin(true).build()) + .build(); + persistResource(user); + jpaTm() + .transact( + () -> { + assertThat( + jpaTm() + .query("FROM User WHERE emailAddress = 'email@email.com'", User.class) + .getSingleResult()) + .isEqualTo(user); + assertThat( + jpaTm() + .query("FROM User WHERE emailAddress = 'bad@fake.com'", User.class) + .getResultList()) + .isEmpty(); + }); + } + + @Test + void testPersistence_lookupByGaiaId() { + User user = + new User.Builder() + .setGaiaId("gaiaId") + .setEmailAddress("email@email.com") + .setUserRoles( + new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).setIsAdmin(true).build()) + .build(); + persistResource(user); + jpaTm() + .transact( + () -> { + assertThat( + jpaTm() + .query("FROM User WHERE gaiaId = 'gaiaId'", User.class) + .getSingleResult()) + .isEqualTo(user); + assertThat( + jpaTm() + .query("FROM User WHERE gaiaId = 'badGaiaId'", User.class) + .getResultList()) + .isEmpty(); + }); + } @Test void testFailure_badInputs() { diff --git a/core/src/test/java/google/registry/persistence/converter/RegistrarToRoleConverterTest.java b/core/src/test/java/google/registry/persistence/converter/RegistrarToRoleConverterTest.java new file mode 100644 index 000000000..68168f995 --- /dev/null +++ b/core/src/test/java/google/registry/persistence/converter/RegistrarToRoleConverterTest.java @@ -0,0 +1,68 @@ +// Copyright 2022 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 com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import google.registry.model.ImmutableObject; +import google.registry.model.console.RegistrarRole; +import google.registry.persistence.transaction.JpaTestExtensions; +import google.registry.persistence.transaction.JpaTestExtensions.JpaUnitTestExtension; +import google.registry.testing.DatabaseHelper; +import java.util.Map; +import javax.persistence.Entity; +import javax.persistence.Id; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +/** Tests for {@link RegistrarToRoleConverter}. */ +public class RegistrarToRoleConverterTest { + + @RegisterExtension + public final JpaUnitTestExtension jpa = + new JpaTestExtensions.Builder().withEntityClass(TestEntity.class).buildUnitTestExtension(); + + @Test + void testRoundTripConversion() { + Map map = + ImmutableMap.of( + "TheRegistrar", + RegistrarRole.ACCOUNT_MANAGER, + "NewRegistrar", + RegistrarRole.PRIMARY_CONTACT, + "FooRegistrar", + RegistrarRole.TECH_CONTACT); + TestEntity entity = new TestEntity(map); + DatabaseHelper.insertInDb(entity); + TestEntity persisted = Iterables.getOnlyElement(DatabaseHelper.loadAllOf(TestEntity.class)); + assertThat(persisted.map).isEqualTo(map); + } + + @Entity(name = "TestEntity") + private static class TestEntity extends ImmutableObject { + + @Id String name = "id"; + + Map map; + + private TestEntity() {} + + private TestEntity(Map map) { + this.map = map; + } + } +} diff --git a/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java b/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java index f60920d3c..a3be844f0 100644 --- a/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java +++ b/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java @@ -18,6 +18,7 @@ import static com.google.common.truth.Truth.assert_; import google.registry.model.billing.BillingEventTest; import google.registry.model.common.CursorTest; +import google.registry.model.console.UserTest; import google.registry.model.contact.ContactResourceTest; import google.registry.model.domain.DomainSqlTest; import google.registry.model.domain.token.AllocationTokenTest; @@ -101,6 +102,7 @@ import org.junit.runner.RunWith; SignedMarkRevocationListDaoTest.class, Spec11ThreatMatchTest.class, TmchCrlTest.class, + UserTest.class, // AfterSuiteTest must be the last entry. See class javadoc for details. AfterSuiteTest.class }) 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 65efc7421..d0065992e 100644 --- a/db/src/main/resources/sql/schema/db-schema.sql.generated +++ b/db/src/main/resources/sql/schema/db-schema.sql.generated @@ -740,6 +740,18 @@ url text not null, primary key (id) ); + + create table "User" ( + id int8 not null, + email_address text not null, + gaia_id text not null, + 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) + ); create index allocation_token_domain_name_idx on "AllocationToken" (domain_name); create index IDX9g3s7mjv1yn4t06nqid39whss on "AllocationToken" (token_type); create index IDXtmlqd31dpvvd2g1h9i7erw6aj on "AllocationToken" (redemption_domain_repo_id); @@ -825,6 +837,8 @@ 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_gaia_id_idx on "User" (gaia_id); +create index user_email_address_idx on "User" (email_address); alter table if exists "DelegationSignerData" add constraint FKtr24j9v14ph2mfuw2gsmt12kq