guru.nidi.ramltester.core.RamlChecker Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of raml-tester Show documentation
Show all versions of raml-tester Show documentation
Test if a request/response matches a given raml definition.
/*
* Copyright (C) 2014 Stefan Niederhauser ([email protected])
*
* 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 guru.nidi.ramltester.core;
import guru.nidi.ramltester.model.RamlRequest;
import guru.nidi.ramltester.model.RamlResponse;
import guru.nidi.ramltester.model.Values;
import guru.nidi.ramltester.util.FormDecoder;
import guru.nidi.ramltester.util.Message;
import guru.nidi.ramltester.util.UriComponents;
import org.raml.model.*;
import org.raml.model.parameter.AbstractParam;
import java.io.UnsupportedEncodingException;
import java.util.*;
import static guru.nidi.ramltester.core.CheckerHelper.*;
import static guru.nidi.ramltester.core.UsageBuilder.*;
/**
*
*/
public class RamlChecker {
private final CheckerConfig config;
private RamlViolations requestViolations;
private RamlViolationsPerSecurity violationsPerSecurity;
private Locator locator;
private Usage usage;
private static final class DefaultHeaders {
private static final Set
REQUEST = new HashSet<>(Arrays.asList("accept", "accept-charset", "accept-encoding", "accept-language", "accept-datetime", "authorization", "cache-control", "connection", "cookie", "content-length", "content-md5", "content-type", "date", "dnt", "expect", "from", "host", "if-match", "if-modified-since", "if-none-match", "if-range", "if-unmodified-since", "max-forwards", "origin", "pragma", "proxy-authorization", "range", "referer", "te", "user-agent", "upgrade", "via", "warning")),
RESPONSE = new HashSet<>(Arrays.asList("access-control-allow-origin", "accept-ranges", "age", "allow", "cache-control", "connection", "content-encoding", "content-language", "content-length", "content-location", "content-md5", "content-disposition", "content-range", "content-type", "date", "etag", "expires", "last-modified", "link", "location", "p3p", "pragma", "proxy-authenticate", "refresh", "retry-after", "server", "set-cookie", "status", "strict-transport-security", "trailer", "transfer-encoding", "upgrade", "vary", "via", "warning", "www-authenticate", "x-frame-options"));
}
public RamlChecker(CheckerConfig config) {
this.config = config;
}
public RamlReport check(RamlRequest request) {
return check(request, null);
}
public RamlReport check(RamlRequest request, RamlResponse response) {
final RamlReport report = new RamlReport(config.raml);
usage = report.getUsage();
requestViolations = report.getRequestViolations();
final RamlViolations responseViolations = report.getResponseViolations();
locator = new Locator();
try {
final Action action = findAction(request);
final SecurityExtractor security = new SecurityExtractor(config.raml, action, requestViolations);
security.check(requestViolations);
violationsPerSecurity = new RamlViolationsPerSecurity(security);
checkRequest(request, action, security);
if (response != null) {
checkResponse(request, response, action, security);
}
violationsPerSecurity.addLeastViolations(requestViolations, responseViolations);
} catch (RamlViolationException e) {
//ignore, results are in report
}
if (config.failFast && !report.isEmpty()) {
throw new RamlViolationException(report);
}
return report;
}
public Action findAction(RamlRequest request) {
final UriComponents requestUri = UriComponents.fromHttpUrl(request.getRequestUrl(config.baseUri, config.includeServletPath));
if (config.raml.getBaseUri() == null) {
final UriComponents ramlUri = UriComponents.fromHttpUrl("http://server"); //dummy url as we only match paths
final VariableMatcher pathMatch = getPathMatch(requestUri, ramlUri);
return findAction(pathMatch.getSuffix(), request.getMethod());
}
final UriComponents ramlUri = UriComponents.fromHttpUrl(config.raml.getBaseUri());
final VariableMatcher hostMatch = getHostMatch(requestUri, ramlUri);
final VariableMatcher pathMatch = getPathMatch(requestUri, ramlUri);
final Action action = findAction(pathMatch.getSuffix(), request.getMethod());
checkProtocol(action, requestUri, ramlUri);
checkBaseUriParameters(hostMatch, pathMatch, action);
return action;
}
private Action findAction(String path, String method) {
final Resource resource = findResourceByPath(path);
resourceUsage(usage, resource).incUses(1);
final Action action = resource.getAction(method);
if (action == null) {
requestViolations.add("action.undefined", locator, method);
throw new RamlViolationException();
}
actionUsage(usage, action).incUses(1);
locator.action(action);
return action;
}
private Resource findResourceByPath(String resourcePath) {
final Values values = new Values();
final List matches = findResource(resourcePath, config.raml.getResources(), values);
if (matches.isEmpty()) {
requestViolations.add("resource.undefined", resourcePath);
throw new RamlViolationException();
}
if (matches.size() > 1 && matches.get(0).compareTo(matches.get(1)) == 0) {
requestViolations.add("resource.ambiguous", resourcePath, matches.get(0).resource.getUri(), matches.get(1).resource.getUri());
throw new RamlViolationException();
}
final Resource resource = matches.get(0).resource;
locator.resource(resource);
checkUriParams(values, resource);
return resource;
}
public void checkRequest(RamlRequest request, Action action, SecurityExtractor security) {
checkQueryParameters(request.getQueryValues(), action, security);
checkRequestHeaderParameters(request.getHeaderValues(), action, security);
final MediaTypeMatch typeMatch = MediaTypeMatch.find(requestViolations, request, action.getBody(), locator);
if (typeMatch != null) {
locator.requestMime(typeMatch.getMatchingMime());
if (FormDecoder.supportsFormParameters(typeMatch.getTargetType())) {
checkFormParameters(action, request.getFormValues(), typeMatch.getMatchingMime());
} else {
checkSchema(requestViolations, request.getContent(), typeMatch);
}
}
}
private void checkFormParameters(Action action, Values values, MimeType mimeType) {
if (mimeType.getSchema() != null) {
requestViolations.add("schema.superfluous", locator);
}
@SuppressWarnings("unchecked")
final Map> formParameters = (Map) mimeType.getFormParameters();
if (formParameters == null) {
requestViolations.add("formParameters.missing", locator);
} else {
checkFormParametersValues(action, mimeType, values, formParameters);
}
}
private void checkFormParametersValues(Action action, MimeType mimeType, Values values, Map> formParameters) {
mimeTypeUsage(usage, action, mimeType).addFormParameters(
new ParameterChecker(requestViolations)
.checkListParameters(formParameters, values, new Message("formParam", locator))
);
}
private void checkQueryParameters(Values values, Action action, SecurityExtractor security) {
//TODO usage is multiplied by security schemes
for (final SecurityScheme scheme : security.getSchemes()) {
actionUsage(usage, action).addQueryParameters(
new ParameterChecker(violationsPerSecurity.requestViolations(scheme))
.checkParameters(mergeMaps(action.getQueryParameters(), security.queryParameters(scheme)), values, new Message("queryParam", locator))
);
}
}
private void checkRequestHeaderParameters(Values values, Action action, SecurityExtractor security) {
//TODO usage is multiplied by security schemes
for (final SecurityScheme scheme : security.getSchemes()) {
actionUsage(usage, action).addRequestHeaders(
new ParameterChecker(violationsPerSecurity.requestViolations(scheme))
.acceptWildcard()
.ignoreX(config.ignoreXheaders)
.caseSensitive(false)
.predefined(DefaultHeaders.REQUEST)
.checkParameters(mergeMaps(action.getHeaders(), security.headers(scheme)), values, new Message("headerParam", locator))
);
}
}
private void checkBaseUriParameters(VariableMatcher hostMatch, VariableMatcher pathMatch, Action action) {
final ParameterChecker paramChecker = new ParameterChecker(requestViolations).acceptUndefined();
final Map> baseUriParams = getEffectiveBaseUriParams(config.raml.getBaseUriParameters(), action);
paramChecker.checkListParameters(baseUriParams, hostMatch.getVariables(), new Message("baseUriParam", locator));
paramChecker.checkListParameters(baseUriParams, pathMatch.getVariables(), new Message("baseUriParam", locator));
}
private VariableMatcher getPathMatch(UriComponents requestUri, UriComponents ramlUri) {
final VariableMatcher pathMatch = VariableMatcher.match(ramlUri.getPath(), requestUri.getPath());
if (!pathMatch.isMatch()) {
requestViolations.add("baseUri.unmatched", requestUri.getUri(), config.raml.getBaseUri());
throw new RamlViolationException();
}
return pathMatch;
}
private VariableMatcher getHostMatch(UriComponents requestUri, UriComponents ramlUri) {
final VariableMatcher hostMatch = VariableMatcher.match(ramlUri.getHost(), requestUri.getHost());
if (!hostMatch.isCompleteMatch()) {
requestViolations.add("baseUri.unmatched", requestUri.getUri(), config.raml.getBaseUri());
throw new RamlViolationException();
}
return hostMatch;
}
private void checkProtocol(Action action, UriComponents requestUri, UriComponents ramlUri) {
final List protocols = findProtocols(action, ramlUri.getScheme());
requestViolations.addIf(!protocols.contains(protocolOf(requestUri.getScheme())), "protocol.undefined", locator, requestUri.getScheme());
}
private List findProtocols(Action action, String fallback) {
List protocols = action.getProtocols();
if (protocols == null || protocols.isEmpty()) {
protocols = config.raml.getProtocols();
}
if (protocols == null || protocols.isEmpty()) {
protocols = Collections.singletonList(Protocol.valueOf(fallback.toUpperCase(Locale.ENGLISH)));
}
return protocols;
}
private void checkUriParams(Values values, Resource resource) {
final ParameterChecker paramChecker = new ParameterChecker(requestViolations).acceptUndefined();
for (final Map.Entry> entry : values) {
final AbstractParam uriParam = findUriParam(entry.getKey(), resource);
final Message message = new Message("uriParam", locator, entry.getKey());
if (uriParam != null) {
paramChecker.checkParameter(uriParam, entry.getValue().get(0), message);
}
}
}
public void checkResponse(RamlRequest request, RamlResponse response, Action action, SecurityExtractor security) {
//TODO usage is multiplied by security schemes
for (final SecurityScheme scheme : security.getSchemes()) {
final RamlViolations requestViolations = violationsPerSecurity.requestViolations(scheme);
final RamlViolations responseViolations = violationsPerSecurity.responseViolations(scheme);
final MediaTypeMatch typeMatch = doCheckReponse(responseViolations, response, action, security.responses(scheme));
if (typeMatch != null) {
new ContentNegotiationChecker(requestViolations, responseViolations)
.check(request, response, action, typeMatch);
}
}
}
private MediaTypeMatch doCheckReponse(RamlViolations violations, RamlResponse response, Action action, Map securityResponses) {
final Map responseMap = mergeMaps(action.getResponses(), securityResponses);
final Response res = responseMap.get(Integer.toString(response.getStatus()));
if (res == null) {
violations.add("responseCode.undefined", locator, response.getStatus());
return null;
}
final String statusStr = Integer.toString(response.getStatus());
actionUsage(usage, action).addResponseCode(statusStr);
locator.responseCode(statusStr);
checkResponseHeaderParameters(violations, response.getHeaderValues(), action, statusStr, res);
final MediaTypeMatch typeMatch = MediaTypeMatch.find(violations, response, res.getBody(), locator);
if (typeMatch != null) {
locator.responseMime(typeMatch.getMatchingMime());
checkSchema(violations, response.getContent(), typeMatch);
}
return typeMatch;
}
private void checkSchema(RamlViolations violations, byte[] body, MediaTypeMatch typeMatch) {
final String schema = typeMatch.getMatchingMime().getSchema();
if (schema == null) {
return;
}
final SchemaValidator validator = findSchemaValidator(config.schemaValidators, typeMatch.getTargetType());
if (validator == null) {
violations.add("schemaValidator.missing", locator, typeMatch.getTargetType());
return;
}
if (body == null || body.length == 0) {
violations.add("body.empty", locator, typeMatch.getTargetType());
return;
}
final String charset = typeMatch.getTargetCharset();
try {
final String content = new String(body, charset);
validator.validate(new NamedReader(content, new Message("body").toString()), resolveSchema(config.raml, schema), violations, new Message("schema.body.mismatch", locator, content));
} catch (UnsupportedEncodingException e) {
violations.add("charset.invalid", charset);
}
}
private void checkResponseHeaderParameters(RamlViolations violations, Values values, Action action, String responseCode, Response response) {
responseUsage(usage, action, responseCode).addResponseHeaders(
new ParameterChecker(violations)
.acceptWildcard()
.ignoreX(config.ignoreXheaders)
.caseSensitive(false)
.predefined(DefaultHeaders.RESPONSE)
.checkParameters(response.getHeaders(), values, new Message("headerParam", locator))
);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy