diff --git a/core/src/main/java/google/registry/model/console/ConsolePermission.java b/core/src/main/java/google/registry/model/console/ConsolePermission.java new file mode 100644 index 000000000..edcf01edb --- /dev/null +++ b/core/src/main/java/google/registry/model/console/ConsolePermission.java @@ -0,0 +1,67 @@ +// 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.model.console; + +/** Permissions that users may have in the UI, either per-registrar or globally. */ +public enum ConsolePermission { + /** Add, update, or remove other console users. */ + MANAGE_USERS, + /** Add, update, or remove registrars. */ + MANAGE_REGISTRARS, + /** Manage related registrars, e.g. when one registrar owns another. */ + MANAGE_ACCREDITATION, + /** Set up the EPP connection (e.g. certs). */ + CONFIGURE_EPP_CONNECTION, + /** Retrieve the unredacted registrant email from a domain. */ + GET_REGISTRANT_EMAIL, + /** Suspend a domain for compliance (non-URS) reasons. */ + SUSPEND_DOMAIN, + /** Suspend a domain for the Uniform Rapid Suspension process. */ + SUSPEND_DOMAIN_URS, + /** Download a list of domains under management. */ + DOWNLOAD_DOMAINS, + /** Change the password for a registrar. */ + CHANGE_NOMULUS_PASSWORD, + /** View all possible TLDs. */ + VIEW_TLD_PORTFOLIO, + /** Onboarding the registrar(s) to extra programs like registry locking. */ + ONBOARD_ADDITIONAL_PROGRAMS, + /** Execute arbitrary EPP commands through the UI. */ + EXECUTE_EPP_COMMANDS, + /** Send messages to the registry support team. */ + CONTACT_SUPPORT, + /** Access billing and payment details for a registrar. */ + ACCESS_BILLING_DETAILS, + /** Access any available documentation about the registry. */ + ACCESS_DOCUMENTATION, + /** Change the documentation available in the UI about the registry. */ + MANAGE_DOCUMENTATION, + /** Viewing the onboarding status of a registrar on a TLD. */ + CHECK_ONBOARDING_STATUS, + /** View premium and/or reserved lists for a TLD. */ + VIEW_PREMIUM_RESERVED_LISTS, + /** Sign and/or change legal documents like RRAs. */ + UPLOAD_CONTRACTS, + /** Viewing legal documents like RRAs. */ + ACCESS_CONTRACTS, + /** View analytics on operations and billing data (possibly TBD). */ + VIEW_OPERATIONAL_DATA, + /** Create announcements that can be displayed in the UI. */ + SEND_ANNOUNCEMENTS, + /** View announcements in the UI. */ + VIEW_ANNOUNCEMENTS, + /** Viewing a record of actions performed in the UI for a particular registrar. */ + VIEW_ACTIVITY_LOG +} diff --git a/core/src/main/java/google/registry/model/console/ConsoleRoleDefinitions.java b/core/src/main/java/google/registry/model/console/ConsoleRoleDefinitions.java new file mode 100644 index 000000000..fbf5db433 --- /dev/null +++ b/core/src/main/java/google/registry/model/console/ConsoleRoleDefinitions.java @@ -0,0 +1,104 @@ +// 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.model.console; + +import com.google.common.collect.ImmutableSet; + +/** + * Definitions of the {@link ConsolePermission} sets that each role contains. + * + *

