Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.foo.rest.examples.spring.openapi.v3.security.hiddenaccessible

import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*


@SpringBootApplication(exclude = [SecurityAutoConfiguration::class])
@RequestMapping(path = ["/api"])
@RestController
open class HiddenAccessibleApplication {

companion object {
@JvmStatic
fun main(args: Array<String>) {
SpringApplication.run(HiddenAccessibleApplication::class.java, *args)
}
}

@PostMapping(path = ["/resources"])
open fun post(): ResponseEntity<String> {
return ResponseEntity.status(200).body("OK")
}

@GetMapping(path = ["/resources"])
open fun get(): ResponseEntity<String> {
return ResponseEntity.status(200).body("OK")
}

@GetMapping(path = ["/resources/{id}"])
open fun getId(@PathVariable("id") id: Int): ResponseEntity<String> {
return ResponseEntity.status(200).body("OK")
}

@DeleteMapping(path = ["/resources/{id}"])
open fun deleteId(@PathVariable("id") id: Int): ResponseEntity<String> {
return ResponseEntity.status(200).body("OK")
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
openapi: 3.0.1
info:
title: OpenAPI definition
version: v0
servers:
- url: http://localhost:8080
description: Generated server url
paths:
/api/resources:
# get:
# tags:
# - hidden-accessible-application
# operationId: get
# responses:
# "200":
# description: OK
# content:
# '*/*':
# schema:
# type: string
post:
tags:
- hidden-accessible-application
operationId: post
responses:
"200":
description: OK
content:
'*/*':
schema:
type: string
/api/resources/{id}:
get:
tags:
- hidden-accessible-application
operationId: getId
parameters:
- name: id
in: path
required: true
schema:
type: integer
format: int32
responses:
"200":
description: OK
content:
'*/*':
schema:
type: string
# delete:
# tags:
# - hidden-accessible-application
# operationId: deleteId
# parameters:
# - name: id
# in: path
# required: true
# schema:
# type: integer
# format: int32
# responses:
# "200":
# description: OK
# content:
# '*/*':
# schema:
# type: string
components: {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.foo.rest.examples.spring.openapi.v3.security.hiddenaccessible

import com.foo.rest.examples.spring.openapi.v3.SpringController
import org.evomaster.client.java.controller.problem.ProblemInfo
import org.evomaster.client.java.controller.problem.RestProblem

class HiddenAccessibleController : SpringController(HiddenAccessibleApplication::class.java){


override fun getProblemInfo(): ProblemInfo {
return RestProblem(
"http://localhost:$sutPort/openapi-hiddenaccessible.yaml",
null
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package org.evomaster.e2etests.spring.openapi.v3.security.hiddenaccessible

import com.foo.rest.examples.spring.openapi.v3.security.hiddenaccessible.HiddenAccessibleController
import org.evomaster.core.problem.enterprise.DetectedFaultUtils
import org.evomaster.core.problem.enterprise.ExperimentalFaultCategory
import org.evomaster.core.problem.rest.data.HttpVerb
import org.evomaster.e2etests.spring.openapi.v3.SpringTestBase
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertNotNull

class HiddenAccessibleEMTest : SpringTestBase(){

companion object {
@BeforeAll
@JvmStatic
fun init() {
initClass(HiddenAccessibleController())
}
}


@Test
fun testRunEM() {

runTestHandlingFlakyAndCompilation(
"HiddenAccessibleEM",
20
) { args: MutableList<String> ->

setOption(args, "security", "true")
setOption(args, "useExperimentalOracles", "true")

val solution = initAndRun(args)

assertTrue(solution.individuals.size >= 1)

assertHasAtLeastOne(solution, HttpVerb.POST, 200, "/api/resources", "OK")
assertHasAtLeastOne(solution, HttpVerb.GET, 200, "/api/resources/{id}", "OK")
assertHasAtLeastOne(solution, HttpVerb.OPTIONS, 200, "/api/resources", null)
assertHasAtLeastOne(solution, HttpVerb.OPTIONS, 200, "/api/resources/{id}", null)


val faults = DetectedFaultUtils.getDetectedFaults(solution)
assertTrue(faults.size >= 2)

val hidden = faults.filter{it.category == ExperimentalFaultCategory.HIDDEN_ACCESSIBLE_ENDPOINT}
assertEquals(2, hidden.size)

assertNotNull(hidden.find { it.operationId == "GET:/api/resources" })
assertNotNull(hidden.find { it.operationId == "DELETE:/api/resources/{id}" })
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import org.evomaster.core.EMConfig
import org.evomaster.core.problem.enterprise.SampleType
import org.evomaster.core.problem.rest.data.RestCallAction
import org.evomaster.core.problem.rest.data.RestIndividual
import org.evomaster.core.problem.rest.oracle.RestSecurityOracle
import org.evomaster.core.problem.rest.service.RestIndividualBuilder
import org.evomaster.core.problem.rest.service.fitness.AbstractRestFitness
import org.evomaster.core.problem.rest.service.sampler.AbstractRestSampler
import org.evomaster.core.problem.rest.service.SecurityRest
import org.evomaster.core.problem.rest.service.RestSecurityBuilder
import org.evomaster.core.search.EvaluatedIndividual
import org.evomaster.core.search.service.Archive
import org.evomaster.core.seeding.service.rest.PirToRest
Expand Down Expand Up @@ -51,12 +52,14 @@ abstract class IntegrationTestRestBase : RestTestBase() {

fun getArchive() = injector.getInstance(Archive::class.java) as Archive<RestIndividual>

fun getSecurityRest() = injector.getInstance(SecurityRest::class.java) as SecurityRest
fun getSecurityRest() = injector.getInstance(RestSecurityBuilder::class.java) as RestSecurityBuilder

fun getEMConfig() = injector.getInstance(EMConfig::class.java)

fun getBuilder() = injector.getInstance(RestIndividualBuilder::class.java)

fun getSecurityOracle() = injector.getInstance(RestSecurityOracle::class.java)

/**
* Create and evaluate an individual
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class ForgottenAuthenticationTest: IntegrationTestRestBase() {
assertEquals(403, r2.getStatusCode())
assertEquals(200, r3.getStatusCode())

val faultDetected = RestSecurityOracle.hasForgottenAuthentication(get42NotAuth.getName(), ei.individual, ei.seeResults())
val faultDetected = getSecurityOracle().hasForgottenAuthentication(get42NotAuth.getName(), ei.individual, ei.seeResults())
assertTrue(faultDetected)

//fault should be put on 200 with no authentication
Expand Down Expand Up @@ -129,7 +129,7 @@ class ForgottenAuthenticationTest: IntegrationTestRestBase() {
assertEquals(200, r1.getStatusCode())

// we couldn't say this is forgotten because GET could be open, so we cannot be sure.
val faultDetected = RestSecurityOracle.hasForgottenAuthentication(put42.getName(), ei.individual, ei.seeResults())
val faultDetected = getSecurityOracle().hasForgottenAuthentication(put42.getName(), ei.individual, ei.seeResults())
assertFalse(faultDetected)

assertEquals(0, r0.getFaults().size)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class SecurityExistenceLeakageTest: IntegrationTestRestBase() {
assertEquals(403, r1.getStatusCode())
assertEquals(404, r2.getStatusCode())

val faultDetected = RestSecurityOracle.hasExistenceLeakage(RestPath("/api/resources/{id}"),ei.individual, ei.seeResults(), listOf())
val faultDetected = getSecurityOracle().hasExistenceLeakage(RestPath("/api/resources/{id}"),ei.individual, ei.seeResults())
assertTrue(faultDetected)

//fault should be put on 404
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class SecurityForbiddenOperationTest : IntegrationTestRestBase() {
assertEquals(403, (ind.evaluatedMainActions()[1].result as RestCallResult).getStatusCode())
assertEquals(204, (ind.evaluatedMainActions()[2].result as RestCallResult).getStatusCode())

val faultDetected = RestSecurityOracle.hasForbiddenOperation(HttpVerb.DELETE,ind.individual, ind.seeResults())
val faultDetected = getSecurityOracle().hasForbiddenOperation(HttpVerb.DELETE,ind.individual, ind.seeResults())
assertTrue(faultDetected)
}

Expand Down Expand Up @@ -94,7 +94,7 @@ class SecurityForbiddenOperationTest : IntegrationTestRestBase() {
assertEquals(403, (ind.evaluatedMainActions()[1].result as RestCallResult).getStatusCode())
assertEquals(204, (ind.evaluatedMainActions()[2].result as RestCallResult).getStatusCode())

val faultDetected = RestSecurityOracle.hasForbiddenOperation(HttpVerb.DELETE,ind.individual, ind.seeResults())
val faultDetected = getSecurityOracle().hasForbiddenOperation(HttpVerb.DELETE,ind.individual, ind.seeResults())
assertTrue(faultDetected)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import bar.examples.it.spring.simplesecuritydeleteput.SimpleSecurityDeletePutCon
import org.evomaster.core.problem.enterprise.SampleType
import org.evomaster.core.problem.rest.data.HttpVerb
import org.evomaster.core.problem.rest.IntegrationTestRestBase
import org.evomaster.core.problem.rest.oracle.RestSecurityOracle
import org.evomaster.core.problem.rest.param.QueryParam
import org.evomaster.core.search.gene.string.StringGene
import org.junit.jupiter.api.Assertions.assertTrue
Expand Down Expand Up @@ -55,7 +54,7 @@ class SimpleSecurityDeletePutControllerTest : IntegrationTestRestBase() {

val sampleInd = createIndividual(listOf(action1Ind1, action2Ind1, action3Ind1, action4Ind1, action5Ind1), SampleType.SECURITY)

val testCovered = RestSecurityOracle.hasForbiddenOperation(HttpVerb.DELETE,sampleInd.individual, sampleInd.seeResults() )
val testCovered = getSecurityOracle().hasForbiddenOperation(HttpVerb.DELETE,sampleInd.individual, sampleInd.seeResults() )

assertTrue(testCovered)
}
Expand Down
13 changes: 2 additions & 11 deletions core/src/main/kotlin/org/evomaster/core/Main.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.evomaster.core

import com.google.inject.Inject
import com.google.inject.Injector
import com.google.inject.Key
import com.google.inject.TypeLiteral
Expand Down Expand Up @@ -329,15 +328,7 @@ class Main {
when (config.problemType) {
EMConfig.ProblemType.REST -> {
val k = data.find { it.header == Statistics.COVERED_2XX }!!.element.toInt()
val t = if (sampler.getPreDefinedIndividuals().isNotEmpty()) {
/*
FIXME this is a temporary hack...
right now we might have 1 call to Schema that messes up this statistics
*/
n + 1
} else {
n
}
val t = n
assert(k <= t)
val p = String.format("%.0f", (k.toDouble() / t) * 100)
LoggingUtil.getInfoLogger()
Expand Down Expand Up @@ -462,7 +453,7 @@ class Main {

return when (config.problemType) {
EMConfig.ProblemType.REST -> {
val securityRest = injector.getInstance(SecurityRest::class.java)
val securityRest = injector.getInstance(RestSecurityBuilder::class.java)
securityRest.applySecurityPhase()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -735,14 +735,7 @@ abstract class HttpWsTestCaseWriter : ApiTestCaseWriter() {
if (config.enableBasicAssertions && !call.shouldSkipAssertionsOnResponseBody()) {
handleResponseAssertions(lines, res, null)
}

}

// else if (partialOracles.generatesExpectation(call, res)
// && format.isJavaOrKotlin()){
// //FIXME what is this for???
// lines.add(".then()")
// }
}

//----------------------------------------------------------------------------------------
Expand All @@ -766,6 +759,20 @@ abstract class HttpWsTestCaseWriter : ApiTestCaseWriter() {
lines.add(".assertThat()")
}

val allow = res.getAllow()
if(!allow.isNullOrBlank()){
val instruction = when {
format.isJavaOrKotlin() -> ".header(\"Allow\", \"$allow\")"
format.isJavaScript() ->
"expect($responseVariableName.header[\"allow\"].startsWith(\"$allow\")).toBe(true);"
format.isPython() -> "assert \"$allow\" in $responseVariableName.headers[\"allow\"]"
else -> throw IllegalStateException("Unsupported format $format")
}
//lines.add(instruction)
//TODO: verb order in Allow header is flaky
lines.addSingleCommentLine(instruction)
}

if (res.getTooLargeBody()) {
lines.addSingleCommentLine("the response payload was too large, above the threshold of ${config.maxResponseByteSize} bytes." +
" No assertion on it is therefore generated.")
Expand Down Expand Up @@ -802,7 +809,6 @@ abstract class HttpWsTestCaseWriter : ApiTestCaseWriter() {
} else{
lines.addSingleCommentLine(instruction)
}

}

val type = res.getBodyType()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,8 @@ abstract class TestCaseWriter {
result.getFaults().sortedBy { it.category.code }
.forEach {
val cat = it.category
lines.addSingleCommentLine("Fault${cat.code}. ${cat.descriptiveName}. ${it.context}.")
val context = if(it.context != null) " ${it.context}" else ""
lines.addSingleCommentLine("Fault${cat.code}. ${cat.descriptiveName}.$context")
if(it.localMessage != null){
lines.append(" ${it.localMessage}")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ enum class ExperimentalFaultCategory(
LEAKED_STACK_TRACES(902, "Leaked Stack Trace",
"leakedStackTrace",
"TODO"),

HIDDEN_ACCESSIBLE_ENDPOINT(903, "Hidden Accessible Endpoint",
"hiddenAccessible",
"TODO"),


HTTP_INVALID_PAYLOAD_SYNTAX(911, "Invalid Payload Syntax", "rejectedWithInvalidPayloadSyntax",
Expand Down
Loading