@sap/odata-v4
Version:
OData V4.0 server library
195 lines (174 loc) • 7.84 kB
JavaScript
'use strict';
const HttpMethods = require('../http/HttpMethod').Methods;
const UriResource = require('../uri/UriResource');
const ResourceKinds = UriResource.ResourceKind;
const validateThat = require('./ParameterValidator').validateThat;
const MethodNotAllowedError = require('../errors/MethodNotAllowedError');
const BadRequestError = require('../errors/BadRequestError');
const NotImplementedError = require('../errors/NotImplementedError');
const FeatureSupport = require('../FeatureSupport');
/**
* White list of the allowed HTTP requests for each defined resource kind.
*
* @type {Map.<UriResource.ResourceKind, Map.<HttpMethods, boolean>>}
* @private
*/
const whiteList = new Map()
.set(ResourceKinds.SERVICE, new Map()
.set(HttpMethods.GET, true)
.set(HttpMethods.HEAD, true)
)
.set(ResourceKinds.METADATA, new Map()
.set(HttpMethods.GET, true)
.set(HttpMethods.HEAD, false)
)
.set(ResourceKinds.BATCH, new Map().set(HttpMethods.POST, true))
.set(ResourceKinds.CROSSJOIN, new Map().set(HttpMethods.GET, FeatureSupport.features.CrossJoin))
.set(ResourceKinds.ALL, new Map().set(HttpMethods.GET, FeatureSupport.features.All))
.set(ResourceKinds.ENTITY_ID, new Map()
.set(HttpMethods.GET, FeatureSupport.features.Entity_id)
.set(HttpMethods.PUT, FeatureSupport.features.Entity_id)
.set(HttpMethods.PATCH, FeatureSupport.features.Entity_id)
.set(HttpMethods.DELETE, FeatureSupport.features.Entity_id)
)
.set(ResourceKinds.ENTITY, new Map()
.set(HttpMethods.GET, true)
.set(HttpMethods.PUT, true)
.set(HttpMethods.PATCH, true)
.set(HttpMethods.DELETE, true)
)
.set(ResourceKinds.ENTITY_COLLECTION, new Map()
.set(HttpMethods.GET, true)
.set(HttpMethods.POST, true)
)
.set(ResourceKinds.SINGLETON, new Map()
.set(HttpMethods.GET, FeatureSupport.features.Singleton)
.set(HttpMethods.PUT, FeatureSupport.features.Singleton)
.set(HttpMethods.PATCH, FeatureSupport.features.Singleton)
)
.set(ResourceKinds.NAVIGATION_TO_ONE, new Map()
.set(HttpMethods.GET, true)
.set(HttpMethods.PUT, true)
.set(HttpMethods.PATCH, true)
.set(HttpMethods.DELETE, true)
)
.set(ResourceKinds.NAVIGATION_TO_MANY, new Map()
.set(HttpMethods.GET, true)
.set(HttpMethods.POST, true)
)
.set(ResourceKinds.COUNT, new Map().set(HttpMethods.GET, true))
.set(ResourceKinds.REF, new Map()
.set(HttpMethods.GET, true)
.set(HttpMethods.PUT, true)
.set(HttpMethods.DELETE, true)
)
.set(ResourceKinds.REF_COLLECTION, new Map()
.set(HttpMethods.GET, true)
.set(HttpMethods.POST, true)
.set(HttpMethods.DELETE, FeatureSupport.features.Ref)
)
.set(ResourceKinds.VALUE, new Map()
.set(HttpMethods.GET, true)
.set(HttpMethods.PUT, true)
.set(HttpMethods.DELETE, true)
)
.set(ResourceKinds.PRIMITIVE_PROPERTY, new Map()
.set(HttpMethods.GET, true)
.set(HttpMethods.PUT, true)
.set(HttpMethods.DELETE, true)
)
.set(ResourceKinds.COMPLEX_PROPERTY, new Map()
.set(HttpMethods.GET, true)
.set(HttpMethods.PUT, true)
.set(HttpMethods.PATCH, true)
.set(HttpMethods.DELETE, true)
)
.set(ResourceKinds.PRIMITIVE_COLLECTION_PROPERTY, new Map()
.set(HttpMethods.GET, true)
.set(HttpMethods.PUT, true)
.set(HttpMethods.DELETE, true)
)
.set(ResourceKinds.COMPLEX_COLLECTION_PROPERTY, new Map()
.set(HttpMethods.GET, true)
.set(HttpMethods.PUT, true)
.set(HttpMethods.DELETE, true)
)
// modification should be done through canonical resource path
.set(ResourceKinds.TYPE_CAST, new Map().set(HttpMethods.GET, FeatureSupport.features.TypeCast))
.set(ResourceKinds.FUNCTION_IMPORT, new Map().set(HttpMethods.GET, true))
.set(ResourceKinds.BOUND_FUNCTION, new Map().set(HttpMethods.GET, true))
.set(ResourceKinds.ACTION_IMPORT, new Map().set(HttpMethods.POST, true))
.set(ResourceKinds.BOUND_ACTION, new Map().set(HttpMethods.POST, true));
class OperationValidator {
/**
* Creates an instance of OperationValidator.
* @param {LoggerFacade} logger the logger
*/
constructor(logger) {
this._logger = logger;
}
/**
* Proves the given HTTP method against the given path segments.
*
* The last path segment is checked against a defined white list.
* If a request on a specific resource kind is not found, then this method throws a MethodNotAllowedError.
* If it is found with a value not "true", it throws a NotImplementedError.
*
* If the HTTP method is DELETE, it is checked whether the delete operation is on a
* not nullable property or property value. If yes, a Bad Request error is thrown.
*
* If the HTTP method is not GET and the last segment is "$ref", it is checked that the previous segment is a
* navigation; otherwise a MethodNotAllowedError is thrown.
*
* If intermediate navigation is found and the HTTP method is not GET, a NotImplementedError is thrown.
*
* If all checks are passed, this method returns true.
*
* @param {HttpMethod.Methods|string} httpMethod
* @param {UriResource[]} pathSegments
* @returns {boolean}
*/
validate(httpMethod, pathSegments) {
this._logger.path('Entering OperationValidator.validate()...');
validateThat('httpMethod', httpMethod).truthy().typeOf('string');
validateThat('pathSegments', pathSegments).truthy().array().containsInstancesOf(UriResource);
const length = pathSegments.length;
const lastSegmentResourceKind = pathSegments[length - 1].getKind();
const allowedResKind = whiteList.get(lastSegmentResourceKind);
if (!allowedResKind) {
throw new Error('Unknown resource kind ' + lastSegmentResourceKind);
}
const allowedHttpMethod = allowedResKind.get(httpMethod);
if (allowedHttpMethod === undefined) {
throw new MethodNotAllowedError(`Method ${httpMethod} not allowed for ${lastSegmentResourceKind}`);
} else if (allowedHttpMethod === false) {
throw new NotImplementedError();
} else if (allowedHttpMethod !== true) {
FeatureSupport.failUnsupported(allowedHttpMethod);
}
if (httpMethod === HttpMethods.DELETE) {
if (lastSegmentResourceKind === ResourceKinds.VALUE
&& !pathSegments[length - 2].getProperty().isNullable()
|| (lastSegmentResourceKind === ResourceKinds.PRIMITIVE_PROPERTY
|| lastSegmentResourceKind === ResourceKinds.COMPLEX_PROPERTY)
&& !pathSegments[length - 1].getProperty().isNullable()) {
throw new BadRequestError("Can't DELETE a non-nullable value.");
}
}
if (httpMethod !== HttpMethods.GET) {
if ((lastSegmentResourceKind === ResourceKinds.REF
|| lastSegmentResourceKind === ResourceKinds.REF_COLLECTION)
&& !pathSegments[length - 2].getNavigationProperty()) {
throw new MethodNotAllowedError("Modification requests on '$ref' are only allowed on navigation.");
}
}
if (httpMethod === HttpMethods.PUT
&& lastSegmentResourceKind === ResourceKinds.REF
&& pathSegments[length - 2].getNavigationProperty().isCollection()) {
throw new MethodNotAllowedError(
"Update requests on '$ref' are only allowed on non-collection navigation properties.");
}
return true;
}
}
module.exports = OperationValidator;