From 1171c5cfcb2a415c373058aa8c87da4d3b9ff6cb Mon Sep 17 00:00:00 2001 From: Lai Jiang Date: Thu, 17 Oct 2024 16:48:10 -0400 Subject: [PATCH] Delete legacy console (#2579) --- appengine_war.gradle | 18 +- core/build.gradle | 160 +---- core/gradle.lockfile | 93 ++- .../registry/config/RegistryConfig.java | 75 +- .../config/RegistryConfigSettings.java | 9 - .../registry/config/files/default-config.yaml | 15 - .../alpha/default/WEB-INF/appengine-web.xml | 6 +- .../env/common/default/WEB-INF/web.xml | 83 +-- .../crash/default/WEB-INF/appengine-web.xml | 5 +- .../local/default/WEB-INF/appengine-web.xml | 11 +- .../default/WEB-INF/appengine-web.xml | 5 +- .../env/qa/default/WEB-INF/appengine-web.xml | 5 +- .../sandbox/default/WEB-INF/appengine-web.xml | 5 +- .../registry/model/common/FeatureFlag.java | 1 - .../frontend/FrontendRequestComponent.java | 22 +- .../registry/ui/assets/images/ajax-loader.gif | Bin 673 -> 0 bytes .../registry/ui/assets/images/android_sad.png | Bin 2601 -> 0 bytes .../images/disclosure_arrow_dk_grey.png | Bin 138 -> 0 bytes .../images/disclosure_arrow_dk_grey_down.png | Bin 138 -> 0 bytes .../disclosure_arrow_dk_grey_up_down.png | Bin 184 -> 0 bytes .../registry/ui/assets/images/explore_24.png | Bin 1016 -> 0 bytes .../registry/ui/assets/images/folder.png | Bin 309 -> 0 bytes .../ui/assets/images/google_registry.png | Bin 4555 -> 0 bytes .../registry/ui/assets/images/homeContact.png | Bin 3961 -> 0 bytes .../ui/assets/images/homeResources.png | Bin 3890 -> 0 bytes .../ui/assets/images/homeSettings.png | Bin 4741 -> 0 bytes .../ui/assets/images/ic_contacts_blue_12.png | Bin 535 -> 0 bytes .../ui/assets/images/ic_contacts_blue_8.png | Bin 327 -> 0 bytes .../assets/images/icons/svg/Gcomm/search.svg | 17 - .../ui/assets/images/icons/svg/search.svg | 17 - .../ui/assets/images/icons/svg/settings.svg | 19 - .../registry/ui/assets/images/loader1x.gif | Bin 28577 -> 0 bytes .../registry/ui/assets/images/loader4x.gif | Bin 81351 -> 0 bytes .../google/registry/ui/assets/images/logo.png | Bin 3358 -> 0 bytes .../registry/ui/assets/images/logo_sm.gif | Bin 3972 -> 0 bytes .../ui/assets/images/offline_lightning.png | Bin 1073 -> 0 bytes .../ui/assets/images/ribbon_certified-128.png | Bin 2726 -> 0 bytes .../ui/assets/images/ribbon_certified-64.png | Bin 1470 -> 0 bytes .../ui/assets/images/visibleOff_16.png | Bin 556 -> 0 bytes .../ui/assets/images/visibleOn_16.png | Bin 480 -> 0 bytes .../registry/ui/epptmpl/contact_info.xml | 14 - .../ui/epptmpl/contact_transfer_request.xml | 14 - .../domain_create_no_hosts_or_dsdata.xml | 18 - .../registry/ui/epptmpl/domain_delete.xml | 11 - .../epptmpl/domain_update_restore_request.xml | 17 - .../registry/ui/epptmpl/login_valid.xml | 18 - .../google/registry/ui/epptmpl/logout.xml | 6 - .../java/google/registry/ui/epptmpl/poll.xml | 6 - .../ui/server/console/ConsoleApiAction.java | 26 - .../console/ConsoleRegistryLockAction.java | 7 +- .../console/settings/ContactAction.java | 191 ++++- .../registrar/ConsoleOteSetupAction.java | 210 ------ .../ConsoleRegistrarCreatorAction.java | 331 --------- .../ui/server/registrar/ConsoleUiAction.java | 198 ------ .../ui/server/registrar/HtmlAction.java | 92 --- .../ui/server/registrar/JsonGetAction.java | 21 - .../ui/server/registrar/OteStatusAction.java | 126 ---- .../registrar/RegistrarConsoleMetrics.java | 69 -- .../registrar/RegistrarSettingsAction.java | 641 ----------------- .../registrar/RegistryLockGetAction.java | 182 ----- .../registrar/RegistryLockPostAction.java | 197 ------ .../registrar/RegistryLockVerifyAction.java | 91 --- .../ui/server/registrar/package-info.java | 16 - .../registry/module/RequestComponentTest.java | 21 +- .../registry/server/RegistryTestServer.java | 16 +- .../ui/server/ActionMembershipTest.java | 13 +- .../console/settings/ContactActionTest.java | 273 +++++-- .../registrar/ConsoleOteSetupActionTest.java | 212 ------ .../ConsoleRegistrarCreatorActionTest.java | 465 ------------ .../server/registrar/ConsoleUiActionTest.java | 203 ------ .../server/registrar/ContactSettingsTest.java | 409 ----------- .../server/registrar/OteStatusActionTest.java | 159 ----- .../RegistrarSettingsActionTest.java | 669 ------------------ .../RegistrarSettingsActionTestCase.java | 180 ----- .../registrar/RegistryLockGetActionTest.java | 405 ----------- .../registrar/RegistryLockPostActionTest.java | 547 -------------- .../RegistryLockVerifyActionTest.java | 336 --------- .../registrar/SecuritySettingsTest.java | 200 ------ .../server/registrar/WhoisSettingsTest.java | 131 ---- .../OteSetupConsoleScreenshotTest.java | 70 -- .../java/google/registry/webdriver/README.md | 2 +- .../RegistrarConsoleScreenshotTest.java | 545 -------------- .../webdriver/RegistrarConsoleWebTest.java | 205 ------ .../RegistrarCreateConsoleScreenshotTest.java | 81 --- .../module/frontend/frontend_routing.txt | 8 - .../ui/server/registrar/update_registrar.json | 71 -- .../update_registrar_duplicate_contacts.json | 70 -- .../registrar/update_registrar_email.txt | 19 - ...t_admin_fails_badEmail_oteResultFailed.png | Bin 46942 -> 0 bytes ...nshotTest_get_admin_succeeds_formEmpty.png | Bin 38471 -> 0 bytes ...shotTest_get_admin_succeeds_formFilled.png | Bin 39143 -> 0 bytes ...nshotTest_get_admin_succeeds_oteResult.png | Bin 54558 -> 0 bytes ...nshotTest_get_owner_fails_unauthorized.png | Bin 24713 -> 0 bytes ...arConsoleScreenshotTest_contactUs_page.png | Bin 66556 -> 0 bytes ...hotTest_deprecationWarning_active_page.png | Bin 81890 -> 0 bytes ...st_getOteStatus_completed_before_click.png | Bin 48664 -> 0 bytes ...shotTest_getOteStatus_completed_result.png | Bin 87975 -> 0 bytes ...t_getOteStatus_noButtonWhenReal_result.png | Bin 46990 -> 0 bytes ...tTest_getOteStatus_notCompleted_result.png | Bin 88799 -> 0 bytes ...tTest_indexPage_smallScrolledDown_page.png | Bin 10175 -> 0 bytes ...creenshotTest_index_adminAndOwner_page.png | Bin 78000 -> 0 bytes ...ConsoleScreenshotTest_index_admin_page.png | Bin 77608 -> 0 bytes ...ConsoleScreenshotTest_index_owner_page.png | Bin 73075 -> 0 bytes ...nshotTest_index_registrarDisabled_view.png | Bin 26608 -> 0 bytes ...otTest_registryLockVerify_success_page.png | Bin 24655 -> 0 bytes ...st_registryLockVerify_unknownLock_page.png | Bin 24790 -> 0 bytes ...ScreenshotTest_registryLock_empty_page.png | Bin 43823 -> 0 bytes ...enshotTest_registryLock_lockModal_page.png | Bin 52446 -> 0 bytes ...tTest_registryLock_nonEmpty_admin_page.png | Bin 62388 -> 0 bytes ...eenshotTest_registryLock_nonEmpty_page.png | Bin 47801 -> 0 bytes ...st_registryLock_notAllowedForUser_page.png | Bin 37009 -> 0 bytes ...nshotTest_registryLock_notAllowed_page.png | Bin 35639 -> 0 bytes ...shotTest_registryLock_unlockModal_page.png | Bin 61926 -> 0 bytes ...nshotTest_settingsAdmin_whenAdmin_edit.png | Bin 49519 -> 0 bytes ...nshotTest_settingsAdmin_whenAdmin_view.png | Bin 46805 -> 0 bytes ...tingsAdmin_whenNotAdmin_showsHome_view.png | Bin 70868 -> 0 bytes ...ScreenshotTest_settingsContactAdd_page.png | Bin 81101 -> 0 bytes ...creenshotTest_settingsContactEdit_page.png | Bin 88321 -> 0 bytes ...etRegistryLockPassword_alreadySet_page.png | Bin 88114 -> 0 bytes ...t_setRegistryLockPassword_contact_view.png | Bin 51230 -> 0 bytes ...LockPassword_notAllowedForContact_page.png | Bin 88321 -> 0 bytes ...egistryLockPassword_page_with_password.png | Bin 103271 -> 0 bytes ...Password_page_with_password_after_hide.png | Bin 98262 -> 0 bytes ...yLockPassword_page_with_shown_password.png | Bin 98839 -> 0 bytes ...tTest_settingsContactItem_asAdmin_page.png | Bin 43803 -> 0 bytes ...creenshotTest_settingsContactItem_page.png | Bin 41239 -> 0 bytes ...oleScreenshotTest_settingsContact_page.png | Bin 46068 -> 0 bytes ...hotTest_settingsSecurityWithCerts_edit.png | Bin 182098 -> 0 bytes ...hotTest_settingsSecurityWithCerts_view.png | Bin 180443 -> 0 bytes ...Test_settingsSecurityWithHashOnly_edit.png | Bin 63482 -> 0 bytes ...Test_settingsSecurityWithHashOnly_view.png | Bin 75492 -> 0 bytes ...shotTest_settingsSecurity_asAdmin_view.png | Bin 61290 -> 0 bytes ...leScreenshotTest_settingsSecurity_edit.png | Bin 131466 -> 0 bytes ...leScreenshotTest_settingsSecurity_view.png | Bin 58021 -> 0 bytes ...enshotTest_settingsWhoisEditError_page.png | Bin 57734 -> 0 bytes ...eScreenshotTest_settingsWhoisEdit_page.png | Bin 53564 -> 0 bytes ...nsoleScreenshotTest_settingsWhois_page.png | Bin 50219 -> 0 bytes ...dmin_fails_badEmail_createResultFailed.png | Bin 96459 -> 0 bytes ...otTest_get_admin_succeeds_createResult.png | Bin 56112 -> 0 bytes ...nshotTest_get_admin_succeeds_formEmpty.png | Bin 57073 -> 0 bytes ...shotTest_get_admin_succeeds_formFilled.png | Bin 50660 -> 0 bytes ...nshotTest_get_owner_fails_unauthorized.png | Bin 24713 -> 0 bytes .../sql/schema/db-schema.sql.generated | 2 +- dependencies.gradle | 35 +- jetty/gradle.lockfile | 4 +- proxy/gradle.lockfile | 65 +- release/cloudbuild-kythe.yaml | 2 +- services/backend/gradle.lockfile | 4 +- services/bsa/gradle.lockfile | 4 +- services/default/gradle.lockfile | 4 +- services/pubapi/gradle.lockfile | 4 +- services/tools/gradle.lockfile | 4 +- 152 files changed, 548 insertions(+), 7958 deletions(-) delete mode 100644 core/src/main/java/google/registry/ui/assets/images/ajax-loader.gif delete mode 100644 core/src/main/java/google/registry/ui/assets/images/android_sad.png delete mode 100644 core/src/main/java/google/registry/ui/assets/images/disclosure_arrow_dk_grey.png delete mode 100644 core/src/main/java/google/registry/ui/assets/images/disclosure_arrow_dk_grey_down.png delete mode 100644 core/src/main/java/google/registry/ui/assets/images/disclosure_arrow_dk_grey_up_down.png delete mode 100644 core/src/main/java/google/registry/ui/assets/images/explore_24.png delete mode 100644 core/src/main/java/google/registry/ui/assets/images/folder.png delete mode 100644 core/src/main/java/google/registry/ui/assets/images/google_registry.png delete mode 100644 core/src/main/java/google/registry/ui/assets/images/homeContact.png delete mode 100644 core/src/main/java/google/registry/ui/assets/images/homeResources.png delete mode 100644 core/src/main/java/google/registry/ui/assets/images/homeSettings.png delete mode 100644 core/src/main/java/google/registry/ui/assets/images/ic_contacts_blue_12.png delete mode 100644 core/src/main/java/google/registry/ui/assets/images/ic_contacts_blue_8.png delete mode 100644 core/src/main/java/google/registry/ui/assets/images/icons/svg/Gcomm/search.svg delete mode 100644 core/src/main/java/google/registry/ui/assets/images/icons/svg/search.svg delete mode 100644 core/src/main/java/google/registry/ui/assets/images/icons/svg/settings.svg delete mode 100644 core/src/main/java/google/registry/ui/assets/images/loader1x.gif delete mode 100644 core/src/main/java/google/registry/ui/assets/images/loader4x.gif delete mode 100644 core/src/main/java/google/registry/ui/assets/images/logo.png delete mode 100644 core/src/main/java/google/registry/ui/assets/images/logo_sm.gif delete mode 100644 core/src/main/java/google/registry/ui/assets/images/offline_lightning.png delete mode 100644 core/src/main/java/google/registry/ui/assets/images/ribbon_certified-128.png delete mode 100644 core/src/main/java/google/registry/ui/assets/images/ribbon_certified-64.png delete mode 100644 core/src/main/java/google/registry/ui/assets/images/visibleOff_16.png delete mode 100644 core/src/main/java/google/registry/ui/assets/images/visibleOn_16.png delete mode 100644 core/src/main/java/google/registry/ui/epptmpl/contact_info.xml delete mode 100644 core/src/main/java/google/registry/ui/epptmpl/contact_transfer_request.xml delete mode 100644 core/src/main/java/google/registry/ui/epptmpl/domain_create_no_hosts_or_dsdata.xml delete mode 100644 core/src/main/java/google/registry/ui/epptmpl/domain_delete.xml delete mode 100644 core/src/main/java/google/registry/ui/epptmpl/domain_update_restore_request.xml delete mode 100644 core/src/main/java/google/registry/ui/epptmpl/login_valid.xml delete mode 100644 core/src/main/java/google/registry/ui/epptmpl/logout.xml delete mode 100644 core/src/main/java/google/registry/ui/epptmpl/poll.xml delete mode 100644 core/src/main/java/google/registry/ui/server/registrar/ConsoleOteSetupAction.java delete mode 100644 core/src/main/java/google/registry/ui/server/registrar/ConsoleRegistrarCreatorAction.java delete mode 100644 core/src/main/java/google/registry/ui/server/registrar/ConsoleUiAction.java delete mode 100644 core/src/main/java/google/registry/ui/server/registrar/HtmlAction.java delete mode 100644 core/src/main/java/google/registry/ui/server/registrar/JsonGetAction.java delete mode 100644 core/src/main/java/google/registry/ui/server/registrar/OteStatusAction.java delete mode 100644 core/src/main/java/google/registry/ui/server/registrar/RegistrarConsoleMetrics.java delete mode 100644 core/src/main/java/google/registry/ui/server/registrar/RegistrarSettingsAction.java delete mode 100644 core/src/main/java/google/registry/ui/server/registrar/RegistryLockGetAction.java delete mode 100644 core/src/main/java/google/registry/ui/server/registrar/RegistryLockPostAction.java delete mode 100644 core/src/main/java/google/registry/ui/server/registrar/RegistryLockVerifyAction.java delete mode 100644 core/src/main/java/google/registry/ui/server/registrar/package-info.java delete mode 100644 core/src/test/java/google/registry/ui/server/registrar/ConsoleOteSetupActionTest.java delete mode 100644 core/src/test/java/google/registry/ui/server/registrar/ConsoleRegistrarCreatorActionTest.java delete mode 100644 core/src/test/java/google/registry/ui/server/registrar/ConsoleUiActionTest.java delete mode 100644 core/src/test/java/google/registry/ui/server/registrar/ContactSettingsTest.java delete mode 100644 core/src/test/java/google/registry/ui/server/registrar/OteStatusActionTest.java delete mode 100644 core/src/test/java/google/registry/ui/server/registrar/RegistrarSettingsActionTest.java delete mode 100644 core/src/test/java/google/registry/ui/server/registrar/RegistrarSettingsActionTestCase.java delete mode 100644 core/src/test/java/google/registry/ui/server/registrar/RegistryLockGetActionTest.java delete mode 100644 core/src/test/java/google/registry/ui/server/registrar/RegistryLockPostActionTest.java delete mode 100644 core/src/test/java/google/registry/ui/server/registrar/RegistryLockVerifyActionTest.java delete mode 100644 core/src/test/java/google/registry/ui/server/registrar/SecuritySettingsTest.java delete mode 100644 core/src/test/java/google/registry/ui/server/registrar/WhoisSettingsTest.java delete mode 100644 core/src/test/java/google/registry/webdriver/OteSetupConsoleScreenshotTest.java delete mode 100644 core/src/test/java/google/registry/webdriver/RegistrarConsoleScreenshotTest.java delete mode 100644 core/src/test/java/google/registry/webdriver/RegistrarConsoleWebTest.java delete mode 100644 core/src/test/java/google/registry/webdriver/RegistrarCreateConsoleScreenshotTest.java delete mode 100644 core/src/test/resources/google/registry/ui/server/registrar/update_registrar.json delete mode 100644 core/src/test/resources/google/registry/ui/server/registrar/update_registrar_duplicate_contacts.json delete mode 100644 core/src/test/resources/google/registry/ui/server/registrar/update_registrar_email.txt delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/OteSetupConsoleScreenshotTest_get_admin_fails_badEmail_oteResultFailed.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/OteSetupConsoleScreenshotTest_get_admin_succeeds_formEmpty.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/OteSetupConsoleScreenshotTest_get_admin_succeeds_formFilled.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/OteSetupConsoleScreenshotTest_get_admin_succeeds_oteResult.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/OteSetupConsoleScreenshotTest_get_owner_fails_unauthorized.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_contactUs_page.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_deprecationWarning_active_page.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_getOteStatus_completed_before_click.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_getOteStatus_completed_result.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_getOteStatus_noButtonWhenReal_result.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_getOteStatus_notCompleted_result.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_indexPage_smallScrolledDown_page.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_index_adminAndOwner_page.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_index_admin_page.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_index_owner_page.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_index_registrarDisabled_view.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLockVerify_success_page.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLockVerify_unknownLock_page.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLock_empty_page.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLock_lockModal_page.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLock_nonEmpty_admin_page.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLock_nonEmpty_page.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLock_notAllowedForUser_page.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLock_notAllowed_page.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLock_unlockModal_page.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_settingsAdmin_whenAdmin_edit.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_settingsAdmin_whenAdmin_view.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_settingsAdmin_whenNotAdmin_showsHome_view.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_settingsContactAdd_page.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_settingsContactEdit_page.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_settingsContactEdit_setRegistryLockPassword_alreadySet_page.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_settingsContactEdit_setRegistryLockPassword_contact_view.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_settingsContactEdit_setRegistryLockPassword_notAllowedForContact_page.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_settingsContactEdit_setRegistryLockPassword_page_with_password.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_settingsContactEdit_setRegistryLockPassword_page_with_password_after_hide.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_settingsContactEdit_setRegistryLockPassword_page_with_shown_password.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_settingsContactItem_asAdmin_page.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_settingsContactItem_page.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_settingsContact_page.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_settingsSecurityWithCerts_edit.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_settingsSecurityWithCerts_view.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_settingsSecurityWithHashOnly_edit.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_settingsSecurityWithHashOnly_view.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_settingsSecurity_asAdmin_view.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_settingsSecurity_edit.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_settingsSecurity_view.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_settingsWhoisEditError_page.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_settingsWhoisEdit_page.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_settingsWhois_page.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarCreateConsoleScreenshotTest_get_admin_fails_badEmail_createResultFailed.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarCreateConsoleScreenshotTest_get_admin_succeeds_createResult.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarCreateConsoleScreenshotTest_get_admin_succeeds_formEmpty.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarCreateConsoleScreenshotTest_get_admin_succeeds_formFilled.png delete mode 100644 core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarCreateConsoleScreenshotTest_get_owner_fails_unauthorized.png diff --git a/appengine_war.gradle b/appengine_war.gradle index f2239220e..45d61cc29 100644 --- a/appengine_war.gradle +++ b/appengine_war.gradle @@ -47,20 +47,9 @@ war { if (project.path == ":services:default") { war { - from("${coreResourcesDir}/google/registry/ui") { - include "registrar_bin.js" - if (environment != "production") { - include "registrar_bin.js.map" - } - into("assets/js") - } - from("${coreResourcesDir}/google/registry/ui/css") { - include "registrar*" - into("assets/css") - } - from("${coreResourcesDir}/google/registry/ui/assets/images") { - include "**/*" - into("assets/images") + from("${coreResourcesDir}/google/registry/ui/html") { + include "*.html" + into("registrar") } } } @@ -104,7 +93,6 @@ appengineDeployAll.finalizedBy ':deployCloudSchedulerAndQueue' rootProject.deploy.dependsOn appengineDeployAll rootProject.stage.dependsOn appengineStage -tasks['war'].dependsOn ':core:compileProdJS' tasks['war'].dependsOn ':core:processResources' tasks['war'].dependsOn ':core:jar' diff --git a/core/build.gradle b/core/build.gradle index 9bfeabbbf..b91332767 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -98,10 +98,8 @@ processTestResources { } configurations { - css jaxb soy - closureCompiler devtool nonprodImplementation.extendsFrom implementation @@ -120,8 +118,6 @@ configurations { all { // servlet-api:3.1 pulled in but not used by soy compiler exclude group: 'javax.servlet', module: 'javax.servlet-api' - // Jetty's servlet-api:2.5 implementation, pulled in by other Jetty jars - exclude group: 'org.mortbay.jetty', module: 'servlet-api' } } @@ -297,22 +293,15 @@ dependencies { jaxb deps['org.glassfish.jaxb:jaxb-xjc'] // Dependency needed for soy to java compilation. - soy deps['com.google.inject:guice'] soy deps['com.google.protobuf:protobuf-java'] soy deps['com.google.template:soy'] - // Dependencies needed for compiling stylesheets to javascript - css deps['com.google.closure-stylesheets:closure-stylesheets'] - css deps['args4j:args4j'] - // Tool dependencies. used for doc generation. implementation files("${System.properties['java.home']}/../lib/tools.jar") // Flyway classes needed to generate the golden file. implementation deps['org.flywaydb:flyway-core'] implementation deps['org.flywaydb:flyway-database-postgresql'] - - closureCompiler deps['com.google.javascript:closure-compiler'] } task jaxbToJava { @@ -383,9 +372,8 @@ task soyToJava { // Relative paths of soy directories. def spec11SoyDir = "google/registry/reporting/spec11/soy" def toolsSoyDir = "google/registry/tools/soy" - def registrarSoyDir = "google/registry/ui/soy/registrar" - def soyRelativeDirs = [spec11SoyDir, toolsSoyDir, registrarSoyDir] + def soyRelativeDirs = [spec11SoyDir, toolsSoyDir] soyRelativeDirs.each { inputs.dir "${resourcesSourceDir}/${it}" outputs.dir "${generatedDir}/${it}" @@ -399,8 +387,7 @@ task soyToJava { "--outputDirectory", "${outputDirectory}", "--javaClassNameSource", "filename", "--allowExternalCalls", "true", - "--srcs", "${soyFiles.join(',')}", - "--compileTimeGlobalsFile", "${resourcesSourceDir}/google/registry/ui/globals.txt" + "--srcs", "${soyFiles.join(',')}" } // Replace the "@link" tags after the "@deprecated" tags in the generated @@ -427,12 +414,6 @@ task soyToJava { dir: "${resourcesSourceDir}/${toolsSoyDir}", include: ['**/*.soy'])) - soyToJava('google.registry.ui.soy.registrar', - "${generatedDir}/${registrarSoyDir}", - fileTree( - dir: "${resourcesSourceDir}/${registrarSoyDir}", - include: ['**/*.soy'])) - soyToJava('google.registry.reporting.spec11.soy', "${generatedDir}/${spec11SoyDir}", fileTree( @@ -441,134 +422,8 @@ task soyToJava { } } -task soyToJS(type: JavaExec) { - def rootSoyDirectory = "${resourcesSourceDir}/google/registry/ui/soy/registrar" - def outputSoyDirectory = "${generatedDir}/google/registry/ui/soy/registrar" - inputs.dir rootSoyDirectory - outputs.dir outputSoyDirectory - - def inputSoyFiles = files { - file("${rootSoyDirectory}").listFiles() - }.filter { - it.name.endsWith(".soy") - } - - classpath configurations.soy - main = "com.google.template.soy.SoyToJsSrcCompiler" - args "--outputPathFormat", "${outputSoyDirectory}/{INPUT_FILE_NAME}.js", - "--allowExternalCalls", "false", - "--srcs", "${inputSoyFiles.join(',')}", - "--compileTimeGlobalsFile", "${resourcesSourceDir}/google/registry/ui/globals.txt" -} - -task stylesheetsToJavascript { - def cssSourceDir = "${jsDir}/google/registry/ui/css" - def outputDir = "${resourcesDir}/google/registry/ui/css" - inputs.dir cssSourceDir - outputs.dir outputDir - - ext.cssCompile = { outputName, debug, cssFiles -> - javaexec { - main = "com.google.common.css.compiler.commandline.ClosureCommandLineCompiler" - classpath configurations.css - - def argsBuffer = [ - "--output-file", "${outputName}.css", - "--output-source-map", "${outputName}.css.map", - "--input-orientation", "LTR", - "--output-orientation", "NOCHANGE", - "--output-renaming-map", "${outputName}.css.js", - "--output-renaming-map-format", "CLOSURE_COMPILED_SPLIT_HYPHENS" - ] - if (debug) { - argsBuffer.addAll(["--rename", "DEBUG", "--pretty-print"]) - } else { - argsBuffer.addAll(["--rename", "CLOSURE"]) - } - - argsBuffer.addAll(cssFiles) - args argsBuffer - } - } - - doLast { - file("${outputDir}").mkdirs() - def ignoredFiles = ["demo_css.css", "registrar_imports_raw.css"] - def sourceFiles = [] - // include all CSS files that we find except for the ones explicitly ignored - fileTree(cssSourceDir).each { - if (it.name.endsWith(".css") && !ignoredFiles.contains(it.name)) { - sourceFiles << (cssSourceDir + "/" + it.name) - } - } - - // The css files have to be passed to the compiler in alphabetic order to - // avoid some flaky style issues - sourceFiles.sort() - - cssCompile("${outputDir}/registrar_bin", false, sourceFiles) - cssCompile("${outputDir}/registrar_dbg", true, sourceFiles) - } -} - -task compileProdJS(type: JavaExec) { - def outputDir = "${resourcesDir}/google/registry/ui" - def nodeModulesDir = "${rootDir}/node_modules" - def cssSourceDir = "${resourcesDir}/google/registry/ui/css" - def jsSourceDir = "${jsDir}/google/registry/ui/js" - def externsDir = "${jsDir}/google/registry/ui/externs" - def soySourceDir = "${generatedDir}/google/registry/ui/soy" - - [nodeModulesDir, cssSourceDir, jsSourceDir, externsDir, soySourceDir].each { - inputs.dir "${it}" - } - outputs.dir outputDir - - classpath configurations.closureCompiler - main = 'com.google.javascript.jscomp.CommandLineRunner' - - def closureArgs = [] - closureArgs << "--js_output_file=${outputDir}/registrar_bin.js" - // sourcemap-related configuration - closureArgs << "--create_source_map=${outputDir}/registrar_bin.js.map" - closureArgs << "--source_map_include_content=true" - closureArgs << "--source_map_location_mapping=${rootDir}/|" - closureArgs << "--output_wrapper=\"%output% //# sourceMappingURL=registrar_bin.js.map\"" - - // compilation options - closureArgs << "--compilation_level=ADVANCED" - closureArgs << "--entry_point=goog:registry.registrar.main" - closureArgs << "--generate_exports" - - // manually include all the required js files - closureArgs << "--js=${nodeModulesDir}/google-closure-library/**/*.js" - closureArgs << "--js=${jsDir}/*.js" - closureArgs << "--js=${cssSourceDir}/registrar_bin.css.js" - closureArgs << "--js=${jsSourceDir}/**.js" - closureArgs << "--js=${externsDir}/json.js" - closureArgs << "--js=${soySourceDir}/**.js" - args closureArgs -} -compileProdJS.dependsOn soyToJava - compileJava.dependsOn jaxbToJava compileJava.dependsOn soyToJava -// The Closure JS compiler does not support Windows. It is fine to disable it if -// all we want to do is to complile the Java code on Windows. -if (!System.properties['os.name'].toLowerCase().contains('windows')) { - compileJava.dependsOn compileProdJS - assemble.dependsOn compileProdJS -} - -// stylesheetsToJavascript must happen after processResources, which wipes the -// resources folder before copying data into it. -stylesheetsToJavascript.dependsOn processResources -classes.dependsOn stylesheetsToJavascript -compileProdJS.dependsOn stylesheetsToJavascript -compileProdJS.dependsOn rootProject.npmInstall -compileProdJS.dependsOn processResources -compileProdJS.dependsOn processTestResources -compileProdJS.dependsOn soyToJS // Make testing artifacts available to be depended up on by other projects. // TODO: factor out google.registry.testing to be a separate project. @@ -829,16 +684,7 @@ artifacts { devtool devtool } -task copyJsFilesForTestServer(dependsOn: assemble, type: Copy) { - // Unfortunately the test server relies on having some compiled JS/CSS - // in place, so copy it over here - from "${resourcesDir}/google/registry/ui/" - include '**/*.js' - include '**/*.css' - into "${project.projectDir}/src/main/resources/google/registry/ui/" -} - -task runTestServer(dependsOn: copyJsFilesForTestServer, type: JavaExec) { +task runTestServer(type: JavaExec) { main = 'google.registry.server.RegistryTestServerMain' classpath = sourceSets.test.runtimeClasspath } diff --git a/core/gradle.lockfile b/core/gradle.lockfile index 85412b903..55960e891 100644 --- a/core/gradle.lockfile +++ b/core/gradle.lockfile @@ -3,9 +3,7 @@ # This file is expected to be part of source control. aopalliance:aopalliance:1.0=annotationProcessor,compileClasspath,deploy_jar,errorprone,nonprodAnnotationProcessor,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath args4j:args4j:2.0.23=soy -args4j:args4j:2.0.26=css args4j:args4j:2.33=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -cglib:cglib-nodep:2.2=css com.charleskorn.kaml:kaml:0.20.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath com.fasterxml.jackson.core:jackson-annotations:2.18.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.fasterxml.jackson.core:jackson-core:2.18.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath @@ -38,7 +36,7 @@ com.google.api-client:google-api-client-servlet:2.2.0=testRuntimeClasspath com.google.api-client:google-api-client-servlet:2.7.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath com.google.api-client:google-api-client:2.7.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.google.api.grpc:gapic-google-cloud-storage-v2:2.32.1-alpha=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath -com.google.api.grpc:gapic-google-cloud-storage-v2:2.43.1-beta=testCompileClasspath,testRuntimeClasspath +com.google.api.grpc:gapic-google-cloud-storage-v2:2.43.2-beta=testCompileClasspath,testRuntimeClasspath com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1:3.5.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta1:0.177.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta2:0.177.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath @@ -49,7 +47,7 @@ com.google.api.grpc:grpc-google-cloud-spanner-admin-database-v1:6.66.0=compileCl com.google.api.grpc:grpc-google-cloud-spanner-admin-instance-v1:6.66.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.google.api.grpc:grpc-google-cloud-spanner-v1:6.66.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.google.api.grpc:grpc-google-cloud-storage-v2:2.32.1-alpha=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath -com.google.api.grpc:grpc-google-cloud-storage-v2:2.43.1-beta=testCompileClasspath,testRuntimeClasspath +com.google.api.grpc:grpc-google-cloud-storage-v2:2.43.2-beta=testCompileClasspath,testRuntimeClasspath com.google.api.grpc:grpc-google-common-protos:2.39.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.google.api.grpc:proto-google-cloud-bigquerystorage-v1:3.5.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.google.api.grpc:proto-google-cloud-bigquerystorage-v1beta1:0.177.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath @@ -70,19 +68,25 @@ com.google.api.grpc:proto-google-cloud-spanner-admin-instance-v1:6.66.0=compileC com.google.api.grpc:proto-google-cloud-spanner-executor-v1:6.66.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.google.api.grpc:proto-google-cloud-spanner-v1:6.66.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.google.api.grpc:proto-google-cloud-storage-v2:2.32.1-alpha=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath -com.google.api.grpc:proto-google-cloud-storage-v2:2.43.1-beta=testCompileClasspath,testRuntimeClasspath +com.google.api.grpc:proto-google-cloud-storage-v2:2.43.2-beta=testCompileClasspath,testRuntimeClasspath com.google.api.grpc:proto-google-cloud-tasks-v2:2.29.0=testRuntimeClasspath com.google.api.grpc:proto-google-cloud-tasks-v2:2.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath com.google.api.grpc:proto-google-cloud-tasks-v2beta2:0.119.0=testRuntimeClasspath com.google.api.grpc:proto-google-cloud-tasks-v2beta2:0.141.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath com.google.api.grpc:proto-google-cloud-tasks-v2beta3:0.119.0=testRuntimeClasspath com.google.api.grpc:proto-google-cloud-tasks-v2beta3:0.141.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath -com.google.api.grpc:proto-google-common-protos:2.45.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.google.api.grpc:proto-google-iam-v1:1.40.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.google.api:api-common:2.37.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.google.api:gax-grpc:2.54.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.google.api:gax-httpjson:2.54.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.google.api:gax:2.54.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.google.api.grpc:proto-google-common-protos:2.45.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath +com.google.api.grpc:proto-google-common-protos:2.46.0=testCompileClasspath,testRuntimeClasspath +com.google.api.grpc:proto-google-iam-v1:1.40.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath +com.google.api.grpc:proto-google-iam-v1:1.41.0=testCompileClasspath,testRuntimeClasspath +com.google.api:api-common:2.37.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath +com.google.api:api-common:2.38.0=testCompileClasspath,testRuntimeClasspath +com.google.api:gax-grpc:2.54.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath +com.google.api:gax-grpc:2.55.0=testCompileClasspath,testRuntimeClasspath +com.google.api:gax-httpjson:2.54.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath +com.google.api:gax-httpjson:2.55.0=testCompileClasspath,testRuntimeClasspath +com.google.api:gax:2.54.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath +com.google.api:gax:2.55.0=testCompileClasspath,testRuntimeClasspath com.google.apis:google-api-services-admin-directory:directory_v1-rev20240924-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.google.apis:google-api-services-bigquery:v2-rev20240919-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.google.apis:google-api-services-cloudresourcemanager:v1-rev20240310-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath @@ -99,10 +103,10 @@ com.google.apis:google-api-services-pubsub:v1-rev20220904-2.0.0=compileClasspath com.google.apis:google-api-services-sheets:v4-rev20240917-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.google.apis:google-api-services-sqladmin:v1beta4-rev20240925-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.google.apis:google-api-services-storage:v1-rev20240319-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath -com.google.apis:google-api-services-storage:v1-rev20240819-2.0.0=testRuntimeClasspath -com.google.apis:google-api-services-storage:v1-rev20240924-2.0.0=testCompileClasspath -com.google.auth:google-auth-library-credentials:1.27.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.google.auth:google-auth-library-oauth2-http:1.27.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.google.apis:google-api-services-storage:v1-rev20240924-2.0.0=testCompileClasspath,testRuntimeClasspath +com.google.auth:google-auth-library-credentials:1.28.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.google.auth:google-auth-library-oauth2-http:1.27.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath +com.google.auth:google-auth-library-oauth2-http:1.28.0=testCompileClasspath,testRuntimeClasspath com.google.auto.service:auto-service-annotations:1.0.1=errorprone,nonprodAnnotationProcessor,testAnnotationProcessor com.google.auto.service:auto-service-annotations:1.1.1=annotationProcessor,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.google.auto.service:auto-service:1.1.1=annotationProcessor @@ -111,7 +115,6 @@ com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,errorprone, com.google.auto.value:auto-value:1.10.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.google.auto.value:auto-value:1.11.0=annotationProcessor,testAnnotationProcessor com.google.auto:auto-common:1.2.1=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor -com.google.closure-stylesheets:closure-stylesheets:1.5.0=css com.google.cloud.bigdataoss:gcsio:2.2.16=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.google.cloud.bigdataoss:util:2.2.16=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.google.cloud.bigtable:bigtable-client-core-config:1.28.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath @@ -125,14 +128,14 @@ com.google.cloud.sql:postgres-socket-factory:1.21.0=deploy_jar,runtimeClasspath, com.google.cloud:google-cloud-bigquerystorage:3.5.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.google.cloud:google-cloud-bigtable:2.39.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.google.cloud:google-cloud-core-grpc:2.38.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath -com.google.cloud:google-cloud-core-grpc:2.44.1=testCompileClasspath,testRuntimeClasspath +com.google.cloud:google-cloud-core-grpc:2.45.0=testCompileClasspath,testRuntimeClasspath com.google.cloud:google-cloud-core-http:2.31.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath -com.google.cloud:google-cloud-core-http:2.44.1=testCompileClasspath,testRuntimeClasspath +com.google.cloud:google-cloud-core-http:2.45.0=testCompileClasspath,testRuntimeClasspath com.google.cloud:google-cloud-core:2.38.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath -com.google.cloud:google-cloud-core:2.44.1=testCompileClasspath,testRuntimeClasspath +com.google.cloud:google-cloud-core:2.45.0=testCompileClasspath,testRuntimeClasspath com.google.cloud:google-cloud-firestore:3.21.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.google.cloud:google-cloud-monitoring:3.44.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.google.cloud:google-cloud-nio:0.127.24=testCompileClasspath +com.google.cloud:google-cloud-nio:0.127.25=testCompileClasspath com.google.cloud:google-cloud-nio:0.127.6=testRuntimeClasspath com.google.cloud:google-cloud-pubsub:1.129.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.google.cloud:google-cloud-pubsublite:1.13.5=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath @@ -140,25 +143,25 @@ com.google.cloud:google-cloud-secretmanager:2.29.0=testRuntimeClasspath com.google.cloud:google-cloud-secretmanager:2.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath com.google.cloud:google-cloud-spanner:6.66.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.google.cloud:google-cloud-storage:2.32.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath -com.google.cloud:google-cloud-storage:2.43.1=testCompileClasspath,testRuntimeClasspath +com.google.cloud:google-cloud-storage:2.43.2=testCompileClasspath,testRuntimeClasspath com.google.cloud:google-cloud-tasks:2.29.0=testRuntimeClasspath com.google.cloud:google-cloud-tasks:2.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath com.google.cloud:grpc-gcp:1.5.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.google.cloud:libraries-bom:26.26.0=testRuntimeClasspath com.google.cloud:proto-google-cloud-firestore-bundle-v1:3.21.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.google.code.findbugs:jsr305:3.0.1=css com.google.code.findbugs:jsr305:3.0.2=annotationProcessor,checkstyle,compileClasspath,deploy_jar,errorprone,nonprodAnnotationProcessor,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath com.google.code.gson:gson:2.11.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.google.code.gson:gson:2.7=css,soy +com.google.code.gson:gson:2.7=soy com.google.common.html.types:types:1.0.6=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath com.google.dagger:dagger-compiler:2.51.1=annotationProcessor,testAnnotationProcessor com.google.dagger:dagger-spi:2.51.1=annotationProcessor,testAnnotationProcessor com.google.dagger:dagger:2.51.1=annotationProcessor,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath com.google.devtools.ksp:symbol-processing-api:1.9.20-1.0.14=annotationProcessor,testAnnotationProcessor com.google.errorprone:error_prone_annotation:2.23.0=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor +com.google.errorprone:error_prone_annotations:2.1.3=soy com.google.errorprone:error_prone_annotations:2.23.0=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor -com.google.errorprone:error_prone_annotations:2.32.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.google.errorprone:error_prone_annotations:2.7.1=checkstyle,soy +com.google.errorprone:error_prone_annotations:2.33.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.google.errorprone:error_prone_annotations:2.7.1=checkstyle com.google.errorprone:error_prone_check_api:2.23.0=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor com.google.errorprone:error_prone_core:2.23.0=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor com.google.errorprone:error_prone_type_annotations:2.23.0=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor @@ -170,17 +173,17 @@ com.google.flogger:flogger-system-backend:0.8=compileClasspath,deploy_jar,nonpro com.google.flogger:flogger:0.8=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.google.flogger:google-extensions:0.8=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.google.googlejavaformat:google-java-format:1.5=annotationProcessor,testAnnotationProcessor -com.google.guava:failureaccess:1.0.1=checkstyle,errorprone,nonprodAnnotationProcessor,soy +com.google.guava:failureaccess:1.0.1=checkstyle,errorprone,nonprodAnnotationProcessor com.google.guava:failureaccess:1.0.2=annotationProcessor,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath com.google.guava:guava-parent:32.1.1-jre=errorprone,nonprodAnnotationProcessor com.google.guava:guava-testlib:33.3.1-jre=testCompileClasspath,testRuntimeClasspath -com.google.guava:guava:20.0=css -com.google.guava:guava:31.0.1-jre=checkstyle,soy +com.google.guava:guava:25.1-jre=soy +com.google.guava:guava:31.0.1-jre=checkstyle com.google.guava:guava:32.1.1-jre=errorprone,nonprodAnnotationProcessor com.google.guava:guava:33.0.0-jre=annotationProcessor,testAnnotationProcessor com.google.guava:guava:33.3.1-android=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath com.google.guava:guava:33.3.1-jre=testCompileClasspath,testRuntimeClasspath -com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath com.google.gwt:gwt-user:2.10.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.google.http-client:google-http-client-apache-v2:1.45.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.google.http-client:google-http-client-appengine:1.43.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath @@ -191,14 +194,11 @@ com.google.http-client:google-http-client-jackson2:1.45.0=testCompileClasspath,t com.google.http-client:google-http-client-protobuf:1.44.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.google.http-client:google-http-client:1.45.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.google.inject.extensions:guice-multibindings:4.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath -com.google.inject:guice:4.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.google.inject:guice:4.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath com.google.inject:guice:5.1.0=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor -com.google.inject:guice:7.0.0=soy -com.google.j2objc:j2objc-annotations:1.3=checkstyle,soy +com.google.j2objc:j2objc-annotations:1.1=soy +com.google.j2objc:j2objc-annotations:1.3=checkstyle com.google.j2objc:j2objc-annotations:3.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.google.javascript:closure-compiler-externs:v20160713=css -com.google.javascript:closure-compiler-unshaded:v20160713=css -com.google.javascript:closure-compiler:v20210505=closureCompiler com.google.jsinterop:jsinterop-annotations:1.0.1=soy com.google.jsinterop:jsinterop-annotations:2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.google.monitoring-client:contrib:1.0.7=testCompileClasspath,testRuntimeClasspath @@ -211,7 +211,6 @@ com.google.oauth-client:google-oauth-client-servlet:1.34.1=testRuntimeClasspath com.google.oauth-client:google-oauth-client-servlet:1.36.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath com.google.oauth-client:google-oauth-client:1.36.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.google.protobuf:protobuf-java-util:3.25.5=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.google.protobuf:protobuf-java:2.5.0=css com.google.protobuf:protobuf-java:3.19.6=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor com.google.protobuf:protobuf-java:3.25.5=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath com.google.re2j:re2j:1.7=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath @@ -281,11 +280,12 @@ io.grpc:grpc-grpclb:1.68.0=compileClasspath,deploy_jar,nonprodCompileClasspath,n io.grpc:grpc-inprocess:1.68.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.grpc:grpc-netty-shaded:1.68.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.grpc:grpc-netty:1.62.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -io.grpc:grpc-opentelemetry:1.68.0=testCompileClasspath,testRuntimeClasspath -io.grpc:grpc-protobuf-lite:1.68.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.grpc:grpc-opentelemetry:1.67.1=testCompileClasspath,testRuntimeClasspath +io.grpc:grpc-protobuf-lite:1.67.1=testCompileClasspath +io.grpc:grpc-protobuf-lite:1.68.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath io.grpc:grpc-protobuf:1.68.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.grpc:grpc-rls:1.62.2=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath -io.grpc:grpc-rls:1.68.0=testRuntimeClasspath +io.grpc:grpc-rls:1.67.1=testRuntimeClasspath io.grpc:grpc-services:1.62.2=compileClasspath,nonprodCompileClasspath,testCompileClasspath io.grpc:grpc-services:1.68.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath io.grpc:grpc-stub:1.68.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath @@ -346,17 +346,15 @@ io.smallrye:jandex:3.1.2=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,tes jakarta-regexp:jakarta-regexp:1.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath jakarta.activation:jakarta.activation-api:2.1.3=compileClasspath,deploy_jar,jaxb,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath jakarta.inject:jakarta.inject-api:1.0.5=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -jakarta.inject:jakarta.inject-api:2.0.1=soy jakarta.mail:jakarta.mail-api:2.1.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath jakarta.persistence:jakarta.persistence-api:3.2.0=annotationProcessor,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath jakarta.servlet:jakarta.servlet-api:6.0.0=testCompileClasspath,testRuntimeClasspath jakarta.servlet:jakarta.servlet-api:6.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath jakarta.transaction:jakarta.transaction-api:2.0.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath jakarta.xml.bind:jakarta.xml.bind-api:4.0.2=compileClasspath,deploy_jar,jaxb,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -javacc:javacc:4.1=css javax.annotation:javax.annotation-api:1.3.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath javax.annotation:jsr250-api:1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath -javax.inject:javax.inject:1=annotationProcessor,compileClasspath,deploy_jar,errorprone,nonprodAnnotationProcessor,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath +javax.inject:javax.inject:1=annotationProcessor,compileClasspath,deploy_jar,errorprone,nonprodAnnotationProcessor,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath javax.jdo:jdo2-api:2.3-20090302111651=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath javax.servlet:servlet-api:2.5=testRuntimeClasspath javax.validation:validation-api:1.0.0.GA=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath @@ -367,7 +365,6 @@ net.bytebuddy:byte-buddy-agent:1.15.3=testCompileClasspath,testRuntimeClasspath net.bytebuddy:byte-buddy:1.14.12=compileClasspath,nonprodCompileClasspath net.bytebuddy:byte-buddy:1.14.15=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath net.bytebuddy:byte-buddy:1.15.3=testCompileClasspath,testRuntimeClasspath -net.java.dev.javacc:javacc:4.1=css net.java.dev.jna:jna:5.13.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath net.ltgt.gradle.incap:incap:0.2=annotationProcessor,testAnnotationProcessor net.sf.saxon:Saxon-HE:10.6=checkstyle @@ -415,7 +412,7 @@ org.apache.sshd:sshd-common:2.14.0=testCompileClasspath,testRuntimeClasspath org.apache.sshd:sshd-core:2.14.0=testCompileClasspath,testRuntimeClasspath org.apache.sshd:sshd-scp:2.14.0=testCompileClasspath,testRuntimeClasspath org.apache.sshd:sshd-sftp:2.14.0=testCompileClasspath,testRuntimeClasspath -org.apache.tomcat:tomcat-annotations-api:11.0.0-M26=testCompileClasspath,testRuntimeClasspath +org.apache.tomcat:tomcat-annotations-api:11.0.0=testCompileClasspath,testRuntimeClasspath org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath org.bouncycastle:bcpg-jdk18on:1.78.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.bouncycastle:bcpkix-jdk18on:1.78.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath @@ -424,13 +421,14 @@ org.bouncycastle:bcutil-jdk18on:1.78.1=compileClasspath,deploy_jar,nonprodCompil org.checkerframework:checker-compat-qual:2.5.3=compileClasspath,nonprodCompileClasspath,testCompileClasspath org.checkerframework:checker-compat-qual:2.5.5=annotationProcessor,testAnnotationProcessor org.checkerframework:checker-compat-qual:2.5.6=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath -org.checkerframework:checker-qual:3.12.0=checkstyle,soy +org.checkerframework:checker-qual:2.0.0=soy +org.checkerframework:checker-qual:3.12.0=checkstyle org.checkerframework:checker-qual:3.33.0=errorprone,nonprodAnnotationProcessor org.checkerframework:checker-qual:3.41.0=annotationProcessor,testAnnotationProcessor org.checkerframework:checker-qual:3.47.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.codehaus.mojo:animal-sniffer-annotations:1.14=soy org.codehaus.mojo:animal-sniffer-annotations:1.24=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath org.conscrypt:conscrypt-openjdk-uber:2.5.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.easymock:easymock:3.0=css org.eclipse.angus:angus-activation:2.0.2=deploy_jar,jaxb,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath org.eclipse.angus:jakarta.mail:2.0.3=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath org.eclipse.collections:eclipse-collections-api:11.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath @@ -457,7 +455,6 @@ org.glassfish.jaxb:txw2:4.0.2=deploy_jar,nonprodRuntimeClasspath,runtimeClasspat org.glassfish.jaxb:txw2:4.0.5=jaxb org.glassfish.jaxb:xsom:4.0.5=jaxb org.gwtproject:gwt-user:2.10.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.hamcrest:hamcrest-core:1.1=css org.hamcrest:hamcrest-core:1.3=nonprodCompileClasspath,nonprodRuntimeClasspath org.hamcrest:hamcrest-core:3.0=testCompileClasspath,testRuntimeClasspath org.hamcrest:hamcrest-library:3.0=testCompileClasspath,testRuntimeClasspath @@ -510,10 +507,8 @@ org.junit.platform:junit-platform-runner:1.11.2=testCompileClasspath,testRuntime org.junit.platform:junit-platform-suite-api:1.11.2=testCompileClasspath,testRuntimeClasspath org.junit.platform:junit-platform-suite-commons:1.11.2=testRuntimeClasspath org.junit:junit-bom:5.11.2=testCompileClasspath,testRuntimeClasspath -org.mockito:mockito-core:1.10.19=css org.mockito:mockito-core:5.14.1=testCompileClasspath,testRuntimeClasspath org.mockito:mockito-junit-jupiter:5.14.1=testCompileClasspath,testRuntimeClasspath -org.objenesis:objenesis:2.1=css org.objenesis:objenesis:3.3=testRuntimeClasspath org.ogce:xpp3:1.1.6=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testRuntimeClasspath diff --git a/core/src/main/java/google/registry/config/RegistryConfig.java b/core/src/main/java/google/registry/config/RegistryConfig.java index 6b12f80e4..884e3213b 100644 --- a/core/src/main/java/google/registry/config/RegistryConfig.java +++ b/core/src/main/java/google/registry/config/RegistryConfig.java @@ -46,8 +46,6 @@ import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.net.URI; import java.net.URL; -import java.util.Collections; -import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; @@ -138,22 +136,10 @@ public final class RegistryConfig { return config.gcpProject.locationId; } - - /** - * The filename of the logo to be displayed in the header of the registrar console. - * - * @see google.registry.ui.server.registrar.ConsoleUiAction - */ - @Provides - @Config("logoFilename") - public static String provideLogoFilename(RegistryConfigSettings config) { - return config.registrarConsole.logoFilename; - } - /** * The product name of this specific registry. Used throughout the registrar console. * - * @see google.registry.ui.server.registrar.ConsoleUiAction + * @see google.registry.ui.server.console.ConsoleUserDataAction */ @Provides @Config("productName") @@ -180,23 +166,11 @@ public final class RegistryConfig { return config.misc.isEmailSendingEnabled; } - /** - * The e-mail address for questions about integrating with the registry. Used in the - * "contact-us" section of the registrar console. - * - * @see google.registry.ui.server.registrar.ConsoleUiAction - */ - @Provides - @Config("integrationEmail") - public static String provideIntegrationEmail(RegistryConfigSettings config) { - return config.registrarConsole.integrationEmailAddress; - } - /** * The e-mail address for general support. Used in the "contact-us" section of the registrar * console. * - * @see google.registry.ui.server.registrar.ConsoleUiAction + * @see google.registry.ui.server.console.ConsoleUserDataAction */ @Provides @Config("supportEmail") @@ -204,18 +178,6 @@ public final class RegistryConfig { return config.registrarConsole.supportEmailAddress; } - /** - * The "From" e-mail address for announcements. Used in the "contact-us" section of the - * registrar console. - * - * @see google.registry.ui.server.registrar.ConsoleUiAction - */ - @Provides - @Config("announcementsEmail") - public static String provideAnnouncementsEmail(RegistryConfigSettings config) { - return config.registrarConsole.announcementsEmailAddress; - } - /** * The DUM file name, used as a file name base for DUM csv file * @@ -230,7 +192,7 @@ public final class RegistryConfig { /** * The contact phone number. Used in the "contact-us" section of the registrar console. * - * @see google.registry.ui.server.registrar.ConsoleUiAction + * @see google.registry.ui.server.console.ConsoleUserDataAction */ @Provides @Config("supportPhoneNumber") @@ -242,7 +204,7 @@ public final class RegistryConfig { * The URL for technical support docs. Used in the "contact-us" section of the registrar * console. * - * @see google.registry.ui.server.registrar.ConsoleUiAction + * @see google.registry.ui.server.console.ConsoleUserDataAction */ @Provides @Config("technicalDocsUrl") @@ -250,22 +212,6 @@ public final class RegistryConfig { return config.registrarConsole.technicalDocsUrl; } - /** - * Configuration for analytics services installed in the web console. - * - * @see google.registry.ui.server.registrar.ConsoleUiAction - * @see google.registry.ui.soy.registrar.AnalyticsSoyInfo - */ - @Provides - @Config("analyticsConfig") - public static Map provideAnalyticsConfig(RegistryConfigSettings config) { - // Can't be an ImmutableMap because it may contain null values. - HashMap analyticsConfig = new HashMap<>(); - analyticsConfig.put( - "googleAnalyticsId", config.registrarConsole.analyticsConfig.googleAnalyticsId); - return Collections.unmodifiableMap(analyticsConfig); - } - /** * Returns the Google Cloud Storage bucket for storing zone files. * @@ -520,7 +466,7 @@ public final class RegistryConfig { * Returns the email address(es) that notifications of registrar and/or registrar contact * updates should be sent to, or the empty list if updates should not be sent. * - * @see google.registry.ui.server.registrar.RegistrarSettingsAction + * @see google.registry.ui.server.SendEmailUtils */ @Provides @Config("registrarChangesNotificationEmailAddresses") @@ -914,17 +860,6 @@ public final class RegistryConfig { return URI.create(config.rde.uploadUrl); } - /** - * Whether or not the registrar console is enabled. - * - * @see google.registry.ui.server.registrar.ConsoleUiAction - */ - @Provides - @Config("registrarConsoleEnabled") - public static boolean provideRegistrarConsoleEnabled() { - return true; - } - /** * Maximum amount of time for syncing a spreadsheet, before killing. * diff --git a/core/src/main/java/google/registry/config/RegistryConfigSettings.java b/core/src/main/java/google/registry/config/RegistryConfigSettings.java index 781eba482..47c0e4bde 100644 --- a/core/src/main/java/google/registry/config/RegistryConfigSettings.java +++ b/core/src/main/java/google/registry/config/RegistryConfigSettings.java @@ -189,18 +189,9 @@ public class RegistryConfigSettings { /** Configuration for the web-based registrar console. */ public static class RegistrarConsole { public String dumFileName; - public String logoFilename; public String supportPhoneNumber; public String supportEmailAddress; - public String announcementsEmailAddress; - public String integrationEmailAddress; public String technicalDocsUrl; - public AnalyticsConfig analyticsConfig; - } - - /** Configuration for analytics services installed in the registrar console */ - public static class AnalyticsConfig { - public String googleAnalyticsId; } /** Configuration for monitoring. */ diff --git a/core/src/main/java/google/registry/config/files/default-config.yaml b/core/src/main/java/google/registry/config/files/default-config.yaml index 2ca2405ff..a5abfe222 100644 --- a/core/src/main/java/google/registry/config/files/default-config.yaml +++ b/core/src/main/java/google/registry/config/files/default-config.yaml @@ -422,30 +422,15 @@ registrarConsole: # DUM download file name, excluding the extension dumFileName: dum_file_name - # Filename of the logo to use in the header of the console. This filename is - # relative to ui/assets/images/ - logoFilename: logo.png - # Contact phone number for support with the registry. supportPhoneNumber: +1 (888) 555 0123 # Contact email address for support with the registry. supportEmailAddress: support@example.com - # From: email address used to send announcements from the registry. - announcementsEmailAddress: announcements@example.com - - # Contact email address for questions about integrating with the registry. - integrationEmailAddress: integration@example.com - # URL linking to directory of technical support docs on the registry. technicalDocsUrl: http://example.com/your_support_docs/ - # Configuration for all analytics services installed in the web console - analyticsConfig: - # Google Analytics account where data on console use is sent, optional - googleAnalyticsId: null - monitoring: # Max queries per second for the Google Cloud Monitoring V3 (aka Stackdriver) # API. The limit can be adjusted by contacting Cloud Support. diff --git a/core/src/main/java/google/registry/env/alpha/default/WEB-INF/appengine-web.xml b/core/src/main/java/google/registry/env/alpha/default/WEB-INF/appengine-web.xml index 9e48b2ade..aef316a24 100644 --- a/core/src/main/java/google/registry/env/alpha/default/WEB-INF/appengine-web.xml +++ b/core/src/main/java/google/registry/env/alpha/default/WEB-INF/appengine-web.xml @@ -24,9 +24,7 @@ - - - - + + diff --git a/core/src/main/java/google/registry/env/common/default/WEB-INF/web.xml b/core/src/main/java/google/registry/env/common/default/WEB-INF/web.xml index 4eb1b3832..747b42402 100644 --- a/core/src/main/java/google/registry/env/common/default/WEB-INF/web.xml +++ b/core/src/main/java/google/registry/env/common/default/WEB-INF/web.xml @@ -18,58 +18,6 @@ /_dr/epp - - - frontend-servlet - /registrar-xhr - - - - - frontend-servlet - /registrar - - - - - frontend-servlet - /registrar-create - - - - - frontend-servlet - /registrar-ote-setup - - - - - frontend-servlet - /registrar-ote-status - - - - - frontend-servlet - /registrar-settings - - - - - frontend-servlet - /registry-lock-get - - - - frontend-servlet - /registry-lock-post - - - - frontend-servlet - /registry-lock-verify - - frontend-servlet @@ -84,47 +32,18 @@ Admin-only internal section. Requests for paths covered by the URL patterns below will be checked for a logged-in user account that's allowed to access the AppEngine admin console (NOTE: this includes Editor/Viewer permissions in addition to Owner and the new IAM - App Engine Admin role. See https://cloud.google.com/appengine/docs/java/access-control + App Engine Admin role. See https://cloud.google.com/appengine/docs/java/access-control specifically the "Access handlers that have a login:admin restriction" line.) TODO(b/28219927): lift some of these restrictions so that we can allow OAuth authentication for endpoints that need to be accessed by open-source automated processes. - /_ah/* - - - /assets/sources/* - - - /assets/js/registrar_bin.js.map - /assets/js/registrar_dbg.js - /assets/css/registrar_dbg.css - admin - - - - CONFIDENTIAL - - - - - - Registrar console - - Registrar console requires user login. This is in addition to the - code-level "requireLogin" configuration on individual @Actions. - - /registrar* - - - * - CONFIDENTIAL diff --git a/core/src/main/java/google/registry/env/crash/default/WEB-INF/appengine-web.xml b/core/src/main/java/google/registry/env/crash/default/WEB-INF/appengine-web.xml index 4be188269..e6dc4b529 100644 --- a/core/src/main/java/google/registry/env/crash/default/WEB-INF/appengine-web.xml +++ b/core/src/main/java/google/registry/env/crash/default/WEB-INF/appengine-web.xml @@ -24,9 +24,6 @@ - - - - + diff --git a/core/src/main/java/google/registry/env/local/default/WEB-INF/appengine-web.xml b/core/src/main/java/google/registry/env/local/default/WEB-INF/appengine-web.xml index 0dccbdd7d..5799e282d 100644 --- a/core/src/main/java/google/registry/env/local/default/WEB-INF/appengine-web.xml +++ b/core/src/main/java/google/registry/env/local/default/WEB-INF/appengine-web.xml @@ -28,16 +28,7 @@ - - - - - - - - - - + diff --git a/core/src/main/java/google/registry/env/production/default/WEB-INF/appengine-web.xml b/core/src/main/java/google/registry/env/production/default/WEB-INF/appengine-web.xml index a2a649c74..7816aa150 100644 --- a/core/src/main/java/google/registry/env/production/default/WEB-INF/appengine-web.xml +++ b/core/src/main/java/google/registry/env/production/default/WEB-INF/appengine-web.xml @@ -23,10 +23,7 @@ - - - - + diff --git a/core/src/main/java/google/registry/env/qa/default/WEB-INF/appengine-web.xml b/core/src/main/java/google/registry/env/qa/default/WEB-INF/appengine-web.xml index 5525b99ab..854c9a21d 100644 --- a/core/src/main/java/google/registry/env/qa/default/WEB-INF/appengine-web.xml +++ b/core/src/main/java/google/registry/env/qa/default/WEB-INF/appengine-web.xml @@ -27,10 +27,7 @@ - - - - + diff --git a/core/src/main/java/google/registry/env/sandbox/default/WEB-INF/appengine-web.xml b/core/src/main/java/google/registry/env/sandbox/default/WEB-INF/appengine-web.xml index c5b18dac4..0e1d65048 100644 --- a/core/src/main/java/google/registry/env/sandbox/default/WEB-INF/appengine-web.xml +++ b/core/src/main/java/google/registry/env/sandbox/default/WEB-INF/appengine-web.xml @@ -23,10 +23,7 @@ - - - - + diff --git a/core/src/main/java/google/registry/model/common/FeatureFlag.java b/core/src/main/java/google/registry/model/common/FeatureFlag.java index 3386bf1d8..bd682348b 100644 --- a/core/src/main/java/google/registry/model/common/FeatureFlag.java +++ b/core/src/main/java/google/registry/model/common/FeatureFlag.java @@ -66,7 +66,6 @@ public class FeatureFlag extends ImmutableObject implements Buildable { TEST_FEATURE, MINIMUM_DATASET_CONTACTS_OPTIONAL, MINIMUM_DATASET_CONTACTS_PROHIBITED, - NEW_CONSOLE } /** The name of the flag/feature. */ diff --git a/core/src/main/java/google/registry/module/frontend/FrontendRequestComponent.java b/core/src/main/java/google/registry/module/frontend/FrontendRequestComponent.java index fc1b9e964..aece4d002 100644 --- a/core/src/main/java/google/registry/module/frontend/FrontendRequestComponent.java +++ b/core/src/main/java/google/registry/module/frontend/FrontendRequestComponent.java @@ -40,14 +40,6 @@ import google.registry.ui.server.console.RegistrarsAction; import google.registry.ui.server.console.settings.ContactAction; import google.registry.ui.server.console.settings.SecurityAction; import google.registry.ui.server.console.settings.WhoisRegistrarFieldsAction; -import google.registry.ui.server.registrar.ConsoleOteSetupAction; -import google.registry.ui.server.registrar.ConsoleRegistrarCreatorAction; -import google.registry.ui.server.registrar.ConsoleUiAction; -import google.registry.ui.server.registrar.OteStatusAction; -import google.registry.ui.server.registrar.RegistrarSettingsAction; -import google.registry.ui.server.registrar.RegistryLockGetAction; -import google.registry.ui.server.registrar.RegistryLockPostAction; -import google.registry.ui.server.registrar.RegistryLockVerifyAction; /** Dagger component with per-request lifetime for "default" App Engine module. */ @RequestScope @@ -69,15 +61,10 @@ public interface FrontendRequestComponent { ConsoleOteAction consoleOteAction(); - ConsoleOteSetupAction consoleOteSetupAction(); - ConsoleRegistrarCreatorAction consoleRegistrarCreatorAction(); - ConsoleRegistryLockAction consoleRegistryLockAction(); ConsoleRegistryLockVerifyAction consoleRegistryLockVerifyAction(); - ConsoleUiAction consoleUiAction(); - ConsoleUpdateRegistrarAction consoleUpdateRegistrarAction(); ConsoleUserDataAction consoleUserDataAction(); @@ -89,18 +76,13 @@ public interface FrontendRequestComponent { ContactAction contactAction(); EppTlsAction eppTlsAction(); + FlowComponent.Builder flowComponentBuilder(); - OteStatusAction oteStatusAction(); RegistrarsAction registrarsAction(); - RegistrarSettingsAction registrarSettingsAction(); - - RegistryLockGetAction registryLockGetAction(); - - RegistryLockPostAction registryLockPostAction(); - RegistryLockVerifyAction registryLockVerifyAction(); SecurityAction securityAction(); + WhoisRegistrarFieldsAction whoisRegistrarFieldsAction(); @Subcomponent.Builder diff --git a/core/src/main/java/google/registry/ui/assets/images/ajax-loader.gif b/core/src/main/java/google/registry/ui/assets/images/ajax-loader.gif deleted file mode 100644 index d0bce1542342e912da81a2c260562df172f30d73..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 673 zcmZ?wbhEHb6krfw_{6~Q|Nnmm28Kh24mmkF0U1e2Nli^nlO|14{Lk&@8WQa67~pE8 zXTZz|lvDgC+Z`3#dv5h=E26FfcG1 zbL_hF&)}42ws10s6^G;;cE1^EoUR)U5A70}d2pLv!jVIT7j&Z~EblI3x0K*v_sV|m z0kj3v921Z^em#l`(k(o@H$3ZdDRc@9NidXDNbqrumReCGv$gd8+e8WW28HVqkJ_9i zH>s*<31KtHjANIPvi2#*6BEu%3Dak5O_t&NBI)H?V$TxT}#l{vOTn5naXTfF^&~Hhq+NX@#Ccc>y7T?;vjI&jdhsDsPJyAw*m0Qz>i}K7# zL9w50Ng{fT}A5JUe8lRK1h7_Y2;BWJDd=c6f&i?Wv5(5q?6|P zQw{>maxZP<537OA37Uk}7@%_$4o$EWe_Zl>&#id|lE-BpDC#+Fn|msJ%_2h{Hg1vP z#N8WAzfWasG}yq|xqE)DrWaOofX=z|?*pgc%{ig5vl!pqDlC|q&~Z0$&Rvsft&VO- z4MZj+%-+Vx%W}v;V76hyp=;+R;x+~t^Q%*xuFTQAF2})fSfTHDAs>sO!OBw`)&)o$ c0!CNZt))x~rAZP^^P&YOFfdqy5)K#u0POD40{{R3 diff --git a/core/src/main/java/google/registry/ui/assets/images/android_sad.png b/core/src/main/java/google/registry/ui/assets/images/android_sad.png deleted file mode 100644 index 166d49fa77b094e83c105fd445fa0921c2cb95ba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2601 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 diff --git a/core/src/main/java/google/registry/ui/assets/images/disclosure_arrow_dk_grey.png b/core/src/main/java/google/registry/ui/assets/images/disclosure_arrow_dk_grey.png deleted file mode 100644 index 8d3d124fb643e8707eb926bc83a5e6746ffcbdb9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^EI`c8!3HGtQ{P+wQj#UE5hcO-X(i=}MX3yqDfvmM z3ZA)%>8U}fi7AzZCsS>JigY|(978H@CH3_5{5gN%z=0DCtUdw1>~}H52t+dIbm$%E j&^gethBc4dp@EU%v>~hHU4ajNKz$6Ju6{1-oD!M_E)I!3HFqj;YoHDajJoh?3y^w370~qErUQl>DSr z1<%~X^wgl##FWaylc_d9MLM1?jv*Ddl6rc2{+w@MHefVVHtS41J_E)T!3HEZPnf;}NJ*BsMwA5SrsiY0lC)n(h z4=5@>k(4I?^(zav;78Tk7n%lnB8!`*&n$H-IRAvRNnWG9JYh|}&nm4lw>iOGcPrxe f{0(?3&&b9Qf6Gy&um7Vn&=v+yS3j3^P62 diff --git a/core/src/main/java/google/registry/ui/assets/images/explore_24.png b/core/src/main/java/google/registry/ui/assets/images/explore_24.png deleted file mode 100644 index a2c32fa19d96d3291662e3d7fecb579bab9801e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1016 zcmVdbVG7wVRUJ4ZXi@?ZDjyCFEKPPF*G29EG7T|15rst zK~zYIt(HM=8fOrOpIHzDn*(Wskz8ma6{4EjhvW~q?SZR(sFVh^hc=aQD(B$Hs+vDg zSCv%`bsb|hy`%zJie&j{4iWwX?I8-PQgzi9V_=gi9K52eS-u{^B8&mMQs%_W;hA@4 z=bJ$oVd-@Gve)aq?sB<40OCriAb^NG1B$k7KWa9c+sn(#e-2~Fpy2xY`dc24XSLmK zUl$Q=lnA9%Thp{{rPNocRO;_D6J#=(q*5v)BK}d}0f~-8B$Y~~vO^PWY;4?dxm;_# z@o+dyEEXde3^G1G4uENzR4Ns8U8h_w_vKMvE-o&vol20&WRk$oPAm`zFgrU-C=?p0 zyjH8RzrRntUhgIWK6WaR4*X4})Dx#IlarGq5(zvW&)K~JEXyLF&vSTq=#+8PYPF`9 zmX`j~07T>)2ObCn{ulh;$G>7ed_W?RAP@-9;rTrt&nkeV)9K4TpYIQ6TIS~F&TNYV zev!RT z8{hqiH}=jc^^Wy*Ag(#jAL!0`zrD!b=hCeeyjS9Quf(}@Yh|E%0Kfml`<&23WZ=5! zRo~y1Vd?>F+dkoIfZ;f%X%18m@ciBy&8J10Pm4Uiw>D7y0EXcPia;2kQmJ%jxnF(r z_unYr`5fQOHD3Sh_CWOj)oQiZExK*ne&lkw5&*icGc`4JN!;^L;HMTDe@tFbpOpCc5g+3kEO@gM)(u2i|Tp8aV;j+SRWUz5 zkNNPy&<{Dtj*pMo+1cqXJrT(*EG#570L!xODy5D9>h(JLe4gUFxmK$+-47pFL9cag#SIa8Pbu}Thy**( mXCm@MDOG4R8o5z_TmAz<1)kcw4+B5|00004nJ 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 Te5G0v00000NkvXXu0mjfN86Mo diff --git a/core/src/main/java/google/registry/ui/assets/images/homeResources.png b/core/src/main/java/google/registry/ui/assets/images/homeResources.png deleted file mode 100644 index 019a5ee681af720c253b26cff3d8c31c55d8acad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3890 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

S^Bc7Ja5Y2CcPo`0Ngl+Ohz!EEwtn{&l| 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 M-9WA3sl)640c1ib%K!iX diff --git a/core/src/main/java/google/registry/ui/assets/images/ic_contacts_blue_12.png b/core/src/main/java/google/registry/ui/assets/images/ic_contacts_blue_12.png deleted file mode 100644 index 8d718a9d13f0f072bab9600cb7a2cad28fd8b1c2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 535 zcmV+y0_gpTP);9Ey1u#UvNpW_Tff3c%Ymg@nXw;4o)J zgZr8|Plyy~$wd;<*+AF5uKBkiPZo_cuNEQVM)%@duZAxRLb|JBP5A=>SMySkb$fb8 zDF;4oYGR%NahLEi;JlC43QepEDp3`9W`@%eo2KdoUU`HU z-hbNeJp;roD-sxWGlUo8o1|pK?aRgXo&kq!Wz^7I2!F?TD*$)`?rXB5{yUM-vz(}7 zj&ng~mDvBkMS=z3f!?vbw8{Hvtx4PYU9($}$tOZC%^?HjZh6h%hK4za$m+{NK|izY zYQup>1R5~HRqoX3$X^QZfZ2h**bF2xL{&+$pXj{ooP`~uB)(0`1~Rzbk`yo#hh=g( znXpvSjE~7){Z1+nn<&56hXWi>)V}@|a1`Aj=jM6!c71G002ovPDHLkV1nT6@^}CM diff --git a/core/src/main/java/google/registry/ui/assets/images/ic_contacts_blue_8.png b/core/src/main/java/google/registry/ui/assets/images/ic_contacts_blue_8.png deleted file mode 100644 index 67723facdd5342150ce8131f85f8f4df19cd77a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 327 zcmV-N0l5B&P)$vh8@`D7+zri z{gZ!Nz0fE3o|oKze(`)k)rQH#Z~@3>zea}sKdjsZFy$n<0H%pp8aWW(13g;8^6!t* zM-gr|hX2e8z%*72H0V7(!{A={&-g!ZZzaz`gkp||Kz5&F+t+ng0FPLKXwDv5PYvXQVVc Z0|0_qU6bh$= - - -]> - - - - - - diff --git a/core/src/main/java/google/registry/ui/assets/images/icons/svg/search.svg b/core/src/main/java/google/registry/ui/assets/images/icons/svg/search.svg deleted file mode 100644 index e948041f7..000000000 --- a/core/src/main/java/google/registry/ui/assets/images/icons/svg/search.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - -]> - - - - - - diff --git a/core/src/main/java/google/registry/ui/assets/images/icons/svg/settings.svg b/core/src/main/java/google/registry/ui/assets/images/icons/svg/settings.svg deleted file mode 100644 index c8b484f61..000000000 --- a/core/src/main/java/google/registry/ui/assets/images/icons/svg/settings.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - -]> - - - - - - diff --git a/core/src/main/java/google/registry/ui/assets/images/loader1x.gif b/core/src/main/java/google/registry/ui/assets/images/loader1x.gif deleted file mode 100644 index 88cd65c49f46d7684b9be35e4671f1ac5687399f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28577 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 vH9AGu(+aXNN=uj+CLL-CV3|95nvN diff --git a/core/src/main/java/google/registry/ui/assets/images/loader4x.gif b/core/src/main/java/google/registry/ui/assets/images/loader4x.gif deleted file mode 100644 index 1e8e40af0b0f204d9d3d615d8cb4cbffb38f790e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 81351 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

dsmLXtY@A3vzh%XOYwy_}M(1beeI(o`n?lCs* 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 diff --git a/core/src/main/java/google/registry/ui/assets/images/logo.png b/core/src/main/java/google/registry/ui/assets/images/logo.png deleted file mode 100644 index 0531700ca7468c5c0705632a0af108c92c43cdbc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3358 zcmV+(4dL>MP)00004b3#c}2nYxW zd2ZlvW5~4_O)ld;CCr56QWO#i zeGSUEjN8O?QxTQBMHkYI%9ueS5*g&qgps6(F)<^Fgr@mpy*ss#%>> znAt+$1pK*PQ_bpRVK8tNaC}9CNZLhGkIGT0IEord1T(u0I1)Hn(v?-G(%K0#`ybE; z7zw;y&FW;r%svBd##imN5!VCv0gqHr&R)QfimQ_k>lW|w1I+A6Nml?@OS;$0W>=A3 zYbDI=+UnLJCte4BE@{)X&Alk)+<5T3-mQ=SgWkzz#9a}5V!zUFB6cZDk z2HXkM&!QqFH3ZJanV9W>+Q4k!1z?hy{k4Gnk-(Q`)+7ACA8;Ga>@2_o!NG|!=Kz-j z^?~Prv(4=70&^S=oC>r8wg%<{Q-RBa`;s>1BH%zXYZp6rC~ynVBE$$t5+2Vr#;NvO zfc|FoeU?6j5a{8H%&bw){sVw6X4W91y#a7O&=F@h{|CGa3Jx=J-N6iUe_scf zC~1PEtuo{<1`Z0cjs~6vKERD`8gPAJ;G+440=;mbwFj^PFga^Qm(&~gk+;Sl%n`sH zz;A(Bl3HfywH0pkam)jD0d|&DOVSu%0Prv#SdRm)2-jZakhC3e9{$p&wYLP`0=nP~ zSx2BN@HifWy(Fn=kvaMSf5%_u0r<1M0bdy2k<>bL-4B13anuExq{yrd+>bwt0l-l~ zPm^?6qWg6Gomc_f6*~r-**Cy@z|KGuNp&Sn#;J9MIcDMuML~p}_B>!_$!-dOnJts_ zF22IA!h(g>^mbq@&^J{bD!1&jh-mDJ44{*#zuKAsO~68g0@vqwYQcu7lv%filc z0M4N7Wo91+`J;hXaQE=Sgnlh>)}@!3^^P@7k#s9C8W=07shK4oavJ7`zx9EuaRcAO z%%bxXB|QU7kTlZFrsa@X7QzdDAHKQ|OljX6cocsQN<5oUl7;|dfa#L9c%g<9=o28UvnE^OMbXN#jp?15;(=o#)NZIBrU<8_07>vXJ!vb+6Xwo%vQTj zGg~a_3!q`RA4`CJ&1_k?hT%cxL1y+$0iBKl>H&9^yQ2WSk8gb|A*mU@vJ5h_PFXy@ znY|3`4SWIIn9^khPzzsKEM_DTuESTe4mb^ZW@IEm{t`TiDoM2xb3Y9MR2 z9CJ;>?{BB*L|`1-;=6f4gsO!>Ip&Fvc@K{>MoIcpB{7E2nAsZ{Z4-g-aN-~ho$<|J z5|o%nNnl4jsE@E)koQuFa$X{P^?s_rxUYbDNjE2kXsh0=(BzV~!~?&r@EvG`eBOR>oCVCq znW+my%s4b9hRuu2_`}SWN*asro@w{wbYOh&e9GHVB(=hW*Xgm-jc^Ohvtq?D3uu;% zC4es}o&hgpNSTndP4MgI0-xXkd=GqQI{?@|bS;{dak6#8QmJ< z&u8nL$1GN*UK;?5N^pbb;+5#7T*voj))%L&uLHUP?@Q`bcw{D4BO?+R2J9{ASMigQ z_QfgYm2q0RJ4$yv$e9&8_aVL_Z;DsC=0!u`Ev6He%i zmbAQ-Twg%Jyp@@~BIz|ew(A~i>I%GVvCm6N-cdq=?HE?Cvx!#!WB^zU&4Cw7)uS9p zs*lswV{kfoOjg45#|AT7j=L-;1^PXS`M_#Pz7jAWe_!&(SrwTJaQ4#7y5nr!;9~vD z#4w5*$XL+;yx~~Y)KnSDr8t=fd-+Xpt3Orcd?^5xgD@E za8JBbZbJ!F;S<0|WVEE-ofO(lL^Trx?`;;1Wr{C^CL`JQ&WqTek*Yle9@jdmX&YrqbQpu)t5^hnz}R zoS69}-mj>k1Z%$tcpl$=TSrVl%A}L=n|W7o(XwRLmY3>x0nW&hR|R+-4^-P{=(Ras z@AcLi(x=i9DEA4#HS%w0qVw-w5Po zt0HExYesulU{)DP$GjL0930-+R-`0bqhNm1kYI_jYG4RqPtX z6}YjViU;`-2I6&T_lB3^9z4;v9H+vCzm$`3x_K3t*>ZUu-xT0wN#_Ainb{YTw!mxU zZo=8n-ng${6rtp#FK*ml;iSU~N%exxN8;~dRbe{L(v8FOVXGxnqZQ{i;9ueR5Z^sU z0|(;FP$M(zpTkI^0PIw}e4ssEGOyT0~0XNn{IG+MOYgmz50slyx{}^a3X%D(1e__U7qBl*@z2FG|M9qF6Wj>ncmg-bq|6n-#ggv87wFA^e*#ZuxLl6| zhZIn=Ev4o3UJZX!tGsKiSn-qmoroG#F@l@@IldeGrMmSg21%oUre^l*wDyYBdexu? zg%D-C*V^9~h#>z$vpogA8LBaq8dMc};%?L2>Q*ENI^ec2E~`UDiXv-JgH!}&>`>sa z>eeMEy5PTvnwj^hF_apV3`rN@1kliA@E<7S!T9eQRN2>@l?6#V;$NkveG|13HHK1y za!?EC51c6JQJgVrf-|W5 z-sGbU@ec))d3obeed}p$>m^mQ6)p2sUGp!dHXEjgw{2`%!VSjGiA{xw%tT}65=3^% zc28|7V~3qTTRE;DamaUa<9K*I_wuO=Jy~^zRuCRue&q@;GIH#w_o}n&wwvpU_ldoL z6M~?#dlya*2hgTM!upcpcfu~NL`AJ9M2j+T02_kkNr)6liB!mgl?2RlMY(KU^b>V# zy_#&Rwr-E5UCHsIOdsEh3jqxwmidMc|*saz84>e%688kHr6} z#r%9O{UgDzTdk=_qsj3OE1wpMAICSiI5fJ@`An04=wpxI zcHhwFzE_(s$2<;;E5DN1Z0^%-?cZbJH(+^c*ec+a&Dk+~+H1S>6ZV(hlh1UyT^jHT z?GB9TCkMZE4xMnn_Lg*M#v%0MvB>xS@l!!bBaYYRoFf+8qZi%dKDk6KdnA7Ly7lQ( z5;HNmAU37?7e+^HT65B5YFyUqD=CwHsViqWUoU1Xh32isKlq$jx>k_+Yg%SOLEgQh zqT>1-PV?iOxzIK1`Bw$BwW*E_n#p76hR41TWZ+ITUz)!p4a zI503gJo0vEU}#h@{x6ds{$*vTe`~P&uZdSX)03}033gXThbLCYKW=?oUi`SYwYa$W zWoc=3b#-TTWqWUB>C5)wm%rCGer#@Te%smk`SU06KUBVn{;&1_oB#a;fZPYZqZBAS zZf7!5N;hzTSK6Hhsks+Zo|pBe{`gBnzSnptS8HwWOlB4S56iI06V>%G9z~Y%-(7}^ ztEp;v8kPkF9VX(;u?MJahEK1KRHYlmNT)5u%}49RWZRq+s&S<^ajiwer>uv~QEMCV zdSOjevhXR=^p!OKa<4Y9=@KJSJw#H&BmbClqUF7(=hAlPQx{H)QgY92{e4<2R`(kB zRBKYIUMx=0`o==nM|2A1?nk!_O7Hk?b5!?D_Z)F!g}Oh^-JCualp#7aX>?P2{?aR;>nxi{;G+y-r0%RcpofrZX|gwqZ+FMB-X(+Ms?o4ilsUI7)j2K(SuJF zC%_4mdCIgIYv53;?cuTO5P3??@d9>PHPP{O9zLr;dILo?-q(<{=|^sM|Aht z;kC(ij&UF1BL8`&&EH%%s@p8R;Z@1Zm1L~NG*A5w-QZ>10(mE8+7W+IwR?d)W2QN% zT*vl3Xy5+}))LpBH zQZ}GArakB>>2hqal)QuVal~3gp}XoW2tL8~Pq_I=rPiutS^JeO8XU)4o*y=9FTUH= zsw1F_haf+S^b>-_$A^uy;Zm5Ix`fWm>&V}43~5x5L_9Qd1Iyerb4AO-4c|_^Q;>8Z zxU1T~{vhl;*uFY{)W|jt2u0hxedK?n>3ZbAp_fr1zLm;CE{3?oH66A6Q`%Gn{PQ|A z!fF3q&qcp2CHc6W)2L}SA)v9Xm#B*=#A-bh&SQvgsXj-~P{eXD8Oz5ssFF@C+84?9 zZ$Whe)89;wp$~=gV0e%JT*%NYAZ&mIw-j@`_nRBb3Bh)Y<#fi(0qOC^_KR^07k`hh z7jUv}S0hqQR&mlZ8V!5QfH8r#YPx!QN@VuHhz6qvf=P!zCB0rX5HVa74tzYQNF$QP zSW;WBKIAHF^skt$cOS=_$MJ`Z>U5esiG(mZLsYwP;Sba6%=IC5i0-4_133Xf)uha8 zl5FE8cp5trZf0)jJ@{vNuf!Xu75!LBwCqghDK$#Kj?=>xERkWRMh+b~Pf=)wbeZq1=Q20__KIT0RO5h%=9AoK~@zMOZUq3rk%EzR!5OC zyKUd?eNaILVZ=8?P(d|wS(6dT2b1_>;ftX02_8(kxakD_(eR z53Pm&6ZOL!J~Lm>vY$*B@pXUNKR} z#3x%G<*-z8ObxOq=j~_5`W&V@56nwL`ZKZm!3?g50Xr3I%VCfj0r64lA{$?2B&zTHISV$zA99Ku!{?yt5y|dq=gw@ zuBiW!e$~*#P$ZoU6I?U1a&;d4>Ehu68CtY`ZPj;Acs-*oiS1+J}aqqz)X>U$^o- z%Eq6ln>*_OK@Otd_4}-oCRv3;eETq{`0!UK_iGD7Qwzq zoiXKjkVWiKk@iZ5`l;8`kv)31O3So+eeN08yP~x_7%0AKFGi=odWAENf&mt*uGWV9aB(UL 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 diff --git a/core/src/main/java/google/registry/ui/assets/images/ribbon_certified-64.png b/core/src/main/java/google/registry/ui/assets/images/ribbon_certified-64.png deleted file mode 100644 index 6d9b88deca14edf381693f3647b73b9b8229f351..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1470 zcmV;v1ws0WP)?*0DwH3vjNHY0mV!6aS|x_s9cl*=;eaH)mR6=ytNwccXz;p z-Y$4_U>^)0{0M&C|DN^td%L#4&#fzAu)ekugm_c|707qBGI1dD8Mj+@E+e!lmKnfnWz~NMPW85xG6-*zen&7rDx&go~csOca znHPkR2^;{K_9ej#ZQtm*c>xIdNC3Fg)F@@>x6ePZB=Zc`6i!VD1whOm1O`Rhny8+h zKWj;)FtQ7#ux3E90N&l)CT-;JA8%MvNr#`7&Swi@JTL(4i;c?Yqn?dCv837_y?TL? zHJ{=wbYu-+?$b-Q4p4DwfuS-JT(9HrFhE~g`;0tcWn{W16HrCJBD z<#GV%egi1k*eDm|pI0t{>h;(6*WmHtLyq5#FHjr+Rel2)_sWaw{YoQc96JL44nGn$ zj)n0_-|>py%K?xJstf2c0H|<8-Hv(%05J%l3&iaGUj_hbr2n*|dD&vf0gw&?fX=P~ zRI5LG{V{OCEdU6m4ax*Pdgrzy0Q}75Vkn$8h$A5y^natHmRbB0(*cVey@!`*{-baK z7DgLPt8RYK{KqPfoq^XSWaMZN=b!sS+)>7Rm;$1UB8U9fA&53; zD~5*5E|5r(!-bOw0A|A!yOI{_mYC=)@Isu?kH(gm%ASBn+hVy$08o81l#xT_FNC!v zR5SQk|*%E^?r@jcm>zPo-yy5nM#ESz_oh*BXAsE)WG5;@XDU2s6^xdm_cQBG#`Yk3+EOCzF1cWR~KnY{}}?!vVULI zk*5F(kpf@_q>;d2*2Zt)%~gk>t~m?UYje=s_^uLw#?C?O`_^u|U^i};9wh)d8kpAo z7-p>h#xh^s_BG6D>4X0IM2SJX*SV}`+>D|6Ao(HBFe}4UJahnN5KU}wwY%XGF|XWkLmD1`xx1 zsIYmi%H&=FfPKevYtS-f~^WxLsP90CcCo=JoszGd5hbnLlIG zcnB}8J7@jAB7j^d05Jb51vj_rp*6Z=BX1gSK0PJ^fdZKQ&Y-G!s-`diWJQ9Tz2jzC z060rrng^1GvA_V>+n$JKZo14(ftvOGus|OwcmP1TEvf>@%LF&C$AIc}=Szy|$l}RT7Wqk!e+MZFaA`@|cr)JxYV(Z&_;QD7w zK{Z4we!1^gX^XM|5+nefNaOv3<=W%Sw=Y3M*T9I(GemjLGgTyp6nQ=?TMImU84_)g zTK6z)`uJ8-W>Mv~)ddcqLSAPS>Wkr5oPGk(94d88^u@~SH@`pu5Hkf?BM#u=6BU4c zN*5%a%Xs<57bXCE+e}D2XK#zzgats%q*dP7NL$PM9bXUta5SqFpVcNjfT-&QfXNQv Ye*<$SLdo&}pa1{>07*qoM6N<$f~M@V82|tP diff --git a/core/src/main/java/google/registry/ui/assets/images/visibleOff_16.png b/core/src/main/java/google/registry/ui/assets/images/visibleOff_16.png deleted file mode 100644 index f83bab423eda7d32d3678fe4b3e76607606ac2c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 556 zcmV+{0@MA8P)dbVG7wVRUJ4ZXi@?ZDjyCFEKPPF*G29EG7T|0fk9K zK~yM_m6FeDQ$ZNTf8X3d>J7{yC?urd6($TW0=|M2q`rVI-LzZr4SWL;aU}}fx#-GG zU6npTH;Ri8K^G0&&}5O^IAEAN<0g^Ya%ss~%^$wQZ_XS6u+|=j5KK`NPx3s!WD6;P z5W+(dd8D;I?Q}Z#wl;9i?Z+5@0)whamSsCxmhIeRAe%+6*V|3g^h`ty@O`mZ>?KLk zOw;rXxGN&(Wm$fi&*w+e>9n{)0MKf+o{C6&y&Y(+Ps_6W>zsQGOo6>5N#3>F?cc_j zgDOGN@Ap56$cq}nDaSQinCezkmlfnE)S^QmUvK#(V#~8tmpd z>$8m(0*JNtZH)2dHIJ{}``3*|;~2PqokS^hG8_(H{r?4P?XwWV8xiT%5mhsZF}lfQ z@&_Q<+{|XPAKh;Ez4v~olnOu^W4r^DB60yZz%-l9{ncvqdAVFVsPy1A)%7IL^GkK> uH(?~ z1*vl`N=+=u%+FH@$;?eH5H+!wQONy_H7-K`ny^a^1GvBi}AJk_LaBW!ml%VA< zTX+r_f1bGY+bPTRnU&tzcNr60o}^mMm0OZo#mpepkY^&5?s7$O5eLJJN_*eSc@k}_ z&+IXf;gk1TdZ|4qYi@_q(va0bE2p?GzNmcl^;d3&-LBe>0#no{pX@SR#?El>w?)^Y z^yhc1>xx!(nWgm{e_RmvyV-xL%E@^hbHqcFeY8(++V|b=+r8tF)4qPT$rrEgSTS4W z*j0)#JZ@c#E$##L~=lQd?MtNuN)l}Npp;Mb*t-46g`2N}bAGv2GO!l5K T)#4B^lo>o-{an^LB{Ts5NXxyY diff --git a/core/src/main/java/google/registry/ui/epptmpl/contact_info.xml b/core/src/main/java/google/registry/ui/epptmpl/contact_info.xml deleted file mode 100644 index 6c9de20ad..000000000 --- a/core/src/main/java/google/registry/ui/epptmpl/contact_info.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - sh8013 - - 2fooBAR - - - - ABC-12345 - - diff --git a/core/src/main/java/google/registry/ui/epptmpl/contact_transfer_request.xml b/core/src/main/java/google/registry/ui/epptmpl/contact_transfer_request.xml deleted file mode 100644 index dddbf6786..000000000 --- a/core/src/main/java/google/registry/ui/epptmpl/contact_transfer_request.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - sh8013 - - 2fooBAR - - - - ABC-12345 - - diff --git a/core/src/main/java/google/registry/ui/epptmpl/domain_create_no_hosts_or_dsdata.xml b/core/src/main/java/google/registry/ui/epptmpl/domain_create_no_hosts_or_dsdata.xml deleted file mode 100644 index 41c6a586e..000000000 --- a/core/src/main/java/google/registry/ui/epptmpl/domain_create_no_hosts_or_dsdata.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - example.tld - 2 - jd1234 - sh8013 - sh8013 - - 2fooBAR - - - - ABC-12345 - - diff --git a/core/src/main/java/google/registry/ui/epptmpl/domain_delete.xml b/core/src/main/java/google/registry/ui/epptmpl/domain_delete.xml deleted file mode 100644 index a0f5cc345..000000000 --- a/core/src/main/java/google/registry/ui/epptmpl/domain_delete.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - example.tld - - - ABC-12345 - - diff --git a/core/src/main/java/google/registry/ui/epptmpl/domain_update_restore_request.xml b/core/src/main/java/google/registry/ui/epptmpl/domain_update_restore_request.xml deleted file mode 100644 index 124bc0b89..000000000 --- a/core/src/main/java/google/registry/ui/epptmpl/domain_update_restore_request.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - example.tld - - - - - - - - - ABC-12345 - - diff --git a/core/src/main/java/google/registry/ui/epptmpl/login_valid.xml b/core/src/main/java/google/registry/ui/epptmpl/login_valid.xml deleted file mode 100644 index 210c07aa3..000000000 --- a/core/src/main/java/google/registry/ui/epptmpl/login_valid.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - NewRegistrar - foo-BAR2 - - 1.0 - en - - - urn:ietf:params:xml:ns:host-1.0 - urn:ietf:params:xml:ns:domain-1.0 - urn:ietf:params:xml:ns:contact-1.0 - - - ABC-12345 - - diff --git a/core/src/main/java/google/registry/ui/epptmpl/logout.xml b/core/src/main/java/google/registry/ui/epptmpl/logout.xml deleted file mode 100644 index 1a0687d52..000000000 --- a/core/src/main/java/google/registry/ui/epptmpl/logout.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - ABC-12345 - - diff --git a/core/src/main/java/google/registry/ui/epptmpl/poll.xml b/core/src/main/java/google/registry/ui/epptmpl/poll.xml deleted file mode 100644 index f6f78893f..000000000 --- a/core/src/main/java/google/registry/ui/epptmpl/poll.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - ABC-12345 - - diff --git a/core/src/main/java/google/registry/ui/server/console/ConsoleApiAction.java b/core/src/main/java/google/registry/ui/server/console/ConsoleApiAction.java index 5a02ae2dd..8625bef98 100644 --- a/core/src/main/java/google/registry/ui/server/console/ConsoleApiAction.java +++ b/core/src/main/java/google/registry/ui/server/console/ConsoleApiAction.java @@ -16,9 +16,6 @@ package google.registry.ui.server.console; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableSet.toImmutableSet; -import static google.registry.model.common.FeatureFlag.FeatureName.NEW_CONSOLE; -import static google.registry.model.common.FeatureFlag.isActiveNow; -import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.request.Action.Method.GET; import static google.registry.request.Action.Method.POST; import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST; @@ -36,19 +33,15 @@ import google.registry.batch.CloudTasksUtils; import google.registry.config.RegistryConfig; import google.registry.export.sheet.SyncRegistrarsSheetAction; import google.registry.model.console.ConsolePermission; -import google.registry.model.console.GlobalRole; import google.registry.model.console.User; -import google.registry.model.console.UserRoles; import google.registry.model.registrar.Registrar; import google.registry.model.registrar.RegistrarPoc; import google.registry.model.registrar.RegistrarPocBase; import google.registry.request.HttpException; import google.registry.security.XsrfTokenManager; -import google.registry.ui.server.registrar.ConsoleUiAction; import google.registry.util.DiffUtils; import google.registry.util.RegistryEnvironment; import jakarta.servlet.http.Cookie; -import java.io.IOException; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; @@ -83,25 +76,6 @@ public abstract class ConsoleApiAction implements Runnable { } User user = consoleApiParams.authResult().user().get(); - // This allows us to enable console to a selected cohort of users with release - // We can ignore it in tests - UserRoles userRoles = user.getUserRoles(); - boolean hasGlobalOrTestingRole = - !GlobalRole.NONE.equals(userRoles.getGlobalRole()) - || userRoles.hasPermission( - registryAdminClientId, ConsolePermission.VIEW_REGISTRAR_DETAILS); - - if (!hasGlobalOrTestingRole - && RegistryEnvironment.get() != RegistryEnvironment.UNITTEST - && tm().transact(() -> !isActiveNow(NEW_CONSOLE))) { - try { - consoleApiParams.response().sendRedirect(ConsoleUiAction.PATH); - return; - } catch (IOException e) { - throw new RuntimeException(e); - } - } - try { if (consoleApiParams.request().getMethod().equals(GET.toString())) { getHandler(user); diff --git a/core/src/main/java/google/registry/ui/server/console/ConsoleRegistryLockAction.java b/core/src/main/java/google/registry/ui/server/console/ConsoleRegistryLockAction.java index 760deab20..b5d3878d5 100644 --- a/core/src/main/java/google/registry/ui/server/console/ConsoleRegistryLockAction.java +++ b/core/src/main/java/google/registry/ui/server/console/ConsoleRegistryLockAction.java @@ -19,7 +19,6 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.request.Action.Method.GET; import static google.registry.request.Action.Method.POST; -import static google.registry.ui.server.registrar.RegistryLockPostAction.VERIFICATION_EMAIL_TEMPLATE; import static jakarta.servlet.http.HttpServletResponse.SC_OK; import static jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; @@ -64,6 +63,12 @@ import org.joda.time.Duration; public class ConsoleRegistryLockAction extends ConsoleApiAction { static final String PATH = "/console-api/registry-lock"; + static final String VERIFICATION_EMAIL_TEMPLATE = + """ + Please click the link below to perform the lock / unlock action on domain %s. Note: this\ + code will expire in one hour. + + %s"""; private final DomainLockUtils domainLockUtils; private final GmailClient gmailClient; diff --git a/core/src/main/java/google/registry/ui/server/console/settings/ContactAction.java b/core/src/main/java/google/registry/ui/server/console/settings/ContactAction.java index cf2e9e96b..1a183aaa4 100644 --- a/core/src/main/java/google/registry/ui/server/console/settings/ContactAction.java +++ b/core/src/main/java/google/registry/ui/server/console/settings/ContactAction.java @@ -16,19 +16,24 @@ package google.registry.ui.server.console.settings; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableSet.toImmutableSet; +import static com.google.common.collect.Sets.difference; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.request.Action.Method.GET; import static google.registry.request.Action.Method.POST; import static jakarta.servlet.http.HttpServletResponse.SC_OK; +import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Multimap; import com.google.common.flogger.FluentLogger; import com.google.gson.Gson; import google.registry.model.console.ConsolePermission; import google.registry.model.console.User; import google.registry.model.registrar.Registrar; import google.registry.model.registrar.RegistrarPoc; +import google.registry.model.registrar.RegistrarPocBase.Type; import google.registry.persistence.transaction.QueryComposer.Comparator; import google.registry.request.Action; import google.registry.request.Action.GaeService; @@ -36,11 +41,14 @@ import google.registry.request.Action.GkeService; import google.registry.request.Parameter; import google.registry.request.auth.Auth; import google.registry.ui.forms.FormException; +import google.registry.ui.server.RegistrarFormFields; import google.registry.ui.server.console.ConsoleApiAction; import google.registry.ui.server.console.ConsoleApiParams; -import google.registry.ui.server.registrar.RegistrarSettingsAction; import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; import java.util.Optional; +import java.util.Set; import javax.inject.Inject; @Action( @@ -97,18 +105,20 @@ public class ContactAction extends ConsoleApiAction { String.format("Unknown registrar %s", registrarId))); ImmutableSet oldContacts = registrar.getContacts(); - // TODO: @ptkach - refactor out contacts update functionality after RegistrarSettingsAction is - // deprecated ImmutableSet updatedContacts = - RegistrarSettingsAction.readContacts( - registrar, - oldContacts, - Collections.singletonMap( - "contacts", - contacts.get().stream().map(RegistrarPoc::toJsonMap).collect(toImmutableList()))); + RegistrarFormFields.getRegistrarContactBuilders( + oldContacts, + Collections.singletonMap( + "contacts", + contacts.get().stream() + .map(RegistrarPoc::toJsonMap) + .collect(toImmutableList()))) + .stream() + .map(builder -> builder.setRegistrar(registrar).build()) + .collect(toImmutableSet()); try { - RegistrarSettingsAction.checkContactRequirements(oldContacts, updatedContacts); + checkContactRequirements(oldContacts, updatedContacts); } catch (FormException e) { logger.atWarning().withCause(e).log( "Error processing contacts post request for registrar: %s", registrarId); @@ -127,4 +137,165 @@ public class ContactAction extends ConsoleApiAction { consoleApiParams.response().setStatus(SC_OK); } + + /** + * Enforces business logic checks on registrar contacts. + * + * @throws FormException if the checks fail. + */ + private static void checkContactRequirements( + ImmutableSet existingContacts, ImmutableSet updatedContacts) { + // Check that no two contacts use the same email address. + Set emails = new HashSet<>(); + for (RegistrarPoc contact : updatedContacts) { + if (!emails.add(contact.getEmailAddress())) { + throw new ContactRequirementException( + String.format( + "One email address (%s) cannot be used for multiple contacts", + contact.getEmailAddress())); + } + } + // Check that required contacts don't go away, once they are set. + Multimap oldContactsByType = HashMultimap.create(); + for (RegistrarPoc contact : existingContacts) { + for (Type t : contact.getTypes()) { + oldContactsByType.put(t, contact); + } + } + Multimap newContactsByType = HashMultimap.create(); + for (RegistrarPoc contact : updatedContacts) { + for (Type t : contact.getTypes()) { + newContactsByType.put(t, contact); + } + } + for (Type t : difference(oldContactsByType.keySet(), newContactsByType.keySet())) { + if (t.isRequired()) { + throw new ContactRequirementException(t); + } + } + ensurePhoneNumberNotRemovedForContactTypes(oldContactsByType, newContactsByType, Type.TECH); + Optional domainWhoisAbuseContact = + getDomainWhoisVisibleAbuseContact(updatedContacts); + // If the new set has a domain WHOIS abuse contact, it must have a phone number. + if (domainWhoisAbuseContact.isPresent() + && domainWhoisAbuseContact.get().getPhoneNumber() == null) { + throw new ContactRequirementException( + "The abuse contact visible in domain WHOIS query must have a phone number"); + } + // If there was a domain WHOIS abuse contact in the old set, the new set must have one. + if (getDomainWhoisVisibleAbuseContact(existingContacts).isPresent() + && domainWhoisAbuseContact.isEmpty()) { + throw new ContactRequirementException( + "An abuse contact visible in domain WHOIS query must be designated"); + } + checkContactRegistryLockRequirements(existingContacts, updatedContacts); + } + + private static void checkContactRegistryLockRequirements( + ImmutableSet existingContacts, ImmutableSet updatedContacts) { + // Any contact(s) with new passwords must be allowed to set them + for (RegistrarPoc updatedContact : updatedContacts) { + if (updatedContact.isRegistryLockAllowed() + || updatedContact.isAllowedToSetRegistryLockPassword()) { + RegistrarPoc existingContact = + existingContacts.stream() + .filter( + contact -> contact.getEmailAddress().equals(updatedContact.getEmailAddress())) + .findFirst() + .orElseThrow( + () -> + new FormException( + "Cannot set registry lock password directly on new contact")); + // Can't modify registry lock email address + if (!Objects.equals( + updatedContact.getRegistryLockEmailAddress(), + existingContact.getRegistryLockEmailAddress())) { + throw new FormException("Cannot modify registryLockEmailAddress through the UI"); + } + if (updatedContact.isRegistryLockAllowed()) { + // the password must have been set before or the user was allowed to set it now + if (!existingContact.isAllowedToSetRegistryLockPassword() + && !existingContact.isRegistryLockAllowed()) { + throw new FormException("Registrar contact not allowed to set registry lock password"); + } + } + if (updatedContact.isAllowedToSetRegistryLockPassword()) { + if (!existingContact.isAllowedToSetRegistryLockPassword()) { + throw new FormException( + "Cannot modify isAllowedToSetRegistryLockPassword through the UI"); + } + } + } + } + + // Any previously-existing contacts with registry lock enabled cannot be deleted + existingContacts.stream() + .filter(RegistrarPoc::isRegistryLockAllowed) + .forEach( + contact -> { + Optional updatedContactOptional = + updatedContacts.stream() + .filter( + updatedContact -> + updatedContact.getEmailAddress().equals(contact.getEmailAddress())) + .findFirst(); + if (updatedContactOptional.isEmpty()) { + throw new FormException( + String.format( + "Cannot delete the contact %s that has registry lock enabled", + contact.getEmailAddress())); + } + if (!updatedContactOptional.get().isRegistryLockAllowed()) { + throw new FormException( + String.format( + "Cannot remove the ability to use registry lock on the contact %s", + contact.getEmailAddress())); + } + }); + } + + /** + * Retrieves the registrar contact whose phone number and email address is visible in domain WHOIS + * query as abuse contact (if any). + * + *

Frontend processing ensures that only one contact can be set as abuse contact in domain + * WHOIS record. + * + *

Therefore, it is possible to return inside the loop once one such contact is found. + */ + private static Optional getDomainWhoisVisibleAbuseContact( + Set contacts) { + return contacts.stream().filter(RegistrarPoc::getVisibleInDomainWhoisAsAbuse).findFirst(); + } + + /** + * Ensure that for each given registrar type, a phone number is present after update, if there was + * one before. + */ + private static void ensurePhoneNumberNotRemovedForContactTypes( + Multimap oldContactsByType, + Multimap newContactsByType, + Type... types) { + for (Type type : types) { + if (oldContactsByType.get(type).stream().anyMatch(contact -> contact.getPhoneNumber() != null) + && newContactsByType.get(type).stream() + .noneMatch(contact -> contact.getPhoneNumber() != null)) { + throw new ContactRequirementException( + String.format( + "Please provide a phone number for at least one %s contact", + type.getDisplayName())); + } + } + } + + /** Thrown when a set of contacts doesn't meet certain constraints. */ + private static class ContactRequirementException extends FormException { + ContactRequirementException(String msg) { + super(msg); + } + + ContactRequirementException(Type type) { + super(String.format("Must have at least one %s contact", type.getDisplayName())); + } + } } diff --git a/core/src/main/java/google/registry/ui/server/registrar/ConsoleOteSetupAction.java b/core/src/main/java/google/registry/ui/server/registrar/ConsoleOteSetupAction.java deleted file mode 100644 index 69c3f2075..000000000 --- a/core/src/main/java/google/registry/ui/server/registrar/ConsoleOteSetupAction.java +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright 2018 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.ui.server.registrar; - -import static com.google.common.base.Preconditions.checkState; -import static google.registry.ui.server.SoyTemplateUtils.CSS_RENAMING_MAP_SUPPLIER; -import static google.registry.util.RegistryEnvironment.PRODUCTION; -import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN; - -import com.google.common.base.Ascii; -import com.google.common.base.Supplier; -import com.google.common.collect.ImmutableMap; -import com.google.common.flogger.FluentLogger; -import com.google.template.soy.tofu.SoyTofu; -import google.registry.batch.CloudTasksUtils; -import google.registry.config.RegistryConfig.Config; -import google.registry.model.OteAccountBuilder; -import google.registry.request.Action; -import google.registry.request.Action.GaeService; -import google.registry.request.Action.Method; -import google.registry.request.HttpException.BadRequestException; -import google.registry.request.Parameter; -import google.registry.request.auth.Auth; -import google.registry.request.auth.AuthenticatedRegistrarAccessor; -import google.registry.tools.IamClient; -import google.registry.ui.server.SendEmailUtils; -import google.registry.ui.server.SoyTemplateUtils; -import google.registry.ui.soy.registrar.OteSetupConsoleSoyInfo; -import google.registry.util.RegistryEnvironment; -import google.registry.util.StringGenerator; -import java.util.Map; -import java.util.Optional; -import javax.inject.Inject; -import javax.inject.Named; - -/** - * Action that serves OT&E setup web page. - * - *

This Action does 2 things: - for GET, just returns the form that asks for the clientId and - * email. - for POST, receives the clientId and email and generates the OTE entities. - * - *

TODO(b/120201577): once we can have 2 different Actions with the same path (different - * Methods), separate this class to 2 Actions. - */ -@Action( - service = GaeService.DEFAULT, - path = ConsoleOteSetupAction.PATH, - method = {Method.POST, Method.GET}, - auth = Auth.AUTH_PUBLIC_LOGGED_IN) -public final class ConsoleOteSetupAction extends HtmlAction { - - public static final String PATH = "/registrar-ote-setup"; - private static final int PASSWORD_LENGTH = 16; - private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - private static final Supplier TOFU_SUPPLIER = - SoyTemplateUtils.createTofuSupplier( - google.registry.ui.soy.registrar.AnalyticsSoyInfo.getInstance(), - google.registry.ui.soy.registrar.ConsoleSoyInfo.getInstance(), - google.registry.ui.soy.registrar.ConsoleUtilsSoyInfo.getInstance(), - google.registry.ui.soy.registrar.FormsSoyInfo.getInstance(), - google.registry.ui.soy.registrar.OteSetupConsoleSoyInfo.getInstance()); - - @Inject AuthenticatedRegistrarAccessor registrarAccessor; - @Inject SendEmailUtils sendEmailUtils; - - @Inject - @Named("base58StringGenerator") - StringGenerator passwordGenerator; - - @Inject - @Parameter("consoleClientId") - Optional clientId; - - @Inject - @Parameter("email") - Optional email; - - @Inject - @Parameter("password") - Optional optionalPassword; - - @Inject CloudTasksUtils cloudTasksUtils; - - @Inject IamClient iamClient; - - @Inject - @Config("gSuiteConsoleUserGroupEmailAddress") - Optional maybeGroupEmailAddress; - - @Inject - ConsoleOteSetupAction() {} - - @Override - public void runAfterLogin(Map data) { - checkState( - !RegistryEnvironment.get().equals(PRODUCTION), "Can't create OT&E in prod"); - - if (!registrarAccessor.isAdmin()) { - response.setStatus(SC_FORBIDDEN); - response.setPayload( - TOFU_SUPPLIER - .get() - .newRenderer(OteSetupConsoleSoyInfo.WHOAREYOU) - .setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get()) - .setData(data) - .render()); - return; - } - switch (method) { - case POST -> runPost(data); - case GET -> runGet(data); - default -> - throw new BadRequestException( - String.format("Action cannot be called with method %s", method)); - } - } - - @Override - public String getPath() { - return PATH; - } - - private void runPost(Map data) { - try { - checkState(clientId.isPresent() && email.isPresent(), "Must supply clientId and email"); - - data.put("baseClientId", clientId.get()); - data.put("contactEmail", email.get()); - - String password = optionalPassword.orElse(passwordGenerator.createString(PASSWORD_LENGTH)); - OteAccountBuilder oteAccountBuilder = - OteAccountBuilder.forRegistrarId(clientId.get()) - .addUser(email.get()) - .setPassword(password); - ImmutableMap clientIdToTld = oteAccountBuilder.buildAndPersist(); - - oteAccountBuilder.grantIapPermission(maybeGroupEmailAddress, cloudTasksUtils, iamClient); - - sendExternalUpdates(clientIdToTld); - - data.put("clientIdToTld", clientIdToTld); - data.put("password", password); - - response.setPayload( - TOFU_SUPPLIER - .get() - .newRenderer(OteSetupConsoleSoyInfo.RESULT_SUCCESS) - .setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get()) - .setData(data) - .render()); - } catch (Throwable e) { - logger.atWarning().withCause(e).log( - "Failed to setup OT&E. clientId: %s, email: %s", clientId.get(), email.get()); - data.put("errorMessage", e.getMessage()); - response.setPayload( - TOFU_SUPPLIER - .get() - .newRenderer(OteSetupConsoleSoyInfo.FORM_PAGE) - .setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get()) - .setData(data) - .render()); - } - } - - private void runGet(Map data) { - // set the values to pre-fill, if given - data.put("baseClientId", clientId.orElse(null)); - data.put("contactEmail", email.orElse(null)); - - response.setPayload( - TOFU_SUPPLIER - .get() - .newRenderer(OteSetupConsoleSoyInfo.FORM_PAGE) - .setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get()) - .setData(data) - .render()); - } - - private void sendExternalUpdates(ImmutableMap clientIdToTld) { - if (!sendEmailUtils.hasRecipients()) { - return; - } - String environment = Ascii.toLowerCase(String.valueOf(RegistryEnvironment.get())); - StringBuilder builder = new StringBuilder(); - builder.append( - String.format( - "The following entities were created in %s by %s:\n", - environment, registrarAccessor.userIdForLogging())); - clientIdToTld.forEach( - (clientId, tld) -> - builder.append( - String.format(" Registrar %s with access to TLD %s\n", clientId, tld))); - builder.append(String.format("Gave user %s web access to these Registrars\n", email.get())); - sendEmailUtils.sendEmail( - String.format("OT&E for registrar %s created in %s", clientId.get(), environment), - builder.toString()); - } -} diff --git a/core/src/main/java/google/registry/ui/server/registrar/ConsoleRegistrarCreatorAction.java b/core/src/main/java/google/registry/ui/server/registrar/ConsoleRegistrarCreatorAction.java deleted file mode 100644 index f75cb6802..000000000 --- a/core/src/main/java/google/registry/ui/server/registrar/ConsoleRegistrarCreatorAction.java +++ /dev/null @@ -1,331 +0,0 @@ -// Copyright 2018 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.ui.server.registrar; - -import static com.google.common.base.Preconditions.checkState; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.collect.ImmutableMap.toImmutableMap; -import static google.registry.persistence.transaction.TransactionManagerFactory.tm; -import static google.registry.ui.server.SoyTemplateUtils.CSS_RENAMING_MAP_SUPPLIER; -import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN; - -import com.google.common.base.Ascii; -import com.google.common.base.Splitter; -import com.google.common.collect.ImmutableMap; -import com.google.common.flogger.FluentLogger; -import com.google.template.soy.tofu.SoyTofu; -import google.registry.batch.CloudTasksUtils; -import google.registry.config.RegistryConfig.Config; -import google.registry.model.console.RegistrarRole; -import google.registry.model.console.User; -import google.registry.model.console.UserRoles; -import google.registry.model.registrar.Registrar; -import google.registry.model.registrar.RegistrarAddress; -import google.registry.model.registrar.RegistrarBase.State; -import google.registry.request.Action; -import google.registry.request.Action.GaeService; -import google.registry.request.Action.Method; -import google.registry.request.HttpException.BadRequestException; -import google.registry.request.Parameter; -import google.registry.request.auth.Auth; -import google.registry.request.auth.AuthenticatedRegistrarAccessor; -import google.registry.tools.IamClient; -import google.registry.ui.server.SendEmailUtils; -import google.registry.ui.server.SoyTemplateUtils; -import google.registry.ui.soy.registrar.AnalyticsSoyInfo; -import google.registry.ui.soy.registrar.ConsoleSoyInfo; -import google.registry.ui.soy.registrar.ConsoleUtilsSoyInfo; -import google.registry.ui.soy.registrar.FormsSoyInfo; -import google.registry.ui.soy.registrar.RegistrarCreateConsoleSoyInfo; -import google.registry.util.RegistryEnvironment; -import google.registry.util.StringGenerator; -import java.util.Map; -import java.util.Optional; -import java.util.function.Supplier; -import java.util.stream.Stream; -import javax.inject.Inject; -import javax.inject.Named; -import org.joda.money.CurrencyUnit; - -/** - * Action that serves Registrar creation page. - * - *

This Action does 2 things: - for GET, just returns the form that asks for the required - * information. - for POST, receives the information and creates the Registrar. - */ -@Action( - service = GaeService.DEFAULT, - path = ConsoleRegistrarCreatorAction.PATH, - method = {Method.POST, Method.GET}, - auth = Auth.AUTH_PUBLIC_LOGGED_IN) -public final class ConsoleRegistrarCreatorAction extends HtmlAction { - - private static final int PASSWORD_LENGTH = 16; - private static final int PASSCODE_LENGTH = 5; - - private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - - public static final String PATH = "/registrar-create"; - - private static final Supplier TOFU_SUPPLIER = - SoyTemplateUtils.createTofuSupplier( - AnalyticsSoyInfo.getInstance(), - ConsoleSoyInfo.getInstance(), - ConsoleUtilsSoyInfo.getInstance(), - FormsSoyInfo.getInstance(), - RegistrarCreateConsoleSoyInfo.getInstance()); - - @Inject AuthenticatedRegistrarAccessor registrarAccessor; - @Inject SendEmailUtils sendEmailUtils; - @Inject @Named("base58StringGenerator") StringGenerator passwordGenerator; - @Inject @Named("digitOnlyStringGenerator") StringGenerator passcodeGenerator; - - @Inject - @Parameter("consoleClientId") - Optional clientId; - - @Inject - @Parameter("consoleName") - Optional name; - - @Inject CloudTasksUtils cloudTasksUtils; - - @Inject IamClient iamClient; - - @Inject - @Config("gSuiteConsoleUserGroupEmailAddress") - Optional maybeGroupEmailAddress; - - @Inject @Parameter("billingAccount") Optional billingAccount; - @Inject @Parameter("ianaId") Optional ianaId; - @Inject @Parameter("referralEmail") Optional referralEmail; - @Inject @Parameter("driveId") Optional driveId; - @Inject @Parameter("consoleUserEmail") Optional consoleUserEmail; - - // Address fields, some of which are required and others are optional. - @Inject @Parameter("street1") Optional street1; - @Inject @Parameter("street2") Optional optionalStreet2; - @Inject @Parameter("street3") Optional optionalStreet3; - @Inject @Parameter("city") Optional city; - @Inject @Parameter("state") Optional optionalState; - @Inject @Parameter("zip") Optional optionalZip; - @Inject @Parameter("countryCode") Optional countryCode; - - @Inject @Parameter("password") Optional optionalPassword; - @Inject @Parameter("passcode") Optional optionalPasscode; - - @Inject ConsoleRegistrarCreatorAction() {} - - @Override - public void runAfterLogin(Map data) { - if (!registrarAccessor.isAdmin()) { - response.setStatus(SC_FORBIDDEN); - response.setPayload( - TOFU_SUPPLIER - .get() - .newRenderer(RegistrarCreateConsoleSoyInfo.WHOAREYOU) - .setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get()) - .setData(data) - .render()); - return; - } - switch (method) { - case POST -> runPost(data); - case GET -> runGet(data); - default -> - throw new BadRequestException( - String.format("Action cannot be called with method %s", method)); - } - } - - @Override - public String getPath() { - return PATH; - } - - private static void checkPresent(Optional value, String name) { - checkState(value.isPresent(), "Missing value for %s", name); - } - - private static final Splitter LINE_SPLITTER = - Splitter.onPattern("\r?\n").trimResults().omitEmptyStrings(); - - private static final Splitter ENTRY_SPLITTER = - Splitter.on('=').trimResults().omitEmptyStrings().limit(2); - - private static ImmutableMap parseBillingAccount(String billingAccount) { - try { - return LINE_SPLITTER.splitToList(billingAccount).stream() - .map(ENTRY_SPLITTER::splitToList) - .peek( - list -> - checkState( - list.size() == 2, - "Can't parse line %s. The format should be [currency]=[account ID]", - list)) - .collect( - toImmutableMap( - list -> CurrencyUnit.of(Ascii.toUpperCase(list.getFirst())), - list -> list.get(1))); - } catch (Throwable e) { - throw new RuntimeException("Error parsing billing accounts - " + e.getMessage(), e); - } - } - - private void runPost(Map data) { - try { - checkPresent(clientId, "clientId"); - checkPresent(name, "name"); - checkPresent(billingAccount, "billingAccount"); - checkPresent(ianaId, "ianaId"); - checkPresent(referralEmail, "referralEmail"); - checkPresent(driveId, "driveId"); - checkPresent(consoleUserEmail, "consoleUserEmail"); - checkPresent(street1, "street"); - checkPresent(city, "city"); - checkPresent(countryCode, "countryCode"); - - data.put("clientId", clientId.get()); - data.put("name", name.get()); - data.put("ianaId", ianaId.get()); - data.put("referralEmail", referralEmail.get()); - data.put("billingAccount", billingAccount.get()); - data.put("driveId", driveId.get()); - data.put("consoleUserEmail", consoleUserEmail.get()); - - data.put("street1", street1.get()); - optionalStreet2.ifPresent(street2 -> data.put("street2", street2)); - optionalStreet3.ifPresent(street3 -> data.put("street3", street3)); - data.put("city", city.get()); - optionalState.ifPresent(state -> data.put("state", state)); - optionalZip.ifPresent(zip -> data.put("zip", zip)); - data.put("countryCode", countryCode.get()); - - String password = optionalPassword.orElse(passwordGenerator.createString(PASSWORD_LENGTH)); - String phonePasscode = - optionalPasscode.orElse(passcodeGenerator.createString(PASSCODE_LENGTH)); - Registrar registrar = - new Registrar.Builder() - .setRegistrarId(clientId.get()) - .setRegistrarName(name.get()) - .setBillingAccountMap(parseBillingAccount(billingAccount.get())) - .setIanaIdentifier(Long.valueOf(ianaId.get())) - .setIcannReferralEmail(referralEmail.get()) - .setEmailAddress(referralEmail.get()) - .setDriveFolderId(driveId.get()) - .setType(Registrar.Type.REAL) - .setPassword(password) - .setPhonePasscode(phonePasscode) - .setState(State.PENDING) - .setLocalizedAddress( - new RegistrarAddress.Builder() - .setStreet( - Stream.of(street1, optionalStreet2, optionalStreet3) - .filter(Optional::isPresent) - .map(Optional::get) - .collect(toImmutableList())) - .setCity(city.get()) - .setState(optionalState.orElse(null)) - .setCountryCode(countryCode.get()) - .setZip(optionalZip.orElse(null)) - .build()) - .build(); - User user = - new User.Builder() - .setEmailAddress(consoleUserEmail.get()) - .setUserRoles( - new UserRoles.Builder() - .setRegistrarRoles( - ImmutableMap.of( - registrar.getRegistrarId(), RegistrarRole.ACCOUNT_MANAGER)) - .build()) - .build(); - tm().transact( - () -> { - checkState( - Registrar.loadByRegistrarId(registrar.getRegistrarId()).isEmpty(), - "Registrar with client ID %s already exists", - registrar.getRegistrarId()); - tm().put(registrar); - tm().put(user); - }); - User.grantIapPermission( - user.getEmailAddress(), maybeGroupEmailAddress, cloudTasksUtils, null, iamClient); - data.put("password", password); - data.put("passcode", phonePasscode); - - sendExternalUpdates(); - response.setPayload( - TOFU_SUPPLIER - .get() - .newRenderer(RegistrarCreateConsoleSoyInfo.RESULT_SUCCESS) - .setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get()) - .setData(data) - .render()); - } catch (Throwable e) { - logger.atWarning().withCause(e).log( - "Failed to create registrar. clientId: %s, data: %s", clientId.get(), data); - data.put("errorMessage", e.getMessage()); - response.setPayload( - TOFU_SUPPLIER - .get() - .newRenderer(RegistrarCreateConsoleSoyInfo.FORM_PAGE) - .setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get()) - .setData(data) - .render()); - } - } - - private void runGet(Map data) { - // set the values to pre-fill, if given - data.put("clientId", clientId.orElse(null)); - data.put("name", name.orElse(null)); - data.put("ianaId", ianaId.orElse(null)); - data.put("referralEmail", referralEmail.orElse(null)); - data.put("driveId", driveId.orElse(null)); - data.put("consoleUserEmail", consoleUserEmail.orElse(null)); - - response.setPayload( - TOFU_SUPPLIER - .get() - .newRenderer(RegistrarCreateConsoleSoyInfo.FORM_PAGE) - .setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get()) - .setData(data) - .render()); - } - - private static String toEmailLine(Optional value, String name) { - return String.format(" %s: %s\n", name, value.orElse(null)); - } - private void sendExternalUpdates() { - if (!sendEmailUtils.hasRecipients()) { - return; - } - String environment = Ascii.toLowerCase(String.valueOf(RegistryEnvironment.get())); - String body = - String.format( - "The following registrar was created in %s by %s:\n", - environment, registrarAccessor.userIdForLogging()) - + toEmailLine(clientId, "clientId") - + toEmailLine(name, "name") - + toEmailLine(billingAccount, "billingAccount") - + toEmailLine(ianaId, "ianaId") - + toEmailLine(referralEmail, "referralEmail") - + toEmailLine(driveId, "driveId") - + String.format("Gave user %s web access to the registrar\n", consoleUserEmail.get()); - sendEmailUtils.sendEmail( - String.format("Registrar %s created in %s", clientId.get(), environment), body); - } -} diff --git a/core/src/main/java/google/registry/ui/server/registrar/ConsoleUiAction.java b/core/src/main/java/google/registry/ui/server/registrar/ConsoleUiAction.java deleted file mode 100644 index 0a0915ea0..000000000 --- a/core/src/main/java/google/registry/ui/server/registrar/ConsoleUiAction.java +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright 2017 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.ui.server.registrar; - -import static com.google.common.base.Strings.isNullOrEmpty; -import static google.registry.persistence.transaction.TransactionManagerFactory.tm; -import static google.registry.request.auth.AuthenticatedRegistrarAccessor.Role.ADMIN; -import static google.registry.request.auth.AuthenticatedRegistrarAccessor.Role.OWNER; -import static google.registry.ui.server.SoyTemplateUtils.CSS_RENAMING_MAP_SUPPLIER; -import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN; -import static jakarta.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE; - -import com.google.common.base.Supplier; -import com.google.common.collect.ImmutableSetMultimap; -import com.google.common.flogger.FluentLogger; -import com.google.template.soy.data.SoyMapData; -import com.google.template.soy.tofu.SoyTofu; -import google.registry.config.RegistryConfig.Config; -import google.registry.model.common.FeatureFlag; -import google.registry.request.Action; -import google.registry.request.Action.GaeService; -import google.registry.request.Parameter; -import google.registry.request.auth.Auth; -import google.registry.request.auth.AuthenticatedRegistrarAccessor; -import google.registry.request.auth.AuthenticatedRegistrarAccessor.RegistrarAccessDeniedException; -import google.registry.request.auth.AuthenticatedRegistrarAccessor.Role; -import google.registry.ui.server.SoyTemplateUtils; -import google.registry.ui.soy.registrar.ConsoleSoyInfo; -import google.registry.util.RegistryEnvironment; -import java.io.IOException; -import java.util.Map; -import java.util.Optional; -import javax.inject.Inject; - -/** Action that serves Registrar Console single HTML page (SPA). */ -@Action( - service = GaeService.DEFAULT, - path = ConsoleUiAction.PATH, - auth = Auth.AUTH_PUBLIC_LOGGED_IN) -public final class ConsoleUiAction extends HtmlAction { - - private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - - public static final String PATH = "/registrar"; - - private static final Supplier TOFU_SUPPLIER = - SoyTemplateUtils.createTofuSupplier( - google.registry.ui.soy.registrar.AnalyticsSoyInfo.getInstance(), - google.registry.ui.soy.registrar.ConsoleSoyInfo.getInstance(), - google.registry.ui.soy.registrar.ConsoleUtilsSoyInfo.getInstance()); - - @Inject RegistrarConsoleMetrics registrarConsoleMetrics; - @Inject AuthenticatedRegistrarAccessor registrarAccessor; - - @Inject - @Config("integrationEmail") - String integrationEmail; - - @Inject - @Config("supportEmail") - String supportEmail; - - @Inject - @Config("announcementsEmail") - String announcementsEmail; - - @Inject - @Config("supportPhoneNumber") - String supportPhoneNumber; - - @Inject - @Config("technicalDocsUrl") - String technicalDocsUrl; - - @Inject - @Config("registrarConsoleEnabled") - boolean enabled; - - @Inject - @Parameter("consoleClientId") - Optional paramClientId; - - @Inject - ConsoleUiAction() {} - - @Override - public void runAfterLogin(Map data) { - // This console is deprecated. - // Unless an explict "noredirect" URL parameter is included, it will redirect to the new - // console. - if (isNullOrEmpty(req.getParameter("noredirect"))) { - try { - response.sendRedirect("/console"); - return; - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - SoyMapData soyMapData = new SoyMapData(); - data.forEach((key, value) -> soyMapData.put(key, value)); - - soyMapData.put("integrationEmail", integrationEmail); - soyMapData.put("supportEmail", supportEmail); - soyMapData.put("announcementsEmail", announcementsEmail); - soyMapData.put("supportPhoneNumber", supportPhoneNumber); - soyMapData.put("technicalDocsUrl", technicalDocsUrl); - - if (!enabled) { - response.setStatus(SC_SERVICE_UNAVAILABLE); - response.setPayload( - TOFU_SUPPLIER - .get() - .newRenderer(ConsoleSoyInfo.DISABLED) - .setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get()) - .setData(soyMapData) - .render()); - return; - } - - ImmutableSetMultimap roleMap = registrarAccessor.getAllRegistrarIdsWithRoles(); - soyMapData.put("allClientIds", roleMap.keySet()); - soyMapData.put("environment", RegistryEnvironment.get().toString()); - boolean newConsole = - tm().transact( - () -> - FeatureFlag.getUncached(FeatureFlag.FeatureName.NEW_CONSOLE) - .map( - flag -> - flag.getStatus(tm().getTransactionTime()) - .equals(FeatureFlag.FeatureStatus.ACTIVE)) - .orElse(false)); - soyMapData.put("includeDeprecationWarning", newConsole); - // We set the initial value to the value that will show if guessClientId throws. - String clientId = ""; - try { - clientId = paramClientId.orElse(registrarAccessor.guessRegistrarId()); - soyMapData.put("clientId", clientId); - soyMapData.put("isOwner", roleMap.containsEntry(clientId, OWNER)); - soyMapData.put("isAdmin", roleMap.containsEntry(clientId, ADMIN)); - - // We want to load the registrar even if we won't use it later (even if we remove the - // requireFeeExtension) - to make sure the user indeed has access to the guessed registrar. - // - // Note that not doing so (and just passing the "clientId" as given) isn't a security issue - // since we double-check the access to the registrar on any read / update request. We have to - // - since the access might get revoked between the initial page load and the request! (also - // because the requests come from the browser, and can easily be faked) - registrarAccessor.getRegistrar(clientId); - } catch (RegistrarAccessDeniedException e) { - logger.atWarning().withCause(e).log( - "User %s doesn't have access to registrar console.", authResult.userIdForLogging()); - response.setStatus(SC_FORBIDDEN); - response.setPayload( - TOFU_SUPPLIER - .get() - .newRenderer(ConsoleSoyInfo.WHOAREYOU) - .setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get()) - .setData(soyMapData) - .render()); - registrarConsoleMetrics.registerConsoleRequest( - clientId, paramClientId.isPresent(), roleMap.get(clientId), "FORBIDDEN"); - return; - } catch (Exception e) { - registrarConsoleMetrics.registerConsoleRequest( - clientId, paramClientId.isPresent(), roleMap.get(clientId), "UNEXPECTED ERROR"); - throw e; - } - - String payload = - TOFU_SUPPLIER - .get() - .newRenderer(ConsoleSoyInfo.MAIN) - .setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get()) - .setData(soyMapData) - .render(); - response.setPayload(payload); - registrarConsoleMetrics.registerConsoleRequest( - clientId, paramClientId.isPresent(), roleMap.get(clientId), "SUCCESS"); - } - - @Override - public String getPath() { - return PATH; - } -} diff --git a/core/src/main/java/google/registry/ui/server/registrar/HtmlAction.java b/core/src/main/java/google/registry/ui/server/registrar/HtmlAction.java deleted file mode 100644 index c5867f9a8..000000000 --- a/core/src/main/java/google/registry/ui/server/registrar/HtmlAction.java +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2019 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.ui.server.registrar; - -import static com.google.common.net.HttpHeaders.X_FRAME_OPTIONS; -import static jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; - -import com.google.common.flogger.FluentLogger; -import com.google.common.net.MediaType; -import google.registry.config.RegistryConfig.Config; -import google.registry.model.console.User; -import google.registry.request.Action; -import google.registry.request.RequestMethod; -import google.registry.request.Response; -import google.registry.request.auth.AuthResult; -import google.registry.security.XsrfTokenManager; -import jakarta.servlet.http.HttpServletRequest; -import java.util.HashMap; -import java.util.Map; -import javax.inject.Inject; - -/** - * Handles some of the nitty-gritty of responding to requests that should return HTML, including - * login, redirects, analytics, and some headers. - * - *

If the user is not logged in, this will redirect to the login URL. - */ -public abstract class HtmlAction implements Runnable { - - private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - - @Inject HttpServletRequest req; - @Inject Response response; - @Inject XsrfTokenManager xsrfTokenManager; - @Inject AuthResult authResult; - @Inject @RequestMethod Action.Method method; - - @Inject - @Config("logoFilename") - String logoFilename; - - @Inject - @Config("productName") - String productName; - - @Inject - @Config("analyticsConfig") - Map analyticsConfig; - - @Override - public void run() { - response.setHeader(X_FRAME_OPTIONS, "SAMEORIGIN"); // Disallow iframing. - response.setHeader("X-Ui-Compatible", "IE=edge"); // Ask IE not to be silly. - - if (authResult.user().isEmpty()) { - response.setStatus(SC_UNAUTHORIZED); - return; - } - response.setContentType(MediaType.HTML_UTF_8); - - User user = authResult.user().get(); - // Using HashMap to allow null values - HashMap data = new HashMap<>(); - data.put("logoFilename", logoFilename); - data.put("productName", productName); - data.put("username", user.getEmailAddress()); - data.put("logoutUrl", "/registrar?gcp-iap-mode=CLEAR_LOGIN_COOKIE"); - data.put("analyticsConfig", analyticsConfig); - data.put("xsrfToken", xsrfTokenManager.generateToken(user.getEmailAddress())); - - logger.atInfo().log( - "User %s is accessing %s with method %s.", - authResult.userIdForLogging(), getClass().getName(), method); - runAfterLogin(data); - } - - public abstract void runAfterLogin(Map data); - - public abstract String getPath(); -} diff --git a/core/src/main/java/google/registry/ui/server/registrar/JsonGetAction.java b/core/src/main/java/google/registry/ui/server/registrar/JsonGetAction.java deleted file mode 100644 index fb38b7c9c..000000000 --- a/core/src/main/java/google/registry/ui/server/registrar/JsonGetAction.java +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2019 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.ui.server.registrar; - -/** - * Marker interface for {@link google.registry.request.Action}s that serve GET requests and return - * JSON, rather than HTML. - */ -public interface JsonGetAction extends Runnable {} diff --git a/core/src/main/java/google/registry/ui/server/registrar/OteStatusAction.java b/core/src/main/java/google/registry/ui/server/registrar/OteStatusAction.java deleted file mode 100644 index 8eaec51b6..000000000 --- a/core/src/main/java/google/registry/ui/server/registrar/OteStatusAction.java +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2019 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.ui.server.registrar; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static google.registry.security.JsonResponseHelper.Status.ERROR; -import static google.registry.security.JsonResponseHelper.Status.SUCCESS; - -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableMap; -import com.google.common.flogger.FluentLogger; -import google.registry.model.OteAccountBuilder; -import google.registry.model.OteStats; -import google.registry.model.OteStats.StatType; -import google.registry.model.registrar.Registrar; -import google.registry.model.registrar.RegistrarBase.Type; -import google.registry.request.Action; -import google.registry.request.Action.GaeService; -import google.registry.request.JsonActionRunner; -import google.registry.request.auth.Auth; -import google.registry.request.auth.AuthenticatedRegistrarAccessor; -import google.registry.request.auth.AuthenticatedRegistrarAccessor.RegistrarAccessDeniedException; -import google.registry.security.JsonResponseHelper; -import java.util.Map; -import java.util.Optional; -import javax.inject.Inject; - -/** - * Admin servlet that allows creating or updating a registrar. Deletes are not allowed so as to - * preserve history. - */ -@Action( - service = GaeService.DEFAULT, - path = OteStatusAction.PATH, - method = Action.Method.POST, - auth = Auth.AUTH_PUBLIC_LOGGED_IN) -public final class OteStatusAction implements Runnable, JsonActionRunner.JsonAction { - - public static final String PATH = "/registrar-ote-status"; - - private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - - private static final String CLIENT_ID_PARAM = "clientId"; - private static final String COMPLETED_PARAM = "completed"; - private static final String DETAILS_PARAM = "details"; - private static final String STAT_TYPE_DESCRIPTION_PARAM = "description"; - private static final String STAT_TYPE_REQUIREMENT_PARAM = "requirement"; - private static final String STAT_TYPE_TIMES_PERFORMED_PARAM = "timesPerformed"; - - @Inject AuthenticatedRegistrarAccessor registrarAccessor; - @Inject JsonActionRunner jsonActionRunner; - - @Inject - OteStatusAction() {} - - @Override - public void run() { - jsonActionRunner.run(this); - } - - @Override - public Map handleJsonRequest(Map input) { - try { - checkArgument(input != null, "Malformed JSON"); - - String oteClientId = (String) input.get(CLIENT_ID_PARAM); - checkArgument( - !Strings.isNullOrEmpty(oteClientId), "Missing key for OT&E client: %s", CLIENT_ID_PARAM); - - String baseClientId = OteAccountBuilder.getBaseRegistrarId(oteClientId); - Registrar oteRegistrar = registrarAccessor.getRegistrar(oteClientId); - verifyOteAccess(baseClientId); - checkArgument( - Type.OTE.equals(oteRegistrar.getType()), - "Registrar with ID %s is not an OT&E registrar", - oteClientId); - - OteStats oteStats = OteStats.getFromRegistrar(baseClientId); - return JsonResponseHelper.create( - SUCCESS, "OT&E check completed successfully", convertOteStats(baseClientId, oteStats)); - } catch (Throwable e) { - logger.atWarning().withCause(e).log( - "Failed to verify OT&E status for registrar with input: %s", input); - return JsonResponseHelper.create( - ERROR, Optional.ofNullable(e.getMessage()).orElse("Unspecified error")); - } - } - - private void verifyOteAccess(String baseClientId) throws RegistrarAccessDeniedException { - for (String oteClientId : OteAccountBuilder.createRegistrarIdToTldMap(baseClientId).keySet()) { - registrarAccessor.verifyAccess(oteClientId); - } - } - - private Map convertOteStats(String baseClientId, OteStats oteStats) { - return ImmutableMap.of( - CLIENT_ID_PARAM, baseClientId, - COMPLETED_PARAM, oteStats.getFailures().isEmpty(), - DETAILS_PARAM, - StatType.REQUIRED_STAT_TYPES.stream() - .map(statType -> convertSingleRequirement(statType, oteStats.getCount(statType))) - .collect(toImmutableList())); - } - - private Map convertSingleRequirement(StatType statType, int count) { - int requirement = statType.getRequirement(); - return ImmutableMap.of( - STAT_TYPE_DESCRIPTION_PARAM, statType.getDescription(), - STAT_TYPE_REQUIREMENT_PARAM, requirement, - STAT_TYPE_TIMES_PERFORMED_PARAM, count, - COMPLETED_PARAM, count >= requirement); - } -} diff --git a/core/src/main/java/google/registry/ui/server/registrar/RegistrarConsoleMetrics.java b/core/src/main/java/google/registry/ui/server/registrar/RegistrarConsoleMetrics.java deleted file mode 100644 index e1993bb1c..000000000 --- a/core/src/main/java/google/registry/ui/server/registrar/RegistrarConsoleMetrics.java +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2018 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.ui.server.registrar; - -import com.google.common.collect.ImmutableSet; -import com.google.monitoring.metrics.IncrementableMetric; -import com.google.monitoring.metrics.LabelDescriptor; -import com.google.monitoring.metrics.MetricRegistryImpl; -import google.registry.request.auth.AuthenticatedRegistrarAccessor.Role; -import javax.inject.Inject; - -final class RegistrarConsoleMetrics { - - private static final ImmutableSet CONSOLE_LABEL_DESCRIPTORS = - ImmutableSet.of( - LabelDescriptor.create("clientId", "target registrar client ID"), - LabelDescriptor.create("explicitClientId", "whether the client ID is set explicitly"), - LabelDescriptor.create("role", "Role[s] of the user making the request"), - LabelDescriptor.create("status", "whether the request is successful")); - - static final IncrementableMetric consoleRequestMetric = - MetricRegistryImpl.getDefault() - .newIncrementableMetric( - "/console/registrar/console_requests", - "Count of /registrar requests", - "count", - CONSOLE_LABEL_DESCRIPTORS); - - private static final ImmutableSet SETTINGS_LABEL_DESCRIPTORS = - ImmutableSet.of( - LabelDescriptor.create("clientId", "target registrar client ID"), - LabelDescriptor.create("action", "action performed"), - LabelDescriptor.create("role", "Role[s] of the user making the request"), - LabelDescriptor.create("status", "whether the request is successful")); - - static final IncrementableMetric settingsRequestMetric = - MetricRegistryImpl.getDefault() - .newIncrementableMetric( - "/console/registrar/setting_requests", - "Count of /registrar-settings requests", - "count", - SETTINGS_LABEL_DESCRIPTORS); - - @Inject - public RegistrarConsoleMetrics() {} - - void registerConsoleRequest( - String clientId, boolean explicitClientId, ImmutableSet roles, String status) { - consoleRequestMetric.increment( - clientId, String.valueOf(explicitClientId), String.valueOf(roles), status); - } - - void registerSettingsRequest( - String clientId, String action, ImmutableSet roles, String status) { - settingsRequestMetric.increment(clientId, action, String.valueOf(roles), status); - } -} diff --git a/core/src/main/java/google/registry/ui/server/registrar/RegistrarSettingsAction.java b/core/src/main/java/google/registry/ui/server/registrar/RegistrarSettingsAction.java deleted file mode 100644 index 7558349c1..000000000 --- a/core/src/main/java/google/registry/ui/server/registrar/RegistrarSettingsAction.java +++ /dev/null @@ -1,641 +0,0 @@ -// Copyright 2017 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.ui.server.registrar; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.collect.ImmutableSet.toImmutableSet; -import static com.google.common.collect.Sets.difference; -import static google.registry.persistence.transaction.TransactionManagerFactory.tm; -import static google.registry.security.JsonResponseHelper.Status.ERROR; -import static google.registry.security.JsonResponseHelper.Status.SUCCESS; -import static google.registry.util.PreconditionsUtils.checkArgumentPresent; -import static google.registry.util.RegistryEnvironment.PRODUCTION; - -import com.google.common.base.Ascii; -import com.google.common.base.Strings; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableMultimap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Multimap; -import com.google.common.collect.Sets; -import com.google.common.collect.Streams; -import com.google.common.flogger.FluentLogger; -import google.registry.batch.CloudTasksUtils; -import google.registry.export.sheet.SyncRegistrarsSheetAction; -import google.registry.flows.certs.CertificateChecker; -import google.registry.flows.certs.CertificateChecker.InsecureCertificateException; -import google.registry.model.registrar.Registrar; -import google.registry.model.registrar.RegistrarPoc; -import google.registry.model.registrar.RegistrarPocBase.Type; -import google.registry.request.Action; -import google.registry.request.Action.GaeService; -import google.registry.request.HttpException.BadRequestException; -import google.registry.request.HttpException.ForbiddenException; -import google.registry.request.JsonActionRunner; -import google.registry.request.auth.Auth; -import google.registry.request.auth.AuthResult; -import google.registry.request.auth.AuthenticatedRegistrarAccessor; -import google.registry.request.auth.AuthenticatedRegistrarAccessor.RegistrarAccessDeniedException; -import google.registry.request.auth.AuthenticatedRegistrarAccessor.Role; -import google.registry.security.JsonResponseHelper; -import google.registry.ui.forms.FormException; -import google.registry.ui.forms.FormFieldException; -import google.registry.ui.server.RegistrarFormFields; -import google.registry.ui.server.SendEmailUtils; -import google.registry.util.CollectionUtils; -import google.registry.util.DiffUtils; -import google.registry.util.RegistryEnvironment; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import javax.inject.Inject; -import org.joda.time.DateTime; - -/** - * Admin servlet that allows creating or updating a registrar. Deletes are not allowed so as to - * preserve history. - */ -@Action( - service = GaeService.DEFAULT, - path = RegistrarSettingsAction.PATH, - method = Action.Method.POST, - auth = Auth.AUTH_PUBLIC_LOGGED_IN) -public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonAction { - - public static final String PATH = "/registrar-settings"; - - private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - - static final String OP_PARAM = "op"; - static final String ARGS_PARAM = "args"; - static final String ID_PARAM = "id"; - - @Inject JsonActionRunner jsonActionRunner; - @Inject RegistrarConsoleMetrics registrarConsoleMetrics; - @Inject SendEmailUtils sendEmailUtils; - @Inject AuthenticatedRegistrarAccessor registrarAccessor; - @Inject AuthResult authResult; - @Inject CertificateChecker certificateChecker; - @Inject CloudTasksUtils cloudTasksUtils; - - @Inject RegistrarSettingsAction() {} - - private static boolean hasPhone(RegistrarPoc contact) { - return contact.getPhoneNumber() != null; - } - - @Override - public void run() { - jsonActionRunner.run(this); - } - - @Override - public Map handleJsonRequest(Map input) { - if (input == null) { - throw new BadRequestException("Malformed JSON"); - } - - String registrarId = (String) input.get(ID_PARAM); - if (Strings.isNullOrEmpty(registrarId)) { - throw new BadRequestException(String.format("Missing key for resource ID: %s", ID_PARAM)); - } - - // Process the operation. Though originally derived from a CRUD - // handler, registrar-settings really only supports read and update. - String op = Optional.ofNullable((String) input.get(OP_PARAM)).orElse("read"); - @SuppressWarnings("unchecked") - Map args = - (Map) - Optional.ofNullable(input.get(ARGS_PARAM)).orElse(ImmutableMap.of()); - - logger.atInfo().log( - "Received request '%s' on registrar '%s' with args %s", op, registrarId, args); - String status = "SUCCESS"; - try { - return switch (op) { - case "update" -> update(args, registrarId).toJsonResponse(); - case "read" -> read(registrarId).toJsonResponse(); - default -> throw new IllegalArgumentException("Unknown or unsupported operation: " + op); - }; - } catch (Throwable e) { - logger.atWarning().withCause(e).log( - "Failed to perform operation '%s' on registrar '%s' for args %s", op, registrarId, args); - status = "ERROR: " + e.getClass().getSimpleName(); - if (e instanceof FormFieldException formFieldException) { - return JsonResponseHelper.createFormFieldError( - formFieldException.getMessage(), formFieldException.getFieldName()); - } - return JsonResponseHelper.create( - ERROR, Optional.ofNullable(e.getMessage()).orElse("Unspecified error")); - } finally { - registrarConsoleMetrics.registerSettingsRequest( - registrarId, op, registrarAccessor.getRolesForRegistrar(registrarId), status); - } - } - - record RegistrarResult(String message, Registrar registrar) { - - Map toJsonResponse() { - return JsonResponseHelper.create(SUCCESS, message(), registrar().toJsonMap()); - } - - static RegistrarResult create(String message, Registrar registrar) { - return new RegistrarResult(message, registrar); - } - } - - record EmailInfo( - Registrar registrar, - Registrar updatedRegistrar, - ImmutableSet contacts, - ImmutableSet updatedContacts) { - - static EmailInfo create( - Registrar registrar, - Registrar updatedRegistrar, - ImmutableSet contacts, - ImmutableSet updatedContacts) { - return new EmailInfo(registrar, updatedRegistrar, contacts, updatedContacts); - } - } - - private RegistrarResult read(String registrarId) { - return RegistrarResult.create("Success", loadRegistrarUnchecked(registrarId)); - } - - private Registrar loadRegistrarUnchecked(String registrarId) { - try { - return registrarAccessor.getRegistrar(registrarId); - } catch (RegistrarAccessDeniedException e) { - throw new ForbiddenException(e.getMessage(), e); - } - } - - private RegistrarResult update(final Map args, String registrarId) { - // Email the updates - sendExternalUpdatesIfNecessary(tm().transact(() -> saveUpdates(args, registrarId))); - // Reload the result outside the transaction to get the most recent version - return RegistrarResult.create("Saved " + registrarId, loadRegistrarUnchecked(registrarId)); - } - - /** Saves the updates and returns info needed for the update email */ - private EmailInfo saveUpdates(final Map args, String registrarId) { - // We load the registrar here rather than outside the transaction - to make - // sure we have the latest version. This one is loaded inside the transaction, so it's - // guaranteed to not change before we update it. - Registrar registrar = loadRegistrarUnchecked(registrarId); - // Detach the registrar to avoid Hibernate object-updates, since we wish to email - // out the diffs between the existing and updated registrar objects - tm().getEntityManager().detach(registrar); - // Verify that the registrar hasn't been changed. - // To do that - we find the latest update time (or null if the registrar has been - // deleted) and compare to the update time from the args. The update time in the args - // comes from the read that gave the UI the data - if it's out of date, then the UI - // had out of date data. - DateTime latest = registrar.getLastUpdateTime(); - DateTime latestFromArgs = RegistrarFormFields.LAST_UPDATE_TIME.extractUntyped(args).get(); - if (!latestFromArgs.equals(latest)) { - logger.atWarning().log( - "Registrar changed since reading the data!" - + " Last updated at %s, but args data last updated at %s.", - latest, latestFromArgs); - throw new IllegalStateException( - "Registrar has been changed by someone else. Please reload and retry."); - } - - // Keep the current contacts, so we can later check that no required contact was - // removed, email the changes to the contacts - ImmutableSet contacts = registrar.getContacts(); - - Registrar updatedRegistrar = registrar; - // Do OWNER only updates to the registrar from the request. - updatedRegistrar = checkAndUpdateOwnerControlledFields(updatedRegistrar, args); - // Do ADMIN only updates to the registrar from the request. - updatedRegistrar = checkAndUpdateAdminControlledFields(updatedRegistrar, args); - - // read the contacts from the request. - ImmutableSet updatedContacts = readContacts(registrar, contacts, args); - - // Save the updated contacts - if (!updatedContacts.equals(contacts)) { - if (!registrarAccessor.hasRoleOnRegistrar(Role.OWNER, registrar.getRegistrarId())) { - throw new ForbiddenException("Only OWNERs can update the contacts"); - } - checkContactRequirements(contacts, updatedContacts); - RegistrarPoc.updateContacts(updatedRegistrar, updatedContacts); - updatedRegistrar = updatedRegistrar.asBuilder().setContactsRequireSyncing(true).build(); - } - - // Save the updated registrar - if (!updatedRegistrar.equals(registrar)) { - tm().put(updatedRegistrar); - } - return EmailInfo.create(registrar, updatedRegistrar, contacts, updatedContacts); - } - - private Map expandRegistrarWithContacts( - Iterable contacts, Registrar registrar) { - ImmutableSet> expandedContacts = - Streams.stream(contacts) - .map(RegistrarPoc::toDiffableFieldMap) - // Note: per the javadoc, toDiffableFieldMap includes sensitive data, but we don't want - // to display it here - .peek( - map -> { - map.remove("registryLockPasswordHash"); - map.remove("registryLockPasswordSalt"); - }) - .collect(toImmutableSet()); - // Use LinkedHashMap here to preserve ordering; null values mean we can't use ImmutableMap. - LinkedHashMap result = new LinkedHashMap<>(registrar.toDiffableFieldMap()); - result.put("contacts", expandedContacts); - return result; - } - - /** - * Updates registrar with the OWNER-controlled args from the http request. - * - *

If any changes were made and the user isn't an OWNER - throws a {@link ForbiddenException}. - */ - private Registrar checkAndUpdateOwnerControlledFields( - Registrar initialRegistrar, Map args) { - - Registrar.Builder builder = initialRegistrar.asBuilder(); - - // WHOIS - // - // Because of how whoisServer handles "default value", it's possible that setting the existing - // value will still change the Registrar. So we first check whether the value has changed. - // - // The problem is - if the Registrar has a "null" whoisServer value, the console gets the - // "default value" instead of the actual (null) value. - // This was done so we display the "default" value, but it also means that it always looks like - // the user updated the whoisServer value from "null" to the default value. - // - // TODO(b/119913848):once a null whoisServer value is sent to the console as "null", there's no - // need to check for equality before setting the value in the builder. - String updatedWhoisServer = - RegistrarFormFields.WHOIS_SERVER_FIELD.extractUntyped(args).orElse(null); - if (!Objects.equals(initialRegistrar.getWhoisServer(), updatedWhoisServer)) { - builder.setWhoisServer(updatedWhoisServer); - } - builder.setUrl(RegistrarFormFields.URL_FIELD.extractUntyped(args).orElse(null)); - - // If the email is already null / empty - we can keep it so. But if it's set - it's required to - // remain set. - (Strings.isNullOrEmpty(initialRegistrar.getEmailAddress()) - ? RegistrarFormFields.EMAIL_ADDRESS_FIELD_OPTIONAL - : RegistrarFormFields.EMAIL_ADDRESS_FIELD_REQUIRED) - .extractUntyped(args) - .ifPresent(builder::setEmailAddress); - builder.setPhoneNumber( - RegistrarFormFields.PHONE_NUMBER_FIELD.extractUntyped(args).orElse(null)); - builder.setFaxNumber(RegistrarFormFields.FAX_NUMBER_FIELD.extractUntyped(args).orElse(null)); - builder.setLocalizedAddress( - RegistrarFormFields.L10N_ADDRESS_FIELD.extractUntyped(args).orElse(null)); - - // Security - builder.setIpAddressAllowList( - RegistrarFormFields.IP_ADDRESS_ALLOW_LIST_FIELD - .extractUntyped(args) - .orElse(ImmutableList.of())); - - Optional certificateString = - RegistrarFormFields.CLIENT_CERTIFICATE_FIELD.extractUntyped(args); - if (certificateString.isPresent()) { - if (validateCertificate(initialRegistrar.getClientCertificate(), certificateString.get())) { - builder.setClientCertificate(certificateString.get(), tm().getTransactionTime()); - } - } - - Optional failoverCertificateString = - RegistrarFormFields.FAILOVER_CLIENT_CERTIFICATE_FIELD.extractUntyped(args); - if (failoverCertificateString.isPresent()) { - if (validateCertificate( - initialRegistrar.getFailoverClientCertificate(), failoverCertificateString.get())) { - builder.setFailoverClientCertificate( - failoverCertificateString.get(), tm().getTransactionTime()); - } - } - - return checkNotChangedUnlessAllowed(builder, initialRegistrar, Role.OWNER); - } - - /** - * Returns true if the registrar should accept the new certificate. Returns false if the - * certificate is already the one stored for the registrar. - */ - private boolean validateCertificate( - Optional existingCertificate, String certificateString) { - if (existingCertificate.isEmpty() || !existingCertificate.get().equals(certificateString)) { - try { - certificateChecker.validateCertificate(certificateString); - } catch (InsecureCertificateException e) { - throw new IllegalArgumentException(e.getMessage()); - } - return true; - } - return false; - } - - /** - * Updates a registrar with the ADMIN-controlled args from the http request. - * - *

If any changes were made and the user isn't an ADMIN - throws a {@link ForbiddenException}. - */ - private Registrar checkAndUpdateAdminControlledFields( - Registrar initialRegistrar, Map args) { - Registrar.Builder builder = initialRegistrar.asBuilder(); - - Set updatedAllowedTlds = - RegistrarFormFields.ALLOWED_TLDS_FIELD.extractUntyped(args).orElse(ImmutableSet.of()); - // Temporarily block anyone from removing an allowed TLD. - // This is so we can start having Support users use the console in production before we finish - // implementing configurable access control. - // TODO(b/119549884): remove this code once configurable access control is implemented. - if (!Sets.difference(initialRegistrar.getAllowedTlds(), updatedAllowedTlds).isEmpty()) { - throw new ForbiddenException("Can't remove allowed TLDs using the console."); - } - if (!Sets.difference(updatedAllowedTlds, initialRegistrar.getAllowedTlds()).isEmpty()) { - // If a REAL registrar isn't in compliance with regard to having an abuse contact set, - // prevent addition of allowed TLDs until that's fixed. - if (Registrar.Type.REAL.equals(initialRegistrar.getType()) - && PRODUCTION.equals(RegistryEnvironment.get())) { - checkArgumentPresent( - initialRegistrar.getWhoisAbuseContact(), - "Cannot add allowed TLDs if there is no WHOIS abuse contact set."); - } - } - builder.setAllowedTlds(updatedAllowedTlds); - return checkNotChangedUnlessAllowed(builder, initialRegistrar, Role.ADMIN); - } - - /** - * Makes sure {@code builder.build}is different from {@code originalRegistrar} only if we have the - * correct role. - * - *

On success, returns {@code builder.build()}. - */ - private Registrar checkNotChangedUnlessAllowed( - Registrar.Builder builder, Registrar originalRegistrar, Role allowedRole) { - Registrar updatedRegistrar = builder.build(); - if (updatedRegistrar.equals(originalRegistrar)) { - return updatedRegistrar; - } - checkArgument( - updatedRegistrar.getRegistrarId().equals(originalRegistrar.getRegistrarId()), - "Can't change clientId (%s -> %s)", - originalRegistrar.getRegistrarId(), - updatedRegistrar.getRegistrarId()); - if (registrarAccessor.hasRoleOnRegistrar(allowedRole, originalRegistrar.getRegistrarId())) { - return updatedRegistrar; - } - Map diffs = - DiffUtils.deepDiff( - originalRegistrar.toDiffableFieldMap(), updatedRegistrar.toDiffableFieldMap(), true); - - // It's expected that the update timestamp will be changed, as it gets reset whenever we change - // nested collections. If it's the only change, just return the original registrar. - if (diffs.keySet().equals(ImmutableSet.of("lastUpdateTime"))) { - return originalRegistrar; - } - - throw new ForbiddenException( - String.format("Unauthorized: only %s can change fields %s", allowedRole, diffs.keySet())); - } - - /** Reads the contacts from the supplied args. */ - public static ImmutableSet readContacts( - Registrar registrar, ImmutableSet existingContacts, Map args) { - return RegistrarFormFields.getRegistrarContactBuilders(existingContacts, args).stream() - .map(builder -> builder.setRegistrar(registrar).build()) - .collect(toImmutableSet()); - } - - /** - * Enforces business logic checks on registrar contacts. - * - * @throws FormException if the checks fail. - */ - public static void checkContactRequirements( - ImmutableSet existingContacts, ImmutableSet updatedContacts) { - // Check that no two contacts use the same email address. - Set emails = new HashSet<>(); - for (RegistrarPoc contact : updatedContacts) { - if (!emails.add(contact.getEmailAddress())) { - throw new ContactRequirementException( - String.format( - "One email address (%s) cannot be used for multiple contacts", - contact.getEmailAddress())); - } - } - // Check that required contacts don't go away, once they are set. - Multimap oldContactsByType = HashMultimap.create(); - for (RegistrarPoc contact : existingContacts) { - for (Type t : contact.getTypes()) { - oldContactsByType.put(t, contact); - } - } - Multimap newContactsByType = HashMultimap.create(); - for (RegistrarPoc contact : updatedContacts) { - for (Type t : contact.getTypes()) { - newContactsByType.put(t, contact); - } - } - for (Type t : difference(oldContactsByType.keySet(), newContactsByType.keySet())) { - if (t.isRequired()) { - throw new ContactRequirementException(t); - } - } - ensurePhoneNumberNotRemovedForContactTypes(oldContactsByType, newContactsByType, Type.TECH); - Optional domainWhoisAbuseContact = - getDomainWhoisVisibleAbuseContact(updatedContacts); - // If the new set has a domain WHOIS abuse contact, it must have a phone number. - if (domainWhoisAbuseContact.isPresent() - && domainWhoisAbuseContact.get().getPhoneNumber() == null) { - throw new ContactRequirementException( - "The abuse contact visible in domain WHOIS query must have a phone number"); - } - // If there was a domain WHOIS abuse contact in the old set, the new set must have one. - if (getDomainWhoisVisibleAbuseContact(existingContacts).isPresent() - && domainWhoisAbuseContact.isEmpty()) { - throw new ContactRequirementException( - "An abuse contact visible in domain WHOIS query must be designated"); - } - checkContactRegistryLockRequirements(existingContacts, updatedContacts); - } - - private static void checkContactRegistryLockRequirements( - ImmutableSet existingContacts, ImmutableSet updatedContacts) { - // Any contact(s) with new passwords must be allowed to set them - for (RegistrarPoc updatedContact : updatedContacts) { - if (updatedContact.isRegistryLockAllowed() - || updatedContact.isAllowedToSetRegistryLockPassword()) { - RegistrarPoc existingContact = - existingContacts.stream() - .filter( - contact -> contact.getEmailAddress().equals(updatedContact.getEmailAddress())) - .findFirst() - .orElseThrow( - () -> - new FormException( - "Cannot set registry lock password directly on new contact")); - // Can't modify registry lock email address - if (!Objects.equals( - updatedContact.getRegistryLockEmailAddress(), - existingContact.getRegistryLockEmailAddress())) { - throw new FormException("Cannot modify registryLockEmailAddress through the UI"); - } - if (updatedContact.isRegistryLockAllowed()) { - // the password must have been set before or the user was allowed to set it now - if (!existingContact.isAllowedToSetRegistryLockPassword() - && !existingContact.isRegistryLockAllowed()) { - throw new FormException("Registrar contact not allowed to set registry lock password"); - } - } - if (updatedContact.isAllowedToSetRegistryLockPassword()) { - if (!existingContact.isAllowedToSetRegistryLockPassword()) { - throw new FormException( - "Cannot modify isAllowedToSetRegistryLockPassword through the UI"); - } - } - } - } - - // Any previously-existing contacts with registry lock enabled cannot be deleted - existingContacts.stream() - .filter(RegistrarPoc::isRegistryLockAllowed) - .forEach( - contact -> { - Optional updatedContactOptional = - updatedContacts.stream() - .filter( - updatedContact -> - updatedContact.getEmailAddress().equals(contact.getEmailAddress())) - .findFirst(); - if (updatedContactOptional.isEmpty()) { - throw new FormException( - String.format( - "Cannot delete the contact %s that has registry lock enabled", - contact.getEmailAddress())); - } - if (!updatedContactOptional.get().isRegistryLockAllowed()) { - throw new FormException( - String.format( - "Cannot remove the ability to use registry lock on the contact %s", - contact.getEmailAddress())); - } - }); - } - - /** - * Ensure that for each given registrar type, a phone number is present after update, if there was - * one before. - */ - private static void ensurePhoneNumberNotRemovedForContactTypes( - Multimap oldContactsByType, - Multimap newContactsByType, - Type... types) { - for (Type type : types) { - if (oldContactsByType.get(type).stream().anyMatch(RegistrarSettingsAction::hasPhone) - && newContactsByType.get(type).stream().noneMatch(RegistrarSettingsAction::hasPhone)) { - throw new ContactRequirementException( - String.format( - "Please provide a phone number for at least one %s contact", - type.getDisplayName())); - } - } - } - - /** - * Retrieves the registrar contact whose phone number and email address is visible in domain WHOIS - * query as abuse contact (if any). - * - *

Frontend processing ensures that only one contact can be set as abuse contact in domain - * WHOIS record. Therefore, it is possible to return inside the loop once one such contact is - * found. - */ - private static Optional getDomainWhoisVisibleAbuseContact( - Set contacts) { - return contacts.stream().filter(RegistrarPoc::getVisibleInDomainWhoisAsAbuse).findFirst(); - } - - /** - * Determines if any changes were made to the registrar besides the lastUpdateTime, and if so, - * sends an email with a diff of the changes to the configured notification email address and all - * contact addresses and enqueues a task to re-sync the registrar sheet. - */ - private void sendExternalUpdatesIfNecessary(EmailInfo emailInfo) { - ImmutableSet existingContacts = emailInfo.contacts(); - if (!sendEmailUtils.hasRecipients() && existingContacts.isEmpty()) { - return; - } - Registrar existingRegistrar = emailInfo.registrar(); - Map diffs = - DiffUtils.deepDiff( - expandRegistrarWithContacts(existingContacts, existingRegistrar), - expandRegistrarWithContacts(emailInfo.updatedContacts(), emailInfo.updatedRegistrar()), - true); - @SuppressWarnings("unchecked") - Set changedKeys = (Set) diffs.keySet(); - if (CollectionUtils.difference(changedKeys, "lastUpdateTime").isEmpty()) { - return; - } - if (!RegistryEnvironment.isInTestServer()) { - // Enqueues a sync registrar sheet task if enqueuing is not triggered by console tests and - // there's an update besides the lastUpdateTime - cloudTasksUtils.enqueue( - SyncRegistrarsSheetAction.QUEUE, - cloudTasksUtils.createTask( - SyncRegistrarsSheetAction.class, Action.Method.POST, ImmutableMultimap.of())); - } - String environment = Ascii.toLowerCase(String.valueOf(RegistryEnvironment.get())); - sendEmailUtils.sendEmail( - String.format( - "Registrar %s (%s) updated in registry %s environment", - existingRegistrar.getRegistrarName(), existingRegistrar.getRegistrarId(), environment), - String.format( - """ - The following changes were made in registry %s environment to the registrar %s by\ - %s: - - %s""", - environment, - existingRegistrar.getRegistrarId(), - authResult.userIdForLogging(), - DiffUtils.prettyPrintDiffedMap(diffs, null)), - existingContacts.stream() - .filter(c -> c.getTypes().contains(Type.ADMIN)) - .map(RegistrarPoc::getEmailAddress) - .collect(toImmutableList())); - } - - /** Thrown when a set of contacts doesn't meet certain constraints. */ - private static class ContactRequirementException extends FormException { - ContactRequirementException(String msg) { - super(msg); - } - - ContactRequirementException(Type type) { - super(String.format("Must have at least one %s contact", type.getDisplayName())); - } - } -} 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 deleted file mode 100644 index 90d760d9d..000000000 --- a/core/src/main/java/google/registry/ui/server/registrar/RegistryLockGetAction.java +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright 2019 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.ui.server.registrar; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static google.registry.persistence.transaction.TransactionManagerFactory.tm; -import static google.registry.security.JsonResponseHelper.Status.SUCCESS; -import static google.registry.ui.server.console.ConsoleModule.PARAM_CLIENT_ID; -import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN; -import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.flogger.FluentLogger; -import com.google.common.net.MediaType; -import com.google.gson.Gson; -import google.registry.model.console.ConsolePermission; -import google.registry.model.console.User; -import google.registry.model.domain.RegistryLock; -import google.registry.model.registrar.Registrar; -import google.registry.model.tld.RegistryLockDao; -import google.registry.request.Action; -import google.registry.request.Action.GaeService; -import google.registry.request.Action.Method; -import google.registry.request.Parameter; -import google.registry.request.RequestMethod; -import google.registry.request.Response; -import google.registry.request.auth.Auth; -import google.registry.request.auth.AuthResult; -import google.registry.request.auth.AuthenticatedRegistrarAccessor; -import google.registry.request.auth.AuthenticatedRegistrarAccessor.RegistrarAccessDeniedException; -import google.registry.security.JsonResponseHelper; -import java.util.Optional; -import javax.inject.Inject; -import org.joda.time.DateTime; - -/** - * Action that allows for getting locks for a particular registrar. - * - *

Note: at the moment we have no mechanism for JSON GET/POSTs in the same class or at the same - * URL, which is why this is distinct from the {@link RegistryLockPostAction}. - */ -@Action( - service = GaeService.DEFAULT, - path = RegistryLockGetAction.PATH, - auth = Auth.AUTH_PUBLIC_LOGGED_IN) -public final class RegistryLockGetAction implements JsonGetAction { - - public static final String PATH = "/registry-lock-get"; - - private static final String LOCK_ENABLED_FOR_CONTACT_PARAM = "lockEnabledForContact"; - private static final String EMAIL_PARAM = "email"; - private static final String LOCKS_PARAM = "locks"; - private static final String DOMAIN_NAME_PARAM = "domainName"; - private static final String LOCKED_TIME_PARAM = "lockedTime"; - private static final String LOCKED_BY_PARAM = "lockedBy"; - private static final String IS_LOCK_PENDING_PARAM = "isLockPending"; - private static final String IS_UNLOCK_PENDING_PARAM = "isUnlockPending"; - private static final String USER_CAN_UNLOCK_PARAM = "userCanUnlock"; - - private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - private static final Gson GSON = new Gson(); - - @VisibleForTesting Method method; - private final Response response; - @VisibleForTesting AuthenticatedRegistrarAccessor registrarAccessor; - @VisibleForTesting AuthResult authResult; - @VisibleForTesting Optional paramClientId; - - @Inject - RegistryLockGetAction( - @RequestMethod Method method, - Response response, - AuthenticatedRegistrarAccessor registrarAccessor, - AuthResult authResult, - @Parameter("consoleClientId") Optional paramClientId) { - this.method = method; - this.response = response; - this.registrarAccessor = registrarAccessor; - this.authResult = authResult; - this.paramClientId = paramClientId; - } - - @Override - public void run() { - checkArgument(Method.GET.equals(method), "Only GET requests allowed"); - checkArgument(authResult.user().isPresent(), "User must be present"); - checkArgument(paramClientId.isPresent(), "clientId must be present"); - response.setContentType(MediaType.JSON_UTF_8); - - try { - ImmutableMap resultMap = getLockedDomainsMap(paramClientId.get()); - ImmutableMap payload = - JsonResponseHelper.create(SUCCESS, "Successful locks retrieval", resultMap); - response.setPayload(GSON.toJson(payload)); - } catch (RegistrarAccessDeniedException e) { - logger.atWarning().withCause(e).log( - "User %s doesn't have access to this registrar.", authResult.userIdForLogging()); - response.setStatus(SC_FORBIDDEN); - } catch (Exception e) { - logger.atWarning().withCause(e).log( - "Unexpected error when retrieving locks for a registrar."); - response.setStatus(SC_INTERNAL_SERVER_ERROR); - } - } - - static void verifyLockAccess( - AuthenticatedRegistrarAccessor registrarAccessor, String clientId, boolean isAdmin) - throws RegistrarAccessDeniedException { - Registrar registrar = registrarAccessor.getRegistrar(clientId); - checkArgument( - isAdmin || registrar.isRegistryLockAllowed(), - "Registry lock not allowed for registrar %s", - clientId); - } - - private ImmutableMap getLockedDomainsMap(String registrarId) - throws RegistrarAccessDeniedException { - // Note: admins always have access to the locks page - checkArgument(authResult.user().isPresent(), "User must be present"); - - boolean isAdmin = registrarAccessor.isAdmin(); - verifyLockAccess(registrarAccessor, registrarId, isAdmin); - - User user = authResult.user().get(); - // Split logic depending on whether we are using the old auth system or the new one - boolean isRegistryLockAllowed; - isRegistryLockAllowed = - user.getUserRoles().hasPermission(registrarId, ConsolePermission.REGISTRY_LOCK); - String relevantEmail = user.getRegistryLockEmailAddress().orElse(user.getEmailAddress()); - // Use the contact's registry lock email if it's present, else use the login email (for admins) - return ImmutableMap.of( - LOCK_ENABLED_FOR_CONTACT_PARAM, - isRegistryLockAllowed, - EMAIL_PARAM, - relevantEmail, - PARAM_CLIENT_ID, - registrarId, - LOCKS_PARAM, - getLockedDomains(registrarId, isAdmin)); - } - - private static ImmutableList> getLockedDomains( - String registrarId, boolean isAdmin) { - return tm().transact( - () -> - RegistryLockDao.getLocksByRegistrarId(registrarId).stream() - .filter(lock -> !lock.isLockRequestExpired(tm().getTransactionTime())) - .map(lock -> lockToMap(lock, isAdmin)) - .collect(toImmutableList())); - } - - private static ImmutableMap lockToMap(RegistryLock lock, boolean isAdmin) { - DateTime now = tm().getTransactionTime(); - return new ImmutableMap.Builder() - .put(DOMAIN_NAME_PARAM, lock.getDomainName()) - .put(LOCKED_TIME_PARAM, lock.getLockCompletionTime().map(DateTime::toString).orElse("")) - .put(LOCKED_BY_PARAM, lock.isSuperuser() ? "admin" : lock.getRegistrarPocId()) - .put(IS_LOCK_PENDING_PARAM, lock.getLockCompletionTime().isEmpty()) - .put( - IS_UNLOCK_PENDING_PARAM, - lock.getUnlockRequestTime().isPresent() - && lock.getUnlockCompletionTime().isEmpty() - && !lock.isUnlockRequestExpired(now)) - .put(USER_CAN_UNLOCK_PARAM, isAdmin || !lock.isSuperuser()) - .build(); - } -} 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 deleted file mode 100644 index 8dd6d448b..000000000 --- a/core/src/main/java/google/registry/ui/server/registrar/RegistryLockPostAction.java +++ /dev/null @@ -1,197 +0,0 @@ -// 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.ui.server.registrar; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static google.registry.persistence.transaction.TransactionManagerFactory.tm; -import static google.registry.security.JsonResponseHelper.Status.ERROR; -import static google.registry.security.JsonResponseHelper.Status.SUCCESS; -import static google.registry.ui.server.registrar.RegistryLockGetAction.verifyLockAccess; -import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; - -import com.google.common.base.Strings; -import com.google.common.base.Throwables; -import com.google.common.collect.ImmutableList; -import com.google.common.flogger.FluentLogger; -import com.google.gson.Gson; -import google.registry.flows.domain.DomainFlowUtils; -import google.registry.groups.GmailClient; -import google.registry.model.console.User; -import google.registry.model.domain.RegistryLock; -import google.registry.request.Action; -import google.registry.request.Action.GaeService; -import google.registry.request.Action.Method; -import google.registry.request.HttpException.ForbiddenException; -import google.registry.request.JsonActionRunner; -import google.registry.request.auth.Auth; -import google.registry.request.auth.AuthResult; -import google.registry.request.auth.AuthenticatedRegistrarAccessor; -import google.registry.request.auth.AuthenticatedRegistrarAccessor.RegistrarAccessDeniedException; -import google.registry.security.JsonResponseHelper; -import google.registry.tools.DomainLockUtils; -import google.registry.util.EmailMessage; -import jakarta.mail.internet.AddressException; -import jakarta.mail.internet.InternetAddress; -import jakarta.servlet.http.HttpServletRequest; -import java.net.URISyntaxException; -import java.util.Map; -import java.util.Optional; -import javax.inject.Inject; -import org.apache.http.client.utils.URIBuilder; -import org.joda.time.Duration; - -/** - * UI action that allows for creating registry locks. Locks / unlocks must be verified separately - * before they are written permanently. - * - *

Note: at the moment we have no mechanism for JSON GET/POSTs in the same class or at the same - * URL, which is why this is distinct from the {@link RegistryLockGetAction}. - */ -@Action( - service = GaeService.DEFAULT, - path = RegistryLockPostAction.PATH, - method = Method.POST, - auth = Auth.AUTH_PUBLIC_LOGGED_IN) -public class RegistryLockPostAction implements Runnable, JsonActionRunner.JsonAction { - public static final String PATH = "/registry-lock-post"; - public static final String VERIFICATION_EMAIL_TEMPLATE = - """ - Please click the link below to perform the lock / unlock action on domain %s. Note: this\ - code will expire in one hour. - - %s"""; - - private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - private static final Gson GSON = new Gson(); - - private final HttpServletRequest req; - private final JsonActionRunner jsonActionRunner; - private final AuthResult authResult; - private final AuthenticatedRegistrarAccessor registrarAccessor; - private final GmailClient gmailClient; - private final DomainLockUtils domainLockUtils; - - @Inject - RegistryLockPostAction( - HttpServletRequest req, - JsonActionRunner jsonActionRunner, - AuthResult authResult, - AuthenticatedRegistrarAccessor registrarAccessor, - GmailClient gmailClient, - DomainLockUtils domainLockUtils) { - this.req = req; - this.jsonActionRunner = jsonActionRunner; - this.authResult = authResult; - this.registrarAccessor = registrarAccessor; - this.gmailClient = gmailClient; - this.domainLockUtils = domainLockUtils; - } - - @Override - public void run() { - jsonActionRunner.run(this); - } - - @Override - public Map handleJsonRequest(Map input) { - try { - checkArgumentNotNull(input, "Null JSON"); - RegistryLockPostInput postInput = - GSON.fromJson(GSON.toJsonTree(input), RegistryLockPostInput.class); - String registrarId = postInput.registrarId; - checkArgument(!Strings.isNullOrEmpty(registrarId), "Missing key for registrarId"); - checkArgument(!Strings.isNullOrEmpty(postInput.domainName), "Missing key for domainName"); - DomainFlowUtils.validateDomainName(postInput.domainName); - checkNotNull(postInput.isLock, "Missing key for isLock"); - User user = - authResult.user().orElseThrow(() -> new ForbiddenException("User is not logged in")); - - // TODO: Move this line to the transaction below during nested transaction refactoring. - String userEmail = verifyPasswordAndGetEmail(user, postInput); - tm().transact( - () -> { - RegistryLock registryLock = - postInput.isLock - ? domainLockUtils.saveNewRegistryLockRequest( - postInput.domainName, - registrarId, - userEmail, - registrarAccessor.isAdmin()) - : domainLockUtils.saveNewRegistryUnlockRequest( - postInput.domainName, - registrarId, - registrarAccessor.isAdmin(), - Optional.ofNullable(postInput.relockDurationMillis).map(Duration::new)); - sendVerificationEmail(registryLock, userEmail, postInput.isLock); - }); - String action = postInput.isLock ? "lock" : "unlock"; - return JsonResponseHelper.create(SUCCESS, String.format("Successful %s", action)); - } catch (Throwable e) { - logger.atWarning().withCause(e).log("Failed to lock/unlock domain."); - return JsonResponseHelper.create( - ERROR, - Optional.ofNullable(Throwables.getRootCause(e).getMessage()).orElse("Unspecified error")); - } - } - - private void sendVerificationEmail(RegistryLock lock, String userEmail, boolean isLock) { - try { - String url = - new URIBuilder() - .setScheme("https") - .setHost(req.getServerName()) - .setPath("registry-lock-verify") - .setParameter("lockVerificationCode", lock.getVerificationCode()) - .build() - .toString(); - String body = String.format(VERIFICATION_EMAIL_TEMPLATE, lock.getDomainName(), url); - ImmutableList recipients = - ImmutableList.of(new InternetAddress(userEmail, true)); - String action = isLock ? "lock" : "unlock"; - gmailClient.sendEmail( - EmailMessage.newBuilder() - .setBody(body) - .setSubject(String.format("Registry %s verification", action)) - .setRecipients(recipients) - .build()); - } catch (AddressException | URISyntaxException e) { - throw new RuntimeException(e); // caught above -- this is so we can run in a transaction - } - } - - private String verifyPasswordAndGetEmail(User user, RegistryLockPostInput postInput) - throws RegistrarAccessDeniedException { - if (registrarAccessor.isAdmin()) { - return user.getEmailAddress(); - } - // Verify that the registrar has locking enabled - verifyLockAccess(registrarAccessor, postInput.registrarId, false); - checkArgument( - user.verifyRegistryLockPassword(postInput.password), - "Incorrect registry lock password for user"); - return user.getRegistryLockEmailAddress() - .orElseThrow(() -> new IllegalArgumentException("User has no registry lock email address")); - } - - /** Value class that represents the expected input body from the UI request. */ - private static class RegistryLockPostInput { - private String registrarId; - private String domainName; - private Boolean isLock; - private String password; - private Long relockDurationMillis; - } -} diff --git a/core/src/main/java/google/registry/ui/server/registrar/RegistryLockVerifyAction.java b/core/src/main/java/google/registry/ui/server/registrar/RegistryLockVerifyAction.java deleted file mode 100644 index d8751442c..000000000 --- a/core/src/main/java/google/registry/ui/server/registrar/RegistryLockVerifyAction.java +++ /dev/null @@ -1,91 +0,0 @@ -// 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.ui.server.registrar; - -import static google.registry.ui.server.SoyTemplateUtils.CSS_RENAMING_MAP_SUPPLIER; - -import com.google.common.base.Supplier; -import com.google.common.base.Throwables; -import com.google.common.flogger.FluentLogger; -import com.google.template.soy.tofu.SoyTofu; -import google.registry.model.domain.RegistryLock; -import google.registry.request.Action; -import google.registry.request.Action.GaeService; -import google.registry.request.Parameter; -import google.registry.request.auth.Auth; -import google.registry.tools.DomainLockUtils; -import google.registry.ui.server.SoyTemplateUtils; -import google.registry.ui.soy.registrar.RegistryLockVerificationSoyInfo; -import java.util.Map; -import javax.inject.Inject; - -/** Action that allows for verification of registry lock / unlock requests */ -@Action( - service = GaeService.DEFAULT, - path = RegistryLockVerifyAction.PATH, - auth = Auth.AUTH_PUBLIC_LOGGED_IN) -public final class RegistryLockVerifyAction extends HtmlAction { - - public static final String PATH = "/registry-lock-verify"; - - private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - - private static final Supplier TOFU_SUPPLIER = - SoyTemplateUtils.createTofuSupplier( - google.registry.ui.soy.registrar.AnalyticsSoyInfo.getInstance(), - google.registry.ui.soy.registrar.ConsoleSoyInfo.getInstance(), - google.registry.ui.soy.registrar.ConsoleUtilsSoyInfo.getInstance(), - google.registry.ui.soy.registrar.RegistryLockVerificationSoyInfo.getInstance()); - - private final DomainLockUtils domainLockUtils; - private final String lockVerificationCode; - - @Inject - public RegistryLockVerifyAction( - DomainLockUtils domainLockUtils, - @Parameter("lockVerificationCode") String lockVerificationCode) { - this.domainLockUtils = domainLockUtils; - this.lockVerificationCode = lockVerificationCode; - } - - @Override - public void runAfterLogin(Map data) { - try { - boolean isAdmin = authResult.user().get().getUserRoles().isAdmin(); - RegistryLock resultLock = - domainLockUtils.verifyVerificationCode(lockVerificationCode, isAdmin); - data.put("isLock", resultLock.getUnlockCompletionTime().isEmpty()); - data.put("success", true); - data.put("domainName", resultLock.getDomainName()); - } catch (Throwable t) { - logger.atWarning().withCause(t).log( - "Error when verifying verification code '%s'.", lockVerificationCode); - data.put("success", false); - data.put("errorMessage", Throwables.getRootCause(t).getMessage()); - } - response.setPayload( - TOFU_SUPPLIER - .get() - .newRenderer(RegistryLockVerificationSoyInfo.VERIFICATION_PAGE) - .setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get()) - .setData(data) - .render()); - } - - @Override - public String getPath() { - return PATH; - } -} diff --git a/core/src/main/java/google/registry/ui/server/registrar/package-info.java b/core/src/main/java/google/registry/ui/server/registrar/package-info.java deleted file mode 100644 index 508f80830..000000000 --- a/core/src/main/java/google/registry/ui/server/registrar/package-info.java +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2017 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. - -@javax.annotation.ParametersAreNonnullByDefault -package google.registry.ui.server.registrar; diff --git a/core/src/test/java/google/registry/module/RequestComponentTest.java b/core/src/test/java/google/registry/module/RequestComponentTest.java index 6f2db2477..e1a2fbd54 100644 --- a/core/src/test/java/google/registry/module/RequestComponentTest.java +++ b/core/src/test/java/google/registry/module/RequestComponentTest.java @@ -19,8 +19,6 @@ import static com.google.common.truth.Truth.assertThat; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Sets; import google.registry.module.backend.BackendRequestComponent; import google.registry.module.bsa.BsaRequestComponent; import google.registry.module.frontend.FrontendRequestComponent; @@ -43,18 +41,6 @@ public class RequestComponentTest { PubApiRequestComponent.class, "pubapi", BsaRequestComponent.class, "bsa"); - // Paths that do not route to Jetty (all for the legacy console). - private static final ImmutableSet ignoredPaths = - ImmutableSet.of( - "/registrar", - "/registrar-create", - "/registrar-ote-setup", - "/registrar-ote-status", - "/registrar-settings", - "/registry-lock-get", - "/registry-lock-post", - "/registry-lock-verify"); - @Test void testRoutingMap() { GoldenFileTestHelper.assertThatRoutesFromComponent(RequestComponent.class) @@ -69,12 +55,7 @@ public class RequestComponentTest { for (var component : GaeComponents.entrySet()) { gaeRoutes.addAll(getRoutes(component.getKey(), component.getValue() + "_routing.txt")); } - assertThat(Sets.difference(jettyRoutes, gaeRoutes)).isEmpty(); - assertThat( - Sets.difference(gaeRoutes, jettyRoutes).stream() - .map(Route::path) - .collect(Collectors.toSet())) - .containsExactlyElementsIn(ignoredPaths); + assertThat(jettyRoutes).isEqualTo(gaeRoutes); } private Set getRoutes(Class context, String filename) { diff --git a/core/src/test/java/google/registry/server/RegistryTestServer.java b/core/src/test/java/google/registry/server/RegistryTestServer.java index df4adb3e6..c59db4d4a 100644 --- a/core/src/test/java/google/registry/server/RegistryTestServer.java +++ b/core/src/test/java/google/registry/server/RegistryTestServer.java @@ -40,10 +40,6 @@ public final class RegistryTestServer { .put( "/error.html", PROJECT_ROOT.resolve("core/src/main/java/google/registry/ui/html/error.html")) - .put("/assets/js/*", RESOURCES_DIR.resolve("google/registry/ui")) - .put("/assets/css/*", RESOURCES_DIR.resolve("google/registry/ui/css")) - .put("/assets/sources/*", PROJECT_ROOT) - .put("/assets/*", PROJECT_ROOT.resolve("core/src/main/java/google/registry/ui/assets")) .put("/console/*", PROJECT_ROOT.resolve("console-webapp/staged/dist")) .build(); @@ -73,17 +69,7 @@ public final class RegistryTestServer { // Notification of Registered Domain Names (NORDN) route("/_dr/task/nordnUpload", BackendServlet.class), - route("/_dr/task/nordnVerify", BackendServlet.class), - - // Registrar Console - route("/registrar", FrontendServlet.class), - route("/registrar-create", FrontendServlet.class), - route("/registrar-ote-setup", FrontendServlet.class), - route("/registrar-ote-status", FrontendServlet.class), - route("/registrar-settings", FrontendServlet.class), - route("/registry-lock-get", FrontendServlet.class), - route("/registry-lock-post", FrontendServlet.class), - route("/registry-lock-verify", FrontendServlet.class)); + route("/_dr/task/nordnVerify", BackendServlet.class)); private final TestServer server; diff --git a/core/src/test/java/google/registry/ui/server/ActionMembershipTest.java b/core/src/test/java/google/registry/ui/server/ActionMembershipTest.java index a4f6af0e2..2c85d8dff 100644 --- a/core/src/test/java/google/registry/ui/server/ActionMembershipTest.java +++ b/core/src/test/java/google/registry/ui/server/ActionMembershipTest.java @@ -20,8 +20,6 @@ import com.google.common.collect.ImmutableSet; import google.registry.request.Action; import google.registry.request.JsonActionRunner; import google.registry.ui.server.console.ConsoleApiAction; -import google.registry.ui.server.registrar.HtmlAction; -import google.registry.ui.server.registrar.JsonGetAction; import io.github.classgraph.ClassGraph; import io.github.classgraph.ScanResult; import org.junit.jupiter.api.Test; @@ -32,10 +30,8 @@ final class ActionMembershipTest { @Test void testAllActionsEitherHtmlOrJson() { // All UI actions should serve valid HTML or JSON. There are three valid options: - // 1. Extending HtmlAction to signal that we are serving an HTML page - // 2. Extending JsonAction to show that we are serving JSON POST requests - // 3. Extending JsonGetAction to serve JSON GET requests - // 4. Extending ConsoleApiAction to serve JSON requests + // 1. Extending JsonAction to show that we are serving JSON POST requests + // 2. Extending ConsoleApiAction to serve JSON requests ImmutableSet.Builder failingClasses = new ImmutableSet.Builder<>(); try (ScanResult scanResult = new ClassGraph().enableAnnotationInfo().whitelistPackages("google.registry.ui").scan()) { @@ -44,9 +40,8 @@ final class ActionMembershipTest { .forEach( classInfo -> { if (!classInfo.extendsSuperclass(ConsoleApiAction.class.getName()) - && !classInfo.extendsSuperclass(HtmlAction.class.getName()) - && !classInfo.implementsInterface(JsonActionRunner.JsonAction.class.getName()) - && !classInfo.implementsInterface(JsonGetAction.class.getName())) { + && !classInfo.implementsInterface( + JsonActionRunner.JsonAction.class.getName())) { failingClasses.add(classInfo.getName()); } }); diff --git a/core/src/test/java/google/registry/ui/server/console/settings/ContactActionTest.java b/core/src/test/java/google/registry/ui/server/console/settings/ContactActionTest.java index 040188d4a..e58234c98 100644 --- a/core/src/test/java/google/registry/ui/server/console/settings/ContactActionTest.java +++ b/core/src/test/java/google/registry/ui/server/console/settings/ContactActionTest.java @@ -16,15 +16,17 @@ package google.registry.ui.server.console.settings; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.truth.Truth.assertThat; +import static google.registry.model.registrar.RegistrarPocBase.Type.ABUSE; import static google.registry.model.registrar.RegistrarPocBase.Type.ADMIN; +import static google.registry.model.registrar.RegistrarPocBase.Type.TECH; import static google.registry.testing.DatabaseHelper.createAdminUser; import static google.registry.testing.DatabaseHelper.insertInDb; import static google.registry.testing.DatabaseHelper.loadAllOf; import static google.registry.testing.SqlHelper.saveRegistrar; +import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST; import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN; import static jakarta.servlet.http.HttpServletResponse.SC_OK; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -46,13 +48,10 @@ import google.registry.request.auth.AuthResult; import google.registry.testing.ConsoleApiParamsUtils; import google.registry.testing.FakeResponse; import google.registry.ui.server.console.ConsoleApiParams; -import google.registry.ui.server.console.ConsoleModule; import google.registry.util.EmailMessage; import jakarta.mail.internet.AddressException; import jakarta.mail.internet.InternetAddress; -import java.io.BufferedReader; import java.io.IOException; -import java.io.StringReader; import java.util.HashMap; import java.util.Optional; import org.junit.jupiter.api.BeforeEach; @@ -69,17 +68,11 @@ class ContactActionTest { + "\"types\":[\"ADMIN\"],\"visibleInWhoisAsAdmin\":true," + "\"visibleInWhoisAsTech\":false,\"visibleInDomainWhoisAsAbuse\":false}"; - private static String jsonRegistrar2 = - "{\"name\":\"Test Registrar 2\"," - + "\"emailAddress\":\"test.registrar2@example.com\"," - + "\"registrarId\":\"registrarId\"," - + "\"phoneNumber\":\"+1.1234567890\",\"faxNumber\":\"+1.1234567891\"," - + "\"types\":[\"ADMIN\"],\"visibleInWhoisAsAdmin\":true," - + "\"visibleInWhoisAsTech\":false,\"visibleInDomainWhoisAsAbuse\":false}"; - private Registrar testRegistrar; private ConsoleApiParams consoleApiParams; - private RegistrarPoc testRegistrarPoc; + private RegistrarPoc testRegistrarPoc1; + private RegistrarPoc testRegistrarPoc2; + private static final Gson GSON = RequestModule.provideGson(); @RegisterExtension @@ -89,7 +82,7 @@ class ContactActionTest { @BeforeEach void beforeEach() { testRegistrar = saveRegistrar("registrarId"); - testRegistrarPoc = + testRegistrarPoc1 = new RegistrarPoc.Builder() .setRegistrar(testRegistrar) .setName("Test Registrar 1") @@ -101,17 +94,24 @@ class ContactActionTest { .setVisibleInWhoisAsTech(false) .setVisibleInDomainWhoisAsAbuse(false) .build(); + testRegistrarPoc2 = + testRegistrarPoc1 + .asBuilder() + .setName("Test Registrar 2") + .setEmailAddress("test.registrar2@example.com") + .setPhoneNumber("+1.1234567890") + .setFaxNumber("+1.1234567891") + .build(); } @Test void testSuccess_getContactInfo() throws IOException { - insertInDb(testRegistrarPoc); + insertInDb(testRegistrarPoc1); ContactAction action = createAction( Action.Method.GET, AuthResult.createUser(createAdminUser("email@email.com")), - testRegistrar.getRegistrarId(), - null); + testRegistrar.getRegistrarId()); action.run(); assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_OK); assertThat(((FakeResponse) consoleApiParams.response()).getPayload()) @@ -120,13 +120,13 @@ class ContactActionTest { @Test void testSuccess_noOp() throws IOException { - insertInDb(testRegistrarPoc); + insertInDb(testRegistrarPoc1); ContactAction action = createAction( Action.Method.POST, AuthResult.createUser(createAdminUser("email@email.com")), testRegistrar.getRegistrarId(), - "[" + jsonRegistrar1 + "]"); + testRegistrarPoc1); action.run(); assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_OK); verify(consoleApiParams.sendEmailUtils().gmailClient, never()).sendEmail(any()); @@ -134,14 +134,13 @@ class ContactActionTest { @Test void testSuccess_onlyContactsWithNonEmptyType() throws IOException { - testRegistrarPoc = testRegistrarPoc.asBuilder().setTypes(ImmutableSet.of()).build(); - insertInDb(testRegistrarPoc); + testRegistrarPoc1 = testRegistrarPoc1.asBuilder().setTypes(ImmutableSet.of()).build(); + insertInDb(testRegistrarPoc1); ContactAction action = createAction( Action.Method.GET, AuthResult.createUser(createAdminUser("email@email.com")), - testRegistrar.getRegistrarId(), - null); + testRegistrar.getRegistrarId()); action.run(); assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_OK); assertThat(((FakeResponse) consoleApiParams.response()).getPayload()).isEqualTo("[]"); @@ -149,13 +148,14 @@ class ContactActionTest { @Test void testSuccess_postCreateContactInfo() throws IOException { - insertInDb(testRegistrarPoc); + insertInDb(testRegistrarPoc1); ContactAction action = createAction( Action.Method.POST, AuthResult.createUser(createAdminUser("email@email.com")), testRegistrar.getRegistrarId(), - "[" + jsonRegistrar1 + "," + jsonRegistrar2 + "]"); + testRegistrarPoc1, + testRegistrarPoc2); action.run(); assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_OK); assertThat( @@ -168,14 +168,14 @@ class ContactActionTest { @Test void testSuccess_postUpdateContactInfo() throws IOException { - testRegistrarPoc = testRegistrarPoc.asBuilder().setEmailAddress("incorrect@email.com").build(); - insertInDb(testRegistrarPoc); + insertInDb(testRegistrarPoc1.asBuilder().setEmailAddress("incorrect@email.com").build()); ContactAction action = createAction( Action.Method.POST, AuthResult.createUser(createAdminUser("email@email.com")), testRegistrar.getRegistrarId(), - "[" + jsonRegistrar1 + "," + jsonRegistrar2 + "]"); + testRegistrarPoc1, + testRegistrarPoc2); action.run(); assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_OK); HashMap testResult = new HashMap<>(); @@ -191,15 +191,205 @@ class ContactActionTest { } @Test - void testSuccess_sendsEmail() throws IOException, AddressException { - testRegistrarPoc = testRegistrarPoc.asBuilder().setEmailAddress("incorrect@email.com").build(); - insertInDb(testRegistrarPoc); + void testFailure_postUpdateContactInfo_duplicateEmails() throws IOException { ContactAction action = createAction( Action.Method.POST, AuthResult.createUser(createAdminUser("email@email.com")), testRegistrar.getRegistrarId(), - "[" + jsonRegistrar1 + "]"); + testRegistrarPoc1, + testRegistrarPoc2.asBuilder().setEmailAddress("test.registrar1@example.com").build()); + action.run(); + assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_BAD_REQUEST); + assertThat(((FakeResponse) consoleApiParams.response()).getPayload()) + .isEqualTo( + "One email address (test.registrar1@example.com) cannot be used for multiple contacts"); + assertThat( + loadAllOf(RegistrarPoc.class).stream() + .filter(r -> r.registrarId.equals(testRegistrar.getRegistrarId())) + .collect(toImmutableList())) + .isEmpty(); + } + + @Test + void testFailure_postUpdateContactInfo_requiredContactRemoved() throws IOException { + insertInDb(testRegistrarPoc1); + ContactAction action = + createAction( + Action.Method.POST, + AuthResult.createUser(createAdminUser("email@email.com")), + testRegistrar.getRegistrarId(), + testRegistrarPoc1.asBuilder().setTypes(ImmutableSet.of(ABUSE)).build()); + action.run(); + assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_BAD_REQUEST); + assertThat(((FakeResponse) consoleApiParams.response()).getPayload()) + .isEqualTo("Must have at least one primary contact"); + assertThat( + loadAllOf(RegistrarPoc.class).stream() + .filter(r -> r.registrarId.equals(testRegistrar.getRegistrarId())) + .collect(toImmutableList())) + .containsExactly(testRegistrarPoc1); + } + + @Test + void testFailure_postUpdateContactInfo_phoneNumberRemoved() throws IOException { + testRegistrarPoc1 = + testRegistrarPoc1.asBuilder().setTypes(ImmutableSet.of(ADMIN, TECH)).build(); + insertInDb(testRegistrarPoc1); + ContactAction action = + createAction( + Action.Method.POST, + AuthResult.createUser(createAdminUser("email@email.com")), + testRegistrar.getRegistrarId(), + testRegistrarPoc1 + .asBuilder() + .setPhoneNumber(null) + .setTypes(ImmutableSet.of(ADMIN, TECH)) + .build()); + action.run(); + assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_BAD_REQUEST); + assertThat(((FakeResponse) consoleApiParams.response()).getPayload()) + .isEqualTo("Please provide a phone number for at least one technical contact"); + assertThat( + loadAllOf(RegistrarPoc.class).stream() + .filter(r -> r.registrarId.equals(testRegistrar.getRegistrarId())) + .collect(toImmutableList())) + .containsExactly(testRegistrarPoc1); + } + + @Test + void testFailure_postUpdateContactInfo_whoisContactMissingPhoneNumber() throws IOException { + ContactAction action = + createAction( + Action.Method.POST, + AuthResult.createUser(createAdminUser("email@email.com")), + testRegistrar.getRegistrarId(), + testRegistrarPoc1 + .asBuilder() + .setPhoneNumber(null) + .setVisibleInDomainWhoisAsAbuse(true) + .build()); + action.run(); + assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_BAD_REQUEST); + assertThat(((FakeResponse) consoleApiParams.response()).getPayload()) + .isEqualTo("The abuse contact visible in domain WHOIS query must have a phone number"); + assertThat( + loadAllOf(RegistrarPoc.class).stream() + .filter(r -> r.registrarId.equals(testRegistrar.getRegistrarId())) + .collect(toImmutableList())) + .isEmpty(); + } + + @Test + void testFailure_postUpdateContactInfo_whoisContactPhoneNumberRemoved() throws IOException { + testRegistrarPoc1 = testRegistrarPoc1.asBuilder().setVisibleInDomainWhoisAsAbuse(true).build(); + insertInDb(testRegistrarPoc1); + ContactAction action = + createAction( + Action.Method.POST, + AuthResult.createUser(createAdminUser("email@email.com")), + testRegistrar.getRegistrarId(), + testRegistrarPoc1.asBuilder().setVisibleInDomainWhoisAsAbuse(false).build()); + action.run(); + assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_BAD_REQUEST); + assertThat(((FakeResponse) consoleApiParams.response()).getPayload()) + .isEqualTo("An abuse contact visible in domain WHOIS query must be designated"); + assertThat( + loadAllOf(RegistrarPoc.class).stream() + .filter(r -> r.registrarId.equals(testRegistrar.getRegistrarId())) + .collect(toImmutableList())) + .containsExactly(testRegistrarPoc1); + } + + @Test + void testFailure_postUpdateContactInfo_newContactCannotSetRegistryLockPassword() + throws IOException { + ContactAction action = + createAction( + Action.Method.POST, + AuthResult.createUser(createAdminUser("email@email.com")), + testRegistrar.getRegistrarId(), + testRegistrarPoc1 + .asBuilder() + .setAllowedToSetRegistryLockPassword(true) + .setRegistryLockEmailAddress("lock@example.com") + .build()); + action.run(); + assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_BAD_REQUEST); + assertThat(((FakeResponse) consoleApiParams.response()).getPayload()) + .isEqualTo("Cannot set registry lock password directly on new contact"); + assertThat( + loadAllOf(RegistrarPoc.class).stream() + .filter(r -> r.registrarId.equals(testRegistrar.getRegistrarId())) + .collect(toImmutableList())) + .isEmpty(); + } + + @Test + void testFailure_postUpdateContactInfo_cannotModifyRegistryLockEmail() throws IOException { + testRegistrarPoc1 = + testRegistrarPoc1 + .asBuilder() + .setRegistryLockEmailAddress("lock@example.com") + .setAllowedToSetRegistryLockPassword(true) + .build(); + insertInDb(testRegistrarPoc1); + ContactAction action = + createAction( + Action.Method.POST, + AuthResult.createUser(createAdminUser("email@email.com")), + testRegistrar.getRegistrarId(), + testRegistrarPoc1 + .asBuilder() + .setRegistryLockEmailAddress("unlock@example.com") + .build()); + action.run(); + assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_BAD_REQUEST); + assertThat(((FakeResponse) consoleApiParams.response()).getPayload()) + .isEqualTo("Cannot modify registryLockEmailAddress through the UI"); + assertThat( + loadAllOf(RegistrarPoc.class).stream() + .filter(r -> r.registrarId.equals(testRegistrar.getRegistrarId())) + .collect(toImmutableList())) + .containsExactly(testRegistrarPoc1); + } + + @Test + void testFailure_postUpdateContactInfo_cannotSetIsAllowedToSetRegistryLockPassword() + throws IOException { + testRegistrarPoc1 = + testRegistrarPoc1 + .asBuilder() + .setRegistryLockEmailAddress("lock@example.com") + .setAllowedToSetRegistryLockPassword(false) + .build(); + insertInDb(testRegistrarPoc1); + ContactAction action = + createAction( + Action.Method.POST, + AuthResult.createUser(createAdminUser("email@email.com")), + testRegistrar.getRegistrarId(), + testRegistrarPoc1.asBuilder().setAllowedToSetRegistryLockPassword(true).build()); + action.run(); + assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_BAD_REQUEST); + assertThat(((FakeResponse) consoleApiParams.response()).getPayload()) + .isEqualTo("Cannot modify isAllowedToSetRegistryLockPassword through the UI"); + assertThat( + loadAllOf(RegistrarPoc.class).stream() + .filter(r -> r.registrarId.equals(testRegistrar.getRegistrarId())) + .collect(toImmutableList())) + .containsExactly(testRegistrarPoc1); + } + + @Test + void testSuccess_sendsEmail() throws IOException, AddressException { + insertInDb(testRegistrarPoc1.asBuilder().setEmailAddress("incorrect@email.com").build()); + ContactAction action = + createAction( + Action.Method.POST, + AuthResult.createUser(createAdminUser("email@email.com")), + testRegistrar.getRegistrarId(), + testRegistrarPoc1); action.run(); assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_OK); verify(consoleApiParams.sendEmailUtils().gmailClient, times(1)) @@ -245,13 +435,13 @@ class ContactActionTest { @Test void testSuccess_postDeleteContactInfo() throws IOException { - insertInDb(testRegistrarPoc); + insertInDb(testRegistrarPoc1); ContactAction action = createAction( Action.Method.POST, AuthResult.createUser(createAdminUser("email@email.com")), testRegistrar.getRegistrarId(), - "[" + jsonRegistrar2 + "]"); + testRegistrarPoc2); action.run(); assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_OK); assertThat( @@ -264,7 +454,7 @@ class ContactActionTest { @Test void testFailure_postDeleteContactInfo_missingPermission() throws IOException { - insertInDb(testRegistrarPoc); + insertInDb(testRegistrarPoc1); ContactAction action = createAction( Action.Method.POST, @@ -279,26 +469,21 @@ class ContactActionTest { .build()) .build()), testRegistrar.getRegistrarId(), - "[" + jsonRegistrar2 + "]"); + testRegistrarPoc2); action.run(); assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_FORBIDDEN); } private ContactAction createAction( - Action.Method method, AuthResult authResult, String registrarId, String contacts) + Action.Method method, AuthResult authResult, String registrarId, RegistrarPoc... contacts) throws IOException { consoleApiParams = ConsoleApiParamsUtils.createFake(authResult); when(consoleApiParams.request().getMethod()).thenReturn(method.toString()); if (method.equals(Action.Method.GET)) { return new ContactAction(consoleApiParams, GSON, registrarId, Optional.empty()); } else { - doReturn(new BufferedReader(new StringReader(contacts))) - .when(consoleApiParams.request()) - .getReader(); - Optional> maybeContacts = - ConsoleModule.provideContacts( - GSON, RequestModule.provideJsonBody(consoleApiParams.request(), GSON)); - return new ContactAction(consoleApiParams, GSON, registrarId, maybeContacts); + return new ContactAction( + consoleApiParams, GSON, registrarId, Optional.of(ImmutableSet.copyOf(contacts))); } } } diff --git a/core/src/test/java/google/registry/ui/server/registrar/ConsoleOteSetupActionTest.java b/core/src/test/java/google/registry/ui/server/registrar/ConsoleOteSetupActionTest.java deleted file mode 100644 index e04973299..000000000 --- a/core/src/test/java/google/registry/ui/server/registrar/ConsoleOteSetupActionTest.java +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright 2018 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.ui.server.registrar; - -import static com.google.common.truth.Truth.assertThat; -import static google.registry.model.OteAccountBuilderTest.verifyIapPermission; -import static google.registry.model.OteAccountBuilderTest.verifyUser; -import static google.registry.model.registrar.Registrar.loadByRegistrarId; -import static google.registry.testing.DatabaseHelper.persistPremiumList; -import static jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; -import static org.joda.money.CurrencyUnit.USD; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSetMultimap; -import google.registry.groups.GmailClient; -import google.registry.model.console.User; -import google.registry.model.console.UserRoles; -import google.registry.model.tld.Tld; -import google.registry.persistence.transaction.JpaTestExtensions; -import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension; -import google.registry.request.Action.Method; -import google.registry.request.auth.AuthResult; -import google.registry.request.auth.AuthenticatedRegistrarAccessor; -import google.registry.security.XsrfTokenManager; -import google.registry.testing.CloudTasksHelper; -import google.registry.testing.DeterministicStringGenerator; -import google.registry.testing.FakeClock; -import google.registry.testing.FakeResponse; -import google.registry.testing.SystemPropertyExtension; -import google.registry.tools.IamClient; -import google.registry.ui.server.SendEmailUtils; -import google.registry.util.EmailMessage; -import google.registry.util.RegistryEnvironment; -import jakarta.servlet.http.HttpServletRequest; -import java.util.Optional; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -/** Unit tests for {@link ConsoleOteSetupAction}. */ -@ExtendWith(MockitoExtension.class) -public final class ConsoleOteSetupActionTest { - - @RegisterExtension - final JpaIntegrationTestExtension jpa = - new JpaTestExtensions.Builder().buildIntegrationTestExtension(); - - @RegisterExtension - @Order(value = Integer.MAX_VALUE) - final SystemPropertyExtension systemPropertyExtension = new SystemPropertyExtension(); - - private final IamClient iamClient = mock(IamClient.class); - private final CloudTasksHelper cloudTasksHelper = new CloudTasksHelper(); - private final FakeResponse response = new FakeResponse(); - private final ConsoleOteSetupAction action = new ConsoleOteSetupAction(); - private final User user = - new User.Builder() - .setEmailAddress("marla.singer@example.com") - .setUserRoles(new UserRoles()) - .build(); - - @Mock HttpServletRequest request; - @Mock GmailClient gmailClient; - - @BeforeEach - void beforeEach() throws Exception { - persistPremiumList("default_sandbox_list", USD, "sandbox,USD 1000"); - - action.req = request; - action.method = Method.GET; - action.response = response; - action.registrarAccessor = - AuthenticatedRegistrarAccessor.createForTesting( - ImmutableSetMultimap.of("unused", AuthenticatedRegistrarAccessor.Role.ADMIN)); - action.xsrfTokenManager = new XsrfTokenManager(new FakeClock()); - action.authResult = AuthResult.createUser(user); - action.sendEmailUtils = - new SendEmailUtils( - ImmutableList.of("notification@test.example", "notification2@test.example"), - gmailClient); - action.logoFilename = "logo.png"; - action.productName = "Nomulus"; - action.clientId = Optional.empty(); - action.email = Optional.empty(); - action.analyticsConfig = ImmutableMap.of("googleAnalyticsId", "sampleId"); - - action.optionalPassword = Optional.empty(); - action.passwordGenerator = new DeterministicStringGenerator("abcdefghijklmnopqrstuvwxyz"); - action.maybeGroupEmailAddress = Optional.of("group@example.com"); - action.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils(); - action.iamClient = iamClient; - } - - @Test - void testNoUser_redirect() { - action.authResult = AuthResult.NOT_AUTHENTICATED; - action.run(); - assertThat(response.getStatus()).isEqualTo(SC_UNAUTHORIZED); - } - - @Test - void testGet_authorized() { - action.run(); - assertThat(response.getPayload()).contains("

Setup OT&E

"); - assertThat(response.getPayload()).contains("gtag('config', 'sampleId')"); - } - - @Test - void testGet_authorized_onProduction() { - RegistryEnvironment.PRODUCTION.setup(systemPropertyExtension); - assertThrows(IllegalStateException.class, action::run); - } - - @Test - void testGet_unauthorized() { - action.registrarAccessor = - AuthenticatedRegistrarAccessor.createForTesting(ImmutableSetMultimap.of()); - action.run(); - assertThat(response.getPayload()).contains("

You need permission

"); - assertThat(response.getPayload()).contains("gtag('config', 'sampleId')"); - } - - @Test - void testPost_authorized() { - action.clientId = Optional.of("myclientid"); - action.email = Optional.of("contact@registry.example"); - action.method = Method.POST; - action.run(); - - // We just check some samples to make sure OteAccountBuilder was called successfully. We aren't - // checking that all the entities are there or that they have the correct values. - assertThat(loadByRegistrarId("myclientid-3")).isPresent(); - assertThat(Tld.get("myclientid-ga")).isNotNull(); - verifyUser("myclientid-5", "contact@registry.example"); - assertThat(response.getPayload()) - .contains("

OT&E successfully created for registrar myclientid!

"); - ArgumentCaptor contentCaptor = ArgumentCaptor.forClass(EmailMessage.class); - verify(gmailClient).sendEmail(contentCaptor.capture()); - EmailMessage emailMessage = contentCaptor.getValue(); - assertThat(emailMessage.subject()) - .isEqualTo("OT&E for registrar myclientid created in unittest"); - assertThat(emailMessage.body()) - .isEqualTo( - """ - The following entities were created in unittest by TestUserId: - Registrar myclientid-1 with access to TLD myclientid-sunrise - Registrar myclientid-3 with access to TLD myclientid-ga - Registrar myclientid-4 with access to TLD myclientid-ga - Registrar myclientid-5 with access to TLD myclientid-eap - Gave user contact@registry.example web access to these Registrars - """); - assertThat(response.getPayload()).contains("gtag('config', 'sampleId')"); - verifyIapPermission( - "contact@registry.example", action.maybeGroupEmailAddress, cloudTasksHelper, iamClient); - } - - @Test - void testPost_authorized_noConsoleGroup() { - action.maybeGroupEmailAddress = Optional.empty(); - testPost_authorized(); - } - - @Test - void testPost_authorized_setPassword() { - action.clientId = Optional.of("myclientid"); - action.email = Optional.of("contact@registry.example"); - action.optionalPassword = Optional.of("SomePassword"); - action.method = Method.POST; - action.run(); - - // We just check some samples to make sure OteAccountBuilder was called successfully. We aren't - // checking that all the entities are there or that they have the correct values. - assertThat(loadByRegistrarId("myclientid-4").get().verifyPassword("SomePassword")).isTrue(); - assertThat(response.getPayload()) - .contains("

OT&E successfully created for registrar myclientid!

"); - assertThat(response.getPayload()) - .contains("SomePassword"); - } - - @Test - void testPost_unauthorized() { - action.registrarAccessor = - AuthenticatedRegistrarAccessor.createForTesting(ImmutableSetMultimap.of()); - action.clientId = Optional.of("myclientid"); - action.email = Optional.of("contact@registry.example"); - action.method = Method.POST; - action.run(); - assertThat(response.getPayload()).contains("

You need permission

"); - assertThat(response.getPayload()).contains("gtag('config', 'sampleId')"); - } -} diff --git a/core/src/test/java/google/registry/ui/server/registrar/ConsoleRegistrarCreatorActionTest.java b/core/src/test/java/google/registry/ui/server/registrar/ConsoleRegistrarCreatorActionTest.java deleted file mode 100644 index 44100d58a..000000000 --- a/core/src/test/java/google/registry/ui/server/registrar/ConsoleRegistrarCreatorActionTest.java +++ /dev/null @@ -1,465 +0,0 @@ -// Copyright 2018 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.ui.server.registrar; - -import static com.google.common.truth.Truth.assertThat; -import static google.registry.model.OteAccountBuilderTest.verifyIapPermission; -import static google.registry.model.OteAccountBuilderTest.verifyUser; -import static google.registry.model.registrar.Registrar.loadByRegistrarId; -import static google.registry.testing.DatabaseHelper.persistPremiumList; -import static jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; -import static org.joda.money.CurrencyUnit.USD; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSetMultimap; -import google.registry.groups.GmailClient; -import google.registry.model.console.User; -import google.registry.model.console.UserRoles; -import google.registry.model.registrar.Registrar; -import google.registry.model.registrar.RegistrarAddress; -import google.registry.persistence.transaction.JpaTestExtensions; -import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension; -import google.registry.request.Action.Method; -import google.registry.request.auth.AuthResult; -import google.registry.request.auth.AuthenticatedRegistrarAccessor; -import google.registry.security.XsrfTokenManager; -import google.registry.testing.CloudTasksHelper; -import google.registry.testing.DeterministicStringGenerator; -import google.registry.testing.FakeClock; -import google.registry.testing.FakeResponse; -import google.registry.testing.SystemPropertyExtension; -import google.registry.tools.IamClient; -import google.registry.ui.server.SendEmailUtils; -import google.registry.util.EmailMessage; -import google.registry.util.RegistryEnvironment; -import jakarta.servlet.http.HttpServletRequest; -import java.util.Optional; -import org.joda.money.CurrencyUnit; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(MockitoExtension.class) -final class ConsoleRegistrarCreatorActionTest { - - @RegisterExtension - final JpaIntegrationTestExtension jpa = - new JpaTestExtensions.Builder().buildIntegrationTestExtension(); - - @RegisterExtension - @Order(Integer.MAX_VALUE) - final SystemPropertyExtension systemPropertyExtension = new SystemPropertyExtension(); - - private final FakeResponse response = new FakeResponse(); - private final ConsoleRegistrarCreatorAction action = new ConsoleRegistrarCreatorAction(); - private final User user = - new User.Builder() - .setEmailAddress("marla.singer@example.com") - .setUserRoles(new UserRoles()) - .build(); - private final CloudTasksHelper cloudTasksHelper = new CloudTasksHelper(); - private final IamClient iamClient = mock(IamClient.class); - - @Mock HttpServletRequest request; - @Mock GmailClient gmailClient; - - @BeforeEach - void beforeEach() throws Exception { - persistPremiumList("default_sandbox_list", USD, "sandbox,USD 1000"); - - action.req = request; - action.method = Method.GET; - action.response = response; - action.registrarAccessor = - AuthenticatedRegistrarAccessor.createForTesting( - ImmutableSetMultimap.of("unused", AuthenticatedRegistrarAccessor.Role.ADMIN)); - action.xsrfTokenManager = new XsrfTokenManager(new FakeClock()); - action.authResult = AuthResult.createUser(user); - action.sendEmailUtils = - new SendEmailUtils( - ImmutableList.of("notification@test.example", "notification2@test.example"), - gmailClient); - action.logoFilename = "logo.png"; - action.productName = "Nomulus"; - - action.clientId = Optional.empty(); - action.name = Optional.empty(); - action.billingAccount = Optional.empty(); - action.ianaId = Optional.empty(); - action.referralEmail = Optional.empty(); - action.driveId = Optional.empty(); - action.consoleUserEmail = Optional.empty(); - - action.street1 = Optional.empty(); - action.optionalStreet2 = Optional.empty(); - action.optionalStreet3 = Optional.empty(); - action.city = Optional.empty(); - action.optionalState = Optional.empty(); - action.optionalZip = Optional.empty(); - action.countryCode = Optional.empty(); - - action.optionalPassword = Optional.empty(); - action.optionalPasscode = Optional.empty(); - - action.passwordGenerator = new DeterministicStringGenerator("abcdefghijklmnopqrstuvwxyz"); - action.passcodeGenerator = new DeterministicStringGenerator("314159265"); - - action.analyticsConfig = ImmutableMap.of("googleAnalyticsId", "sampleId"); - - action.maybeGroupEmailAddress = Optional.of("group@example.com"); - action.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils(); - action.iamClient = iamClient; - } - - @Test - void testNoUser_unauthroized() { - action.authResult = AuthResult.NOT_AUTHENTICATED; - action.run(); - assertThat(response.getStatus()).isEqualTo(SC_UNAUTHORIZED); - } - - @Test - void testGet_authorized() { - action.run(); - assertThat(response.getPayload()).contains("

Create Registrar

"); - assertThat(response.getPayload()).contains("gtag('config', 'sampleId')"); - } - - @Test - void testGet_authorized_onProduction() { - RegistryEnvironment.PRODUCTION.setup(systemPropertyExtension); - action.run(); - assertThat(response.getPayload()).contains("

Create Registrar

"); - assertThat(response.getPayload()).contains("gtag('config', 'sampleId')"); - } - - @Test - void testGet_unauthorized() { - action.registrarAccessor = - AuthenticatedRegistrarAccessor.createForTesting(ImmutableSetMultimap.of()); - action.run(); - assertThat(response.getPayload()).contains("

You need permission

"); - assertThat(response.getPayload()).contains("gtag('config', 'sampleId')"); - } - - void runTestPost_authorized_minimalAddress() { - action.clientId = Optional.of("myclientid"); - action.name = Optional.of("registrar name"); - action.billingAccount = Optional.of("USD=billing-account"); - action.ianaId = Optional.of(12321); - action.referralEmail = Optional.of("icann@example.com"); - action.driveId = Optional.of("drive-id"); - action.consoleUserEmail = Optional.of("myclientid@registry.example"); - - action.street1 = Optional.of("my street"); - action.city = Optional.of("my city"); - action.countryCode = Optional.of("CC"); - - - action.method = Method.POST; - action.run(); - - assertThat(response.getPayload()) - .contains("

Successfully created Registrar myclientid

"); - - ArgumentCaptor contentCaptor = ArgumentCaptor.forClass(EmailMessage.class); - verify(gmailClient).sendEmail(contentCaptor.capture()); - EmailMessage emailMessage = contentCaptor.getValue(); - assertThat(emailMessage.subject()).isEqualTo("Registrar myclientid created in unittest"); - assertThat(emailMessage.body()) - .isEqualTo( - """ - The following registrar was created in unittest by TestUserId: - clientId: myclientid - name: registrar name - billingAccount: USD=billing-account - ianaId: 12321 - referralEmail: icann@example.com - driveId: drive-id - Gave user myclientid@registry.example web access to the registrar - """); - Registrar registrar = loadByRegistrarId("myclientid").orElse(null); - assertThat(registrar).isNotNull(); - assertThat(registrar.getRegistrarId()).isEqualTo("myclientid"); - assertThat(registrar.getBillingAccountMap()).containsExactly(USD, "billing-account"); - - assertThat(registrar.getDriveFolderId()).isEqualTo("drive-id"); - assertThat(registrar.getIanaIdentifier()).isEqualTo(12321L); - assertThat(registrar.getIcannReferralEmail()).isEqualTo("icann@example.com"); - assertThat(registrar.getEmailAddress()).isEqualTo("icann@example.com"); - assertThat(registrar.verifyPassword("abcdefghijklmnop")).isTrue(); - assertThat(registrar.getPhonePasscode()).isEqualTo("31415"); - assertThat(registrar.getState()).isEqualTo(Registrar.State.PENDING); - assertThat(registrar.getType()).isEqualTo(Registrar.Type.REAL); - - assertThat(registrar.getLocalizedAddress()).isEqualTo(new RegistrarAddress.Builder() - .setStreet(ImmutableList.of("my street")) - .setCity("my city") - .setCountryCode("CC") - .build()); - - verifyUser("myclientid", "myclientid@registry.example"); - } - - @Test - void testPost_authorized_minimalAddress_consoleUserGroup() { - runTestPost_authorized_minimalAddress(); - verifyIapPermission( - "myclientid@registry.example", action.maybeGroupEmailAddress, cloudTasksHelper, iamClient); - } - - @Test - void testPost_authorized_minimalAddress_NoConsoleUserGroup() { - action.maybeGroupEmailAddress = Optional.empty(); - runTestPost_authorized_minimalAddress(); - verifyIapPermission( - "myclientid@registry.example", action.maybeGroupEmailAddress, cloudTasksHelper, iamClient); - } - - @Test - void testPost_authorized_allAddress() { - action.clientId = Optional.of("myclientid"); - action.name = Optional.of("registrar name"); - action.billingAccount = Optional.of("USD=billing-account"); - action.ianaId = Optional.of(12321); - action.referralEmail = Optional.of("icann@example.com"); - action.driveId = Optional.of("drive-id"); - action.consoleUserEmail = Optional.of("myclientid@registry.example"); - - action.street1 = Optional.of("my street"); - action.optionalStreet2 = Optional.of("more street"); - action.optionalStreet3 = Optional.of("final street"); - action.city = Optional.of("my city"); - action.optionalState = Optional.of("province"); - action.optionalZip = Optional.of("12345-678"); - action.countryCode = Optional.of("CC"); - - - action.method = Method.POST; - action.run(); - - assertThat(response.getPayload()) - .contains("

Successfully created Registrar myclientid

"); - assertThat(response.getPayload()).contains("gtag('config', 'sampleId')"); - - Registrar registrar = loadByRegistrarId("myclientid").orElse(null); - assertThat(registrar).isNotNull(); - assertThat(registrar.getLocalizedAddress()).isEqualTo(new RegistrarAddress.Builder() - .setStreet(ImmutableList.of("my street", "more street", "final street")) - .setCity("my city") - .setState("province") - .setZip("12345-678") - .setCountryCode("CC") - .build()); - } - - @Test - void testPost_authorized_multipleBillingLines() { - action.clientId = Optional.of("myclientid"); - action.name = Optional.of("registrar name"); - action.ianaId = Optional.of(12321); - action.referralEmail = Optional.of("icann@example.com"); - action.driveId = Optional.of("drive-id"); - action.consoleUserEmail = Optional.of("myclientid@registry.example"); - - action.street1 = Optional.of("my street"); - action.city = Optional.of("my city"); - action.countryCode = Optional.of("CC"); - - action.method = Method.POST; - - action.billingAccount = - Optional.of( - """ - JPY=billing-account-yen - Usd = billing-account-usd \r - - \s - eur=billing-account-eur - """); - action.run(); - - assertThat(response.getPayload()) - .contains("

Successfully created Registrar myclientid

"); - - Registrar registrar = loadByRegistrarId("myclientid").orElse(null); - assertThat(registrar).isNotNull(); - assertThat(registrar.getBillingAccountMap()) - .containsExactly( - CurrencyUnit.JPY, - "billing-account-yen", - USD, - "billing-account-usd", - CurrencyUnit.EUR, - "billing-account-eur"); - } - - @Test - void testPost_authorized_repeatingCurrency_fails() { - action.clientId = Optional.of("myclientid"); - action.name = Optional.of("registrar name"); - action.ianaId = Optional.of(12321); - action.referralEmail = Optional.of("icann@example.com"); - action.driveId = Optional.of("drive-id"); - action.consoleUserEmail = Optional.of("myclientid@registry.example"); - - action.street1 = Optional.of("my street"); - action.city = Optional.of("my city"); - action.countryCode = Optional.of("CC"); - - action.method = Method.POST; - - action.billingAccount = - Optional.of( - """ - JPY=billing-account-1 - jpy=billing-account-2 - """); - action.run(); - - assertThat(response.getPayload()) - .contains( - "Failed: Error parsing billing accounts - Multiple entries with same key:" - + " JPY=billing-account-2 and JPY=billing-account-1"); - } - - @Test - void testPost_authorized_badCurrency_fails() { - action.clientId = Optional.of("myclientid"); - action.name = Optional.of("registrar name"); - action.ianaId = Optional.of(12321); - action.referralEmail = Optional.of("icann@example.com"); - action.driveId = Optional.of("drive-id"); - action.consoleUserEmail = Optional.of("myclientid@registry.example"); - - action.street1 = Optional.of("my street"); - action.city = Optional.of("my city"); - action.countryCode = Optional.of("CC"); - - action.method = Method.POST; - - action.billingAccount = - Optional.of( - """ - JPY=billing-account-1 - xyz=billing-account-2 - usd=billing-account-3 - """); - action.run(); - - assertThat(response.getPayload()) - .contains("Failed: Error parsing billing accounts - Unknown currency 'XYZ'"); - } - - @Test - void testPost_authorized_badBillingLine_fails() { - action.clientId = Optional.of("myclientid"); - action.name = Optional.of("registrar name"); - action.ianaId = Optional.of(12321); - action.referralEmail = Optional.of("icann@example.com"); - action.driveId = Optional.of("drive-id"); - action.consoleUserEmail = Optional.of("myclientid@registry.example"); - - action.street1 = Optional.of("my street"); - action.city = Optional.of("my city"); - action.countryCode = Optional.of("CC"); - - action.method = Method.POST; - - action.billingAccount = - Optional.of( - """ - JPY=billing-account-1 - some bad line - usd=billing-account-2 - """); - action.run(); - - assertThat(response.getPayload()) - .contains( - "Failed: Error parsing billing accounts - Can't parse line [some bad line]." - + " The format should be [currency]=[account ID]"); - } - - @Test - void testPost_authorized_setPassword() { - action.clientId = Optional.of("myclientid"); - action.name = Optional.of("registrar name"); - action.billingAccount = Optional.of("USD=billing-account"); - action.ianaId = Optional.of(12321); - action.referralEmail = Optional.of("icann@example.com"); - action.driveId = Optional.of("drive-id"); - action.consoleUserEmail = Optional.of("myclientid@registry.example"); - - action.street1 = Optional.of("my street"); - action.city = Optional.of("my city"); - action.countryCode = Optional.of("CC"); - - action.optionalPassword = Optional.of("SomePassword"); - action.optionalPasscode = Optional.of("10203"); - - action.method = Method.POST; - action.run(); - - assertThat(response.getPayload()) - .contains("

Successfully created Registrar myclientid

"); - - Registrar registrar = loadByRegistrarId("myclientid").orElse(null); - assertThat(registrar).isNotNull(); - assertThat(registrar.verifyPassword("SomePassword")).isTrue(); - assertThat(registrar.getPhonePasscode()).isEqualTo("10203"); - } - - @Test - void testPost_badEmailFails() { - action.clientId = Optional.of("myclientid"); - action.name = Optional.of("registrar name"); - action.billingAccount = Optional.of("USD=billing-account"); - action.ianaId = Optional.of(12321); - action.referralEmail = Optional.of("lolcat"); - action.driveId = Optional.of("drive-id"); - action.consoleUserEmail = Optional.of("myclientid@registry.example"); - - action.street1 = Optional.of("my street"); - action.city = Optional.of("my city"); - action.countryCode = Optional.of("CC"); - - action.method = Method.POST; - action.run(); - - assertThat(response.getPayload()) - .contains("Failed: Provided email lolcat is not a valid email address"); - } - - @Test - void testPost_unauthorized() { - action.registrarAccessor = - AuthenticatedRegistrarAccessor.createForTesting(ImmutableSetMultimap.of()); - action.clientId = Optional.of("myclientid"); - action.consoleUserEmail = Optional.of("myclientid@registry.example"); - action.method = Method.POST; - action.run(); - assertThat(response.getPayload()).contains("

You need permission

"); - assertThat(response.getPayload()).contains("gtag('config', 'sampleId')"); - } -} diff --git a/core/src/test/java/google/registry/ui/server/registrar/ConsoleUiActionTest.java b/core/src/test/java/google/registry/ui/server/registrar/ConsoleUiActionTest.java deleted file mode 100644 index 51d5946b6..000000000 --- a/core/src/test/java/google/registry/ui/server/registrar/ConsoleUiActionTest.java +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2017 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.ui.server.registrar; - -import static com.google.common.truth.Truth.assertThat; -import static com.google.monitoring.metrics.contrib.LongMetricSubject.assertThat; -import static google.registry.request.auth.AuthenticatedRegistrarAccessor.Role.ADMIN; -import static google.registry.request.auth.AuthenticatedRegistrarAccessor.Role.OWNER; -import static jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSetMultimap; -import com.google.common.net.MediaType; -import google.registry.model.console.User; -import google.registry.model.console.UserRoles; -import google.registry.persistence.transaction.JpaTestExtensions; -import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension; -import google.registry.request.Action.Method; -import google.registry.request.auth.AuthResult; -import google.registry.request.auth.AuthenticatedRegistrarAccessor; -import google.registry.security.XsrfTokenManager; -import google.registry.testing.FakeClock; -import google.registry.testing.FakeResponse; -import jakarta.servlet.http.HttpServletRequest; -import java.util.Optional; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -/** Unit tests for {@link ConsoleUiAction}. */ -class ConsoleUiActionTest { - - @RegisterExtension - final JpaIntegrationTestExtension jpa = - new JpaTestExtensions.Builder().buildIntegrationTestExtension(); - - private final HttpServletRequest request = mock(HttpServletRequest.class); - private final FakeResponse response = new FakeResponse(); - private final ConsoleUiAction action = new ConsoleUiAction(); - private final User user = - new User.Builder() - .setEmailAddress("marla.singer@example.com") - .setUserRoles(new UserRoles()) - .build(); - - @BeforeEach - void beforeEach() { - action.enabled = true; - action.logoFilename = "logo.png"; - action.productName = "Nomulus"; - action.integrationEmail = "integration@example.com"; - action.supportEmail = "support@example.com"; - action.announcementsEmail = "announcements@example.com"; - action.supportPhoneNumber = "1 (888) 555 0123"; - action.technicalDocsUrl = "http://example.com/technical-docs"; - action.req = request; - action.response = response; - action.registrarConsoleMetrics = new RegistrarConsoleMetrics(); - action.xsrfTokenManager = new XsrfTokenManager(new FakeClock()); - action.method = Method.GET; - action.paramClientId = Optional.empty(); - action.authResult = AuthResult.createUser(user); - action.analyticsConfig = ImmutableMap.of("googleAnalyticsId", "sampleId"); - - action.registrarAccessor = - AuthenticatedRegistrarAccessor.createForTesting( - ImmutableSetMultimap.of( - "TheRegistrar", OWNER, - "NewRegistrar", OWNER, - "NewRegistrar", ADMIN, - "AdminRegistrar", ADMIN)); - RegistrarConsoleMetrics.consoleRequestMetric.reset(); - when(request.getParameter("noredirect")).thenReturn("true"); - } - - @AfterEach - void afterEach() { - assertThat(RegistrarConsoleMetrics.consoleRequestMetric).hasNoOtherValues(); - } - - void assertMetric(String registrarId, String explicitClientId, String roles, String status) { - assertThat(RegistrarConsoleMetrics.consoleRequestMetric) - .hasValueForLabels(1, registrarId, explicitClientId, roles, status); - RegistrarConsoleMetrics.consoleRequestMetric.reset( - registrarId, explicitClientId, roles, status); - } - - @Test - void testWebPage_redirect() { - when(request.getParameter("noredirect")).thenReturn(null); - action.run(); - assertThat(response.getStatus()).isEqualTo(302); - assertThat(response.getPayload()).isEqualTo("Redirected to /console"); - } - - @Test - void testWebPage_disallowsIframe() { - action.run(); - assertThat(response.getHeaders()).containsEntry("X-Frame-Options", "SAMEORIGIN"); - assertMetric("TheRegistrar", "false", "[OWNER]", "SUCCESS"); - } - - @Test - void testWebPage_setsHtmlUtf8ContentType() { - action.run(); - assertThat(response.getContentType()).isEqualTo(MediaType.HTML_UTF_8); - assertMetric("TheRegistrar", "false", "[OWNER]", "SUCCESS"); - } - - @Test - void testWebPage_containsUserNickname() { - action.run(); - assertThat(response.getPayload()).contains("marla.singer"); - assertMetric("TheRegistrar", "false", "[OWNER]", "SUCCESS"); - } - - @Test - void testWebPage_containsGoogleAnalyticsId() { - action.run(); - assertThat(response.getPayload()).contains("gtag('config', 'sampleId')"); - assertMetric("TheRegistrar", "false", "[OWNER]", "SUCCESS"); - } - - @Test - void testUserHasAccessAsTheRegistrar_showsRegistrarConsole() { - action.run(); - assertThat(response.getPayload()).contains("Registrar Console"); - assertThat(response.getPayload()).contains("reg-content-and-footer"); - assertMetric("TheRegistrar", "false", "[OWNER]", "SUCCESS"); - } - - @Test - void testConsoleDisabled_showsDisabledPage() { - action.enabled = false; - action.run(); - assertThat(response.getPayload()).contains("

Console is disabled

"); - assertThat(response.getPayload()).contains("gtag('config', 'sampleId')"); - } - - @Test - void testUserDoesntHaveAccessToAnyRegistrar_showsWhoAreYouPage() { - action.registrarAccessor = - AuthenticatedRegistrarAccessor.createForTesting(ImmutableSetMultimap.of()); - action.run(); - assertThat(response.getPayload()).contains("

You need permission

"); - assertThat(response.getPayload()).contains("not associated with Nomulus."); - assertThat(response.getPayload()).contains("gtag('config', 'sampleId')"); - assertMetric("", "false", "[]", "FORBIDDEN"); - } - - @Test - void testNoUser_not_logged_in() { - action.authResult = AuthResult.NOT_AUTHENTICATED; - action.run(); - assertThat(response.getStatus()).isEqualTo(SC_UNAUTHORIZED); - } - - @Test - void testSettingClientId_notAllowed_showsNeedPermissionPage() { - // Behaves the same way if fakeRegistrar exists, but we don't have access to it - action.paramClientId = Optional.of("fakeRegistrar"); - action.run(); - assertThat(response.getPayload()).contains("

You need permission

"); - assertThat(response.getPayload()).contains("not associated with the registrar fakeRegistrar."); - assertThat(response.getPayload()).contains("gtag('config', 'sampleId')"); - assertMetric("fakeRegistrar", "true", "[]", "FORBIDDEN"); - } - - @Test - void testSettingClientId_allowed_showsRegistrarConsole() { - action.paramClientId = Optional.of("NewRegistrar"); - action.run(); - assertThat(response.getPayload()).contains("Registrar Console"); - assertThat(response.getPayload()).contains("reg-content-and-footer"); - assertThat(response.getPayload()).contains("gtag('config', 'sampleId')"); - assertMetric("NewRegistrar", "true", "[OWNER, ADMIN]", "SUCCESS"); - } - - @Test - void testUserHasAccessAsTheRegistrar_showsClientIdChooser() { - action.run(); - assertThat(response.getPayload()).contains("