Note: within the "registry" and "registrar" groupings, permissions expand hierarchically, + * where each role has all the permissions of the role below it plus some others. + */ +public class ConsoleRoleDefinitions { + + /** Permissions for a registry support agent. */ + static final ImmutableSet SUPPORT_AGENT_PERMISSIONS = + ImmutableSet.of( + ConsolePermission.MANAGE_USERS, + ConsolePermission.MANAGE_ACCREDITATION, + ConsolePermission.CONFIGURE_EPP_CONNECTION, + ConsolePermission.DOWNLOAD_DOMAINS, + ConsolePermission.VIEW_TLD_PORTFOLIO, + ConsolePermission.ONBOARD_ADDITIONAL_PROGRAMS, + ConsolePermission.CONTACT_SUPPORT, + ConsolePermission.ACCESS_BILLING_DETAILS, + ConsolePermission.ACCESS_DOCUMENTATION, + ConsolePermission.SUSPEND_DOMAIN_URS, + ConsolePermission.CHECK_ONBOARDING_STATUS, + ConsolePermission.VIEW_PREMIUM_RESERVED_LISTS, + ConsolePermission.UPLOAD_CONTRACTS, + ConsolePermission.ACCESS_CONTRACTS, + ConsolePermission.VIEW_OPERATIONAL_DATA, + ConsolePermission.SEND_ANNOUNCEMENTS, + ConsolePermission.VIEW_ANNOUNCEMENTS, + ConsolePermission.VIEW_ACTIVITY_LOG); + + /** Permissions for a registry support lead. */ + static final ImmutableSet SUPPORT_LEAD_PERMISSIONS = + new ImmutableSet.Builder() + .addAll(SUPPORT_AGENT_PERMISSIONS) + .add( + ConsolePermission.MANAGE_REGISTRARS, + ConsolePermission.GET_REGISTRANT_EMAIL, + ConsolePermission.SUSPEND_DOMAIN, + ConsolePermission.EXECUTE_EPP_COMMANDS, + ConsolePermission.MANAGE_DOCUMENTATION) + .build(); + + /** Permissions for a registry full-time employee. */ + static final ImmutableSet FTE_PERMISSIONS = + new ImmutableSet.Builder() + .addAll(SUPPORT_LEAD_PERMISSIONS) + .add(ConsolePermission.CHANGE_NOMULUS_PASSWORD) + .build(); + + /** Permissions for a registrar partner account manager. */ + static final ImmutableSet ACCOUNT_MANAGER_PERMISSIONS = + ImmutableSet.of( + ConsolePermission.DOWNLOAD_DOMAINS, + ConsolePermission.VIEW_TLD_PORTFOLIO, + ConsolePermission.CONTACT_SUPPORT, + ConsolePermission.ACCESS_DOCUMENTATION, + ConsolePermission.VIEW_PREMIUM_RESERVED_LISTS, + ConsolePermission.VIEW_OPERATIONAL_DATA, + ConsolePermission.VIEW_ANNOUNCEMENTS); + + /** Permissions for the tech contact of a registrar. */ + static final ImmutableSet TECH_CONTACT_PERMISSIONS = + new ImmutableSet.Builder() + .addAll(ACCOUNT_MANAGER_PERMISSIONS) + .add( + ConsolePermission.MANAGE_ACCREDITATION, + ConsolePermission.CONFIGURE_EPP_CONNECTION, + ConsolePermission.CHANGE_NOMULUS_PASSWORD, + ConsolePermission.ONBOARD_ADDITIONAL_PROGRAMS, + ConsolePermission.EXECUTE_EPP_COMMANDS, + ConsolePermission.ACCESS_BILLING_DETAILS, + ConsolePermission.CHECK_ONBOARDING_STATUS, + ConsolePermission.UPLOAD_CONTRACTS, + ConsolePermission.ACCESS_CONTRACTS, + ConsolePermission.VIEW_ACTIVITY_LOG) + .build(); + + /** Permissions for the primary registrar contact. */ + static final ImmutableSet PRIMARY_CONTACT_PERMISSIONS = + new ImmutableSet.Builder() + .addAll(TECH_CONTACT_PERMISSIONS) + .add(ConsolePermission.MANAGE_USERS) + .build(); + + private ConsoleRoleDefinitions() {} +} diff --git a/core/src/main/java/google/registry/model/console/GlobalRole.java b/core/src/main/java/google/registry/model/console/GlobalRole.java new file mode 100644 index 000000000..ad17f7703 --- /dev/null +++ b/core/src/main/java/google/registry/model/console/GlobalRole.java @@ -0,0 +1,44 @@ +// 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.model.console; + +import static google.registry.model.console.ConsoleRoleDefinitions.FTE_PERMISSIONS; +import static google.registry.model.console.ConsoleRoleDefinitions.SUPPORT_AGENT_PERMISSIONS; +import static google.registry.model.console.ConsoleRoleDefinitions.SUPPORT_LEAD_PERMISSIONS; + +import com.google.common.collect.ImmutableSet; + +/** Roles for registry employees that apply across all registrars. */ +public enum GlobalRole { + + /** The user has no global role, i.e. they're a registrar partner. */ + NONE(ImmutableSet.of()), + /** The user is a registry support agent. */ + SUPPORT_AGENT(SUPPORT_AGENT_PERMISSIONS), + /** The user is a registry support lead. */ + SUPPORT_LEAD(SUPPORT_LEAD_PERMISSIONS), + /** The user is a registry full-time employee. */ + FTE(FTE_PERMISSIONS); + + private final ImmutableSet permissions; + + GlobalRole(ImmutableSet permissions) { + this.permissions = permissions; + } + + public boolean hasPermission(ConsolePermission permission) { + return permissions.contains(permission); + } +} diff --git a/core/src/main/java/google/registry/model/console/RegistrarRole.java b/core/src/main/java/google/registry/model/console/RegistrarRole.java new file mode 100644 index 000000000..503f0ca29 --- /dev/null +++ b/core/src/main/java/google/registry/model/console/RegistrarRole.java @@ -0,0 +1,42 @@ +// 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.model.console; + +import static google.registry.model.console.ConsoleRoleDefinitions.ACCOUNT_MANAGER_PERMISSIONS; +import static google.registry.model.console.ConsoleRoleDefinitions.PRIMARY_CONTACT_PERMISSIONS; +import static google.registry.model.console.ConsoleRoleDefinitions.TECH_CONTACT_PERMISSIONS; + +import com.google.common.collect.ImmutableSet; + +/** Roles for registrar partners that apply to only one registrar. */ +public enum RegistrarRole { + + /** The user is a standard account manager at a registrar. */ + ACCOUNT_MANAGER(ACCOUNT_MANAGER_PERMISSIONS), + /** The user is a technical contact of a registrar. */ + TECH_CONTACT(TECH_CONTACT_PERMISSIONS), + /** The user is the primary contact at a registrar. */ + PRIMARY_CONTACT(PRIMARY_CONTACT_PERMISSIONS); + + private final ImmutableSet permissions; + + RegistrarRole(ImmutableSet permissions) { + this.permissions = permissions; + } + + public boolean hasPermission(ConsolePermission permission) { + return permissions.contains(permission); + } +} diff --git a/core/src/main/java/google/registry/model/console/User.java b/core/src/main/java/google/registry/model/console/User.java new file mode 100644 index 000000000..b47a4fa31 --- /dev/null +++ b/core/src/main/java/google/registry/model/console/User.java @@ -0,0 +1,148 @@ +// 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.model.console; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Strings.isNullOrEmpty; +import static com.google.common.io.BaseEncoding.base64; +import static google.registry.model.registrar.Registrar.checkValidEmail; +import static google.registry.util.PasswordUtils.SALT_SUPPLIER; +import static google.registry.util.PasswordUtils.hashPassword; +import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; + +import google.registry.model.Buildable; +import google.registry.model.ImmutableObject; + +/** A console user, either a registry employee or a registrar partner. */ +public class User extends ImmutableObject implements Buildable { + + /** Autogenerated unique ID of this user. */ + private long id; + + /** GAIA ID associated with the user in question. */ + private String gaiaId; + + /** Email address of the user in question. */ + private String emailAddress; + + /** Roles (which grant permissions) associated with this user. */ + private UserRoles userRoles; + + /** + * Whether the contact is allowed to set their registry lock password through the registrar + * console. This will be set to false on contact creation and when the user sets a password. + */ + boolean allowedToSetRegistryLockPassword = false; + + /** + * A hashed password that exists iff this contact is registry-lock-enabled. The hash is a base64 + * encoded SHA256 string. + */ + String registryLockPasswordHash; + + /** Randomly generated hash salt. */ + String registryLockPasswordSalt; + + public String getGaiaId() { + return gaiaId; + } + + public String getEmailAddress() { + return emailAddress; + } + + public UserRoles getUserRoles() { + return userRoles; + } + + public boolean isAllowedToSetRegistryLockPassword() { + return allowedToSetRegistryLockPassword; + } + + public boolean isRegistryLockAllowed() { + return !isNullOrEmpty(registryLockPasswordHash) && !isNullOrEmpty(registryLockPasswordSalt); + } + + public boolean verifyRegistryLockPassword(String registryLockPassword) { + if (isNullOrEmpty(registryLockPassword) + || isNullOrEmpty(registryLockPasswordSalt) + || isNullOrEmpty(registryLockPasswordHash)) { + return false; + } + return hashPassword(registryLockPassword, registryLockPasswordSalt) + .equals(registryLockPasswordHash); + } + + @Override + public Builder asBuilder() { + return new Builder(clone(this)); + } + + /** Builder for constructing immutable {@link User} objects. */ + public static class Builder extends Buildable.Builder { + + public Builder() {} + + public Builder(User user) { + super(user); + } + + public User build() { + checkArgumentNotNull(getInstance().gaiaId, "Gaia ID cannot be null"); + checkArgumentNotNull(getInstance().emailAddress, "Email address cannot be null"); + checkArgumentNotNull(getInstance().userRoles, "User roles cannot be null"); + return super.build(); + } + + public Builder setGaiaId(String gaiaId) { + checkArgument(!isNullOrEmpty(gaiaId), "Gaia ID cannot be null or empty"); + getInstance().gaiaId = gaiaId; + return this; + } + + public Builder setEmailAddress(String emailAddress) { + getInstance().emailAddress = checkValidEmail(emailAddress); + return this; + } + + public Builder setUserRoles(UserRoles userRoles) { + checkArgumentNotNull(userRoles, "User roles cannot be null"); + getInstance().userRoles = userRoles; + return this; + } + + public Builder setAllowedToSetRegistryLockPassword(boolean allowedToSetRegistryLockPassword) { + if (allowedToSetRegistryLockPassword) { + getInstance().registryLockPasswordSalt = null; + getInstance().registryLockPasswordHash = null; + } + getInstance().allowedToSetRegistryLockPassword = allowedToSetRegistryLockPassword; + return this; + } + + public Builder setRegistryLockPassword(String registryLockPassword) { + checkArgument( + getInstance().allowedToSetRegistryLockPassword, + "Not allowed to set registry lock password for this user"); + checkArgument( + !isNullOrEmpty(registryLockPassword), "Registry lock password was null or empty"); + getInstance().registryLockPasswordSalt = base64().encode(SALT_SUPPLIER.get()); + getInstance().registryLockPasswordHash = + hashPassword(registryLockPassword, getInstance().registryLockPasswordSalt); + getInstance().allowedToSetRegistryLockPassword = false; + return this; + } + } +} diff --git a/core/src/main/java/google/registry/model/console/UserRoles.java b/core/src/main/java/google/registry/model/console/UserRoles.java new file mode 100644 index 000000000..882935bd5 --- /dev/null +++ b/core/src/main/java/google/registry/model/console/UserRoles.java @@ -0,0 +1,120 @@ +// 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.model.console; + +import static com.google.common.base.Preconditions.checkArgument; +import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; + +import com.google.common.collect.ImmutableMap; +import google.registry.model.Buildable; +import google.registry.model.ImmutableObject; +import java.util.Map; + +/** + * Contains the global and per-registrar roles for a given user. + * + *

