View Javadoc
1   /*
2    * This file is part of Waarp Project (named also Waarp or GG).
3    *
4    *  Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
5    *  tags. See the COPYRIGHT.txt in the distribution for a full listing of
6    * individual contributors.
7    *
8    *  All Waarp Project is free software: you can redistribute it and/or
9    * modify it under the terms of the GNU General Public License as published by
10   * the Free Software Foundation, either version 3 of the License, or (at your
11   * option) any later version.
12   *
13   * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY
14   * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15   * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16   *
17   *  You should have received a copy of the GNU General Public License along with
18   * Waarp . If not, see <http://www.gnu.org/licenses/>.
19   */
20  package org.waarp.gateway.kernel.rest;
21  
22  import com.fasterxml.jackson.databind.JsonNode;
23  import com.fasterxml.jackson.databind.node.ArrayNode;
24  import com.fasterxml.jackson.databind.node.ObjectNode;
25  import io.netty.buffer.Unpooled;
26  import io.netty.handler.codec.http.FullHttpRequest;
27  import io.netty.handler.codec.http.HttpHeaderNames;
28  import io.netty.handler.codec.http.HttpRequest;
29  import io.netty.handler.codec.http.HttpResponseStatus;
30  import io.netty.handler.codec.http.QueryStringDecoder;
31  import io.netty.handler.codec.http.QueryStringEncoder;
32  import io.netty.handler.codec.http.cookie.Cookie;
33  import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
34  import org.joda.time.DateTime;
35  import org.joda.time.Duration;
36  import org.waarp.common.crypto.HmacSha256;
37  import org.waarp.common.exception.InvalidArgumentException;
38  import org.waarp.common.json.JsonHandler;
39  import org.waarp.common.logging.WaarpLogger;
40  import org.waarp.common.logging.WaarpLoggerFactory;
41  import org.waarp.common.role.RoleDefault;
42  import org.waarp.common.role.RoleDefault.ROLE;
43  import org.waarp.common.utility.ParametersChecker;
44  import org.waarp.gateway.kernel.exception.HttpIncorrectRequestException;
45  import org.waarp.gateway.kernel.exception.HttpInvalidAuthenticationException;
46  import org.waarp.gateway.kernel.rest.DataModelRestMethodHandler.COMMAND_TYPE;
47  import org.waarp.gateway.kernel.rest.HttpRestHandler.METHOD;
48  
49  import java.util.Collections;
50  import java.util.Iterator;
51  import java.util.List;
52  import java.util.Map;
53  import java.util.Map.Entry;
54  import java.util.Set;
55  import java.util.TreeMap;
56  
57  /**
58   * Rest object that contains all arguments or answers once all tasks are
59   * over:</br>
60   * - ARG_HASBODY, ARG_METHOD, ARG_PATH, ARG_BASEPATH, ARGS_SUBPATH,
61   * ARG_X_AUTH_KEY, ARG_X_AUTH_USER,
62   * ARG_X_AUTH_TIMESTAMP, ARG_X_AUTH_ROLE: root elements (query only)</br>
63   * - ARG_METHOD, ARG_PATH, ARG_BASEPATH, ARGS_SUBPATH, ARG_X_AUTH_USER,
64   * JSON_STATUSMESSAGE, JSON_STATUSCODE,
65   * JSON_COMMAND: root elements (answer only)</br>
66   * - ARGS_URI: uri elements (query only)</br>
67   * - ARGS_HEADER: header elements (query only)</br>
68   * - ARG_COOKIE: cookie elements</br>
69   * - ARGS_BODY: body elements (query only)</br>
70   * - ARGS_ANSWER: answer part (answer only)</br>
71   */
72  public class RestArgument {
73    /**
74     * Internal Logger
75     */
76    private static final WaarpLogger logger =
77        WaarpLoggerFactory.getLogger(RestArgument.class);
78  
79    public enum REST_GROUP {
80      /**
81       * arguments.path(ARGS_URI) main entry for URI arguments
82       */
83      ARGS_URI("uri"),
84      /**
85       * arguments.path(ARGS_HEADER) main entry for HEADER arguments
86       */
87      ARGS_HEADER("header"),
88      /**
89       * arguments.path(ARGS_COOKIE) main entry for COOKIE arguments
90       */
91      ARGS_COOKIE("cookie"),
92      /**
93       * arguments.path(ARGS_BODY) main entry for BODY arguments
94       */
95      ARGS_BODY("body"),
96      /**
97       * arguments.path(ARGS_ANSWER) main entry for ANSWER arguments
98       */
99      ARGS_ANSWER("answer");
100 
101     public final String group;
102 
103     REST_GROUP(final String group) {
104       this.group = group;
105     }
106   }
107 
108   public enum REST_ROOT_FIELD {
109     /**
110      * arguments.path(ARG_PATH) = uri path
111      */
112     ARG_PATH("path"),
113     /**
114      * arguments.path(ARG_BASEPATH).asText() = uri base path
115      */
116     ARG_BASEPATH("base"),
117     /**
118      * arguments.path(ARGS_SUBPATH) main entry for SUB-PATH arguments<br>
119      * arguments.path(ARGS_SUBPATH).elements() for an iterator or .get(x)
120      * for
121      * xth SUB-PATH argument
122      */
123     ARGS_SUBPATH("subpath"),
124     /**
125      * arguments.path(ARG_METHOD).asText() = method identified
126      */
127     ARG_METHOD("X-method"),
128     /**
129      * arguments.path(ARG_HASBODY).asBoolean() = true if the body has
130      * content
131      */
132     ARG_HASBODY("hasBody"),
133     /**
134      * arguments.path(ARG_X_AUTH_KEY).asText() = Key used
135      */
136     ARG_X_AUTH_KEY("X-Auth-Key"),
137     /**
138      * arguments.path(ARG_X_AUTH_KEY).asText() = Key used
139      */
140     ARG_X_AUTH_USER("X-Auth-User"),
141     /**
142      * Internal Key used (not to be passed through wire)
143      */
144     ARG_X_AUTH_INTERNALKEY("X-Auth-InternalKey"),
145     /**
146      * arguments.path(ARG_X_AUTH_TIMESTAMP).asText() = Timestamp in ISO 8601
147      * format
148      */
149     ARG_X_AUTH_TIMESTAMP("X-Auth-Timestamp"),
150     /**
151      * arguments.path(ARG_X_AUTH_ROLE).asInt() = Role used
152      */
153     ARG_X_AUTH_ROLE("X-Auth-Role"), JSON_STATUSCODE("code"),
154     JSON_STATUSMESSAGE("message"), JSON_COMMAND("command"),
155     JSON_DETAIL("detail");
156 
157     public final String field;
158 
159     REST_ROOT_FIELD(final String field) {
160       this.field = field;
161     }
162   }
163 
164   public enum REST_FIELD {
165     JSON_RESULT("result"), JSON_PATH("path"), JSON_JSON("body"),
166     X_DETAILED_ALLOW("DetailedAllow"), X_ALLOW_URIS("UriAllowed"),
167     JSON_ID("_id");
168 
169     public final String field;
170 
171     REST_FIELD(final String field) {
172       this.field = field;
173     }
174   }
175 
176   public enum DATAMODEL {
177     JSON_COUNT("count"), JSON_RESULTS("results"), JSON_FILTER("filter"),
178     JSON_LIMIT("limit");
179 
180     public final String field;
181 
182     DATAMODEL(final String field) {
183       this.field = field;
184     }
185   }
186 
187   final ObjectNode arguments;
188 
189   /**
190    * Create a RestArgument
191    *
192    * @param emptyArgument might be null, but might be also already
193    *     initialized with some values
194    */
195   public RestArgument(final ObjectNode emptyArgument) {
196     if (emptyArgument == null) {
197       arguments = JsonHandler.createObjectNode();
198     } else {
199       arguments = emptyArgument;
200     }
201   }
202 
203   /**
204    * Clean all internal values
205    */
206   public final void clean() {
207     arguments.removeAll();
208   }
209 
210   /**
211    * Set values according to the request URI
212    *
213    * @param request
214    */
215   public final void setRequest(final HttpRequest request) {
216     arguments.put(REST_ROOT_FIELD.ARG_HASBODY.field,
217                   request instanceof FullHttpRequest &&
218                   ((FullHttpRequest) request).content() !=
219                   Unpooled.EMPTY_BUFFER);
220     arguments.put(REST_ROOT_FIELD.ARG_METHOD.field, request.method().name());
221     final QueryStringDecoder decoderQuery =
222         new QueryStringDecoder(request.uri());
223     final String path = decoderQuery.path();
224     arguments.put(REST_ROOT_FIELD.ARG_PATH.field, path);
225     // compute path main uri
226     String basepath = path;
227     int pos = basepath.indexOf('/');
228     if (pos >= 0) {
229       if (pos == 0) {
230         final int pos2 = basepath.indexOf('/', 1);
231         if (pos2 < 0) {
232           basepath = basepath.substring(1);
233         } else {
234           basepath = basepath.substring(1, pos2);
235         }
236       } else {
237         basepath = basepath.substring(0, pos);
238       }
239     }
240     arguments.put(REST_ROOT_FIELD.ARG_BASEPATH.field, basepath);
241     // compute sub path args
242     if (pos == 0) {
243       pos = path.indexOf('/', 1);
244     }
245     if (pos >= 0) {
246       int pos2 = path.indexOf('/', pos + 1);
247       if (pos2 > 0) {
248         final ArrayNode array =
249             arguments.putArray(REST_ROOT_FIELD.ARGS_SUBPATH.field);
250         while (pos2 > 0) {
251           array.add(path.substring(pos + 1, pos2));
252           pos = pos2;
253           pos2 = path.indexOf('/', pos + 1);
254         }
255       }
256       pos2 = path.indexOf('?', pos + 1);
257       if (pos2 > 0 && pos2 > pos + 1) {
258         ArrayNode array =
259             (ArrayNode) arguments.get(REST_ROOT_FIELD.ARGS_SUBPATH.field);
260         if (array == null) {
261           array = arguments.putArray(REST_ROOT_FIELD.ARGS_SUBPATH.field);
262         }
263         array.add(path.substring(pos + 1, pos2));
264       } else {
265         final String last = path.substring(pos + 1);
266         if (!last.isEmpty()) {
267           ArrayNode array =
268               (ArrayNode) arguments.get(REST_ROOT_FIELD.ARGS_SUBPATH.field);
269           if (array == null) {
270             array = arguments.putArray(REST_ROOT_FIELD.ARGS_SUBPATH.field);
271           }
272           array.add(last);
273         }
274       }
275     }
276     final Map<String, List<String>> map = decoderQuery.parameters();
277     final ObjectNode node = arguments.putObject(REST_GROUP.ARGS_URI.group);
278     for (final Entry<String, List<String>> entry : map.entrySet()) {
279       final String key = entry.getKey();
280       try {
281         ParametersChecker.checkSanityString(
282             entry.getValue().toArray(ParametersChecker.ZERO_ARRAY_STRING));
283         if (key.equalsIgnoreCase(REST_ROOT_FIELD.ARG_X_AUTH_KEY.field)) {
284           arguments.put(REST_ROOT_FIELD.ARG_X_AUTH_KEY.field,
285                         entry.getValue().get(0));
286           continue;
287         }
288         if (key.equalsIgnoreCase(REST_ROOT_FIELD.ARG_X_AUTH_USER.field)) {
289           arguments.put(REST_ROOT_FIELD.ARG_X_AUTH_USER.field,
290                         entry.getValue().get(0));
291           continue;
292         }
293         if (key.equalsIgnoreCase(REST_ROOT_FIELD.ARG_X_AUTH_TIMESTAMP.field)) {
294           arguments.put(REST_ROOT_FIELD.ARG_X_AUTH_TIMESTAMP.field,
295                         entry.getValue().get(0));
296           continue;
297         }
298         final List<String> list = entry.getValue();
299         if (list.size() > 1) {
300           final ArrayNode array = node.putArray(key);
301           for (final String val : entry.getValue()) {
302             array.add(val);
303           }
304         } else if (list.isEmpty()) {
305           node.putNull(key);
306         } else {
307           // 1
308           node.put(key, list.get(0));
309         }
310       } catch (final InvalidArgumentException e) {
311         logger.error("Arguments incompatible with Security: " + entry.getKey(),
312                      e);
313       }
314     }
315   }
316 
317   /**
318    * Set X_AUTH_USER, Method, Path, Basepath and Cookie from source
319    *
320    * @param source
321    */
322   public final void setFromArgument(final RestArgument source) {
323     if (source.arguments.has(REST_ROOT_FIELD.ARG_X_AUTH_USER.field)) {
324       arguments.put(REST_ROOT_FIELD.ARG_X_AUTH_USER.field,
325                     source.arguments.get(REST_ROOT_FIELD.ARG_X_AUTH_USER.field)
326                                     .asText());
327     }
328     if (source.arguments.has(REST_ROOT_FIELD.ARG_METHOD.field)) {
329       arguments.put(REST_ROOT_FIELD.ARG_METHOD.field,
330                     source.arguments.get(REST_ROOT_FIELD.ARG_METHOD.field)
331                                     .asText());
332     }
333     if (source.arguments.has(REST_ROOT_FIELD.ARG_PATH.field)) {
334       arguments.put(REST_ROOT_FIELD.ARG_PATH.field,
335                     source.arguments.get(REST_ROOT_FIELD.ARG_PATH.field)
336                                     .asText());
337     }
338     if (source.arguments.has(REST_ROOT_FIELD.ARG_BASEPATH.field)) {
339       arguments.put(REST_ROOT_FIELD.ARG_BASEPATH.field,
340                     source.arguments.get(REST_ROOT_FIELD.ARG_BASEPATH.field)
341                                     .asText());
342     }
343     if (source.arguments.has(REST_ROOT_FIELD.ARGS_SUBPATH.field)) {
344       arguments.putArray(REST_ROOT_FIELD.ARGS_SUBPATH.field).addAll(
345           (ArrayNode) source.arguments.get(REST_ROOT_FIELD.ARGS_SUBPATH.field));
346     }
347     if (source.arguments.has(REST_GROUP.ARGS_URI.group)) {
348       arguments.putObject(REST_GROUP.ARGS_URI.group).setAll(
349           (ObjectNode) source.arguments.get(REST_GROUP.ARGS_URI.group));
350     }
351     if (source.arguments.has(REST_GROUP.ARGS_COOKIE.group)) {
352       arguments.putObject(REST_GROUP.ARGS_COOKIE.group).setAll(
353           (ObjectNode) source.arguments.get(REST_GROUP.ARGS_COOKIE.group));
354     }
355 
356     logger.debug("DEBUG: {}\n {}", arguments, source);
357   }
358 
359   /**
360    * @return the full Path of the URI
361    */
362   public final String getUri() {
363     return arguments.path(REST_ROOT_FIELD.ARG_PATH.field).asText();
364   }
365 
366   /**
367    * @return the base Path of the URI (first item between '/')
368    */
369   public final String getBaseUri() {
370     return arguments.path(REST_ROOT_FIELD.ARG_BASEPATH.field).asText();
371   }
372 
373   /**
374    * @return An iterator of JsonNode, which values can be retrieved by
375    *     item.asText()
376    */
377   public final Iterator<JsonNode> getSubUri() {
378     return arguments.path(REST_ROOT_FIELD.ARGS_SUBPATH.field).elements();
379   }
380 
381   public final int getSubUriSize() {
382     return arguments.path(REST_ROOT_FIELD.ARGS_SUBPATH.field).size();
383   }
384 
385   public final void addSubUriToUriArgs(final String name, final int rank) {
386     final ObjectNode node = getUriArgs();
387     final JsonNode elt =
388         arguments.path(REST_ROOT_FIELD.ARGS_SUBPATH.field).get(rank);
389     if (elt != null) {
390       node.set(name, elt);
391     }
392   }
393 
394   public final void addIdToUriArgs() {
395     addSubUriToUriArgs(REST_FIELD.JSON_ID.field, 0);
396   }
397 
398   public final JsonNode getId() {
399     return getUriArgs().path(REST_FIELD.JSON_ID.field);
400   }
401 
402   public static JsonNode getId(final ObjectNode node) {
403     return node.path(REST_FIELD.JSON_ID.field);
404   }
405 
406   public final long getLimitFromUri() {
407     return getUriArgs().path(DATAMODEL.JSON_LIMIT.field).asLong(100);
408   }
409 
410   public final String getXAuthKey() {
411     return arguments.path(REST_ROOT_FIELD.ARG_X_AUTH_KEY.field).asText();
412   }
413 
414   public final String getXAuthUser() {
415     return arguments.path(REST_ROOT_FIELD.ARG_X_AUTH_USER.field).asText();
416   }
417 
418   public final String getXAuthTimestamp() {
419     return arguments.path(REST_ROOT_FIELD.ARG_X_AUTH_TIMESTAMP.field).asText();
420   }
421 
422   public final void setXAuthRole(final RoleDefault role) {
423     arguments.put(REST_ROOT_FIELD.ARG_X_AUTH_ROLE.field, role.getRoleAsByte());
424   }
425 
426   public final ROLE getXAuthRole() {
427     final byte role =
428         (byte) arguments.get(REST_ROOT_FIELD.ARG_X_AUTH_ROLE.field).asInt();
429     return ROLE.fromByte(role);
430   }
431 
432   /**
433    * @return The ObjectNode containing all couples key/value
434    */
435   public final ObjectNode getUriArgs() {
436     JsonNode node = arguments.path(REST_GROUP.ARGS_URI.group);
437     if (node == null || node.isMissingNode()) {
438       node = arguments.putObject(REST_GROUP.ARGS_URI.group);
439     }
440     return (ObjectNode) node;
441   }
442 
443   /**
444    * @return the method or null
445    */
446   public final METHOD getMethod() {
447     final String text =
448         arguments.path(REST_ROOT_FIELD.ARG_METHOD.field).asText();
449     if (ParametersChecker.isEmpty(text)) {
450       return METHOD.TRACE;
451     }
452     try {
453       return METHOD.valueOf(text);
454     } catch (final Exception e) {
455       return METHOD.TRACE;
456     }
457   }
458 
459   /**
460    * set values from Header into arguments.path(ARGS_HEADER)
461    *
462    * @throws HttpIncorrectRequestException
463    */
464   public final void setHeaderArgs(final List<Entry<String, String>> list) {
465     ObjectNode node = (ObjectNode) arguments.get(REST_GROUP.ARGS_HEADER.group);
466     if (node == null || node.isMissingNode()) {
467       node = arguments.putObject(REST_GROUP.ARGS_HEADER.group);
468     }
469     for (final Entry<String, String> entry : list) {
470       try {
471         ParametersChecker.checkSanityString(entry.getValue());
472         final String key = entry.getKey();
473         if (!key.equals(HttpHeaderNames.COOKIE.toString())) {
474           if (key.equalsIgnoreCase(REST_ROOT_FIELD.ARG_X_AUTH_KEY.field)) {
475             arguments.put(REST_ROOT_FIELD.ARG_X_AUTH_KEY.field,
476                           entry.getValue());
477             continue;
478           }
479           if (key.equalsIgnoreCase(REST_ROOT_FIELD.ARG_X_AUTH_USER.field)) {
480             arguments.put(REST_ROOT_FIELD.ARG_X_AUTH_USER.field,
481                           entry.getValue());
482             continue;
483           }
484           if (key.equalsIgnoreCase(
485               REST_ROOT_FIELD.ARG_X_AUTH_TIMESTAMP.field)) {
486             arguments.put(REST_ROOT_FIELD.ARG_X_AUTH_TIMESTAMP.field,
487                           entry.getValue());
488             continue;
489           }
490           node.put(key, entry.getValue());
491         }
492       } catch (final InvalidArgumentException e) {
493         logger.error("Arguments incompatible with Security: " + entry.getKey(),
494                      e);
495       }
496     }
497   }
498 
499   /**
500    * set values from Header into arguments.path(ARGS_HEADER)
501    *
502    * @throws HttpIncorrectRequestException
503    */
504   public final void setHeaderArgs(
505       final Iterator<Entry<CharSequence, CharSequence>> iterator) {
506     ObjectNode node = (ObjectNode) arguments.get(REST_GROUP.ARGS_HEADER.group);
507     if (node == null || node.isMissingNode()) {
508       node = arguments.putObject(REST_GROUP.ARGS_HEADER.group);
509     }
510     while (iterator.hasNext()) {
511       final Entry<CharSequence, CharSequence> entry = iterator.next();
512       try {
513         ParametersChecker.checkSanityString(entry.getValue().toString());
514         final String key = entry.getKey().toString();
515         if (!key.equals(HttpHeaderNames.COOKIE.toString())) {
516           if (key.equalsIgnoreCase(REST_ROOT_FIELD.ARG_X_AUTH_KEY.field)) {
517             arguments.put(REST_ROOT_FIELD.ARG_X_AUTH_KEY.field,
518                           entry.getValue().toString());
519             continue;
520           }
521           if (key.equalsIgnoreCase(REST_ROOT_FIELD.ARG_X_AUTH_USER.field)) {
522             arguments.put(REST_ROOT_FIELD.ARG_X_AUTH_USER.field,
523                           entry.getValue().toString());
524             continue;
525           }
526           if (key.equalsIgnoreCase(
527               REST_ROOT_FIELD.ARG_X_AUTH_TIMESTAMP.field)) {
528             arguments.put(REST_ROOT_FIELD.ARG_X_AUTH_TIMESTAMP.field,
529                           entry.getValue().toString());
530             continue;
531           }
532           node.put(key, entry.getValue().toString());
533         }
534       } catch (final InvalidArgumentException e) {
535         logger.error("Arguments incompatible with Security: " + entry.getKey(),
536                      e);
537       }
538     }
539   }
540 
541   /**
542    * @return The ObjectNode containing all couples key/value
543    */
544   public final ObjectNode getHeaderArgs() {
545     JsonNode node = arguments.path(REST_GROUP.ARGS_HEADER.group);
546     if (node == null || node.isMissingNode()) {
547       node = arguments.putObject(REST_GROUP.ARGS_HEADER.group);
548     }
549     return (ObjectNode) node;
550   }
551 
552   /**
553    * set method From URI
554    */
555   public final void methodFromUri() {
556     final JsonNode node = arguments.path(REST_GROUP.ARGS_URI.group)
557                                    .path(REST_ROOT_FIELD.ARG_METHOD.field);
558     if (!node.isMissingNode()) {
559       // override
560       arguments.put(REST_ROOT_FIELD.ARG_METHOD.field, node.asText());
561     }
562   }
563 
564   /**
565    * set method From Header
566    */
567   public final void methodFromHeader() {
568     final JsonNode node = arguments.path(REST_GROUP.ARGS_HEADER.group)
569                                    .path(REST_ROOT_FIELD.ARG_METHOD.field);
570     if (!node.isMissingNode()) {
571       // override
572       arguments.put(REST_ROOT_FIELD.ARG_METHOD.field, node.asText());
573     }
574   }
575 
576   /**
577    * set values from Cookies into arguments.path(ARGS_COOKIE)
578    */
579   public final void setCookieArgs(final String cookieString) {
580     final Set<Cookie> cookies;
581     if (cookieString == null) {
582       cookies = Collections.emptySet();
583     } else {
584       cookies = ServerCookieDecoder.LAX.decode(cookieString);
585     }
586     if (!cookies.isEmpty()) {
587       final ObjectNode node = arguments.putObject(REST_GROUP.ARGS_COOKIE.group);
588       for (final Cookie cookie : cookies) {
589         try {
590           ParametersChecker.checkSanityString(cookie.value());
591           node.put(cookie.name(), cookie.value());
592         } catch (final InvalidArgumentException e) {
593           logger.error("Arguments incompatible with Security: " + cookie.name(),
594                        e);
595         }
596       }
597     }
598   }
599 
600   /**
601    * @return The ObjectNode containing all couples key/value
602    */
603   public final ObjectNode getCookieArgs() {
604     JsonNode node = arguments.path(REST_GROUP.ARGS_COOKIE.group);
605     if (node == null || node.isMissingNode()) {
606       node = arguments.putObject(REST_GROUP.ARGS_COOKIE.group);
607     }
608     return (ObjectNode) node;
609   }
610 
611   /**
612    * @return The ObjectNode containing all couples key/value
613    */
614   public final ObjectNode getBody() {
615     JsonNode node = arguments.path(REST_GROUP.ARGS_BODY.group);
616     if (node == null || node.isMissingNode()) {
617       node = arguments.putObject(REST_GROUP.ARGS_BODY.group);
618     }
619     return (ObjectNode) node;
620   }
621 
622   /**
623    * @return The ObjectNode containing all couples key/value
624    */
625   public final ObjectNode getAnswer() {
626     JsonNode node = arguments.path(REST_GROUP.ARGS_ANSWER.group);
627     if (node == null || node.isMissingNode()) {
628       node = arguments.putObject(REST_GROUP.ARGS_ANSWER.group);
629     }
630     return (ObjectNode) node;
631   }
632 
633   public final void addAnswer(final ObjectNode node) {
634     getAnswer().setAll(node);
635   }
636 
637   public final void setResult(final HttpResponseStatus status) {
638     arguments.put(REST_ROOT_FIELD.JSON_STATUSMESSAGE.field,
639                   status.reasonPhrase());
640     arguments.put(REST_ROOT_FIELD.JSON_STATUSCODE.field, status.code());
641   }
642 
643   /**
644    * @return the Http Status code
645    */
646   public final int getStatusCode() {
647     return arguments.path(REST_ROOT_FIELD.JSON_STATUSCODE.field).asInt();
648   }
649 
650   /**
651    * @return the Http Status message according to the Http Status code
652    */
653   public final String getStatusMessage() {
654     return arguments.path(REST_ROOT_FIELD.JSON_STATUSMESSAGE.field).asText();
655   }
656 
657   public final void setDetail(final String detail) {
658     arguments.put(REST_ROOT_FIELD.JSON_DETAIL.field, detail);
659   }
660 
661   /**
662    * @return the detail information on error (mainly)
663    */
664   public final String getDetail() {
665     return arguments.path(REST_ROOT_FIELD.JSON_DETAIL.field).asText();
666   }
667 
668   public final void setCommand(final COMMAND_TYPE command) {
669     arguments.put(REST_ROOT_FIELD.JSON_COMMAND.field, command.name());
670   }
671 
672   public final void setCommand(final String cmd) {
673     arguments.put(REST_ROOT_FIELD.JSON_COMMAND.field, cmd);
674   }
675 
676   /**
677    * @return the COMMAND field, to be transformed either into COMMAND_TYPE or
678    *     ACTIONS_TYPE
679    */
680   public final String getCommandField() {
681     return arguments.path(REST_ROOT_FIELD.JSON_COMMAND.field).asText();
682   }
683 
684   /**
685    * @return the COMMAND_TYPE but might be null if not found or if of
686    *     ACTIONS_TYPE
687    */
688   public final COMMAND_TYPE getCommand() {
689     final String cmd =
690         arguments.path(REST_ROOT_FIELD.JSON_COMMAND.field).asText();
691     if (ParametersChecker.isNotEmpty(cmd)) {
692       try {
693         return COMMAND_TYPE.valueOf(cmd);
694       } catch (final Exception e) {
695         return null;
696       }
697     } else {
698       return null;
699     }
700   }
701 
702   /**
703    * @param filter the filter used in multi get
704    */
705   public final void addFilter(ObjectNode filter) {
706     if (filter == null) {
707       filter = JsonHandler.createObjectNode();
708     }
709     getAnswer().putObject(DATAMODEL.JSON_FILTER.field).setAll(filter);
710   }
711 
712   /**
713    * @return the filter used in multi get
714    */
715   public final ObjectNode getFilter() {
716     return (ObjectNode) getAnswer().path(DATAMODEL.JSON_FILTER.field);
717   }
718 
719   /**
720    * @return the array of results (in DataModel multi get)
721    */
722   public final ArrayNode getResults() {
723     JsonNode node = getAnswer().path(DATAMODEL.JSON_RESULTS.field);
724     if (node == null || node.isMissingNode()) {
725       node = getAnswer().putArray(DATAMODEL.JSON_RESULTS.field);
726     }
727     return (ArrayNode) node;
728   }
729 
730   /**
731    * @param result added to the array of results (in DataModel multi
732    *     get)
733    */
734   public final void addResult(final ObjectNode result) {
735     getResults().add(result);
736   }
737 
738   /**
739    * @param count added to answer if > 0
740    * @param limit added to answer
741    */
742   public final void addCountLimit(final long count, final long limit) {
743     final ObjectNode node = getAnswer();
744     if (count >= 0) {
745       node.put(DATAMODEL.JSON_COUNT.field, count);
746     }
747     node.put(DATAMODEL.JSON_LIMIT.field, limit);
748   }
749 
750   /**
751    * @return the count of element (-1 if not found)
752    */
753   public final long getCount() {
754     return getAnswer().path(DATAMODEL.JSON_COUNT.field).asLong(-1);
755   }
756 
757   public final long getLimit() {
758     return getAnswer().path(DATAMODEL.JSON_LIMIT.field).asLong(100);
759   }
760 
761   /**
762    * Add options in answer
763    *
764    * @param allow
765    * @param path
766    * @param detailedAllow
767    */
768   public final void addOptions(final String allow, final String path,
769                                final ArrayNode detailedAllow) {
770     final ObjectNode node = getAnswer();
771     node.put(HttpHeaderNames.ALLOW.toString(), allow);
772     node.put(REST_FIELD.X_ALLOW_URIS.field, path);
773     if (detailedAllow != null) {
774       node.putArray(REST_FIELD.X_DETAILED_ALLOW.field).addAll(detailedAllow);
775     }
776   }
777 
778   public final String getAllowOption() {
779     return getAnswer().path(HttpHeaderNames.ALLOW.toString()).asText();
780   }
781 
782   public final String getAllowUrisOption() {
783     return getAnswer().path(REST_FIELD.X_ALLOW_URIS.field).asText();
784   }
785 
786   public final ArrayNode getDetailedAllowOption() {
787     final JsonNode node = getAnswer().path(REST_FIELD.X_DETAILED_ALLOW.field);
788     if (node.isMissingNode()) {
789       return JsonHandler.createArrayNode();
790     } else {
791       return (ArrayNode) node;
792     }
793   }
794 
795   /**
796    * The encoder is completed with extra necessary URI part containing
797    * ARG_X_AUTH_TIMESTAMP & ARG_X_AUTH_KEY
798    *
799    * @param hmacSha256 SHA-256 key to create the signature
800    * @param encoder
801    * @param user might be null
802    * @param extraKey might be null
803    *
804    * @return an array of 2 value in order ARG_X_AUTH_TIMESTAMP and
805    *     ARG_X_AUTH_KEY
806    *
807    * @throws HttpInvalidAuthenticationException if the computation of
808    *     the
809    *     authentication failed
810    */
811   public static String[] getBaseAuthent(final HmacSha256 hmacSha256,
812                                         final QueryStringEncoder encoder,
813                                         final String user,
814                                         final String extraKey)
815       throws HttpInvalidAuthenticationException {
816     final QueryStringDecoder decoderQuery =
817         new QueryStringDecoder(encoder.toString());
818     final Map<String, List<String>> map = decoderQuery.parameters();
819     final TreeMap<String, String> treeMap = new TreeMap<String, String>();
820     for (final Entry<String, List<String>> entry : map.entrySet()) {
821       final String keylower = entry.getKey().toLowerCase();
822       final List<String> values = entry.getValue();
823       if (values != null && !values.isEmpty()) {
824         final String last = values.get(values.size() - 1);
825         treeMap.put(keylower, last);
826       }
827     }
828     final DateTime date = new DateTime();
829     treeMap.put(REST_ROOT_FIELD.ARG_X_AUTH_TIMESTAMP.field.toLowerCase(),
830                 date.toString());
831     if (user != null) {
832       treeMap.put(REST_ROOT_FIELD.ARG_X_AUTH_USER.field.toLowerCase(), user);
833     }
834     try {
835       final String key =
836           computeKey(hmacSha256, extraKey, treeMap, decoderQuery.path());
837       return new String[] { date.toString(), key };
838     } catch (final Exception e) {
839       throw new HttpInvalidAuthenticationException(e);
840     }
841 
842   }
843 
844   /**
845    * Check Time only (no signature)
846    *
847    * @param maxInterval
848    *
849    * @throws HttpInvalidAuthenticationException
850    */
851   public final void checkTime(final long maxInterval)
852       throws HttpInvalidAuthenticationException {
853     final DateTime dateTime = new DateTime();
854     final String date = getXAuthTimestamp();
855     if (ParametersChecker.isNotEmpty(date)) {
856       final DateTime received = DateTime.parse(date);
857       if (maxInterval > 0) {
858         final Duration duration = new Duration(received, dateTime);
859         if (Math.abs(duration.getMillis()) >= maxInterval) {
860           throw new HttpInvalidAuthenticationException(
861               "timestamp is not compatible with the maximum delay allowed");
862         }
863       }
864     } else if (maxInterval > 0) {
865       throw new HttpInvalidAuthenticationException(
866           "timestamp absent while required");
867     }
868   }
869 
870   /**
871    * This implementation of authentication is as follow: if X_AUTH is included
872    * in the URI or Header<br>
873    * 0) Check that timestamp is correct (|curtime - timestamp| < maxinterval)
874    * from ARG_X_AUTH_TIMESTAMP, if
875    * maxInterval is 0, not mandatory<br>
876    * 1) Get all URI args (except ARG_X_AUTH_KEY itself, but including
877    * timestamp), lowered case, in alphabetic
878    * order<br>
879    * 2) Add an extra Key if not null (from ARG_X_AUTH_INTERNALKEY)<br>
880    * 3) Compute an hash (SHA-1 or SHA-256)<br>
881    * 4) Compare this hash with ARG_X_AUTH_KEY<br>
882    *
883    * @param hmacSha256 SHA-256 key to create the signature
884    * @param extraKey will be added as ARG_X_AUTH_INTERNALKEY might be
885    *     null
886    * @param maxInterval ARG_X_AUTH_TIMESTAMP will be tested if value >
887    *     0
888    *
889    * @throws HttpInvalidAuthenticationException if the authentication
890    *     failed
891    */
892   public final void checkBaseAuthent(final HmacSha256 hmacSha256,
893                                      final String extraKey,
894                                      final long maxInterval)
895       throws HttpInvalidAuthenticationException {
896     final TreeMap<String, String> treeMap = new TreeMap<String, String>();
897     final String argPath = getUri();
898     final ObjectNode arguri = getUriArgs();
899     if (arguri == null) {
900       throw new HttpInvalidAuthenticationException("Not enough argument");
901     }
902     final Iterator<Entry<String, JsonNode>> iterator = arguri.fields();
903     final DateTime dateTime = new DateTime();
904     DateTime received = null;
905     while (iterator.hasNext()) {
906       final Entry<String, JsonNode> entry = iterator.next();
907       final String key = entry.getKey();
908       if (key.equalsIgnoreCase(REST_ROOT_FIELD.ARG_X_AUTH_KEY.field)) {
909         continue;
910       }
911       final JsonNode values = entry.getValue();
912       if (key.equalsIgnoreCase(REST_ROOT_FIELD.ARG_X_AUTH_TIMESTAMP.field)) {
913         received = DateTime.parse(values.asText());
914       }
915       final String keylower = key.toLowerCase();
916       if (values != null) {
917         final String val;
918         if (values.isArray()) {
919           final JsonNode jsonNode = values.get(values.size() - 1);
920           val = jsonNode.asText();
921         } else {
922           val = values.asText();
923         }
924         treeMap.put(keylower, val);
925       }
926     }
927     if (received == null) {
928       final String date = getXAuthTimestamp();
929       received = DateTime.parse(date);
930       treeMap.put(REST_ROOT_FIELD.ARG_X_AUTH_TIMESTAMP.field.toLowerCase(),
931                   date);
932     }
933     final String user = getXAuthUser();
934     if (ParametersChecker.isNotEmpty(user)) {
935       treeMap.put(REST_ROOT_FIELD.ARG_X_AUTH_USER.field.toLowerCase(), user);
936     }
937     if (maxInterval > 0 && received != null) {
938       final Duration duration = new Duration(received, dateTime);
939       if (Math.abs(duration.getMillis()) >= maxInterval) {
940         throw new HttpInvalidAuthenticationException(
941             "timestamp is not compatible with the maximum delay allowed");
942       }
943     } else if (maxInterval > 0) {
944       throw new HttpInvalidAuthenticationException(
945           "timestamp absent while required");
946     }
947     final String key = computeKey(hmacSha256, extraKey, treeMap, argPath);
948     if (!key.equalsIgnoreCase(getXAuthKey())) {
949       throw new HttpInvalidAuthenticationException(
950           "Invalid Authentication Key");
951     }
952 
953   }
954 
955   /**
956    * @param hmacSha256 SHA-256 key to create the signature
957    * @param extraKey might be null
958    * @param treeMap
959    * @param argPath
960    *
961    * @throws HttpInvalidAuthenticationException
962    */
963   protected static String computeKey(final HmacSha256 hmacSha256,
964                                      final String extraKey,
965                                      final TreeMap<String, String> treeMap,
966                                      final String argPath)
967       throws HttpInvalidAuthenticationException {
968     final Set<String> keys = treeMap.keySet();
969     final StringBuilder builder = new StringBuilder(argPath);
970     if (!keys.isEmpty() || extraKey != null) {
971       builder.append('?');
972     }
973     boolean first = true;
974     for (final String keylower : keys) {
975       if (first) {
976         first = false;
977       } else {
978         builder.append('&');
979       }
980       builder.append(keylower).append('=').append(treeMap.get(keylower));
981     }
982     if (extraKey != null) {
983       if (!keys.isEmpty()) {
984         builder.append('&');
985       }
986       builder.append(REST_ROOT_FIELD.ARG_X_AUTH_INTERNALKEY.field).append('=')
987              .append(extraKey);
988     }
989     try {
990       return hmacSha256.cryptToHex(builder.toString());
991     } catch (final Exception e) {
992       throw new HttpInvalidAuthenticationException(e);
993     }
994   }
995 
996   @Override
997   public String toString() {
998     return JsonHandler.writeAsString(arguments);
999   }
1000 
1001   public final String prettyPrint() {
1002     return JsonHandler.prettyPrint(arguments);
1003   }
1004 
1005   public static ObjectNode fillDetailedAllow(final METHOD method,
1006                                              final String path,
1007                                              final String command,
1008                                              final ObjectNode body,
1009                                              final JsonNode result) {
1010     final ObjectNode node = JsonHandler.createObjectNode();
1011     final ObjectNode node2 = node.putObject(method.name());
1012     node2.put(REST_FIELD.JSON_PATH.field, '/' + path);
1013     node2.put(REST_ROOT_FIELD.JSON_COMMAND.field, command);
1014     if (body != null) {
1015       node2.set(REST_FIELD.JSON_JSON.field, body);
1016     }
1017     if (result != null) {
1018       node2.set(REST_GROUP.ARGS_ANSWER.group, result);
1019     }
1020     return node;
1021   }
1022 }