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