diff --git a/controllers/api.go b/controllers/api.go index 6adb8c17..c917af68 100644 --- a/controllers/api.go +++ b/controllers/api.go @@ -256,6 +256,79 @@ func API_Templates_Id(w http.ResponseWriter, r *http.Request) { } } +// API_Pages handles requests for the /api/pages/ endpoint +func API_Pages(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == "GET": + ps, err := models.GetPages(ctx.Get(r, "user_id").(int64)) + if checkError(err, w, "Pages not found", http.StatusNotFound) { + return + } + JSONResponse(w, ps, http.StatusOK) + //POST: Create a new page and return it as JSON + case r.Method == "POST": + p := models.Page{} + // Put the request into a page + err := json.NewDecoder(r.Body).Decode(&p) + if checkError(err, w, "Invalid Request", http.StatusBadRequest) { + return + } + _, err = models.GetPageByName(p.Name, ctx.Get(r, "user_id").(int64)) + if err != gorm.RecordNotFound { + JSONResponse(w, models.Response{Success: false, Message: "Template name already in use"}, http.StatusConflict) + return + } + p.ModifiedDate = time.Now() + p.UserId = ctx.Get(r, "user_id").(int64) + err = models.PostPage(&p) + if checkError(err, w, "Error inserting page", http.StatusInternalServerError) { + return + } + JSONResponse(w, p, http.StatusCreated) + } +} + +func API_Pages_Id(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + id, _ := strconv.ParseInt(vars["id"], 0, 64) + p, err := models.GetPage(id, ctx.Get(r, "user_id").(int64)) + if checkError(err, w, "Page not found", http.StatusNotFound) { + Logger.Println(err) + return + } + switch { + case r.Method == "GET": + JSONResponse(w, p, http.StatusOK) + case r.Method == "DELETE": + err = models.DeletePage(id, ctx.Get(r, "user_id").(int64)) + if checkError(err, w, "Error deleting page", http.StatusInternalServerError) { + return + } + JSONResponse(w, models.Response{Success: true, Message: "Page Deleted Successfully"}, http.StatusOK) + case r.Method == "PUT": + p = models.Page{} + err = json.NewDecoder(r.Body).Decode(&p) + if err != nil { + Logger.Println(err) + } + if p.Id != id { + http.Error(w, "Error: /:id and template_id mismatch", http.StatusBadRequest) + return + } + err = p.Validate() + /* if checkError(err, w, http.StatusBadRequest) { + return + }*/ + p.ModifiedDate = time.Now() + p.UserId = ctx.Get(r, "user_id").(int64) + err = models.PutPage(&p) + if checkError(err, w, "Error updating group", http.StatusInternalServerError) { + return + } + JSONResponse(w, p, http.StatusOK) + } +} + func API_Import_Group(w http.ResponseWriter, r *http.Request) { Logger.Println("Parsing CSV....") ts, err := util.ParseCSV(r) diff --git a/controllers/route.go b/controllers/route.go index b493a055..73211ca9 100644 --- a/controllers/route.go +++ b/controllers/route.go @@ -39,6 +39,8 @@ func CreateAdminRouter() http.Handler { api.HandleFunc("/groups/{id:[0-9]+}", Use(API_Groups_Id, mid.RequireAPIKey)) api.HandleFunc("/templates/", Use(API_Templates, mid.RequireAPIKey)) api.HandleFunc("/templates/{id:[0-9]+}", Use(API_Templates_Id, mid.RequireAPIKey)) + api.HandleFunc("/pages/", Use(API_Pages, mid.RequireAPIKey)) + api.HandleFunc("/pages/{id:[0-9]+}", Use(API_Pages_Id, mid.RequireAPIKey)) api.HandleFunc("/import/group", API_Import_Group) // Setup static file serving diff --git a/models/models.go b/models/models.go index 10307cd3..764798c8 100644 --- a/models/models.go +++ b/models/models.go @@ -8,13 +8,18 @@ import ( "github.com/coopernurse/gorp" "github.com/jinzhu/gorm" "github.com/jordan-wright/gophish/config" - _ "github.com/mattn/go-sqlite3" + _ "github.com/mattn/go-sqlite3" // Blank import needed to import sqlite3 ) +// Conn is the connection to the SQLite database var Conn *gorp.DbMap var db gorm.DB var err error + +// ErrUsernameTaken is thrown when a user attempts to register a username that is taken. var ErrUsernameTaken = errors.New("username already taken") + +// Logger is a global logger used to show informational, warning, and error messages var Logger = log.New(os.Stdout, " ", log.Ldate|log.Ltime|log.Lshortfile) const ( @@ -35,6 +40,7 @@ type Flash struct { Message string } +// Response contains the attributes found in an API response type Response struct { Message string `json:"message"` Success bool `json:"success"` @@ -62,16 +68,17 @@ func Setup() error { db.CreateTable(GroupTarget{}) db.CreateTable(Template{}) db.CreateTable(Attachment{}) + db.CreateTable(Page{}) db.CreateTable(SMTP{}) db.CreateTable(Event{}) db.CreateTable(Campaign{}) //Create the default user - init_user := User{ + initUser := User{ Username: "admin", Hash: "$2a$10$IYkPp0.QsM81lYYPrQx6W.U6oQGw7wMpozrKhKAHUBVL4mkm/EvAS", //gophish ApiKey: "12345678901234567890123456789012", } - err = db.Save(&init_user).Error + err = db.Save(&initUser).Error if err != nil { Logger.Println(err) } diff --git a/models/page.go b/models/page.go new file mode 100644 index 00000000..a2383d19 --- /dev/null +++ b/models/page.go @@ -0,0 +1,87 @@ +package models + +import ( + "errors" + "time" +) + +// Page contains the fields used for a Page model +type Page struct { + Id int64 `json:"id"` + UserId int64 `json:"-"` + Name string `json:"name"` + HTML string `json:"html"` + ModifiedDate time.Time `json:"modified_date"` +} + +// ErrPageNameNotSpecified is thrown if the name of the landing page is blank. +var ErrPageNameNotSpecified = errors.New("Template Name not specified") + +// Validate ensures that a page contains the appropriate details +func (p *Page) Validate() error { + if p.Name == "" { + return ErrPageNameNotSpecified + } + return nil +} + +// GetPages returns the pages owned by the given user. +func GetPages(uid int64) ([]Page, error) { + ps := []Page{} + err := db.Where("user_id=?", uid).Find(&ps).Error + if err != nil { + Logger.Println(err) + return ps, err + } + return ps, err +} + +// GetPage returns the page, if it exists, specified by the given id and user_id. +func GetPage(id int64, uid int64) (Page, error) { + p := Page{} + err := db.Where("user_id=? and id=?", uid, id).Find(&p).Error + if err != nil { + Logger.Println(err) + } + return p, err +} + +// GetPageByName returns the page, if it exists, specified by the given name and user_id. +func GetPageByName(n string, uid int64) (Page, error) { + p := Page{} + err := db.Where("user_id=? and name=?", uid, n).Find(&p).Error + if err != nil { + Logger.Println(err) + } + return p, nil +} + +// PostPage creates a new page in the database. +func PostPage(p *Page) error { + // Insert into the DB + err := db.Save(p).Error + if err != nil { + Logger.Println(err) + } + return err +} + +// PutPage edits an existing Page in the database. +// Per the PUT Method RFC, it presumes all data for a page is provided. +func PutPage(p *Page) error { + err := db.Debug().Where("id=?", p.Id).Save(p).Error + if err != nil { + Logger.Println(err) + } + return err +} + +// DeletePage deletes an existing page in the database. +// An error is returned if a page with the given user id and page id is not found. +func DeletePage(id int64, uid int64) error { + err = db.Where("user_id=?", uid).Delete(Page{Id: id}).Error + if err != nil { + Logger.Println(err) + } + return err +} diff --git a/static/js/app/app.js b/static/js/app/app.js index 23e391b8..9915d45e 100644 --- a/static/js/app/app.js +++ b/static/js/app/app.js @@ -29,6 +29,11 @@ app.config(function($routeProvider) { controller: 'TemplateCtrl' }) + .when('/landing_pages', { + templateUrl: 'js/app/partials/landing_pages.html', + controller: 'LandingPageCtrl' + }) + .when('/settings', { templateUrl: 'js/app/partials/settings.html', controller: 'SettingsCtrl' diff --git a/static/js/app/controllers.js b/static/js/app/controllers.js index 9097615d..d1bdf052 100644 --- a/static/js/app/controllers.js +++ b/static/js/app/controllers.js @@ -684,6 +684,109 @@ var TemplateModalCtrl = function($scope, $upload, $modalInstance) { } }; +app.controller('LandingPageCtrl', function($scope, $modal, LandingPageService, ngTableParams) { + $scope.errorFlash = function(message) { + $scope.flashes = []; + $scope.flashes.push({ + "type": "danger", + "message": message, + "icon": "fa-exclamation-circle" + }) + } + + $scope.successFlash = function(message) { + $scope.flashes = []; + $scope.flashes.push({ + "type": "success", + "message": message, + "icon": "fa-check-circle" + }) + } + + $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) { + LandingPageService.query(function(pages) { + $scope.pages = pages + params.total(pages.length) + $defer.resolve(pages.slice((params.page() - 1) * params.count(), params.page() * params.count())); + }) + } + }); + + $scope.editPage = function(page) { + if (page === 'new') { + $scope.newPage = true; + $scope.page = { + name: '', + html: '', + }; + + } else { + $scope.newPage = false; + $scope.page = page; + } + var modalInstance = $modal.open({ + templateUrl: '/js/app/partials/modals/LandingPageModal.html', + controller: LandingPageModalCtrl, + scope: $scope + }); + + modalInstance.result.then(function(selectedItem) { + $scope.selected = selectedItem; + }, function() { + console.log('closed') + }); + }; + + $scope.savePage = function(page) { + var newPage = new LandingPageService(page); + if ($scope.newPage) { + newPage.$save({}, function() { + $scope.pages.push(newPage); + $scope.mainTableParams.reload() + }); + } else { + newPage.$update({ + id: newPage.id + }) + } + $scope.page = { + name: '', + html: '', + }; + } + $scope.deletePage = function(page) { + var deletePage = new LandingPageService(page); + deletePage.$delete({ + id: deletePage.id + }, function(response) { + if (response.success) { + $scope.successFlash(response.message) + } else { + $scope.errorFlash(response.message) + } + $scope.mainTableParams.reload(); + }); + } +}); + +var LandingPageModalCtrl = function($scope, $modalInstance) { + $scope.cancel = function() { + $modalInstance.dismiss('cancel'); + }; + $scope.ok = function(page) { + $modalInstance.dismiss('') + $scope.savePage(page) + }; +}; + app.controller('SettingsCtrl', function($scope, $http, $window) { $scope.flashes = []; $scope.user = user; diff --git a/static/js/app/factories.js b/static/js/app/factories.js index f8b54fcf..9b2a9d92 100644 --- a/static/js/app/factories.js +++ b/static/js/app/factories.js @@ -26,4 +26,14 @@ app.factory('TemplateService', function($resource) { method: 'PUT' } }); -}); \ No newline at end of file +}); + +app.factory('LandingPageService', function($resource) { + return $resource('/api/pages/:id?api_key=' + user.api_key, { + id : "@id" + }, { + update: { + method: 'PUT' + } + }); +}); diff --git a/static/js/app/partials/campaign_results.html b/static/js/app/partials/campaign_results.html index aa31a503..a55afa84 100644 --- a/static/js/app/partials/campaign_results.html +++ b/static/js/app/partials/campaign_results.html @@ -8,7 +8,9 @@