├── src
├── META-INF
│ └── MANIFEST.MF
└── main
│ ├── resources
│ ├── log4j2.xml
│ └── help.txt
│ └── java
│ └── com
│ └── denimgroup
│ └── threadfix
│ └── cli
│ └── endpoints
│ ├── EndpointJob.java
│ ├── Credentials.java
│ ├── EndpointTester.java
│ ├── EndpointValidation.java
│ └── EndpointMain.java
├── .gitignore
├── .project
├── sample-project-list.txt
├── README.md
├── pom.xml
└── LICENSE.md
/src/META-INF/MANIFEST.MF:
--------------------------------------------------------------------------------
1 | Manifest-Version: 1.0
2 | Main-Class: com.denimgroup.threadfix.cli.endpoints.EndpointMain
3 |
4 |
--------------------------------------------------------------------------------
/src/main/resources/log4j2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | threadfix-cli.log*
3 | threadfix-cli.log
4 | threadfix-ide-plugin/intellij/intellij.zip
5 | database
6 | threadfix-main/src/main/resources/threadfix.license
7 |
8 | threadfix-scanagent/threadfix.log
9 | bin
10 | out
11 | *.DS_Store
12 | *dependency-reduced-pom.xml
13 | *.iml
14 |
15 | .classpath
16 | .settings
17 | .idea/
18 | *.DS_Store
19 | threadfix-enterprise
20 | threadfix-scanagent
21 | Build
22 | netrc
23 | scanagent.properties
24 | sonar-project.properties
25 |
26 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | EndpointCLI
4 |
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 | org.eclipse.m2e.core.maven2Builder
15 |
16 |
17 |
18 |
19 |
20 | org.eclipse.m2e.core.maven2Nature
21 | org.eclipse.jdt.core.javanature
22 |
23 |
24 |
--------------------------------------------------------------------------------
/sample-project-list.txt:
--------------------------------------------------------------------------------
1 | # This is a sample project listing file for use with the threadfix-endpoints-cli tool.
2 | # This is used via the -path-list-file="" parameter.
3 |
4 | # 1 folder path per line - HAM will auto-detect the framework types for these
5 | /files/Project 1/src
6 | /files/Project 2/src
7 |
8 | # Define a specific framework to use when parsing endpoints
9 | # by adding "FRAMEWORK_TYPE:" before the folder path.
10 | # The list of currently accepted values are demonstrated below.
11 | # If a new framework is added, its "FrameworkType" name will
12 | # automatically be usable in this file.
13 |
14 | # .NET WEB FORMS
15 | DOT_NET_WEB_FORMS:/files/ASP Web Forms Project/src
16 |
17 | # .NET MVC
18 | DOT_NET_MVC:/files/ASP MVC Project/src
19 |
20 | # STRUTS
21 | STRUTS:/files/Struts Project/src
22 |
23 | # JSP
24 | JSP:/files/JSP Project/src
25 |
26 | # Spring MVC
27 | SPRING_MVC:/files/Spring MVC Project/src
28 |
29 | # Rails
30 | RAILS:/files/Rails Project/src
31 |
32 | # Django
33 | PYTHON:/files/Django Project/src
34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # attack-surface-detector-cli
2 |
3 | ### _[Latest Release - 1.3.5](https://github.com/secdec/attack-surface-detector-cli/releases/tag/v1.3.5)_
4 |
5 | The `attack-surface-detector-cli` program is a command-line tool that takes in a folder location and outputs the set of endpoints detected within that codebase. It uses the [ASTAM Correlator's](https://github.com/secdec/astam-correlator) `threadfix-ham` module to generate these endpoints. The endpoints are output to the console by default, and can save a JSON version of those endpoints through the `-output-file` and `-json` flags. See the [Wiki](https://github.com/secdec/attack-surface-detector-cli/wiki/Usage,-Parameters,-and-Output) for more details.
6 |
7 | This tool supports the following frameworks, as supported by the `threadfix-ham` module:
8 |
9 | - ASP.NET MVC / Web API / Core / Web Forms
10 | - Struts
11 | - Django
12 | - Ruby on Rails
13 | - Spring MVC
14 | - JSP
15 |
16 | ---
17 |
18 | Licensed under the [MPL](https://github.com/secdec/attack-surface-detector-cli/blob/master/LICENSE.md) License.
19 |
20 | ---
21 |
22 | _This material is based on research sponsored by the Department of Homeland Security (DHS) Science and Technology Directorate, Cyber Security Division (DHS S&T/CSD) via contract number HHSP233201600058C._
23 |
--------------------------------------------------------------------------------
/src/main/java/com/denimgroup/threadfix/cli/endpoints/EndpointJob.java:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // Copyright (C) 2018 Applied Visions - http://securedecisions.com
4 | //
5 | // The contents of this file are subject to the Mozilla Public License
6 | // Version 2.0 (the "License"); you may not use this file except in
7 | // compliance with the License. You may obtain a copy of the License at
8 | // http://www.mozilla.org/MPL/
9 | //
10 | // Software distributed under the License is distributed on an "AS IS"
11 | // basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
12 | // License for the specific language governing rights and limitations
13 | // under the License.
14 | //
15 | // This material is based on research sponsored by the Department of Homeland
16 | // Security (DHS) Science and Technology Directorate, Cyber Security Division
17 | // (DHS S&T/CSD) via contract number HHSP233201600058C.
18 | //
19 | // Contributor(s):
20 | // Secure Decisions, a division of Applied Visions, Inc
21 | //
22 | ////////////////////////////////////////////////////////////////////////
23 |
24 | package com.denimgroup.threadfix.cli.endpoints;
25 |
26 | import com.denimgroup.threadfix.data.enums.FrameworkType;
27 |
28 | import java.io.File;
29 | import java.util.Collection;
30 |
31 | public class EndpointJob {
32 |
33 | public Collection frameworkTypes;
34 | public File sourceCodePath;
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/com/denimgroup/threadfix/cli/endpoints/Credentials.java:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // Copyright (C) 2018 Applied Visions - http://securedecisions.com
4 | //
5 | // The contents of this file are subject to the Mozilla Public License
6 | // Version 2.0 (the "License"); you may not use this file except in
7 | // compliance with the License. You may obtain a copy of the License at
8 | // http://www.mozilla.org/MPL/
9 | //
10 | // Software distributed under the License is distributed on an "AS IS"
11 | // basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
12 | // License for the specific language governing rights and limitations
13 | // under the License.
14 | //
15 | // This material is based on research sponsored by the Department of Homeland
16 | // Security (DHS) Science and Technology Directorate, Cyber Security Division
17 | // (DHS S&T/CSD) via contract number HHSP233201600058C.
18 | //
19 | // Contributor(s):
20 | // Secure Decisions, a division of Applied Visions, Inc
21 | //
22 | ////////////////////////////////////////////////////////////////////////
23 |
24 | package com.denimgroup.threadfix.cli.endpoints;
25 |
26 | import java.util.Map;
27 |
28 | import static com.denimgroup.threadfix.CollectionUtils.map;
29 |
30 | public class Credentials {
31 | public String authenticationEndpoint;
32 | public Map parameters = map();
33 |
34 | public Map authenticatedParameters = null;
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/resources/help.txt:
--------------------------------------------------------------------------------
1 | Attack Surface Detector CLI
2 | https://github.com/secdec/attack-surface-detector-cli
3 |
4 | A tool to detect endpoints from source code. Can output these endpoints in multiple JSON formats,
5 | or output to the console (by default) to inspect the quality and coverage of the detected endpoints.
6 |
7 | Usage:
8 | java -jar attack-surface-detector-cli.jar [flags]
9 |
10 | Flags:
11 | -debug -- Print debug information during endpoint detection
12 |
13 | -simple -- Print only endpoint detection summary, without the full list
14 | of detected endpoints
15 |
16 | -path-list-file= -- Detect endpoints from all source code paths listed in the given file
17 |
18 | -defaultFramework= -- Parse the source code using the given framework type
19 | Available values:
20 | DETECT : Attempt to automatically detect the framework
21 | JSP : Java JSP/Servlets
22 | SPRING_MVC : Java Spring MVC
23 | STRUTS : Java Struts
24 | DOT_NET_MVC : ASP.NET MVC/WebAPI
25 | DOT_NET_WEB_FORMS : ASP.NET Web Forms
26 | PYTHON : Django
27 | Rails : Ruby on Rails
28 |
29 | -help -- Displays this message
30 |
31 | [JSON Output]
32 | -json -- Print only simple-format JSON to the console
33 | Simple-format JSON uses a common format for all generated endpoints
34 | regardless of framework. Source code information is not included.
35 |
36 | -keep-source -- Include source code information when combined with the -json flag
37 |
38 | -full-json -- Print full JSON information to the console
39 | Full-format JSON uses unique data formats depending on the framework
40 | that declared the endpoints. Should be used with the
41 | astam-correlator.threadfix-ham module available on Maven.
42 |
43 | -output-file= -- Writes generated JSON to the specified file path.
44 | Must be used with the -json or -full-json flags; otherwise, has no effect.
45 |
46 |
47 |
48 | [Quality Testing]
49 | -validation-server= -- Run HTTP requests against the detected endpoints relative to the
50 | given BASE_URL; ie http://localhost:8080/mywebapp
51 |
52 | -validation-server-auth= -- Use the given form data values to authenticate when validating endpoints against
53 | a test server. Takes the format:
54 |
55 | "AUTH-ENDPOINT;HEADER=VALUE;HEADER=VALUE;..."
56 |
57 | ie:
58 |
59 | "/login;username=user;password=pass"
60 |
61 | The "/login" endpoint is queried with "username=user&password=pass" as form
62 | data. Any "Set-Cookie" headers in the response are used in later requests
63 | during testing. Must be used with the -validation-server flag; otherwise, has
64 | no effect.
65 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 |
6 | com.github.secdec.astam-correlator
7 | attack-surface-detector-cli
8 | attack-surface-detector-cli
9 | 1.3.8
10 |
11 |
12 | This is a standalone tool that uses the ASTAM Correlator HAM module to detect endpoints from source code for
13 | a variety of languages and frameworks. See the readme for more information.
14 |
15 |
16 | jar
17 |
18 |
19 | src/main/java
20 |
21 |
22 |
23 | org.apache.maven.plugins
24 | maven-compiler-plugin
25 | 2.3.2
26 |
27 | 1.8
28 | 1.8
29 |
30 |
31 |
32 |
33 |
34 |
35 | maven-compiler-plugin
36 |
37 |
38 | maven-assembly-plugin
39 |
40 |
41 |
42 | com.denimgroup.threadfix.cli.endpoints.EndpointMain
43 |
44 |
45 |
46 | jar-with-dependencies
47 |
48 |
49 |
50 |
51 |
52 | attached
53 | single
54 |
55 | package
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | dependencycheck
64 |
65 |
66 |
67 | org.owasp
68 | dependency-check-maven
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | commons-io
77 | commons-io
78 | 2.4
79 |
80 |
81 | com.github.secdec.astam-correlator
82 | threadfix-entities
83 | 1.3.8
84 |
85 |
86 | com.github.secdec.astam-correlator
87 | threadfix-ham
88 | 1.3.8
89 |
90 |
91 | org.owasp
92 | dependency-check-maven
93 | 1.3.6
94 | test
95 |
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/src/main/java/com/denimgroup/threadfix/cli/endpoints/EndpointTester.java:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // Copyright (C) 2018 Applied Visions - http://securedecisions.com
4 | //
5 | // The contents of this file are subject to the Mozilla Public License
6 | // Version 2.0 (the "License"); you may not use this file except in
7 | // compliance with the License. You may obtain a copy of the License at
8 | // http://www.mozilla.org/MPL/
9 | //
10 | // Software distributed under the License is distributed on an "AS IS"
11 | // basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
12 | // License for the specific language governing rights and limitations
13 | // under the License.
14 | //
15 | // This material is based on research sponsored by the Department of Homeland
16 | // Security (DHS) Science and Technology Directorate, Cyber Security Division
17 | // (DHS S&T/CSD) via contract number HHSP233201600058C.
18 | //
19 | // Contributor(s):
20 | // Secure Decisions, a division of Applied Visions, Inc
21 | //
22 | ////////////////////////////////////////////////////////////////////////
23 |
24 | package com.denimgroup.threadfix.cli.endpoints;
25 |
26 | import com.denimgroup.threadfix.data.entities.RouteParameter;
27 | import com.denimgroup.threadfix.data.entities.RouteParameterType;
28 | import com.denimgroup.threadfix.data.interfaces.Endpoint;
29 | import com.denimgroup.threadfix.framework.util.PathUtil;
30 |
31 | import java.io.IOException;
32 | import java.io.OutputStream;
33 | import java.io.UnsupportedEncodingException;
34 | import java.net.*;
35 | import java.nio.charset.StandardCharsets;
36 | import java.util.List;
37 | import java.util.Map;
38 | import java.util.StringJoiner;
39 |
40 | import static com.denimgroup.threadfix.CollectionUtils.list;
41 | import static com.denimgroup.threadfix.CollectionUtils.map;
42 |
43 | public class EndpointTester {
44 | String basePath;
45 |
46 | public EndpointTester(String basePath) {
47 | this.basePath = basePath;
48 | }
49 |
50 | public int test(Endpoint endpoint, Credentials credentials) throws IOException {
51 | URL url = new URL(PathUtil.combine(this.basePath, endpoint.getUrlPath()));
52 | HttpURLConnection conn = (HttpURLConnection)url.openConnection();
53 | conn.setRequestMethod(endpoint.getHttpMethod());
54 |
55 | if (credentials != null && credentials.authenticatedParameters != null) {
56 | for (Map.Entry header : credentials.authenticatedParameters.entrySet()) {
57 | conn.setRequestProperty(header.getKey(), header.getValue());
58 | }
59 | }
60 |
61 | conn.getInputStream().close();
62 | return conn.getResponseCode();
63 | }
64 |
65 | public int authorize(Credentials credentials, Endpoint endpoint) throws IOException {
66 | // Get query settings
67 | String httpMethod = endpoint != null ? endpoint.getHttpMethod() : "POST";
68 |
69 | HttpURLConnection.setFollowRedirects(false);
70 |
71 | // Try auth by best-match
72 |
73 | if (endpoint != null) {
74 | try {
75 | String urlParams = configureRequestWithBestMatchParameters(endpoint.getParameters(), credentials, null);
76 | String finalizedPath = credentials.authenticationEndpoint;
77 | if (!urlParams.isEmpty()) {
78 | finalizedPath += "?" + urlParams;
79 | }
80 |
81 | URL url = new URL(PathUtil.combine(this.basePath, finalizedPath));
82 | // Configure connection
83 | HttpURLConnection conn = (HttpURLConnection)url.openConnection();
84 | conn.setRequestMethod(httpMethod);
85 | configureRequestWithBestMatchParameters(endpoint.getParameters(), credentials, conn);
86 |
87 | conn.getInputStream().close();
88 | if (conn.getResponseCode() < 400 && saveCredentialsResponse(conn, credentials)) {
89 | return conn.getResponseCode();
90 | }
91 | } catch (IOException e) {
92 | System.out.println("Unable to authorize using best-match parameters:");
93 | e.printStackTrace();
94 | }
95 | }
96 |
97 | try {
98 | URL url = new URL(PathUtil.combine(this.basePath, credentials.authenticationEndpoint));
99 | HttpURLConnection conn = (HttpURLConnection)url.openConnection();
100 | conn.setRequestMethod(httpMethod);
101 | configureRequestWithFormParameters(credentials, conn);
102 |
103 | conn.getInputStream().close();
104 | if (conn.getResponseCode() < 400 && saveCredentialsResponse(conn, credentials)) {
105 | return conn.getResponseCode();
106 | }
107 | } catch (IOException e) {
108 | System.out.println("Unable to authorize using all-forms parameters:");
109 | e.printStackTrace();
110 | }
111 |
112 | return 0xffff;
113 | }
114 |
115 | private boolean saveCredentialsResponse(HttpURLConnection conn, Credentials creds) {
116 | if (conn.getHeaderField("Set-Cookie") != null) {
117 | List cookies = conn.getHeaderFields().get("Set-Cookie");
118 | List sanitizeCookies = list();
119 |
120 | for (String cookie : cookies) {
121 | String mainPart = cookie;
122 | if (mainPart.contains(";")) {
123 | mainPart = mainPart.substring(0, mainPart.indexOf(';'));
124 | }
125 | sanitizeCookies.add(mainPart);
126 | }
127 |
128 | creds.authenticatedParameters = map();
129 | creds.authenticatedParameters.put("Cookie", String.join("; ", sanitizeCookies));
130 |
131 | return true;
132 | }
133 | return false;
134 | }
135 |
136 | private String configureRequestWithBestMatchParameters(Map parsedParameters, Credentials credentials, HttpURLConnection conn) throws IOException {
137 | StringJoiner queryString = new StringJoiner("&");
138 | StringJoiner formParams = new StringJoiner("&");
139 |
140 | for (Map.Entry credParam : credentials.parameters.entrySet()) {
141 | RouteParameter paramSpec = null;
142 | if (parsedParameters != null) {
143 | if (parsedParameters.containsKey(credParam.getKey())) {
144 | paramSpec = parsedParameters.get(credParam.getKey());
145 | }
146 | }
147 |
148 | if (paramSpec == null) {
149 | paramSpec = RouteParameter.fromDataType(credParam.getKey(), "String");
150 | paramSpec.setParamType(RouteParameterType.FORM_DATA);
151 | }
152 |
153 | String encodedKey, encodedValue;
154 | try {
155 | encodedKey = URLEncoder.encode(credParam.getKey(), "UTF-8");
156 | encodedValue = URLEncoder.encode(credParam.getValue(), "UTF-8");
157 | } catch (UnsupportedEncodingException e) {
158 | e.printStackTrace();
159 | continue;
160 | }
161 |
162 | String encodedPair = encodedKey + "=" + encodedValue;
163 |
164 | switch (paramSpec.getParamType()) {
165 | case QUERY_STRING:
166 | queryString.add(encodedPair);
167 | break;
168 |
169 | default:
170 | // Assume form data for anything else
171 | formParams.add(encodedPair);
172 | }
173 | }
174 |
175 | if (formParams.length() > 0 && conn != null) {
176 |
177 | byte[] out = formParams.toString().getBytes(StandardCharsets.UTF_8);
178 |
179 | conn.setDoOutput(true);
180 | conn.setFixedLengthStreamingMode(out.length);
181 | conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
182 |
183 | conn.connect();
184 | try (OutputStream os = conn.getOutputStream()) {
185 | os.write(out);
186 | }
187 | }
188 |
189 | if (queryString.length() == 0) {
190 | return "";
191 | } else {
192 | return "?" + queryString.toString();
193 | }
194 | }
195 |
196 | private void configureRequestWithFormParameters(Credentials credentials, HttpURLConnection conn) throws IOException {
197 | StringJoiner joiner = new StringJoiner("&");
198 | for (Map.Entry param : credentials.parameters.entrySet()) {
199 | try {
200 | joiner.add(URLEncoder.encode(param.getKey(), "UTF-8") + "=" + URLEncoder.encode(param.getValue(), "UTF-8"));
201 | } catch (UnsupportedEncodingException e) {
202 | e.printStackTrace();
203 | }
204 | }
205 |
206 | byte[] out = joiner.toString().getBytes(StandardCharsets.UTF_8);
207 |
208 | conn.setDoOutput(true);
209 | conn.setFixedLengthStreamingMode(out.length);
210 | conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
211 |
212 | conn.connect();
213 | try (OutputStream os = conn.getOutputStream()) {
214 | os.write(out);
215 | }
216 | }
217 | }
218 |
--------------------------------------------------------------------------------
/src/main/java/com/denimgroup/threadfix/cli/endpoints/EndpointValidation.java:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // Copyright (C) 2018 Applied Visions - http://securedecisions.com
4 | //
5 | // The contents of this file are subject to the Mozilla Public License
6 | // Version 2.0 (the "License"); you may not use this file except in
7 | // compliance with the License. You may obtain a copy of the License at
8 | // http://www.mozilla.org/MPL/
9 | //
10 | // Software distributed under the License is distributed on an "AS IS"
11 | // basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
12 | // License for the specific language governing rights and limitations
13 | // under the License.
14 | //
15 | // This material is based on research sponsored by the Department of Homeland
16 | // Security (DHS) Science and Technology Directorate, Cyber Security Division
17 | // (DHS S&T/CSD) via contract number HHSP233201600058C.
18 | //
19 | // Contributor(s):
20 | // Secure Decisions, a division of Applied Visions, Inc
21 | //
22 | ////////////////////////////////////////////////////////////////////////
23 |
24 | package com.denimgroup.threadfix.cli.endpoints;
25 |
26 | import com.denimgroup.threadfix.data.entities.EndpointStructure;
27 | import com.denimgroup.threadfix.data.entities.RouteParameter;
28 | import com.denimgroup.threadfix.data.enums.EndpointRelevanceStrictness;
29 | import com.denimgroup.threadfix.data.enums.FrameworkType;
30 | import com.denimgroup.threadfix.data.interfaces.Endpoint;
31 | import com.denimgroup.threadfix.framework.engine.full.EndpointSerialization;
32 | import com.denimgroup.threadfix.framework.util.EndpointUtil;
33 | import org.apache.logging.log4j.LogManager;
34 | import org.apache.logging.log4j.Logger;
35 |
36 | import java.io.File;
37 | import java.io.IOException;
38 | import java.util.ArrayList;
39 | import java.util.Collection;
40 | import java.util.List;
41 | import java.util.Map;
42 |
43 | import static com.denimgroup.threadfix.CollectionUtils.list;
44 |
45 | public class EndpointValidation {
46 |
47 | private static Logger logger = LogManager.getLogger(EndpointValidation.class);
48 |
49 | public static boolean validateSerialization(File sourceCodeFolder, List endpoints) {
50 | List allEndpoints = EndpointUtil.flattenWithVariants(endpoints);
51 | List allUrls = list();
52 |
53 | for (Endpoint endpoint : allEndpoints) {
54 | allUrls.add(endpoint.getUrlPath());
55 | }
56 |
57 | try {
58 | String serializedCollection = EndpointSerialization.serializeAll(allEndpoints);
59 | Endpoint[] deserializedCollection = EndpointSerialization.deserializeAll(serializedCollection);
60 | if (deserializedCollection.length != allEndpoints.size()) {
61 | logger.warn("Collection serialization did not match the original input");
62 | return false;
63 | }
64 | } catch (IOException e) {
65 | e.printStackTrace();
66 | logger.warn("Exception thrown during collection serialization");
67 | return false;
68 | }
69 |
70 | for (Endpoint endpoint : allEndpoints) {
71 |
72 | if (endpoint.getFilePath().startsWith(sourceCodeFolder.getAbsolutePath().replace('\\', '/'))) {
73 | logger.warn("Got an absolute file path when a relative path was expected instead, for: " + endpoint.toString());
74 | return false;
75 | }
76 |
77 | if (endpoint.getFilePath().isEmpty()) {
78 | logger.warn("Got an empty file path for: " + endpoint.toString());
79 | }
80 | else if (!endpoint.getFilePath().contains("(lib)")) {
81 | File fullPath = new File(sourceCodeFolder, endpoint.getFilePath());
82 | if (!fullPath.exists()) {
83 | logger.warn("The source code path '" + fullPath.getAbsolutePath() + "' does not exist for: " + endpoint.toString());
84 | return false;
85 | }
86 | }
87 |
88 | for (String url : allUrls) {
89 | try {
90 | endpoint.isRelevant(url, EndpointRelevanceStrictness.STRICT);
91 | endpoint.isRelevant(url, EndpointRelevanceStrictness.LOOSE);
92 | endpoint.compareRelevance(url);
93 | } catch (Exception e) {
94 | logger.warn("Exception occurred while testing relevancy comparisons between [" + url + "] and " + endpoint.toString());
95 | e.printStackTrace();
96 | return false;
97 | }
98 | }
99 |
100 | String serialized;
101 | Endpoint deserialized;
102 | try {
103 | serialized = EndpointSerialization.serialize(endpoint);
104 | } catch (IOException e) {
105 | logger.warn("Exception occurred while serializing: " + endpoint.toString());
106 | e.printStackTrace();
107 | return false;
108 | }
109 |
110 | try {
111 | deserialized = EndpointSerialization.deserialize(serialized);
112 | } catch (IOException e) {
113 | logger.warn("Exception occurred while deserializing: " + endpoint.toString());
114 | e.printStackTrace();
115 | return false;
116 | }
117 |
118 | if (deserialized == null) {
119 | logger.warn("Failed to validate serialization due to NULL DESERIALIZED ENDPOINT on " + endpoint.toString());
120 | return false;
121 | }
122 |
123 | if (!endpoint.getClass().equals(deserialized.getClass())) {
124 | logger.warn("Failed to validate serialization due to MISMATCHED ENDPOINT DATATYPES on " + endpoint.toString());
125 | return false;
126 | }
127 |
128 | if (!deserialized.getUrlPath().equals(endpoint.getUrlPath())) {
129 | logger.warn("Failed to validate serialization due to mismatched URL paths on " + endpoint.toString());
130 | return false;
131 | }
132 |
133 | if (!deserialized.getFilePath().equals(endpoint.getFilePath())) {
134 | logger.warn("Failed to validate serialization due to mismatched FILE paths on " + endpoint.toString());
135 | return false;
136 | }
137 |
138 | if (deserialized.getParameters().size() != endpoint.getParameters().size()) {
139 | logger.warn("Failed to validate serialization due to mismatched PARAMETER COUNTS on " + endpoint.toString());
140 | return false;
141 | }
142 |
143 | if (!deserialized.getHttpMethod().equals(endpoint.getHttpMethod())) {
144 | logger.warn("Failed to validate serialization due to mismatched HTTP METHOD on " + endpoint.toString());
145 | return false;
146 | }
147 |
148 | Map endpointParams = endpoint.getParameters();
149 | Map deserializedParams = deserialized.getParameters();
150 |
151 | if (!endpointParams.keySet().containsAll(deserializedParams.keySet()) ||
152 | !deserializedParams.keySet().containsAll(endpointParams.keySet())) {
153 |
154 | logger.warn("Failed to validate serialization due to mismatched PARAMETER NAMES on " + endpoint.toString());
155 | return false;
156 | }
157 |
158 | for (String param : endpointParams.keySet()) {
159 | RouteParameter endpointParam = endpointParams.get(param);
160 | RouteParameter deserializedParam = deserializedParams.get(param);
161 |
162 | if (endpointParam.getParamType() != deserializedParam.getParamType()) {
163 | logger.warn("Failed to validate serialization due to mismatched PARAM TYPE on " + endpoint.toString());
164 | return false;
165 | }
166 |
167 | if ((endpointParam.getDataTypeSource() == null) != (deserializedParam.getDataTypeSource() == null)) {
168 | logger.warn("Failed to validate serialization due to mismatched PARAM DATA TYPE on " + endpoint.toString());
169 | return false;
170 | }
171 |
172 | if (endpointParam.getDataTypeSource() != null && !endpointParam.getDataTypeSource().equals(deserializedParam.getDataTypeSource())) {
173 | logger.warn("Failed to validate serialization due to mismatched PARAM DATA TYPE on " + endpoint.toString());
174 | return false;
175 | }
176 |
177 | if (!endpointParam.getName().equals(deserializedParam.getName())) {
178 | logger.warn("Failed to validate serialization due to mismatched PARAM NAME on " + endpoint.toString());
179 | return false;
180 | }
181 |
182 | if ((endpointParam.getAcceptedValues() == null) != (deserializedParam.getAcceptedValues() == null)) {
183 | logger.warn("Failed to validate serialization due to mismatched ACCEPTED PARAM VALUES on " + endpoint.toString());
184 | return false;
185 | } else if (endpointParam.getAcceptedValues() != null) {
186 | if (!endpointParam.getAcceptedValues().containsAll(deserializedParam.getAcceptedValues()) ||
187 | !deserializedParam.getAcceptedValues().containsAll(endpointParam.getAcceptedValues())) {
188 | logger.warn("Failed to validate serialization due to mismatched ACCEPTED PARAM VALUES on " + endpoint.toString());
189 | return false;
190 | }
191 | }
192 | }
193 | }
194 |
195 | try {
196 | EndpointStructure testStructure = new EndpointStructure();
197 | testStructure.acceptAllEndpoints(endpoints);
198 | } catch (Exception e) {
199 | System.out.println("Failed to validate endpoint structure generation due to an exception: \n" + e);
200 | }
201 |
202 | return true;
203 | }
204 |
205 | public static boolean validateDuplicates(Collection endpoints) {
206 | boolean validated = true;
207 | List> duplicateEndpoints = detectDuplicates(endpoints);
208 | if (!duplicateEndpoints.isEmpty()) {
209 | logger.warn("Found " + duplicateEndpoints.size() + " duplicated endpoints:");
210 | for (List duplicateSet : duplicateEndpoints) {
211 | logger.warn("- " + duplicateSet.size() + ": " + duplicateSet.get(0).toString());
212 | validated = false;
213 | }
214 | }
215 | return validated;
216 | }
217 |
218 | private static List> detectDuplicates(Collection endpoints) {
219 | List> duplicates = new ArrayList<>();
220 |
221 | for (Endpoint main : endpoints) {
222 | // Check if we already know that this endpoint has duplicates
223 | boolean wasChecked = false;
224 | for (List duplicatesSet : duplicates) {
225 | if (endpointsMatch(main, duplicatesSet.get(0))) {
226 | wasChecked = true;
227 | break;
228 | }
229 | }
230 | if (wasChecked) {
231 | continue;
232 | }
233 |
234 | List currentDuplicates = list();
235 |
236 | for (Endpoint other : endpoints) {
237 | if (endpointsMatch(main, other)) {
238 | currentDuplicates.add(other);
239 | }
240 | }
241 |
242 | if (currentDuplicates.size() > 1) {
243 | duplicates.add(currentDuplicates);
244 | }
245 | }
246 |
247 | return duplicates;
248 | }
249 |
250 | private static boolean endpointsMatch(Endpoint a, Endpoint b) {
251 | return
252 | a.getUrlPath().equals(b.getUrlPath()) &&
253 | a.getHttpMethod().equals(b.getHttpMethod()) &&
254 | a.getFilePath().equals(b.getFilePath()) &&
255 | a.getStartingLineNumber() == b.getStartingLineNumber() &&
256 | a.getEndingLineNumber() == b.getEndingLineNumber() &&
257 | endpointParametersMatch(a, b);
258 | }
259 |
260 | private static boolean endpointParametersMatch(Endpoint a, Endpoint b) {
261 | Map bParams = b.getParameters();
262 | for (Map.Entry param : a.getParameters().entrySet()) {
263 | if (!bParams.containsKey(param.getKey()))
264 | return false;
265 |
266 | RouteParameter aParam = param.getValue();
267 | RouteParameter bParam = bParams.get(param.getKey());
268 |
269 | if (
270 | aParam.getParamType() != bParam.getParamType() ||
271 | aParam.getDataType() != bParam.getDataType() ||
272 | (aParam.getAcceptedValues() == null) != (bParam.getAcceptedValues() == null)
273 | ) {
274 | return false;
275 | }
276 |
277 | if (
278 | aParam.getAcceptedValues() != null && !(
279 | aParam.getAcceptedValues().containsAll(bParam.getAcceptedValues()) ||
280 | bParam.getAcceptedValues().containsAll(aParam.getAcceptedValues())
281 | )
282 | ) {
283 | return false;
284 | }
285 | }
286 |
287 | return true;
288 | }
289 | }
290 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Mozilla Public License Version 2.0
2 | ==================================
3 |
4 | 1. Definitions
5 | --------------
6 |
7 | 1.1. "Contributor"
8 | means each individual or legal entity that creates, contributes to
9 | the creation of, or owns Covered Software.
10 |
11 | 1.2. "Contributor Version"
12 | means the combination of the Contributions of others (if any) used
13 | by a Contributor and that particular Contributor's Contribution.
14 |
15 | 1.3. "Contribution"
16 | means Covered Software of a particular Contributor.
17 |
18 | 1.4. "Covered Software"
19 | means Source Code Form to which the initial Contributor has attached
20 | the notice in Exhibit A, the Executable Form of such Source Code
21 | Form, and Modifications of such Source Code Form, in each case
22 | including portions thereof.
23 |
24 | 1.5. "Incompatible With Secondary Licenses"
25 | means
26 |
27 | (a) that the initial Contributor has attached the notice described
28 | in Exhibit B to the Covered Software; or
29 |
30 | (b) that the Covered Software was made available under the terms of
31 | version 1.1 or earlier of the License, but not also under the
32 | terms of a Secondary License.
33 |
34 | 1.6. "Executable Form"
35 | means any form of the work other than Source Code Form.
36 |
37 | 1.7. "Larger Work"
38 | means a work that combines Covered Software with other material, in
39 | a separate file or files, that is not Covered Software.
40 |
41 | 1.8. "License"
42 | means this document.
43 |
44 | 1.9. "Licensable"
45 | means having the right to grant, to the maximum extent possible,
46 | whether at the time of the initial grant or subsequently, any and
47 | all of the rights conveyed by this License.
48 |
49 | 1.10. "Modifications"
50 | means any of the following:
51 |
52 | (a) any file in Source Code Form that results from an addition to,
53 | deletion from, or modification of the contents of Covered
54 | Software; or
55 |
56 | (b) any new file in Source Code Form that contains any Covered
57 | Software.
58 |
59 | 1.11. "Patent Claims" of a Contributor
60 | means any patent claim(s), including without limitation, method,
61 | process, and apparatus claims, in any patent Licensable by such
62 | Contributor that would be infringed, but for the grant of the
63 | License, by the making, using, selling, offering for sale, having
64 | made, import, or transfer of either its Contributions or its
65 | Contributor Version.
66 |
67 | 1.12. "Secondary License"
68 | means either the GNU General Public License, Version 2.0, the GNU
69 | Lesser General Public License, Version 2.1, the GNU Affero General
70 | Public License, Version 3.0, or any later versions of those
71 | licenses.
72 |
73 | 1.13. "Source Code Form"
74 | means the form of the work preferred for making modifications.
75 |
76 | 1.14. "You" (or "Your")
77 | means an individual or a legal entity exercising rights under this
78 | License. For legal entities, "You" includes any entity that
79 | controls, is controlled by, or is under common control with You. For
80 | purposes of this definition, "control" means (a) the power, direct
81 | or indirect, to cause the direction or management of such entity,
82 | whether by contract or otherwise, or (b) ownership of more than
83 | fifty percent (50%) of the outstanding shares or beneficial
84 | ownership of such entity.
85 |
86 | 2. License Grants and Conditions
87 | --------------------------------
88 |
89 | 2.1. Grants
90 |
91 | Each Contributor hereby grants You a world-wide, royalty-free,
92 | non-exclusive license:
93 |
94 | (a) under intellectual property rights (other than patent or trademark)
95 | Licensable by such Contributor to use, reproduce, make available,
96 | modify, display, perform, distribute, and otherwise exploit its
97 | Contributions, either on an unmodified basis, with Modifications, or
98 | as part of a Larger Work; and
99 |
100 | (b) under Patent Claims of such Contributor to make, use, sell, offer
101 | for sale, have made, import, and otherwise transfer either its
102 | Contributions or its Contributor Version.
103 |
104 | 2.2. Effective Date
105 |
106 | The licenses granted in Section 2.1 with respect to any Contribution
107 | become effective for each Contribution on the date the Contributor first
108 | distributes such Contribution.
109 |
110 | 2.3. Limitations on Grant Scope
111 |
112 | The licenses granted in this Section 2 are the only rights granted under
113 | this License. No additional rights or licenses will be implied from the
114 | distribution or licensing of Covered Software under this License.
115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a
116 | Contributor:
117 |
118 | (a) for any code that a Contributor has removed from Covered Software;
119 | or
120 |
121 | (b) for infringements caused by: (i) Your and any other third party's
122 | modifications of Covered Software, or (ii) the combination of its
123 | Contributions with other software (except as part of its Contributor
124 | Version); or
125 |
126 | (c) under Patent Claims infringed by Covered Software in the absence of
127 | its Contributions.
128 |
129 | This License does not grant any rights in the trademarks, service marks,
130 | or logos of any Contributor (except as may be necessary to comply with
131 | the notice requirements in Section 3.4).
132 |
133 | 2.4. Subsequent Licenses
134 |
135 | No Contributor makes additional grants as a result of Your choice to
136 | distribute the Covered Software under a subsequent version of this
137 | License (see Section 10.2) or under the terms of a Secondary License (if
138 | permitted under the terms of Section 3.3).
139 |
140 | 2.5. Representation
141 |
142 | Each Contributor represents that the Contributor believes its
143 | Contributions are its original creation(s) or it has sufficient rights
144 | to grant the rights to its Contributions conveyed by this License.
145 |
146 | 2.6. Fair Use
147 |
148 | This License is not intended to limit any rights You have under
149 | applicable copyright doctrines of fair use, fair dealing, or other
150 | equivalents.
151 |
152 | 2.7. Conditions
153 |
154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
155 | in Section 2.1.
156 |
157 | 3. Responsibilities
158 | -------------------
159 |
160 | 3.1. Distribution of Source Form
161 |
162 | All distribution of Covered Software in Source Code Form, including any
163 | Modifications that You create or to which You contribute, must be under
164 | the terms of this License. You must inform recipients that the Source
165 | Code Form of the Covered Software is governed by the terms of this
166 | License, and how they can obtain a copy of this License. You may not
167 | attempt to alter or restrict the recipients' rights in the Source Code
168 | Form.
169 |
170 | 3.2. Distribution of Executable Form
171 |
172 | If You distribute Covered Software in Executable Form then:
173 |
174 | (a) such Covered Software must also be made available in Source Code
175 | Form, as described in Section 3.1, and You must inform recipients of
176 | the Executable Form how they can obtain a copy of such Source Code
177 | Form by reasonable means in a timely manner, at a charge no more
178 | than the cost of distribution to the recipient; and
179 |
180 | (b) You may distribute such Executable Form under the terms of this
181 | License, or sublicense it under different terms, provided that the
182 | license for the Executable Form does not attempt to limit or alter
183 | the recipients' rights in the Source Code Form under this License.
184 |
185 | 3.3. Distribution of a Larger Work
186 |
187 | You may create and distribute a Larger Work under terms of Your choice,
188 | provided that You also comply with the requirements of this License for
189 | the Covered Software. If the Larger Work is a combination of Covered
190 | Software with a work governed by one or more Secondary Licenses, and the
191 | Covered Software is not Incompatible With Secondary Licenses, this
192 | License permits You to additionally distribute such Covered Software
193 | under the terms of such Secondary License(s), so that the recipient of
194 | the Larger Work may, at their option, further distribute the Covered
195 | Software under the terms of either this License or such Secondary
196 | License(s).
197 |
198 | 3.4. Notices
199 |
200 | You may not remove or alter the substance of any license notices
201 | (including copyright notices, patent notices, disclaimers of warranty,
202 | or limitations of liability) contained within the Source Code Form of
203 | the Covered Software, except that You may alter any license notices to
204 | the extent required to remedy known factual inaccuracies.
205 |
206 | 3.5. Application of Additional Terms
207 |
208 | You may choose to offer, and to charge a fee for, warranty, support,
209 | indemnity or liability obligations to one or more recipients of Covered
210 | Software. However, You may do so only on Your own behalf, and not on
211 | behalf of any Contributor. You must make it absolutely clear that any
212 | such warranty, support, indemnity, or liability obligation is offered by
213 | You alone, and You hereby agree to indemnify every Contributor for any
214 | liability incurred by such Contributor as a result of warranty, support,
215 | indemnity or liability terms You offer. You may include additional
216 | disclaimers of warranty and limitations of liability specific to any
217 | jurisdiction.
218 |
219 | 4. Inability to Comply Due to Statute or Regulation
220 | ---------------------------------------------------
221 |
222 | If it is impossible for You to comply with any of the terms of this
223 | License with respect to some or all of the Covered Software due to
224 | statute, judicial order, or regulation then You must: (a) comply with
225 | the terms of this License to the maximum extent possible; and (b)
226 | describe the limitations and the code they affect. Such description must
227 | be placed in a text file included with all distributions of the Covered
228 | Software under this License. Except to the extent prohibited by statute
229 | or regulation, such description must be sufficiently detailed for a
230 | recipient of ordinary skill to be able to understand it.
231 |
232 | 5. Termination
233 | --------------
234 |
235 | 5.1. The rights granted under this License will terminate automatically
236 | if You fail to comply with any of its terms. However, if You become
237 | compliant, then the rights granted under this License from a particular
238 | Contributor are reinstated (a) provisionally, unless and until such
239 | Contributor explicitly and finally terminates Your grants, and (b) on an
240 | ongoing basis, if such Contributor fails to notify You of the
241 | non-compliance by some reasonable means prior to 60 days after You have
242 | come back into compliance. Moreover, Your grants from a particular
243 | Contributor are reinstated on an ongoing basis if such Contributor
244 | notifies You of the non-compliance by some reasonable means, this is the
245 | first time You have received notice of non-compliance with this License
246 | from such Contributor, and You become compliant prior to 30 days after
247 | Your receipt of the notice.
248 |
249 | 5.2. If You initiate litigation against any entity by asserting a patent
250 | infringement claim (excluding declaratory judgment actions,
251 | counter-claims, and cross-claims) alleging that a Contributor Version
252 | directly or indirectly infringes any patent, then the rights granted to
253 | You by any and all Contributors for the Covered Software under Section
254 | 2.1 of this License shall terminate.
255 |
256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all
257 | end user license agreements (excluding distributors and resellers) which
258 | have been validly granted by You or Your distributors under this License
259 | prior to termination shall survive termination.
260 |
261 | ************************************************************************
262 | * *
263 | * 6. Disclaimer of Warranty *
264 | * ------------------------- *
265 | * *
266 | * Covered Software is provided under this License on an "as is" *
267 | * basis, without warranty of any kind, either expressed, implied, or *
268 | * statutory, including, without limitation, warranties that the *
269 | * Covered Software is free of defects, merchantable, fit for a *
270 | * particular purpose or non-infringing. The entire risk as to the *
271 | * quality and performance of the Covered Software is with You. *
272 | * Should any Covered Software prove defective in any respect, You *
273 | * (not any Contributor) assume the cost of any necessary servicing, *
274 | * repair, or correction. This disclaimer of warranty constitutes an *
275 | * essential part of this License. No use of any Covered Software is *
276 | * authorized under this License except under this disclaimer. *
277 | * *
278 | ************************************************************************
279 |
280 | ************************************************************************
281 | * *
282 | * 7. Limitation of Liability *
283 | * -------------------------- *
284 | * *
285 | * Under no circumstances and under no legal theory, whether tort *
286 | * (including negligence), contract, or otherwise, shall any *
287 | * Contributor, or anyone who distributes Covered Software as *
288 | * permitted above, be liable to You for any direct, indirect, *
289 | * special, incidental, or consequential damages of any character *
290 | * including, without limitation, damages for lost profits, loss of *
291 | * goodwill, work stoppage, computer failure or malfunction, or any *
292 | * and all other commercial damages or losses, even if such party *
293 | * shall have been informed of the possibility of such damages. This *
294 | * limitation of liability shall not apply to liability for death or *
295 | * personal injury resulting from such party's negligence to the *
296 | * extent applicable law prohibits such limitation. Some *
297 | * jurisdictions do not allow the exclusion or limitation of *
298 | * incidental or consequential damages, so this exclusion and *
299 | * limitation may not apply to You. *
300 | * *
301 | ************************************************************************
302 |
303 | 8. Litigation
304 | -------------
305 |
306 | Any litigation relating to this License may be brought only in the
307 | courts of a jurisdiction where the defendant maintains its principal
308 | place of business and such litigation shall be governed by laws of that
309 | jurisdiction, without reference to its conflict-of-law provisions.
310 | Nothing in this Section shall prevent a party's ability to bring
311 | cross-claims or counter-claims.
312 |
313 | 9. Miscellaneous
314 | ----------------
315 |
316 | This License represents the complete agreement concerning the subject
317 | matter hereof. If any provision of this License is held to be
318 | unenforceable, such provision shall be reformed only to the extent
319 | necessary to make it enforceable. Any law or regulation which provides
320 | that the language of a contract shall be construed against the drafter
321 | shall not be used to construe this License against a Contributor.
322 |
323 | 10. Versions of the License
324 | ---------------------------
325 |
326 | 10.1. New Versions
327 |
328 | Mozilla Foundation is the license steward. Except as provided in Section
329 | 10.3, no one other than the license steward has the right to modify or
330 | publish new versions of this License. Each version will be given a
331 | distinguishing version number.
332 |
333 | 10.2. Effect of New Versions
334 |
335 | You may distribute the Covered Software under the terms of the version
336 | of the License under which You originally received the Covered Software,
337 | or under the terms of any subsequent version published by the license
338 | steward.
339 |
340 | 10.3. Modified Versions
341 |
342 | If you create software not governed by this License, and you want to
343 | create a new license for such software, you may create and use a
344 | modified version of this License if you rename the license and remove
345 | any references to the name of the license steward (except to note that
346 | such modified license differs from this License).
347 |
348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary
349 | Licenses
350 |
351 | If You choose to distribute Source Code Form that is Incompatible With
352 | Secondary Licenses under the terms of this version of the License, the
353 | notice described in Exhibit B of this License must be attached.
354 |
355 | Exhibit A - Source Code Form License Notice
356 | -------------------------------------------
357 |
358 | This Source Code Form is subject to the terms of the Mozilla Public
359 | License, v. 2.0. If a copy of the MPL was not distributed with this
360 | file, You can obtain one at http://mozilla.org/MPL/2.0/.
361 |
362 | If it is not possible or desirable to put the notice in a particular
363 | file, then You may include the notice in a location (such as a LICENSE
364 | file in a relevant directory) where a recipient would be likely to look
365 | for such a notice.
366 |
367 | You may add additional accurate notices of copyright ownership.
368 |
369 | Exhibit B - "Incompatible With Secondary Licenses" Notice
370 | ---------------------------------------------------------
371 |
372 | This Source Code Form is "Incompatible With Secondary Licenses", as
373 | defined by the Mozilla Public License, v. 2.0.
374 |
--------------------------------------------------------------------------------
/src/main/java/com/denimgroup/threadfix/cli/endpoints/EndpointMain.java:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // Copyright (c) 2009-2015 Denim Group, Ltd.
4 | //
5 | // The contents of this file are subject to the Mozilla Public License
6 | // Version 2.0 (the "License"); you may not use this file except in
7 | // compliance with the License. You may obtain a copy of the License at
8 | // http://www.mozilla.org/MPL/
9 | //
10 | // Software distributed under the License is distributed on an "AS IS"
11 | // basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
12 | // License for the specific language governing rights and limitations
13 | // under the License.
14 | //
15 | // The Original Code is ThreadFix.
16 | //
17 | // The Initial Developer of the Original Code is Denim Group, Ltd.
18 | // Portions created by Denim Group, Ltd. are Copyright (C)
19 | // Denim Group, Ltd. All Rights Reserved.
20 | //
21 | // Contributor(s):
22 | // Denim Group, Ltd.
23 | // Secure Decisions, a division of Applied Visions, Inc
24 | //
25 | ////////////////////////////////////////////////////////////////////////
26 |
27 | package com.denimgroup.threadfix.cli.endpoints;
28 |
29 | import com.denimgroup.threadfix.data.entities.RouteParameter;
30 | import com.denimgroup.threadfix.data.entities.RouteParameterType;
31 | import com.denimgroup.threadfix.data.entities.WildcardEndpointPathNode;
32 | import com.denimgroup.threadfix.data.enums.FrameworkType;
33 | import com.denimgroup.threadfix.data.interfaces.Endpoint;
34 | import com.denimgroup.threadfix.data.interfaces.EndpointPathNode;
35 | import com.denimgroup.threadfix.framework.engine.framework.FrameworkCalculator;
36 | import com.denimgroup.threadfix.framework.engine.full.EndpointDatabase;
37 | import com.denimgroup.threadfix.framework.engine.full.EndpointDatabaseFactory;
38 | import com.denimgroup.threadfix.framework.engine.full.EndpointSerialization;
39 | import com.denimgroup.threadfix.framework.engine.full.TemporaryExtractionLocation;
40 | import com.denimgroup.threadfix.framework.util.EndpointUtil;
41 | import com.fasterxml.jackson.databind.ObjectMapper;
42 | import org.apache.commons.io.FileUtils;
43 | import org.apache.commons.io.FilenameUtils;
44 | import org.apache.commons.io.IOUtils;
45 | import org.apache.commons.lang3.StringUtils;
46 | import org.apache.logging.log4j.Level;
47 | import org.apache.logging.log4j.LogManager;
48 | import org.apache.logging.log4j.core.LoggerContext;
49 | import org.apache.logging.log4j.core.config.Configuration;
50 | import org.apache.logging.log4j.core.config.LoggerConfig;
51 |
52 | import java.io.*;
53 | import java.net.UnknownHostException;
54 | import java.util.Collection;
55 | import java.util.List;
56 | import java.util.Map;
57 |
58 | import static com.denimgroup.threadfix.CollectionUtils.list;
59 | import static com.denimgroup.threadfix.CollectionUtils.map;
60 | import static com.denimgroup.threadfix.data.interfaces.Endpoint.PrintFormat.FULL_JSON;
61 | import static com.denimgroup.threadfix.data.interfaces.Endpoint.PrintFormat.SIMPLE_JSON;
62 |
63 | public class EndpointMain {
64 | private static final String FRAMEWORK_COMMAND = "-defaultFramework=";
65 | enum Logging {
66 | ON, OFF
67 | }
68 |
69 | static String PRINTLN_SEPARATOR = StringUtils.repeat('-', 10);
70 |
71 | static Logging logging = Logging.OFF;
72 | static Endpoint.PrintFormat printFormat = Endpoint.PrintFormat.DYNAMIC;
73 | static FrameworkType defaultFramework = FrameworkType.DETECT;
74 | static boolean simplePrint = false;
75 | static String pathListFile = null;
76 | static String outputFilePath = null;
77 | static boolean cleanSourceCode = true;
78 |
79 | static int totalDetectedEndpoints = 0;
80 | static int totalDistinctEndpoints = 0;
81 | static int totalDetectedParameters = 0;
82 | static int totalDistinctParameters = 0;
83 |
84 | static int numProjectsWithDuplicates = 0;
85 |
86 | static String testUrlPath = null;
87 | static Credentials testCredentials = null;
88 |
89 | private static void println(String line) {
90 | if (printFormat != SIMPLE_JSON && printFormat != FULL_JSON) {
91 | System.out.println(line);
92 | }
93 | }
94 |
95 | public static void main(String[] args) {
96 | if (checkArguments(args)) {
97 | resetLoggingConfiguration();
98 | List projectsMissingEndpoints = list();
99 | int numProjectsWithEndpoints = 0;
100 | int numProjects = 0;
101 |
102 | List allEndpoints = list();
103 |
104 | if (outputFilePath != null && !(printFormat == SIMPLE_JSON || printFormat == FULL_JSON)) {
105 | System.out.println("An output file path was specified but neither -json nor -simple-json flags were set, output file path will be ignored");
106 | }
107 |
108 | if (pathListFile != null) {
109 | println("Loading path list file at '" + pathListFile + "'");
110 | List fileContents;
111 | boolean isLongComment = false;
112 |
113 | try {
114 | fileContents = FileUtils.readLines(new File(pathListFile));
115 | List requestedTargets = list();
116 | int lineNo = 1;
117 | for (String line : fileContents) {
118 | line = line.trim();
119 | if (line.startsWith("#!")) {
120 | isLongComment = true;
121 | } else if (line.startsWith("!#")) {
122 | isLongComment = false;
123 | } else if (!line.startsWith("#") && !line.isEmpty() && !isLongComment) {
124 |
125 | List compositeFrameworkTypes = list();
126 | FrameworkType frameworkType = FrameworkType.DETECT;
127 | File asFile;
128 | if (line.contains(":") && !(new File(line)).exists()) {
129 | String[] parts = StringUtils.split(line, ":", 2);
130 | frameworkType = FrameworkType.getFrameworkType(parts[0].trim());
131 | asFile = new File(parts[1].trim());
132 | } else {
133 | asFile = new File(line);
134 | }
135 |
136 | if (!asFile.exists()) {
137 | println("WARN - Unable to find input path '" + line + "' at line " + lineNo + " of " + pathListFile);
138 | } else if (!asFile.isDirectory() && !isZipFile(asFile.getAbsolutePath())) {
139 | println("WARN - Input path '" + line + "' is not a directory or ZIP, at line " + lineNo + " of " + pathListFile);
140 | } else {
141 | if (frameworkType == FrameworkType.NONE) {
142 | println("WARN: Couldn't parse framework type: '" + frameworkType + "', for '" + asFile.getName() + "' using DETECT");
143 | }
144 |
145 | if (frameworkType == FrameworkType.DETECT) {
146 | compositeFrameworkTypes = FrameworkCalculator.getTypes(asFile);
147 | }
148 |
149 | EndpointJob newJob = new EndpointJob();
150 | if (compositeFrameworkTypes.isEmpty()) {
151 | compositeFrameworkTypes.add(frameworkType);
152 | }
153 | newJob.frameworkTypes = compositeFrameworkTypes;
154 | newJob.sourceCodePath = asFile;
155 | requestedTargets.add(newJob);
156 | }
157 | }
158 | ++lineNo;
159 | }
160 |
161 | numProjects = requestedTargets.size();
162 |
163 | boolean isFirst = true;
164 | for (EndpointJob job : requestedTargets) {
165 | if (isFirst) {
166 | println(PRINTLN_SEPARATOR);
167 | isFirst = false;
168 | }
169 | println("Beginning endpoint detection for '" + job.sourceCodePath.getAbsolutePath() + "' with " + job.frameworkTypes.size() + " framework types");
170 | for (FrameworkType subType : job.frameworkTypes) {
171 | println("Using framework=" + subType);
172 | }
173 | List generatedEndpoints = listEndpoints(job.sourceCodePath, job.frameworkTypes);
174 | println("Finished endpoint detection for '" + job.sourceCodePath.getAbsolutePath() + "'");
175 | println(PRINTLN_SEPARATOR);
176 |
177 | if (!generatedEndpoints.isEmpty()) {
178 | ++numProjectsWithEndpoints;
179 |
180 | if (printFormat == SIMPLE_JSON || printFormat == FULL_JSON) {
181 | allEndpoints.addAll(generatedEndpoints);
182 | }
183 | } else {
184 | projectsMissingEndpoints.add(job.sourceCodePath.getAbsolutePath());
185 | }
186 | }
187 |
188 | } catch (IOException e) {
189 | e.printStackTrace();
190 | println("Unable to read path-list at " + pathListFile);
191 | printError();
192 | }
193 | } else {
194 | ++numProjects;
195 |
196 | File rootFolder = new File(args[0]);
197 |
198 | List compositeFrameworkTypes = list();
199 | if (defaultFramework == FrameworkType.DETECT) {
200 | compositeFrameworkTypes.addAll(FrameworkCalculator.getTypes(rootFolder));
201 | } else {
202 | compositeFrameworkTypes.add(defaultFramework);
203 | }
204 |
205 | println("Beginning endpoint detection for '" + rootFolder.getAbsolutePath() + "' with " + compositeFrameworkTypes.size() + " framework types");
206 | for (FrameworkType subType : compositeFrameworkTypes) {
207 | println("Using framework=" + subType);
208 | }
209 |
210 | Collection newEndpoints = listEndpoints(rootFolder, compositeFrameworkTypes);
211 |
212 | println("Finished endpoint detection for '" + rootFolder.getAbsolutePath() + "'");
213 | println(PRINTLN_SEPARATOR);
214 |
215 | if (!newEndpoints.isEmpty()) {
216 | ++numProjectsWithEndpoints;
217 | if (printFormat == SIMPLE_JSON || printFormat == FULL_JSON) {
218 | allEndpoints.addAll(newEndpoints);
219 | }
220 | } else {
221 | projectsMissingEndpoints.add(rootFolder.getAbsolutePath());
222 | }
223 | }
224 |
225 | if (!simplePrint) {
226 | if (printFormat == SIMPLE_JSON) {
227 | Endpoint.Info[] infos = getEndpointInfo(allEndpoints);
228 |
229 | try {
230 | String s = new ObjectMapper().writeValueAsString(infos);
231 | System.out.println(s);
232 |
233 | if (outputFilePath != null) {
234 | FileUtils.writeStringToFile(new File(outputFilePath), s);
235 | }
236 | } catch (IOException e) {
237 | throw new RuntimeException(e);
238 | }
239 | } else if (printFormat == FULL_JSON) {
240 | try {
241 | String s = EndpointSerialization.serializeAll(allEndpoints);
242 | System.out.println(s);
243 |
244 | if (outputFilePath != null) {
245 | FileUtils.writeStringToFile(new File(outputFilePath), s);
246 | }
247 | } catch (IOException e) {
248 | throw new RuntimeException(e);
249 | }
250 | }
251 | }
252 |
253 |
254 |
255 | println("-- DONE --");
256 |
257 | println(numProjectsWithDuplicates + " projects had duplicate endpoints");
258 |
259 | println("Generated " + totalDistinctEndpoints + " distinct endpoints");
260 | println("Generated " + totalDetectedEndpoints + " total endpoints");
261 | println("Generated " + totalDistinctParameters + " distinct parameters");
262 | println("Generated " + totalDetectedParameters + " total parameters");
263 | println(numProjectsWithEndpoints + "/" + numProjects + " projects had endpoints generated");
264 | if (!projectsMissingEndpoints.isEmpty()) {
265 | println("The following projects were missing endpoints:");
266 | for (String path : projectsMissingEndpoints) {
267 | println("--- " + path);
268 | }
269 | }
270 |
271 | if (printFormat != SIMPLE_JSON && printFormat != FULL_JSON) {
272 | println("To enable logging include the -debug argument");
273 | }
274 | } else {
275 | printError();
276 | }
277 | }
278 |
279 | private static boolean isZipFile(String filePath) {
280 | String ext = FilenameUtils.getExtension(filePath).toLowerCase();
281 | return
282 | ext.equals("zip") ||
283 | ext.equals("war");
284 | }
285 |
286 | private static boolean checkArguments(String[] args) {
287 | if (args.length == 0) {
288 | return false;
289 | }
290 |
291 | for (String arg : args) {
292 | if (arg.equals("-help")) {
293 | printHelp();
294 | System.exit(0);
295 | }
296 | }
297 |
298 | File rootFile = new File(args[0]);
299 |
300 | if (rootFile.exists() && rootFile.isDirectory() || args[0].startsWith("-path-list-file")) {
301 |
302 | List arguments = list(args);
303 |
304 | if (rootFile.exists()) {
305 | arguments.remove(0);
306 | }
307 |
308 | for (String arg : arguments) {
309 | if (arg.equals("-debug")) {
310 | logging = Logging.ON;
311 | } else if (arg.equals("-lint")) {
312 | printFormat = Endpoint.PrintFormat.LINT;
313 | } else if (arg.equals("-json")) {
314 | printFormat = SIMPLE_JSON;
315 | } else if (arg.equals("-full-json")) {
316 | printFormat = FULL_JSON;
317 | } else if (arg.contains(FRAMEWORK_COMMAND)) {
318 | String frameworkName = arg.substring(arg.indexOf(
319 | FRAMEWORK_COMMAND) + FRAMEWORK_COMMAND.length(), arg.length());
320 | defaultFramework = FrameworkType.getFrameworkType(frameworkName);
321 | } else if (arg.contains("-keep-source")) {
322 | cleanSourceCode = false;
323 | } else if (arg.equals("-simple")) {
324 | simplePrint = true;
325 | } else if (arg.startsWith("-output-file=")) {
326 | String[] parts = arg.split("=");
327 | String path = parts[1];
328 | File outputFile = new File(path).getAbsoluteFile();
329 | File parentDirectory = outputFile.getParentFile();
330 | if (parentDirectory.isDirectory()) {
331 | parentDirectory.mkdirs();
332 | }
333 | outputFilePath = outputFile.getAbsolutePath();
334 | println("Writing output to file at: \"" + outputFilePath + "\"");
335 | } else if (arg.startsWith("-path-list-file=")) {
336 | String[] parts = arg.split("=");
337 | String path = parts[1];
338 | if (path == null || path.isEmpty()) {
339 | println("Invalid -path-list-file argument, value is empty");
340 | continue;
341 | }
342 | if (path.startsWith("\"") || path.startsWith("'")) {
343 | path = path.substring(1);
344 | }
345 | if (path.endsWith("\"") || path.endsWith("'")) {
346 | path = path.substring(0, path.length() - 1);
347 | }
348 | pathListFile = path;
349 | } else if (arg.startsWith("-validation-server=")) {
350 | String[] parts = arg.split("=");
351 | testUrlPath = parts[1];
352 | } else if (arg.startsWith("-validation-server-auth=")) {
353 | arg = arg.substring("-validation-server-auth=".length());
354 | String[] parts = arg.split(";");
355 | testCredentials = new Credentials();
356 | testCredentials.parameters = map();
357 |
358 | for (String part : parts) {
359 | if (testCredentials.authenticationEndpoint == null) {
360 | testCredentials.authenticationEndpoint = part;
361 | } else {
362 | String[] paramParts = part.split("=");
363 | if (paramParts.length != 2) {
364 | println("Invalid authentication parameter format: " + part);
365 | } else {
366 | testCredentials.parameters.put(paramParts[0], paramParts[1]);
367 | }
368 | }
369 | }
370 | } else {
371 | println("Received unsupported option " + arg + ", run with -help to see available flags.");
372 | return false;
373 | }
374 | }
375 |
376 | return true;
377 |
378 | } else {
379 | println("Please enter a valid file path as the first parameter.");
380 | }
381 |
382 | return false;
383 | }
384 |
385 | static void printError() {
386 | println("The first argument should be a valid file path to scan. Run with -help to see available flags and usage.");
387 | }
388 |
389 | static void printHelp() {
390 | InputStream helpFileStream = EndpointMain.class.getResourceAsStream("/help.txt");
391 | try {
392 | String helpInfo = IOUtils.toString(helpFileStream);
393 | println(helpInfo);
394 | } catch (IOException e) {
395 | e.printStackTrace();
396 | }
397 | }
398 |
399 | private static int printEndpointWithVariants(int i, int currentDepth, Endpoint endpoint) {
400 |
401 | int numPrinted = 1;
402 |
403 | StringBuilder line = new StringBuilder();
404 |
405 | line.append('[');
406 | line.append(i);
407 | line.append("] ");
408 |
409 | for (int s = 0; s < currentDepth * 2; s++) {
410 | line.append('-');
411 | }
412 | if (currentDepth > 0) {
413 | line.append(' ');
414 | }
415 |
416 | line.append(endpoint.getHttpMethod());
417 | line.append(": ");
418 | line.append(endpoint.getUrlPath());
419 |
420 | line.append(" (");
421 | line.append(endpoint.getVariants().size());
422 | line.append(" variants): PARAMETERS=");
423 | line.append(endpoint.getParameters());
424 |
425 | line.append("; FILE=");
426 | line.append(endpoint.getFilePath());
427 |
428 | line.append(" (lines '");
429 | line.append(endpoint.getStartingLineNumber());
430 | line.append("'-'");
431 | line.append(endpoint.getEndingLineNumber());
432 | line.append("')");
433 |
434 | println(line.toString());
435 |
436 | for (Endpoint variant : endpoint.getVariants()) {
437 | numPrinted += printEndpointWithVariants(i + numPrinted, currentDepth + 1, variant);
438 | }
439 |
440 | return numPrinted;
441 | }
442 |
443 | private static List listEndpoints(File rootFile, Collection frameworkTypes) {
444 | List endpoints = list();
445 |
446 | File sourceRootFile = rootFile;
447 | TemporaryExtractionLocation zipExtractor = null;
448 | if (TemporaryExtractionLocation.isArchive(rootFile.getAbsolutePath())) {
449 | zipExtractor = new TemporaryExtractionLocation(rootFile.getAbsolutePath());
450 | zipExtractor.extract();
451 |
452 | sourceRootFile = zipExtractor.getOutputPath();
453 | }
454 |
455 | if (frameworkTypes.size() == 1 && frameworkTypes.iterator().next() == FrameworkType.DETECT) {
456 | frameworkTypes.addAll(FrameworkCalculator.getTypes(rootFile));
457 | }
458 |
459 | List databases = list();
460 | for (FrameworkType frameworkType : frameworkTypes) {
461 | EndpointDatabase database = EndpointDatabaseFactory.getDatabase(sourceRootFile, frameworkType);
462 | if (database != null) {
463 | databases.add(database);
464 | } else {
465 | println("EndpointDatabaseFactory.getDatabase returned null for framework type " + frameworkType);
466 | }
467 | }
468 |
469 | for (EndpointDatabase db : databases) {
470 | endpoints.addAll(db.generateEndpoints());
471 | }
472 |
473 | // Don't do any validation if we're just writing JSON without any output
474 | if (printFormat == FULL_JSON || printFormat == SIMPLE_JSON) {
475 | return endpoints;
476 | }
477 |
478 | List allEndpoints = EndpointUtil.flattenWithVariants(endpoints);
479 |
480 | int numPrimaryEndpoints = endpoints.size();
481 | int numEndpoints = allEndpoints.size();
482 |
483 | totalDetectedEndpoints += numEndpoints;
484 | totalDistinctEndpoints += numPrimaryEndpoints;
485 |
486 | if (!simplePrint) {
487 | int i = 0;
488 | for (Endpoint endpoint : endpoints) {
489 | printEndpointWithVariants(i++, 0, endpoint);
490 | }
491 | }
492 |
493 | if (endpoints.isEmpty()) {
494 | println("No endpoints were found.");
495 |
496 | } else {
497 | println("Generated " + numPrimaryEndpoints +
498 | " distinct endpoints with " +
499 | (numEndpoints - numPrimaryEndpoints) +
500 | " variants for a total of " + numEndpoints +
501 | " endpoints");
502 | }
503 |
504 | if (EndpointValidation.validateSerialization(sourceRootFile, endpoints)) {
505 | println("Successfully validated serialization for these endpoints");
506 | } else {
507 | println("Failed to validate serialization for at least one of these endpoints");
508 | }
509 |
510 | if (!EndpointValidation.validateDuplicates(endpoints)) {
511 | numProjectsWithDuplicates++;
512 | }
513 |
514 | // Run endpoint testing against a given server
515 | if (testUrlPath != null) {
516 | EndpointTester tester = new EndpointTester(testUrlPath);
517 |
518 | println("Testing endpoints against server at: " + testUrlPath);
519 |
520 | if (testCredentials != null) {
521 | try {
522 | if (tester.authorize(testCredentials, null) < 400) {
523 | println("Successfully authenticated");
524 | }
525 | } catch (IOException e) {
526 | println("Warning - unable to authorize against server");
527 | }
528 | }
529 |
530 | List successfulEndpoints = list();
531 | List failedEndpoints = list();
532 | for (Endpoint endpoint : allEndpoints) {
533 | boolean skip = false;
534 | for (EndpointPathNode node : endpoint.getUrlPathNodes()) {
535 | if (node.getClass().equals(WildcardEndpointPathNode.class)) {
536 | skip = true;
537 | break;
538 | }
539 | }
540 |
541 | if (skip) {
542 | continue;
543 | }
544 |
545 | try {
546 | int responseCode = tester.test(endpoint, testCredentials);
547 | if (responseCode != 404) {
548 | successfulEndpoints.add(endpoint);
549 | } else {
550 | failedEndpoints.add(endpoint);
551 | }
552 | } catch (IOException e) {
553 | // Any non-404 error is considered "successful", since any other 4xx or 5xx may indicate
554 | // that the endpoint exists but incorrect parameters were provided
555 | if (e.getMessage().contains("Server returned HTTP response code") && !e.getMessage().contains("code: 404")) {
556 | successfulEndpoints.add(endpoint);
557 | } else {
558 | failedEndpoints.add(endpoint);
559 | }
560 | }
561 | }
562 |
563 | for (Endpoint endpoint : failedEndpoints) {
564 | println("Failed: " + endpoint.getUrlPath() + "[" + endpoint.getHttpMethod() + "]");
565 | }
566 |
567 | println(successfulEndpoints.size() + "/" + (successfulEndpoints.size() + failedEndpoints.size()) + " endpoints were queryable");
568 | println("(" + (allEndpoints.size() - successfulEndpoints.size() - failedEndpoints.size()) + " endpoints skipped since they had a wildcard in the URL)");
569 | }
570 |
571 | int numMissingStartLine = 0;
572 | int numMissingEndLine = 0;
573 | int numSameLineRange = 0;
574 | for (Endpoint endpoint : EndpointUtil.flattenWithVariants(endpoints)) {
575 | if (endpoint.getStartingLineNumber() < 0) {
576 | numMissingStartLine++;
577 | }
578 | if (endpoint.getEndingLineNumber() < 0) {
579 | numMissingEndLine++;
580 | }
581 | if (endpoint.getStartingLineNumber() >= 0 && endpoint.getStartingLineNumber() == endpoint.getEndingLineNumber()) {
582 | numSameLineRange++;
583 | }
584 | }
585 |
586 | println(numMissingStartLine + " endpoints were missing code start line");
587 | println(numMissingEndLine + " endpoints were missing code end line");
588 | println(numSameLineRange + " endpoints had the same code start and end line");
589 |
590 | List distinctParameters = list();
591 | for (Endpoint endpoint : endpoints) {
592 | distinctParameters.addAll(endpoint.getParameters().values());
593 | }
594 |
595 | int numTotalParameters = 0;
596 | for (Endpoint endpoint : allEndpoints) {
597 | numTotalParameters += endpoint.getParameters().size();
598 | }
599 |
600 | totalDistinctParameters += distinctParameters.size();
601 | totalDetectedParameters += numTotalParameters;
602 |
603 | println("Generated " + distinctParameters.size() + " distinct parameters");
604 | println("Generated " + numTotalParameters + " total parameters");
605 |
606 | Map typeOccurrences = map();
607 | int numHaveDataType = 0;
608 | int numHaveParamType = 0;
609 | int numHaveAcceptedValues = 0;
610 | for (RouteParameter param : distinctParameters) {
611 | if (param.getDataType() != null) {
612 | ++numHaveDataType;
613 | }
614 | if (param.getParamType() != RouteParameterType.UNKNOWN) {
615 | ++numHaveParamType;
616 | }
617 | if (param.getAcceptedValues() != null && param.getAcceptedValues().size() > 0) {
618 | ++numHaveAcceptedValues;
619 | }
620 |
621 | if (!typeOccurrences.containsKey(param.getParamType())) {
622 | typeOccurrences.put(param.getParamType(), 1);
623 | } else {
624 | int o = typeOccurrences.get(param.getParamType());
625 | typeOccurrences.put(param.getParamType(), o + 1);
626 | }
627 | }
628 |
629 | int numParams = distinctParameters.size();
630 | println("- " + numHaveDataType + "/" + numParams + " have their data type");
631 | println("- " + numHaveAcceptedValues + "/" + numParams + " have a list of accepted values");
632 | println("- " + numHaveParamType + "/" + numParams + " have their parameter type");
633 | for (RouteParameterType paramType : typeOccurrences.keySet()) {
634 | println("--- " + paramType.name() + ": " + typeOccurrences.get(paramType));
635 | }
636 |
637 | if (zipExtractor != null) {
638 | zipExtractor.release();
639 | }
640 |
641 | return endpoints;
642 | }
643 |
644 | private static Endpoint.Info[] getEndpointInfo(List endpoints) {
645 | List allEndpoints = EndpointUtil.flattenWithVariants(endpoints);
646 | Endpoint.Info[] endpointsInfos = new Endpoint.Info[allEndpoints.size()];
647 |
648 | for (int i = 0; i < allEndpoints.size(); i++) {
649 | endpointsInfos[i] = Endpoint.Info.fromEndpoint(allEndpoints.get(i), !cleanSourceCode);
650 | }
651 |
652 | return endpointsInfos;
653 | }
654 |
655 | private static void resetLoggingConfiguration() {
656 | LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
657 | Configuration config = ctx.getConfiguration();
658 | LoggerConfig loggerConfig = config.getLoggerConfig(LogManager.ROOT_LOGGER_NAME);
659 |
660 | if (logging == Logging.ON) {
661 | loggerConfig.setLevel(Level.DEBUG);
662 | } else {
663 | loggerConfig.setLevel(Level.ERROR);
664 | }
665 |
666 | ctx.updateLoggers();
667 | }
668 | }
--------------------------------------------------------------------------------