HTTP body validation reports invalid error
[cli.git] / profiles / http / src / main / java / org / onap / cli / fw / http / schema / OnapCommandSchemaHttpLoader.java
1 /*
2  * Copyright 2017 Huawei Technologies Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package org.onap.cli.fw.http.schema;
18
19 import java.io.IOException;
20 import java.util.ArrayList;
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Map.Entry;
26 import java.util.Set;
27 import java.util.stream.Collectors;
28
29 import org.onap.cli.fw.cmd.OnapCommand;
30 import org.onap.cli.fw.cmd.OnapCommandType;
31 import org.onap.cli.fw.conf.OnapCommandConfig;
32 import org.onap.cli.fw.conf.OnapCommandConstants;
33 import org.onap.cli.fw.error.OnapCommandException;
34 import org.onap.cli.fw.error.OnapCommandInvalidSchema;
35 import org.onap.cli.fw.error.OnapCommandNotFound;
36 import org.onap.cli.fw.http.auth.OnapCommandHttpService;
37 import org.onap.cli.fw.http.cmd.OnapHttpCommand;
38 import org.onap.cli.fw.http.conf.OnapCommandHttpConstants;
39 import org.onap.cli.fw.http.error.OnapCommandHttpInvalidResultMap;
40 import org.onap.cli.fw.registrar.OnapCommandRegistrar;
41 import org.onap.cli.fw.schema.OnapCommandSchemaLoader;
42 import org.onap.cli.fw.utils.OnapCommandUtils;
43
44 import com.fasterxml.jackson.databind.ObjectMapper;
45
46 import net.minidev.json.JSONObject;
47
48 public class OnapCommandSchemaHttpLoader {
49
50     public static List<String> loadHttpSchema(OnapHttpCommand cmd, String schemaName, boolean includeDefault,
51                                           boolean validateSchema) throws OnapCommandException {
52         try {
53             List<String> errors = new ArrayList<>();
54             if (includeDefault) {
55                 Map<String, ?> defaultParameterMap = OnapCommandSchemaLoader.validateSchemaVersion(OnapCommandHttpConstants.DEFAULT_PARAMETER_HTTP_FILE_NAME, cmd.getSchemaVersion());
56
57                 //mrkanag default_parameter is supported only for parameters.
58                 if (defaultParameterMap.containsKey(OnapCommandConstants.INFO)) {
59                     defaultParameterMap.remove(OnapCommandConstants.INFO);
60                 }
61
62                 errors.addAll(OnapCommandSchemaLoader.parseSchema(cmd, defaultParameterMap, validateSchema));
63             }
64
65             Map<String, List<Map<String, String>>> commandYamlMap =
66                     (Map<String, List<Map<String, String>>>)OnapCommandSchemaLoader.validateSchemaVersion(schemaName, cmd.getSchemaVersion());
67
68             errors.addAll(parseHttpSchema(cmd, commandYamlMap, validateSchema));
69
70             return errors;
71
72         } catch (OnapCommandException e) {
73             throw e;
74         } catch (Exception e) {
75             throw new OnapCommandInvalidSchema(schemaName, e);
76         }
77     }
78
79     /**
80      * Load the schema.
81      *
82      * @param cmd
83      *            OnapHttpCommand
84      * @param schemaName
85      *            schema name
86      * @throws OnapCommandException
87      *             on error
88      */
89     public static ArrayList<String> parseHttpSchema(OnapHttpCommand cmd,
90                                                     final Map<String, ?> values,
91                                                     boolean validate) throws OnapCommandException {
92         ArrayList<String> errorList = new ArrayList<>();
93         try {
94             Map<String, ?> valMap = (Map<String, ?>) values.get(OnapCommandHttpConstants.HTTP);
95
96             if (valMap != null) {
97                 if (validate) {
98                     OnapCommandUtils.validateTags(errorList, valMap, OnapCommandConfig.getCommaSeparatedList(OnapCommandHttpConstants.HTTP_SECTIONS),
99                             OnapCommandConfig.getCommaSeparatedList(OnapCommandHttpConstants.HTTP_MANDATORY_SECTIONS), OnapCommandHttpConstants.HTTP);
100                     errorList.addAll(validateHttpSchemaSection(values));
101                 }
102                 for (Map.Entry<String, ?> entry1 : valMap.entrySet()) {
103                     String key1 = entry1.getKey();
104
105                     switch (key1) {
106                         case OnapCommandHttpConstants.REQUEST:
107                             Map<String, ?> map = (Map<String, ?>) valMap.get(key1);
108
109                             for (Map.Entry<String, ?> entry2 : map.entrySet()) {
110                                 try {
111                                     String key2 = entry2.getKey();
112
113                                     switch (key2) {
114                                         case OnapCommandHttpConstants.URI:
115                                             Object obj = map.get(key2);
116                                             cmd.getInput().setUri(obj.toString());
117                                             break;
118                                         case OnapCommandHttpConstants.METHOD_TYPE:
119                                             Object method = map.get(key2);
120                                             cmd.getInput().setMethod(method.toString());
121                                             break;
122                                         case OnapCommandHttpConstants.BODY:
123                                             Object body = map.get(key2);
124                                             cmd.getInput().setBody(body.toString());
125                                             break;
126                                         case OnapCommandHttpConstants.HEADERS:
127                                             Map<String, String> head = (Map<String, String>) map.get(key2);
128                                             cmd.getInput().setReqHeaders(head);
129                                             break;
130                                         case OnapCommandHttpConstants.QUERIES:
131                                             Map<String, String> query = (Map<String, String>) map.get(key2);
132
133                                             cmd.getInput().setReqQueries(query);
134                                             break;
135                                         case OnapCommandHttpConstants.CONTEXT:
136                                             Map<String, Object> context = (Map<String, Object>) map.get(key2);
137
138                                             for (String key: context.keySet()) {
139                                                 switch (key) {
140                                                     case OnapCommandHttpConstants.CONTEXT_REMOVE_EMPTY_JSON_NODES:
141                                                         Boolean flag = (Boolean) context.get(OnapCommandHttpConstants.CONTEXT_REMOVE_EMPTY_JSON_NODES);
142                                                         cmd.getInput().getContext().put(OnapCommandHttpConstants.CONTEXT_REMOVE_EMPTY_JSON_NODES, flag.toString());
143                                                         break;
144                                                 }
145                                             }
146
147
148                                             break;
149                                         case OnapCommandHttpConstants.MULTIPART_ENTITY_NAME:
150                                             Object multipartEntityName = map.get(key2);
151                                             cmd.getInput().setMultipartEntityName(multipartEntityName.toString());
152                                             break;
153                                     }
154                                 }catch (Exception ex) {
155                                     OnapCommandUtils.throwOrCollect(new OnapCommandInvalidSchema(cmd.getSchemaName(), ex), errorList, validate);
156                                 }
157                             }
158                             break;
159
160                         case OnapCommandHttpConstants.SERVICE:
161                             Map<String, String> serviceMap = (Map<String, String>) valMap.get(key1);
162
163                             if (serviceMap != null) {
164                                 if (validate) {
165                                     OnapCommandUtils.validateTags(errorList, (Map<String, Object>) valMap.get(key1),
166                                             OnapCommandConfig.getCommaSeparatedList(OnapCommandHttpConstants.SERVICE_PARAMS_LIST),
167                                             OnapCommandConfig.getCommaSeparatedList(OnapCommandHttpConstants.SERVICE_PARAMS_MANDATORY_LIST), OnapCommandHttpConstants.SERVICE);
168
169                                     HashMap<String, String> validationMap = new HashMap<>();
170                                     validationMap.put(OnapCommandHttpConstants.AUTH, OnapCommandHttpConstants.AUTH_VALUES);
171                                     validationMap.put(OnapCommandHttpConstants.MODE, OnapCommandHttpConstants.MODE_VALUES);
172
173                                     for (String secKey : validationMap.keySet()) {
174                                         if (serviceMap.containsKey(secKey)) {
175                                             Object obj = serviceMap.get(secKey);
176                                             if (obj == null) {
177                                                 errorList.add("Attribute '" + secKey + "' under '" + OnapCommandHttpConstants.SERVICE + "' is empty");
178                                             } else {
179                                                 String value = String.valueOf(obj);
180                                                 if (!OnapCommandConfig.getCommaSeparatedList(validationMap.get(secKey)).contains(value)) {
181                                                     errorList.add("Attribute '" + secKey + "' contains invalid value. Valide values are "
182                                                             + OnapCommandConfig.getCommaSeparatedList(validationMap.get(key1))); //
183                                                 }
184                                             }
185                                         }
186                                     }
187                                 }
188
189                                 OnapCommandHttpService srv = new OnapCommandHttpService();
190
191                                 for (Map.Entry<String, String> entry : serviceMap.entrySet()) {
192                                     String key = entry.getKey();
193
194                                     switch (key) {
195                                         case OnapCommandConstants.NAME:
196                                             srv.setName(serviceMap.get(key));
197                                             break;
198
199                                         case OnapCommandHttpConstants.VERSION:
200                                             srv.setVersion(serviceMap.get(key).toString());
201                                             break;
202
203                                         case OnapCommandHttpConstants.AUTH:
204                                             Object obj = serviceMap.get(key);
205                                             srv.setAuthType(obj.toString());
206                                             break;
207
208                                         case OnapCommandHttpConstants.MODE:
209                                             Object mode = serviceMap.get(key);
210                                             srv.setMode(mode.toString());
211                                             break;
212                                     }
213                                 }
214
215                                 cmd.setService(srv);
216                             }
217                             break;
218
219                         case OnapCommandHttpConstants.SUCCESS_CODES:
220                             if (validate) {
221                                 validateHttpSccessCodes(errorList, (List<Object>) valMap.get(key1));
222                             }
223                             cmd.setSuccessStatusCodes((ArrayList) valMap.get(key1));
224                             break;
225
226                         case OnapCommandHttpConstants.RESULT_MAP:
227                             if (validate) {
228                                 validateHttpResultMap(errorList, values);
229                             }
230                             cmd.setResultMap((Map<String, String>) valMap.get(key1));
231                             break;
232
233                         case OnapCommandHttpConstants.SAMPLE_RESPONSE:
234                             // (mrkanag) implement sample response handling
235                             break;
236                     }
237                 }
238             }
239         }catch (OnapCommandException e) {
240             OnapCommandUtils.throwOrCollect(e, errorList, validate);
241         }
242
243         //Handle the parameters for auth:
244         // for commands, copy params from login command to this command
245         if (!cmd.getService().isNoAuth()) {
246             if (cmd.getInfo().getCommandType().equals(OnapCommandType.AUTH)) {
247                 OnapCommandUtils.throwOrCollect(new OnapCommandInvalidSchema(
248                         cmd.getSchemaName(), "For auth type commands, http->service->auth should be none"),
249                         errorList,
250                         validate);
251             } else {
252                 OnapCommand login = OnapCommandSchemaHttpLoader.findAuthCommand(cmd, "login");
253                 OnapCommandUtils.copyParamSchemasFrom(login, cmd);
254             }
255         } else {
256             //with service->auth: none,
257             //normal cmd: ignore all auth parms
258             if (!cmd.getInfo().getCommandType().equals(OnapCommandType.AUTH)) {
259                 cmd.getParametersMap().get(OnapCommandHttpConstants.DEAFULT_PARAMETER_USERNAME).setInclude(false);
260                 cmd.getParametersMap().get(OnapCommandHttpConstants.DEAFULT_PARAMETER_PASSWORD).setInclude(false);
261                 cmd.getParametersMap().get(OnapCommandHttpConstants.DEFAULT_PARAMETER_NO_AUTH).setInclude(false);
262             } else {
263                 //auth: login and logout commands, ignore no-auth
264                 cmd.getParametersMap().get(OnapCommandHttpConstants.DEFAULT_PARAMETER_NO_AUTH).setInclude(false);
265                 //auth: only for logout commands, ignore username and password too
266                 if (!cmd.getName().endsWith(OnapCommandHttpConstants.AUTH_SERVICE_LOGIN)) {
267                     cmd.getParametersMap().get(OnapCommandHttpConstants.DEAFULT_PARAMETER_USERNAME).setInclude(false);
268                     cmd.getParametersMap().get(OnapCommandHttpConstants.DEAFULT_PARAMETER_PASSWORD).setInclude(false);
269                 }
270             }
271         }
272
273         return errorList;
274     }
275
276     public static ArrayList<String> validateHttpSchemaSection(Map<String, ?> values) {
277         ArrayList<String> errorList = new ArrayList<>();
278         Map<String, ?> map = (Map<String, ?>) values.get(OnapCommandHttpConstants.HTTP);
279         Map<String, Object> requestMap = (Map<String, Object>) map.get(OnapCommandHttpConstants.REQUEST);
280
281         if (requestMap != null && !requestMap.isEmpty()) {
282             OnapCommandUtils.validateTags(errorList, requestMap, OnapCommandConfig.getCommaSeparatedList(OnapCommandHttpConstants.HTTP_REQUEST_PARAMS),
283                     OnapCommandConfig.getCommaSeparatedList(OnapCommandHttpConstants.HTTP_REQUEST_MANDATORY_PARAMS), OnapCommandHttpConstants.REQUEST);
284             String method = (String) requestMap.get(OnapCommandHttpConstants.METHOD);
285             if (method != null && !method.isEmpty()) {
286                 if (!OnapCommandConfig.getCommaSeparatedList(OnapCommandHttpConstants.HTTP_METHODS).contains(method.toLowerCase())) {
287                     errorList.add("Attribute '" + OnapCommandHttpConstants.METHOD + "' under '" + OnapCommandHttpConstants.REQUEST + "' is invalid, correct types are "
288                             + OnapCommandConfig.getCommaSeparatedList(OnapCommandHttpConstants.HTTP_METHODS).toString());
289                 }
290             } else {
291                 errorList.add("Http request method cann't be null or empty");
292             }
293
294             Set<String> requestParams = getRequestParams(values);
295
296             Set<String> uriParams = validateHttpUri(errorList, requestMap);
297
298             Set<String> bodyParams = validateHttpBody(errorList, requestMap);
299
300             Set<String> headerParams = validateHttpHeaders(requestMap);
301
302             Set<String> queryParams = validateHttpQueries(requestMap);
303
304             HashSet<String> totoalParams = new HashSet<>(uriParams);
305             totoalParams.addAll(bodyParams);
306             totoalParams.addAll(headerParams);
307             totoalParams.addAll(queryParams);
308
309             List<String> nonDeclaredParams = totoalParams.stream().filter(param -> !requestParams.contains(param))
310                     .collect(Collectors.toList());
311
312             nonDeclaredParams.stream().forEach(p -> errorList.add("The parameter '" + p
313                     + "' declared under 'parameters:' section is not mapped into request section."));
314         } else {
315             errorList.add(OnapCommandUtils.emptySection(OnapCommandHttpConstants.REQUEST));
316         }
317         return errorList;
318     }
319
320     public static void validateHttpSccessCodes(List<String> errorList, List<Object> requestSuccessCodes) {
321
322         if (requestSuccessCodes == null || requestSuccessCodes.isEmpty()) {
323             errorList.add(OnapCommandHttpConstants.HTTP_SUCCESS_CODE_INVALID);
324             return;
325         }
326
327         for (Object successCode : requestSuccessCodes) {
328             Integer code = (Integer) successCode;
329             if (code < 200 || code >= 300) {
330                 if ( code != 404) {
331                     errorList.add(OnapCommandHttpConstants.HTTP_SUCCESS_CODE_INVALID);
332                 }
333             }
334         }
335
336     }
337
338     public static void validateHttpResultMap(List<String> errorList, Map<String, ?> values) throws OnapCommandException {
339         Map<String, ?> valMap = (Map<String, ?>) values.get(OnapCommandHttpConstants.HTTP);
340         List<Map<String, String>> attributes = (List<Map<String, String>>) ((Map<String, ?>)values.get(OnapCommandConstants.RESULTS)).get(OnapCommandConstants.ATTRIBUTES);
341         Set<String> resultMapParams = ((Map<String, String>) valMap.get(OnapCommandHttpConstants.RESULT_MAP)).keySet();
342
343         Set<String> resultAttNames = attributes.stream().map(map -> map.get(OnapCommandConstants.NAME))
344                 .collect(Collectors.toSet());
345
346         List<String> invaliResultMapParams = resultMapParams.stream()
347                 .filter(p -> !resultAttNames.contains(p)).collect(Collectors.toList());
348         List<String> attributesMissing = resultAttNames.stream()
349                 .filter(p -> !resultMapParams.contains(p)).collect(Collectors.toList());
350         invaliResultMapParams.addAll(attributesMissing);
351
352         if (!invaliResultMapParams.isEmpty()) {
353             OnapCommandUtils.throwOrCollect(new OnapCommandHttpInvalidResultMap(invaliResultMapParams), errorList, true);
354         }
355     }
356
357     public static Set<String> validateHttpQueries(Map<String, Object> requestMap) {
358         Map<String, Object> queries = (Map<String, Object>) requestMap.get(OnapCommandHttpConstants.QUERIES);
359         Set<String> queryParamNames = new HashSet<>();
360         if (queries != null) {
361             for (Entry<String, Object> entry : queries.entrySet()) {
362                 OnapCommandUtils.parseParameters(String.valueOf(entry.getValue()), queryParamNames);
363             }
364         }
365         return queryParamNames;
366     }
367
368     public static Set<String> validateHttpHeaders(Map<String, Object> requestMap) {
369
370         Map<String, Object> headers = (Map<String, Object>) requestMap.get(OnapCommandHttpConstants.HEADERS);
371         Set<String> headerParamNames = new HashSet<>();
372         if (headers != null) {
373             for (Entry<String, Object> entry : headers.entrySet()) {
374                 OnapCommandUtils.parseParameters(String.valueOf(entry.getValue()), headerParamNames);
375             }
376         }
377         return headerParamNames;
378     }
379
380     public static Set<String> validateHttpBody(List<String> errorList, Map<String, Object> requestMap) {
381         Set<String> bodyParamNames = new HashSet<>();
382         Object bodyString = requestMap.get(OnapCommandHttpConstants.BODY);
383         if (bodyString == null) {
384             return bodyParamNames;
385         }
386
387         String body = String.valueOf(bodyString);
388
389         if (body == null || "".equals(body)) {
390             errorList.add(OnapCommandHttpConstants.HTTP_BODY_JSON_EMPTY);
391         } else {
392             try {
393                 new ObjectMapper().readValue(body, JSONObject.class);
394             } catch (IOException e1) { // NOSONAR
395                 errorList.add(OnapCommandHttpConstants.HTTP_BODY_FAILED_PARSING);
396             }
397         }
398
399         OnapCommandUtils.parseParameters(body, bodyParamNames);
400
401         return bodyParamNames;
402     }
403
404     public static Set<String> validateHttpUri(List<String> errorList, Map<String, Object> requestMap) {
405         Set<String> uriParamNames = new HashSet<>();
406         String uri = (String) requestMap.get(OnapCommandHttpConstants.URI);
407         if (uri == null || uri.isEmpty()) {
408             errorList.add(OnapCommandUtils.emptySection(OnapCommandHttpConstants.URI));
409             return uriParamNames;
410         }
411         OnapCommandUtils.parseParameters(uri, uriParamNames);
412         return uriParamNames;
413     }
414
415     public static Set<String> getRequestParams(Map<String, ?> yamlMap) {
416
417         Set<String> set = new HashSet<>();
418
419         @SuppressWarnings("unchecked")
420         List<Map<String, Object>> inputParams = (List<Map<String, Object>>) yamlMap.get(OnapCommandConstants.PARAMETERS);
421
422         if (inputParams != null) {
423             for (Map<String, Object> map : inputParams) {
424                 for (Entry<String, Object> entry : map.entrySet()) {
425                     Object key = entry.getKey();
426
427                     if (OnapCommandConstants.NAME.equals(key)) {
428                         set.add(String.valueOf(entry.getValue()));
429                         break;
430                     }
431                 }
432             }
433         }
434
435         return set;
436     }
437
438     /**
439         *
440         * @param authAction login/logout
441         * @return
442         * @throws OnapCommandException
443         */
444        public static OnapCommand findAuthCommand(OnapHttpCommand forCmd, String authAction) throws OnapCommandException {
445            OnapCommand auth = null;
446            try {
447                //mrkanag: fix this to discover the auth command by matching info->product & service
448                auth = OnapCommandRegistrar.getRegistrar().get(
449                        forCmd.getInfo().getService() + "-" +
450                        forCmd.getService().getAuthType() + "-" + authAction,
451                        forCmd.getInfo().getProduct());
452            } catch (OnapCommandNotFound e) {  // NOSONAR
453                auth = OnapCommandRegistrar.getRegistrar().get(
454                        forCmd.getService().getAuthType() + "-" + authAction,
455                        forCmd.getInfo().getProduct());
456            }
457
458            return auth;
459        }
460
461
462 }