See go/nomulus-console-authz for more + * information. + */ +public class UserRoles extends ImmutableObject implements Buildable { + + /** Whether the user is a global admin, who has access to everything. */ + private boolean isAdmin = false; + + /** + * The global role (e.g. {@link GlobalRole#SUPPORT_AGENT}) that the user has across all + * registrars. + */ + private GlobalRole globalRole = GlobalRole.NONE; + + /** Any per-registrar roles that this user may have. */ + private Map registrarRoles = ImmutableMap.of(); + + /** Whether the user is a global admin, who has access to everything. */ + public boolean isAdmin() { + return isAdmin; + } + + /** + * The global role (e.g. {@link GlobalRole#SUPPORT_AGENT}) that the user has across all + * registrars. + */ + public GlobalRole getGlobalRole() { + return globalRole; + } + + /** Any per-registrar roles that this user may have. */ + public Map getRegistrarRoles() { + return ImmutableMap.copyOf(registrarRoles); + } + + /** If the user has the given permission either globally or on the given registrar. */ + public boolean hasPermission(String registrarId, ConsolePermission permission) { + if (hasGlobalPermission(permission)) { + return true; + } + if (!registrarRoles.containsKey(registrarId)) { + return false; + } + return registrarRoles.get(registrarId).hasPermission(permission); + } + + /** If the user has the given permission globally across all registrars. */ + public boolean hasGlobalPermission(ConsolePermission permission) { + return isAdmin || globalRole.hasPermission(permission); + } + + @Override + public Builder asBuilder() { + return new Builder(clone(this)); + } + + /** Builder for constructing immutable {@link UserRoles} objects. */ + public static class Builder extends Buildable.Builder { + + public Builder() {} + + private Builder(UserRoles userRoles) { + super(userRoles); + } + + @Override + public UserRoles build() { + // Users should only have a global role or per-registrar roles, not both + checkArgument( + getInstance().globalRole.equals(GlobalRole.NONE) + || getInstance().registrarRoles.isEmpty(), + "Users cannot have both global and per-registrar roles"); + return super.build(); + } + + public Builder setIsAdmin(boolean isAdmin) { + getInstance().isAdmin = isAdmin; + return this; + } + + public Builder setGlobalRole(GlobalRole globalRole) { + checkArgumentNotNull(globalRole, "Global role cannot be null"); + getInstance().globalRole = globalRole; + return this; + } + + public Builder setRegistrarRoles(Map registrarRoles) { + checkArgumentNotNull(registrarRoles, "Registrar roles map cannot be null"); + getInstance().registrarRoles = ImmutableMap.copyOf(registrarRoles); + return this; + } + } +} diff --git a/core/src/main/java/google/registry/model/registrar/Registrar.java b/core/src/main/java/google/registry/model/registrar/Registrar.java index c5ed3a0c6..185a65e14 100644 --- a/core/src/main/java/google/registry/model/registrar/Registrar.java +++ b/core/src/main/java/google/registry/model/registrar/Registrar.java @@ -964,7 +964,7 @@ public class Registrar extends ImmutableObject } /** Verifies that the email address in question is not null and has a valid format. */ - static String checkValidEmail(String email) { + public static String checkValidEmail(String email) { checkNotNull(email, "Provided email was null"); try { InternetAddress internetAddress = new InternetAddress(email, true); diff --git a/core/src/test/java/google/registry/model/console/ConsoleRoleDefinitionsTest.java b/core/src/test/java/google/registry/model/console/ConsoleRoleDefinitionsTest.java new file mode 100644 index 000000000..15dcddbd3 --- /dev/null +++ b/core/src/test/java/google/registry/model/console/ConsoleRoleDefinitionsTest.java @@ -0,0 +1,69 @@ +// 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.model.console; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.jupiter.api.Test; + +/** Tests for {@link ConsoleRoleDefinitions}. */ +public class ConsoleRoleDefinitionsTest { + + @Test + void testHierarchicalPermissions_registry() { + // Note: we can't use Truth's IterableSubject to check all the subset/superset restrictions + // because it is generic to iterables and doesn't know about sets. + assertThat( + ConsoleRoleDefinitions.SUPPORT_LEAD_PERMISSIONS.containsAll( + ConsoleRoleDefinitions.SUPPORT_AGENT_PERMISSIONS)) + .isTrue(); + assertThat( + ConsoleRoleDefinitions.SUPPORT_AGENT_PERMISSIONS.containsAll( + ConsoleRoleDefinitions.SUPPORT_LEAD_PERMISSIONS)) + .isFalse(); + + assertThat( + ConsoleRoleDefinitions.FTE_PERMISSIONS.containsAll( + ConsoleRoleDefinitions.SUPPORT_LEAD_PERMISSIONS)) + .isTrue(); + assertThat( + ConsoleRoleDefinitions.SUPPORT_LEAD_PERMISSIONS.containsAll( + ConsoleRoleDefinitions.FTE_PERMISSIONS)) + .isFalse(); + } + + @Test + void testHierarchicalPermissions_registrar() { + // Note: we can't use Truth's IterableSubject to check all the subset/superset restrictions + // because it is generic to iterables and doesn't know about sets. + assertThat( + ConsoleRoleDefinitions.SUPPORT_LEAD_PERMISSIONS.containsAll( + ConsoleRoleDefinitions.SUPPORT_AGENT_PERMISSIONS)) + .isTrue(); + assertThat( + ConsoleRoleDefinitions.SUPPORT_AGENT_PERMISSIONS.containsAll( + ConsoleRoleDefinitions.SUPPORT_LEAD_PERMISSIONS)) + .isFalse(); + + assertThat( + ConsoleRoleDefinitions.SUPPORT_LEAD_PERMISSIONS.containsAll( + ConsoleRoleDefinitions.SUPPORT_AGENT_PERMISSIONS)) + .isTrue(); + assertThat( + ConsoleRoleDefinitions.SUPPORT_AGENT_PERMISSIONS.containsAll( + ConsoleRoleDefinitions.SUPPORT_LEAD_PERMISSIONS)) + .isFalse(); + } +} diff --git a/core/src/test/java/google/registry/model/console/UserRolesTest.java b/core/src/test/java/google/registry/model/console/UserRolesTest.java new file mode 100644 index 000000000..f40a7e663 --- /dev/null +++ b/core/src/test/java/google/registry/model/console/UserRolesTest.java @@ -0,0 +1,77 @@ +// 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.model.console; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.google.common.collect.ImmutableMap; +import org.junit.jupiter.api.Test; + +/** Tests for {@link UserRoles}. */ +public class UserRolesTest { + + @Test + void testDefaults() { + UserRoles userRoles = new UserRoles.Builder().build(); + for (ConsolePermission permission : ConsolePermission.values()) { + assertThat(userRoles.getGlobalRole().hasPermission(permission)).isFalse(); + } + assertThat(userRoles.getRegistrarRoles()).isEmpty(); + assertThat(userRoles.isAdmin()).isFalse(); + } + + @Test + void testAdmin_overridesAll() { + UserRoles userRoles = new UserRoles.Builder().setIsAdmin(true).build(); + for (ConsolePermission permission : ConsolePermission.values()) { + assertThat(userRoles.hasGlobalPermission(permission)).isTrue(); + assertThat(userRoles.hasPermission("TheRegistrar", permission)).isTrue(); + } + } + + @Test + void testRegistrarPermission_withGlobal() { + UserRoles userRoles = new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build(); + for (ConsolePermission permission : ConsolePermission.values()) { + assertThat(userRoles.hasGlobalPermission(permission)).isTrue(); + assertThat(userRoles.hasPermission("TheRegistrar", permission)).isTrue(); + } + } + + @Test + void testRegistrarRoles() { + UserRoles userRoles = + new UserRoles.Builder() + .setGlobalRole(GlobalRole.NONE) + .setIsAdmin(false) + .setRegistrarRoles(ImmutableMap.of("TheRegistrar", RegistrarRole.PRIMARY_CONTACT)) + .build(); + assertThat(userRoles.hasPermission("TheRegistrar", ConsolePermission.MANAGE_USERS)).isTrue(); + assertThat(userRoles.hasPermission("TheRegistrar", ConsolePermission.SUSPEND_DOMAIN)).isFalse(); + assertThat(userRoles.hasPermission("nonexistent", ConsolePermission.MANAGE_USERS)).isFalse(); + } + + @Test + void testFailure_globalOrPerRegistrar() { + UserRoles.Builder builder = + new UserRoles.Builder() + .setGlobalRole(GlobalRole.SUPPORT_AGENT) + .setRegistrarRoles(ImmutableMap.of("TheRegistrar", RegistrarRole.PRIMARY_CONTACT)); + assertThat(assertThrows(IllegalArgumentException.class, builder::build)) + .hasMessageThat() + .isEqualTo("Users cannot have both global and per-registrar roles"); + } +} diff --git a/core/src/test/java/google/registry/model/console/UserTest.java b/core/src/test/java/google/registry/model/console/UserTest.java new file mode 100644 index 000000000..596a35482 --- /dev/null +++ b/core/src/test/java/google/registry/model/console/UserTest.java @@ -0,0 +1,61 @@ +// 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.model.console; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +/** Tests for {@link User}. */ +public class UserTest { + + @Test + void testFailure_badInputs() { + User.Builder builder = new User.Builder(); + assertThat(assertThrows(IllegalArgumentException.class, () -> builder.setGaiaId(null))) + .hasMessageThat() + .isEqualTo("Gaia ID cannot be null or empty"); + assertThat(assertThrows(IllegalArgumentException.class, () -> builder.setEmailAddress(""))) + .hasMessageThat() + .isEqualTo("Provided email is not a valid email address"); + assertThat(assertThrows(NullPointerException.class, () -> builder.setEmailAddress(null))) + .hasMessageThat() + .isEqualTo("Provided email was null"); + assertThat( + assertThrows( + IllegalArgumentException.class, () -> builder.setEmailAddress("invalidEmail"))) + .hasMessageThat() + .isEqualTo("Provided email invalidEmail is not a valid email address"); + assertThat(assertThrows(IllegalArgumentException.class, () -> builder.setUserRoles(null))) + .hasMessageThat() + .isEqualTo("User roles cannot be null"); + + assertThat(assertThrows(IllegalArgumentException.class, builder::build)) + .hasMessageThat() + .isEqualTo("Gaia ID cannot be null"); + builder.setGaiaId("gaiaId"); + assertThat(assertThrows(IllegalArgumentException.class, builder::build)) + .hasMessageThat() + .isEqualTo("Email address cannot be null"); + builder.setEmailAddress("email@email.com"); + assertThat(assertThrows(IllegalArgumentException.class, builder::build)) + .hasMessageThat() + .isEqualTo("User roles cannot be null"); + + builder.setUserRoles(new UserRoles.Builder().build()); + builder.build(); + } +}