1 /* 2 * Zed Attack Proxy (ZAP) and its related class files. 3 * 4 * ZAP is an HTTP/HTTPS proxy for assessing web application security. 5 * 6 * Copyright 2017 The ZAP Development Team 7 * 8 * Licensed under the Apache License, Version 2.0 (the "License"); 9 * you may not use this file except in compliance with the License. 10 * You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, software 15 * distributed under the License is distributed on an "AS IS" BASIS, 16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 * See the License for the specific language governing permissions and 18 * limitations under the License. 19 */ 20 package org.zaproxy.zap.extension.api; 21 22 import static org.hamcrest.CoreMatchers.notNullValue; 23 import static org.hamcrest.CoreMatchers.nullValue; 24 import static org.hamcrest.MatcherAssert.assertThat; 25 import static org.hamcrest.Matchers.containsString; 26 import static org.hamcrest.Matchers.endsWith; 27 import static org.hamcrest.Matchers.equalTo; 28 import static org.hamcrest.Matchers.is; 29 import static org.hamcrest.Matchers.startsWith; 30 import static org.junit.jupiter.api.Assertions.assertThrows; 31 import static org.mockito.Mockito.when; 32 import static org.mockito.Mockito.withSettings; 33 34 import java.net.Inet4Address; 35 import java.util.ArrayList; 36 import java.util.List; 37 import java.util.concurrent.TimeUnit; 38 import net.sf.json.JSON; 39 import net.sf.json.JSONObject; 40 import org.apache.commons.httpclient.URI; 41 import org.junit.jupiter.api.Test; 42 import org.junit.jupiter.api.extension.ExtendWith; 43 import org.mockito.Mockito; 44 import org.mockito.junit.jupiter.MockitoExtension; 45 import org.parosproxy.paros.core.proxy.ProxyParam; 46 import org.parosproxy.paros.network.HttpHeader; 47 import org.parosproxy.paros.network.HttpInputStream; 48 import org.parosproxy.paros.network.HttpMessage; 49 import org.parosproxy.paros.network.HttpOutputStream; 50 import org.parosproxy.paros.network.HttpRequestHeader; 51 import org.parosproxy.paros.view.View; 52 import org.w3c.dom.Document; 53 import org.w3c.dom.Element; 54 import org.zaproxy.zap.network.DomainMatcher; 55 import org.zaproxy.zap.utils.ZapXmlConfiguration; 56 57 /** Unit test for {@link API}. */ 58 @ExtendWith(MockitoExtension.class) 59 class APIUnitTest { 60 61 private static final String CUSTOM_API_PATH = "/custom/api/"; 62 63 @Test shouldBeEnabledWhenInDaemonMode()64 void shouldBeEnabledWhenInDaemonMode() { 65 // Given 66 API api = new API(); 67 // When 68 View.setDaemon(true); 69 // Then 70 assertThat(api.isEnabled(), is(equalTo(true))); 71 } 72 73 @Test shouldAcceptCallbackIfNoAddressesSet()74 void shouldAcceptCallbackIfNoAddressesSet() throws Exception { 75 // Given 76 API api = new API(); 77 api.setOptionsParamApi(createOptionsParamApi()); 78 TestApiImplementor apiImplementor = new TestApiImplementor(); 79 String requestUri = api.getCallBackUrl(apiImplementor, "http://example.com"); 80 HttpMessage requestHandled = 81 api.handleApiRequest( 82 createApiRequest(new byte[] {127, 0, 0, 1}, "example.com", requestUri), 83 createMockedHttpInputStream(), 84 createMockedHttpOutputStream()); 85 // Then 86 assertThat(requestHandled, is(notNullValue())); 87 assertThat(apiImplementor.wasUsed(), is(equalTo(true))); 88 } 89 90 @Test shouldAcceptCallbackEvenIfAddressNotSet()91 void shouldAcceptCallbackEvenIfAddressNotSet() throws Exception { 92 // Given 93 API api = new API(); 94 OptionsParamApi apiOptions = createOptionsParamApi(); 95 apiOptions.setPermittedAddresses(createPermittedAddresses("127.0.0.1")); 96 api.setOptionsParamApi(apiOptions); 97 TestApiImplementor apiImplementor = new TestApiImplementor(); 98 String requestUri = api.getCallBackUrl(apiImplementor, "http://example.com"); 99 HttpMessage requestHandled = 100 api.handleApiRequest( 101 createApiRequest(new byte[] {10, 0, 0, 2}, "example.com", requestUri), 102 createMockedHttpInputStream(), 103 createMockedHttpOutputStream()); 104 // Then 105 assertThat(requestHandled, is(notNullValue())); 106 assertThat(apiImplementor.wasUsed(), is(equalTo(true))); 107 } 108 109 @Test shouldAcceptCallbackEvenIfHostnameNotSet()110 void shouldAcceptCallbackEvenIfHostnameNotSet() throws Exception { 111 // Given 112 API api = new API(); 113 OptionsParamApi apiOptions = createOptionsParamApi(); 114 apiOptions.setPermittedAddresses(createPermittedAddresses("127.0.0.1", "localhost")); 115 api.setOptionsParamApi(apiOptions); 116 TestApiImplementor apiImplementor = new TestApiImplementor(); 117 String requestUri = api.getCallBackUrl(apiImplementor, "http://example.com"); 118 HttpMessage requestHandled = 119 api.handleApiRequest( 120 createApiRequest(new byte[] {127, 0, 0, 1}, "example.com", requestUri), 121 createMockedHttpInputStream(), 122 createMockedHttpOutputStream()); 123 // Then 124 assertThat(requestHandled, is(notNullValue())); 125 assertThat(apiImplementor.wasUsed(), is(equalTo(true))); 126 } 127 128 @Test shouldAcceptAddressAndHostnameSet()129 void shouldAcceptAddressAndHostnameSet() throws Exception { 130 // Given 131 API api = new API(); 132 OptionsParamApi apiOptions = createOptionsParamApi(); 133 apiOptions.setPermittedAddresses(createPermittedAddresses("10.0.0.8", "example.com")); 134 api.setOptionsParamApi(apiOptions); 135 TestApiImplementor apiImplementor = new TestApiImplementor(); 136 String requestUri = api.getCallBackUrl(apiImplementor, "http://example.com"); 137 HttpMessage requestHandled = 138 api.handleApiRequest( 139 createApiRequest(new byte[] {10, 0, 0, 8}, "example.com", requestUri), 140 createMockedHttpInputStream(), 141 createMockedHttpOutputStream()); 142 // Then 143 assertThat(requestHandled, is(notNullValue())); 144 assertThat(apiImplementor.wasUsed(), is(equalTo(true))); 145 } 146 147 @Test shouldCreateBaseUrlWithHttpSchemeAndZapAddressIfProxyingAndNotSecure()148 void shouldCreateBaseUrlWithHttpSchemeAndZapAddressIfProxyingAndNotSecure() { 149 // Given 150 boolean proxying = true; 151 boolean secure = false; 152 API api = new API(); 153 OptionsParamApi apiOptions = createOptionsParamApi(); 154 apiOptions.setSecureOnly(secure); 155 api.setOptionsParamApi(apiOptions); 156 // When 157 String baseUrl = api.getBaseURL(proxying); 158 // Then 159 assertThat(baseUrl, is(equalTo("http://zap/"))); 160 } 161 162 @Test shouldCreateBaseUrlWithHttpsSchemeAndZapAddressIfProxyingAndSecure()163 void shouldCreateBaseUrlWithHttpsSchemeAndZapAddressIfProxyingAndSecure() { 164 // Given 165 boolean proxying = true; 166 boolean secure = true; 167 API api = new API(); 168 OptionsParamApi apiOptions = createOptionsParamApi(); 169 apiOptions.setSecureOnly(secure); 170 api.setOptionsParamApi(apiOptions); 171 // When 172 String baseUrl = api.getBaseURL(proxying); 173 // Then 174 assertThat(baseUrl, is(equalTo("https://zap/"))); 175 } 176 177 @Test shouldCreateBaseUrlWithHttpsSchemeAndProxyAddressIfNotProxyingAndNotSecure()178 void shouldCreateBaseUrlWithHttpsSchemeAndProxyAddressIfNotProxyingAndNotSecure() { 179 // Given 180 boolean proxying = false; 181 boolean secure = false; 182 API api = new API(); 183 OptionsParamApi apiOptions = createOptionsParamApi(); 184 apiOptions.setSecureOnly(secure); 185 api.setOptionsParamApi(apiOptions); 186 api.setProxyParam(createProxyParam("127.0.0.1", 8080)); 187 // When 188 String baseUrl = api.getBaseURL(proxying); 189 // Then 190 assertThat(baseUrl, is(equalTo("http://127.0.0.1:8080/"))); 191 } 192 193 @Test shouldCreateBaseUrlWithHttpsSchemeAndProxyAddressIfNotProxyingAndSecure()194 void shouldCreateBaseUrlWithHttpsSchemeAndProxyAddressIfNotProxyingAndSecure() { 195 // Given 196 boolean proxying = false; 197 boolean secure = true; 198 API api = new API(); 199 OptionsParamApi apiOptions = createOptionsParamApi(); 200 apiOptions.setSecureOnly(secure); 201 api.setOptionsParamApi(apiOptions); 202 api.setProxyParam(createProxyParam("127.0.0.1", 8080)); 203 // When 204 String baseUrl = api.getBaseURL(proxying); 205 // Then 206 assertThat(baseUrl, is(equalTo("https://127.0.0.1:8080/"))); 207 } 208 209 @Test 210 void shouldCreateBaseUrlWithApiRequestWithHttpSchemeZapAddressAndApiNonceIfProxyingNotSecureAndNotView()211 shouldCreateBaseUrlWithApiRequestWithHttpSchemeZapAddressAndApiNonceIfProxyingNotSecureAndNotView() { 212 // Given 213 boolean proxying = true; 214 boolean secure = false; 215 API api = new API(); 216 OptionsParamApi apiOptions = createOptionsParamApi(); 217 apiOptions.setSecureOnly(secure); 218 api.setOptionsParamApi(apiOptions); 219 // When 220 String baseUrl = 221 api.getBaseURL(API.Format.JSON, "test", API.RequestType.action, "test", proxying); 222 // Then 223 assertThat(baseUrl, startsWith("http://zap/JSON/test/action/test/?apinonce=")); 224 assertThat(baseUrl, endsWith("&")); 225 assertApiNonceMatch(api, baseUrl); 226 } 227 228 @Test 229 void shouldCreateBaseUrlWithApiRequestWithHttpsSchemeZapAddressAndApiNonceIfProxyingIsSecureAndNotView()230 shouldCreateBaseUrlWithApiRequestWithHttpsSchemeZapAddressAndApiNonceIfProxyingIsSecureAndNotView() { 231 // Given 232 boolean proxying = true; 233 boolean secure = true; 234 API api = new API(); 235 OptionsParamApi apiOptions = createOptionsParamApi(); 236 apiOptions.setSecureOnly(secure); 237 api.setOptionsParamApi(apiOptions); 238 // When 239 String baseUrl = 240 api.getBaseURL(API.Format.JSON, "test", API.RequestType.action, "test", proxying); 241 // Then 242 assertThat(baseUrl, startsWith("https://zap/JSON/test/action/test/?apinonce=")); 243 assertThat(baseUrl, endsWith("&")); 244 assertApiNonceMatch(api, baseUrl); 245 } 246 247 @Test 248 void shouldCreateBaseUrlWithApiRequestWithHttpSchemeProxyAddressAndApiNonceIfNotProxyingNotSecureAndNotView()249 shouldCreateBaseUrlWithApiRequestWithHttpSchemeProxyAddressAndApiNonceIfNotProxyingNotSecureAndNotView() { 250 // Given 251 boolean proxying = false; 252 boolean secure = false; 253 API api = new API(); 254 OptionsParamApi apiOptions = createOptionsParamApi(); 255 apiOptions.setSecureOnly(secure); 256 api.setOptionsParamApi(apiOptions); 257 api.setProxyParam(createProxyParam("127.0.0.1", 8080)); 258 // When 259 String baseUrl = 260 api.getBaseURL(API.Format.JSON, "test", API.RequestType.action, "test", proxying); 261 // Then 262 assertThat(baseUrl, startsWith("http://127.0.0.1:8080/JSON/test/action/test/?apinonce=")); 263 assertThat(baseUrl, endsWith("&")); 264 assertApiNonceMatch(api, baseUrl); 265 } 266 267 @Test 268 void shouldCreateBaseUrlWithApiRequestWithHttpsSchemeProxyAddressAndApiNonceIfNotProxyingIsSecureAndNotView()269 shouldCreateBaseUrlWithApiRequestWithHttpsSchemeProxyAddressAndApiNonceIfNotProxyingIsSecureAndNotView() { 270 // Given 271 boolean proxying = false; 272 boolean secure = true; 273 API api = new API(); 274 OptionsParamApi apiOptions = createOptionsParamApi(); 275 apiOptions.setSecureOnly(secure); 276 api.setOptionsParamApi(apiOptions); 277 api.setProxyParam(createProxyParam("127.0.0.1", 8080)); 278 // When 279 String baseUrl = 280 api.getBaseURL(API.Format.JSON, "test", API.RequestType.action, "test", proxying); 281 // Then 282 assertThat(baseUrl, startsWith("https://127.0.0.1:8080/JSON/test/action/test/?apinonce=")); 283 assertThat(baseUrl, endsWith("&")); 284 assertApiNonceMatch(api, baseUrl); 285 } 286 287 @Test 288 void shouldCreateBaseUrlWithApiRequestWithHttpSchemeZapAddressAndNoApiNonceIfProxyingNotSecureAndView()289 shouldCreateBaseUrlWithApiRequestWithHttpSchemeZapAddressAndNoApiNonceIfProxyingNotSecureAndView() { 290 // Given 291 boolean proxying = true; 292 boolean secure = false; 293 API api = new API(); 294 OptionsParamApi apiOptions = createOptionsParamApi(); 295 apiOptions.setSecureOnly(secure); 296 api.setOptionsParamApi(apiOptions); 297 // When 298 String baseUrl = 299 api.getBaseURL(API.Format.JSON, "test", API.RequestType.view, "test", proxying); 300 // Then 301 assertThat(baseUrl, is(equalTo("http://zap/JSON/test/view/test/"))); 302 } 303 304 @Test 305 void shouldCreateBaseUrlWithApiRequestWithHttpsSchemeZapAddressAndNoApiNonceIfProxyingIsSecureAndView()306 shouldCreateBaseUrlWithApiRequestWithHttpsSchemeZapAddressAndNoApiNonceIfProxyingIsSecureAndView() { 307 // Given 308 boolean proxying = true; 309 boolean secure = true; 310 API api = new API(); 311 OptionsParamApi apiOptions = createOptionsParamApi(); 312 apiOptions.setSecureOnly(secure); 313 api.setOptionsParamApi(apiOptions); 314 // When 315 String baseUrl = 316 api.getBaseURL(API.Format.JSON, "test", API.RequestType.view, "test", proxying); 317 // Then 318 assertThat(baseUrl, is(equalTo("https://zap/JSON/test/view/test/"))); 319 } 320 321 @Test 322 void shouldCreateBaseUrlWithApiRequestWithHttpSchemeProxyAddressAndNoApiNonceIfNotProxyingNotSecureAndView()323 shouldCreateBaseUrlWithApiRequestWithHttpSchemeProxyAddressAndNoApiNonceIfNotProxyingNotSecureAndView() { 324 // Given 325 boolean proxying = false; 326 boolean secure = false; 327 API api = new API(); 328 OptionsParamApi apiOptions = createOptionsParamApi(); 329 apiOptions.setSecureOnly(secure); 330 api.setOptionsParamApi(apiOptions); 331 api.setProxyParam(createProxyParam("127.0.0.1", 8080)); 332 // When 333 String baseUrl = 334 api.getBaseURL(API.Format.JSON, "test", API.RequestType.view, "test", proxying); 335 // Then 336 assertThat(baseUrl, is(equalTo("http://127.0.0.1:8080/JSON/test/view/test/"))); 337 } 338 339 @Test 340 void shouldCreateBaseUrlWithApiRequestWithHttpsSchemeProxyAddressAndNoApiNonceIfNotProxyingIsSecureAndView()341 shouldCreateBaseUrlWithApiRequestWithHttpsSchemeProxyAddressAndNoApiNonceIfNotProxyingIsSecureAndView() { 342 // Given 343 boolean proxying = false; 344 boolean secure = true; 345 API api = new API(); 346 OptionsParamApi apiOptions = createOptionsParamApi(); 347 apiOptions.setSecureOnly(secure); 348 api.setOptionsParamApi(apiOptions); 349 api.setProxyParam(createProxyParam("127.0.0.1", 8080)); 350 // When 351 String baseUrl = 352 api.getBaseURL(API.Format.JSON, "test", API.RequestType.view, "test", proxying); 353 // Then 354 assertThat(baseUrl, is(equalTo("https://127.0.0.1:8080/JSON/test/view/test/"))); 355 } 356 357 @Test shouldGenerateOneTimeApiNonce()358 void shouldGenerateOneTimeApiNonce() throws Exception { 359 // Given 360 API api = new API(); 361 api.setOptionsParamApi(createOptionsParamApi()); 362 // When 363 String nonce = api.getOneTimeNonce(CUSTOM_API_PATH); 364 // Then 365 assertThat(nonce, is(notNullValue())); 366 assertThat(isApiNonceValid(api, CUSTOM_API_PATH, nonce), is(equalTo(true))); 367 // Should not be valid more than once 368 assertThat(isApiNonceValid(api, CUSTOM_API_PATH, nonce), is(equalTo(false))); 369 } 370 371 @Test shouldConsumeButNotAcceptOneTimeApiNonceIfPathMismatch()372 void shouldConsumeButNotAcceptOneTimeApiNonceIfPathMismatch() throws Exception { 373 // Given 374 API api = new API(); 375 api.setOptionsParamApi(createOptionsParamApi()); 376 // When 377 String nonce = api.getOneTimeNonce(CUSTOM_API_PATH); 378 // Then 379 assertThat(nonce, is(notNullValue())); 380 assertThat(isApiNonceValid(api, "/not/same/path/", nonce), is(equalTo(false))); 381 assertThat(isApiNonceValid(api, CUSTOM_API_PATH, nonce), is(equalTo(false))); 382 } 383 384 @Test shouldNotAcceptOneTimeApiNonceIfExpired()385 void shouldNotAcceptOneTimeApiNonceIfExpired() throws Exception { 386 // Given 387 int ttlInSecs = 1; 388 OptionsParamApi optionsParamApi = Mockito.mock(OptionsParamApi.class); 389 when(optionsParamApi.getNonceTimeToLiveInSecs()).thenReturn(ttlInSecs); 390 API api = new API(); 391 api.setOptionsParamApi(optionsParamApi); 392 String nonce = api.getOneTimeNonce(CUSTOM_API_PATH); 393 // When 394 Thread.sleep(TimeUnit.SECONDS.toMillis(ttlInSecs + 1)); 395 // Then 396 assertThat(nonce, is(notNullValue())); 397 assertThat(isApiNonceValid(api, CUSTOM_API_PATH, nonce), is(equalTo(false))); 398 } 399 400 @Test shouldExpireExistingOneTimeApiNonces()401 void shouldExpireExistingOneTimeApiNonces() throws Exception { 402 // Given 403 int ttlInSecs = 1; 404 OptionsParamApi optionsParamApi = Mockito.mock(OptionsParamApi.class); 405 when(optionsParamApi.getNonceTimeToLiveInSecs()).thenReturn(ttlInSecs); 406 API api = new API(); 407 api.setOptionsParamApi(optionsParamApi); 408 String nonce1 = api.getOneTimeNonce(CUSTOM_API_PATH + "1"); 409 String nonce2 = api.getOneTimeNonce(CUSTOM_API_PATH + "2"); 410 String nonce3 = api.getOneTimeNonce(CUSTOM_API_PATH + "2"); 411 // When 412 Thread.sleep(TimeUnit.SECONDS.toMillis(ttlInSecs + 1)); 413 // Then 414 assertThat(isApiNonceValid(api, CUSTOM_API_PATH + "1", nonce1), is(equalTo(false))); 415 assertThat(isApiNonceValid(api, CUSTOM_API_PATH + "2", nonce2), is(equalTo(false))); 416 assertThat(isApiNonceValid(api, CUSTOM_API_PATH + "3", nonce3), is(equalTo(false))); 417 } 418 419 @Test shouldGenerateLongLivedApiNonce()420 void shouldGenerateLongLivedApiNonce() { 421 // Given 422 API api = new API(); 423 api.setOptionsParamApi(createOptionsParamApi()); 424 // When 425 String nonce = api.getLongLivedNonce(CUSTOM_API_PATH); 426 // Then 427 assertThat(nonce, is(notNullValue())); 428 assertThat(isApiNonceValid(api, CUSTOM_API_PATH, nonce), is(equalTo(true))); 429 // Should be valid more than once 430 assertThat(isApiNonceValid(api, CUSTOM_API_PATH, nonce), is(equalTo(true))); 431 } 432 433 @Test shouldNotExpireLongLivedApiNonce()434 void shouldNotExpireLongLivedApiNonce() throws Exception { 435 // Given 436 int ttlInSecs = 1; 437 OptionsParamApi optionsParamApi = Mockito.mock(OptionsParamApi.class); 438 when(optionsParamApi.getNonceTimeToLiveInSecs()).thenReturn(ttlInSecs); 439 API api = new API(); 440 api.setOptionsParamApi(optionsParamApi); 441 String nonce = api.getLongLivedNonce(CUSTOM_API_PATH); 442 // When 443 Thread.sleep(TimeUnit.SECONDS.toMillis(ttlInSecs + 1)); 444 // Then 445 assertThat(isApiNonceValid(api, CUSTOM_API_PATH, nonce), is(equalTo(true))); 446 } 447 448 @Test shouldNotAcceptLongLivedApiNonceIfPathMismatch()449 void shouldNotAcceptLongLivedApiNonceIfPathMismatch() throws Exception { 450 // Given 451 API api = new API(); 452 api.setOptionsParamApi(createOptionsParamApi()); 453 // When 454 String nonce = api.getLongLivedNonce(CUSTOM_API_PATH); 455 // Then 456 assertThat(nonce, is(notNullValue())); 457 assertThat(isApiNonceValid(api, "/not/same/path/", nonce), is(equalTo(false))); 458 assertThat(isApiNonceValid(api, CUSTOM_API_PATH, nonce), is(equalTo(true))); 459 } 460 461 @Test shouldNotHandleShortcutIfNotForcedRequest()462 void shouldNotHandleShortcutIfNotForcedRequest() throws Exception { 463 // Given 464 API api = createApiWithoutKey(); 465 TestApiImplementor apiImplementor = new TestApiImplementor(); 466 String shortcut = "shortcut"; 467 apiImplementor.addApiShortcut(shortcut); 468 api.registerApiImplementor(apiImplementor); 469 String requestUri = "/" + shortcut; 470 boolean forced = false; 471 // When 472 HttpMessage requestHandled = 473 api.handleApiRequest( 474 createApiRequest(new byte[] {127, 0, 0, 1}, "localhost", requestUri), 475 createMockedHttpInputStream(), 476 createMockedHttpOutputStream(), 477 forced); 478 // Then 479 assertThat(requestHandled, is(nullValue())); 480 assertThat(apiImplementor.wasUsed(), is(equalTo(false))); 481 } 482 483 @Test shouldHandleShortcutIfForcedRequest()484 void shouldHandleShortcutIfForcedRequest() throws Exception { 485 // Given 486 API api = createApiWithoutKey(); 487 TestApiImplementor apiImplementor = new TestApiImplementor(); 488 String shortcut = "shortcut"; 489 apiImplementor.addApiShortcut(shortcut); 490 api.registerApiImplementor(apiImplementor); 491 String requestUri = "/" + shortcut; 492 boolean forced = true; 493 // When 494 HttpMessage requestHandled = 495 api.handleApiRequest( 496 createApiRequest(new byte[] {127, 0, 0, 1}, "localhost", requestUri), 497 createMockedHttpInputStream(), 498 createMockedHttpOutputStream(), 499 forced); 500 // Then 501 assertThat(requestHandled, is(notNullValue())); 502 assertThat(apiImplementor.wasUsed(), is(equalTo(true))); 503 } 504 505 @Test shouldHandleShortcutIfZapRequest()506 void shouldHandleShortcutIfZapRequest() throws Exception { 507 // Given 508 API api = createApiWithoutKey(); 509 TestApiImplementor apiImplementor = new TestApiImplementor(); 510 String shortcut = "shortcut"; 511 apiImplementor.addApiShortcut(shortcut); 512 api.registerApiImplementor(apiImplementor); 513 String requestUri = "/" + shortcut; 514 // When 515 HttpMessage requestHandled = 516 api.handleApiRequest( 517 createApiRequest(new byte[] {127, 0, 0, 1}, API.API_DOMAIN, requestUri), 518 createMockedHttpInputStream(), 519 createMockedHttpOutputStream()); 520 // Then 521 assertThat(requestHandled, is(notNullValue())); 522 assertThat(apiImplementor.wasUsed(), is(equalTo(true))); 523 } 524 525 @Test shouldHandleShortcutIfSecureZapRequest()526 void shouldHandleShortcutIfSecureZapRequest() throws Exception { 527 // Given 528 API api = createApiWithoutKey(); 529 TestApiImplementor apiImplementor = new TestApiImplementor(); 530 String shortcut = "shortcut"; 531 apiImplementor.addApiShortcut(shortcut); 532 api.registerApiImplementor(apiImplementor); 533 String requestUri = "/" + shortcut; 534 HttpRequestHeader apiRequest = 535 createApiRequest(new byte[] {127, 0, 0, 1}, API.API_DOMAIN, requestUri); 536 apiRequest.setSecure(true); 537 // When 538 HttpMessage requestHandled = 539 api.handleApiRequest( 540 apiRequest, createMockedHttpInputStream(), createMockedHttpOutputStream()); 541 // Then 542 assertThat(requestHandled, is(notNullValue())); 543 assertThat(apiImplementor.wasUsed(), is(equalTo(true))); 544 } 545 546 @Test shouldFailToGetXmlFromResponseWithNullEndpointName()547 void shouldFailToGetXmlFromResponseWithNullEndpointName() throws ApiException { 548 // Given 549 String endpointName = null; 550 ApiResponse response = ApiResponseTest.INSTANCE; 551 // When 552 ApiException e = 553 assertThrows(ApiException.class, () -> API.responseToXml(endpointName, response)); 554 // Then 555 assertThat(e.getMessage(), containsString("internal_error")); 556 } 557 558 @Test shouldFailToGetXmlFromResponseWithEmptyEndpointName()559 void shouldFailToGetXmlFromResponseWithEmptyEndpointName() throws ApiException { 560 // Given 561 String endpointName = ""; 562 ApiResponse response = ApiResponseTest.INSTANCE; 563 // When 564 ApiException e = 565 assertThrows(ApiException.class, () -> API.responseToXml(endpointName, response)); 566 // Then 567 assertThat(e.getMessage(), containsString("internal_error")); 568 } 569 570 @Test shouldFailToGetXmlFromResponseWithInvalidXmlEndpointName()571 void shouldFailToGetXmlFromResponseWithInvalidXmlEndpointName() throws ApiException { 572 // Given 573 String endpointName = "<"; 574 ApiResponse response = ApiResponseTest.INSTANCE; 575 // When 576 ApiException e = 577 assertThrows(ApiException.class, () -> API.responseToXml(endpointName, response)); 578 // Then 579 assertThat(e.getMessage(), containsString("internal_error")); 580 } 581 582 @Test shouldFailToGetXmlFromNullResponse()583 void shouldFailToGetXmlFromNullResponse() throws ApiException { 584 // Given 585 String endpointName = "Name"; 586 ApiResponse response = null; 587 // When 588 ApiException e = 589 assertThrows(ApiException.class, () -> API.responseToXml(endpointName, response)); 590 // Then 591 assertThat(e.getMessage(), containsString("internal_error")); 592 } 593 594 @Test shouldGetXmlFromResponse()595 void shouldGetXmlFromResponse() throws ApiException { 596 // Given 597 String endpointName = "Name"; 598 ApiResponse response = ApiResponseTest.INSTANCE; 599 // When 600 String xmlResponse = API.responseToXml(endpointName, response); 601 // Then 602 assertThat( 603 xmlResponse, 604 is( 605 equalTo( 606 "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><Name>XML</Name>"))); 607 } 608 609 @Test shouldGetHtmlFromResponse()610 void shouldGetHtmlFromResponse() { 611 // Given 612 ApiResponse response = ApiResponseTest.INSTANCE; 613 // When 614 String htmlResponse = API.responseToHtml(response); 615 // Then 616 assertThat(htmlResponse, is(equalTo("<head>\n</head>\n<body>\nHTML</body>\n"))); 617 } 618 619 private static class ApiResponseTest extends ApiResponse { 620 621 private static final ApiResponseTest INSTANCE = new ApiResponseTest(""); 622 ApiResponseTest(String name)623 ApiResponseTest(String name) { 624 super(name); 625 } 626 627 @Override toJSON()628 public JSON toJSON() { 629 return null; 630 } 631 632 @Override toXML(Document doc, Element rootElement)633 public void toXML(Document doc, Element rootElement) { 634 rootElement.appendChild(doc.createTextNode("XML")); 635 } 636 637 @Override toHTML(StringBuilder sb)638 public void toHTML(StringBuilder sb) { 639 sb.append("HTML"); 640 } 641 642 @Override toString(int indent)643 public String toString(int indent) { 644 return null; 645 } 646 } 647 assertApiNonceMatch(API api, String baseUrl)648 private static void assertApiNonceMatch(API api, String baseUrl) { 649 try { 650 // Given 651 URI uri = new URI(baseUrl, true); 652 String hostHeader = uri.getHost() + (uri.getPort() != -1 ? ":" + uri.getPort() : ""); 653 TestApiImplementor apiImplementor = new TestApiImplementor(); 654 api.registerApiImplementor(apiImplementor); 655 // When 656 HttpMessage requestHandled = 657 api.handleApiRequest( 658 createApiRequest(new byte[] {127, 0, 0, 1}, hostHeader, baseUrl), 659 createMockedHttpInputStream(), 660 createMockedHttpOutputStream(), 661 true); 662 // Then 663 assertThat(requestHandled, is(notNullValue())); 664 assertThat(apiImplementor.wasUsed(), is(equalTo(true))); 665 } catch (Exception e) { 666 throw new RuntimeException(e); 667 } 668 } 669 isApiNonceValid(API api, String requestPath, String nonce)670 private static boolean isApiNonceValid(API api, String requestPath, String nonce) { 671 try { 672 URI requestUri = Mockito.mock(URI.class); 673 when(requestUri.getPath()).thenReturn(requestPath); 674 HttpRequestHeader request = 675 Mockito.mock(HttpRequestHeader.class, withSettings().lenient()); 676 when(request.getURI()).thenReturn(requestUri); 677 when(request.getHeader(HttpHeader.X_ZAP_API_NONCE)).thenReturn(nonce); 678 when(request.getSenderAddress()) 679 .thenReturn(Inet4Address.getByAddress(new byte[] {127, 0, 0, 1})); 680 return api.hasValidKey(request, new JSONObject()); 681 } catch (Exception e) { 682 throw new RuntimeException(e); 683 } 684 } 685 createPermittedAddresses(String... addresses)686 private static List<DomainMatcher> createPermittedAddresses(String... addresses) { 687 if (addresses == null || addresses.length == 0) { 688 return new ArrayList<>(); 689 } 690 691 List<DomainMatcher> permittedAddresses = new ArrayList<>(); 692 for (String address : addresses) { 693 permittedAddresses.add(new DomainMatcher(address)); 694 } 695 return permittedAddresses; 696 } 697 createApiRequest( byte[] remoteAddress, String hostname, String requestUri)698 private static HttpRequestHeader createApiRequest( 699 byte[] remoteAddress, String hostname, String requestUri) throws Exception { 700 HttpRequestHeader httpRequestHeader = 701 new HttpRequestHeader( 702 "GET " + requestUri + " HTTP/1.1\r\n" + "Host: " + hostname + "\r\n"); 703 httpRequestHeader.setSenderAddress(Inet4Address.getByAddress(remoteAddress)); 704 return httpRequestHeader; 705 } 706 createOptionsParamApi()707 private static OptionsParamApi createOptionsParamApi() { 708 OptionsParamApi optionsParamApi = new OptionsParamApi(); 709 optionsParamApi.load(new ZapXmlConfiguration()); 710 return optionsParamApi; 711 } 712 createProxyParam(String proxyAddress, int proxyPort)713 private static ProxyParam createProxyParam(String proxyAddress, int proxyPort) { 714 ProxyParam proxyParam = new ProxyParam(); 715 proxyParam.load(new ZapXmlConfiguration()); 716 proxyParam.setProxyIp(proxyAddress); 717 proxyParam.setProxyPort(proxyPort); 718 return proxyParam; 719 } 720 createMockedHttpInputStream()721 private static HttpInputStream createMockedHttpInputStream() { 722 return Mockito.mock(HttpInputStream.class); 723 } 724 createMockedHttpOutputStream()725 private static HttpOutputStream createMockedHttpOutputStream() { 726 return Mockito.mock(HttpOutputStream.class); 727 } 728 createApiWithoutKey()729 private static API createApiWithoutKey() { 730 OptionsParamApi options = createOptionsParamApi(); 731 options.setDisableKey(true); 732 API api = new API(); 733 api.setOptionsParamApi(options); 734 return api; 735 } 736 737 private static class TestApiImplementor extends ApiImplementor { 738 739 static final String PREFIX = "test"; 740 741 private boolean used; 742 743 @Override getPrefix()744 public String getPrefix() { 745 return PREFIX; 746 } 747 748 @Override handleApiView(String name, JSONObject params)749 public ApiResponse handleApiView(String name, JSONObject params) throws ApiException { 750 used = true; 751 return new ApiResponseElement(name, "value"); 752 } 753 754 @Override handleApiAction(String name, JSONObject params)755 public ApiResponse handleApiAction(String name, JSONObject params) throws ApiException { 756 used = true; 757 return new ApiResponseElement(name, "value"); 758 } 759 760 @Override handleApiOther(HttpMessage msg, String name, JSONObject params)761 public HttpMessage handleApiOther(HttpMessage msg, String name, JSONObject params) 762 throws ApiException { 763 used = true; 764 return new HttpMessage(); 765 } 766 767 @Override handleCallBack(HttpMessage msg)768 public String handleCallBack(HttpMessage msg) throws ApiException { 769 used = true; 770 return "response"; 771 } 772 773 @Override handleApiOptionView(String name, JSONObject params)774 public ApiResponse handleApiOptionView(String name, JSONObject params) throws ApiException { 775 used = true; 776 return new ApiResponseElement(name, "value"); 777 } 778 779 @Override handleApiOptionAction(String name, JSONObject params)780 public ApiResponse handleApiOptionAction(String name, JSONObject params) 781 throws ApiException { 782 used = true; 783 return new ApiResponseElement(name, "value"); 784 } 785 786 @Override handleShortcut(HttpMessage msg)787 public HttpMessage handleShortcut(HttpMessage msg) throws ApiException { 788 used = true; 789 return new HttpMessage(); 790 } 791 wasUsed()792 boolean wasUsed() { 793 return used; 794 } 795 } 796 } 797