mirror of https://github.com/gophish/gophish
Moved models.Result into its own file
Added initial dashboard template (some updates and changes will be needed) Added some documentationpull/24/head
parent
0f603e6501
commit
02c7c4b5b1
|
@ -18,6 +18,8 @@ import (
|
||||||
var templateDelims = []string{"{{%", "%}}"}
|
var templateDelims = []string{"{{%", "%}}"}
|
||||||
var Logger = log.New(os.Stdout, " ", log.Ldate|log.Ltime|log.Lshortfile)
|
var Logger = log.New(os.Stdout, " ", log.Ldate|log.Ltime|log.Lshortfile)
|
||||||
|
|
||||||
|
// CreateAdminRouter creates the routes for handling requests to the web interface.
|
||||||
|
// This function returns an http.Handler to be used in http.ListenAndServe().
|
||||||
func CreateAdminRouter() http.Handler {
|
func CreateAdminRouter() http.Handler {
|
||||||
router := mux.NewRouter()
|
router := mux.NewRouter()
|
||||||
// Base Front-end routes
|
// Base Front-end routes
|
||||||
|
@ -62,7 +64,21 @@ func CreatePhishingRouter() http.Handler {
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PhishHandler handles incoming client connections and registers the associated actions performed
|
||||||
|
// (such as clicked link, etc.)
|
||||||
func PhishHandler(w http.ResponseWriter, r *http.Request) {
|
func PhishHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
r.ParseForm()
|
||||||
|
id := r.Form.Get("rid")
|
||||||
|
if id == "" {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rs, err := models.GetResult(id)
|
||||||
|
if err != nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rs.UpdateStatus("Clicked Link")
|
||||||
w.Write([]byte("It Works!"))
|
w.Write([]byte("It Works!"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,17 +47,6 @@ func (c *Campaign) AddEvent (e Event) error {
|
||||||
return db.Debug().Save(&e).Error
|
return db.Debug().Save(&e).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result struct {
|
|
||||||
Id int64 `json:"-"`
|
|
||||||
CampaignId int64 `json:"-"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
Status string `json:"status" sql:"not null"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Result) UpdateStatus(s string) error {
|
|
||||||
return db.Debug().Table("results").Where("id=?", r.Id).Update("status", s).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
type Event struct {
|
type Event struct {
|
||||||
Id int64 `json:"-"`
|
Id int64 `json:"-"`
|
||||||
CampaignId int64 `json:"-"`
|
CampaignId int64 `json:"-"`
|
||||||
|
@ -115,7 +104,7 @@ func PostCampaign(c *Campaign, uid int64) error {
|
||||||
c.UserId = uid
|
c.UserId = uid
|
||||||
c.CreatedDate = time.Now()
|
c.CreatedDate = time.Now()
|
||||||
c.CompletedDate = time.Time{}
|
c.CompletedDate = time.Time{}
|
||||||
c.Status = QUEUED
|
c.Status = CAMPAIGN_QUEUED
|
||||||
// Check to make sure all the groups already exist
|
// Check to make sure all the groups already exist
|
||||||
for i, g := range c.Groups {
|
for i, g := range c.Groups {
|
||||||
c.Groups[i], err = GetGroupByName(g.Name, uid)
|
c.Groups[i], err = GetGroupByName(g.Name, uid)
|
||||||
|
@ -153,7 +142,8 @@ func PostCampaign(c *Campaign, uid int64) error {
|
||||||
// Insert a result for each target in the group
|
// Insert a result for each target in the group
|
||||||
for _, t := range g.Targets {
|
for _, t := range g.Targets {
|
||||||
r := Result{Email: t.Email, Status: "Unknown", CampaignId: c.Id}
|
r := Result{Email: t.Email, Status: "Unknown", CampaignId: c.Id}
|
||||||
err := db.Save(&r).Error
|
r.GenerateId()
|
||||||
|
err = db.Save(&r).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Logger.Printf("Error adding result record for target %s\n", t.Email)
|
Logger.Printf("Error adding result record for target %s\n", t.Email)
|
||||||
Logger.Println(err)
|
Logger.Println(err)
|
||||||
|
|
|
@ -18,9 +18,12 @@ var ErrUsernameTaken = errors.New("username already taken")
|
||||||
var Logger = log.New(os.Stdout, " ", log.Ldate|log.Ltime|log.Lshortfile)
|
var Logger = log.New(os.Stdout, " ", log.Ldate|log.Ltime|log.Lshortfile)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
IN_PROGRESS string = "In progress"
|
CAMPAIGN_IN_PROGRESS string = "In progress"
|
||||||
QUEUED string = "Queued"
|
CAMPAIGN_QUEUED string = "Queued"
|
||||||
COMPLETE string = "Completed"
|
CAMPAIGN_COMPLETE string = "Completed"
|
||||||
|
STATUS_SENT string = "Email Sent"
|
||||||
|
STATUS_OPENED string = "Email Opened"
|
||||||
|
STATUS_CLICKED string = "Clicked Link"
|
||||||
ERROR string = "Error"
|
ERROR string = "Error"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Result struct {
|
||||||
|
Id int64 `json:"-"`
|
||||||
|
CampaignId int64 `json:"-"`
|
||||||
|
RId string `json:"id"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Status string `json:"status" sql:"not null"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Result) UpdateStatus(s string) error {
|
||||||
|
return db.Table("results").Where("id=?", r.Id).Update("status", s).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Result) GenerateId() {
|
||||||
|
// Keep trying until we generate a unique key (shouldn't take more than one or two iterations)
|
||||||
|
k := make([]byte, 32)
|
||||||
|
for {
|
||||||
|
io.ReadFull(rand.Reader, k)
|
||||||
|
r.RId = fmt.Sprintf("%x", k)
|
||||||
|
err := db.Table("results").Where("id=?", r.RId).First(&Result{}).Error
|
||||||
|
if err == gorm.RecordNotFound {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetResult(rid string) (Result, error) {
|
||||||
|
r := Result{}
|
||||||
|
err := db.Where("r_id=?", rid).First(&r).Error
|
||||||
|
return r, err
|
||||||
|
}
|
|
@ -5,8 +5,8 @@ app.config(function($routeProvider) {
|
||||||
|
|
||||||
// route for the home page
|
// route for the home page
|
||||||
.when('/', {
|
.when('/', {
|
||||||
templateUrl: 'js/app/partials/campaigns.html',
|
templateUrl: 'js/app/partials/dashboard.html',
|
||||||
controller: 'CampaignCtrl'
|
controller: 'DashboardCtrl'
|
||||||
})
|
})
|
||||||
|
|
||||||
.when('/campaigns', {
|
.when('/campaigns', {
|
||||||
|
|
|
@ -1,3 +1,73 @@
|
||||||
|
app.controller('DashboardCtrl', function($scope, CampaignService, ngTableParams, $http) {
|
||||||
|
$scope.mainTableParams = new ngTableParams({
|
||||||
|
page: 1, // show first page
|
||||||
|
count: 10, // count per page
|
||||||
|
sorting: {
|
||||||
|
name: 'asc' // initial sorting
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
total: 0, // length of data
|
||||||
|
getData: function($defer, params) {
|
||||||
|
CampaignService.query(function(campaigns) {
|
||||||
|
$scope.campaigns = campaigns
|
||||||
|
var campaign_series = []
|
||||||
|
angular.forEach(campaigns, function(campaign, key) {
|
||||||
|
campaign_series.push({
|
||||||
|
y: 0
|
||||||
|
})
|
||||||
|
angular.forEach(campaign.results, function(result, r_key) {
|
||||||
|
if (result.status == "Clicked Link") {
|
||||||
|
campaign_series[campaign_series.length - 1].y++;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
campaign_series[campaign_series.length - 1].y = Math.floor((campaign_series[campaign_series.length - 1].y / campaign.results.length)*100)
|
||||||
|
console.log(campaign_series)
|
||||||
|
});
|
||||||
|
$scope.overview_chart = {
|
||||||
|
options: {
|
||||||
|
chart: {
|
||||||
|
type: 'area'
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
formatter: function() {
|
||||||
|
return "Successful Phishes: " + this.point.y + "%"
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
padding: 10,
|
||||||
|
fontWeight: 'bold'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
pie: {
|
||||||
|
allowPointSelect: true,
|
||||||
|
cursor: 'pointer',
|
||||||
|
dataLabels: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
showInLegend: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: [{
|
||||||
|
data: campaign_series
|
||||||
|
}],
|
||||||
|
title: {
|
||||||
|
text: 'Phishing Success Overview'
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
height: 300
|
||||||
|
},
|
||||||
|
credits: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
}
|
||||||
|
params.total(Math.min(campaigns.length, 5));
|
||||||
|
$defer.resolve(campaigns.slice(0, params.total()));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
app.controller('CampaignCtrl', function($scope, $modal, CampaignService, GroupService, TemplateService, ngTableParams, $http) {
|
app.controller('CampaignCtrl', function($scope, $modal, CampaignService, GroupService, TemplateService, ngTableParams, $http) {
|
||||||
$scope.flashes = []
|
$scope.flashes = []
|
||||||
$scope.mainTableParams = new ngTableParams({
|
$scope.mainTableParams = new ngTableParams({
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-3 col-md-2 sidebar">
|
||||||
|
<ul class="nav nav-sidebar">
|
||||||
|
<li class="active"><a href="#">Dashboard</a>
|
||||||
|
</li>
|
||||||
|
<li><a href="#/campaigns">Campaigns</a>
|
||||||
|
</li>
|
||||||
|
<li><a href="#/users">Users & Groups</a>
|
||||||
|
</li>
|
||||||
|
<li><a href="#/templates">Templates</a>
|
||||||
|
</li>
|
||||||
|
<li><a href="#/settings">Settings</a>
|
||||||
|
</li>
|
||||||
|
<li><a href="/api/">API Documentation</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main" ng-controller="DashboardCtrl">
|
||||||
|
<h1 class="page-header">
|
||||||
|
Dashboard
|
||||||
|
</h1>
|
||||||
|
<div class="row">
|
||||||
|
<div ng-repeat="flash in flashes" style="text-align:center" class="alert alert-{{flash.type}}">
|
||||||
|
<i class="fa {{flash.icon}}"></i> {{flash.message}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
|
||||||
|
<highchart config="overview_chart"></highchart>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<h2>Recent Campaigns</h2>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<a href="#/campaigns"><button type="button" class="btn btn-primary">View All</button></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<table ng-table="mainTableParams" class="table table-hover table-striped table-bordered">
|
||||||
|
<tbody>
|
||||||
|
<tr ng-repeat="campaign in $data" class="editable-row">
|
||||||
|
<td data-title="'Created Date'" class="col-sm-1">{{campaign.created_date | date:'medium'}}</td>
|
||||||
|
<td data-title="'Name'" class="col-sm-2">{{campaign.name}}
|
||||||
|
<div class="btn-group" style="float: right;">
|
||||||
|
<button type="button" class="btn btn-primary dropdown-toggle edit-button" data-toggle="dropdown">
|
||||||
|
<span class="caret" style="border-top-color:#FFFFFF"></span>
|
||||||
|
<span class="sr-only">Toggle Dropdown</span>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu" style="left:auto; right:0;" role="menu">
|
||||||
|
<li><a ng-href="#/campaigns/{{campaign.id}}">View</a>
|
||||||
|
</li>
|
||||||
|
<li><a href="/campaigns/{{campaign.id}}/relaunch">Relaunch</a>
|
||||||
|
</li>
|
||||||
|
<li class="divider"></li>
|
||||||
|
<li><a ng-click="deleteCampaign(campaign)" ng-href="#">Delete</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td data-title="'Status'" class="col-sm-1">{{campaign.status}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -35,7 +35,7 @@ func (w *Worker) Start() {
|
||||||
|
|
||||||
func processCampaign(c *models.Campaign) {
|
func processCampaign(c *models.Campaign) {
|
||||||
Logger.Printf("Worker received: %s", c.Name)
|
Logger.Printf("Worker received: %s", c.Name)
|
||||||
err := c.UpdateStatus(models.IN_PROGRESS)
|
err := c.UpdateStatus(models.CAMPAIGN_IN_PROGRESS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Logger.Println(err)
|
Logger.Println(err)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue