From cdc776ec035131fdf816090c15e9b3bf7f3bee34 Mon Sep 17 00:00:00 2001 From: Jordan Date: Tue, 11 Feb 2014 17:32:29 -0600 Subject: [PATCH] Implemented PUT /api/groups/:id Updated documentation of functions in db module (other modules to come) Created consistency in API documentation --- controllers/api.go | 21 ++++- db/db.go | 112 +++++++++++++++++++------ static/api-doc/resources/campaigns | 32 ++++---- static/api-doc/resources/groups | 86 +++++++++---------- static/api-doc/resources/templates | 128 ++++++++++++++--------------- static/js/app/gophish.js | 7 +- 6 files changed, 232 insertions(+), 154 deletions(-) diff --git a/controllers/api.go b/controllers/api.go index 678d29b1..21aa3438 100644 --- a/controllers/api.go +++ b/controllers/api.go @@ -185,7 +185,6 @@ func API_Groups_Id(w http.ResponseWriter, r *http.Request) { id, _ := strconv.ParseInt(vars["id"], 0, 64) switch { case r.Method == "GET": - g := models.Group{} g, err := db.GetGroup(id, ctx.Get(r, "user_id").(int64)) if checkError(err, w, "No group found") { return @@ -201,6 +200,26 @@ func API_Groups_Id(w http.ResponseWriter, r *http.Request) { return } writeJSON(w, []byte("{\"success\" : \"true\"}")) + case r.Method == "PUT": + _, err := db.GetGroup(id, ctx.Get(r, "user_id").(int64)) + if checkError(err, w, "No group found") { + return + } + g := models.Group{} + err = json.NewDecoder(r.Body).Decode(&g) + if g.Id != id { + http.Error(w, "Error: /:id and group_id mismatch", http.StatusBadRequest) + return + } + err = db.PutGroup(&g, ctx.Get(r, "user_id").(int64)) + if checkError(err, w, "Error updating group") { + return + } + gj, err := json.MarshalIndent(g, "", " ") + if checkError(err, w, "Error creating JSON response") { + return + } + writeJSON(w, gj) } } diff --git a/db/db.go b/db/db.go index a32430e9..b1443e8e 100644 --- a/db/db.go +++ b/db/db.go @@ -96,7 +96,7 @@ func GetUserByAPIKey(key []byte) (models.User, error) { return u, nil } -// GetUserByAPIKey returns the user that the given API Key corresponds to. If no user is found, an +// GetUserByUsername returns the user that the given username corresponds to. If no user is found, an // error is thrown. func GetUserByUsername(username string) (models.User, error) { u := models.User{} @@ -109,28 +109,27 @@ func GetUserByUsername(username string) (models.User, error) { return u, nil } +// PutUser updates the given user func PutUser(u *models.User) error { _, err := Conn.Update(u) return err } +// GetCampaigns returns the campaigns owned by the given user. func GetCampaigns(uid int64) ([]models.Campaign, error) { cs := []models.Campaign{} _, err := Conn.Select(&cs, "SELECT c.id, name, created_date, completed_date, status, template FROM campaigns c, users u WHERE c.uid=u.id AND u.id=?", uid) return cs, err } +// GetCampaign returns the campaign, if it exists, specified by the given id and user_id. func GetCampaign(id int64, uid int64) (models.Campaign, error) { c := models.Campaign{} err := Conn.SelectOne(&c, "SELECT c.id, name, created_date, completed_date, status, template FROM campaigns c, users u WHERE c.uid=u.id AND c.id =? AND u.id=?", id, uid) return c, err } -func PutCampaign(c *models.Campaign) error { - _, err := Conn.Update(c) - return err -} - +// GetGroups returns the groups owned by the given user. func GetGroups(uid int64) ([]models.Group, error) { gs := []models.Group{} _, err := Conn.Select(&gs, "SELECT g.id, g.name, g.modified_date FROM groups g, user_groups ug, users u WHERE ug.uid=u.id AND ug.gid=g.id AND u.id=?", uid) @@ -147,6 +146,7 @@ func GetGroups(uid int64) ([]models.Group, error) { return gs, nil } +// GetGroup returns the group, if it exists, specified by the given id and user_id. func GetGroup(id int64, uid int64) (models.Group, error) { g := models.Group{} err := Conn.SelectOne(&g, "SELECT g.id, g.name, g.modified_date FROM groups g, user_groups ug, users u WHERE ug.uid=u.id AND ug.gid=g.id AND g.id=? AND u.id=?", id, uid) @@ -161,6 +161,7 @@ func GetGroup(id int64, uid int64) (models.Group, error) { return g, nil } +// PostGroup creates a new group in the database. func PostGroup(g *models.Group, uid int64) error { // Insert into the DB err = Conn.Insert(g) @@ -168,40 +169,97 @@ func PostGroup(g *models.Group, uid int64) error { Logger.Println(err) return err } - // Let's start a transaction to handle the bulk inserting - trans, err := Conn.Begin() - if err != nil { - Logger.Println(err) - return err - } // Now, let's add the user->user_groups->group mapping _, err = Conn.Exec("INSERT OR IGNORE INTO user_groups VALUES (?,?)", uid, g.Id) if err != nil { Logger.Printf("Error adding many-many mapping for group %s\n", g.Name) } - // TODO for _, t := range g.Targets { - if _, err = mail.ParseAddress(t.Email); err != nil { - Logger.Printf("Invalid email %s\n", t.Email) - continue + insertTargetIntoGroup(t, g.Id) + } + return nil +} + +// PutGroup updates the given group if found in the database. +func PutGroup(g *models.Group, uid int64) error { + // Update all the foreign keys, and many to many relationships + // We will only delete the group->targets entries. We keep the actual targets + // since they are needed by the Results table + // Get all the targets currently in the database for the group + ts := []models.Target{} + _, err = Conn.Select(&ts, "SELECT t.id, t.email FROM targets t, group_targets gt WHERE gt.gid=? AND gt.tid=t.id", g.Id) + if err != nil { + Logger.Printf("Error getting targets from group ID: %d", g.Id) + return err + } + // Enumerate through, removing any entries that are no longer in the group + // For every target in the database + tExists := false + for _, t := range ts { + tExists = false + // Is the target still in the group? + for _, nt := range g.Targets { + if t.Email == nt.Email { + tExists = true + break + } } - _, err := Conn.Exec("INSERT OR IGNORE INTO targets VALUES (null, ?)", t.Email) - if err != nil { - Logger.Printf("Error adding email: %s\n", t.Email) + // If the target does not exist in the group any longer, we delete it + if !tExists { + _, err = Conn.Exec("DELETE FROM group_targets WHERE gid=? AND tid=?", g.Id, t.Id) + if err != nil { + Logger.Printf("Error deleting email %s\n", t.Email) + } } - // Bug: res.LastInsertId() does not work for this, so we need to select it manually (how frustrating.) - t.Id, err = Conn.SelectInt("SELECT id FROM targets WHERE email=?", t.Email) - if err != nil { - Logger.Printf("Error getting id for email: %s\n", t.Email) + } + // Insert any entries that are not in the database + // For every target in the new group + for _, nt := range g.Targets { + // Check and see if the target already exists in the db + tExists = false + for _, t := range ts { + if t.Email == nt.Email { + tExists = true + break + } } - _, err = Conn.Exec("INSERT OR IGNORE INTO group_targets VALUES (?,?)", g.Id, t.Id) - if err != nil { - Logger.Printf("Error adding many-many mapping for %s\n", t.Email) + // If the target is not in the db, we add it + if !tExists { + insertTargetIntoGroup(nt, g.Id) } } + return nil +} + +func insertTargetIntoGroup(t models.Target, gid int64) error { + if _, err = mail.ParseAddress(t.Email); err != nil { + Logger.Printf("Invalid email %s\n", t.Email) + return err + } + trans, err := Conn.Begin() + if err != nil { + Logger.Println(err) + return err + } + _, err = trans.Exec("INSERT OR IGNORE INTO targets VALUES (null, ?)", t.Email) + if err != nil { + Logger.Printf("Error adding email: %s\n", t.Email) + return err + } + // Bug: res.LastInsertId() does not work for this, so we need to select it manually (how frustrating.) + t.Id, err = trans.SelectInt("SELECT id FROM targets WHERE email=?", t.Email) + if err != nil { + Logger.Printf("Error getting id for email: %s\n", t.Email) + return err + } + _, err = trans.Exec("INSERT OR IGNORE INTO group_targets VALUES (?,?)", gid, t.Id) + if err != nil { + Logger.Printf("Error adding many-many mapping for %s\n", t.Email) + return err + } err = trans.Commit() if err != nil { - Logger.Println(err) + Logger.Printf("Error committing db changes\n") return err } return nil diff --git a/static/api-doc/resources/campaigns b/static/api-doc/resources/campaigns index f2fe9f7e..08f67d9d 100644 --- a/static/api-doc/resources/campaigns +++ b/static/api-doc/resources/campaigns @@ -35,7 +35,7 @@ }, { "method": "POST", - "summary": "Add a new campaign", + "summary": "Create campaign", "notes": "Requires an API key.", "responseClass": "Campaign", "nickname" : "addCampaigns", @@ -66,15 +66,18 @@ "path": "/campaigns/{campaignId}", "operations": [ { - "method": "DELETE", - "summary": "Deletes a campaign", - "notes": "Requires an API Key", - "responseClass": "void", - "nickname": "deleteCampaign", + "method": "GET", + "summary": "Find campaign by ID", + "notes": "Returns a campaign based on ID", + "responseClass": "Campaign", + "nickname": "getCampaignById", + "produces": [ + "application/json" + ], "parameters": [ { "name": "campaignId", - "description": "Campaign id to delete", + "description": "ID of campaign that needs to be fetched", "required": true, "allowMultiple": false, "dataType": "int64", @@ -101,18 +104,15 @@ ] }, { - "method": "GET", - "summary": "Find campaign by ID", - "notes": "Returns a campaign based on ID", - "responseClass": "Campaign", - "nickname": "getCampaignById", - "produces": [ - "application/json" - ], + "method": "DELETE", + "summary": "Delete campaign", + "notes": "Requires an API Key", + "responseClass": "void", + "nickname": "deleteCampaign", "parameters": [ { "name": "campaignId", - "description": "ID of campaign that needs to be fetched", + "description": "Campaign id to delete", "required": true, "allowMultiple": false, "dataType": "int64", diff --git a/static/api-doc/resources/groups b/static/api-doc/resources/groups index 0d2384a8..50e31159 100644 --- a/static/api-doc/resources/groups +++ b/static/api-doc/resources/groups @@ -40,9 +40,51 @@ { "path": "/groups/{group_id}", "operations": [ + { + "method": "GET", + "summary": "Find group by ID", + "notes": "", + "responseClass": "Group", + "nickname": "getGroupByID", + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "group_id", + "description": "The ID of the group that needs to be fetched.", + "required": true, + "allowMultiple": false, + "dataType": "int64", + "paramType": "path" + } + ], + "responseMessages": [ + { + "code": 400, + "message": "Invalid API Key" + }, + { + "code": 400, + "message": "API Key not set" + }, + { + "code": 400, + "message": "Invalid ID supplied" + }, + { + "code": 200, + "message": "Group fetched successfully" + }, + { + "code": 404, + "message": "Group not found" + } + ] + }, { "method": "PUT", - "summary": "Updated group", + "summary": "Update group", "notes": "Requires an API Key.", "responseClass": "void", "nickname": "updateGroup", @@ -125,48 +167,6 @@ "message": "Group not found" } ] - }, - { - "method": "GET", - "summary": "Get group by group id", - "notes": "", - "responseClass": "Group", - "nickname": "getGroupByID", - "produces": [ - "application/json" - ], - "parameters": [ - { - "name": "group_id", - "description": "The ID of the group that needs to be fetched.", - "required": true, - "allowMultiple": false, - "dataType": "int64", - "paramType": "path" - } - ], - "responseMessages": [ - { - "code": 400, - "message": "Invalid API Key" - }, - { - "code": 400, - "message": "API Key not set" - }, - { - "code": 400, - "message": "Invalid ID supplied" - }, - { - "code": 200, - "message": "Group fetched successfully" - }, - { - "code": 404, - "message": "Group not found" - } - ] } ] } diff --git a/static/api-doc/resources/templates b/static/api-doc/resources/templates index 5cbf5191..619ce821 100644 --- a/static/api-doc/resources/templates +++ b/static/api-doc/resources/templates @@ -7,6 +7,59 @@ "application/json" ], "apis": [ + { + "path": "/templates/", + "operations": [ + { + "method": "GET", + "summary": "Returns all templates for the given user", + "notes": "Requires an API Key", + "responseClass": "List[Template]", + "nickname": "getTemplates", + "responseMessages": [ + { + "code": 400, + "message": "Invalid API Key" + }, + { + "code": 404, + "message": "API Key not set" + } + ] + }, + { + "method": "POST", + "summary": "Create template", + "notes": "Requires an API Key", + "responseClass": "void", + "nickname": "createTemplate", + "parameters": [ + { + "name": "body", + "description": "Template to be added", + "required": true, + "allowMultiple": false, + "dataType": "Template", + "paramType": "body" + } + ], + "responseMessages": [ + { + "code": 400, + "message": "Invalid API Key" + }, + { + "code": 404, + "message": "API Key not set" + }, + { + "code": 400, + "message": "Invalid template supplied" + } + ] + } + ] + }, { "path": "/templates/{templateId}", "operations": [ @@ -48,44 +101,9 @@ } ] }, - { - "method": "DELETE", - "summary": "Delete template by ID", - "notes": "Requires an API Key", - "responseClass": "void", - "nickname": "deleteTemplate", - "parameters": [ - { - "name": "templateId", - "description": "ID of the template that needs to be deleted", - "required": true, - "allowMultiple": false, - "dataType": "int64", - "paramType": "path" - } - ], - "responseMessages": [ - { - "code": 400, - "message": "Invalid API Key" - }, - { - "code": 404, - "message": "API Key not set" - }, - { - "code": 400, - "message": "Invalid ID supplied" - }, - { - "code": 404, - "message": "Template not found" - } - ] - }, { "method": "PUT", - "summary": "Updates the given template", + "summary": "Update template", "notes": "Requires an API Key", "responseClass": "Template", "nickname": "putTemplate", @@ -121,26 +139,21 @@ "message": "Invalid template supplied" } ] - } - ] - }, - { - "path": "/templates/", - "operations": [ + }, { - "method": "POST", - "summary": "Create a new template", + "method": "DELETE", + "summary": "Delete template", "notes": "Requires an API Key", "responseClass": "void", - "nickname": "createTemplate", + "nickname": "deleteTemplate", "parameters": [ { - "name": "body", - "description": "Template to be added", + "name": "templateId", + "description": "ID of the template that needs to be deleted", "required": true, "allowMultiple": false, - "dataType": "Template", - "paramType": "body" + "dataType": "int64", + "paramType": "path" } ], "responseMessages": [ @@ -154,24 +167,11 @@ }, { "code": 400, - "message": "Invalid template supplied" - } - ] - }, - { - "method": "GET", - "summary": "Returns all templates owned by the given user", - "notes": "Requires an API Key", - "responseClass": "List[Template]", - "nickname": "getTemplates", - "responseMessages": [ - { - "code": 400, - "message": "Invalid API Key" + "message": "Invalid ID supplied" }, { "code": 404, - "message": "API Key not set" + "message": "Template not found" } ] } diff --git a/static/js/app/gophish.js b/static/js/app/gophish.js index 8b11c802..8056fa45 100644 --- a/static/js/app/gophish.js +++ b/static/js/app/gophish.js @@ -1,7 +1,7 @@ var app = angular.module('gophish', ['ngTable', 'ngResource']); app.factory('CampaignService', function($resource) { - return $resource('/api/campaigns/:id?api_key=' + API_KEY, {}, { + return $resource('/api/campaigns/:id?api_key=' + API_KEY, {id: "@id"}, { update: { method: 'PUT' } @@ -9,7 +9,7 @@ app.factory('CampaignService', function($resource) { }); app.factory('GroupService', function($resource) { - return $resource('/api/groups/:id?api_key=' + API_KEY, {}, { + return $resource('/api/groups/:id?api_key=' + API_KEY, {id: "@id"}, { update: { method: 'PUT' } @@ -104,7 +104,8 @@ app.controller('GroupCtrl', function($scope, GroupService, ngTableParams) { $scope.mainTableParams.reload() }); } else { - newGroup.$update() + console.log(newGroup.id) + newGroup.$update({id : newGroup.id}) } $scope.group = { name: '',