package controllers import ( "bytes" "encoding/json" "fmt" "io/ioutil" "net/http" "net/url" "reflect" "testing" "github.com/gophish/gophish/config" "github.com/gophish/gophish/models" ) func getFirstCampaign(t *testing.T) models.Campaign { campaigns, err := models.GetCampaigns(1) if err != nil { t.Fatalf("error getting first campaign from database: %v", err) } return campaigns[0] } func getFirstEmailRequest(t *testing.T) models.EmailRequest { campaign := getFirstCampaign(t) req := models.EmailRequest{ TemplateId: campaign.TemplateId, Template: campaign.Template, PageId: campaign.PageId, Page: campaign.Page, URL: "http://localhost.localdomain", UserId: 1, BaseRecipient: campaign.Results[0].BaseRecipient, SMTP: campaign.SMTP, FromAddress: campaign.SMTP.FromAddress, } err := models.PostEmailRequest(&req) if err != nil { t.Fatalf("error creating email request: %v", err) } return req } func openEmail(t *testing.T, ctx *testContext, rid string) { resp, err := http.Get(fmt.Sprintf("%s/track?%s=%s", ctx.phishServer.URL, models.RecipientParameter, rid)) if err != nil { t.Fatalf("error requesting /track endpoint: %v", err) } defer resp.Body.Close() got, err := ioutil.ReadAll(resp.Body) if err != nil { t.Fatalf("error reading response body from /track endpoint: %v", err) } expected, err := ioutil.ReadFile("static/images/pixel.png") if err != nil { t.Fatalf("error reading local transparent pixel: %v", err) } if !bytes.Equal(got, expected) { t.Fatalf("unexpected tracking pixel data received. expected %#v got %#v", expected, got) } } func openEmail404(t *testing.T, ctx *testContext, rid string) { resp, err := http.Get(fmt.Sprintf("%s/track?%s=%s", ctx.phishServer.URL, models.RecipientParameter, rid)) if err != nil { t.Fatalf("error requesting /track endpoint: %v", err) } defer resp.Body.Close() got := resp.StatusCode expected := http.StatusNotFound if got != expected { t.Fatalf("invalid status code received for /track endpoint. expected %d got %d", expected, got) } } func reportedEmail(t *testing.T, ctx *testContext, rid string) { resp, err := http.Get(fmt.Sprintf("%s/report?%s=%s", ctx.phishServer.URL, models.RecipientParameter, rid)) if err != nil { t.Fatalf("error requesting /report endpoint: %v", err) } got := resp.StatusCode expected := http.StatusNoContent if got != expected { t.Fatalf("invalid status code received for /report endpoint. expected %d got %d", expected, got) } } func reportEmail404(t *testing.T, ctx *testContext, rid string) { resp, err := http.Get(fmt.Sprintf("%s/report?%s=%s", ctx.phishServer.URL, models.RecipientParameter, rid)) if err != nil { t.Fatalf("error requesting /report endpoint: %v", err) } got := resp.StatusCode expected := http.StatusNotFound if got != expected { t.Fatalf("invalid status code received for /report endpoint. expected %d got %d", expected, got) } } func clickLink(t *testing.T, ctx *testContext, rid string, expectedHTML string) { resp, err := http.Get(fmt.Sprintf("%s/?%s=%s", ctx.phishServer.URL, models.RecipientParameter, rid)) if err != nil { t.Fatalf("error requesting / endpoint: %v", err) } defer resp.Body.Close() got, err := ioutil.ReadAll(resp.Body) if err != nil { t.Fatalf("error reading payload from / endpoint response: %v", err) } if !bytes.Equal(got, []byte(expectedHTML)) { t.Fatalf("invalid response received from / endpoint. expected %s got %s", got, expectedHTML) } } func clickLink404(t *testing.T, ctx *testContext, rid string) { resp, err := http.Get(fmt.Sprintf("%s/?%s=%s", ctx.phishServer.URL, models.RecipientParameter, rid)) if err != nil { t.Fatalf("error requesting / endpoint: %v", err) } defer resp.Body.Close() got := resp.StatusCode expected := http.StatusNotFound if got != expected { t.Fatalf("invalid status code received for / endpoint. expected %d got %d", expected, got) } } func transparencyRequest(t *testing.T, ctx *testContext, r models.Result, rid, path string) { resp, err := http.Get(fmt.Sprintf("%s%s?%s=%s", ctx.phishServer.URL, path, models.RecipientParameter, rid)) if err != nil { t.Fatalf("error requesting %s endpoint: %v", path, err) } defer resp.Body.Close() got := resp.StatusCode expected := http.StatusOK if got != expected { t.Fatalf("invalid status code received for / endpoint. expected %d got %d", expected, got) } tr := &TransparencyResponse{} err = json.NewDecoder(resp.Body).Decode(tr) if err != nil { t.Fatalf("error unmarshaling transparency request: %v", err) } expectedResponse := &TransparencyResponse{ ContactAddress: ctx.config.ContactAddress, SendDate: r.SendDate, Server: config.ServerName, } if !reflect.DeepEqual(tr, expectedResponse) { t.Fatalf("unexpected transparency response received. expected %v got %v", expectedResponse, tr) } } func TestOpenedPhishingEmail(t *testing.T) { ctx := setupTest(t) defer tearDown(t, ctx) campaign := getFirstCampaign(t) result := campaign.Results[0] if result.Status != models.StatusSending { t.Fatalf("unexpected result status received. expected %s got %s", models.StatusSending, result.Status) } openEmail(t, ctx, result.RId) campaign = getFirstCampaign(t) result = campaign.Results[0] lastEvent := campaign.Events[len(campaign.Events)-1] if result.Status != models.EventOpened { t.Fatalf("unexpected result status received. expected %s got %s", models.EventOpened, result.Status) } if lastEvent.Message != models.EventOpened { t.Fatalf("unexpected event status received. expected %s got %s", lastEvent.Message, models.EventOpened) } if result.ModifiedDate != lastEvent.Time { t.Fatalf("unexpected result modified date received. expected %s got %s", lastEvent.Time, result.ModifiedDate) } } func TestReportedPhishingEmail(t *testing.T) { ctx := setupTest(t) defer tearDown(t, ctx) campaign := getFirstCampaign(t) result := campaign.Results[0] if result.Status != models.StatusSending { t.Fatalf("unexpected result status received. expected %s got %s", models.StatusSending, result.Status) } reportedEmail(t, ctx, result.RId) campaign = getFirstCampaign(t) result = campaign.Results[0] lastEvent := campaign.Events[len(campaign.Events)-1] if result.Reported != true { t.Fatalf("unexpected result report status received. expected %v got %v", true, result.Reported) } if lastEvent.Message != models.EventReported { t.Fatalf("unexpected event status received. expected %s got %s", lastEvent.Message, models.EventReported) } if result.ModifiedDate != lastEvent.Time { t.Fatalf("unexpected result modified date received. expected %s got %s", lastEvent.Time, result.ModifiedDate) } } func TestClickedPhishingLinkAfterOpen(t *testing.T) { ctx := setupTest(t) defer tearDown(t, ctx) campaign := getFirstCampaign(t) result := campaign.Results[0] if result.Status != models.StatusSending { t.Fatalf("unexpected result status received. expected %s got %s", models.StatusSending, result.Status) } openEmail(t, ctx, result.RId) clickLink(t, ctx, result.RId, campaign.Page.HTML) campaign = getFirstCampaign(t) result = campaign.Results[0] lastEvent := campaign.Events[len(campaign.Events)-1] if result.Status != models.EventClicked { t.Fatalf("unexpected result status received. expected %s got %s", models.EventClicked, result.Status) } if lastEvent.Message != models.EventClicked { t.Fatalf("unexpected event status received. expected %s got %s", lastEvent.Message, models.EventClicked) } if result.ModifiedDate != lastEvent.Time { t.Fatalf("unexpected result modified date received. expected %s got %s", lastEvent.Time, result.ModifiedDate) } } func TestNoRecipientID(t *testing.T) { ctx := setupTest(t) defer tearDown(t, ctx) resp, err := http.Get(fmt.Sprintf("%s/track", ctx.phishServer.URL)) if err != nil { t.Fatalf("error requesting /track endpoint: %v", err) } got := resp.StatusCode expected := http.StatusNotFound if got != expected { t.Fatalf("invalid status code received for /track endpoint. expected %d got %d", expected, got) } resp, err = http.Get(ctx.phishServer.URL) if err != nil { t.Fatalf("error requesting /track endpoint: %v", err) } got = resp.StatusCode if got != expected { t.Fatalf("invalid status code received for / endpoint. expected %d got %d", expected, got) } } func TestInvalidRecipientID(t *testing.T) { ctx := setupTest(t) defer tearDown(t, ctx) rid := "XXXXXXXXXX" openEmail404(t, ctx, rid) clickLink404(t, ctx, rid) reportEmail404(t, ctx, rid) } func TestCompletedCampaignClick(t *testing.T) { ctx := setupTest(t) defer tearDown(t, ctx) campaign := getFirstCampaign(t) result := campaign.Results[0] if result.Status != models.StatusSending { t.Fatalf("unexpected result status received. expected %s got %s", models.StatusSending, result.Status) } openEmail(t, ctx, result.RId) campaign = getFirstCampaign(t) result = campaign.Results[0] if result.Status != models.EventOpened { t.Fatalf("unexpected result status received. expected %s got %s", models.EventOpened, result.Status) } models.CompleteCampaign(campaign.Id, 1) openEmail404(t, ctx, result.RId) clickLink404(t, ctx, result.RId) campaign = getFirstCampaign(t) result = campaign.Results[0] if result.Status != models.EventOpened { t.Fatalf("unexpected result status received. expected %s got %s", models.EventOpened, result.Status) } } func TestRobotsHandler(t *testing.T) { ctx := setupTest(t) defer tearDown(t, ctx) resp, err := http.Get(fmt.Sprintf("%s/robots.txt", ctx.phishServer.URL)) if err != nil { t.Fatalf("error requesting /robots.txt endpoint: %v", err) } defer resp.Body.Close() got := resp.StatusCode expectedStatus := http.StatusOK if got != expectedStatus { t.Fatalf("invalid status code received for /track endpoint. expected %d got %d", expectedStatus, got) } expected := []byte("User-agent: *\nDisallow: /\n") body, err := ioutil.ReadAll(resp.Body) if err != nil { t.Fatalf("error reading response body from /robots.txt endpoint: %v", err) } if !bytes.Equal(body, expected) { t.Fatalf("invalid robots.txt response received. expected %s got %s", expected, body) } } func TestInvalidPreviewID(t *testing.T) { ctx := setupTest(t) defer tearDown(t, ctx) bogusRId := fmt.Sprintf("%sbogus", models.PreviewPrefix) openEmail404(t, ctx, bogusRId) clickLink404(t, ctx, bogusRId) reportEmail404(t, ctx, bogusRId) } func TestPreviewTrack(t *testing.T) { ctx := setupTest(t) defer tearDown(t, ctx) req := getFirstEmailRequest(t) openEmail(t, ctx, req.RId) } func TestPreviewClick(t *testing.T) { ctx := setupTest(t) defer tearDown(t, ctx) req := getFirstEmailRequest(t) clickLink(t, ctx, req.RId, req.Page.HTML) } func TestInvalidTransparencyRequest(t *testing.T) { ctx := setupTest(t) defer tearDown(t, ctx) bogusRId := fmt.Sprintf("bogus%s", TransparencySuffix) openEmail404(t, ctx, bogusRId) clickLink404(t, ctx, bogusRId) reportEmail404(t, ctx, bogusRId) } func TestTransparencyRequest(t *testing.T) { ctx := setupTest(t) defer tearDown(t, ctx) campaign := getFirstCampaign(t) result := campaign.Results[0] rid := fmt.Sprintf("%s%s", result.RId, TransparencySuffix) transparencyRequest(t, ctx, result, rid, "/") transparencyRequest(t, ctx, result, rid, "/track") transparencyRequest(t, ctx, result, rid, "/report") // And check with the URL encoded version of a + rid = fmt.Sprintf("%s%s", result.RId, "%2b") transparencyRequest(t, ctx, result, rid, "/") transparencyRequest(t, ctx, result, rid, "/track") transparencyRequest(t, ctx, result, rid, "/report") } func TestRedirectTemplating(t *testing.T) { ctx := setupTest(t) defer tearDown(t, ctx) p := models.Page{ Name: "Redirect Page", HTML: "Test", UserId: 1, RedirectURL: "http://example.com/{{.RId}}", } err := models.PostPage(&p) if err != nil { t.Fatalf("error posting new page: %v", err) } smtp, _ := models.GetSMTP(1, 1) template, _ := models.GetTemplate(1, 1) group, _ := models.GetGroup(1, 1) campaign := models.Campaign{Name: "Redirect campaign"} campaign.UserId = 1 campaign.Template = template campaign.Page = p campaign.SMTP = smtp campaign.Groups = []models.Group{group} err = models.PostCampaign(&campaign, campaign.UserId) if err != nil { t.Fatalf("error creating campaign: %v", err) } client := http.Client{ CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, } result := campaign.Results[0] resp, err := client.PostForm(fmt.Sprintf("%s/?%s=%s", ctx.phishServer.URL, models.RecipientParameter, result.RId), url.Values{"username": {"test"}, "password": {"test"}}) if err != nil { t.Fatalf("error requesting / endpoint: %v", err) } defer resp.Body.Close() got := resp.StatusCode expectedStatus := http.StatusFound if got != expectedStatus { t.Fatalf("invalid status code received for /track endpoint. expected %d got %d", expectedStatus, got) } expectedURL := fmt.Sprintf("http://example.com/%s", result.RId) gotURL, err := resp.Location() if err != nil { t.Fatalf("error getting Location header from response: %v", err) } if gotURL.String() != expectedURL { t.Fatalf("invalid redirect received. expected %s got %s", expectedURL, gotURL) } }