UNPKG

@sap/odata-v4

Version:

OData V4.0 server library

195 lines (174 loc) 7.84 kB
'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;