Allow UserAuthInfo to contain either old GAE Users or new console Users (#1744)

This means that LegacyAuthenticationMechanism or a to-be-created
OAuth2AuthenticationMechanism) can return a UserAuthInfo object that
contains either the GAE User or the console User as appropriate. The
goal is that the non-auth flows shouldn't have to know about which user
type it is. Note: the registry lock flow (for now) needs to know about
the separate types of auth because it is a separate level of auth from
the standard AuthenticatedRegistrarAccessor.

The AuthenticatedRegistrarAccessor code is a bit odd because the new
role system doesn't quite fit neatly into the old registrar ->
OWNER,ADMIN system but this is a fine approximation. Basically, any
new registrar role will map to the old OWNER role.
This commit is contained in:
gbrodman 2022-08-24 14:18:32 -04:00 committed by GitHub
parent a6087bf328
commit dbc6cd2377
13 changed files with 416 additions and 167 deletions

View file

@ -108,7 +108,7 @@ public final class RdapModule {
if (userAuthInfo.isUserAdmin()) { if (userAuthInfo.isUserAdmin()) {
return RdapAuthorization.ADMINISTRATOR_AUTHORIZATION; return RdapAuthorization.ADMINISTRATOR_AUTHORIZATION;
} }
ImmutableSet<String> clientIds = registrarAccessor.getAllClientIdWithRoles().keySet(); ImmutableSet<String> clientIds = registrarAccessor.getAllRegistrarIdsWithRoles().keySet();
if (clientIds.isEmpty()) { if (clientIds.isEmpty()) {
logger.atWarning().log("Couldn't find registrar for User %s.", authResult.userIdForLogging()); logger.atWarning().log("Couldn't find registrar for User %s.", authResult.userIdForLogging());
return RdapAuthorization.PUBLIC_AUTHORIZATION; return RdapAuthorization.PUBLIC_AUTHORIZATION;

View file

@ -42,7 +42,7 @@ public abstract class AuthResult {
userAuthInfo -> userAuthInfo ->
String.format( String.format(
"%s %s", "%s %s",
userAuthInfo.isUserAdmin() ? "admin" : "user", userAuthInfo.user().getEmail())) userAuthInfo.isUserAdmin() ? "admin" : "user", userAuthInfo.getEmailAddress()))
.orElse("<logged-out user>"); .orElse("<logged-out user>");
} }

View file

@ -40,8 +40,8 @@ import javax.inject.Inject;
* <p>A user has OWNER role on a Registrar if there exists a {@link RegistrarPoc} with that user's * <p>A user has OWNER role on a Registrar if there exists a {@link RegistrarPoc} with that user's
* gaeId and the registrar as a parent. * gaeId and the registrar as a parent.
* *
* <p>An "admin" has in addition OWNER role on {@code #registryAdminClientId} and to all non-{@code * <p>An "admin" has in addition OWNER role on {@code #registryAdminRegistrarId} and to all
* REAL} registrars (see {@link Registrar#getType}). * non-{@code REAL} registrars (see {@link Registrar#getType}).
* *
* <p>An "admin" also has ADMIN role on ALL registrars. * <p>An "admin" also has ADMIN role on ALL registrars.
* *
@ -76,7 +76,7 @@ public class AuthenticatedRegistrarAccessor {
private final boolean isAdmin; private final boolean isAdmin;
/** /**
* Gives all roles a user has for a given clientId. * Gives all roles a user has for a given registrar ID.
* *
* <p>The order is significant, with "more specific to this user" coming first. * <p>The order is significant, with "more specific to this user" coming first.
* *
@ -107,13 +107,13 @@ public class AuthenticatedRegistrarAccessor {
@Inject @Inject
public AuthenticatedRegistrarAccessor( public AuthenticatedRegistrarAccessor(
AuthResult authResult, AuthResult authResult,
@Config("registryAdminClientId") String registryAdminClientId, @Config("registryAdminClientId") String registryAdminRegistrarId,
@Config("gSuiteSupportGroupEmailAddress") Optional<String> gSuiteSupportGroupEmailAddress, @Config("gSuiteSupportGroupEmailAddress") Optional<String> gSuiteSupportGroupEmailAddress,
Lazy<GroupsConnection> lazyGroupsConnection) { Lazy<GroupsConnection> lazyGroupsConnection) {
this.isAdmin = userIsAdmin(authResult, gSuiteSupportGroupEmailAddress, lazyGroupsConnection); this.isAdmin = userIsAdmin(authResult, gSuiteSupportGroupEmailAddress, lazyGroupsConnection);
this.userIdForLogging = authResult.userIdForLogging(); this.userIdForLogging = authResult.userIdForLogging();
this.roleMap = createRoleMap(authResult, this.isAdmin, registryAdminClientId); this.roleMap = createRoleMap(authResult, this.isAdmin, registryAdminRegistrarId);
logger.atInfo().log("%s has the following roles: %s", userIdForLogging(), roleMap); logger.atInfo().log("%s has the following roles: %s", userIdForLogging(), roleMap);
} }
@ -129,7 +129,7 @@ public class AuthenticatedRegistrarAccessor {
* Creates a "logged-in user" accessor with a given role map, used for tests. * Creates a "logged-in user" accessor with a given role map, used for tests.
* *
* <p>The user will be allowed to create Registrars (and hence do OT&amp;E setup) iff they have * <p>The user will be allowed to create Registrars (and hence do OT&amp;E setup) iff they have
* the role of ADMIN for at least one clientId. * the role of ADMIN for at least one registrar ID.
* *
* <p>The user's "name" in logs and exception messages is "TestUserId". * <p>The user's "name" in logs and exception messages is "TestUserId".
*/ */
@ -148,59 +148,62 @@ public class AuthenticatedRegistrarAccessor {
} }
/** /**
* A map that gives all roles a user has for a given clientId. * A map that gives all roles a user has for a given registrar ID.
* *
* <p>Throws a {@link RegistrarAccessDeniedException} if the user is not logged in. * <p>Throws a {@link RegistrarAccessDeniedException} if the user is not logged in.
* *
* <p>The result is ordered starting from "most specific to this user". * <p>The result is ordered starting from "most specific to this user".
* *
* <p>If you want to load the {@link Registrar} object from these (or any other) {@code clientId}, * <p>If you want to load the {@link Registrar} object from these (or any other) {@code
* in order to perform actions on behalf of a user, you must use {@link #getRegistrar} which makes * registrarId}, in order to perform actions on behalf of a user, you must use {@link
* sure the user has permissions. * #getRegistrar} which makes sure the user has permissions.
* *
* <p>Note that this is an OPTIONAL step in the authentication - only used if we don't have any * <p>Note that this is an OPTIONAL step in the authentication - only used if we don't have any
* other clue as to the requested {@code clientId}. It is perfectly OK to get a {@code clientId} * other clue as to the requested {@code registrarId}. It is perfectly OK to get a {@code
* from any other source, as long as the registrar is then loaded using {@link #getRegistrar}. * registrarId} from any other source, as long as the registrar is then loaded using {@link
* #getRegistrar}.
*/ */
public ImmutableSetMultimap<String, Role> getAllClientIdWithRoles() { public ImmutableSetMultimap<String, Role> getAllRegistrarIdsWithRoles() {
return roleMap; return roleMap;
} }
/** /**
* Returns all the roles the current user has on the given registrar. * Returns all the roles the current user has on the given registrar.
* *
* <p>This is syntactic sugar for {@code getAllClientIdWithRoles().get(clientId)}. * <p>This is syntactic sugar for {@code getAllRegistrarIdsWithRoles().get(registrarId)}.
*/ */
public ImmutableSet<Role> getRolesForRegistrar(String clientId) { public ImmutableSet<Role> getRolesForRegistrar(String registrarId) {
return getAllClientIdWithRoles().get(clientId); return getAllRegistrarIdsWithRoles().get(registrarId);
} }
/** /**
* Checks if we have a given role for a given registrar. * Checks if we have a given role for a given registrar.
* *
* <p>This is syntactic sugar for {@code getAllClientIdWithRoles().containsEntry(clientId, role)}. * <p>This is syntactic sugar for {@code getAllRegistrarIdsWithRoles().containsEntry(registrarId,
* role)}.
*/ */
public boolean hasRoleOnRegistrar(Role role, String clientId) { public boolean hasRoleOnRegistrar(Role role, String registrarId) {
return getAllClientIdWithRoles().containsEntry(clientId, role); return getAllRegistrarIdsWithRoles().containsEntry(registrarId, role);
} }
/** /**
* "Guesses" which client ID the user wants from all those they have access to. * "Guesses" which client ID the user wants from all those they have access to.
* *
* <p>If no such ClientIds exist, throws a RegistrarAccessDeniedException. * <p>If no such registrar IDs exist, throws a RegistrarAccessDeniedException.
* *
* <p>This should be the ClientId "most likely wanted by the user". * <p>This should be the registrar ID "most likely wanted by the user".
* *
* <p>If you want to load the {@link Registrar} object from this (or any other) {@code clientId}, * <p>If you want to load the {@link Registrar} object from this (or any other) {@code
* in order to perform actions on behalf of a user, you must use {@link #getRegistrar} which makes * registrarId}, in order to perform actions on behalf of a user, you must use {@link
* sure the user has permissions. * #getRegistrar} which makes sure the user has permissions.
* *
* <p>Note that this is an OPTIONAL step in the authentication - only used if we don't have any * <p>Note that this is an OPTIONAL step in the authentication - only used if we don't have any
* other clue as to the requested {@code clientId}. It is perfectly OK to get a {@code clientId} * other clue as to the requested {@code registrarId}. It is perfectly OK to get a {@code
* from any other source, as long as the registrar is then loaded using {@link #getRegistrar}. * registrarId} from any other source, as long as the registrar is then loaded using {@link
* #getRegistrar}.
*/ */
public String guessClientId() throws RegistrarAccessDeniedException { public String guessRegistrarId() throws RegistrarAccessDeniedException {
return getAllClientIdWithRoles().keySet().stream() return getAllRegistrarIdsWithRoles().keySet().stream()
.findFirst() .findFirst()
.orElseThrow( .orElseThrow(
() -> () ->
@ -227,7 +230,7 @@ public class AuthenticatedRegistrarAccessor {
if (!registrarId.equals(registrar.getRegistrarId())) { if (!registrarId.equals(registrar.getRegistrarId())) {
logger.atSevere().log( logger.atSevere().log(
"registrarLoader.apply(clientId) returned a Registrar with a different clientId. " "registrarLoader.apply(registrarId) returned a Registrar with a different registrarId. "
+ "Requested: %s, returned: %s.", + "Requested: %s, returned: %s.",
registrarId, registrar.getRegistrarId()); registrarId, registrar.getRegistrarId());
throw new RegistrarAccessDeniedException("Internal error - please check logs"); throw new RegistrarAccessDeniedException("Internal error - please check logs");
@ -237,7 +240,7 @@ public class AuthenticatedRegistrarAccessor {
} }
public void verifyAccess(String registrarId) throws RegistrarAccessDeniedException { public void verifyAccess(String registrarId) throws RegistrarAccessDeniedException {
ImmutableSet<Role> roles = getAllClientIdWithRoles().get(registrarId); ImmutableSet<Role> roles = getAllRegistrarIdsWithRoles().get(registrarId);
if (roles.isEmpty()) { if (roles.isEmpty()) {
throw new RegistrarAccessDeniedException( throw new RegistrarAccessDeniedException(
@ -279,37 +282,31 @@ public class AuthenticatedRegistrarAccessor {
AuthResult authResult, AuthResult authResult,
Optional<String> gSuiteSupportGroupEmailAddress, Optional<String> gSuiteSupportGroupEmailAddress,
Lazy<GroupsConnection> lazyGroupsConnection) { Lazy<GroupsConnection> lazyGroupsConnection) {
if (!authResult.userAuthInfo().isPresent()) { if (!authResult.userAuthInfo().isPresent()) {
return false; return false;
} }
UserAuthInfo userAuthInfo = authResult.userAuthInfo().get(); UserAuthInfo userAuthInfo = authResult.userAuthInfo().get();
User user = userAuthInfo.user();
// both GAE project admin and members of the gSuiteSupportGroupEmailAddress are considered // both GAE project admin and members of the gSuiteSupportGroupEmailAddress are considered
// admins for the RegistrarConsole. // admins for the RegistrarConsole.
return !bypassAdminCheck return !bypassAdminCheck
&& (userAuthInfo.isUserAdmin() && (userAuthInfo.isUserAdmin()
|| checkIsSupport( || checkIsSupport(
lazyGroupsConnection, user.getEmail(), gSuiteSupportGroupEmailAddress)); lazyGroupsConnection,
userAuthInfo.getEmailAddress(),
gSuiteSupportGroupEmailAddress));
} }
/** /** Returns a map of registrar IDs to roles for all registrars that the user has access to. */
* Returns a map of registrar client IDs to roles for all registrars that the user has access to.
*/
private static ImmutableSetMultimap<String, Role> createRoleMap( private static ImmutableSetMultimap<String, Role> createRoleMap(
AuthResult authResult, AuthResult authResult, boolean isAdmin, String registryAdminRegistrarId) {
boolean isAdmin,
String registryAdminClientId) {
if (!authResult.userAuthInfo().isPresent()) { if (!authResult.userAuthInfo().isPresent()) {
return ImmutableSetMultimap.of(); return ImmutableSetMultimap.of();
} }
UserAuthInfo userAuthInfo = authResult.userAuthInfo().get();
User user = userAuthInfo.user();
ImmutableSetMultimap.Builder<String, Role> builder = new ImmutableSetMultimap.Builder<>(); ImmutableSetMultimap.Builder<String, Role> builder = new ImmutableSetMultimap.Builder<>();
UserAuthInfo userAuthInfo = authResult.userAuthInfo().get();
if (userAuthInfo.appEngineUser().isPresent()) {
User user = userAuthInfo.appEngineUser().get();
logger.atInfo().log("Checking registrar contacts for user ID %s.", user.getUserId()); logger.atInfo().log("Checking registrar contacts for user ID %s.", user.getUserId());
// Find all registrars that have a registrar contact with this user's ID. // Find all registrars that have a registrar contact with this user's ID.
@ -326,6 +323,14 @@ public class AuthenticatedRegistrarAccessor {
.setParameter("state", State.DISABLED) .setParameter("state", State.DISABLED)
.getResultStream() .getResultStream()
.forEach(registrar -> builder.put(registrar.getRegistrarId(), Role.OWNER))); .forEach(registrar -> builder.put(registrar.getRegistrarId(), Role.OWNER)));
} else {
userAuthInfo
.consoleUser()
.get()
.getUserRoles()
.getRegistrarRoles()
.forEach((k, v) -> builder.put(k, Role.OWNER));
}
// Admins have ADMIN access to all registrars, and also OWNER access to the registry registrar // Admins have ADMIN access to all registrars, and also OWNER access to the registry registrar
// and all non-REAL or non-live registrars. // and all non-REAL or non-live registrars.
@ -337,7 +342,7 @@ public class AuthenticatedRegistrarAccessor {
registrar -> { registrar -> {
if (registrar.getType() != Registrar.Type.REAL if (registrar.getType() != Registrar.Type.REAL
|| !registrar.isLive() || !registrar.isLive()
|| registrar.getRegistrarId().equals(registryAdminClientId)) { || registrar.getRegistrarId().equals(registryAdminRegistrarId)) {
builder.put(registrar.getRegistrarId(), Role.OWNER); builder.put(registrar.getRegistrarId(), Role.OWNER);
} }
builder.put(registrar.getRegistrarId(), Role.ADMIN); builder.put(registrar.getRegistrarId(), Role.ADMIN);

View file

@ -23,7 +23,7 @@ import java.util.Optional;
public abstract class UserAuthInfo { public abstract class UserAuthInfo {
/** User object from the AppEngine Users API. */ /** User object from the AppEngine Users API. */
public abstract User user(); public abstract Optional<User> appEngineUser();
/** /**
* Whether the user is an admin. * Whether the user is an admin.
@ -34,16 +34,37 @@ public abstract class UserAuthInfo {
*/ */
public abstract boolean isUserAdmin(); public abstract boolean isUserAdmin();
public abstract Optional<google.registry.model.console.User> consoleUser();
/** Used by the OAuth authentication mechanism (only) to return information about the session. */ /** Used by the OAuth authentication mechanism (only) to return information about the session. */
public abstract Optional<OAuthTokenInfo> oauthTokenInfo(); public abstract Optional<OAuthTokenInfo> oauthTokenInfo();
public String getEmailAddress() {
return appEngineUser()
.map(User::getEmail)
.orElseGet(() -> consoleUser().get().getEmailAddress());
}
public String getUsername() {
return appEngineUser()
.map(User::getNickname)
.orElseGet(() -> consoleUser().get().getEmailAddress());
}
public static UserAuthInfo create( public static UserAuthInfo create(
User user, boolean isUserAdmin) { User user, boolean isUserAdmin) {
return new AutoValue_UserAuthInfo(user, isUserAdmin, Optional.empty()); return new AutoValue_UserAuthInfo(
Optional.of(user), isUserAdmin, Optional.empty(), Optional.empty());
} }
public static UserAuthInfo create( public static UserAuthInfo create(
User user, boolean isUserAdmin, OAuthTokenInfo oauthTokenInfo) { User user, boolean isUserAdmin, OAuthTokenInfo oauthTokenInfo) {
return new AutoValue_UserAuthInfo(user, isUserAdmin, Optional.of(oauthTokenInfo)); return new AutoValue_UserAuthInfo(
Optional.of(user), isUserAdmin, Optional.empty(), Optional.of(oauthTokenInfo));
}
public static UserAuthInfo create(google.registry.model.console.User user) {
return new AutoValue_UserAuthInfo(
Optional.empty(), user.getUserRoles().isAdmin(), Optional.of(user), Optional.empty());
} }
} }

View file

@ -109,13 +109,13 @@ public final class ConsoleUiAction extends HtmlAction {
.render()); .render());
return; return;
} }
ImmutableSetMultimap<String, Role> roleMap = registrarAccessor.getAllClientIdWithRoles(); ImmutableSetMultimap<String, Role> roleMap = registrarAccessor.getAllRegistrarIdsWithRoles();
soyMapData.put("allClientIds", roleMap.keySet()); soyMapData.put("allClientIds", roleMap.keySet());
soyMapData.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.guessRegistrarId());
soyMapData.put("clientId", clientId); soyMapData.put("clientId", clientId);
soyMapData.put("isOwner", roleMap.containsEntry(clientId, OWNER)); soyMapData.put("isOwner", roleMap.containsEntry(clientId, OWNER));
soyMapData.put("isAdmin", roleMap.containsEntry(clientId, ADMIN)); soyMapData.put("isAdmin", roleMap.containsEntry(clientId, ADMIN));

View file

@ -18,7 +18,6 @@ import static com.google.common.net.HttpHeaders.LOCATION;
import static com.google.common.net.HttpHeaders.X_FRAME_OPTIONS; import static com.google.common.net.HttpHeaders.X_FRAME_OPTIONS;
import static javax.servlet.http.HttpServletResponse.SC_MOVED_TEMPORARILY; 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.appengine.api.users.UserService;
import com.google.common.flogger.FluentLogger; import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType; import com.google.common.net.MediaType;
@ -27,6 +26,7 @@ import google.registry.request.Action;
import google.registry.request.RequestMethod; import google.registry.request.RequestMethod;
import google.registry.request.Response; import google.registry.request.Response;
import google.registry.request.auth.AuthResult; import google.registry.request.auth.AuthResult;
import google.registry.request.auth.UserAuthInfo;
import google.registry.security.XsrfTokenManager; import google.registry.security.XsrfTokenManager;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -86,16 +86,15 @@ public abstract class HtmlAction implements Runnable {
} }
response.setContentType(MediaType.HTML_UTF_8); response.setContentType(MediaType.HTML_UTF_8);
User user = authResult.userAuthInfo().get().user(); UserAuthInfo authInfo = authResult.userAuthInfo().get();
// Using HashMap to allow null values // Using HashMap to allow null values
HashMap<String, Object> data = new HashMap<>(); HashMap<String, Object> data = new HashMap<>();
data.put("logoFilename", logoFilename); data.put("logoFilename", logoFilename);
data.put("productName", productName); data.put("productName", productName);
data.put("username", user.getNickname()); data.put("username", authInfo.getUsername());
data.put("logoutUrl", userService.createLogoutURL(getPath())); data.put("logoutUrl", userService.createLogoutURL(getPath()));
data.put("analyticsConfig", analyticsConfig); data.put("analyticsConfig", analyticsConfig);
data.put("xsrfToken", xsrfTokenManager.generateToken(user.getEmail())); data.put("xsrfToken", xsrfTokenManager.generateToken(authInfo.getEmailAddress()));
logger.atInfo().log( logger.atInfo().log(
"User %s is accessing %s with method %s.", "User %s is accessing %s with method %s.",

View file

@ -29,6 +29,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.flogger.FluentLogger; import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType; import com.google.common.net.MediaType;
import com.google.gson.Gson; import com.google.gson.Gson;
import google.registry.model.console.ConsolePermission;
import google.registry.model.domain.RegistryLock; import google.registry.model.domain.RegistryLock;
import google.registry.model.registrar.Registrar; import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarPoc; import google.registry.model.registrar.RegistrarPoc;
@ -42,6 +43,7 @@ import google.registry.request.auth.Auth;
import google.registry.request.auth.AuthResult; 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.UserAuthInfo;
import google.registry.security.JsonResponseHelper; import google.registry.security.JsonResponseHelper;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
@ -153,24 +155,35 @@ public final class RegistryLockGetAction implements JsonGetAction {
boolean isAdmin = registrarAccessor.isAdmin(); boolean isAdmin = registrarAccessor.isAdmin();
Registrar registrar = getRegistrarAndVerifyLockAccess(registrarAccessor, registrarId, isAdmin); Registrar registrar = getRegistrarAndVerifyLockAccess(registrarAccessor, registrarId, isAdmin);
User user = authResult.userAuthInfo().get().user();
UserAuthInfo userAuthInfo = authResult.userAuthInfo().get();
// Split logic depending on whether we are using the old auth system or the new one
boolean isRegistryLockAllowed;
String relevantEmail;
if (userAuthInfo.appEngineUser().isPresent()) {
User user = userAuthInfo.appEngineUser().get();
Optional<RegistrarPoc> contactOptional = getContactMatchingLogin(user, registrar); Optional<RegistrarPoc> contactOptional = getContactMatchingLogin(user, registrar);
boolean isRegistryLockAllowed = isRegistryLockAllowed =
isAdmin || contactOptional.map(RegistrarPoc::isRegistryLockAllowed).orElse(false); isAdmin || contactOptional.map(RegistrarPoc::isRegistryLockAllowed).orElse(false);
// Use the contact's registry lock email if it's present, else use the login email (for admins) relevantEmail =
String relevantEmail =
isAdmin isAdmin
? user.getEmail() ? user.getEmail()
// if the contact isn't present, we shouldn't display the email anyway so empty is fine // if the contact isn't present, we shouldn't display the email anyway
: contactOptional.flatMap(RegistrarPoc::getRegistryLockEmailAddress).orElse(""); : contactOptional.flatMap(RegistrarPoc::getRegistryLockEmailAddress).orElse("");
} else {
google.registry.model.console.User user = userAuthInfo.consoleUser().get();
isRegistryLockAllowed =
user.getUserRoles().hasPermission(registrarId, ConsolePermission.REGISTRY_LOCK);
relevantEmail = user.getEmailAddress();
}
// Use the contact's registry lock email if it's present, else use the login email (for admins)
return ImmutableMap.of( return ImmutableMap.of(
LOCK_ENABLED_FOR_CONTACT_PARAM, LOCK_ENABLED_FOR_CONTACT_PARAM,
isRegistryLockAllowed, isRegistryLockAllowed,
EMAIL_PARAM, EMAIL_PARAM,
relevantEmail, relevantEmail,
PARAM_CLIENT_ID, PARAM_CLIENT_ID,
registrar.getRegistrarId(), registrarId,
LOCKS_PARAM, LOCKS_PARAM,
getLockedDomains(registrarId, isAdmin)); getLockedDomains(registrarId, isAdmin));
} }

View file

@ -184,10 +184,29 @@ public class RegistryLockPostAction implements Runnable, JsonActionRunner.JsonAc
private String verifyPasswordAndGetEmail( private String verifyPasswordAndGetEmail(
UserAuthInfo userAuthInfo, RegistryLockPostInput postInput) UserAuthInfo userAuthInfo, RegistryLockPostInput postInput)
throws RegistrarAccessDeniedException { throws RegistrarAccessDeniedException {
User user = userAuthInfo.user();
if (registrarAccessor.isAdmin()) { if (registrarAccessor.isAdmin()) {
return user.getEmail(); return userAuthInfo.getEmailAddress();
} }
if (userAuthInfo.appEngineUser().isPresent()) {
return verifyPasswordAndGetEmailLegacyUser(userAuthInfo.appEngineUser().get(), postInput);
} else {
return verifyPasswordAndGetEmailConsoleUser(userAuthInfo.consoleUser().get(), postInput);
}
}
private String verifyPasswordAndGetEmailConsoleUser(
google.registry.model.console.User user, RegistryLockPostInput postInput)
throws RegistrarAccessDeniedException {
// Verify that the registrar has locking enabled
getRegistrarAndVerifyLockAccess(registrarAccessor, postInput.registrarId, false);
checkArgument(
user.verifyRegistryLockPassword(postInput.password),
"Incorrect registry lock password for user");
return user.getEmailAddress();
}
private String verifyPasswordAndGetEmailLegacyUser(User user, RegistryLockPostInput postInput)
throws RegistrarAccessDeniedException {
// Verify that the user can access the registrar, that the user has // Verify that the user can access the registrar, that the user has
// registry lock enabled, and that the user provided a correct password // registry lock enabled, and that the user provided a correct password
Registrar registrar = Registrar registrar =

View file

@ -474,7 +474,7 @@ public final class RequestHandlerTest {
assertThat(providedAuthResult).isNotNull(); assertThat(providedAuthResult).isNotNull();
assertThat(providedAuthResult.authLevel()).isEqualTo(AuthLevel.USER); assertThat(providedAuthResult.authLevel()).isEqualTo(AuthLevel.USER);
assertThat(providedAuthResult.userAuthInfo()).isPresent(); assertThat(providedAuthResult.userAuthInfo()).isPresent();
assertThat(providedAuthResult.userAuthInfo().get().user()).isEqualTo(testUser); assertThat(providedAuthResult.userAuthInfo().get().appEngineUser()).hasValue(testUser);
assertThat(providedAuthResult.userAuthInfo().get().oauthTokenInfo()).isEmpty(); assertThat(providedAuthResult.userAuthInfo().get().oauthTokenInfo()).isEmpty();
assertMetric("/auth/adminUser", GET, AuthLevel.USER, true); assertMetric("/auth/adminUser", GET, AuthLevel.USER, true);
} }

View file

@ -28,11 +28,15 @@ import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import com.google.appengine.api.users.User; import com.google.appengine.api.users.User;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.testing.NullPointerTester; import com.google.common.testing.NullPointerTester;
import com.google.common.testing.TestLogHandler; import com.google.common.testing.TestLogHandler;
import dagger.Lazy; import dagger.Lazy;
import google.registry.groups.GroupsConnection; import google.registry.groups.GroupsConnection;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.UserRoles;
import google.registry.model.registrar.Registrar; import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.Registrar.State; import google.registry.model.registrar.Registrar.State;
import google.registry.request.auth.AuthenticatedRegistrarAccessor.RegistrarAccessDeniedException; import google.registry.request.auth.AuthenticatedRegistrarAccessor.RegistrarAccessDeniedException;
@ -71,14 +75,14 @@ class AuthenticatedRegistrarAccessorTest {
private static final AuthResult GAE_ADMIN = createAuthResult(true); private static final AuthResult GAE_ADMIN = createAuthResult(true);
private static final AuthResult NO_USER = AuthResult.create(AuthLevel.NONE); private static final AuthResult NO_USER = AuthResult.create(AuthLevel.NONE);
private static final Optional<String> SUPPORT_GROUP = Optional.of("support@registry.example"); private static final Optional<String> SUPPORT_GROUP = Optional.of("support@registry.example");
/** Client ID of a REAL registrar with a RegistrarContact for USER and GAE_ADMIN. */ /** Registrar ID of a REAL registrar with a RegistrarContact for USER and GAE_ADMIN. */
private static final String CLIENT_ID_WITH_CONTACT = "TheRegistrar"; private static final String REGISTRAR_ID_WITH_CONTACT = "TheRegistrar";
/** Client ID of a REAL registrar without a RegistrarContact. */ /** Registrar ID of a REAL registrar without a RegistrarContact. */
private static final String REAL_CLIENT_ID_WITHOUT_CONTACT = "NewRegistrar"; private static final String REAL_REGISTRAR_ID_WITHOUT_CONTACT = "NewRegistrar";
/** Client ID of an OTE registrar without a RegistrarContact. */ /** Registrar ID of an OTE registrar without a RegistrarContact. */
private static final String OTE_CLIENT_ID_WITHOUT_CONTACT = "OteRegistrar"; private static final String OTE_REGISTRAR_ID_WITHOUT_CONTACT = "OteRegistrar";
/** Client ID of the Admin registrar without a RegistrarContact. */ /** Registrar ID of the Admin registrar without a RegistrarContact. */
private static final String ADMIN_CLIENT_ID = "AdminRegistrar"; private static final String ADMIN_REGISTRAR_ID = "AdminRegistrar";
/** /**
* Creates an AuthResult for a fake user. * Creates an AuthResult for a fake user.
@ -104,18 +108,17 @@ class AuthenticatedRegistrarAccessorTest {
void beforeEach() { void beforeEach() {
when(lazyGroupsConnection.get()).thenReturn(groupsConnection); when(lazyGroupsConnection.get()).thenReturn(groupsConnection);
JdkLoggerConfig.getConfig(AuthenticatedRegistrarAccessor.class).addHandler(testLogHandler); JdkLoggerConfig.getConfig(AuthenticatedRegistrarAccessor.class).addHandler(testLogHandler);
// persistResource(loadRegistrar(ADMIN_CLIENT_ID));
persistResource( persistResource(
loadRegistrar(REAL_CLIENT_ID_WITHOUT_CONTACT) loadRegistrar(REAL_REGISTRAR_ID_WITHOUT_CONTACT)
.asBuilder() .asBuilder()
.setRegistrarId(OTE_CLIENT_ID_WITHOUT_CONTACT) .setRegistrarId(OTE_REGISTRAR_ID_WITHOUT_CONTACT)
.setType(Registrar.Type.OTE) .setType(Registrar.Type.OTE)
.setIanaIdentifier(null) .setIanaIdentifier(null)
.build()); .build());
persistResource( persistResource(
loadRegistrar(REAL_CLIENT_ID_WITHOUT_CONTACT) loadRegistrar(REAL_REGISTRAR_ID_WITHOUT_CONTACT)
.asBuilder() .asBuilder()
.setRegistrarId(ADMIN_CLIENT_ID) .setRegistrarId(ADMIN_REGISTRAR_ID)
.setType(Registrar.Type.OTE) .setType(Registrar.Type.OTE)
.setIanaIdentifier(null) .setIanaIdentifier(null)
.build()); .build());
@ -129,24 +132,24 @@ class AuthenticatedRegistrarAccessorTest {
/** Users are owners for registrars if and only if they are in the contacts for that registrar. */ /** Users are owners for registrars if and only if they are in the contacts for that registrar. */
@Test @Test
void getAllClientIdWithAccess_user() { void getAllRegistrarIdWithAccess_user() {
AuthenticatedRegistrarAccessor registrarAccessor = AuthenticatedRegistrarAccessor registrarAccessor =
new AuthenticatedRegistrarAccessor( new AuthenticatedRegistrarAccessor(
USER, ADMIN_CLIENT_ID, SUPPORT_GROUP, lazyGroupsConnection); USER, ADMIN_REGISTRAR_ID, SUPPORT_GROUP, lazyGroupsConnection);
assertThat(registrarAccessor.getAllClientIdWithRoles()) assertThat(registrarAccessor.getAllRegistrarIdsWithRoles())
.containsExactly(CLIENT_ID_WITH_CONTACT, OWNER); .containsExactly(REGISTRAR_ID_WITH_CONTACT, OWNER);
verify(lazyGroupsConnection).get(); verify(lazyGroupsConnection).get();
} }
/** Logged-out users don't have access to anything. */ /** Logged-out users don't have access to anything. */
@Test @Test
void getAllClientIdWithAccess_loggedOutUser() { void getAllRegistrarIdWithAccess_loggedOutUser() {
AuthenticatedRegistrarAccessor registrarAccessor = AuthenticatedRegistrarAccessor registrarAccessor =
new AuthenticatedRegistrarAccessor( new AuthenticatedRegistrarAccessor(
NO_USER, ADMIN_CLIENT_ID, SUPPORT_GROUP, lazyGroupsConnection); NO_USER, ADMIN_REGISTRAR_ID, SUPPORT_GROUP, lazyGroupsConnection);
assertThat(registrarAccessor.getAllClientIdWithRoles()).isEmpty(); assertThat(registrarAccessor.getAllRegistrarIdsWithRoles()).isEmpty();
verifyNoInteractions(lazyGroupsConnection); verifyNoInteractions(lazyGroupsConnection);
} }
@ -162,23 +165,20 @@ class AuthenticatedRegistrarAccessorTest {
* <p>(in other words - they don't have OWNER access only to REAL registrars owned by others) * <p>(in other words - they don't have OWNER access only to REAL registrars owned by others)
*/ */
@Test @Test
void getAllClientIdWithAccess_gaeAdmin() { void getAllRegistrarIdWithAccess_gaeAdmin() {
AuthenticatedRegistrarAccessor registrarAccessor = AuthenticatedRegistrarAccessor registrarAccessor =
new AuthenticatedRegistrarAccessor( new AuthenticatedRegistrarAccessor(
GAE_ADMIN, ADMIN_CLIENT_ID, SUPPORT_GROUP, lazyGroupsConnection); GAE_ADMIN, ADMIN_REGISTRAR_ID, SUPPORT_GROUP, lazyGroupsConnection);
assertThat(registrarAccessor.getAllClientIdWithRoles()) assertThat(registrarAccessor.getAllRegistrarIdsWithRoles())
.containsExactly( .containsExactly(
CLIENT_ID_WITH_CONTACT, ADMIN, REGISTRAR_ID_WITH_CONTACT, ADMIN,
CLIENT_ID_WITH_CONTACT, OWNER, REGISTRAR_ID_WITH_CONTACT, OWNER,
REAL_REGISTRAR_ID_WITHOUT_CONTACT, ADMIN,
REAL_CLIENT_ID_WITHOUT_CONTACT, ADMIN, OTE_REGISTRAR_ID_WITHOUT_CONTACT, ADMIN,
OTE_REGISTRAR_ID_WITHOUT_CONTACT, OWNER,
OTE_CLIENT_ID_WITHOUT_CONTACT, ADMIN, ADMIN_REGISTRAR_ID, ADMIN,
OTE_CLIENT_ID_WITHOUT_CONTACT, OWNER, ADMIN_REGISTRAR_ID, OWNER);
ADMIN_CLIENT_ID, ADMIN,
ADMIN_CLIENT_ID, OWNER);
verifyNoInteractions(lazyGroupsConnection); verifyNoInteractions(lazyGroupsConnection);
} }
@ -194,51 +194,48 @@ class AuthenticatedRegistrarAccessorTest {
* <p>(in other words - they don't have OWNER access only to REAL registrars owned by others) * <p>(in other words - they don't have OWNER access only to REAL registrars owned by others)
*/ */
@Test @Test
void getAllClientIdWithAccess_userInSupportGroup() { void getAllRegistrarIdWithAccess_userInSupportGroup() {
when(groupsConnection.isMemberOfGroup("user@gmail.com", SUPPORT_GROUP.get())).thenReturn(true); when(groupsConnection.isMemberOfGroup("user@gmail.com", SUPPORT_GROUP.get())).thenReturn(true);
AuthenticatedRegistrarAccessor registrarAccessor = AuthenticatedRegistrarAccessor registrarAccessor =
new AuthenticatedRegistrarAccessor( new AuthenticatedRegistrarAccessor(
USER, ADMIN_CLIENT_ID, SUPPORT_GROUP, lazyGroupsConnection); USER, ADMIN_REGISTRAR_ID, SUPPORT_GROUP, lazyGroupsConnection);
assertThat(registrarAccessor.getAllClientIdWithRoles()) assertThat(registrarAccessor.getAllRegistrarIdsWithRoles())
.containsExactly( .containsExactly(
CLIENT_ID_WITH_CONTACT, ADMIN, REGISTRAR_ID_WITH_CONTACT, ADMIN,
CLIENT_ID_WITH_CONTACT, OWNER, REGISTRAR_ID_WITH_CONTACT, OWNER,
REAL_REGISTRAR_ID_WITHOUT_CONTACT, ADMIN,
REAL_CLIENT_ID_WITHOUT_CONTACT, ADMIN, OTE_REGISTRAR_ID_WITHOUT_CONTACT, ADMIN,
OTE_REGISTRAR_ID_WITHOUT_CONTACT, OWNER,
OTE_CLIENT_ID_WITHOUT_CONTACT, ADMIN, ADMIN_REGISTRAR_ID, ADMIN,
OTE_CLIENT_ID_WITHOUT_CONTACT, OWNER, ADMIN_REGISTRAR_ID, OWNER);
ADMIN_CLIENT_ID, ADMIN,
ADMIN_CLIENT_ID, OWNER);
verify(lazyGroupsConnection).get(); verify(lazyGroupsConnection).get();
} }
/** Empty Support group email - skips check and doesn't generate the lazy. */ /** Empty Support group email - skips check and doesn't generate the lazy. */
@Test @Test
void getAllClientIdWithAccess_emptySupportEmail_works() { void getAllRegistrarIdWithAccess_emptySupportEmail_works() {
AuthenticatedRegistrarAccessor registrarAccessor = AuthenticatedRegistrarAccessor registrarAccessor =
new AuthenticatedRegistrarAccessor( new AuthenticatedRegistrarAccessor(
USER, ADMIN_CLIENT_ID, Optional.empty(), lazyGroupsConnection); USER, ADMIN_REGISTRAR_ID, Optional.empty(), lazyGroupsConnection);
assertThat(registrarAccessor.getAllClientIdWithRoles()) assertThat(registrarAccessor.getAllRegistrarIdsWithRoles())
.containsExactly(CLIENT_ID_WITH_CONTACT, OWNER); .containsExactly(REGISTRAR_ID_WITH_CONTACT, OWNER);
// Make sure we didn't instantiate the lazyGroupsConnection // Make sure we didn't instantiate the lazyGroupsConnection
verifyNoInteractions(lazyGroupsConnection); verifyNoInteractions(lazyGroupsConnection);
} }
/** Support group check throws - continue anyway. */ /** Support group check throws - continue anyway. */
@Test @Test
void getAllClientIdWithAccess_throwingGroupCheck_stillWorks() { void getAllRegistrarIdWithAccess_throwingGroupCheck_stillWorks() {
when(groupsConnection.isMemberOfGroup(any(), any())).thenThrow(new RuntimeException("blah")); when(groupsConnection.isMemberOfGroup(any(), any())).thenThrow(new RuntimeException("blah"));
AuthenticatedRegistrarAccessor registrarAccessor = AuthenticatedRegistrarAccessor registrarAccessor =
new AuthenticatedRegistrarAccessor( new AuthenticatedRegistrarAccessor(
USER, ADMIN_CLIENT_ID, SUPPORT_GROUP, lazyGroupsConnection); USER, ADMIN_REGISTRAR_ID, SUPPORT_GROUP, lazyGroupsConnection);
verify(groupsConnection).isMemberOfGroup("user@gmail.com", SUPPORT_GROUP.get()); verify(groupsConnection).isMemberOfGroup("user@gmail.com", SUPPORT_GROUP.get());
assertThat(registrarAccessor.getAllClientIdWithRoles()) assertThat(registrarAccessor.getAllRegistrarIdsWithRoles())
.containsExactly(CLIENT_ID_WITH_CONTACT, OWNER); .containsExactly(REGISTRAR_ID_WITH_CONTACT, OWNER);
verify(lazyGroupsConnection).get(); verify(lazyGroupsConnection).get();
} }
@ -246,7 +243,7 @@ class AuthenticatedRegistrarAccessorTest {
@Test @Test
void testGetRegistrarForUser_noAccess_isNotAdmin() { void testGetRegistrarForUser_noAccess_isNotAdmin() {
expectGetRegistrarFailure( expectGetRegistrarFailure(
REAL_CLIENT_ID_WITHOUT_CONTACT, REAL_REGISTRAR_ID_WITHOUT_CONTACT,
USER, USER,
"user user@gmail.com doesn't have access to registrar NewRegistrar"); "user user@gmail.com doesn't have access to registrar NewRegistrar");
verify(lazyGroupsConnection).get(); verify(lazyGroupsConnection).get();
@ -261,7 +258,7 @@ class AuthenticatedRegistrarAccessorTest {
.setState(State.DISABLED) .setState(State.DISABLED)
.build()); .build());
expectGetRegistrarFailure( expectGetRegistrarFailure(
CLIENT_ID_WITH_CONTACT, REGISTRAR_ID_WITH_CONTACT,
USER, USER,
"user user@gmail.com doesn't have access to registrar TheRegistrar"); "user user@gmail.com doesn't have access to registrar TheRegistrar");
verify(lazyGroupsConnection).get(); verify(lazyGroupsConnection).get();
@ -271,7 +268,7 @@ class AuthenticatedRegistrarAccessorTest {
@Test @Test
void testGetRegistrarForUser_noAccess_isNotAdmin_notReal() { void testGetRegistrarForUser_noAccess_isNotAdmin_notReal() {
expectGetRegistrarFailure( expectGetRegistrarFailure(
OTE_CLIENT_ID_WITHOUT_CONTACT, OTE_REGISTRAR_ID_WITHOUT_CONTACT,
USER, USER,
"user user@gmail.com doesn't have access to registrar OteRegistrar"); "user user@gmail.com doesn't have access to registrar OteRegistrar");
verify(lazyGroupsConnection).get(); verify(lazyGroupsConnection).get();
@ -281,7 +278,7 @@ class AuthenticatedRegistrarAccessorTest {
@Test @Test
void testGetRegistrarForUser_noUser() { void testGetRegistrarForUser_noUser() {
expectGetRegistrarFailure( expectGetRegistrarFailure(
CLIENT_ID_WITH_CONTACT, REGISTRAR_ID_WITH_CONTACT,
NO_USER, NO_USER,
"<logged-out user> doesn't have access to registrar TheRegistrar"); "<logged-out user> doesn't have access to registrar TheRegistrar");
verifyNoInteractions(lazyGroupsConnection); verifyNoInteractions(lazyGroupsConnection);
@ -291,7 +288,7 @@ class AuthenticatedRegistrarAccessorTest {
@Test @Test
void testGetRegistrarForUser_inContacts_isNotAdmin() throws Exception { void testGetRegistrarForUser_inContacts_isNotAdmin() throws Exception {
expectGetRegistrarSuccess( expectGetRegistrarSuccess(
CLIENT_ID_WITH_CONTACT, REGISTRAR_ID_WITH_CONTACT,
USER, USER,
"user user@gmail.com has [OWNER] access to registrar TheRegistrar"); "user user@gmail.com has [OWNER] access to registrar TheRegistrar");
verify(lazyGroupsConnection).get(); verify(lazyGroupsConnection).get();
@ -301,7 +298,7 @@ class AuthenticatedRegistrarAccessorTest {
@Test @Test
void testGetRegistrarForUser_inContacts_isAdmin() throws Exception { void testGetRegistrarForUser_inContacts_isAdmin() throws Exception {
expectGetRegistrarSuccess( expectGetRegistrarSuccess(
CLIENT_ID_WITH_CONTACT, REGISTRAR_ID_WITH_CONTACT,
GAE_ADMIN, GAE_ADMIN,
"admin admin@gmail.com has [OWNER, ADMIN] access to registrar TheRegistrar"); "admin admin@gmail.com has [OWNER, ADMIN] access to registrar TheRegistrar");
verifyNoInteractions(lazyGroupsConnection); verifyNoInteractions(lazyGroupsConnection);
@ -311,7 +308,7 @@ class AuthenticatedRegistrarAccessorTest {
@Test @Test
void testGetRegistrarForUser_notInContacts_isAdmin() throws Exception { void testGetRegistrarForUser_notInContacts_isAdmin() throws Exception {
expectGetRegistrarSuccess( expectGetRegistrarSuccess(
REAL_CLIENT_ID_WITHOUT_CONTACT, REAL_REGISTRAR_ID_WITHOUT_CONTACT,
GAE_ADMIN, GAE_ADMIN,
"admin admin@gmail.com has [ADMIN] access to registrar NewRegistrar."); "admin admin@gmail.com has [ADMIN] access to registrar NewRegistrar.");
verifyNoInteractions(lazyGroupsConnection); verifyNoInteractions(lazyGroupsConnection);
@ -326,7 +323,7 @@ class AuthenticatedRegistrarAccessorTest {
.setState(State.DISABLED) .setState(State.DISABLED)
.build()); .build());
expectGetRegistrarSuccess( expectGetRegistrarSuccess(
REAL_CLIENT_ID_WITHOUT_CONTACT, REAL_REGISTRAR_ID_WITHOUT_CONTACT,
GAE_ADMIN, GAE_ADMIN,
"admin admin@gmail.com has [OWNER, ADMIN] access to registrar NewRegistrar."); "admin admin@gmail.com has [OWNER, ADMIN] access to registrar NewRegistrar.");
verifyNoInteractions(lazyGroupsConnection); verifyNoInteractions(lazyGroupsConnection);
@ -336,7 +333,7 @@ class AuthenticatedRegistrarAccessorTest {
@Test @Test
void testGetRegistrarForUser_notInContacts_isAdmin_notReal() throws Exception { void testGetRegistrarForUser_notInContacts_isAdmin_notReal() throws Exception {
expectGetRegistrarSuccess( expectGetRegistrarSuccess(
OTE_CLIENT_ID_WITHOUT_CONTACT, OTE_REGISTRAR_ID_WITHOUT_CONTACT,
GAE_ADMIN, GAE_ADMIN,
"admin admin@gmail.com has [OWNER, ADMIN] access to registrar OteRegistrar."); "admin admin@gmail.com has [OWNER, ADMIN] access to registrar OteRegistrar.");
verifyNoInteractions(lazyGroupsConnection); verifyNoInteractions(lazyGroupsConnection);
@ -346,9 +343,7 @@ class AuthenticatedRegistrarAccessorTest {
@Test @Test
void testGetRegistrarForUser_doesntExist_isAdmin() { void testGetRegistrarForUser_doesntExist_isAdmin() {
expectGetRegistrarFailure( expectGetRegistrarFailure(
"BadClientId", "BadRegistrarId", GAE_ADMIN, "Registrar BadRegistrarId does not exist");
GAE_ADMIN,
"Registrar BadClientId does not exist");
verifyNoInteractions(lazyGroupsConnection); verifyNoInteractions(lazyGroupsConnection);
} }
@ -356,7 +351,7 @@ class AuthenticatedRegistrarAccessorTest {
throws Exception { throws Exception {
AuthenticatedRegistrarAccessor registrarAccessor = AuthenticatedRegistrarAccessor registrarAccessor =
new AuthenticatedRegistrarAccessor( new AuthenticatedRegistrarAccessor(
authResult, ADMIN_CLIENT_ID, SUPPORT_GROUP, lazyGroupsConnection); authResult, ADMIN_REGISTRAR_ID, SUPPORT_GROUP, lazyGroupsConnection);
// make sure loading the registrar succeeds and returns a value // make sure loading the registrar succeeds and returns a value
assertThat(registrarAccessor.getRegistrar(registrarId)).isNotNull(); assertThat(registrarAccessor.getRegistrar(registrarId)).isNotNull();
@ -367,7 +362,7 @@ class AuthenticatedRegistrarAccessorTest {
String registrarId, AuthResult authResult, String message) { String registrarId, AuthResult authResult, String message) {
AuthenticatedRegistrarAccessor registrarAccessor = AuthenticatedRegistrarAccessor registrarAccessor =
new AuthenticatedRegistrarAccessor( new AuthenticatedRegistrarAccessor(
authResult, ADMIN_CLIENT_ID, SUPPORT_GROUP, lazyGroupsConnection); authResult, ADMIN_REGISTRAR_ID, SUPPORT_GROUP, lazyGroupsConnection);
// make sure getRegistrar fails // make sure getRegistrar fails
RegistrarAccessDeniedException exception = RegistrarAccessDeniedException exception =
@ -378,27 +373,27 @@ class AuthenticatedRegistrarAccessorTest {
assertThat(exception).hasMessageThat().contains(message); assertThat(exception).hasMessageThat().contains(message);
} }
/** guessClientIdForUser returns the first clientId in getAllClientIdWithRoles. */ /** guessRegistrarIdForUser returns the first registrarId in getAllRegistrarIdWithRoles. */
@Test @Test
void testGuessClientIdForUser_hasAccess_returnsFirst() throws Exception { void testGuessRegistrarIdForUser_hasAccess_returnsFirst() throws Exception {
AuthenticatedRegistrarAccessor registrarAccessor = AuthenticatedRegistrarAccessor registrarAccessor =
AuthenticatedRegistrarAccessor.createForTesting( AuthenticatedRegistrarAccessor.createForTesting(
ImmutableSetMultimap.of( ImmutableSetMultimap.of(
"clientId-1", OWNER, "registrarId-1", OWNER,
"clientId-2", OWNER, "registrarId-2", OWNER,
"clientId-2", ADMIN)); "registrarId-2", ADMIN));
assertThat(registrarAccessor.guessClientId()).isEqualTo("clientId-1"); assertThat(registrarAccessor.guessRegistrarId()).isEqualTo("registrarId-1");
} }
/** If a user doesn't have access to any registrars, guess fails. */ /** If a user doesn't have access to any registrars, guess fails. */
@Test @Test
void testGuessClientIdForUser_noAccess_fails() { void testGuessRegistrarIdForUser_noAccess_fails() {
AuthenticatedRegistrarAccessor registrarAccessor = AuthenticatedRegistrarAccessor registrarAccessor =
AuthenticatedRegistrarAccessor.createForTesting(ImmutableSetMultimap.of()); AuthenticatedRegistrarAccessor.createForTesting(ImmutableSetMultimap.of());
RegistrarAccessDeniedException thrown = RegistrarAccessDeniedException thrown =
assertThrows(RegistrarAccessDeniedException.class, registrarAccessor::guessClientId); assertThrows(RegistrarAccessDeniedException.class, registrarAccessor::guessRegistrarId);
assertThat(thrown).hasMessageThat().isEqualTo("TestUserId isn't associated with any registrar"); assertThat(thrown).hasMessageThat().isEqualTo("TestUserId isn't associated with any registrar");
} }
@ -409,4 +404,75 @@ class AuthenticatedRegistrarAccessorTest {
.setDefault(HttpServletResponse.class, rsp) .setDefault(HttpServletResponse.class, rsp)
.testAllPublicStaticMethods(AuthenticatedRegistrarAccessor.class); .testAllPublicStaticMethods(AuthenticatedRegistrarAccessor.class);
} }
@Test
void testConsoleUser_admin() {
google.registry.model.console.User consoleUser =
new google.registry.model.console.User.Builder()
.setGaiaId("gaiaId")
.setEmailAddress("email@email.com")
.setUserRoles(
new UserRoles.Builder().setIsAdmin(true).setGlobalRole(GlobalRole.FTE).build())
.build();
AuthResult authResult = AuthResult.create(AuthLevel.USER, UserAuthInfo.create(consoleUser));
AuthenticatedRegistrarAccessor registrarAccessor =
new AuthenticatedRegistrarAccessor(
authResult, ADMIN_REGISTRAR_ID, SUPPORT_GROUP, lazyGroupsConnection);
// Admin access to all, and owner access to the non-real registrar and the admin registrar
assertThat(registrarAccessor.getAllRegistrarIdsWithRoles())
.containsExactly(
REGISTRAR_ID_WITH_CONTACT, ADMIN,
REAL_REGISTRAR_ID_WITHOUT_CONTACT, ADMIN,
OTE_REGISTRAR_ID_WITHOUT_CONTACT, ADMIN,
OTE_REGISTRAR_ID_WITHOUT_CONTACT, OWNER,
ADMIN_REGISTRAR_ID, ADMIN,
ADMIN_REGISTRAR_ID, OWNER);
}
@Test
void testConsoleUser_globalRole() {
// Users with global roles shouldn't necessarily have access to specific registrars if they're
// not admins
google.registry.model.console.User consoleUser =
new google.registry.model.console.User.Builder()
.setGaiaId("gaiaId")
.setEmailAddress("email@email.com")
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.SUPPORT_AGENT).build())
.build();
AuthResult authResult = AuthResult.create(AuthLevel.USER, UserAuthInfo.create(consoleUser));
AuthenticatedRegistrarAccessor registrarAccessor =
new AuthenticatedRegistrarAccessor(
authResult, ADMIN_REGISTRAR_ID, SUPPORT_GROUP, lazyGroupsConnection);
// Explicit access to registrars is required for non-admins
assertThat(registrarAccessor.getAllRegistrarIdsWithRoles()).isEmpty();
}
@Test
void testConsoleUser_registrarRoles() {
// Registrar employees should have OWNER access to their registrars
google.registry.model.console.User consoleUser =
new google.registry.model.console.User.Builder()
.setGaiaId("gaiaId")
.setEmailAddress("email@email.com")
.setUserRoles(
new UserRoles.Builder()
.setRegistrarRoles(
ImmutableMap.of(
REGISTRAR_ID_WITH_CONTACT,
RegistrarRole.ACCOUNT_MANAGER,
REAL_REGISTRAR_ID_WITHOUT_CONTACT,
RegistrarRole.ACCOUNT_MANAGER))
.build())
.build();
AuthResult authResult = AuthResult.create(AuthLevel.USER, UserAuthInfo.create(consoleUser));
AuthenticatedRegistrarAccessor registrarAccessor =
new AuthenticatedRegistrarAccessor(
authResult, ADMIN_REGISTRAR_ID, SUPPORT_GROUP, lazyGroupsConnection);
assertThat(registrarAccessor.getAllRegistrarIdsWithRoles())
.containsExactly(
REGISTRAR_ID_WITH_CONTACT, OWNER,
REAL_REGISTRAR_ID_WITHOUT_CONTACT, OWNER);
}
} }

View file

@ -209,7 +209,7 @@ class RequestAuthenticatorTest {
assertThat(authResult).isPresent(); assertThat(authResult).isPresent();
assertThat(authResult.get().authLevel()).isEqualTo(AuthLevel.USER); assertThat(authResult.get().authLevel()).isEqualTo(AuthLevel.USER);
assertThat(authResult.get().userAuthInfo()).isPresent(); assertThat(authResult.get().userAuthInfo()).isPresent();
assertThat(authResult.get().userAuthInfo().get().user()).isEqualTo(testUser); assertThat(authResult.get().userAuthInfo().get().appEngineUser()).hasValue(testUser);
assertThat(authResult.get().userAuthInfo().get().isUserAdmin()).isFalse(); assertThat(authResult.get().userAuthInfo().get().isUserAdmin()).isFalse();
assertThat(authResult.get().userAuthInfo().get().oauthTokenInfo()).isEmpty(); assertThat(authResult.get().userAuthInfo().get().oauthTokenInfo()).isEmpty();
} }
@ -224,7 +224,7 @@ class RequestAuthenticatorTest {
assertThat(authResult).isPresent(); assertThat(authResult).isPresent();
assertThat(authResult.get().authLevel()).isEqualTo(AuthLevel.USER); assertThat(authResult.get().authLevel()).isEqualTo(AuthLevel.USER);
assertThat(authResult.get().userAuthInfo()).isPresent(); assertThat(authResult.get().userAuthInfo()).isPresent();
assertThat(authResult.get().userAuthInfo().get().user()).isEqualTo(testUser); assertThat(authResult.get().userAuthInfo().get().appEngineUser()).hasValue(testUser);
assertThat(authResult.get().userAuthInfo().get().oauthTokenInfo()).isEmpty(); assertThat(authResult.get().userAuthInfo().get().oauthTokenInfo()).isEmpty();
} }
@ -264,7 +264,7 @@ class RequestAuthenticatorTest {
assertThat(authResult).isPresent(); assertThat(authResult).isPresent();
assertThat(authResult.get().authLevel()).isEqualTo(AuthLevel.USER); assertThat(authResult.get().authLevel()).isEqualTo(AuthLevel.USER);
assertThat(authResult.get().userAuthInfo()).isPresent(); assertThat(authResult.get().userAuthInfo()).isPresent();
assertThat(authResult.get().userAuthInfo().get().user()).isEqualTo(testUser); assertThat(authResult.get().userAuthInfo().get().appEngineUser()).hasValue(testUser);
assertThat(authResult.get().userAuthInfo().get().isUserAdmin()).isTrue(); assertThat(authResult.get().userAuthInfo().get().isUserAdmin()).isTrue();
assertThat(authResult.get().userAuthInfo().get().oauthTokenInfo()).isEmpty(); assertThat(authResult.get().userAuthInfo().get().oauthTokenInfo()).isEmpty();
} }
@ -280,7 +280,7 @@ class RequestAuthenticatorTest {
assertThat(authResult).isPresent(); assertThat(authResult).isPresent();
assertThat(authResult.get().authLevel()).isEqualTo(AuthLevel.USER); assertThat(authResult.get().authLevel()).isEqualTo(AuthLevel.USER);
assertThat(authResult.get().userAuthInfo()).isPresent(); assertThat(authResult.get().userAuthInfo()).isPresent();
assertThat(authResult.get().userAuthInfo().get().user()).isEqualTo(testUser); assertThat(authResult.get().userAuthInfo().get().appEngineUser()).hasValue(testUser);
assertThat(authResult.get().userAuthInfo().get().isUserAdmin()).isFalse(); assertThat(authResult.get().userAuthInfo().get().isUserAdmin()).isFalse();
assertThat(authResult.get().userAuthInfo().get().oauthTokenInfo()).isPresent(); assertThat(authResult.get().userAuthInfo().get().oauthTokenInfo()).isPresent();
assertThat(authResult.get().userAuthInfo().get().oauthTokenInfo().get().authorizedScopes()) assertThat(authResult.get().userAuthInfo().get().oauthTokenInfo().get().authorizedScopes())
@ -303,7 +303,7 @@ class RequestAuthenticatorTest {
assertThat(authResult).isPresent(); assertThat(authResult).isPresent();
assertThat(authResult.get().authLevel()).isEqualTo(AuthLevel.USER); assertThat(authResult.get().authLevel()).isEqualTo(AuthLevel.USER);
assertThat(authResult.get().userAuthInfo()).isPresent(); assertThat(authResult.get().userAuthInfo()).isPresent();
assertThat(authResult.get().userAuthInfo().get().user()).isEqualTo(testUser); assertThat(authResult.get().userAuthInfo().get().appEngineUser()).hasValue(testUser);
assertThat(authResult.get().userAuthInfo().get().isUserAdmin()).isTrue(); assertThat(authResult.get().userAuthInfo().get().isUserAdmin()).isTrue();
assertThat(authResult.get().userAuthInfo().get().oauthTokenInfo()).isPresent(); assertThat(authResult.get().userAuthInfo().get().oauthTokenInfo()).isPresent();
assertThat(authResult.get().userAuthInfo().get().oauthTokenInfo().get().authorizedScopes()) assertThat(authResult.get().userAuthInfo().get().oauthTokenInfo().get().authorizedScopes())
@ -372,7 +372,7 @@ class RequestAuthenticatorTest {
assertThat(authResult).isPresent(); assertThat(authResult).isPresent();
assertThat(authResult.get().authLevel()).isEqualTo(AuthLevel.USER); assertThat(authResult.get().authLevel()).isEqualTo(AuthLevel.USER);
assertThat(authResult.get().userAuthInfo()).isPresent(); assertThat(authResult.get().userAuthInfo()).isPresent();
assertThat(authResult.get().userAuthInfo().get().user()).isEqualTo(testUser); assertThat(authResult.get().userAuthInfo().get().appEngineUser()).hasValue(testUser);
assertThat(authResult.get().userAuthInfo().get().isUserAdmin()).isFalse(); assertThat(authResult.get().userAuthInfo().get().isUserAdmin()).isFalse();
assertThat(authResult.get().userAuthInfo().get().oauthTokenInfo()).isPresent(); assertThat(authResult.get().userAuthInfo().get().oauthTokenInfo()).isPresent();
assertThat(authResult.get().userAuthInfo().get().oauthTokenInfo().get().authorizedScopes()) assertThat(authResult.get().userAuthInfo().get().oauthTokenInfo().get().authorizedScopes())

View file

@ -32,6 +32,8 @@ import com.google.common.collect.ImmutableList;
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.gson.Gson; import com.google.gson.Gson;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.UserRoles;
import google.registry.model.domain.RegistryLock; import google.registry.model.domain.RegistryLock;
import google.registry.model.registrar.RegistrarPoc; import google.registry.model.registrar.RegistrarPoc;
import google.registry.request.Action.Method; import google.registry.request.Action.Method;
@ -83,6 +85,60 @@ final class RegistryLockGetActionTest {
Method.GET, response, accessor, authResult, Optional.of("TheRegistrar")); Method.GET, response, accessor, authResult, Optional.of("TheRegistrar"));
} }
@Test
void testSuccess_newConsoleUser() {
RegistryLock regularLock =
new RegistryLock.Builder()
.setRepoId("repoId")
.setDomainName("example.test")
.setRegistrarId("TheRegistrar")
.setVerificationCode("123456789ABCDEFGHJKLMNPQRSTUVWXY")
.setRegistrarPocId("johndoe@theregistrar.com")
.setLockCompletionTime(fakeClock.nowUtc())
.build();
saveRegistryLock(regularLock);
google.registry.model.console.User consoleUser =
new google.registry.model.console.User.Builder()
.setEmailAddress("johndoe@theregistrar.com")
.setGaiaId("gaiaId")
.setUserRoles(
new UserRoles.Builder()
.setRegistrarRoles(
ImmutableMap.of(
"TheRegistrar", RegistrarRole.ACCOUNT_MANAGER_WITH_REGISTRY_LOCK))
.build())
.build();
action.authResult = AuthResult.create(AuthLevel.USER, UserAuthInfo.create(consoleUser));
action.run();
assertThat(response.getStatus()).isEqualTo(HttpStatusCodes.STATUS_CODE_OK);
assertThat(GSON.fromJson(response.getPayload(), Map.class))
.containsExactly(
"status",
"SUCCESS",
"message",
"Successful locks retrieval",
"results",
ImmutableList.of(
ImmutableMap.of(
"lockEnabledForContact",
true,
"email",
"johndoe@theregistrar.com",
"clientId",
"TheRegistrar",
"locks",
ImmutableList.of(
new ImmutableMap.Builder<>()
.put("domainName", "example.test")
.put("lockedTime", "2000-06-08T22:00:00.000Z")
.put("lockedBy", "johndoe@theregistrar.com")
.put("isLockPending", false)
.put("isUnlockPending", false)
.put("userCanUnlock", true)
.build()))));
}
@Test @Test
void testSuccess_retrievesLocks() { void testSuccess_retrievesLocks() {
RegistryLock expiredLock = RegistryLock expiredLock =

View file

@ -33,6 +33,8 @@ import com.google.appengine.api.users.User;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.UserRoles;
import google.registry.model.domain.Domain; import google.registry.model.domain.Domain;
import google.registry.model.domain.RegistryLock; import google.registry.model.domain.RegistryLock;
import google.registry.request.JsonActionRunner; import google.registry.request.JsonActionRunner;
@ -222,6 +224,47 @@ final class RegistryLockPostActionTest {
assertSuccess(response, "lock", "johndoe@theregistrar.com"); assertSuccess(response, "lock", "johndoe@theregistrar.com");
} }
@Test
void testSuccess_consoleUser() throws Exception {
google.registry.model.console.User consoleUser =
new google.registry.model.console.User.Builder()
.setEmailAddress("johndoe@theregistrar.com")
.setGaiaId("gaiaId")
.setUserRoles(
new UserRoles.Builder()
.setRegistrarRoles(
ImmutableMap.of(
"TheRegistrar", RegistrarRole.ACCOUNT_MANAGER_WITH_REGISTRY_LOCK))
.build())
.setRegistryLockPassword("hi")
.build();
AuthResult consoleAuthResult =
AuthResult.create(AuthLevel.USER, UserAuthInfo.create(consoleUser));
action = createAction(consoleAuthResult);
Map<String, ?> response = action.handleJsonRequest(lockRequest());
assertSuccess(response, "lock", "johndoe@theregistrar.com");
}
@Test
void testSuccess_consoleUser_admin() throws Exception {
google.registry.model.console.User consoleUser =
new google.registry.model.console.User.Builder()
.setEmailAddress("johndoe@theregistrar.com")
.setGaiaId("gaiaId")
.setUserRoles(new UserRoles.Builder().setIsAdmin(true).build())
.build();
AuthResult consoleAuthResult =
AuthResult.create(AuthLevel.USER, UserAuthInfo.create(consoleUser));
action = createAction(consoleAuthResult);
Map<String, Object> requestMapWithoutPassword =
ImmutableMap.of(
"isLock", true,
"registrarId", "TheRegistrar",
"domainName", "example.tld");
Map<String, ?> response = action.handleJsonRequest(requestMapWithoutPassword);
assertSuccess(response, "lock", "johndoe@theregistrar.com");
}
@Test @Test
void testFailure_noInput() { void testFailure_noInput() {
Map<String, ?> response = action.handleJsonRequest(null); Map<String, ?> response = action.handleJsonRequest(null);
@ -397,6 +440,33 @@ final class RegistryLockPostActionTest {
assertFailureWithMessage(response, "Domain example.tld is already unlocked"); assertFailureWithMessage(response, "Domain example.tld is already unlocked");
} }
@Test
void testFailure_consoleUser_wrongPassword_noAdmin() {
google.registry.model.console.User consoleUser =
new google.registry.model.console.User.Builder()
.setEmailAddress("johndoe@theregistrar.com")
.setGaiaId("gaiaId")
.setUserRoles(
new UserRoles.Builder()
.setRegistrarRoles(
ImmutableMap.of(
"TheRegistrar", RegistrarRole.ACCOUNT_MANAGER_WITH_REGISTRY_LOCK))
.build())
.setRegistryLockPassword("hi")
.build();
AuthResult consoleAuthResult =
AuthResult.create(AuthLevel.USER, UserAuthInfo.create(consoleUser));
action = createAction(consoleAuthResult);
Map<String, ?> response =
action.handleJsonRequest(
ImmutableMap.of(
"registrarId", "TheRegistrar",
"domainName", "example.tld",
"isLock", true,
"password", "badPassword"));
assertFailureWithMessage(response, "Incorrect registry lock password for user");
}
private ImmutableMap<String, Object> lockRequest() { private ImmutableMap<String, Object> lockRequest() {
return fullRequest(true); return fullRequest(true);
} }