Skip to content

Commit eadec94

Browse files
authored
Merge pull request #1285 from WebFuzzing/externalEndpointURL
enable to override externalEndpointURL
2 parents 0c762cc + 0b2272f commit eadec94

16 files changed

Lines changed: 334 additions & 12 deletions

File tree

core/src/main/kotlin/org/evomaster/core/EMConfig.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2553,6 +2553,15 @@ class EMConfig {
25532553
"Only available for JVM languages")
25542554
var dtoForRequestPayload = false
25552555

2556+
@Cfg("Override the value of externalEndpointURL in auth configurations." +
2557+
" This is useful when the auth server is running locally on an ephemeral port, or when several instances" +
2558+
" are run in parallel, to avoid creating/modifying auth configuration files." +
2559+
" If what provided is a URL starting with 'http', then full replacement will occur." +
2560+
" Otherwise, the input will be treated as a 'hostname:port', and only that info will be updated (e.g.," +
2561+
" path element of the URL will not change).")
2562+
var overrideAuthExternalEndpointURL : String? = null
2563+
2564+
25562565
fun getProbabilityUseDataPool() : Double{
25572566
return if(blackBox){
25582567
bbProbabilityUseDataPool

core/src/main/kotlin/org/evomaster/core/output/auth/TokenWriter.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,7 @@ object TokenWriter {
5454
if(k.token!!.headerPrefix.isNotEmpty()) {
5555
lines.append("\"${k.token!!.headerPrefix}\"")
5656
}else{
57-
if (format.isJavaScript() || format.isPython())
58-
lines.append("\"\"")
57+
lines.append("\"\"")
5958
}
6059

6160
if (!format.isPython()) {
@@ -66,7 +65,6 @@ object TokenWriter {
6665
}
6766
}
6867

69-
7068
when{
7169
format.isJavaOrKotlin() -> lines.append("given()")
7270
format.isJavaScript() -> {
@@ -97,9 +95,12 @@ object TokenWriter {
9795
lines.indented { lines.add("error => {console.log(error.response.body); throw Error(\"Auth failed.\")});") }
9896
} else if (format.isPython()) {
9997
lines.add("${tokenName(k)} = ${tokenName(k)} + ${responseName(k)}.json()$path")
100-
}else
98+
}else if (format.isJavaOrKotlin()) {
10199
lines.add(".then().extract().response().path(\"$path\")")
102-
100+
if(format.isKotlin()) {
101+
lines.append("!!")
102+
}
103+
}
103104
lines.appendSemicolon()
104105
lines.addEmpty()
105106

core/src/main/kotlin/org/evomaster/core/problem/httpws/auth/EndpointCallLogin.kt

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package org.evomaster.core.problem.httpws.auth
22

33
import com.webfuzzing.commons.auth.LoginEndpoint
44
import com.webfuzzing.commons.auth.PayloadUsernamePassword
5+
import org.apache.http.client.utils.URIBuilder
56
import org.evomaster.core.Lazy
7+
import org.evomaster.core.config.ConfigProblemException
68
import org.evomaster.core.problem.rest.data.ContentType
79
import org.evomaster.core.problem.rest.data.HttpVerb
810
import java.net.MalformedURLException
@@ -79,10 +81,43 @@ class EndpointCallLogin(
7981
}
8082

8183
companion object {
82-
fun fromDto(name: String, dto: LoginEndpoint) = EndpointCallLogin(
84+
fun fromDto(name: String, dto: LoginEndpoint, externalEndpointURL: String? = null) = EndpointCallLogin(
8385
name = name,
8486
endpoint = dto.endpoint,
85-
externalEndpointURL = dto.externalEndpointURL,
87+
externalEndpointURL = if(externalEndpointURL != null){
88+
if(externalEndpointURL.startsWith("http")){
89+
externalEndpointURL
90+
} else if(dto.externalEndpointURL == null){
91+
/*
92+
if we are doing a partial replacement with hostname:port, but there is no
93+
origin URL, then there is nothing to do.
94+
*/
95+
null
96+
} else {
97+
val tokens = externalEndpointURL.split(":")
98+
if(tokens.size != 2){
99+
throw ConfigProblemException("Invalid hostname:port pair -> $externalEndpointURL")
100+
}
101+
val hostname = tokens[0]
102+
val port = try{
103+
tokens[1].toInt()
104+
} catch (e: NumberFormatException){
105+
throw ConfigProblemException("Invalid port number in hostname:port pair " +
106+
"-> $externalEndpointURL -> ${e.message}")
107+
}
108+
109+
val builder = try{
110+
URIBuilder(dto.externalEndpointURL)
111+
} catch (e: MalformedURLException){
112+
throw ConfigProblemException("Invalid dto.externalEndpointURL -> ${e.message}")
113+
}
114+
builder.setHost(hostname)
115+
builder.setPort(port)
116+
builder.build().toString()
117+
}
118+
} else {
119+
dto.externalEndpointURL
120+
},
86121
payload = dto.payloadRaw ?:
87122
dto.payloadUserPwd?.let { computePayload(it, ContentType.from(dto.contentType)) },
88123
headers = dto.headers?.map { AuthenticationHeader(it.name, it.value) } ?: emptyList(),

core/src/main/kotlin/org/evomaster/core/problem/httpws/auth/HttpWsAuthenticationInfo.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@ open class HttpWsAuthenticationInfo(
3737

3838
companion object{
3939

40-
fun fromDto(dto: AuthenticationDto) : HttpWsAuthenticationInfo{
40+
/**
41+
* @param dto the DTO to convert into internal representation
42+
* @param externalEndpointURL optionally override the value for externalEndpointURL in the DTO
43+
*/
44+
fun fromDto(dto: AuthenticationDto, externalEndpointURL: String? = null) : HttpWsAuthenticationInfo{
4145

4246
if (dto.name == null || dto.name.isBlank()) {
4347
throw IllegalArgumentException("Missing name in authentication info")
@@ -57,7 +61,7 @@ open class HttpWsAuthenticationInfo(
5761

5862
val endpointCallLogin = if(dto.loginEndpointAuth != null){
5963
try {
60-
EndpointCallLogin.fromDto(dto.name, dto.loginEndpointAuth)
64+
EndpointCallLogin.fromDto(dto.name, dto.loginEndpointAuth, externalEndpointURL)
6165
} catch (e: Exception){
6266
throw IllegalArgumentException("Issue when parsing auth info for '${dto.name}': ${e.message}")
6367
}

core/src/main/kotlin/org/evomaster/core/problem/httpws/service/HttpWsSampler.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ abstract class HttpWsSampler<T> : ApiWsSampler<T>() where T : Individual{
9595
private fun handleAuthInfo(i: AuthenticationDto) {
9696

9797
val auth = try{
98-
HttpWsAuthenticationInfo.fromDto(i)
98+
HttpWsAuthenticationInfo.fromDto(i, config.overrideAuthExternalEndpointURL)
9999
}catch (e: Exception){
100100
throw SutProblemException("Failed to parse auth info: " + e.message!!)
101101
}

docs/options.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ There are 3 types of options:
165165
|`nameWithQueryParameters`| __Boolean__. Specify if true boolean query parameters are included in the test case name. Used for test case naming disambiguation. Only valid for Action based naming strategy. *Default value*: `true`.|
166166
|`namingStrategy`| __Enum__. Specify the naming strategy for test cases. *Valid values*: `NUMBERED, ACTION`. *Default value*: `ACTION`.|
167167
|`outputExecutedSQL`| __Enum__. Whether to output executed sql info. *DEBUG option*. *Valid values*: `NONE, ALL_AT_END, ONCE_EXECUTED`. *Default value*: `NONE`.|
168+
|`overrideAuthExternalEndpointURL`| __String__. Override the value of externalEndpointURL in auth configurations. This is useful when the auth server is running locally on an ephemeral port, or when several instances are run in parallel, to avoid creating/modifying auth configuration files. If what provided is a URL starting with 'http', then full replacement will occur. Otherwise, the input will be treated as a 'hostname:port', and only that info will be updated (e.g., path element of the URL will not change). *Default value*: `null`.|
168169
|`populationSize`| __Int__. Define the population size in the search algorithms that use populations (e.g., Genetic Algorithms, but not MIO). *Constraints*: `min=1.0`. *Default value*: `30`.|
169170
|`probOfApplySQLActionToCreateResources`| __Double__. Specify a probability to apply SQL actions for preparing resources for REST Action. *Constraints*: `probability 0.0-1.0`. *Default value*: `0.1`.|
170171
|`probOfArchiveMutation`| __Double__. Specify a probability to enable archive-based mutation. *Constraints*: `probability 0.0-1.0`. *Default value*: `0.5`.|
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package com.foo.rest.examples.bb.externalauth
2+
3+
import org.evomaster.e2etests.utils.CoveredTargets
4+
import org.springframework.boot.SpringApplication
5+
import org.springframework.boot.autoconfigure.SpringBootApplication
6+
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
7+
import org.springframework.http.MediaType
8+
import org.springframework.http.ResponseEntity
9+
import org.springframework.web.bind.annotation.*
10+
import javax.servlet.http.HttpServletResponse
11+
12+
@RestController
13+
@RequestMapping(path = ["/api/externalauth"])
14+
@SpringBootApplication(exclude = [SecurityAutoConfiguration::class])
15+
open class ExternalAuthApplication {
16+
17+
companion object {
18+
@JvmStatic
19+
fun main(args: Array<String>) {
20+
SpringApplication.run(ExternalAuthApplication::class.java, *args)
21+
}
22+
}
23+
24+
25+
@PostMapping(path = ["/login1"], consumes = [MediaType.APPLICATION_JSON_VALUE])
26+
open fun login(@RequestBody login : LoginDto, response : HttpServletResponse) : ResponseEntity<Map<String, String>> {
27+
if(login.username == "foo" && login.password == "123"){
28+
return ResponseEntity.ok(mapOf("access_token" to "token1"))
29+
}
30+
return ResponseEntity.status(401).build()
31+
}
32+
33+
@PostMapping(path = ["/login2"], consumes = [MediaType.APPLICATION_JSON_VALUE])
34+
open fun login2(@RequestBody login : LoginDto, response : HttpServletResponse) : ResponseEntity<Map<String, String>> {
35+
if(login.username == "foo" && login.password == "123"){
36+
return ResponseEntity.ok(mapOf("access_token" to "token2"))
37+
}
38+
return ResponseEntity.status(401).build()
39+
}
40+
41+
@GetMapping(path = ["/check"])
42+
open fun check(@RequestHeader("Authorization") authorization: String?) : ResponseEntity<String> {
43+
44+
if(authorization.isNullOrEmpty()){
45+
return ResponseEntity.status(401).build()
46+
}
47+
if(authorization == "token1" || authorization == "token2") {
48+
CoveredTargets.cover("token1")
49+
return ResponseEntity.ok(authorization)
50+
}
51+
return ResponseEntity.status(401).build()
52+
}
53+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.foo.rest.examples.bb.externalauth
2+
3+
class LoginDto(
4+
var username: String? = null,
5+
var password: String? = null
6+
)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.foo.rest.examples.bb.externalauth
2+
3+
import com.foo.rest.examples.bb.SpringController
4+
import org.evomaster.client.java.controller.problem.ProblemInfo
5+
import org.evomaster.client.java.controller.problem.RestProblem
6+
7+
class ExternalAuthController : SpringController(ExternalAuthApplication::class.java) {
8+
9+
override fun getProblemInfo(): ProblemInfo {
10+
return RestProblem(
11+
"http://localhost:$sutPort/v3/api-docs",
12+
listOf("/api/externalauth/login1","/api/externalauth/login2")
13+
)
14+
}
15+
16+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package org.evomaster.e2etests.spring.rest.bb.externalauth
2+
3+
import com.foo.rest.examples.bb.externalauth.ExternalAuthController
4+
import org.evomaster.core.output.OutputFormat
5+
import org.evomaster.core.problem.rest.data.HttpVerb
6+
import org.evomaster.e2etests.spring.rest.bb.SpringTestBase
7+
import org.junit.jupiter.api.Assertions.assertTrue
8+
import org.junit.jupiter.api.BeforeAll
9+
import org.junit.jupiter.params.ParameterizedTest
10+
import org.junit.jupiter.params.provider.EnumSource
11+
12+
class BBExternalAuthEMTest : SpringTestBase() {
13+
14+
companion object {
15+
@BeforeAll
16+
@JvmStatic
17+
fun init() {
18+
initClass(ExternalAuthController())
19+
}
20+
}
21+
22+
@ParameterizedTest
23+
@EnumSource
24+
fun testBlackBoxOutput(outputFormat: OutputFormat) {
25+
26+
executeAndEvaluateBBTest(
27+
outputFormat,
28+
"externalauth",
29+
20,
30+
3,
31+
listOf("token1")
32+
){ args: MutableList<String> ->
33+
34+
setOption(args, "configPath", "src/test/resources/config/external_auth.yaml")
35+
setOption(args, "endpointFocus", "/api/externalauth/check")
36+
val uri = java.net.URI(baseUrlOfSut)
37+
val port = uri.port
38+
39+
setOption(args, "overrideAuthExternalEndpointURL", "localhost:$port")
40+
41+
val solution = initAndRun(args)
42+
43+
assertTrue(solution.individuals.size >= 1)
44+
assertHasAtLeastOne(solution, HttpVerb.GET, 200, "/api/externalauth/check", "token1")
45+
assertNone(solution, HttpVerb.GET, 200, "/api/externalauth/check", "token2")
46+
}
47+
}
48+
}

0 commit comments

Comments
 (0)