View Javadoc
1   package com.jsql.model.injection.method;
2   
3   import com.jsql.model.InjectionModel;
4   import com.jsql.model.exception.JSqlException;
5   import com.jsql.model.exception.StoppedByUserSlidingException;
6   import com.jsql.util.I18nUtil;
7   import com.jsql.util.JsonUtil;
8   import com.jsql.util.LogLevelUtil;
9   import org.apache.commons.lang3.StringUtils;
10  import org.apache.logging.log4j.LogManager;
11  import org.apache.logging.log4j.Logger;
12  import org.json.JSONException;
13  
14  import java.io.Serializable;
15  import java.util.AbstractMap.SimpleEntry;
16  import java.util.List;
17  import java.util.regex.Pattern;
18  
19  public abstract class AbstractMethodInjection implements Serializable {
20      
21      private static final Logger LOGGER = LogManager.getRootLogger();
22      
23      protected final InjectionModel injectionModel;
24      
25      protected AbstractMethodInjection(InjectionModel injectionModel) {
26          this.injectionModel = injectionModel;
27      }
28      
29      public abstract boolean isCheckingAllParam();
30      public abstract String getParamsAsString();
31      public abstract List<SimpleEntry<String, String>> getParams();
32      public abstract String name();
33      
34      public boolean testParameters(boolean hasFoundInjection) throws JSqlException {
35          if (!hasFoundInjection) {
36              LOGGER.log(
37                  LogLevelUtil.CONSOLE_DEFAULT,
38                  "{} {} params...",
39                  () -> I18nUtil.valueByKey("LOG_CHECKING"),
40                  () -> this.name().toLowerCase()
41              );
42              return this.testParameters();
43          }
44          return true;
45      }
46  
47      /**
48       * Verify if injection works for specific Method using 3 modes: standard (last param), injection point
49       * and full params injection. Special injections like JSON and SOAP are checked.
50       * @return true if injection didn't fail
51       * @throws JSqlException when no params integrity, process stopped by user, or injection failure
52       */
53      public boolean testParameters() throws JSqlException {
54          var hasFoundInjection = false;
55          
56          // Injects URL, Request or Header params only if user tests every params
57          // or method is selected by user.
58          if (
59              !this.injectionModel.getMediatorUtils().getPreferencesUtil().isCheckingAllParam()
60              && this.injectionModel.getMediatorUtils().getConnectionUtil().getMethodInjection() != this
61          ) {
62              return false;
63          }
64          
65          // Force injection method of model to current running method
66          this.injectionModel.getMediatorUtils().getConnectionUtil().setMethodInjection(this);
67          
68          // Injection by injection point in params or in path
69          if (
70              this.getParamsAsString().contains(InjectionModel.STAR)
71              || this.injectionModel.getMediatorUtils().getConnectionUtil().getUrlBase().contains(InjectionModel.STAR)
72          ) {
73              hasFoundInjection = this.checkParamWithStar();
74          } else if (!this.isCheckingAllParam()) {
75              hasFoundInjection = this.checkLastParam();
76          } else {
77              hasFoundInjection = this.checkAllParams();
78          }
79          return hasFoundInjection;
80      }
81  
82      private boolean checkParamWithStar() throws JSqlException {
83          SimpleEntry<String, String> parameterToInject = this.getParams().stream()
84              .filter(entry -> entry.getValue().contains("*"))
85              .findFirst()
86              .orElse(null);
87          return this.injectionModel.getMediatorStrategy().testStrategies(parameterToInject);
88      }
89  
90      /**
91       *  Default injection: last param tested only
92       */
93      private boolean checkLastParam() throws JSqlException {
94          // Will check param value by user.
95          // Notice options 'Inject each URL params' and 'inject JSON' must be checked both
96          // for JSON injection of last param
97          SimpleEntry<String, String> parameterToInject = this.getParams().stream()
98              .reduce((a, b) -> b)
99              .orElseThrow(() -> new JSqlException("Missing last parameter"));
100         return this.injectionModel.getMediatorStrategy().testStrategies(parameterToInject);
101     }
102 
103     /**
104      * Injection of every params: isCheckingAllParam() == true.
105      * Params are tested one by one in two loops:
106      * - inner loop erases * from previous param
107      * - outer loop adds * to current param
108      */
109     private boolean checkAllParams() throws StoppedByUserSlidingException {
110         // This param will be marked by * if injection is found,
111         // inner loop will erase mark * otherwise
112         for (SimpleEntry<String, String> paramBase: this.getParams()) {
113             // This param is the current tested one.
114             // For JSON value attributes are traversed one by one to test every value.
115             // For standard value mark * is simply added to the end of its value.
116             for (SimpleEntry<String, String> paramStar: this.getParams()) {
117                 if (paramStar == paramBase) {
118                     try {
119                         if (this.isParamInjectable(paramStar)) {
120                             return true;
121                         }
122                     } catch (JSONException e) {
123                         LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
124                     }
125                 }
126             }
127         }
128         return false;
129     }
130 
131     private boolean isParamInjectable(SimpleEntry<String, String> paramStar) throws StoppedByUserSlidingException {
132         boolean hasFoundInjection;
133         
134         // Will test if current value is a JSON entity
135         Object jsonEntity = JsonUtil.getJson(paramStar.getValue());
136         
137         // Define a tree of JSON attributes with path as the key: root.a => value of a
138         List<SimpleEntry<String, String>> attributesJson = JsonUtil.createEntries(jsonEntity, "root", null);
139         
140         // When option 'Inject JSON' is selected and there's a JSON entity to inject
141         // then loop through each path to add * at the end of value and test each strategy.
142         // Marks * are erased between each test.
143         if (!attributesJson.isEmpty() && this.injectionModel.getMediatorUtils().getPreferencesUtil().isCheckingAllJsonParam()) {
144             hasFoundInjection = this.injectionModel.getMediatorUtils().getJsonUtil().testJsonParam(this, paramStar);
145         } else {
146             hasFoundInjection = this.testJsonlessParam(paramStar);  // Standard non JSON injection
147         }
148         return hasFoundInjection;
149     }
150     
151     public boolean testJsonlessParam(SimpleEntry<String, String> paramStar) throws StoppedByUserSlidingException {
152         var hasFoundInjection = false;
153 
154         paramStar.setValue(paramStar.getValue() + InjectionModel.STAR);
155         
156         try {
157             LOGGER.log(
158                 LogLevelUtil.CONSOLE_INFORM,
159                 "{} {} parameter {}={}",
160                 () -> I18nUtil.valueByKey("LOG_CHECKING"),
161                 this::name,
162                 paramStar::getKey,
163                 () -> paramStar.getValue().replace(InjectionModel.STAR, StringUtils.EMPTY)
164             );
165             
166             // Test current standard value marked with * for injection
167             // Keep original param
168             hasFoundInjection = this.injectionModel.getMediatorStrategy().testStrategies(paramStar);
169             
170         } catch (StoppedByUserSlidingException e) { // Break all params processing in upper methods
171             throw e;
172         } catch (JSqlException e) {  // Injection failure
173             LOGGER.log(
174                 LogLevelUtil.CONSOLE_ERROR,
175                 "No {} injection found for parameter {}={} ({})",
176                 this.name(),
177                 paramStar.getKey(),
178                 paramStar.getValue().replaceAll("\\+.?$|\\" + InjectionModel.STAR, StringUtils.EMPTY),
179                 e.getMessage()
180             );
181         } finally {
182             if (!hasFoundInjection) {  // Erase * from JSON if failure
183                 
184                 // Erase * at the end of each params
185                 this.getParams().forEach(e ->
186                     e.setValue(
187                         e.getValue().replaceAll(Pattern.quote(InjectionModel.STAR) +"$", StringUtils.EMPTY)
188                     )
189                 );
190                 
191                 // TODO It erases STAR from value => * can't be used in parameter
192                 paramStar.setValue(paramStar.getValue().replace(InjectionModel.STAR, StringUtils.EMPTY));
193             }
194         }
195         return hasFoundInjection;
196     }
197 }