Allow admins to access registrar console without a RegistrarContact

This allows admins to access the registrar console without needing to be added
as a registrar contact. If they are a registrar contact, then that registrar
takes precedence.

Tested=In Alpha

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=164182623
This commit is contained in:
bbilbo 2017-08-03 14:29:05 -07:00 committed by Ben McIlwain
parent 151ae2558f
commit f3919e056d
9 changed files with 308 additions and 69 deletions

View file

@ -931,6 +931,16 @@ public final class RegistryConfig {
return config.registryPolicy.checkApiServletClientId;
}
/**
* Returns the clientId of the registrar that admins are automatically logged in as if they
* aren't otherwise associated with one.
*/
@Provides
@Config("registryAdminClientId")
public static String provideRegistryAdminClientId(RegistryConfigSettings config) {
return config.registryPolicy.registryAdminClientId;
}
@Singleton
@Provides
static RegistryConfigSettings provideRegistryConfigSettings() {

View file

@ -78,6 +78,7 @@ public class RegistryConfigSettings {
public String tmchCrlUrl;
public String tmchMarksDbUrl;
public String checkApiServletClientId;
public String registryAdminClientId;
public String reservedTermsExportDisclaimer;
public String whoisDisclaimer;
}

View file

@ -71,6 +71,11 @@ registryPolicy:
# domain checks.
checkApiServletClientId: TheRegistrar
# The registry admin's registrar. Admins are granted permission to log in
# using this registrar automatically if they are not associated with any
# registrar
registryAdminClientId: TheRegistrar
# Disclaimer at the top of the exported reserved terms list.
reservedTermsExportDisclaimer: |
This list contains reserved terms for the TLD. Other terms may be reserved

View file

@ -29,6 +29,7 @@ registryPolicy:
tmchCrlUrl: http://crl.icann.org/tmch.crl
tmchMarksDbUrl: https://ry.marksdb.org
checkApiServletClientId: placeholder
registryAdminClientId: placeholder
whoisDisclaimer: |
multi-line
placeholder

View file

@ -21,7 +21,6 @@ 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 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;
@ -36,6 +35,7 @@ import google.registry.request.Action;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.UserAuthInfo;
import google.registry.security.XsrfTokenManager;
import google.registry.ui.server.SoyTemplateUtils;
import google.registry.ui.soy.registrar.ConsoleSoyInfo;
@ -97,7 +97,7 @@ public final class ConsoleUiAction implements Runnable {
response.setHeader(LOCATION, location);
return;
}
User user = authResult.userAuthInfo().get().user();
UserAuthInfo userAuthInfo = authResult.userAuthInfo().get();
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.
@ -119,9 +119,9 @@ public final class ConsoleUiAction implements Runnable {
.render());
return;
}
data.put("username", user.getNickname());
data.put("username", userAuthInfo.user().getNickname());
data.put("logoutUrl", userService.createLogoutURL(PATH));
if (!sessionUtils.checkRegistrarConsoleLogin(req, user)) {
if (!sessionUtils.checkRegistrarConsoleLogin(req, userAuthInfo)) {
response.setStatus(SC_FORBIDDEN);
response.setPayload(
TOFU_SUPPLIER.get()
@ -135,7 +135,7 @@ public final class ConsoleUiAction implements Runnable {
Registrar registrar =
checkArgumentPresent(
Registrar.loadByClientIdCached(clientId), "Registrar %s does not exist", clientId);
data.put("xsrfToken", xsrfTokenManager.generateToken(user.getEmail()));
data.put("xsrfToken", xsrfTokenManager.generateToken(userAuthInfo.user().getEmail()));
data.put("clientId", clientId);
data.put("showPaymentLink", registrar.getBillingMethod() == Registrar.BillingMethod.BRAINTREE);

View file

@ -22,12 +22,15 @@ import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
import com.google.appengine.api.users.User;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.FluentIterable;
import com.googlecode.objectify.Key;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarContact;
import google.registry.request.HttpException.ForbiddenException;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.UserAuthInfo;
import google.registry.util.FormattingLogger;
import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
@ -40,10 +43,14 @@ import javax.servlet.http.HttpSession;
@Immutable
public class SessionUtils {
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
public static final String CLIENT_ID_ATTRIBUTE = "clientId";
@Inject
@Config("registryAdminClientId")
String registryAdminClientId;
@Inject public SessionUtils() {}
/**
@ -58,7 +65,7 @@ public class SessionUtils {
if (!authResult.userAuthInfo().isPresent()) {
throw new ForbiddenException("Not logged in");
}
if (!checkRegistrarConsoleLogin(request, authResult.userAuthInfo().get().user())) {
if (!checkRegistrarConsoleLogin(request, authResult.userAuthInfo().get())) {
throw new ForbiddenException("Not authorized to access Registrar Console");
}
String clientId = getRegistrarClientId(request);
@ -86,28 +93,62 @@ public class SessionUtils {
* error response and abort the request.
*/
@CheckReturnValue
public boolean checkRegistrarConsoleLogin(HttpServletRequest req, User user) {
checkState(user != null, "No logged in user found");
public boolean checkRegistrarConsoleLogin(HttpServletRequest req, UserAuthInfo userAuthInfo) {
checkState(userAuthInfo != null, "No logged in user found");
User user = userAuthInfo.user();
HttpSession session = req.getSession();
String clientId = (String) session.getAttribute(CLIENT_ID_ATTRIBUTE);
if (clientId == null) {
Optional<Registrar> registrar = guessRegistrar(user.getUserId());
if (!registrar.isPresent()) {
logger.infofmt("User not associated with any Registrar: %s (%s)",
user.getUserId(), user.getEmail());
return false;
}
verify(hasAccessToRegistrar(registrar.get(), user.getUserId()));
session.setAttribute(CLIENT_ID_ATTRIBUTE, registrar.get().getClientId());
} else {
// Use the clientId if it exists
if (clientId != null) {
if (!hasAccessToRegistrar(clientId, user.getUserId())) {
logger.infofmt("Registrar Console access revoked: %s for %s (%s)",
clientId, user.getEmail(), user.getUserId());
logger.infofmt("Registrar Console access revoked: %s", clientId);
session.invalidate();
return false;
}
logger.infofmt("Associating user %s with given registrar %s.", user.getUserId(), clientId);
return true;
}
return true;
// The clientId was null, so let's try and find a registrar this user is associated with
Optional<Registrar> registrar = findRegistrarForUser(user.getUserId());
if (registrar.isPresent()) {
verify(hasAccessToRegistrar(registrar.get(), user.getUserId()));
logger.infofmt(
"Associating user %s with found registrar %s.",
user.getUserId(), registrar.get().getClientId());
session.setAttribute(CLIENT_ID_ATTRIBUTE, registrar.get().getClientId());
return true;
}
// We couldn't guess the registrar, but maybe the user is an admin and we can use the
// registryAdminClientId
if (userAuthInfo.isUserAdmin()) {
if (Strings.isNullOrEmpty(registryAdminClientId)) {
logger.infofmt(
"Cannot associate admin user %s with configured client Id."
+ " ClientId is null or empty.",
user.getUserId());
return false;
}
if (!Registrar.loadByClientIdCached(registryAdminClientId).isPresent()) {
logger.infofmt(
"Cannot associate admin user %s with configured client Id %s."
+ " Registrar does not exist.",
user.getUserId(), registryAdminClientId);
return false;
}
logger.infofmt(
"User %s is an admin with no associated registrar."
+ " Automatically associating the user with configured client Id %s.",
user.getUserId(), registryAdminClientId);
session.setAttribute(CLIENT_ID_ATTRIBUTE, registryAdminClientId);
return true;
}
// We couldn't find any relevant clientId
logger.infofmt("User not associated with any Registrar: %s", user.getUserId());
return false;
}
/**
@ -123,7 +164,7 @@ public class SessionUtils {
}
/** Returns first {@link Registrar} that {@code gaeUserId} is authorized to administer. */
private static Optional<Registrar> guessRegistrar(String gaeUserId) {
private static Optional<Registrar> findRegistrarForUser(String gaeUserId) {
RegistrarContact contact = ofy().load()
.type(RegistrarContact.class)
.filter("gaeUserId", gaeUserId)
@ -141,7 +182,7 @@ public class SessionUtils {
}
/** @see #hasAccessToRegistrar(Registrar, String) */
private static boolean hasAccessToRegistrar(String clientId, final String gaeUserId) {
protected static boolean hasAccessToRegistrar(String clientId, final String gaeUserId) {
Optional<Registrar> registrar = Registrar.loadByClientIdCached(clientId);
if (!registrar.isPresent()) {
logger.warningfmt("Registrar '%s' disappeared from Datastore!", clientId);