/*
* RESTHeart - the Web API for MongoDB
* Copyright (C) SoftInstigate Srl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.restheart.handlers.metadata;
import io.undertow.server.HttpServerExchange;
import java.util.List;
import java.util.Objects;
import org.bson.BsonArray;
import org.bson.BsonDocument;
import org.bson.BsonValue;
import org.restheart.metadata.checkers.RequestChecker;
import org.restheart.metadata.checkers.Checker;
import org.restheart.metadata.checkers.Checker.PHASE;
import org.restheart.metadata.checkers.CheckersUtils;
import org.restheart.metadata.NamedSingletonsFactory;
import org.restheart.handlers.PipedHttpHandler;
import org.restheart.handlers.RequestContext;
import org.restheart.utils.HttpStatus;
import org.restheart.utils.ResponseHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author Andrea Di Cesare {@literal <andrea@softinstigate.com>}
*/
public class BeforeWriteCheckMetadataHandler extends PipedHttpHandler {
static final Logger LOGGER
= LoggerFactory.getLogger(BeforeWriteCheckMetadataHandler.class);
public static final String SINGLETON_GROUP_NAME = "checkers";
public static final String ROOT_KEY = "checkers";
/**
* Creates a new instance of CheckMetadataHandler
*
* handler that applies the checkers defined in the collection properties
*
* @param next
*/
public BeforeWriteCheckMetadataHandler(PipedHttpHandler next) {
super(next);
}
@Override
public void handleRequest(
HttpServerExchange exchange,
RequestContext context)
throws Exception {
if (doesCheckerAppy(context)) {
if (check(exchange, context)) {
next(exchange, context);
} else {
ResponseHelper.endExchangeWithMessage(
exchange,
context,
HttpStatus.SC_BAD_REQUEST,
"request check failed");
next(exchange, context);
return;
}
} else {
next(exchange, context);
}
}
private boolean doesCheckerAppy(RequestContext context) {
return context.getCollectionProps() != null
&& context.getCollectionProps().containsKey(ROOT_KEY);
}
protected boolean check(
HttpServerExchange exchange,
RequestContext context)
throws InvalidMetadataException {
List<RequestChecker> checkers = RequestChecker
.getFromJson(context.getCollectionProps());
return checkers != null
&& checkers.stream().allMatch(checker -> {
try {
NamedSingletonsFactory nsf = NamedSingletonsFactory
.getInstance();
Checker _checker = (Checker) nsf
.get(ROOT_KEY, checker.getName());
BsonDocument confArgs
= nsf.getArgs(ROOT_KEY, checker.getName());
if (_checker == null) {
throw new IllegalArgumentException(
"cannot find singleton "
+ checker.getName()
+ " in singleton group checkers");
}
// all checkers (both BEFORE_WRITE and AFTER_WRITE) are checked
// to support the request; if any checker does not support the
// request and it is configured to fail in this case,
// then the request fails.
if (!_checker.doesSupportRequests(context)
&& !checker.skipNotSupported()) {
LOGGER.debug("checker "
+ _checker.getClass().getSimpleName()
+ " does not support this request. "
+ "check will "
+ (checker.skipNotSupported()
? "not fail"
: "fail"));
String noteMsg = "";
if (CheckersUtils.doesRequestUsesDotNotation(
context.getContent())) {
noteMsg = noteMsg.concat(
"uses the dot notation");
}
if (CheckersUtils.doesRequestUsesUpdateOperators(
context.getContent())) {
noteMsg = noteMsg.isEmpty()
? "uses update operators"
: noteMsg
.concat(" and update operators");
}
if (CheckersUtils.isBulkRequest(context)) {
noteMsg = noteMsg.isEmpty()
? "is a bulk operation"
: noteMsg
.concat(" and it is a "
+ "bulk operation");
}
context.addWarning("the checker "
+ _checker.getClass().getSimpleName()
+ " does not support this request and "
+ "is configured to fail in this case. "
+ "Note that the request "
+ noteMsg);
return false;
}
if (doesCheckerApply(context, _checker)
&& _checker.doesSupportRequests(context)) {
BsonValue content = context.getContent();
BsonValue _data;
if (_checker.getPhase(context)
== PHASE.BEFORE_WRITE) {
_data = context.getContent();
} else {
Objects.requireNonNull(
context.getDbOperationResult());
_data = context
.getDbOperationResult()
.getNewData();
}
if (_data.isDocument()) {
return _checker.check(
exchange,
context,
_data.asDocument(),
checker.getArgs(),
confArgs);
} else if (content.isArray()) {
// content can be an array of bulk POST
BsonArray arrayContent = _data.asArray();
return arrayContent.stream().allMatch(obj -> {
if (obj.isDocument()) {
return _checker
.check(
exchange,
context,
obj.asDocument(),
checker.getArgs(),
confArgs);
} else {
LOGGER.warn(
"element of content array "
+ "is not an object");
return true;
}
});
} else {
LOGGER.warn(
"content is not an object or an array");
return true;
}
} else {
return true;
}
} catch (IllegalArgumentException ex) {
context.addWarning("error applying checker: "
+ ex.getMessage());
return false;
}
});
}
protected boolean doesCheckerApply(
RequestContext context,
Checker checker) {
return checker.getPhase(context) == Checker.PHASE.BEFORE_WRITE;
}
}