Compare commits

..
Author SHA1 Message Date
gbrodmanandGitHub 92b684d7ec Only forbid DISABLED registrars in the console (#3188)
PENDING registrars may just need to add contacts etc in order to be
enabled, and they should be able to use the console to do so.
2026-07-30 20:57:43 +00:00
Pavlo TkachandGitHub 49cecf6776 Update cloud build release with new epp service (#3189) 2026-07-30 19:49:40 +00:00
gbrodmanandGitHub 4cc3fc9cd2 Use native query for registrar-users console user query (#3182)
This means we don't have to load all users and filter them out later. In
practice this doesn't matter because the user table is relatively small
(a few hundred) but 1. who knows what can happen in the future? 2. this
makes the code analysis tools happier
2026-07-29 22:30:07 +00:00
Juan CelhayandGitHub 74f441765e Fix image replacement in cd (#3186) 2026-07-29 19:37:12 +00:00
gbrodmanandGitHub 0ce83c8b2d Change syncRemoteTask scheduled task to be a POST (#3185)
Turns out before we were using GET for everything. The action is, and
should remain, a POST on the back end so let's call that.
2026-07-29 18:52:59 +00:00
10 changed files with 83 additions and 41 deletions
@@ -326,6 +326,7 @@
<task>
<url><![CDATA[/_dr/task/syncRemoteCache]]></url>
<name>syncRemoteCache</name>
<method>POST</method>
<description>
Syncs remote (Valkey/Redis) EPP resource caches with changes made recently.
</description>
@@ -113,11 +113,10 @@ public abstract class ConsoleApiAction implements Runnable {
() ->
new ConsolePermissionForbiddenException(
String.format("Registrar %s does not exist", registrarId)));
if (!registrar.isLive()) {
if (registrar.getState().equals(Registrar.State.DISABLED)) {
throw new ConsolePermissionForbiddenException(
String.format(
"Permission forbidden because registrar %s is currently %s",
registrarId, registrar.getState()));
"Permission forbidden because registrar %s is currently DISABLED", registrarId));
}
if (!user.getUserRoles().hasPermission(registrarId, permission)) {
throw new ConsolePermissionForbiddenException(
@@ -15,7 +15,6 @@
package google.registry.ui.server.console;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.model.console.RegistrarRole.ACCOUNT_MANAGER;
import static google.registry.model.console.RegistrarRole.TECH_CONTACT;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
@@ -357,12 +356,17 @@ public class ConsoleUsersAction extends ConsoleApiAction {
return updatedUser;
}
@SuppressWarnings("unchecked")
private ImmutableList<User> getAllRegistrarUsers(String registrarId) {
return tm().transact(
() ->
tm().loadAllOf(User.class).stream()
.filter(u -> u.getUserRoles().getRegistrarRoles().containsKey(registrarId))
.collect(toImmutableList()));
ImmutableList.copyOf(
tm().getEntityManager()
.createNativeQuery(
"SELECT * FROM \"User\" WHERE exist(registrar_roles, :registrarId)",
User.class)
.setParameter("registrarId", registrarId)
.getResultList()));
}
/** Maps a request role string to a RegistrarRole, using ACCOUNT_MANAGER as the default. */
@@ -15,6 +15,9 @@
package google.registry.ui.server.console;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.DatabaseHelper.loadByKey;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
import static google.registry.testing.DatabaseHelper.persistResource;
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static jakarta.servlet.http.HttpServletResponse.SC_CREATED;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
@@ -36,6 +39,7 @@ import google.registry.model.console.GlobalRole;
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.persistence.VKey;
import google.registry.request.auth.AuthResult;
import google.registry.testing.CloudTasksHelper;
@@ -126,6 +130,23 @@ class ConsoleUsersActionTest extends ConsoleActionBaseTestCase {
"[{\"emailAddress\":\"test1@test.com\",\"role\":\"PRIMARY_CONTACT\"},{\"emailAddress\":\"test2@test.com\",\"role\":\"PRIMARY_CONTACT\"}]");
}
@Test
void testSuccess_pendingRegistrar() throws Exception {
persistResource(
loadRegistrar("TheRegistrar").asBuilder().setState(Registrar.State.PENDING).build());
AuthResult authResult =
AuthResult.createUser(loadByKey(VKey.create(User.class, "test1@test.com")));
ConsoleUsersAction action =
createAction(
Optional.of(ConsoleApiParamsUtils.createFake(authResult)),
Optional.of("GET"),
Optional.empty());
action.run();
assertThat(response.getStatus()).isEqualTo(SC_OK);
assertThat(response.getPayload()).contains("test1@test.com");
}
@Test
void testFailure_noPermission() throws IOException {
UserRoles userRoles =
@@ -205,7 +226,7 @@ class ConsoleUsersActionTest extends ConsoleActionBaseTestCase {
@Test
void testFailure_noPermissionToDeleteUser() throws IOException {
User user1 = DatabaseHelper.loadByKey(VKey.create(User.class, "test1@test.com"));
User user1 = loadByKey(VKey.create(User.class, "test1@test.com"));
AuthResult authResult =
AuthResult.createUser(
user1
@@ -248,7 +269,7 @@ class ConsoleUsersActionTest extends ConsoleActionBaseTestCase {
@Test
void testSuccess_deletesUser_nonConsoleMintedAddress_skipsWorkspaceAccountDeletion()
throws IOException {
User user1 = DatabaseHelper.loadByKey(VKey.create(User.class, "test1@test.com"));
User user1 = loadByKey(VKey.create(User.class, "test1@test.com"));
AuthResult authResult =
AuthResult.createUser(
user1
@@ -274,7 +295,7 @@ class ConsoleUsersActionTest extends ConsoleActionBaseTestCase {
@Test
void testSuccess_deletesUser_consoleMintedAddress_deletesWorkspaceAccount() throws IOException {
User user1 = DatabaseHelper.loadByKey(VKey.create(User.class, "test1@test.com"));
User user1 = loadByKey(VKey.create(User.class, "test1@test.com"));
AuthResult authResult =
AuthResult.createUser(
user1
@@ -282,7 +303,7 @@ class ConsoleUsersActionTest extends ConsoleActionBaseTestCase {
.setUserRoles(user1.getUserRoles().asBuilder().setIsAdmin(true).build())
.build());
String mintedEmail = "abc.TheRegistrar@email.com";
DatabaseHelper.persistResource(
persistResource(
new User.Builder()
.setEmailAddress(mintedEmail)
.setUserRoles(
@@ -311,14 +332,14 @@ class ConsoleUsersActionTest extends ConsoleActionBaseTestCase {
@Test
void testSuccess_removesRole() throws IOException {
User user1 = DatabaseHelper.loadByKey(VKey.create(User.class, "test1@test.com"));
User user1 = loadByKey(VKey.create(User.class, "test1@test.com"));
AuthResult authResult =
AuthResult.createUser(
user1
.asBuilder()
.setUserRoles(user1.getUserRoles().asBuilder().setIsAdmin(true).build())
.build());
DatabaseHelper.persistResource(
persistResource(
new User.Builder()
.setEmailAddress("test4@test.com")
.setUserRoles(
@@ -355,7 +376,7 @@ class ConsoleUsersActionTest extends ConsoleActionBaseTestCase {
@Test
void testFailure_limitedTo4UsersPerRegistrar() throws IOException {
User user1 = DatabaseHelper.loadByKey(VKey.create(User.class, "test1@test.com"));
User user1 = loadByKey(VKey.create(User.class, "test1@test.com"));
AuthResult authResult =
AuthResult.createUser(
user1
@@ -395,7 +416,7 @@ class ConsoleUsersActionTest extends ConsoleActionBaseTestCase {
@Test
void testSuccess_updatesUserRole() throws IOException {
User user1 = DatabaseHelper.loadByKey(VKey.create(User.class, "test1@test.com"));
User user1 = loadByKey(VKey.create(User.class, "test1@test.com"));
AuthResult authResult =
AuthResult.createUser(
user1
@@ -404,7 +425,7 @@ class ConsoleUsersActionTest extends ConsoleActionBaseTestCase {
.build());
assertThat(
DatabaseHelper.loadByKey(VKey.create(User.class, "test2@test.com"))
loadByKey(VKey.create(User.class, "test2@test.com"))
.getUserRoles()
.getRegistrarRoles()
.get("TheRegistrar"))
@@ -420,7 +441,7 @@ class ConsoleUsersActionTest extends ConsoleActionBaseTestCase {
action.run();
assertThat(response.getStatus()).isEqualTo(SC_OK);
assertThat(
DatabaseHelper.loadByKey(VKey.create(User.class, "test2@test.com"))
loadByKey(VKey.create(User.class, "test2@test.com"))
.getUserRoles()
.getRegistrarRoles()
.get("TheRegistrar"))
@@ -429,7 +450,7 @@ class ConsoleUsersActionTest extends ConsoleActionBaseTestCase {
@Test
void testFailure_noPermissionToUpdateUser() throws IOException {
User user1 = DatabaseHelper.loadByKey(VKey.create(User.class, "test1@test.com"));
User user1 = loadByKey(VKey.create(User.class, "test1@test.com"));
AuthResult authResult =
AuthResult.createUser(
user1
@@ -461,14 +482,14 @@ class ConsoleUsersActionTest extends ConsoleActionBaseTestCase {
new UserData("test3@test.com", null, RegistrarRole.TECH_CONTACT.name(), null)));
action.run();
assertThat(response.getStatus()).isEqualTo(SC_OK);
User appendedUser = DatabaseHelper.loadByKey(VKey.create(User.class, "test3@test.com"));
User appendedUser = loadByKey(VKey.create(User.class, "test3@test.com"));
assertThat(appendedUser.getUserRoles().getRegistrarRoles().get("TheRegistrar"))
.isEqualTo(RegistrarRole.TECH_CONTACT);
}
@Test
void testFailure_appendUser_crossTenantNoPermission() throws IOException {
User callingUser = DatabaseHelper.loadByKey(VKey.create(User.class, "test1@test.com"));
User callingUser = loadByKey(VKey.create(User.class, "test1@test.com"));
AuthResult authResult = AuthResult.createUser(callingUser);
ConsoleUsersAction action =
createAction(
@@ -483,7 +504,7 @@ class ConsoleUsersActionTest extends ConsoleActionBaseTestCase {
@Test
void testSuccess_appendUser_crossTenantWithPermission() throws IOException {
User callingUser =
DatabaseHelper.persistResource(
persistResource(
new User.Builder()
.setEmailAddress("multitenant@test.com")
.setUserRoles(
@@ -506,7 +527,7 @@ class ConsoleUsersActionTest extends ConsoleActionBaseTestCase {
new UserData("test3@test.com", null, RegistrarRole.TECH_CONTACT.name(), null)));
action.run();
assertThat(response.getStatus()).isEqualTo(SC_OK);
User appendedUser = DatabaseHelper.loadByKey(VKey.create(User.class, "test3@test.com"));
User appendedUser = loadByKey(VKey.create(User.class, "test3@test.com"));
assertThat(appendedUser.getUserRoles().getRegistrarRoles().get("TheRegistrar"))
.isEqualTo(RegistrarRole.TECH_CONTACT);
}
@@ -515,7 +536,7 @@ class ConsoleUsersActionTest extends ConsoleActionBaseTestCase {
void testFailure_appendUser_globalAdmin() throws IOException {
User user = DatabaseHelper.createAdminUser("email@email.com");
AuthResult authResult = AuthResult.createUser(user);
DatabaseHelper.persistResource(
persistResource(
new User.Builder()
.setEmailAddress("globaladmin@test.com")
.setUserRoles(
@@ -539,7 +560,7 @@ class ConsoleUsersActionTest extends ConsoleActionBaseTestCase {
void testFailure_appendUser_globalRole() throws IOException {
User user = DatabaseHelper.createAdminUser("email@email.com");
AuthResult authResult = AuthResult.createUser(user);
DatabaseHelper.persistResource(
persistResource(
new User.Builder()
.setEmailAddress("support@test.com")
.setUserRoles(
@@ -566,7 +587,7 @@ class ConsoleUsersActionTest extends ConsoleActionBaseTestCase {
User user = DatabaseHelper.createAdminUser("email@email.com");
AuthResult authResult = AuthResult.createUser(user);
// Historically associated global admin
DatabaseHelper.persistResource(
persistResource(
new User.Builder()
.setEmailAddress("globaladmin@test.com")
.setUserRoles(
@@ -596,7 +617,7 @@ class ConsoleUsersActionTest extends ConsoleActionBaseTestCase {
User user = DatabaseHelper.createAdminUser("email@email.com");
AuthResult authResult = AuthResult.createUser(user);
// Historically associated global admin
DatabaseHelper.persistResource(
persistResource(
new User.Builder()
.setEmailAddress("globaladmin@test.com")
.setUserRoles(
@@ -626,7 +647,7 @@ class ConsoleUsersActionTest extends ConsoleActionBaseTestCase {
User user = DatabaseHelper.createAdminUser("email@email.com");
AuthResult authResult = AuthResult.createUser(user);
// Historically associated user with global role
DatabaseHelper.persistResource(
persistResource(
new User.Builder()
.setEmailAddress("support@test.com")
.setUserRoles(
@@ -251,18 +251,6 @@ public class ConsoleBulkDomainActionTest extends ConsoleActionBaseTestCase {
assertThat(response.getStatus()).isEqualTo(SC_FORBIDDEN);
}
@Test
void testFailure_pendingRegistrar() {
persistResource(
loadRegistrar("TheRegistrar").asBuilder().setState(Registrar.State.PENDING).build());
JsonElement payload =
GSON.toJsonTree(
ImmutableMap.of("domainList", ImmutableList.of("example.tld"), "reason", "test"));
ConsoleBulkDomainAction action = createAction("DELETE", payload);
action.run();
assertThat(response.getStatus()).isEqualTo(SC_FORBIDDEN);
}
@Test
void testFailure_nonexistentRegistrar() {
JsonElement payload =
@@ -71,6 +71,7 @@ type Task struct {
Timeout string `xml:"timeout"`
Schedule string `xml:"schedule"`
Name string `xml:"name"`
Method string `xml:"method"`
}
type QueuesSyncManager struct {
@@ -191,6 +192,11 @@ func (manager TasksSyncManager) getArgs(task Task, operationType string) []strin
var uri string
uri = fmt.Sprintf("https://%s.%s%s", service, baseDomain, strings.TrimSpace(task.URL))
method := "get"
if task.Method != "" {
method = strings.ToLower(task.Method)
}
args := []string{
"--project", projectName,
"scheduler", "jobs", operationType,
@@ -199,7 +205,7 @@ func (manager TasksSyncManager) getArgs(task Task, operationType string) []strin
"--schedule", task.Schedule,
"--uri", uri,
"--description", description,
"--http-method", "get",
"--http-method", method,
"--oidc-service-account-email", getCloudSchedulerServiceAccountEmail(),
"--oidc-token-audience", clientId,
}
+1 -1
View File
@@ -200,7 +200,7 @@ steps:
--delivery-pipeline="$pipeline" \
--region="$region" \
--project=${PROJECT_ID} \
--images="nomulus=gcr.io/${PROJECT_ID}/nomulus@${nomulus_digest}" \
--images="gcr.io/${PROJECT_ID}/nomulus=gcr.io/${PROJECT_ID}/nomulus@${nomulus_digest}" \
--source=. \
--skaffold-file=release/clouddeploy/skaffold.yaml \
--deploy-parameters="deployed_image=gcr.io/${PROJECT_ID}/nomulus@${nomulus_digest},base_image=us-docker.pkg.dev/${PROJECT_ID}/gcr.io/nomulus"
+21
View File
@@ -253,6 +253,27 @@ steps:
sed s/SERVICE/${service}-canary/g ./jetty/kubernetes/gateway/nomulus-backend-policy-${env}.yaml \
> ./jetty/kubernetes/gateway/nomulus-backend-policy-${env}-${service}-canary.yaml
done
# Generate manifests for epp-server (which doesn't use HTTP gateway routing)
for env in alpha crash qa sandbox production
do
if [ ${env} == production ]
then
project="domain-registry"
else
project="domain-registry-${env}"
fi
# non-canary
sed s/GCP_PROJECT/${PROJECT_ID}/g ./jetty/kubernetes/nomulus-epp-server.yaml | \
sed s/latest/${TAG_NAME}/g | \
sed s/ENVIRONMENT/${env}/g > ./jetty/kubernetes/nomulus-${env}-epp-server.yaml
# canary
sed s/GCP_PROJECT/${PROJECT_ID}/g ./jetty/kubernetes/nomulus-epp-server.yaml | \
sed s/latest/${TAG_NAME}/g | \
sed s/ENVIRONMENT/${env}/g | \
sed s/epp-server/epp-server-canary/g | \
sed s/EPP-v2-ipv4-main/EPP-v2-ipv4-canary/g | \
sed s/EPP-v2-ipv6-main/EPP-v2-ipv6-canary/g > ./jetty/kubernetes/nomulus-${env}-epp-server-canary.yaml
done
done
# Upload the Gradle binary to GCS if it does not exist and point URL in Gradle wrapper to it.
- name: 'gcr.io/cloud-builders/gcloud'
+1
View File
@@ -6,6 +6,7 @@ metadata:
requireApproval: true
executionConfigs:
- usages:
- PREDEPLOY
- RENDER
- ANALYSIS
- POSTDEPLOY
+1
View File
@@ -5,6 +5,7 @@ metadata:
requireApproval: true
executionConfigs:
- usages:
- PREDEPLOY
- RENDER
- ANALYSIS
- POSTDEPLOY