Fix security risk 'Improper Input Validation'
[sdc.git] / catalog-be / src / main / java / org / openecomp / sdc / be / servlets / BeGenericServlet.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * SDC
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20 package org.openecomp.sdc.be.servlets;
21
22 import com.fasterxml.jackson.databind.DeserializationFeature;
23 import com.fasterxml.jackson.databind.ObjectMapper;
24 import com.fasterxml.jackson.databind.module.SimpleModule;
25 import com.google.common.annotations.VisibleForTesting;
26 import com.google.gson.Gson;
27 import com.google.gson.GsonBuilder;
28 import com.google.gson.reflect.TypeToken;
29 import fj.data.Either;
30 import java.io.IOException;
31 import java.lang.reflect.Type;
32 import java.util.HashMap;
33 import java.util.Iterator;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Map.Entry;
37 import java.util.Objects;
38 import java.util.Set;
39 import java.util.function.Supplier;
40 import javax.servlet.ServletContext;
41 import javax.servlet.http.HttpServletRequest;
42 import javax.ws.rs.core.Context;
43 import javax.ws.rs.core.Response;
44 import javax.ws.rs.core.Response.ResponseBuilder;
45 import org.json.simple.JSONArray;
46 import org.json.simple.JSONObject;
47 import org.json.simple.parser.JSONParser;
48 import org.json.simple.parser.ParseException;
49 import org.openecomp.sdc.be.components.impl.ArtifactsBusinessLogic;
50 import org.openecomp.sdc.be.components.impl.BaseBusinessLogic;
51 import org.openecomp.sdc.be.components.impl.ComponentInstanceBusinessLogic;
52 import org.openecomp.sdc.be.components.impl.ElementBusinessLogic;
53 import org.openecomp.sdc.be.components.impl.GenericArtifactBrowserBusinessLogic;
54 import org.openecomp.sdc.be.components.impl.InputsBusinessLogic;
55 import org.openecomp.sdc.be.components.impl.OutputsBusinessLogic;
56 import org.openecomp.sdc.be.components.impl.PolicyBusinessLogic;
57 import org.openecomp.sdc.be.components.impl.ResourceBusinessLogic;
58 import org.openecomp.sdc.be.components.impl.ServiceBusinessLogic;
59 import org.openecomp.sdc.be.components.impl.exceptions.ByActionStatusComponentException;
60 import org.openecomp.sdc.be.components.impl.exceptions.ComponentException;
61 import org.openecomp.sdc.be.config.BeEcompErrorManager;
62 import org.openecomp.sdc.be.dao.api.ActionStatus;
63 import org.openecomp.sdc.be.datatypes.enums.ComponentTypeEnum;
64 import org.openecomp.sdc.be.datatypes.enums.DeclarationTypeEnum;
65 import org.openecomp.sdc.be.datatypes.tosca.ToscaDataDefinition;
66 import org.openecomp.sdc.be.impl.ComponentsUtils;
67 import org.openecomp.sdc.be.impl.WebAppContextWrapper;
68 import org.openecomp.sdc.be.model.ComponentInstInputsMap;
69 import org.openecomp.sdc.be.model.InputDefinition;
70 import org.openecomp.sdc.be.model.PropertyConstraint;
71 import org.openecomp.sdc.be.model.PropertyDefinition;
72 import org.openecomp.sdc.be.model.User;
73 import org.openecomp.sdc.be.model.operations.impl.PropertyOperation;
74 import org.openecomp.sdc.be.model.operations.impl.PropertyOperation.PropertyConstraintJacksonDeserializer;
75 import org.openecomp.sdc.be.model.operations.impl.UniqueIdBuilder;
76 import org.openecomp.sdc.be.resources.data.auditing.AuditingActionEnum;
77 import org.openecomp.sdc.be.user.UserBusinessLogic;
78 import org.openecomp.sdc.common.api.Constants;
79 import org.openecomp.sdc.common.log.wrappers.Logger;
80 import org.openecomp.sdc.common.servlets.BasicServlet;
81 import org.openecomp.sdc.exception.ResponseFormat;
82 import org.springframework.web.context.WebApplicationContext;
83
84 public class BeGenericServlet extends BasicServlet {
85
86     private static final Logger log = Logger.getLogger(BeGenericServlet.class);
87     private static final String PROPERTY_NAME_REGEX = "[a-zA-Z0-9_:-@]+";
88     @Context
89     protected HttpServletRequest servletRequest;
90     protected ComponentsUtils componentsUtils;
91
92     public BeGenericServlet(ComponentsUtils componentsUtils) {
93         this.componentsUtils = componentsUtils;
94     }
95
96     private static Response buildOkResponseStatic(Object entity) {
97         return Response.status(Response.Status.OK).entity(entity).build();
98     }
99
100     /******************** New error response mechanism
101      * @param requestErrorWrapper **************/
102     protected Response buildErrorResponse(ResponseFormat requestErrorWrapper) {
103         return Response.status(requestErrorWrapper.getStatus()).entity(gson.toJson(requestErrorWrapper.getRequestError())).build();
104     }
105
106     protected Response buildGeneralErrorResponse() {
107         return buildErrorResponse(getComponentsUtils().getResponseFormat(ActionStatus.GENERAL_ERROR));
108     }
109
110     protected Response buildOkResponse(Object entity) {
111         return buildOkResponseStatic(entity);
112     }
113
114     public HttpServletRequest getServletRequest() {
115         return servletRequest;
116     }
117
118     @VisibleForTesting
119     public void setRequestServlet(HttpServletRequest request) {
120         this.servletRequest = request;
121     }
122
123     protected Response buildOkResponse(ResponseFormat errorResponseWrapper, Object entity) {
124         return buildOkResponse(errorResponseWrapper, entity, null);
125     }
126
127     protected Response buildOkResponse(ResponseFormat errorResponseWrapper, Object entity, Map<String, String> additionalHeaders) {
128         int status = errorResponseWrapper.getStatus();
129         ResponseBuilder responseBuilder = Response.status(status);
130         if (entity != null) {
131             if (log.isTraceEnabled()) {
132                 log.trace("returned entity is {}", entity.toString());
133             }
134             responseBuilder = responseBuilder.entity(entity);
135         }
136         if (additionalHeaders != null) {
137             for (Entry<String, String> additionalHeader : additionalHeaders.entrySet()) {
138                 String headerName = additionalHeader.getKey();
139                 String headerValue = additionalHeader.getValue();
140                 if (log.isTraceEnabled()) {
141                     log.trace("Adding header {} with value {} to the response", headerName, headerValue);
142                 }
143                 responseBuilder.header(headerName, headerValue);
144             }
145         }
146         return responseBuilder.build();
147     }
148
149     /*******************************************************************************************************/
150     protected Either<User, ResponseFormat> getUser(final HttpServletRequest request, String userId) {
151         User user;
152         try {
153             user = getUserAdminManager(request.getSession().getServletContext()).getUser(userId, false);
154             return Either.left(user);
155         } catch (ComponentException ce) {
156             log.info("createResource method - user is not listed. userId= {}", userId);
157             ResponseFormat errorResponse = getComponentsUtils().getResponseFormat(ce);
158             user = new User("", "", userId, "", null, null);
159             getComponentsUtils().auditResource(errorResponse, user, "", AuditingActionEnum.CHECKOUT_RESOURCE);
160             return Either.right(errorResponse);
161         }
162     }
163
164     UserBusinessLogic getUserAdminManager(ServletContext context) {
165         return getClassFromWebAppContext(context, () -> UserBusinessLogic.class);
166     }
167
168     protected GenericArtifactBrowserBusinessLogic getGenericArtifactBrowserBL(ServletContext context) {
169         return getClassFromWebAppContext(context, () -> GenericArtifactBrowserBusinessLogic.class);
170     }
171
172     protected ResourceBusinessLogic getResourceBL(ServletContext context) {
173         return getClassFromWebAppContext(context, () -> ResourceBusinessLogic.class);
174     }
175
176     protected ServiceBusinessLogic getServiceBL(ServletContext context) {
177         return getClassFromWebAppContext(context, () -> ServiceBusinessLogic.class);
178     }
179
180     protected ArtifactsBusinessLogic getArtifactBL(ServletContext context) {
181         return getClassFromWebAppContext(context, () -> ArtifactsBusinessLogic.class);
182     }
183
184     protected ElementBusinessLogic getElementBL(ServletContext context) {
185         return getClassFromWebAppContext(context, () -> ElementBusinessLogic.class);
186     }
187
188     <T> T getClassFromWebAppContext(final ServletContext context, final Supplier<Class<T>> businessLogicClassGen) {
189         return getWebAppContext(context).getBean(businessLogicClassGen.get());
190     }
191
192     protected ComponentInstanceBusinessLogic getComponentInstanceBL(final ServletContext context) {
193         return getClassFromWebAppContext(context, () -> ComponentInstanceBusinessLogic.class);
194     }
195
196     protected ComponentsUtils getComponentsUtils() {
197         final ServletContext context = this.servletRequest.getSession().getServletContext();
198         return getClassFromWebAppContext(context, () -> ComponentsUtils.class);
199     }
200
201     /**
202      * Used to support Unit Test.<br> Header Params are not supported in Unit Tests
203      *
204      * @return
205      */
206     String initHeaderParam(String headerValue, HttpServletRequest request, String headerName) {
207         String retValue;
208         if (headerValue != null) {
209             retValue = headerValue;
210         } else {
211             retValue = request.getHeader(headerName);
212         }
213         return retValue;
214     }
215
216     protected String getContentDispositionValue(String artifactFileName) {
217         return new StringBuilder().append("attachment; filename=\"").append(artifactFileName).append("\"").toString();
218     }
219
220     <T> T convertJsonToObjectOfClass(String json, Class<T> clazz) {
221         T object = null;
222         ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
223             .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
224         try {
225             log.trace("Starting to convert json to object. Json=\n{}", json);
226             SimpleModule module = new SimpleModule("customDeserializationModule");
227             module.addDeserializer(PropertyConstraint.class, new PropertyConstraintJacksonDeserializer());
228             mapper.registerModule(module);
229             object = mapper.readValue(json, clazz);
230             if (object != null) {
231                 return object;
232             } else {
233                 BeEcompErrorManager.getInstance().logBeInvalidJsonInput("convertJsonToObject");
234                 log.debug("The object of class {} is null after converting from json. ", clazz);
235                 throw new ByActionStatusComponentException(ActionStatus.INVALID_CONTENT);
236             }
237         } catch (IOException e) {
238             BeEcompErrorManager.getInstance().logBeInvalidJsonInput("convertJsonToObject");
239             log.debug("The exception {} occurred upon json to object convertation. Json=\n{}", e, json);
240             throw new ByActionStatusComponentException(ActionStatus.INVALID_CONTENT);
241         }
242     }
243
244     protected Either<Map<String, PropertyDefinition>, ActionStatus> getPropertyModel(String componentId, String data) {
245         JSONParser parser = new JSONParser();
246         JSONObject root;
247         try {
248             Map<String, PropertyDefinition> properties = new HashMap<>();
249             root = (JSONObject) parser.parse(data);
250             Set entrySet = root.entrySet();
251             Iterator iterator = entrySet.iterator();
252             while (iterator.hasNext()) {
253                 Entry next = (Entry) iterator.next();
254                 String propertyName = (String) next.getKey();
255                 if (!isPropertyNameValid(propertyName)) {
256                     return Either.right(ActionStatus.INVALID_PROPERTY_NAME);
257                 }
258                 JSONObject value = (JSONObject) next.getValue();
259                 Either<PropertyDefinition, ActionStatus> propertyDefinitionEither = getPropertyDefinitionFromJson(componentId, propertyName, value);
260                 if (propertyDefinitionEither.isRight()) {
261                     return Either.right(propertyDefinitionEither.right().value());
262                 }
263                 properties.put(propertyName, propertyDefinitionEither.left().value());
264             }
265             return Either.left(properties);
266         } catch (ParseException e) {
267             log.info("Property conetnt is invalid - {}", data);
268             return Either.right(ActionStatus.INVALID_CONTENT);
269         }
270     }
271
272     protected boolean isPropertyNameValid(String propertyName) {
273         return Objects.nonNull(propertyName) && propertyName.matches(PROPERTY_NAME_REGEX);
274     }
275
276     private Either<PropertyDefinition, ActionStatus> getPropertyDefinitionFromJson(String componentId, String propertyName, JSONObject value) {
277         String jsonString = value.toJSONString();
278         Either<PropertyDefinition, ActionStatus> convertJsonToObject = convertJsonToObject(jsonString, PropertyDefinition.class);
279         if (convertJsonToObject.isRight()) {
280             return Either.right(convertJsonToObject.right().value());
281         }
282         PropertyDefinition propertyDefinition = convertJsonToObject.left().value();
283         String uniqueId = UniqueIdBuilder.buildPropertyUniqueId(componentId, propertyName);
284         propertyDefinition.setUniqueId(uniqueId);
285         return Either.left(propertyDefinition);
286     }
287
288     private Either<InputDefinition, ActionStatus> getInputDefinitionFromJson(String componentId, String inputName, JSONObject value) {
289         String jsonString = value.toJSONString();
290         Either<InputDefinition, ActionStatus> convertJsonToObject = convertJsonToObject(jsonString, InputDefinition.class);
291         if (convertJsonToObject.isRight()) {
292             return Either.right(convertJsonToObject.right().value());
293         }
294         InputDefinition inputDefinition = convertJsonToObject.left().value();
295         String uniqueId = UniqueIdBuilder.buildPropertyUniqueId(componentId, inputName);
296         inputDefinition.setUniqueId(uniqueId);
297         return Either.left(inputDefinition);
298     }
299
300     protected Either<Map<String, PropertyDefinition>, ActionStatus> getPropertiesListForUpdate(String data) {
301         Map<String, PropertyDefinition> properties = new HashMap<>();
302         JSONParser parser = new JSONParser();
303         JSONArray jsonArray;
304         try {
305             jsonArray = (JSONArray) parser.parse(data);
306             for (Object jsonElement : jsonArray) {
307                 String propertyAsString = jsonElement.toString();
308                 Either<PropertyDefinition, ActionStatus> convertJsonToObject = convertJsonToObject(propertyAsString, PropertyDefinition.class);
309                 if (convertJsonToObject.isRight()) {
310                     return Either.right(convertJsonToObject.right().value());
311                 }
312                 PropertyDefinition propertyDefinition = convertJsonToObject.left().value();
313                 properties.put(propertyDefinition.getName(), propertyDefinition);
314             }
315             return Either.left(properties);
316         } catch (Exception e) {
317             log.info("Property content is invalid - {}", data);
318             return Either.right(ActionStatus.INVALID_CONTENT);
319         }
320     }
321
322     protected String propertyToJson(Map.Entry<String, PropertyDefinition> property) {
323         JSONObject root = new JSONObject();
324         PropertyDefinition propertyDefinition = property.getValue();
325         root.put(property.getKey(), getPropertyDefinitionJSONObject(propertyDefinition));
326         propertyDefinition.getType();
327         return root.toString();
328     }
329
330     private JSONObject getPropertyDefinitionJSONObject(PropertyDefinition propertyDefinition) {
331         Either<String, ActionStatus> either = convertObjectToJson(propertyDefinition);
332         if (either.isRight()) {
333             return new JSONObject();
334         }
335         try {
336             return (JSONObject) new JSONParser().parse(either.left().value());
337         } catch (ParseException e) {
338             log.info("failed to convert input to json");
339             log.error("failed to convert to json", e);
340             return new JSONObject();
341         }
342     }
343
344     protected <T> Either<T, ActionStatus> convertJsonToObject(String data, Class<T> clazz) {
345         T t = null;
346         Type constraintType = new TypeToken<PropertyConstraint>() {
347         }.getType();
348         Gson gson = new GsonBuilder().registerTypeAdapter(constraintType, new PropertyOperation.PropertyConstraintDeserialiser()).create();
349         try {
350             log.trace("convert json to object. json=\n {}", data);
351             t = gson.fromJson(data, clazz);
352             if (t == null) {
353                 log.info("object is null after converting from json");
354                 return Either.right(ActionStatus.INVALID_CONTENT);
355             }
356         } catch (Exception e) {
357             // INVALID JSON
358             log.info("failed to convert from json");
359             log.error("failed to convert from json", e);
360             return Either.right(ActionStatus.INVALID_CONTENT);
361         }
362         return Either.left(t);
363     }
364
365     private Either<String, ActionStatus> convertObjectToJson(PropertyDefinition propertyDefinition) {
366         Type constraintType = new TypeToken<PropertyConstraint>() {
367         }.getType();
368         Gson gson = new GsonBuilder().registerTypeAdapter(constraintType, new PropertyOperation.PropertyConstraintSerialiser()).create();
369         try {
370             log.trace("convert object to json. propertyDefinition= {}", propertyDefinition);
371             String json = gson.toJson(propertyDefinition);
372             if (json == null) {
373                 log.info("object is null after converting to json");
374                 return Either.right(ActionStatus.INVALID_CONTENT);
375             }
376             return Either.left(json);
377         } catch (Exception e) {
378             // INVALID JSON
379             log.info("failed to convert to json");
380             log.debug("failed to convert fto json", e);
381             return Either.right(ActionStatus.INVALID_CONTENT);
382         }
383     }
384
385     private OutputsBusinessLogic getOutputBL(final ServletContext context) {
386         return getClassFromWebAppContext(context, () -> OutputsBusinessLogic.class);
387     }
388
389     private InputsBusinessLogic getInputBL(final ServletContext context) {
390         return getClassFromWebAppContext(context, () -> InputsBusinessLogic.class);
391     }
392
393     private PolicyBusinessLogic getPolicyBL(final ServletContext context) {
394         return getClassFromWebAppContext(context, () -> PolicyBusinessLogic.class);
395     }
396
397     private WebApplicationContext getWebAppContext(final ServletContext context) {
398         return ((WebAppContextWrapper) context.getAttribute(Constants.WEB_APPLICATION_CONTEXT_WRAPPER_ATTR)).getWebAppContext(context);
399     }
400
401     protected <T> Either<T, ResponseFormat> parseToComponentInstanceMap(final String componentJson, final User user,
402                                                                         final ComponentTypeEnum componentType, final Class<T> clazz) {
403         return getComponentsUtils()
404             .convertJsonToObjectUsingObjectMapper(componentJson, user, clazz, AuditingActionEnum.CREATE_RESOURCE, componentType);
405     }
406
407     protected Response declareProperties(String userId, String componentId, String componentType, String componentInstInputsMapObj,
408                                          DeclarationTypeEnum typeEnum, HttpServletRequest request) {
409         ServletContext context = request.getSession().getServletContext();
410         String url = request.getMethod() + " " + request.getRequestURI();
411         log.debug("(get) Start handle request of {}", url);
412         try {
413             BaseBusinessLogic businessLogic = getBlForDeclaration(typeEnum, context);
414             // get modifier id
415             User modifier = new User();
416             modifier.setUserId(userId);
417             log.debug("modifier id is {}", userId);
418             ComponentTypeEnum componentTypeEnum = ComponentTypeEnum.findByParamName(componentType);
419             Either<ComponentInstInputsMap, ResponseFormat> componentInstInputsMapRes = parseToComponentInstanceMap(componentInstInputsMapObj,
420                 modifier, componentTypeEnum, ComponentInstInputsMap.class);
421             if (componentInstInputsMapRes.isRight()) {
422                 log.debug("failed to parse componentInstInputsMap");
423                 return buildErrorResponse(componentInstInputsMapRes.right().value());
424             }
425             Either<List<ToscaDataDefinition>, ResponseFormat> propertiesAfterDeclaration = businessLogic
426                 .declareProperties(userId, componentId, componentTypeEnum, componentInstInputsMapRes.left().value());
427             if (propertiesAfterDeclaration.isRight()) {
428                 log.debug("failed to create inputs  for service: {}", componentId);
429                 return buildErrorResponse(propertiesAfterDeclaration.right().value());
430             }
431             Object properties = RepresentationUtils.toRepresentation(propertiesAfterDeclaration.left().value());
432             return buildOkResponse(getComponentsUtils().getResponseFormat(ActionStatus.OK), properties);
433         } catch (Exception e) {
434             BeEcompErrorManager.getInstance().logBeRestApiGeneralError("Create inputs for service with id: " + componentId);
435             log.debug("Properties declaration failed with exception", e);
436             return buildErrorResponse(getComponentsUtils().getResponseFormat(ActionStatus.GENERAL_ERROR));
437         }
438     }
439
440     public BaseBusinessLogic getBlForDeclaration(final DeclarationTypeEnum typeEnum, final ServletContext context) {
441         switch (typeEnum) {
442             case OUTPUT:
443                 return getOutputBL(context);
444             case POLICY:
445                 return getPolicyBL(context);
446             case INPUT:
447                 return getInputBL(context);
448             default:
449                 throw new IllegalArgumentException("Invalid DeclarationTypeEnum");
450         }
451     }
452
453     protected Either<Map<String, InputDefinition>, ActionStatus> getInputModel(String componentId, String data) {
454         JSONParser parser = new JSONParser();
455         JSONObject root;
456         try {
457             Map<String, InputDefinition> inputs = new HashMap<>();
458             root = (JSONObject) parser.parse(data);
459             Set entrySet = root.entrySet();
460             Iterator iterator = entrySet.iterator();
461             while (iterator.hasNext()) {
462                 Entry next = (Entry) iterator.next();
463                 String inputName = (String) next.getKey();
464                 if (!isInputNameValid(inputName)) {
465                     return Either.right(ActionStatus.INVALID_INPUT_NAME);
466                 }
467                 JSONObject value = (JSONObject) next.getValue();
468                 Either<InputDefinition, ActionStatus> inputDefinitionEither = getInputDefinitionFromJson(componentId, inputName, value);
469                 if (inputDefinitionEither.isRight()) {
470                     return Either.right(inputDefinitionEither.right().value());
471                 }
472                 inputs.put(inputName, inputDefinitionEither.left().value());
473             }
474             return Either.left(inputs);
475         } catch (ParseException e) {
476             log.warn("Input content is invalid - {}", data, e);
477             return Either.right(ActionStatus.INVALID_CONTENT);
478         }
479     }
480
481     protected boolean isInputNameValid(String inputName) {
482         return Objects.nonNull(inputName) && inputName.matches(PROPERTY_NAME_REGEX);
483     }
484 }