getDesiredRegistrar() {
return registrarParam;
}
/**
* Returns true if the query should include deleted items.
*
* This is true only if the request specified an includeDeleted parameter of true, AND is
* eligible to see deleted information. Admins can see all deleted information, while
* authenticated registrars can see only their own deleted information. Note that if this method
* returns true, it just means that some deleted information might be viewable. If this is a
* registrar request, the caller must still verify that the registrar can see each particular
* item by calling {@link RdapAuthorization#isAuthorizedForClientId}.
*/
boolean shouldIncludeDeleted() {
// If includeDeleted is not specified, or set to false, we don't need to go any further.
if (!includeDeletedParam.orElse(false)) {
return false;
}
if (!authResult.userAuthInfo().isPresent()) {
return false;
}
UserAuthInfo userAuthInfo = authResult.userAuthInfo().get();
if (userAuthInfo.isUserAdmin()) {
return true;
}
if (!sessionUtils.checkRegistrarConsoleLogin(request, userAuthInfo)) {
return false;
}
String clientId = sessionUtils.getRegistrarClientId(request);
checkState(
Registrar.loadByClientIdCached(clientId).isPresent(),
"Registrar with clientId %s doesn't exist",
clientId);
return true;
}
/**
* Returns true if the request is authorized to see the resource.
*
*
This is true if the resource is not deleted, or the request wants to see deleted items, and
* is authorized to do so.
*/
boolean isAuthorized(EppResource eppResource, DateTime now) {
return now.isBefore(eppResource.getDeletionTime())
|| (shouldIncludeDeleted()
&& getAuthorization()
.isAuthorizedForClientId(eppResource.getPersistedCurrentSponsorClientId()));
}
/**
* Returns true if the EPP resource should be visible.
*
*
This is true iff:
* 1. The resource is not deleted, or the request wants to see deleted items, and is authorized to
* do so, and:
* 2. The request did not specify a registrar to filter on, or the registrar matches.
*/
boolean shouldBeVisible(EppResource eppResource, DateTime now) {
return isAuthorized(eppResource, now)
&& (!registrarParam.isPresent()
|| registrarParam.get().equals(eppResource.getPersistedCurrentSponsorClientId()));
}
/**
* Returns true if the registrar should be visible. This is true iff:
* 1. The resource is active and publicly visible, or the request wants to see deleted items, and
* is authorized to do so, and:
* 2. The request did not specify a registrar to filter on, or the registrar matches.
*/
boolean shouldBeVisible(Registrar registrar) {
return (registrar.isLiveAndPubliclyVisible()
|| (shouldIncludeDeleted()
&& getAuthorization().isAuthorizedForClientId(registrar.getClientId())))
&& (!registrarParam.isPresent() || registrarParam.get().equals(registrar.getClientId()));
}
String canonicalizeName(String name) {
name = canonicalizeDomainName(name);
if (name.endsWith(".")) {
name = name.substring(0, name.length() - 1);
}
return name;
}
/**
* Handles prefix searches in cases where, if we need to filter out deleted items, there are no
* pending deletes. In such cases, it is sufficient to check whether {@code deletionTime} is equal
* to {@code END_OF_TIME}, because any other value means it has already been deleted. This allows
* us to use an equality query for the deletion time.
*
* @param clazz the type of resource to be queried
* @param filterField the database field of interest
* @param partialStringQuery the details of the search string; if there is no wildcard, an
* equality query is used; if there is a wildcard, a range query is used instead; the
* initial string should not be empty, and any search suffix will be ignored, so the caller
* must filter the results if a suffix is specified
* @param includeDeleted whether to search for deleted items as well
* @param resultSetMaxSize the maximum number of results to return
* @return the results of the query
*/
static Query queryItems(
Class clazz,
String filterField,
RdapSearchPattern partialStringQuery,
boolean includeDeleted,
int resultSetMaxSize) {
if (partialStringQuery.getInitialString().length()
< RdapSearchPattern.MIN_INITIAL_STRING_LENGTH) {
throw new UnprocessableEntityException(
String.format(
"Initial search string must be at least %d characters",
RdapSearchPattern.MIN_INITIAL_STRING_LENGTH));
}
Query query = ofy().load().type(clazz);
if (!partialStringQuery.getHasWildcard()) {
query = query.filter(filterField, partialStringQuery.getInitialString());
} else {
// Ignore the suffix; the caller will need to filter on the suffix, if any.
query = query
.filter(filterField + " >=", partialStringQuery.getInitialString())
.filter(filterField + " <", partialStringQuery.getNextInitialString());
}
if (!includeDeleted) {
query = query.filter("deletionTime", END_OF_TIME);
}
return query.limit(resultSetMaxSize);
}
/** Variant of queryItems using a simple string rather than an {@link RdapSearchPattern}. */
static Query queryItems(
Class clazz,
String filterField,
String queryString,
boolean includeDeleted,
int resultSetMaxSize) {
if (queryString.length() < RdapSearchPattern.MIN_INITIAL_STRING_LENGTH) {
throw new UnprocessableEntityException(
String.format(
"Initial search string must be at least %d characters",
RdapSearchPattern.MIN_INITIAL_STRING_LENGTH));
}
Query query = ofy().load().type(clazz).filter(filterField, queryString);
return setOtherQueryAttributes(query, includeDeleted, resultSetMaxSize);
}
/** Variant of queryItems where the field to be searched is the key. */
static Query queryItemsByKey(
Class clazz,
RdapSearchPattern partialStringQuery,
boolean includeDeleted,
int resultSetMaxSize) {
if (partialStringQuery.getInitialString().length()
< RdapSearchPattern.MIN_INITIAL_STRING_LENGTH) {
throw new UnprocessableEntityException(
String.format(
"Initial search string must be at least %d characters",
RdapSearchPattern.MIN_INITIAL_STRING_LENGTH));
}
Query query = ofy().load().type(clazz);
if (!partialStringQuery.getHasWildcard()) {
query = query.filterKey("=", Key.create(clazz, partialStringQuery.getInitialString()));
} else {
// Ignore the suffix; the caller will need to filter on the suffix, if any.
query = query
.filterKey(">=", Key.create(clazz, partialStringQuery.getInitialString()))
.filterKey("<", Key.create(clazz, partialStringQuery.getNextInitialString()));
}
return setOtherQueryAttributes(query, includeDeleted, resultSetMaxSize);
}
/** Variant of queryItems searching for a key by a simple string. */
static Query queryItemsByKey(
Class clazz,
String queryString,
boolean includeDeleted,
int resultSetMaxSize) {
if (queryString.length() < RdapSearchPattern.MIN_INITIAL_STRING_LENGTH) {
throw new UnprocessableEntityException(
String.format(
"Initial search string must be at least %d characters",
RdapSearchPattern.MIN_INITIAL_STRING_LENGTH));
}
Query query = ofy().load().type(clazz).filterKey("=", Key.create(clazz, queryString));
return setOtherQueryAttributes(query, includeDeleted, resultSetMaxSize);
}
private static Query setOtherQueryAttributes(
Query query, boolean includeDeleted, int resultSetMaxSize) {
if (!includeDeleted) {
query = query.filter("deletionTime", END_OF_TIME);
}
return query.limit(resultSetMaxSize);
}
/**
* Runs the given query, and checks for permissioning if necessary.
*
* @param query an already-defined query to be run; a filter on currentSponsorClientId will be
* added if appropriate
* @param now the time as of which to evaluate the query
* @param checkForVisibility true if the results should be checked to make sure they are visible;
* normally this should be equal to the shouldIncludeDeleted setting, but in cases where
* the query could not check deletion status (due to Datastore limitations such as the
* limit of one field queried for inequality, for instance), it may need to be set to true
* even when not including deleted records
* @return an {@link RdapResultSet} object containing the list of
* resources and an incompleteness warning flag, which is set to MIGHT_BE_INCOMPLETE iff
* any resources were excluded due to lack of visibility, and the resulting list of
* resources is less than the maximum allowable, which indicates that we may not have
* fetched enough resources
*/
RdapResultSet getMatchingResources(
Query query, boolean checkForVisibility, DateTime now) {
Optional desiredRegistrar = getDesiredRegistrar();
if (desiredRegistrar.isPresent()) {
query = query.filter("currentSponsorClientId", desiredRegistrar.get());
}
if (!checkForVisibility) {
return RdapResultSet.create(query.list());
}
// If we are including deleted resources, we need to check that we're authorized for each one.
List resources = new ArrayList<>();
int numResourcesQueried = 0;
boolean someExcluded = false;
for (T resource : query) {
if (shouldBeVisible(resource, now)) {
resources.add(resource);
} else {
someExcluded = true;
}
numResourcesQueried++;
if (resources.size() > rdapResultSetMaxSize) {
break;
}
}
return RdapResultSet.create(
resources,
(someExcluded && (resources.size() < rdapResultSetMaxSize + 1))
? IncompletenessWarningType.MIGHT_BE_INCOMPLETE
: IncompletenessWarningType.COMPLETE,
numResourcesQueried);
}
}