mirror of
https://github.com/google/nomulus.git
synced 2025-07-10 13:13:28 +02:00
Refactor UI actions to be more reliable (#348)
* Refactor UI actions to be more reliable - Created HtmlAction to handle the nitty-gritty of login and setting up HTML pages - Created a test to verify that all UI actions implement JSON GET, JSON POST, or HTML classes - Move CSS renaming into a utility class * Move logging of request into HtmlAction * Comment and wording in exception * mention JsonGetAction in the comment * JsonGetAction extends Runnable
This commit is contained in:
parent
3a47fa2fe9
commit
dea7dfcf28
9 changed files with 248 additions and 235 deletions
|
@ -19,6 +19,7 @@ import static com.google.common.io.Resources.asCharSource;
|
||||||
import static com.google.common.io.Resources.getResource;
|
import static com.google.common.io.Resources.getResource;
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.base.Joiner;
|
import com.google.common.base.Joiner;
|
||||||
import com.google.common.base.Splitter;
|
import com.google.common.base.Splitter;
|
||||||
import com.google.common.base.Supplier;
|
import com.google.common.base.Supplier;
|
||||||
|
@ -42,6 +43,12 @@ import java.util.Map;
|
||||||
/** Helper methods for rendering Soy templates from Java code. */
|
/** Helper methods for rendering Soy templates from Java code. */
|
||||||
public final class SoyTemplateUtils {
|
public final class SoyTemplateUtils {
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public static final Supplier<SoyCssRenamingMap> CSS_RENAMING_MAP_SUPPLIER =
|
||||||
|
SoyTemplateUtils.createCssRenamingMapSupplier(
|
||||||
|
Resources.getResource("google/registry/ui/css/registrar_bin.css.js"),
|
||||||
|
Resources.getResource("google/registry/ui/css/registrar_dbg.css.js"));
|
||||||
|
|
||||||
/** Returns a memoized supplier containing compiled tofu. */
|
/** Returns a memoized supplier containing compiled tofu. */
|
||||||
public static Supplier<SoyTofu> createTofuSupplier(final SoyFileInfo... soyInfos) {
|
public static Supplier<SoyTofu> createTofuSupplier(final SoyFileInfo... soyInfos) {
|
||||||
return memoize(
|
return memoize(
|
||||||
|
|
|
@ -15,45 +15,30 @@
|
||||||
package google.registry.ui.server.registrar;
|
package google.registry.ui.server.registrar;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
import static com.google.common.net.HttpHeaders.LOCATION;
|
|
||||||
import static com.google.common.net.HttpHeaders.X_FRAME_OPTIONS;
|
|
||||||
import static google.registry.config.RegistryEnvironment.PRODUCTION;
|
import static google.registry.config.RegistryEnvironment.PRODUCTION;
|
||||||
|
import static google.registry.ui.server.SoyTemplateUtils.CSS_RENAMING_MAP_SUPPLIER;
|
||||||
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
|
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
|
||||||
import static javax.servlet.http.HttpServletResponse.SC_MOVED_TEMPORARILY;
|
|
||||||
|
|
||||||
import com.google.appengine.api.users.User;
|
|
||||||
import com.google.appengine.api.users.UserService;
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
|
||||||
import com.google.common.base.Ascii;
|
import com.google.common.base.Ascii;
|
||||||
import com.google.common.base.Supplier;
|
import com.google.common.base.Supplier;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.flogger.FluentLogger;
|
import com.google.common.flogger.FluentLogger;
|
||||||
import com.google.common.io.Resources;
|
|
||||||
import com.google.common.net.MediaType;
|
|
||||||
import com.google.template.soy.shared.SoyCssRenamingMap;
|
|
||||||
import com.google.template.soy.tofu.SoyTofu;
|
import com.google.template.soy.tofu.SoyTofu;
|
||||||
import google.registry.config.RegistryConfig.Config;
|
|
||||||
import google.registry.config.RegistryEnvironment;
|
import google.registry.config.RegistryEnvironment;
|
||||||
import google.registry.model.OteAccountBuilder;
|
import google.registry.model.OteAccountBuilder;
|
||||||
import google.registry.request.Action;
|
import google.registry.request.Action;
|
||||||
import google.registry.request.Action.Method;
|
import google.registry.request.Action.Method;
|
||||||
import google.registry.request.Parameter;
|
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.Auth;
|
||||||
import google.registry.request.auth.AuthResult;
|
|
||||||
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
|
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
|
||||||
import google.registry.security.XsrfTokenManager;
|
|
||||||
import google.registry.ui.server.SendEmailUtils;
|
import google.registry.ui.server.SendEmailUtils;
|
||||||
import google.registry.ui.server.SoyTemplateUtils;
|
import google.registry.ui.server.SoyTemplateUtils;
|
||||||
import google.registry.ui.soy.registrar.OteSetupConsoleSoyInfo;
|
import google.registry.ui.soy.registrar.OteSetupConsoleSoyInfo;
|
||||||
import google.registry.util.StringGenerator;
|
import google.registry.util.StringGenerator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Action that serves OT&E setup web page.
|
* Action that serves OT&E setup web page.
|
||||||
|
@ -69,14 +54,9 @@ import javax.servlet.http.HttpServletRequest;
|
||||||
path = ConsoleOteSetupAction.PATH,
|
path = ConsoleOteSetupAction.PATH,
|
||||||
method = {Method.POST, Method.GET},
|
method = {Method.POST, Method.GET},
|
||||||
auth = Auth.AUTH_PUBLIC)
|
auth = Auth.AUTH_PUBLIC)
|
||||||
public final class ConsoleOteSetupAction implements Runnable {
|
public final class ConsoleOteSetupAction extends HtmlAction {
|
||||||
|
|
||||||
public static final String PATH = "/registrar-ote-setup";
|
public static final String PATH = "/registrar-ote-setup";
|
||||||
@VisibleForTesting // webdriver and screenshot tests need this
|
|
||||||
public static final Supplier<SoyCssRenamingMap> CSS_RENAMING_MAP_SUPPLIER =
|
|
||||||
SoyTemplateUtils.createCssRenamingMapSupplier(
|
|
||||||
Resources.getResource("google/registry/ui/css/registrar_bin.css.js"),
|
|
||||||
Resources.getResource("google/registry/ui/css/registrar_dbg.css.js"));
|
|
||||||
private static final int PASSWORD_LENGTH = 16;
|
private static final int PASSWORD_LENGTH = 16;
|
||||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||||
private static final Supplier<SoyTofu> TOFU_SUPPLIER =
|
private static final Supplier<SoyTofu> TOFU_SUPPLIER =
|
||||||
|
@ -85,27 +65,10 @@ public final class ConsoleOteSetupAction implements Runnable {
|
||||||
google.registry.ui.soy.FormsSoyInfo.getInstance(),
|
google.registry.ui.soy.FormsSoyInfo.getInstance(),
|
||||||
google.registry.ui.soy.AnalyticsSoyInfo.getInstance(),
|
google.registry.ui.soy.AnalyticsSoyInfo.getInstance(),
|
||||||
google.registry.ui.soy.registrar.OteSetupConsoleSoyInfo.getInstance());
|
google.registry.ui.soy.registrar.OteSetupConsoleSoyInfo.getInstance());
|
||||||
@Inject HttpServletRequest req;
|
|
||||||
@Inject @RequestMethod Method method;
|
|
||||||
@Inject Response response;
|
|
||||||
@Inject AuthenticatedRegistrarAccessor registrarAccessor;
|
@Inject AuthenticatedRegistrarAccessor registrarAccessor;
|
||||||
@Inject UserService userService;
|
|
||||||
@Inject XsrfTokenManager xsrfTokenManager;
|
|
||||||
@Inject AuthResult authResult;
|
|
||||||
@Inject SendEmailUtils sendEmailUtils;
|
@Inject SendEmailUtils sendEmailUtils;
|
||||||
|
|
||||||
@Inject
|
|
||||||
@Config("logoFilename")
|
|
||||||
String logoFilename;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
@Config("productName")
|
|
||||||
String productName;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
@Config("analyticsConfig")
|
|
||||||
Map<String, Object> analyticsConfig;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@Named("base58StringGenerator")
|
@Named("base58StringGenerator")
|
||||||
StringGenerator passwordGenerator;
|
StringGenerator passwordGenerator;
|
||||||
|
@ -126,43 +89,9 @@ public final class ConsoleOteSetupAction implements Runnable {
|
||||||
ConsoleOteSetupAction() {}
|
ConsoleOteSetupAction() {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void runAfterLogin(HashMap<String, Object> data) {
|
||||||
response.setHeader(X_FRAME_OPTIONS, "SAMEORIGIN"); // Disallow iframing.
|
|
||||||
response.setHeader("X-Ui-Compatible", "IE=edge"); // Ask IE not to be silly.
|
|
||||||
|
|
||||||
logger.atInfo().log(
|
|
||||||
"User %s is accessing the OT&E setup page. Method= %s",
|
|
||||||
registrarAccessor.userIdForLogging(), method);
|
|
||||||
checkState(
|
checkState(
|
||||||
!RegistryEnvironment.get().equals(PRODUCTION), "Can't create OT&E in prod");
|
!RegistryEnvironment.get().equals(PRODUCTION), "Can't create OT&E in prod");
|
||||||
if (!authResult.userAuthInfo().isPresent()) {
|
|
||||||
response.setStatus(SC_MOVED_TEMPORARILY);
|
|
||||||
String location;
|
|
||||||
try {
|
|
||||||
location = userService.createLoginURL(req.getRequestURI());
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
// UserServiceImpl.createLoginURL() throws IllegalArgumentException if underlying API call
|
|
||||||
// returns an error code of NOT_ALLOWED. createLoginURL() assumes that the error is caused
|
|
||||||
// by an invalid URL. But in fact, the error can also occur if UserService doesn't have any
|
|
||||||
// user information, which happens when the request has been authenticated as internal. In
|
|
||||||
// this case, we want to avoid dying before we can send the redirect, so just redirect to
|
|
||||||
// the root path.
|
|
||||||
location = "/";
|
|
||||||
}
|
|
||||||
response.setHeader(LOCATION, location);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
User user = authResult.userAuthInfo().get().user();
|
|
||||||
|
|
||||||
// Using HashMap to allow null values
|
|
||||||
HashMap<String, Object> data = new HashMap<>();
|
|
||||||
data.put("logoFilename", logoFilename);
|
|
||||||
data.put("productName", productName);
|
|
||||||
data.put("username", user.getNickname());
|
|
||||||
data.put("logoutUrl", userService.createLogoutURL(PATH));
|
|
||||||
data.put("xsrfToken", xsrfTokenManager.generateToken(user.getEmail()));
|
|
||||||
data.put("analyticsConfig", analyticsConfig);
|
|
||||||
response.setContentType(MediaType.HTML_UTF_8);
|
|
||||||
|
|
||||||
if (!registrarAccessor.isAdmin()) {
|
if (!registrarAccessor.isAdmin()) {
|
||||||
response.setStatus(SC_FORBIDDEN);
|
response.setStatus(SC_FORBIDDEN);
|
||||||
|
@ -187,6 +116,11 @@ public final class ConsoleOteSetupAction implements Runnable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPath() {
|
||||||
|
return PATH;
|
||||||
|
}
|
||||||
|
|
||||||
private void runPost(HashMap<String, Object> data) {
|
private void runPost(HashMap<String, Object> data) {
|
||||||
try {
|
try {
|
||||||
checkState(clientId.isPresent() && email.isPresent(), "Must supply clientId and email");
|
checkState(clientId.isPresent() && email.isPresent(), "Must supply clientId and email");
|
||||||
|
|
|
@ -18,27 +18,18 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||||
import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
||||||
import static com.google.common.net.HttpHeaders.LOCATION;
|
|
||||||
import static com.google.common.net.HttpHeaders.X_FRAME_OPTIONS;
|
|
||||||
import static google.registry.model.common.GaeUserIdConverter.convertEmailAddressToGaeUserId;
|
import static google.registry.model.common.GaeUserIdConverter.convertEmailAddressToGaeUserId;
|
||||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||||
import static google.registry.model.transaction.TransactionManagerFactory.tm;
|
import static google.registry.model.transaction.TransactionManagerFactory.tm;
|
||||||
|
import static google.registry.ui.server.SoyTemplateUtils.CSS_RENAMING_MAP_SUPPLIER;
|
||||||
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
|
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
|
||||||
import static javax.servlet.http.HttpServletResponse.SC_MOVED_TEMPORARILY;
|
|
||||||
|
|
||||||
import com.google.appengine.api.users.User;
|
|
||||||
import com.google.appengine.api.users.UserService;
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
|
||||||
import com.google.common.base.Ascii;
|
import com.google.common.base.Ascii;
|
||||||
import com.google.common.base.Splitter;
|
import com.google.common.base.Splitter;
|
||||||
import com.google.common.base.Supplier;
|
import com.google.common.base.Supplier;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.flogger.FluentLogger;
|
import com.google.common.flogger.FluentLogger;
|
||||||
import com.google.common.io.Resources;
|
|
||||||
import com.google.common.net.MediaType;
|
|
||||||
import com.google.template.soy.shared.SoyCssRenamingMap;
|
|
||||||
import com.google.template.soy.tofu.SoyTofu;
|
import com.google.template.soy.tofu.SoyTofu;
|
||||||
import google.registry.config.RegistryConfig.Config;
|
|
||||||
import google.registry.config.RegistryEnvironment;
|
import google.registry.config.RegistryEnvironment;
|
||||||
import google.registry.model.registrar.Registrar;
|
import google.registry.model.registrar.Registrar;
|
||||||
import google.registry.model.registrar.RegistrarAddress;
|
import google.registry.model.registrar.RegistrarAddress;
|
||||||
|
@ -46,23 +37,17 @@ import google.registry.model.registrar.RegistrarContact;
|
||||||
import google.registry.request.Action;
|
import google.registry.request.Action;
|
||||||
import google.registry.request.Action.Method;
|
import google.registry.request.Action.Method;
|
||||||
import google.registry.request.Parameter;
|
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.Auth;
|
||||||
import google.registry.request.auth.AuthResult;
|
|
||||||
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
|
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
|
||||||
import google.registry.security.XsrfTokenManager;
|
|
||||||
import google.registry.ui.server.SendEmailUtils;
|
import google.registry.ui.server.SendEmailUtils;
|
||||||
import google.registry.ui.server.SoyTemplateUtils;
|
import google.registry.ui.server.SoyTemplateUtils;
|
||||||
import google.registry.ui.soy.registrar.RegistrarCreateConsoleSoyInfo;
|
import google.registry.ui.soy.registrar.RegistrarCreateConsoleSoyInfo;
|
||||||
import google.registry.util.StringGenerator;
|
import google.registry.util.StringGenerator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import org.joda.money.CurrencyUnit;
|
import org.joda.money.CurrencyUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -79,7 +64,7 @@ import org.joda.money.CurrencyUnit;
|
||||||
path = ConsoleRegistrarCreatorAction.PATH,
|
path = ConsoleRegistrarCreatorAction.PATH,
|
||||||
method = {Method.POST, Method.GET},
|
method = {Method.POST, Method.GET},
|
||||||
auth = Auth.AUTH_PUBLIC)
|
auth = Auth.AUTH_PUBLIC)
|
||||||
public final class ConsoleRegistrarCreatorAction implements Runnable {
|
public final class ConsoleRegistrarCreatorAction extends HtmlAction {
|
||||||
|
|
||||||
private static final int PASSWORD_LENGTH = 16;
|
private static final int PASSWORD_LENGTH = 16;
|
||||||
private static final int PASSCODE_LENGTH = 5;
|
private static final int PASSCODE_LENGTH = 5;
|
||||||
|
@ -95,23 +80,8 @@ public final class ConsoleRegistrarCreatorAction implements Runnable {
|
||||||
google.registry.ui.soy.AnalyticsSoyInfo.getInstance(),
|
google.registry.ui.soy.AnalyticsSoyInfo.getInstance(),
|
||||||
google.registry.ui.soy.registrar.RegistrarCreateConsoleSoyInfo.getInstance());
|
google.registry.ui.soy.registrar.RegistrarCreateConsoleSoyInfo.getInstance());
|
||||||
|
|
||||||
@VisibleForTesting // webdriver and screenshot tests need this
|
|
||||||
public static final Supplier<SoyCssRenamingMap> CSS_RENAMING_MAP_SUPPLIER =
|
|
||||||
SoyTemplateUtils.createCssRenamingMapSupplier(
|
|
||||||
Resources.getResource("google/registry/ui/css/registrar_bin.css.js"),
|
|
||||||
Resources.getResource("google/registry/ui/css/registrar_dbg.css.js"));
|
|
||||||
|
|
||||||
@Inject HttpServletRequest req;
|
|
||||||
@Inject @RequestMethod Method method;
|
|
||||||
@Inject Response response;
|
|
||||||
@Inject AuthenticatedRegistrarAccessor registrarAccessor;
|
@Inject AuthenticatedRegistrarAccessor registrarAccessor;
|
||||||
@Inject UserService userService;
|
|
||||||
@Inject XsrfTokenManager xsrfTokenManager;
|
|
||||||
@Inject AuthResult authResult;
|
|
||||||
@Inject SendEmailUtils sendEmailUtils;
|
@Inject SendEmailUtils sendEmailUtils;
|
||||||
@Inject @Config("logoFilename") String logoFilename;
|
|
||||||
@Inject @Config("productName") String productName;
|
|
||||||
@Inject @Config("analyticsConfig") Map<String, Object> analyticsConfig;
|
|
||||||
@Inject @Named("base58StringGenerator") StringGenerator passwordGenerator;
|
@Inject @Named("base58StringGenerator") StringGenerator passwordGenerator;
|
||||||
@Inject @Named("digitOnlyStringGenerator") StringGenerator passcodeGenerator;
|
@Inject @Named("digitOnlyStringGenerator") StringGenerator passcodeGenerator;
|
||||||
@Inject @Parameter("clientId") Optional<String> clientId;
|
@Inject @Parameter("clientId") Optional<String> clientId;
|
||||||
|
@ -137,42 +107,7 @@ public final class ConsoleRegistrarCreatorAction implements Runnable {
|
||||||
@Inject ConsoleRegistrarCreatorAction() {}
|
@Inject ConsoleRegistrarCreatorAction() {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void runAfterLogin(HashMap<String, Object> data) {
|
||||||
response.setHeader(X_FRAME_OPTIONS, "SAMEORIGIN"); // Disallow iframing.
|
|
||||||
response.setHeader("X-Ui-Compatible", "IE=edge"); // Ask IE not to be silly.
|
|
||||||
|
|
||||||
logger.atInfo().log(
|
|
||||||
"User %s is accessing the Registrar creation page. Method= %s",
|
|
||||||
registrarAccessor.userIdForLogging(), method);
|
|
||||||
if (!authResult.userAuthInfo().isPresent()) {
|
|
||||||
response.setStatus(SC_MOVED_TEMPORARILY);
|
|
||||||
String location;
|
|
||||||
try {
|
|
||||||
location = userService.createLoginURL(req.getRequestURI());
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
// UserServiceImpl.createLoginURL() throws IllegalArgumentException if underlying API call
|
|
||||||
// returns an error code of NOT_ALLOWED. createLoginURL() assumes that the error is caused
|
|
||||||
// by an invalid URL. But in fact, the error can also occur if UserService doesn't have any
|
|
||||||
// user information, which happens when the request has been authenticated as internal. In
|
|
||||||
// this case, we want to avoid dying before we can send the redirect, so just redirect to
|
|
||||||
// the root path.
|
|
||||||
location = "/";
|
|
||||||
}
|
|
||||||
response.setHeader(LOCATION, location);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
User user = authResult.userAuthInfo().get().user();
|
|
||||||
|
|
||||||
// Using HashMap to allow null values
|
|
||||||
HashMap<String, Object> data = new HashMap<>();
|
|
||||||
data.put("logoFilename", logoFilename);
|
|
||||||
data.put("productName", productName);
|
|
||||||
data.put("username", user.getNickname());
|
|
||||||
data.put("logoutUrl", userService.createLogoutURL(PATH));
|
|
||||||
data.put("xsrfToken", xsrfTokenManager.generateToken(user.getEmail()));
|
|
||||||
data.put("analyticsConfig", analyticsConfig);
|
|
||||||
response.setContentType(MediaType.HTML_UTF_8);
|
|
||||||
|
|
||||||
if (!registrarAccessor.isAdmin()) {
|
if (!registrarAccessor.isAdmin()) {
|
||||||
response.setStatus(SC_FORBIDDEN);
|
response.setStatus(SC_FORBIDDEN);
|
||||||
response.setPayload(
|
response.setPayload(
|
||||||
|
@ -196,6 +131,11 @@ public final class ConsoleRegistrarCreatorAction implements Runnable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPath() {
|
||||||
|
return PATH;
|
||||||
|
}
|
||||||
|
|
||||||
private void checkPresent(Optional<?> value, String name) {
|
private void checkPresent(Optional<?> value, String name) {
|
||||||
checkState(value.isPresent(), "Missing value for %s", name);
|
checkState(value.isPresent(), "Missing value for %s", name);
|
||||||
}
|
}
|
||||||
|
@ -226,7 +166,6 @@ public final class ConsoleRegistrarCreatorAction implements Runnable {
|
||||||
|
|
||||||
private void runPost(HashMap<String, Object> data) {
|
private void runPost(HashMap<String, Object> data) {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
checkPresent(clientId, "clientId");
|
checkPresent(clientId, "clientId");
|
||||||
checkPresent(name, "name");
|
checkPresent(name, "name");
|
||||||
checkPresent(billingAccount, "billingAccount");
|
checkPresent(billingAccount, "billingAccount");
|
||||||
|
@ -321,11 +260,11 @@ public final class ConsoleRegistrarCreatorAction implements Runnable {
|
||||||
data.put("errorMessage", e.getMessage());
|
data.put("errorMessage", e.getMessage());
|
||||||
response.setPayload(
|
response.setPayload(
|
||||||
TOFU_SUPPLIER
|
TOFU_SUPPLIER
|
||||||
.get()
|
.get()
|
||||||
.newRenderer(RegistrarCreateConsoleSoyInfo.FORM_PAGE)
|
.newRenderer(RegistrarCreateConsoleSoyInfo.FORM_PAGE)
|
||||||
.setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get())
|
.setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get())
|
||||||
.setData(data)
|
.setData(data)
|
||||||
.render());
|
.render());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -357,8 +296,8 @@ public final class ConsoleRegistrarCreatorAction implements Runnable {
|
||||||
String environment = Ascii.toLowerCase(String.valueOf(RegistryEnvironment.get()));
|
String environment = Ascii.toLowerCase(String.valueOf(RegistryEnvironment.get()));
|
||||||
String body =
|
String body =
|
||||||
String.format(
|
String.format(
|
||||||
"The following registrar was created in %s by %s:\n",
|
"The following registrar was created in %s by %s:\n",
|
||||||
environment, registrarAccessor.userIdForLogging())
|
environment, registrarAccessor.userIdForLogging())
|
||||||
+ toEmailLine(clientId, "clientId")
|
+ toEmailLine(clientId, "clientId")
|
||||||
+ toEmailLine(name, "name")
|
+ toEmailLine(name, "name")
|
||||||
+ toEmailLine(billingAccount, "billingAccount")
|
+ toEmailLine(billingAccount, "billingAccount")
|
||||||
|
|
|
@ -14,47 +14,35 @@
|
||||||
|
|
||||||
package google.registry.ui.server.registrar;
|
package google.registry.ui.server.registrar;
|
||||||
|
|
||||||
import static com.google.common.net.HttpHeaders.LOCATION;
|
|
||||||
import static com.google.common.net.HttpHeaders.X_FRAME_OPTIONS;
|
|
||||||
import static google.registry.request.auth.AuthenticatedRegistrarAccessor.Role.ADMIN;
|
import static google.registry.request.auth.AuthenticatedRegistrarAccessor.Role.ADMIN;
|
||||||
import static google.registry.request.auth.AuthenticatedRegistrarAccessor.Role.OWNER;
|
import static google.registry.request.auth.AuthenticatedRegistrarAccessor.Role.OWNER;
|
||||||
|
import static google.registry.ui.server.SoyTemplateUtils.CSS_RENAMING_MAP_SUPPLIER;
|
||||||
import static google.registry.ui.server.registrar.RegistrarConsoleModule.PARAM_CLIENT_ID;
|
import static google.registry.ui.server.registrar.RegistrarConsoleModule.PARAM_CLIENT_ID;
|
||||||
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
|
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
|
||||||
import static javax.servlet.http.HttpServletResponse.SC_MOVED_TEMPORARILY;
|
|
||||||
import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
|
import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
|
||||||
|
|
||||||
import com.google.appengine.api.users.User;
|
|
||||||
import com.google.appengine.api.users.UserService;
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
|
||||||
import com.google.common.base.Supplier;
|
import com.google.common.base.Supplier;
|
||||||
import com.google.common.collect.ImmutableSetMultimap;
|
import com.google.common.collect.ImmutableSetMultimap;
|
||||||
import com.google.common.flogger.FluentLogger;
|
import com.google.common.flogger.FluentLogger;
|
||||||
import com.google.common.io.Resources;
|
|
||||||
import com.google.common.net.MediaType;
|
|
||||||
import com.google.template.soy.data.SoyMapData;
|
import com.google.template.soy.data.SoyMapData;
|
||||||
import com.google.template.soy.shared.SoyCssRenamingMap;
|
|
||||||
import com.google.template.soy.tofu.SoyTofu;
|
import com.google.template.soy.tofu.SoyTofu;
|
||||||
import google.registry.config.RegistryConfig.Config;
|
import google.registry.config.RegistryConfig.Config;
|
||||||
import google.registry.config.RegistryEnvironment;
|
import google.registry.config.RegistryEnvironment;
|
||||||
import google.registry.request.Action;
|
import google.registry.request.Action;
|
||||||
import google.registry.request.Parameter;
|
import google.registry.request.Parameter;
|
||||||
import google.registry.request.Response;
|
|
||||||
import google.registry.request.auth.Auth;
|
import google.registry.request.auth.Auth;
|
||||||
import google.registry.request.auth.AuthResult;
|
|
||||||
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
|
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
|
||||||
import google.registry.request.auth.AuthenticatedRegistrarAccessor.RegistrarAccessDeniedException;
|
import google.registry.request.auth.AuthenticatedRegistrarAccessor.RegistrarAccessDeniedException;
|
||||||
import google.registry.request.auth.AuthenticatedRegistrarAccessor.Role;
|
import google.registry.request.auth.AuthenticatedRegistrarAccessor.Role;
|
||||||
import google.registry.security.XsrfTokenManager;
|
|
||||||
import google.registry.ui.server.SoyTemplateUtils;
|
import google.registry.ui.server.SoyTemplateUtils;
|
||||||
import google.registry.ui.soy.registrar.ConsoleSoyInfo;
|
import google.registry.ui.soy.registrar.ConsoleSoyInfo;
|
||||||
import java.util.Map;
|
import java.util.HashMap;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
/** Action that serves Registrar Console single HTML page (SPA). */
|
/** Action that serves Registrar Console single HTML page (SPA). */
|
||||||
@Action(service = Action.Service.DEFAULT, path = ConsoleUiAction.PATH, auth = Auth.AUTH_PUBLIC)
|
@Action(service = Action.Service.DEFAULT, path = ConsoleUiAction.PATH, auth = Auth.AUTH_PUBLIC)
|
||||||
public final class ConsoleUiAction implements Runnable {
|
public final class ConsoleUiAction extends HtmlAction {
|
||||||
|
|
||||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||||
|
|
||||||
|
@ -66,27 +54,8 @@ public final class ConsoleUiAction implements Runnable {
|
||||||
google.registry.ui.soy.registrar.ConsoleSoyInfo.getInstance(),
|
google.registry.ui.soy.registrar.ConsoleSoyInfo.getInstance(),
|
||||||
google.registry.ui.soy.AnalyticsSoyInfo.getInstance());
|
google.registry.ui.soy.AnalyticsSoyInfo.getInstance());
|
||||||
|
|
||||||
@VisibleForTesting // webdriver and screenshot tests need this
|
|
||||||
public static final Supplier<SoyCssRenamingMap> CSS_RENAMING_MAP_SUPPLIER =
|
|
||||||
SoyTemplateUtils.createCssRenamingMapSupplier(
|
|
||||||
Resources.getResource("google/registry/ui/css/registrar_bin.css.js"),
|
|
||||||
Resources.getResource("google/registry/ui/css/registrar_dbg.css.js"));
|
|
||||||
|
|
||||||
@Inject HttpServletRequest req;
|
|
||||||
@Inject Response response;
|
|
||||||
@Inject RegistrarConsoleMetrics registrarConsoleMetrics;
|
@Inject RegistrarConsoleMetrics registrarConsoleMetrics;
|
||||||
@Inject AuthenticatedRegistrarAccessor registrarAccessor;
|
@Inject AuthenticatedRegistrarAccessor registrarAccessor;
|
||||||
@Inject UserService userService;
|
|
||||||
@Inject XsrfTokenManager xsrfTokenManager;
|
|
||||||
@Inject AuthResult authResult;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
@Config("logoFilename")
|
|
||||||
String logoFilename;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
@Config("productName")
|
|
||||||
String productName;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@Config("integrationEmail")
|
@Config("integrationEmail")
|
||||||
|
@ -112,10 +81,6 @@ public final class ConsoleUiAction implements Runnable {
|
||||||
@Config("registrarConsoleEnabled")
|
@Config("registrarConsoleEnabled")
|
||||||
boolean enabled;
|
boolean enabled;
|
||||||
|
|
||||||
@Inject
|
|
||||||
@Config("analyticsConfig")
|
|
||||||
Map<String, Object> analyticsConfig;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@Parameter(PARAM_CLIENT_ID)
|
@Parameter(PARAM_CLIENT_ID)
|
||||||
Optional<String> paramClientId;
|
Optional<String> paramClientId;
|
||||||
|
@ -124,37 +89,15 @@ public final class ConsoleUiAction implements Runnable {
|
||||||
ConsoleUiAction() {}
|
ConsoleUiAction() {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void runAfterLogin(HashMap<String, Object> data) {
|
||||||
if (!authResult.userAuthInfo().isPresent()) {
|
SoyMapData soyMapData = new SoyMapData();
|
||||||
response.setStatus(SC_MOVED_TEMPORARILY);
|
data.forEach((key, value) -> soyMapData.put(key, value));
|
||||||
String location;
|
|
||||||
try {
|
soyMapData.put("integrationEmail", integrationEmail);
|
||||||
location = userService.createLoginURL(req.getRequestURI());
|
soyMapData.put("supportEmail", supportEmail);
|
||||||
} catch (IllegalArgumentException e) {
|
soyMapData.put("announcementsEmail", announcementsEmail);
|
||||||
// UserServiceImpl.createLoginURL() throws IllegalArgumentException if underlying API call
|
soyMapData.put("supportPhoneNumber", supportPhoneNumber);
|
||||||
// returns an error code of NOT_ALLOWED. createLoginURL() assumes that the error is caused
|
soyMapData.put("technicalDocsUrl", technicalDocsUrl);
|
||||||
// by an invalid URL. But in fact, the error can also occur if UserService doesn't have any
|
|
||||||
// user information, which happens when the request has been authenticated as internal. In
|
|
||||||
// this case, we want to avoid dying before we can send the redirect, so just redirect to
|
|
||||||
// the root path.
|
|
||||||
location = "/";
|
|
||||||
}
|
|
||||||
response.setHeader(LOCATION, location);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
User user = authResult.userAuthInfo().get().user();
|
|
||||||
response.setContentType(MediaType.HTML_UTF_8);
|
|
||||||
response.setHeader(X_FRAME_OPTIONS, "SAMEORIGIN"); // Disallow iframing.
|
|
||||||
response.setHeader("X-Ui-Compatible", "IE=edge"); // Ask IE not to be silly.
|
|
||||||
SoyMapData data = new SoyMapData();
|
|
||||||
data.put("logoFilename", logoFilename);
|
|
||||||
data.put("productName", productName);
|
|
||||||
data.put("integrationEmail", integrationEmail);
|
|
||||||
data.put("supportEmail", supportEmail);
|
|
||||||
data.put("announcementsEmail", announcementsEmail);
|
|
||||||
data.put("supportPhoneNumber", supportPhoneNumber);
|
|
||||||
data.put("technicalDocsUrl", technicalDocsUrl);
|
|
||||||
data.put("analyticsConfig", analyticsConfig);
|
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
response.setStatus(SC_SERVICE_UNAVAILABLE);
|
response.setStatus(SC_SERVICE_UNAVAILABLE);
|
||||||
response.setPayload(
|
response.setPayload(
|
||||||
|
@ -162,23 +105,20 @@ public final class ConsoleUiAction implements Runnable {
|
||||||
.get()
|
.get()
|
||||||
.newRenderer(ConsoleSoyInfo.DISABLED)
|
.newRenderer(ConsoleSoyInfo.DISABLED)
|
||||||
.setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get())
|
.setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get())
|
||||||
.setData(data)
|
.setData(soyMapData)
|
||||||
.render());
|
.render());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
data.put("username", user.getNickname());
|
|
||||||
data.put("logoutUrl", userService.createLogoutURL(PATH));
|
|
||||||
data.put("xsrfToken", xsrfTokenManager.generateToken(user.getEmail()));
|
|
||||||
ImmutableSetMultimap<String, Role> roleMap = registrarAccessor.getAllClientIdWithRoles();
|
ImmutableSetMultimap<String, Role> roleMap = registrarAccessor.getAllClientIdWithRoles();
|
||||||
data.put("allClientIds", roleMap.keySet());
|
soyMapData.put("allClientIds", roleMap.keySet());
|
||||||
data.put("environment", RegistryEnvironment.get().toString());
|
soyMapData.put("environment", RegistryEnvironment.get().toString());
|
||||||
// We set the initial value to the value that will show if guessClientId throws.
|
// We set the initial value to the value that will show if guessClientId throws.
|
||||||
String clientId = "<null>";
|
String clientId = "<null>";
|
||||||
try {
|
try {
|
||||||
clientId = paramClientId.orElse(registrarAccessor.guessClientId());
|
clientId = paramClientId.orElse(registrarAccessor.guessClientId());
|
||||||
data.put("clientId", clientId);
|
soyMapData.put("clientId", clientId);
|
||||||
data.put("isOwner", roleMap.containsEntry(clientId, OWNER));
|
soyMapData.put("isOwner", roleMap.containsEntry(clientId, OWNER));
|
||||||
data.put("isAdmin", roleMap.containsEntry(clientId, ADMIN));
|
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
|
// 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.
|
// requireFeeExtension) - to make sure the user indeed has access to the guessed registrar.
|
||||||
|
@ -197,7 +137,7 @@ public final class ConsoleUiAction implements Runnable {
|
||||||
.get()
|
.get()
|
||||||
.newRenderer(ConsoleSoyInfo.WHOAREYOU)
|
.newRenderer(ConsoleSoyInfo.WHOAREYOU)
|
||||||
.setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get())
|
.setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get())
|
||||||
.setData(data)
|
.setData(soyMapData)
|
||||||
.render());
|
.render());
|
||||||
registrarConsoleMetrics.registerConsoleRequest(
|
registrarConsoleMetrics.registerConsoleRequest(
|
||||||
clientId, paramClientId.isPresent(), roleMap.get(clientId), "FORBIDDEN");
|
clientId, paramClientId.isPresent(), roleMap.get(clientId), "FORBIDDEN");
|
||||||
|
@ -213,10 +153,15 @@ public final class ConsoleUiAction implements Runnable {
|
||||||
.get()
|
.get()
|
||||||
.newRenderer(ConsoleSoyInfo.MAIN)
|
.newRenderer(ConsoleSoyInfo.MAIN)
|
||||||
.setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get())
|
.setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get())
|
||||||
.setData(data)
|
.setData(soyMapData)
|
||||||
.render();
|
.render();
|
||||||
response.setPayload(payload);
|
response.setPayload(payload);
|
||||||
registrarConsoleMetrics.registerConsoleRequest(
|
registrarConsoleMetrics.registerConsoleRequest(
|
||||||
clientId, paramClientId.isPresent(), roleMap.get(clientId), "SUCCESS");
|
clientId, paramClientId.isPresent(), roleMap.get(clientId), "SUCCESS");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPath() {
|
||||||
|
return PATH;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
// 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.LOCATION;
|
||||||
|
import static com.google.common.net.HttpHeaders.X_FRAME_OPTIONS;
|
||||||
|
import static javax.servlet.http.HttpServletResponse.SC_MOVED_TEMPORARILY;
|
||||||
|
|
||||||
|
import com.google.appengine.api.users.User;
|
||||||
|
import com.google.appengine.api.users.UserService;
|
||||||
|
import com.google.common.flogger.FluentLogger;
|
||||||
|
import com.google.common.net.MediaType;
|
||||||
|
import google.registry.config.RegistryConfig.Config;
|
||||||
|
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 java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 UserService userService;
|
||||||
|
@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<String, Object> 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.userAuthInfo().isPresent()) {
|
||||||
|
response.setStatus(SC_MOVED_TEMPORARILY);
|
||||||
|
String location;
|
||||||
|
try {
|
||||||
|
location = userService.createLoginURL(req.getRequestURI());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// UserServiceImpl.createLoginURL() throws IllegalArgumentException if underlying API call
|
||||||
|
// returns an error code of NOT_ALLOWED. createLoginURL() assumes that the error is caused
|
||||||
|
// by an invalid URL. But in fact, the error can also occur if UserService doesn't have any
|
||||||
|
// user information, which happens when the request has been authenticated as internal. In
|
||||||
|
// this case, we want to avoid dying before we can send the redirect, so just redirect to
|
||||||
|
// the root path.
|
||||||
|
location = "/";
|
||||||
|
}
|
||||||
|
response.setHeader(LOCATION, location);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
response.setContentType(MediaType.HTML_UTF_8);
|
||||||
|
|
||||||
|
User user = authResult.userAuthInfo().get().user();
|
||||||
|
|
||||||
|
// Using HashMap to allow null values
|
||||||
|
HashMap<String, Object> data = new HashMap<>();
|
||||||
|
data.put("logoFilename", logoFilename);
|
||||||
|
data.put("productName", productName);
|
||||||
|
data.put("username", user.getNickname());
|
||||||
|
data.put("logoutUrl", userService.createLogoutURL(getPath()));
|
||||||
|
data.put("analyticsConfig", analyticsConfig);
|
||||||
|
data.put("xsrfToken", xsrfTokenManager.generateToken(user.getEmail()));
|
||||||
|
|
||||||
|
logger.atInfo().log(
|
||||||
|
"User %s is accessing %s. Method= %s",
|
||||||
|
authResult.userIdForLogging(), getClass().getName(), method);
|
||||||
|
runAfterLogin(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void runAfterLogin(HashMap<String, Object> data);
|
||||||
|
|
||||||
|
public abstract String getPath();
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
// 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 {}
|
|
@ -16,7 +16,6 @@ package google.registry.ui.server.registrar;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||||
import static com.google.common.net.HttpHeaders.X_FRAME_OPTIONS;
|
|
||||||
import static google.registry.security.JsonResponseHelper.Status.SUCCESS;
|
import static google.registry.security.JsonResponseHelper.Status.SUCCESS;
|
||||||
import static google.registry.ui.server.registrar.RegistrarConsoleModule.PARAM_CLIENT_ID;
|
import static google.registry.ui.server.registrar.RegistrarConsoleModule.PARAM_CLIENT_ID;
|
||||||
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
|
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
|
||||||
|
@ -58,7 +57,7 @@ import org.joda.time.DateTime;
|
||||||
service = Action.Service.DEFAULT,
|
service = Action.Service.DEFAULT,
|
||||||
path = RegistryLockGetAction.PATH,
|
path = RegistryLockGetAction.PATH,
|
||||||
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
|
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
|
||||||
public final class RegistryLockGetAction implements Runnable {
|
public final class RegistryLockGetAction implements JsonGetAction {
|
||||||
|
|
||||||
public static final String PATH = "/registry-lock-get";
|
public static final String PATH = "/registry-lock-get";
|
||||||
|
|
||||||
|
@ -98,8 +97,6 @@ public final class RegistryLockGetAction implements Runnable {
|
||||||
checkArgument(authResult.userAuthInfo().isPresent(), "User auth info must be present");
|
checkArgument(authResult.userAuthInfo().isPresent(), "User auth info must be present");
|
||||||
checkArgument(paramClientId.isPresent(), "clientId must be present");
|
checkArgument(paramClientId.isPresent(), "clientId must be present");
|
||||||
response.setContentType(MediaType.JSON_UTF_8);
|
response.setContentType(MediaType.JSON_UTF_8);
|
||||||
response.setHeader(X_FRAME_OPTIONS, "SAMEORIGIN"); // Disallow iframing.
|
|
||||||
response.setHeader("X-Ui-Compatible", "IE=edge"); // Ask IE not to be silly.
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ImmutableMap<String, ?> resultMap = getLockedDomainsMap(paramClientId.get());
|
ImmutableMap<String, ?> resultMap = getLockedDomainsMap(paramClientId.get());
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertWithMessage;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import google.registry.request.Action;
|
||||||
|
import google.registry.request.JsonActionRunner;
|
||||||
|
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.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.JUnit4;
|
||||||
|
|
||||||
|
@RunWith(JUnit4.class)
|
||||||
|
public final class ActionMembershipTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public 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
|
||||||
|
ImmutableSet.Builder<String> failingClasses = new ImmutableSet.Builder<>();
|
||||||
|
try (ScanResult scanResult =
|
||||||
|
new ClassGraph().enableAnnotationInfo().whitelistPackages("google.registry.ui").scan()) {
|
||||||
|
scanResult
|
||||||
|
.getClassesWithAnnotation(Action.class.getName())
|
||||||
|
.forEach(
|
||||||
|
classInfo -> {
|
||||||
|
if (!classInfo.extendsSuperclass(HtmlAction.class.getName())
|
||||||
|
&& !classInfo.implementsInterface(JsonActionRunner.JsonAction.class.getName())
|
||||||
|
&& !classInfo.implementsInterface(JsonGetAction.class.getName())) {
|
||||||
|
failingClasses.add(classInfo.getName());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
assertWithMessage(
|
||||||
|
"All UI actions must implement / extend HtmlAction, JsonGetAction, "
|
||||||
|
+ "or JsonActionRunner.JsonAction. Failing classes:")
|
||||||
|
.that(failingClasses.build())
|
||||||
|
.isEmpty();
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,6 +28,7 @@ import com.google.appengine.api.users.UserServiceFactory;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.ImmutableSetMultimap;
|
import com.google.common.collect.ImmutableSetMultimap;
|
||||||
import com.google.common.net.MediaType;
|
import com.google.common.net.MediaType;
|
||||||
|
import google.registry.request.Action.Method;
|
||||||
import google.registry.request.auth.AuthLevel;
|
import google.registry.request.auth.AuthLevel;
|
||||||
import google.registry.request.auth.AuthResult;
|
import google.registry.request.auth.AuthResult;
|
||||||
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
|
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
|
||||||
|
@ -77,6 +78,7 @@ public class ConsoleUiActionTest {
|
||||||
action.registrarConsoleMetrics = new RegistrarConsoleMetrics();
|
action.registrarConsoleMetrics = new RegistrarConsoleMetrics();
|
||||||
action.userService = UserServiceFactory.getUserService();
|
action.userService = UserServiceFactory.getUserService();
|
||||||
action.xsrfTokenManager = new XsrfTokenManager(new FakeClock(), action.userService);
|
action.xsrfTokenManager = new XsrfTokenManager(new FakeClock(), action.userService);
|
||||||
|
action.method = Method.GET;
|
||||||
action.paramClientId = Optional.empty();
|
action.paramClientId = Optional.empty();
|
||||||
action.authResult = AuthResult.create(AuthLevel.USER, UserAuthInfo.create(user, false));
|
action.authResult = AuthResult.create(AuthLevel.USER, UserAuthInfo.create(user, false));
|
||||||
action.analyticsConfig = ImmutableMap.of("googleAnalyticsId", "sampleId");
|
action.analyticsConfig = ImmutableMap.of("googleAnalyticsId", "sampleId");
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue