google-nomulus/java/google/registry/ui/server/registrar/SessionUtils.java
guyben 1d621bd14d Allow admins read-only access to all registrars
We want to be able to view / test / debug how the registrar console looks for our clients.

However, we don't want to accidentally change the data for registrars, especially in a "non-accountable" way (where we later don't know who did that change)

So we do 2 things here:

- Add a "mode" (read-only and read-write) to the getRegistrarForUser function. We set it according to what we want to do with the registrar. Currently, read-write is only requested for the "update" RegistrarSetting action. Admins will have read-only access to all registrars, but read-write access only to the "admin registrar" (or whatever registrar they are contacts for).

- Support an undocumented "clientId=XXX" query param that replaces the "guessClientIdForUser" function in the original page load. We can then set it when we want to view a different account.

We also change the navigation links on the HTML page to preserve the query.

-------------------------

This might be used also for a better user experience for our clients, especially those with multiple "clientId"s (some registrar entities have multiple "registrar" objects)

Currently, they have to have a separate user for each clientId, and only have one user allowed which has both read and write permissions.

Using this change, we can give them the possibility to add users on their own, some with read-only access (to view billing information without being able to change anything), and use a single user for all their clientIds.

-------------------------

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=215480610
2018-10-03 12:10:28 -04:00

199 lines
7.9 KiB
Java

// Copyright 2017 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 google.registry.model.ofy.ObjectifyService.ofy;
import com.google.appengine.api.users.User;
import com.google.common.base.Strings;
import com.google.common.flogger.FluentLogger;
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 java.util.Optional;
import java.util.function.Function;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
/** HTTP session management helper class. */
@Immutable
public class SessionUtils {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@Inject
@Config("registryAdminClientId")
String registryAdminClientId;
@Inject
public SessionUtils() {}
/** Type of access we're requesting. */
public enum AccessType {READ_ONLY, READ_WRITE}
/**
* Loads Registrar on behalf of an authorised user.
*
* <p>Throws a {@link ForbiddenException} if the user is not logged in, or not authorized to
* access the requested registrar.
*
* @param clientId ID of the registrar we request
* @param accessType what kind of access do we want for this registrar - just read it or write as
* well? (different users might have different access levels)
* @param authResult AuthResult of the user on behalf of which we want to access the data
*/
public Registrar getRegistrarForUser(
String clientId, AccessType accessType, AuthResult authResult) {
return getAndAuthorize(Registrar::loadByClientId, clientId, accessType, authResult);
}
/**
* Loads a Registrar from the cache on behalf of an authorised user.
*
* <p>Throws a {@link ForbiddenException} if the user is not logged in, or not authorized to
* access the requested registrar.
*
* @param clientId ID of the registrar we request
* @param accessType what kind of access do we want for this registrar - just read it or write as
* well? (different users might have different access levels)
* @param authResult AuthResult of the user on behalf of which we want to access the data
*/
public Registrar getRegistrarForUserCached(
String clientId, AccessType accessType, AuthResult authResult) {
return getAndAuthorize(Registrar::loadByClientIdCached, clientId, accessType, authResult);
}
private Registrar getAndAuthorize(
Function<String, Optional<Registrar>> registrarLoader,
String clientId,
AccessType accessType,
AuthResult authResult) {
UserAuthInfo userAuthInfo =
authResult.userAuthInfo().orElseThrow(() -> new ForbiddenException("Not logged in"));
boolean isAdmin = userAuthInfo.isUserAdmin();
User user = userAuthInfo.user();
String userIdForLogging = authResult.userIdForLogging();
Registrar registrar =
registrarLoader
.apply(clientId)
.orElseThrow(
() -> new ForbiddenException(String.format("Registrar %s not found", clientId)));
if (isInAllowedContacts(registrar, user)) {
logger.atInfo().log("User %s has access to registrar %s.", userIdForLogging, clientId);
return registrar;
}
if (isAdmin && clientId.equals(registryAdminClientId)) {
// Admins have access to the registryAdminClientId even if they aren't explicitly in the
// allowed contacts
logger.atInfo().log("Allowing admin %s access to registrar %s.", userIdForLogging, clientId);
return registrar;
}
if (isAdmin && accessType == AccessType.READ_ONLY) {
// Admins have read-only access to all registrars
logger.atInfo().log(
"Allowing admin %s read-only access to registrar %s.", userIdForLogging, clientId);
return registrar;
}
throw new ForbiddenException(
String.format(
"User %s doesn't have %s access to registrar %s",
userIdForLogging, accessType, clientId));
}
/**
* Tries to guess the {@link Registrar} with which the user is associated.
*
* <p>Returns the {@code clientId} of a {@link Registrar} the user has access to (is on the
* contact list). If the user has access to multiple {@link Registrar}s, an arbitrary one is
* selected. If the user is an admin without access to any {@link Registrar}s, {@link
* #registryAdminClientId} is returned if it is defined.
*
* <p>If no {@code clientId} is found, throws a {@link ForbiddenException}.
*
* <p>If you want to load the {@link Registrar} object from this (or any other) {@code clientId},
* in order to perform actions on behalf of a user, you must use {@link #getRegistrarForUser}
* 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
* other clue as to the requested {@code clientId}. It is perfectly OK to get a {@code clientId}
* from any other source, as long as the registrar is then loaded using {@link
* #getRegistrarForUser}.
*/
public String guessClientIdForUser(AuthResult authResult) {
UserAuthInfo userAuthInfo =
authResult.userAuthInfo().orElseThrow(() -> new ForbiddenException("No logged in"));
boolean isAdmin = userAuthInfo.isUserAdmin();
User user = userAuthInfo.user();
String userIdForLogging = authResult.userIdForLogging();
RegistrarContact contact =
ofy()
.load()
.type(RegistrarContact.class)
.filter("gaeUserId", user.getUserId())
.first()
.now();
if (contact != null) {
String registrarClientId = contact.getParent().getName();
logger.atInfo().log(
"Associating user %s with found registrar %s.", userIdForLogging, registrarClientId);
return registrarClientId;
}
// We couldn't find the registrar, but maybe the user is an admin and we can use the
// registryAdminClientId
if (isAdmin) {
if (!Strings.isNullOrEmpty(registryAdminClientId)) {
logger.atInfo().log(
"User %s is an admin with no associated registrar."
+ " Automatically associating the user with configured client Id %s.",
userIdForLogging, registryAdminClientId);
return registryAdminClientId;
}
logger.atInfo().log(
"Cannot associate admin user %s with configured client Id."
+ " ClientId is null or empty.",
userIdForLogging);
}
// We couldn't find any relevant clientId
throw new ForbiddenException(
String.format("User %s isn't associated with any registrar", userIdForLogging));
}
/**
* Returns {@code true} if {@code user} is listed in contacts with access to the registrar.
*
* <p>Each registrar contact can either have getGaeUserId equals null or the user's gaeUserId.
* Null means the contact doesn't have access to the registrar console. None-null means the
* contact has access.
*/
private static boolean isInAllowedContacts(Registrar registrar, User user) {
String gaeUserId = user.getUserId();
return registrar
.getContacts()
.stream()
.anyMatch(contact -> gaeUserId.equals(contact.getGaeUserId()));
}
}