Skip to content

Commit b74acec

Browse files
authored
Merge pull request #1604 from WebFuzzing/invalid-allow
http oracle - invalid allow
2 parents 0b9dc64 + 459d57f commit b74acec

10 files changed

Lines changed: 425 additions & 0 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package com.foo.rest.examples.spring.openapi.v3.httporacle.invalidallow.base
2+
3+
import io.swagger.v3.oas.annotations.Hidden
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.ResponseEntity
8+
import org.springframework.web.bind.annotation.DeleteMapping
9+
import org.springframework.web.bind.annotation.GetMapping
10+
import org.springframework.web.bind.annotation.PathVariable
11+
import org.springframework.web.bind.annotation.PutMapping
12+
import org.springframework.web.bind.annotation.RequestMapping
13+
import org.springframework.web.bind.annotation.RestController
14+
15+
16+
@SpringBootApplication(exclude = [SecurityAutoConfiguration::class])
17+
@RequestMapping(path = ["/api"])
18+
@RestController
19+
open class HttpInvalidAllowApplication {
20+
21+
companion object {
22+
@JvmStatic
23+
fun main(args: Array<String>) {
24+
SpringApplication.run(HttpInvalidAllowApplication::class.java, *args)
25+
}
26+
27+
private val products = mutableMapOf<Int, String>()
28+
private val orders = mutableMapOf<Int, String>()
29+
30+
fun reset() {
31+
products.clear()
32+
orders.clear()
33+
}
34+
}
35+
36+
// Faulty resource: DELETE is mapped but hidden from the schema,
37+
// so OPTIONS Allow lists a verb (DELETE) not documented in OpenAPI.
38+
39+
@GetMapping(path = ["/products/{id}"])
40+
open fun getProduct(@PathVariable("id") id: Int): ResponseEntity<String> {
41+
val value = products[id] ?: return ResponseEntity.status(404).build()
42+
return ResponseEntity.status(200).body(value)
43+
}
44+
45+
@PutMapping(path = ["/products/{id}"])
46+
open fun putProduct(@PathVariable("id") id: Int): ResponseEntity<Any> {
47+
val isNew = !products.containsKey(id)
48+
products[id] = "$id"
49+
return ResponseEntity.status(if (isNew) 201 else 200).build()
50+
}
51+
52+
@Hidden
53+
@DeleteMapping(path = ["/products/{id}"])
54+
open fun deleteProduct(@PathVariable("id") id: Int): ResponseEntity<Any> {
55+
products.remove(id)
56+
return ResponseEntity.status(204).build()
57+
}
58+
59+
// Clean resource: Allow matches the schema (ignoring HEAD/OPTIONS).
60+
@GetMapping(path = ["/orders/{id}"])
61+
open fun getOrder(@PathVariable("id") id: Int): ResponseEntity<String> {
62+
val value = orders[id] ?: return ResponseEntity.status(404).build()
63+
return ResponseEntity.status(200).body(value)
64+
}
65+
66+
@PutMapping(path = ["/orders/{id}"])
67+
open fun putOrder(@PathVariable("id") id: Int): ResponseEntity<Any> {
68+
val isNew = !orders.containsKey(id)
69+
orders[id] = "$id"
70+
return ResponseEntity.status(if (isNew) 201 else 200).build()
71+
}
72+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package com.foo.rest.examples.spring.openapi.v3.httporacle.invalidallow.missing
2+
3+
import org.springframework.boot.SpringApplication
4+
import org.springframework.boot.autoconfigure.SpringBootApplication
5+
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
6+
import org.springframework.http.ResponseEntity
7+
import org.springframework.web.bind.annotation.GetMapping
8+
import org.springframework.web.bind.annotation.PathVariable
9+
import org.springframework.web.bind.annotation.PutMapping
10+
import org.springframework.web.bind.annotation.RequestMapping
11+
import org.springframework.web.bind.annotation.RestController
12+
13+
14+
@SpringBootApplication(exclude = [SecurityAutoConfiguration::class])
15+
@RequestMapping(path = ["/api"])
16+
@RestController
17+
open class HttpMissingAllowApplication {
18+
19+
companion object {
20+
@JvmStatic
21+
fun main(args: Array<String>) {
22+
SpringApplication.run(HttpMissingAllowApplication::class.java, *args)
23+
}
24+
25+
private val products = mutableMapOf<Int, String>()
26+
private val orders = mutableMapOf<Int, String>()
27+
28+
fun reset() {
29+
products.clear()
30+
orders.clear()
31+
}
32+
}
33+
34+
// Faulty resource: the manual schema declares DELETE, but no DELETE handler is mapped,
35+
// so OPTIONS Allow omits a verb (DELETE) that is documented in OpenAPI.
36+
37+
@GetMapping(path = ["/products/{id}"])
38+
open fun getProduct(@PathVariable("id") id: Int): ResponseEntity<String> {
39+
val value = products[id] ?: return ResponseEntity.status(404).build()
40+
return ResponseEntity.status(200).body(value)
41+
}
42+
43+
@PutMapping(path = ["/products/{id}"])
44+
open fun putProduct(@PathVariable("id") id: Int): ResponseEntity<Any> {
45+
val isNew = !products.containsKey(id)
46+
products[id] = "$id"
47+
return ResponseEntity.status(if (isNew) 201 else 200).build()
48+
}
49+
50+
// Clean resource: Allow matches the schema (ignoring HEAD/OPTIONS).
51+
52+
@GetMapping(path = ["/orders/{id}"])
53+
open fun getOrder(@PathVariable("id") id: Int): ResponseEntity<String> {
54+
val value = orders[id] ?: return ResponseEntity.status(404).build()
55+
return ResponseEntity.status(200).body(value)
56+
}
57+
58+
@PutMapping(path = ["/orders/{id}"])
59+
open fun putOrder(@PathVariable("id") id: Int): ResponseEntity<Any> {
60+
val isNew = !orders.containsKey(id)
61+
orders[id] = "$id"
62+
return ResponseEntity.status(if (isNew) 201 else 200).build()
63+
}
64+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
openapi: 3.0.0
2+
info:
3+
title: Missing Allow API
4+
version: 1.0.0
5+
paths:
6+
# Faulty resource: declares DELETE, but the server has no DELETE handler,
7+
# so OPTIONS Allow omits a declared verb.
8+
/api/products/{id}:
9+
get:
10+
parameters:
11+
- name: id
12+
in: path
13+
required: true
14+
schema:
15+
type: integer
16+
format: int32
17+
responses:
18+
"200":
19+
description: Found
20+
"404":
21+
description: Not found
22+
put:
23+
parameters:
24+
- name: id
25+
in: path
26+
required: true
27+
schema:
28+
type: integer
29+
format: int32
30+
responses:
31+
"200":
32+
description: Updated
33+
"201":
34+
description: Created
35+
delete:
36+
parameters:
37+
- name: id
38+
in: path
39+
required: true
40+
schema:
41+
type: integer
42+
format: int32
43+
responses:
44+
"204":
45+
description: Deleted
46+
# Clean resource: declared verbs match the served ones.
47+
/api/orders/{id}:
48+
get:
49+
parameters:
50+
- name: id
51+
in: path
52+
required: true
53+
schema:
54+
type: integer
55+
format: int32
56+
responses:
57+
"200":
58+
description: Found
59+
"404":
60+
description: Not found
61+
put:
62+
parameters:
63+
- name: id
64+
in: path
65+
required: true
66+
schema:
67+
type: integer
68+
format: int32
69+
responses:
70+
"200":
71+
description: Updated
72+
"201":
73+
description: Created
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.foo.rest.examples.spring.openapi.v3.httporacle.invalidallow
2+
3+
import com.foo.rest.examples.spring.openapi.v3.SpringController
4+
import com.foo.rest.examples.spring.openapi.v3.httporacle.invalidallow.base.HttpInvalidAllowApplication
5+
6+
7+
class HttpInvalidAllowController : SpringController(HttpInvalidAllowApplication::class.java) {
8+
9+
override fun resetStateOfSUT() {
10+
HttpInvalidAllowApplication.reset()
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.foo.rest.examples.spring.openapi.v3.httporacle.invalidallow.missing
2+
3+
import com.foo.rest.examples.spring.openapi.v3.SpringController
4+
import org.evomaster.client.java.controller.problem.ProblemInfo
5+
import org.evomaster.client.java.controller.problem.RestProblem
6+
7+
8+
class HttpMissingAllowController : SpringController(HttpMissingAllowApplication::class.java) {
9+
10+
override fun resetStateOfSUT() {
11+
HttpMissingAllowApplication.reset()
12+
}
13+
14+
override fun getProblemInfo(): ProblemInfo {
15+
return RestProblem(
16+
"http://localhost:$sutPort/openapi-missingallow.yml",
17+
null
18+
)
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package org.evomaster.e2etests.spring.openapi.v3.httporacle.invalidallow
2+
3+
import com.foo.rest.examples.spring.openapi.v3.httporacle.invalidallow.HttpInvalidAllowController
4+
import org.evomaster.core.problem.enterprise.DetectedFaultUtils
5+
import org.evomaster.core.problem.enterprise.ExperimentalFaultCategory
6+
import org.evomaster.e2etests.spring.openapi.v3.SpringTestBase
7+
import org.junit.jupiter.api.Assertions.assertTrue
8+
import org.junit.jupiter.api.BeforeAll
9+
import org.junit.jupiter.api.Test
10+
11+
class HttpInvalidAllowEMTest : SpringTestBase() {
12+
13+
companion object {
14+
@BeforeAll
15+
@JvmStatic
16+
fun init() {
17+
initClass(HttpInvalidAllowController())
18+
}
19+
}
20+
21+
22+
@Test
23+
fun testRunEM() {
24+
25+
runTestHandlingFlakyAndCompilation(
26+
"HttpInvalidAllowEM",
27+
20
28+
) { args: MutableList<String> ->
29+
30+
setOption(args, "security", "false")
31+
setOption(args, "schemaOracles", "false")
32+
setOption(args, "httpOracles", "true")
33+
setOption(args, "useExperimentalOracles", "true")
34+
35+
val solution = initAndRun(args)
36+
37+
assertTrue(solution.individuals.size >= 1)
38+
39+
val faults = DetectedFaultUtils.getDetectedFaultCategories(solution)
40+
assertTrue({ ExperimentalFaultCategory.HTTP_INVALID_ALLOW in faults })
41+
42+
val allowFaults = DetectedFaultUtils.getDetectedFaults(solution)
43+
.filter { it.category == ExperimentalFaultCategory.HTTP_INVALID_ALLOW }
44+
45+
assertTrue(allowFaults.any { it.operationId.contains("/api/products/") })
46+
assertTrue(allowFaults.none { it.operationId.contains("/api/orders/") })
47+
}
48+
}
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package org.evomaster.e2etests.spring.openapi.v3.httporacle.invalidallow.missing
2+
3+
import com.foo.rest.examples.spring.openapi.v3.httporacle.invalidallow.missing.HttpMissingAllowController
4+
import org.evomaster.core.problem.enterprise.DetectedFaultUtils
5+
import org.evomaster.core.problem.enterprise.ExperimentalFaultCategory
6+
import org.evomaster.e2etests.spring.openapi.v3.SpringTestBase
7+
import org.junit.jupiter.api.Assertions.assertTrue
8+
import org.junit.jupiter.api.BeforeAll
9+
import org.junit.jupiter.api.Test
10+
11+
class HttpMissingAllowEMTest : SpringTestBase() {
12+
13+
companion object {
14+
@BeforeAll
15+
@JvmStatic
16+
fun init() {
17+
initClass(HttpMissingAllowController())
18+
}
19+
}
20+
21+
22+
@Test
23+
fun testRunEM() {
24+
25+
runTestHandlingFlakyAndCompilation(
26+
"HttpMissingAllowEM",
27+
20
28+
) { args: MutableList<String> ->
29+
30+
setOption(args, "security", "false")
31+
setOption(args, "schemaOracles", "false")
32+
setOption(args, "httpOracles", "true")
33+
setOption(args, "useExperimentalOracles", "true")
34+
35+
val solution = initAndRun(args)
36+
37+
assertTrue(solution.individuals.size >= 1)
38+
39+
val faults = DetectedFaultUtils.getDetectedFaultCategories(solution)
40+
assertTrue(ExperimentalFaultCategory.HTTP_INVALID_ALLOW in faults)
41+
42+
val allowFaults = DetectedFaultUtils.getDetectedFaults(solution)
43+
.filter { it.category == ExperimentalFaultCategory.HTTP_INVALID_ALLOW }
44+
45+
assertTrue(allowFaults.any { it.operationId.contains("/api/products/") })
46+
assertTrue(allowFaults.none { it.operationId.contains("/api/orders/") })
47+
}
48+
}
49+
}

core/src/main/kotlin/org/evomaster/core/problem/enterprise/ExperimentalFaultCategory.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ enum class ExperimentalFaultCategory(
3737

3838
HTTP_NON_IDEMPOTENT_PUT(918, "PUT is idempotent", "nonIdempotentPut",
3939
"TODO"),
40+
HTTP_INVALID_ALLOW(919, "Invalid allow", "invalidAllow",
41+
"TODO"),
4042

4143
HTTP_STATUS_NO_NON_STANDARD_CODES(950, "no-non-standard-codes", "invalidStatusCode", "TODO"),
4244
HTTP_STATUS_NO_201_IF_DELETE(951, "no-201-if-delete", "201OnDelete", "TODO"),

0 commit comments

Comments
 (0)