Path: blob/dev/cmd/integration-test/workflow.go
2070 views
package main12import (3"fmt"4"io"5"log"6"net/http"7"net/http/httptest"8"strings"910"github.com/julienschmidt/httprouter"1112"github.com/projectdiscovery/nuclei/v3/pkg/templates"13"github.com/projectdiscovery/nuclei/v3/pkg/templates/signer"14"github.com/projectdiscovery/nuclei/v3/pkg/testutils"15sliceutil "github.com/projectdiscovery/utils/slice"16)1718var workflowTestcases = []TestCaseInfo{19{Path: "workflow/basic.yaml", TestCase: &workflowBasic{}},20{Path: "workflow/condition-matched.yaml", TestCase: &workflowConditionMatched{}},21{Path: "workflow/condition-unmatched.yaml", TestCase: &workflowConditionUnmatch{}},22{Path: "workflow/matcher-name.yaml", TestCase: &workflowMatcherName{}},23{Path: "workflow/complex-conditions.yaml", TestCase: &workflowComplexConditions{}},24{Path: "workflow/http-value-share-workflow.yaml", TestCase: &workflowHttpKeyValueShare{}},25{Path: "workflow/dns-value-share-workflow.yaml", TestCase: &workflowDnsKeyValueShare{}},26{Path: "workflow/code-value-share-workflow.yaml", TestCase: &workflowCodeKeyValueShare{}, DisableOn: isCodeDisabled}, // isCodeDisabled declared in code.go27{Path: "workflow/multiprotocol-value-share-workflow.yaml", TestCase: &workflowMultiProtocolKeyValueShare{}},28{Path: "workflow/multimatch-value-share-workflow.yaml", TestCase: &workflowMultiMatchKeyValueShare{}},29{Path: "workflow/shared-cookie.yaml", TestCase: &workflowSharedCookies{}},30}3132func init() {33// sign code templates (unless they are disabled)34if !isCodeDisabled() {35// allow local file access to load content of file references in template36// in order to sign them for testing purposes37templates.TemplateSignerLFA()3839// testCertFile and testKeyFile are declared in code.go40tsigner, err := signer.NewTemplateSignerFromFiles(testCertFile, testKeyFile)41if err != nil {42panic(err)43}4445// only the code templates are necessary to be signed46var templatesToSign = []string{47"workflow/code-template-1.yaml",48"workflow/code-template-2.yaml",49}50for _, templatePath := range templatesToSign {51if err := templates.SignTemplate(tsigner, templatePath); err != nil {52log.Fatalf("Could not sign template %v got: %s\n", templatePath, err)53}54}55}56}5758type workflowBasic struct{}5960// Execute executes a test case and returns an error if occurred61func (h *workflowBasic) Execute(filePath string) error {62router := httprouter.New()63router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {64_, _ = fmt.Fprintf(w, "This is test matcher text")65})66ts := httptest.NewServer(router)67defer ts.Close()6869results, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)70if err != nil {71return err72}7374return expectResultsCount(results, 2)75}7677type workflowConditionMatched struct{}7879// Execute executes a test case and returns an error if occurred80func (h *workflowConditionMatched) Execute(filePath string) error {81router := httprouter.New()82router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {83_, _ = fmt.Fprintf(w, "This is test matcher text")84})85ts := httptest.NewServer(router)86defer ts.Close()8788results, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)89if err != nil {90return err91}9293return expectResultsCount(results, 1)94}9596type workflowConditionUnmatch struct{}9798// Execute executes a test case and returns an error if occurred99func (h *workflowConditionUnmatch) Execute(filePath string) error {100router := httprouter.New()101router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {102_, _ = fmt.Fprintf(w, "This is test matcher text")103})104ts := httptest.NewServer(router)105defer ts.Close()106107results, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)108if err != nil {109return err110}111112return expectResultsCount(results, 0)113}114115type workflowMatcherName struct{}116117// Execute executes a test case and returns an error if occurred118func (h *workflowMatcherName) Execute(filePath string) error {119router := httprouter.New()120router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {121_, _ = fmt.Fprintf(w, "This is test matcher text")122})123ts := httptest.NewServer(router)124defer ts.Close()125126results, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)127if err != nil {128return err129}130131return expectResultsCount(results, 1)132}133134type workflowComplexConditions struct{}135136// Execute executes a test case and returns an error if occurred137func (h *workflowComplexConditions) Execute(filePath string) error {138router := httprouter.New()139router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {140_, _ = fmt.Fprintf(w, "This is test matcher text")141})142ts := httptest.NewServer(router)143defer ts.Close()144145results, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)146if err != nil {147return err148}149150for _, result := range results {151if !strings.Contains(result, "test-matcher-3") {152return fmt.Errorf("incorrect result: the \"basic-get-third:test-matcher-3\" and only that should be matched!\nResults:\n\t%s", strings.Join(results, "\n\t"))153}154}155return expectResultsCount(results, 2)156}157158type workflowHttpKeyValueShare struct{}159160// Execute executes a test case and returns an error if occurred161func (h *workflowHttpKeyValueShare) Execute(filePath string) error {162router := httprouter.New()163router.GET("/path1", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {164_, _ = fmt.Fprintf(w, "href=\"test-value\"")165})166router.GET("/path2", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {167body, _ := io.ReadAll(r.Body)168_, _ = fmt.Fprintf(w, "%s", body)169})170ts := httptest.NewServer(router)171defer ts.Close()172173results, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)174if err != nil {175return err176}177178return expectResultsCount(results, 1)179}180181type workflowDnsKeyValueShare struct{}182183// Execute executes a test case and returns an error if occurred184func (h *workflowDnsKeyValueShare) Execute(filePath string) error {185results, err := testutils.RunNucleiWorkflowAndGetResults(filePath, "http://scanme.sh", debug)186if err != nil {187return err188}189190// no results - ensure that the variable sharing works191return expectResultsCount(results, 1)192}193194type workflowCodeKeyValueShare struct{}195196// Execute executes a test case and returns an error if occurred197func (h *workflowCodeKeyValueShare) Execute(filePath string) error {198// provide the Certificate File that the code templates are signed with199certEnvVar := signer.CertEnvVarName + "=" + testCertFile200201results, err := testutils.RunNucleiArgsWithEnvAndGetResults(debug, []string{certEnvVar}, "-workflows", filePath, "-target", "input", "-code")202if err != nil {203return err204}205206return expectResultsCount(results, 1)207}208209type workflowMultiProtocolKeyValueShare struct{}210211// Execute executes a test case and returns an error if occurred212func (h *workflowMultiProtocolKeyValueShare) Execute(filePath string) error {213router := httprouter.New()214// the response of path1 contains a domain that will be extracted and shared with the second template215router.GET("/path1", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {216_, _ = fmt.Fprintf(w, "href=\"blog.projectdiscovery.io\"")217})218// path2 responds with the value of the "extracted" query parameter, e.g.: /path2?extracted=blog.projectdiscovery.io => blog.projectdiscovery.io219router.GET("/path2", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {220_, _ = fmt.Fprintf(w, "%s", r.URL.Query().Get("extracted"))221})222ts := httptest.NewServer(router)223defer ts.Close()224225results, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)226if err != nil {227return err228}229230return expectResultsCount(results, 2)231}232233type workflowMultiMatchKeyValueShare struct{}234235// Execute executes a test case and returns an error if occurred236func (h *workflowMultiMatchKeyValueShare) Execute(filePath string) error {237var receivedData []string238router := httprouter.New()239router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {240_, _ = fmt.Fprintf(w, "This is test matcher text")241})242router.GET("/path1", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {243_, _ = fmt.Fprintf(w, "href=\"test-value-%s\"", r.URL.Query().Get("v"))244})245router.GET("/path2", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {246body, _ := io.ReadAll(r.Body)247receivedData = append(receivedData, string(body))248_, _ = fmt.Fprintf(w, "test-value")249})250ts := httptest.NewServer(router)251defer ts.Close()252253results, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)254if err != nil {255return err256}257258// Check if we received the data from both request to /path1 and it is not overwritten by the later one.259// They will appear in brackets because of another bug: https://github.com/orgs/projectdiscovery/discussions/3766260if !sliceutil.Contains(receivedData, "[test-value-1]") || !sliceutil.Contains(receivedData, "[test-value-2]") {261return fmt.Errorf(262"incorrect data: did not receive both extracted data from the first request!\nReceived Data:\n\t%s\nResults:\n\t%s",263strings.Join(receivedData, "\n\t"),264strings.Join(results, "\n\t"),265)266}267// The number of expected results is 3: the workflow's Matcher Name based condition check forwards both match, and the other branch with simple subtemplates goes with one268return expectResultsCount(results, 3)269}270271type workflowSharedCookies struct{}272273// Execute executes a test case and returns an error if occurred274func (h *workflowSharedCookies) Execute(filePath string) error {275handleFunc := func(name string, w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {276cookie := &http.Cookie{Name: name, Value: name}277http.SetCookie(w, cookie)278}279280var gotCookies []string281router := httprouter.New()282router.GET("/http1", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {283handleFunc("http1", w, r, p)284})285router.GET("/http2", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {286handleFunc("http2", w, r, p)287})288router.GET("/headless1", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {289handleFunc("headless1", w, r, p)290})291router.GET("/http3", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {292for _, cookie := range r.Cookies() {293gotCookies = append(gotCookies, cookie.Name)294}295})296ts := httptest.NewServer(router)297defer ts.Close()298299_, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug, "-headless")300if err != nil {301return err302}303304return expectResultsCount(gotCookies, 3)305}306307308