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