mirror of
https://github.com/google/nomulus
synced 2026-05-20 23:01:53 +00:00
Compare commits
4 Commits
proxy-2024
...
nomulus-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a4abd93dc | ||
|
|
142c910e3b | ||
|
|
c68d54a5ed | ||
|
|
d17188b820 |
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
|
||||
import { Component, ViewChild, effect } from '@angular/core';
|
||||
import { MatPaginator, PageEvent } from '@angular/material/paginator';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
@@ -91,7 +91,10 @@ export class DomainListComponent {
|
||||
loadLocks() {
|
||||
this.registryLockService.retrieveLocks().subscribe({
|
||||
error: (err: HttpErrorResponse) => {
|
||||
this._snackBar.open(err.message);
|
||||
if (err.status !== HttpStatusCode.Forbidden) {
|
||||
// Some users may not have registry lock permissions and that's OK
|
||||
this._snackBar.open(err.message);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import static google.registry.util.PasswordUtils.SALT_SUPPLIER;
|
||||
import static google.registry.util.PasswordUtils.hashPassword;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import com.google.gson.annotations.Expose;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.UpdateAutoTimestampEntity;
|
||||
import google.registry.util.PasswordUtils;
|
||||
@@ -50,12 +51,13 @@ public class UserBase extends UpdateAutoTimestampEntity implements Buildable {
|
||||
private static final long serialVersionUID = 6936728603828566721L;
|
||||
|
||||
/** Email address of the user in question. */
|
||||
@Transient String emailAddress;
|
||||
@Transient @Expose String emailAddress;
|
||||
|
||||
/** Optional external email address to use for registry lock confirmation emails. */
|
||||
@Column String registryLockEmailAddress;
|
||||
|
||||
/** Roles (which grant permissions) associated with this user. */
|
||||
@Expose
|
||||
@Column(nullable = false)
|
||||
UserRoles userRoles;
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gson.annotations.Expose;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.persistence.converter.RegistrarToRoleMapUserType;
|
||||
@@ -53,6 +54,7 @@ public class UserRoles extends ImmutableObject implements Buildable {
|
||||
private GlobalRole globalRole = GlobalRole.NONE;
|
||||
|
||||
/** Any per-registrar roles that this user may have. */
|
||||
@Expose
|
||||
@Type(RegistrarToRoleMapUserType.class)
|
||||
private Map<String, RegistrarRole> registrarRoles = ImmutableMap.of();
|
||||
|
||||
|
||||
@@ -119,6 +119,7 @@ import google.registry.ui.server.console.ConsoleRegistryLockAction;
|
||||
import google.registry.ui.server.console.ConsoleRegistryLockVerifyAction;
|
||||
import google.registry.ui.server.console.ConsoleUpdateRegistrarAction;
|
||||
import google.registry.ui.server.console.ConsoleUserDataAction;
|
||||
import google.registry.ui.server.console.ConsoleUsersAction;
|
||||
import google.registry.ui.server.console.RegistrarsAction;
|
||||
import google.registry.ui.server.console.settings.ContactAction;
|
||||
import google.registry.ui.server.console.settings.SecurityAction;
|
||||
@@ -189,6 +190,8 @@ interface RequestComponent {
|
||||
|
||||
ConsoleUserDataAction consoleUserDataAction();
|
||||
|
||||
ConsoleUsersAction consoleUsersAction();
|
||||
|
||||
ConsoleDumDownloadAction consoleDumDownloadAction();
|
||||
|
||||
ContactAction contactAction();
|
||||
|
||||
@@ -35,6 +35,7 @@ import google.registry.ui.server.console.ConsoleRegistryLockAction;
|
||||
import google.registry.ui.server.console.ConsoleRegistryLockVerifyAction;
|
||||
import google.registry.ui.server.console.ConsoleUpdateRegistrarAction;
|
||||
import google.registry.ui.server.console.ConsoleUserDataAction;
|
||||
import google.registry.ui.server.console.ConsoleUsersAction;
|
||||
import google.registry.ui.server.console.RegistrarsAction;
|
||||
import google.registry.ui.server.console.settings.ContactAction;
|
||||
import google.registry.ui.server.console.settings.SecurityAction;
|
||||
@@ -81,6 +82,8 @@ public interface FrontendRequestComponent {
|
||||
|
||||
ConsoleUserDataAction consoleUserDataAction();
|
||||
|
||||
ConsoleUsersAction consoleUsersAction();
|
||||
|
||||
ConsoleDumDownloadAction consoleDumDownloadAction();
|
||||
|
||||
ContactAction contactAction();
|
||||
|
||||
@@ -47,6 +47,7 @@ import google.registry.model.domain.token.AllocationToken.RegistrationBehavior;
|
||||
import google.registry.model.domain.token.AllocationToken.TokenStatus;
|
||||
import google.registry.model.domain.token.AllocationToken.TokenType;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.tools.params.MoneyParameter;
|
||||
import google.registry.tools.params.TransitionListParameter.TokenStatusTransitions;
|
||||
import google.registry.util.CollectionUtils;
|
||||
import google.registry.util.DomainNameUtils;
|
||||
@@ -140,6 +141,17 @@ class GenerateAllocationTokensCommand implements Command {
|
||||
arity = 1)
|
||||
private Boolean discountPremiums;
|
||||
|
||||
@Parameter(
|
||||
names = {"--discount_price"},
|
||||
description =
|
||||
"A discount that allows the setting of promotional prices. This field is different from "
|
||||
+ "{@code discountFraction} because the price set here is treated as the domain "
|
||||
+ "price, versus {@code discountFraction} that applies a fraction discount to the "
|
||||
+ "domain base price. Use CURRENCY PRICE format, example: USD 777.99",
|
||||
converter = MoneyParameter.class,
|
||||
validateWith = MoneyParameter.class)
|
||||
private Money discountPrice;
|
||||
|
||||
@Parameter(
|
||||
names = {"--discount_years"},
|
||||
description = "The number of years the discount applies for. Default is 1, max value is 10.")
|
||||
@@ -233,6 +245,7 @@ class GenerateAllocationTokensCommand implements Command {
|
||||
.collect(toImmutableSet()));
|
||||
Optional.ofNullable(discountFraction).ifPresent(token::setDiscountFraction);
|
||||
Optional.ofNullable(discountPremiums).ifPresent(token::setDiscountPremiums);
|
||||
Optional.ofNullable(discountPrice).ifPresent(token::setDiscountPrice);
|
||||
Optional.ofNullable(discountYears).ifPresent(token::setDiscountYears);
|
||||
Optional.ofNullable(tokenStatusTransitions)
|
||||
.ifPresent(token::setTokenStatusTransitions);
|
||||
|
||||
@@ -34,6 +34,7 @@ import google.registry.model.domain.token.AllocationToken;
|
||||
import google.registry.model.domain.token.AllocationToken.RegistrationBehavior;
|
||||
import google.registry.model.domain.token.AllocationToken.TokenStatus;
|
||||
import google.registry.model.domain.token.AllocationToken.TokenType;
|
||||
import google.registry.tools.params.MoneyParameter;
|
||||
import google.registry.tools.params.StringListParameter;
|
||||
import google.registry.tools.params.TransitionListParameter.TokenStatusTransitions;
|
||||
import java.util.List;
|
||||
@@ -91,6 +92,17 @@ final class UpdateAllocationTokensCommand extends UpdateOrDeleteAllocationTokens
|
||||
arity = 1)
|
||||
private Boolean discountPremiums;
|
||||
|
||||
@Parameter(
|
||||
names = {"--discount_price"},
|
||||
description =
|
||||
"A discount that allows the setting of promotional prices. This field is different from "
|
||||
+ "{@code discountFraction} because the price set here is treated as the domain "
|
||||
+ "price, versus {@code discountFraction} that applies a fraction discount to the "
|
||||
+ "domain base price. Use CURRENCY PRICE format, example: USD 777.99",
|
||||
converter = MoneyParameter.class,
|
||||
validateWith = MoneyParameter.class)
|
||||
private Money discountPrice;
|
||||
|
||||
@Parameter(
|
||||
names = {"-y", "--discount_years"},
|
||||
description = "The number of years the discount applies for. Default is 1, max value is 10.")
|
||||
@@ -203,6 +215,7 @@ final class UpdateAllocationTokensCommand extends UpdateOrDeleteAllocationTokens
|
||||
.collect(toImmutableSet())));
|
||||
Optional.ofNullable(discountFraction).ifPresent(builder::setDiscountFraction);
|
||||
Optional.ofNullable(discountPremiums).ifPresent(builder::setDiscountPremiums);
|
||||
Optional.ofNullable(discountPrice).ifPresent(builder::setDiscountPrice);
|
||||
Optional.ofNullable(discountYears).ifPresent(builder::setDiscountYears);
|
||||
Optional.ofNullable(tokenStatusTransitions).ifPresent(builder::setTokenStatusTransitions);
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
<title>Nomulus</title>
|
||||
<body lang="en-US">
|
||||
If this page doesn't change automatically, please go
|
||||
to <a href="/registrar">https://www.registry.google/registrar</a>
|
||||
to <a href="/console">https://www.registry.google/console</a>
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.ui.server.console;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static google.registry.model.console.RegistrarRole.ACCOUNT_MANAGER;
|
||||
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_FORBIDDEN;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
|
||||
import com.google.api.services.directory.Directory;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gson.Gson;
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.console.UserRoles;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GkeService;
|
||||
import google.registry.request.HttpException.BadRequestException;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.util.StringGenerator;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
@Action(
|
||||
service = Action.GaeService.DEFAULT,
|
||||
gkeService = GkeService.CONSOLE,
|
||||
path = ConsoleUsersAction.PATH,
|
||||
method = {GET, POST},
|
||||
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
|
||||
public class ConsoleUsersAction extends ConsoleApiAction {
|
||||
static final String PATH = "/console-api/users";
|
||||
private static final int PASSWORD_LENGTH = 16;
|
||||
|
||||
private static final Splitter EMAIL_SPLITTER = Splitter.on('@').trimResults();
|
||||
|
||||
private final Gson gson;
|
||||
private final String registrarId;
|
||||
private final Directory directory;
|
||||
private final StringGenerator passwordGenerator;
|
||||
|
||||
@Inject
|
||||
public ConsoleUsersAction(
|
||||
ConsoleApiParams consoleApiParams,
|
||||
Gson gson,
|
||||
Directory directory,
|
||||
@Named("base58StringGenerator") StringGenerator passwordGenerator,
|
||||
@Parameter("registrarId") String registrarId) {
|
||||
super(consoleApiParams);
|
||||
this.gson = gson;
|
||||
this.registrarId = registrarId;
|
||||
this.directory = directory;
|
||||
this.passwordGenerator = passwordGenerator;
|
||||
}
|
||||
|
||||
private static String generateNewEmailAddress(User user, String increment) {
|
||||
List<String> emailParts = EMAIL_SPLITTER.splitToList(user.getEmailAddress());
|
||||
return String.format("%s-%s@%s", emailParts.get(0), increment, emailParts.get(1));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void postHandler(User user) {
|
||||
// Temporary flag while testing
|
||||
if (user.getUserRoles().isAdmin()) {
|
||||
checkPermission(user, registrarId, ConsolePermission.MANAGE_USERS);
|
||||
tm().transact(() -> runInTransaction(user));
|
||||
} else {
|
||||
consoleApiParams.response().setStatus(SC_FORBIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void getHandler(User user) {
|
||||
checkPermission(user, registrarId, ConsolePermission.MANAGE_USERS);
|
||||
List<User> users =
|
||||
getAllUsers().stream()
|
||||
.filter(u -> u.getUserRoles().getRegistrarRoles().containsKey(registrarId))
|
||||
.collect(Collectors.toList());
|
||||
consoleApiParams.response().setPayload(gson.toJson(users));
|
||||
consoleApiParams.response().setStatus(SC_OK);
|
||||
}
|
||||
|
||||
private void runInTransaction(User user) throws IOException {
|
||||
String nextAvailableIncrement =
|
||||
Stream.of("1", "2", "3")
|
||||
.filter(
|
||||
increment ->
|
||||
tm().loadByKeyIfPresent(
|
||||
VKey.create(User.class, generateNewEmailAddress(user, increment)))
|
||||
.isEmpty())
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new BadRequestException("Extra users amount is limited to 3"));
|
||||
|
||||
com.google.api.services.directory.model.User newUser =
|
||||
new com.google.api.services.directory.model.User();
|
||||
newUser.setPassword(passwordGenerator.createString(PASSWORD_LENGTH));
|
||||
newUser.setPrimaryEmail(generateNewEmailAddress(user, nextAvailableIncrement));
|
||||
|
||||
try {
|
||||
directory.users().insert(newUser).execute();
|
||||
} catch (IOException e) {
|
||||
setFailedResponse("Failed to create the user workspace account", SC_INTERNAL_SERVER_ERROR);
|
||||
throw e;
|
||||
}
|
||||
|
||||
UserRoles userRoles =
|
||||
new UserRoles.Builder()
|
||||
.setRegistrarRoles(ImmutableMap.of(registrarId, ACCOUNT_MANAGER))
|
||||
.build();
|
||||
|
||||
User.Builder builder =
|
||||
new User.Builder().setUserRoles(userRoles).setEmailAddress(newUser.getPrimaryEmail());
|
||||
tm().put(builder.build());
|
||||
|
||||
consoleApiParams.response().setStatus(SC_OK);
|
||||
consoleApiParams
|
||||
.response()
|
||||
.setPayload(
|
||||
gson.toJson(
|
||||
ImmutableMap.of(
|
||||
"password", newUser.getPassword(), "email", newUser.getPrimaryEmail())));
|
||||
}
|
||||
|
||||
private ImmutableList<User> getAllUsers() {
|
||||
return tm().transact(
|
||||
() ->
|
||||
tm().loadAllOf(User.class).stream()
|
||||
.filter(u -> !u.getUserRoles().getRegistrarRoles().isEmpty())
|
||||
.collect(toImmutableList()));
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ import static google.registry.request.auth.AuthenticatedRegistrarAccessor.Role.A
|
||||
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_PERMANENT_REDIRECT;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
@@ -30,7 +29,6 @@ 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.model.console.GlobalRole;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Parameter;
|
||||
@@ -44,7 +42,6 @@ import google.registry.util.RegistryEnvironment;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/** Action that serves Registrar Console single HTML page (SPA). */
|
||||
@@ -100,6 +97,18 @@ public final class ConsoleUiAction extends HtmlAction {
|
||||
|
||||
@Override
|
||||
public void runAfterLogin(Map<String, Object> 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));
|
||||
|
||||
@@ -121,21 +130,6 @@ public final class ConsoleUiAction extends HtmlAction {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set permanent redirect to the new console for tech support
|
||||
if (isNullOrEmpty(req.getParameter("redirect"))
|
||||
&& Stream.of(GlobalRole.SUPPORT_LEAD, GlobalRole.SUPPORT_AGENT)
|
||||
.anyMatch(
|
||||
globalRole ->
|
||||
globalRole.equals(authResult.user().get().getUserRoles().getGlobalRole()))) {
|
||||
response.setStatus(SC_PERMANENT_REDIRECT);
|
||||
try {
|
||||
response.sendRedirect("/console");
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
ImmutableSetMultimap<String, Role> roleMap = registrarAccessor.getAllRegistrarIdsWithRoles();
|
||||
soyMapData.put("allClientIds", roleMap.keySet());
|
||||
soyMapData.put("environment", RegistryEnvironment.get().toString());
|
||||
|
||||
@@ -164,6 +164,48 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_promotionToken_withDiscountPrice() throws Exception {
|
||||
DateTime promoStart = DateTime.now(UTC);
|
||||
DateTime promoEnd = promoStart.plusMonths(1);
|
||||
runCommand(
|
||||
"--number",
|
||||
"1",
|
||||
"--prefix",
|
||||
"promo",
|
||||
"--type",
|
||||
"UNLIMITED_USE",
|
||||
"--allowed_client_ids",
|
||||
"TheRegistrar,NewRegistrar",
|
||||
"--allowed_tlds",
|
||||
"tld,example",
|
||||
"--allowed_epp_actions",
|
||||
"CREATE,RENEW",
|
||||
"--discount_price",
|
||||
"USD 3",
|
||||
"--discount_years",
|
||||
"6",
|
||||
"--token_status_transitions",
|
||||
String.format("%s=NOT_STARTED,%s=VALID,%s=ENDED", START_OF_TIME, promoStart, promoEnd));
|
||||
assertAllocationTokens(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("promo123456789ABCDEFG")
|
||||
.setTokenType(UNLIMITED_USE)
|
||||
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar", "NewRegistrar"))
|
||||
.setAllowedTlds(ImmutableSet.of("tld", "example"))
|
||||
.setAllowedEppActions(ImmutableSet.of(CommandName.CREATE, CommandName.RENEW))
|
||||
.setDiscountPrice(Money.of(CurrencyUnit.USD, 3))
|
||||
.setDiscountPremiums(false)
|
||||
.setDiscountYears(6)
|
||||
.setTokenStatusTransitions(
|
||||
ImmutableSortedMap.<DateTime, TokenStatus>naturalOrder()
|
||||
.put(START_OF_TIME, TokenStatus.NOT_STARTED)
|
||||
.put(promoStart, TokenStatus.VALID)
|
||||
.put(promoEnd, TokenStatus.ENDED)
|
||||
.build())
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_specifyTokens() throws Exception {
|
||||
runCommand("--tokens", "foobar,foobaz");
|
||||
|
||||
@@ -141,6 +141,16 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
|
||||
assertThat(reloadResource(token).getDiscountFraction()).isEqualTo(0.15);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdateDiscountPrice() throws Exception {
|
||||
AllocationToken token =
|
||||
persistResource(
|
||||
builderWithPromo().setDiscountPrice(Money.of(CurrencyUnit.USD, 10)).build());
|
||||
runCommandForced("--prefix", "token", "--discount_price", "USD 2.15");
|
||||
assertThat(reloadResource(token).getDiscountPrice().get())
|
||||
.isEqualTo(Money.of(CurrencyUnit.USD, 2.15));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdateDiscountPremiums() throws Exception {
|
||||
AllocationToken token =
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.ui.server.console;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.api.services.directory.Directory;
|
||||
import com.google.api.services.directory.Directory.Users;
|
||||
import com.google.api.services.directory.Directory.Users.Insert;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gson.Gson;
|
||||
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.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.request.RequestModule;
|
||||
import google.registry.request.auth.AuthResult;
|
||||
import google.registry.testing.ConsoleApiParamsUtils;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
import google.registry.testing.DeterministicStringGenerator;
|
||||
import google.registry.testing.FakeResponse;
|
||||
import google.registry.util.StringGenerator;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
class ConsoleUsersActionTest {
|
||||
|
||||
private static final Gson GSON = RequestModule.provideGson();
|
||||
|
||||
private final Directory directory = mock(Directory.class);
|
||||
private final Users users = mock(Users.class);
|
||||
private final Insert insert = mock(Insert.class);
|
||||
|
||||
private StringGenerator passwordGenerator =
|
||||
new DeterministicStringGenerator("abcdefghijklmnopqrstuvwxyz");
|
||||
|
||||
private ConsoleApiParams consoleApiParams;
|
||||
|
||||
@RegisterExtension
|
||||
final JpaTestExtensions.JpaIntegrationTestExtension jpa =
|
||||
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
User dbUser1 =
|
||||
new User.Builder()
|
||||
.setEmailAddress("test1@test.com")
|
||||
.setUserRoles(
|
||||
new UserRoles()
|
||||
.asBuilder()
|
||||
.setRegistrarRoles(
|
||||
ImmutableMap.of("TheRegistrar", RegistrarRole.PRIMARY_CONTACT))
|
||||
.build())
|
||||
.build();
|
||||
User dbUser2 =
|
||||
new User.Builder()
|
||||
.setEmailAddress("test2@test.com")
|
||||
.setUserRoles(
|
||||
new UserRoles()
|
||||
.asBuilder()
|
||||
.setRegistrarRoles(
|
||||
ImmutableMap.of("TheRegistrar", RegistrarRole.PRIMARY_CONTACT))
|
||||
.build())
|
||||
.build();
|
||||
User dbUser3 =
|
||||
new User.Builder()
|
||||
.setEmailAddress("test3@test.com")
|
||||
.setUserRoles(
|
||||
new UserRoles()
|
||||
.asBuilder()
|
||||
.setRegistrarRoles(
|
||||
ImmutableMap.of("NewRegistrar", RegistrarRole.PRIMARY_CONTACT))
|
||||
.build())
|
||||
.build();
|
||||
DatabaseHelper.persistResources(ImmutableList.of(dbUser1, dbUser2, dbUser3));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_registrarAccess() throws IOException {
|
||||
UserRoles userRoles =
|
||||
new UserRoles.Builder()
|
||||
.setGlobalRole(GlobalRole.NONE)
|
||||
.setIsAdmin(false)
|
||||
.setRegistrarRoles(ImmutableMap.of("TheRegistrar", RegistrarRole.PRIMARY_CONTACT))
|
||||
.build();
|
||||
|
||||
User user =
|
||||
new User.Builder().setEmailAddress("email@email.com").setUserRoles(userRoles).build();
|
||||
|
||||
AuthResult authResult = AuthResult.createUser(user);
|
||||
ConsoleUsersAction action =
|
||||
createAction(Optional.of(ConsoleApiParamsUtils.createFake(authResult)), Optional.of("GET"));
|
||||
action.run();
|
||||
var response = ((FakeResponse) consoleApiParams.response());
|
||||
User[] users = GSON.fromJson(response.getPayload(), User[].class);
|
||||
assertThat(Arrays.stream(users).map(u -> u.getEmailAddress()).collect(Collectors.toList()))
|
||||
.containsExactlyElementsIn(ImmutableList.of("test1@test.com", "test2@test.com"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_noPermission() throws IOException {
|
||||
UserRoles userRoles =
|
||||
new UserRoles.Builder()
|
||||
.setGlobalRole(GlobalRole.NONE)
|
||||
.setIsAdmin(false)
|
||||
.setRegistrarRoles(ImmutableMap.of("TheRegistrar", RegistrarRole.ACCOUNT_MANAGER))
|
||||
.build();
|
||||
|
||||
User user =
|
||||
new User.Builder().setEmailAddress("email@email.com").setUserRoles(userRoles).build();
|
||||
|
||||
AuthResult authResult = AuthResult.createUser(user);
|
||||
ConsoleUsersAction action =
|
||||
createAction(Optional.of(ConsoleApiParamsUtils.createFake(authResult)), Optional.of("GET"));
|
||||
action.run();
|
||||
var response = ((FakeResponse) consoleApiParams.response());
|
||||
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_createsUser() throws IOException {
|
||||
User user = DatabaseHelper.createAdminUser("email@email.com");
|
||||
AuthResult authResult = AuthResult.createUser(user);
|
||||
ConsoleUsersAction action =
|
||||
createAction(
|
||||
Optional.of(ConsoleApiParamsUtils.createFake(authResult)), Optional.of("POST"));
|
||||
when(directory.users()).thenReturn(users);
|
||||
when(users.insert(any(com.google.api.services.directory.model.User.class))).thenReturn(insert);
|
||||
action.run();
|
||||
var response = ((FakeResponse) consoleApiParams.response());
|
||||
assertThat(response.getStatus()).isEqualTo(SC_OK);
|
||||
assertThat(response.getPayload())
|
||||
.contains("{\"password\":\"abcdefghijklmnop\",\"email\":\"email-1@email.com\"}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_limitedTo3NewUsers() throws IOException {
|
||||
User user = DatabaseHelper.createAdminUser("email@email.com");
|
||||
DatabaseHelper.createAdminUser("email-1@email.com");
|
||||
DatabaseHelper.createAdminUser("email-2@email.com");
|
||||
DatabaseHelper.createAdminUser("email-3@email.com");
|
||||
AuthResult authResult = AuthResult.createUser(user);
|
||||
ConsoleUsersAction action =
|
||||
createAction(
|
||||
Optional.of(ConsoleApiParamsUtils.createFake(authResult)), Optional.of("POST"));
|
||||
when(directory.users()).thenReturn(users);
|
||||
when(users.insert(any(com.google.api.services.directory.model.User.class))).thenReturn(insert);
|
||||
action.run();
|
||||
var response = ((FakeResponse) consoleApiParams.response());
|
||||
assertThat(response.getStatus()).isEqualTo(SC_BAD_REQUEST);
|
||||
assertThat(response.getPayload()).contains("Extra users amount is limited to 3");
|
||||
}
|
||||
|
||||
private ConsoleUsersAction createAction(
|
||||
Optional<ConsoleApiParams> maybeConsoleApiParams, Optional<String> method)
|
||||
throws IOException {
|
||||
consoleApiParams =
|
||||
maybeConsoleApiParams.orElseGet(
|
||||
() -> ConsoleApiParamsUtils.createFake(AuthResult.NOT_AUTHENTICATED));
|
||||
when(consoleApiParams.request().getMethod()).thenReturn(method.orElse("GET"));
|
||||
return new ConsoleUsersAction(
|
||||
consoleApiParams, GSON, directory, passwordGenerator, "TheRegistrar");
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import static google.registry.request.auth.AuthenticatedRegistrarAccessor.Role.A
|
||||
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;
|
||||
@@ -84,6 +85,7 @@ class ConsoleUiActionTest {
|
||||
"NewRegistrar", ADMIN,
|
||||
"AdminRegistrar", ADMIN));
|
||||
RegistrarConsoleMetrics.consoleRequestMetric.reset();
|
||||
when(request.getParameter("noredirect")).thenReturn("true");
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
@@ -98,6 +100,14 @@ class ConsoleUiActionTest {
|
||||
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();
|
||||
|
||||
@@ -43,12 +43,14 @@ import google.registry.testing.DatabaseHelper;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.junitpioneer.jupiter.RetryingTest;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.Dimension;
|
||||
|
||||
/** Registrar Console Screenshot Differ tests. */
|
||||
@Disabled
|
||||
class RegistrarConsoleScreenshotTest extends WebDriverTestCase {
|
||||
|
||||
@RegisterExtension
|
||||
|
||||
@@ -28,12 +28,14 @@ import google.registry.model.registrar.RegistrarPoc;
|
||||
import google.registry.module.frontend.FrontendServlet;
|
||||
import google.registry.server.RegistryTestServer;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.junitpioneer.jupiter.RetryingTest;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.WebElement;
|
||||
|
||||
/** WebDriver tests for Registrar Console UI. */
|
||||
@Disabled
|
||||
public class RegistrarConsoleWebTest extends WebDriverTestCase {
|
||||
|
||||
@RegisterExtension
|
||||
|
||||
@@ -20,4 +20,5 @@ CONSOLE /console-api/registry-lock-verify ConsoleRegistryLockVerifyAction GET
|
||||
CONSOLE /console-api/settings/contacts ContactAction GET,POST n USER PUBLIC
|
||||
CONSOLE /console-api/settings/security SecurityAction POST n USER PUBLIC
|
||||
CONSOLE /console-api/settings/whois-fields WhoisRegistrarFieldsAction POST n USER PUBLIC
|
||||
CONSOLE /console-api/userdata ConsoleUserDataAction GET n USER PUBLIC
|
||||
CONSOLE /console-api/userdata ConsoleUserDataAction GET n USER PUBLIC
|
||||
CONSOLE /console-api/users ConsoleUsersAction GET,POST n USER PUBLIC
|
||||
@@ -78,4 +78,5 @@ CONSOLE /console-api/registry-lock-verify ConsoleRegistryLockV
|
||||
CONSOLE /console-api/settings/contacts ContactAction GET,POST n USER PUBLIC
|
||||
CONSOLE /console-api/settings/security SecurityAction POST n USER PUBLIC
|
||||
CONSOLE /console-api/settings/whois-fields WhoisRegistrarFieldsAction POST n USER PUBLIC
|
||||
CONSOLE /console-api/userdata ConsoleUserDataAction GET n USER PUBLIC
|
||||
CONSOLE /console-api/userdata ConsoleUserDataAction GET n USER PUBLIC
|
||||
CONSOLE /console-api/users ConsoleUsersAction GET,POST n USER PUBLIC
|
||||
|
||||
Reference in New Issue
Block a user