54 changed files with 1329 additions and 51 deletions
@ -0,0 +1,11 @@ |
|||
Copyright 2019 Samuel Pua. All rights reserved. |
|||
|
|||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: |
|||
|
|||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. |
|||
|
|||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. |
|||
|
|||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. |
|||
|
|||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,29 @@ |
|||
# Tap It - Text Phishing Framework |
|||
|
|||
Tap It is a SMS phishing framework that allows handling of large SMS phishing campaigns. It automatically handles text template and allows basic handling of web template should phishing URLs be sent through the text. |
|||
|
|||
### Prerequisites |
|||
|
|||
Tap It is built on Go, and have all required libraries built-in the binary. To attempt to recompile the application, the following is required: |
|||
* Go |
|||
* Angular CLI |
|||
* GORM |
|||
* Gorilla Mux |
|||
* Teabag XLSX Library |
|||
|
|||
## Deployment |
|||
|
|||
The entire application is designed to be Dockerised. To build the Docker environment with the pre-compiled binaries, simple run the following: |
|||
``` |
|||
sudo docker-compose up |
|||
``` |
|||
|
|||
## Authors |
|||
|
|||
* **Samuel Pua** - *Initial work* - [GitHub](https://github.com/telboon) |
|||
|
|||
## License |
|||
|
|||
This project is licensed under the BSD License - see the [LICENSE](LICENSE) file for details |
|||
|
|||
|
@ -1,4 +1,4 @@ |
|||
#!/bin/bash |
|||
|
|||
sudo docker rm postgres --force |
|||
sudo docker run -d -e POSTGRES_USER="tapit" -e POSTGRES_PASSWORD="secret-tapit-password" -e POSTGRES_DB="tapit" --name postgres postgres |
|||
sudo docker run -d -p5432:5432 -e POSTGRES_USER="tapit" -e POSTGRES_PASSWORD="secret-tapit-password" -e POSTGRES_DB="tapit" --name postgres postgres |
|||
|
@ -0,0 +1,114 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"github.com/jinzhu/gorm" |
|||
"strings" |
|||
"net/http" |
|||
"io/ioutil" |
|||
"encoding/json" |
|||
) |
|||
|
|||
// GlobalSettings contains basic common settings across the app
|
|||
type GlobalSettings struct { |
|||
gorm.Model |
|||
SecretRegistrationCode string |
|||
ThreadsPerCampaign int |
|||
BcryptCost int |
|||
MaxRequestRetries int |
|||
WaitBeforeRetry int |
|||
WebTemplatePrefix string |
|||
WebTemplateRoute string |
|||
} |
|||
|
|||
type GlobalSettingsJson struct { |
|||
SecretRegistrationCode string `json:"secretRegistrationCode"` |
|||
ThreadsPerCampaign int `json:"threadsPerCampaign"` |
|||
BcryptCost int `json:"bcryptCost"` |
|||
MaxRequestRetries int `json:"maxRequestRetries"` |
|||
WaitBeforeRetry int `json:"waitBeforeRetry"` |
|||
WebTemplatePrefix string `json:"webTemplatePrefix"` |
|||
WebTemplateRoute string `json:"webTemplateRoute"` |
|||
} |
|||
|
|||
|
|||
func (tapit *Tapit) handleGlobalSettings(w http.ResponseWriter, r *http.Request) { |
|||
if strings.ToUpper(r.Method) == "PUT" { |
|||
tapit.updateGlobalSettings(w, r) |
|||
} else if strings.ToUpper(r.Method) == "GET" { |
|||
tapit.getGlobalSettings(w,r) |
|||
} else { |
|||
http.Error(w, "HTTP method not implemented", 400) |
|||
return |
|||
} |
|||
} |
|||
|
|||
func (tapit *Tapit) updateGlobalSettings(w http.ResponseWriter, r *http.Request) { |
|||
requestBody, err:= ioutil.ReadAll(r.Body) |
|||
if err != nil { |
|||
http.Error(w, "Bad request", 400) |
|||
return |
|||
} |
|||
var globalSettingsJson GlobalSettingsJson |
|||
var globalSettings GlobalSettings |
|||
err = tapit.db.Last(&globalSettings).Error |
|||
if err != nil { |
|||
http.Error(w, "Bad request", 400) |
|||
return |
|||
} |
|||
err = json.Unmarshal(requestBody, &globalSettingsJson) |
|||
if err != nil { |
|||
http.Error(w, "Bad request", 400) |
|||
return |
|||
} |
|||
if globalSettingsJson.SecretRegistrationCode != "" && globalSettingsJson.ThreadsPerCampaign != 0 && globalSettingsJson.BcryptCost != 0 && globalSettingsJson.WebTemplatePrefix != "" && globalSettingsJson.WebTemplateRoute != "" { |
|||
globalSettings.SecretRegistrationCode = globalSettingsJson.SecretRegistrationCode |
|||
globalSettings.ThreadsPerCampaign = globalSettingsJson.ThreadsPerCampaign |
|||
globalSettings.BcryptCost = globalSettingsJson.BcryptCost |
|||
globalSettings.MaxRequestRetries = globalSettingsJson.MaxRequestRetries |
|||
globalSettings.WaitBeforeRetry = globalSettingsJson.WaitBeforeRetry |
|||
globalSettings.WebTemplatePrefix = globalSettingsJson.WebTemplatePrefix |
|||
globalSettings.WebTemplateRoute = globalSettingsJson.WebTemplateRoute |
|||
err = tapit.db.Save(&globalSettings).Error |
|||
|
|||
if err != nil { |
|||
notifyPopup(w, r, "failure", "Failed to update global settings", nil) |
|||
return |
|||
} else { |
|||
tapit.globalSettings = globalSettings |
|||
notifyPopup(w, r, "success", "Successfully updated global settings", globalSettingsJson) |
|||
return |
|||
} |
|||
} else { |
|||
notifyPopup(w, r, "failure", "Failed to update global settings", nil) |
|||
return |
|||
} |
|||
} |
|||
|
|||
func (tapit *Tapit) getGlobalSettings(w http.ResponseWriter, r *http.Request) { |
|||
var globalSettingsJson GlobalSettingsJson |
|||
var globalSettings GlobalSettings |
|||
err := tapit.db.Last(&globalSettings).Error |
|||
|
|||
if err == nil { |
|||
globalSettingsJson.SecretRegistrationCode = globalSettings.SecretRegistrationCode |
|||
globalSettingsJson.ThreadsPerCampaign = globalSettings.ThreadsPerCampaign |
|||
globalSettingsJson.BcryptCost = globalSettings.BcryptCost |
|||
globalSettingsJson.MaxRequestRetries = globalSettings.MaxRequestRetries |
|||
globalSettingsJson.WaitBeforeRetry = globalSettings.WaitBeforeRetry |
|||
globalSettingsJson.WebTemplatePrefix = globalSettings.WebTemplatePrefix |
|||
globalSettingsJson.WebTemplateRoute = globalSettings.WebTemplateRoute |
|||
|
|||
jsonResults, err := json.Marshal(globalSettingsJson) |
|||
if err != nil { |
|||
http.Error(w, err.Error(), 500) |
|||
return |
|||
} else { |
|||
w.Header().Set("Content-Type", "application/json") |
|||
w.Write(jsonResults) |
|||
return |
|||
} |
|||
} else { |
|||
http.Error(w, "Bad request", 400) |
|||
return |
|||
} |
|||
} |
Binary file not shown.
@ -0,0 +1,468 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"github.com/jinzhu/gorm" |
|||
"github.com/gorilla/mux" |
|||
"time" |
|||
"log" |
|||
"math/rand" |
|||
"net/http" |
|||
"net/http/httputil" |
|||
"strings" |
|||
"encoding/json" |
|||
"io/ioutil" |
|||
"strconv" |
|||
"encoding/csv" |
|||
"bytes" |
|||
) |
|||
|
|||
// WebTemplate is the persistent object within Postgres
|
|||
type WebTemplate struct { |
|||
gorm.Model |
|||
Name string |
|||
TemplateType string // enum redirect, harvester
|
|||
RedirectAgent string |
|||
RedirectNegAgent string |
|||
RedirectPlaceholderHtml string |
|||
RedirectUrl string |
|||
HarvesterBeforeHtml string |
|||
HarvesterAfterHtml string |
|||
} |
|||
|
|||
// WebTemplateJson is the temporary object for JSON data passing
|
|||
type WebTemplateJson struct { |
|||
Id int `json:"id"` |
|||
Name string `json:"name"` |
|||
TemplateType string `json:"templateType"` |
|||
RedirectAgent string `json:"redirectAgent"` |
|||
RedirectNegAgent string `json:"redirectNegAgent"` |
|||
RedirectPlaceholderHtml string `json:"redirectPlaceholderHtml"` |
|||
RedirectUrl string `json:"redirectUrl"` |
|||
HarvesterBeforeHtml string `json:"harvesterBeforeHtml"` |
|||
HarvesterAfterHtml string `json:"harvesterAfterHtml"` |
|||
CreateDate time.Time `json:"createDate"` |
|||
} |
|||
|
|||
type Visit struct { |
|||
gorm.Model |
|||
JobId uint |
|||
SourceIp string |
|||
UserAgent string |
|||
Method string |
|||
BodyContent string |
|||
RawRequest string |
|||
} |
|||
|
|||
type VisitJson struct { |
|||
Id uint `json:"id"` |
|||
JobId uint `json:"jobId"` |
|||
SourceIP string `json:"sourceIp"` |
|||
UserAgent string `json:"userAgent"` |
|||
Method string `json:"method"` |
|||
BodyContent string `json:"bodyContent"` |
|||
RawRequest string `json:"rawRequest"` |
|||
CreateDate time.Time `json:"createDate"` |
|||
} |
|||
|
|||
func (tapit *Tapit) handleWebTemplate(w http.ResponseWriter, r *http.Request) { |
|||
if strings.ToUpper(r.Method) == "GET" { |
|||
tapit.getWebTemplates(w, r) |
|||
} else if strings.ToUpper(r.Method) == "POST" { |
|||
tapit.createWebTemplate(w, r) |
|||
} else { |
|||
http.Error(w, "HTTP method not implemented", 400) |
|||
return |
|||
} |
|||
} |
|||
|
|||
func (tapit *Tapit) getWebTemplates(w http.ResponseWriter, r *http.Request) { |
|||
webTemplates := []WebTemplate{} |
|||
tapit.db.Find(&webTemplates) |
|||
jsonResults, err := json.Marshal(webTemplatesToJson(webTemplates)) |
|||
if err != nil { |
|||
http.Error(w, err.Error(), 500) |
|||
return |
|||
} else { |
|||
w.Header().Set("Content-Type", "application/json") |
|||
w.Write(jsonResults) |
|||
return |
|||
} |
|||
} |
|||
|
|||
func webTemplatesToJson(webTemplates []WebTemplate) []WebTemplateJson { |
|||
webTemplateJson := make([]WebTemplateJson, 0) |
|||
for _, webTemplate := range webTemplates { |
|||
var currentWebTemplateJson WebTemplateJson |
|||
currentWebTemplateJson.Id = int(webTemplate.ID) |
|||
currentWebTemplateJson.Name = webTemplate.Name |
|||
currentWebTemplateJson.TemplateType = webTemplate.TemplateType |
|||
currentWebTemplateJson.RedirectAgent = webTemplate.RedirectAgent |
|||
currentWebTemplateJson.RedirectNegAgent = webTemplate.RedirectNegAgent |
|||
currentWebTemplateJson.RedirectPlaceholderHtml = webTemplate.RedirectPlaceholderHtml |
|||
currentWebTemplateJson.RedirectUrl = webTemplate.RedirectUrl |
|||
currentWebTemplateJson.HarvesterBeforeHtml = webTemplate.HarvesterBeforeHtml |
|||
currentWebTemplateJson.HarvesterAfterHtml = webTemplate.HarvesterAfterHtml |
|||
currentWebTemplateJson.CreateDate = webTemplate.CreatedAt |
|||
|
|||
webTemplateJson = append(webTemplateJson, currentWebTemplateJson) |
|||
} |
|||
return webTemplateJson |
|||
} |
|||
|
|||
func (tapit *Tapit) createWebTemplate(w http.ResponseWriter, r *http.Request) { |
|||
// start doing work
|
|||
requestBody, err:= ioutil.ReadAll(r.Body) |
|||
if err != nil { |
|||
http.Error(w, "Bad request", 400) |
|||
return |
|||
} |
|||
newWebTemplateJson := WebTemplateJson{} |
|||
err = json.Unmarshal(requestBody, &newWebTemplateJson) |
|||
if err != nil { |
|||
http.Error(w, "Bad request", 400) |
|||
return |
|||
} |
|||
if newWebTemplateJson.Name != "" && newWebTemplateJson.TemplateType != "" { |
|||
// check that not both user agents are filled
|
|||
if newWebTemplateJson.RedirectAgent != "" && newWebTemplateJson.RedirectNegAgent != "" { |
|||
notifyPopup(w, r, "failure", "Please fill in only either positive or negative redirect user agent.", nil) |
|||
return |
|||
} |
|||
newWebTemplate := jsonToWebTemplate(newWebTemplateJson) |
|||
tapit.db.NewRecord(&newWebTemplate) |
|||
tapit.db.Create(&newWebTemplate) |
|||
if newWebTemplate.ID == 0 { |
|||
notifyPopup(w, r, "failure", "Failed to create text template", nil) |
|||
return |
|||
} |
|||
newWebTemplateJson.Id = int(newWebTemplate.ID) |
|||
newWebTemplateJson.CreateDate = newWebTemplate.CreatedAt |
|||
|
|||
notifyPopup(w, r, "success", "Successfully added new text template", newWebTemplateJson) |
|||
return |
|||
} else { |
|||
notifyPopup(w, r, "failure", "Please fill in all details", nil) |
|||
return |
|||
} |
|||
} |
|||
|
|||
func jsonToWebTemplate(currentWebTemplateJson WebTemplateJson) WebTemplate { |
|||
var webTemplate WebTemplate |
|||
|
|||
webTemplate.Name = currentWebTemplateJson.Name |
|||
webTemplate.TemplateType = currentWebTemplateJson.TemplateType |
|||
webTemplate.RedirectAgent = currentWebTemplateJson.RedirectAgent |
|||
webTemplate.RedirectNegAgent = currentWebTemplateJson.RedirectNegAgent |
|||
webTemplate.RedirectPlaceholderHtml = currentWebTemplateJson.RedirectPlaceholderHtml |
|||
webTemplate.RedirectUrl = currentWebTemplateJson.RedirectUrl |
|||
webTemplate.HarvesterBeforeHtml = currentWebTemplateJson.HarvesterBeforeHtml |
|||
webTemplate.HarvesterAfterHtml = currentWebTemplateJson.HarvesterAfterHtml |
|||
|
|||
return webTemplate |
|||
} |
|||
|
|||
func (tapit *Tapit) handleSpecificWebTemplate(w http.ResponseWriter, r *http.Request) { |
|||
if strings.ToUpper(r.Method) == "PUT" { |
|||
tapit.updateWebTemplate(w, r) |
|||
} else if strings.ToUpper(r.Method) == "DELETE" { |
|||
tapit.deleteWebTemplate(w,r) |
|||
} else if strings.ToUpper(r.Method) == "GET" { |
|||
tapit.getWebTemplate(w,r) |
|||
} else { |
|||
http.Error(w, "HTTP method not implemented", 400) |
|||
return |
|||
} |
|||
} |
|||
|
|||
func (tapit *Tapit) updateWebTemplate(w http.ResponseWriter, r *http.Request) { |
|||
requestBody, err:= ioutil.ReadAll(r.Body) |
|||
if err != nil { |
|||
http.Error(w, "Bad request", 400) |
|||
return |
|||
} |
|||
var newWebTemplateJson WebTemplateJson |
|||
err = json.Unmarshal(requestBody, &newWebTemplateJson) |
|||
if err != nil { |
|||
http.Error(w, "Bad request", 400) |
|||
return |
|||
} |
|||
if newWebTemplateJson.Name != "" && newWebTemplateJson.TemplateType != "" { |
|||
var newWebTemplate WebTemplate |
|||
|
|||
// get current phonebook
|
|||
var dbSearchWT WebTemplate |
|||
dbSearchWT.ID = uint(newWebTemplateJson.Id) |
|||
tapit.db.Where(&dbSearchWT).First(&newWebTemplate) |
|||
|
|||
if newWebTemplate.ID == uint(newWebTemplateJson.Id) { |
|||
// update name & template
|
|||
newWebTemplate.Name = newWebTemplateJson.Name |
|||
newWebTemplate.TemplateType = newWebTemplateJson.TemplateType |
|||
newWebTemplate.RedirectAgent = newWebTemplateJson.RedirectAgent |
|||
newWebTemplate.RedirectNegAgent = newWebTemplateJson.RedirectNegAgent |
|||
newWebTemplate.RedirectPlaceholderHtml = newWebTemplateJson.RedirectPlaceholderHtml |
|||
newWebTemplate.RedirectUrl = newWebTemplateJson.RedirectUrl |
|||
newWebTemplate.HarvesterBeforeHtml = newWebTemplateJson.HarvesterBeforeHtml |
|||
newWebTemplate.HarvesterAfterHtml = newWebTemplateJson.HarvesterAfterHtml |
|||
|
|||
// update database
|
|||
tapit.db.Save(&newWebTemplate) |
|||
if newWebTemplate.ID == 0 { |
|||
notifyPopup(w, r, "failure", "Failed to update phonebook", nil) |
|||
return |
|||
} |
|||
newWebTemplateJson.Id = int(newWebTemplate.ID) |
|||
newWebTemplateJson.CreateDate = newWebTemplate.CreatedAt |
|||
|
|||
notifyPopup(w, r, "success", "Successfully updated web template", newWebTemplateJson) |
|||
return |
|||
} else { |
|||
notifyPopup(w, r, "failure", "Failed to update web template", nil) |
|||
return |
|||
} |
|||
} else { |
|||
notifyPopup(w, r, "failure", "Please enter all details", nil) |
|||
return |
|||
} |
|||
} |
|||
|
|||
func (tapit *Tapit) deleteWebTemplate(w http.ResponseWriter, r *http.Request) { |
|||
vars := mux.Vars(r) |
|||
tempID, err := strconv.Atoi(vars["id"]) |
|||
if err != nil { |
|||
http.Error(w, "Bad request", 400) |
|||
return |
|||
} |
|||
|
|||
// start working
|
|||
var webTemplate WebTemplate |
|||
|
|||
// get tt
|
|||
var dbSearchWT WebTemplate |
|||
dbSearchWT.ID = uint(tempID) |
|||
tapit.db.Where(dbSearchWT).First(&webTemplate) |
|||
|
|||
if webTemplate.ID == uint(tempID) { |
|||
// finally delete it
|
|||
tapit.db.Delete(&webTemplate) |
|||
notifyPopup(w, r, "success", "Successfully deleted phonebook", nil) |
|||
return |
|||
} else { |
|||
http.Error(w, "Bad request", 400) |
|||
return |
|||
} |
|||
} |
|||
|
|||
func (tapit *Tapit) getWebTemplate(w http.ResponseWriter, r *http.Request) { |
|||
vars := mux.Vars(r) |
|||
tempID, err := strconv.Atoi(vars["id"]) |
|||
if err != nil { |
|||
http.Error(w, "Bad request", 400) |
|||
return |
|||
} |
|||
|
|||
// start working
|
|||
var webTemplate WebTemplate |
|||
|
|||
// get tt
|
|||
var dbSearchWT WebTemplate |
|||
dbSearchWT.ID = uint(tempID) |
|||
tapit.db.Where(dbSearchWT).First(&webTemplate) |
|||
|
|||
if webTemplate.ID == uint(tempID) { |
|||
jsonResults, err := json.Marshal(webTemplateToJson(webTemplate)) |
|||
if err != nil { |
|||
http.Error(w, err.Error(), 500) |
|||
return |
|||
} else { |
|||
w.Header().Set("Content-Type", "application/json") |
|||
w.Write(jsonResults) |
|||
return |
|||
} |
|||
} else { |
|||
http.Error(w, "Bad request", 400) |
|||
return |
|||
} |
|||
} |
|||
|
|||
func (tapit *Tapit) generateWebTemplateRoute() string { |
|||
charset := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" |
|||
// generate 5 char
|
|||
var newRoute string |
|||
var successRoute bool |
|||
successRoute = false |
|||
|
|||
for !successRoute { |
|||
newRoute = "" |
|||
for i:=0; i<5; i++ { |
|||
num := rand.Int() % len(charset) |
|||
newRoute = newRoute + string(charset[num]) |
|||
|
|||
// search if route already exists
|
|||
var dbSearchJob Job |
|||
var jobs []Job |
|||
dbSearchJob.WebRoute = newRoute |
|||
tapit.db.Where(&dbSearchJob).Find(&jobs) |
|||
if len(jobs) == 0 { |
|||
successRoute = true |
|||
} |
|||
} |
|||
} |
|||
return newRoute |
|||
} |
|||
|
|||
func webTemplateToJson(webTemplate WebTemplate) WebTemplateJson { |
|||
var currentWebTemplateJson WebTemplateJson |
|||
currentWebTemplateJson.Id = int(webTemplate.ID) |
|||
currentWebTemplateJson.Name = webTemplate.Name |
|||
currentWebTemplateJson.TemplateType = webTemplate.TemplateType |
|||
currentWebTemplateJson.RedirectAgent = webTemplate.RedirectAgent |
|||
currentWebTemplateJson.RedirectNegAgent = webTemplate.RedirectNegAgent |
|||
currentWebTemplateJson.RedirectPlaceholderHtml = webTemplate.RedirectPlaceholderHtml |
|||
currentWebTemplateJson.RedirectUrl = webTemplate.RedirectUrl |
|||
currentWebTemplateJson.HarvesterBeforeHtml = webTemplate.HarvesterBeforeHtml |
|||
currentWebTemplateJson.HarvesterAfterHtml = webTemplate.HarvesterAfterHtml |
|||
currentWebTemplateJson.CreateDate = webTemplate.CreatedAt |
|||
return currentWebTemplateJson |
|||
} |
|||
|
|||
func (tapit *Tapit) webTemplateRouteHandler(w http.ResponseWriter, r *http.Request) { |
|||
var err error |
|||
vars := mux.Vars(r) |
|||
currRoute := vars["route"] |
|||
|
|||
currJob := Job{} |
|||
err = tapit.db.Where(&Job{WebRoute:currRoute}).First(&currJob).Error |
|||
if err != nil { |
|||
log.Println(err) |
|||
http.Error(w, "Bad request", 400) |
|||
return |
|||
} |
|||
|
|||
currCampaign := Campaign{} |
|||
err = tapit.db.Where(&Campaign{Model: gorm.Model{ID:currJob.CampaignId}}).First(&currCampaign).Error |
|||
if err != nil { |
|||
log.Println(err) |
|||
http.Error(w, "Bad request", 400) |
|||
return |
|||
} |
|||
|
|||
currWebTemplate := WebTemplate{} |
|||
err = tapit.db.Where(&WebTemplate{Model: gorm.Model{ID:currCampaign.WebTemplateId}}).First(&currWebTemplate).Error |
|||
if err != nil { |
|||
log.Println(err) |
|||
http.Error(w, "Bad request", 400) |
|||
return |
|||
} |
|||
|
|||
// check type for "redirect" or "harvester"
|
|||
if currWebTemplate.TemplateType == "redirect" { |
|||
if currWebTemplate.RedirectAgent != "" { |
|||
listOfUA := strings.Split(currWebTemplate.RedirectAgent, ",") |
|||
currCheck := false |
|||
for _, currUA := range listOfUA { |
|||
// check if user agent matches
|
|||
if strings.Contains(r.UserAgent(), currUA) { |
|||
currCheck = true |
|||
} |
|||
} |
|||
|
|||
// if matches at least once, redirect, otherwise placeholder
|
|||
if currCheck == true { |
|||
http.Redirect(w, r, currWebTemplate.RedirectUrl, 302) |
|||
} else { |
|||
w.Write([]byte(currWebTemplate.RedirectPlaceholderHtml)) |
|||
} |
|||
} else { |
|||
listOfUA := strings.Split(currWebTemplate.RedirectNegAgent, ",") |
|||
currCheck := true |
|||
for _, currUA := range listOfUA { |
|||
// check if user agent matches
|
|||
if strings.Contains(r.UserAgent(), currUA) { |
|||
currCheck = false |
|||
} |
|||
} |
|||
|
|||
// if matches at least once, redirect, otherwise placeholder
|
|||
if currCheck == true { |
|||
http.Redirect(w, r, currWebTemplate.RedirectUrl, 302) |
|||
} else { |
|||
w.Write([]byte(currWebTemplate.RedirectPlaceholderHtml)) |
|||
} |
|||
} |
|||
} else if currWebTemplate.TemplateType == "harvester" { |
|||
// if get show before, if post show after
|
|||
if strings.ToUpper(r.Method) == "GET"{ |
|||
w.Write([]byte(currWebTemplate.HarvesterBeforeHtml)) |
|||
} else if strings.ToUpper(r.Method) == "POST"{ |
|||
w.Write([]byte(currWebTemplate.HarvesterAfterHtml)) |
|||
} else { |
|||
http.Error(w, "Bad request", 400) |
|||
} |
|||
} |
|||
|
|||
// saving records
|
|||
requestBody, err:= ioutil.ReadAll(r.Body) |
|||
if err != nil { |
|||
http.Error(w, "Bad request", 400) |
|||
return |
|||
} |
|||
|
|||
var newVisit Visit |
|||
|
|||
newVisit = Visit{} |
|||
newVisit.JobId = currJob.ID |
|||
if r.Header.Get("X-Forwarded-For") == "" { |
|||
newVisit.SourceIp = r.RemoteAddr |
|||
} else { |
|||
newVisit.SourceIp = r.Header.Get("X-Forwarded-For") |
|||
} |
|||
newVisit.UserAgent = r.UserAgent() |
|||
newVisit.Method = r.Method |
|||
newVisit.BodyContent = string(requestBody) |
|||
rawReqBytes, err := httputil.DumpRequest(r, true) |
|||
if err == nil { |
|||
newVisit.RawRequest = string(rawReqBytes) |
|||
} |
|||
|
|||
// Update visited status
|
|||
var visits []Visit |
|||
tapit.db.Where(Visit{JobId: uint(currJob.ID)}).Find(&visits) |
|||
currJob.WebStatus = strconv.Itoa(len(visits) + 1) + " visits" |
|||
|
|||
tapit.db.Save(&currJob) |
|||
|
|||
tapit.db.NewRecord(&newVisit) |
|||
tapit.db.Create(&newVisit) |
|||
|
|||
return |
|||
} |
|||
|
|||
func (tapit *Tapit) handleDownloadView(w http.ResponseWriter, r *http.Request) { |
|||
if strings.ToUpper(r.Method) == "GET" { |
|||
var csvBuffer bytes.Buffer |
|||
vars := mux.Vars(r) |
|||
tempID, err := strconv.Atoi(vars["id"]) |
|||
if err != nil { |
|||
http.Error(w, "Bad request", 400) |
|||
return |
|||
} |
|||
|
|||
var visits []Visit |
|||
tapit.db.Where(Visit{JobId: uint(tempID)}).Find(&visits) |
|||
|
|||
// generate csv
|
|||
csvWriter := csv.NewWriter(&csvBuffer) |
|||
csvWriter.Write([]string{"ID", "Time", "Source IP", "User Agent", "Method", "Body Content", "Raw Request"}) |
|||
for _, visit := range visits { |
|||
csvWriter.Write([]string{strconv.Itoa(int(visit.ID)), visit.CreatedAt.String(), visit.SourceIp, visit.UserAgent, visit.Method, visit.BodyContent, visit.RawRequest}) |
|||
} |
|||
csvWriter.Flush() |
|||
w.Header().Set("Content-Disposition", "attachment; filename=\"results.csv\"") |
|||
w.Write(csvBuffer.Bytes()) |
|||
return |
|||
} else { |
|||
http.Error(w, "HTTP method not implemented", 400) |
|||
return |
|||
} |
|||
} |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 7.2 KiB |
After Width: | Height: | Size: 2.2 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1,3 +1,14 @@ |
|||
.campaign-details:read-only { |
|||
background-color: white; |
|||
} |
|||
|
|||
.download-visits { |
|||
color: #007bff; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.download-visits:hover { |
|||
color: #007bff; |
|||
cursor: pointer; |
|||
text-decoration: underline; |
|||
} |
|||
|
@ -0,0 +1,12 @@ |
|||
import { TestBed } from '@angular/core/testing'; |
|||
|
|||
import { GlobalSettingsService } from './global-settings.service'; |
|||
|
|||
describe('GlobalSettingsService', () => { |
|||
beforeEach(() => TestBed.configureTestingModule({})); |
|||
|
|||
it('should be created', () => { |
|||
const service: GlobalSettingsService = TestBed.get(GlobalSettingsService); |
|||
expect(service).toBeTruthy(); |
|||
}); |
|||
}); |
@ -0,0 +1,60 @@ |
|||
import { Injectable } from '@angular/core'; |
|||
import { Router } from '@angular/router'; |
|||
import { HttpClient, HttpHeaders } from '@angular/common/http'; |
|||
import { NotificationService } from './notification.service'; |
|||
import { Observable, of } from 'rxjs'; |
|||
|
|||
export class GlobalSettings { |
|||
secretRegistrationCode: string; |
|||
threadsPerCampaign: number; |
|||
bcryptCost: number; |
|||
maxRequestRetries: number; |
|||
waitBeforeRetry: number; |
|||
webTemplatePrefix: string; |
|||
webTemplateRoute: string; |
|||
} |
|||
|
|||
export class GlobalSettingsNotification { |
|||
resultType: string; |
|||
text: string; |
|||
payload: GlobalSettings; |
|||
} |
|||
|
|||
@Injectable({ |
|||
providedIn: 'root' |
|||
}) |
|||
export class GlobalSettingsService { |
|||
globalSettings = new GlobalSettings(); |
|||
|
|||
globalSettingsUrl = 'api/globalsettings'; |
|||
|
|||
httpOptions = { |
|||
headers: new HttpHeaders({ |
|||
'Content-Type': 'application/json', |
|||
}), |
|||
}; |
|||
|
|||
getGlobalSettings() { |
|||
this.http.get<GlobalSettings>(this.globalSettingsUrl, this.httpOptions).subscribe(globalSettings => { |
|||
this.globalSettings = globalSettings; |
|||
}); |
|||
} |
|||
|
|||
getGlobalSettingsObs(): Observable<GlobalSettings> { |
|||
return this.http.get<GlobalSettings>(this.globalSettingsUrl, this.httpOptions); |
|||
} |
|||
|
|||
updateGlobalSettings(globalSettings: GlobalSettings) { |
|||
this.http.put<GlobalSettingsNotification>(this.globalSettingsUrl, globalSettings, this.httpOptions).subscribe(settingsMessage => { |
|||
this.notificationService.addNotification(settingsMessage.resultType, settingsMessage.text); |
|||
this.globalSettings = settingsMessage.payload; |
|||
}, |
|||
err => { |
|||
this.notificationService.addNotification('failure', 'Error in updating settings'); |
|||
}); |
|||
} |
|||
|
|||
constructor(private http: HttpClient, private router: Router, private notificationService: NotificationService) { |
|||
this.getGlobalSettings(); |
|||
} |
|||
} |
@ -0,0 +1,56 @@ |
|||
<div class="row"> |
|||
<div class="col-12"> |
|||
<div class="row mt-3"> |
|||
<div class="col-12 d-flex"> |
|||
<h4>Global TapIt Settings</h4> |
|||
</div> |
|||
</div> |
|||
<div class="row mt-3"> |
|||
<div class="col-12 d-flex"> |
|||
<label for="registration-code" class="pr-2 mt-auto mb-auto">Secret Registration Code</label> |
|||
<input type="text" class="flex-grow-1" id="registration-code" [(ngModel)]="displaySettings.secretRegistrationCode" > |
|||
</div> |
|||
</div> |
|||
<div class="row mt-3"> |
|||
<div class="col-12 d-flex"> |
|||
<label for="threads" class="pr-2 mt-auto mb-auto">SMS Threads Per Campaign</label> |
|||
<input type="text" class="flex-grow-1" id="threads" [(ngModel)]="displaySettings.threadsPerCampaign" > |
|||
</div> |
|||
</div> |
|||
<div class="row mt-3"> |
|||
<div class="col-12 d-flex"> |
|||
<label for="bcrypt-cost" class="pr-2 mt-auto mb-auto">BCrypt Cost</label> |
|||
<input type="text" class="flex-grow-1" id="bcrypt-cost" [(ngModel)]="displaySettings.bcryptCost" > |
|||
</div> |
|||
</div> |
|||
<div class="row mt-3"> |
|||
<div class="col-12 d-flex"> |
|||
<label for="max-retries" class="pr-2 mt-auto mb-auto">Max Request Retries</label> |
|||
<input type="text" class="flex-grow-1" id="max-retries" [(ngModel)]="displaySettings.maxRequestRetries" > |
|||
</div> |
|||
</div> |
|||
<div class="row mt-3"> |
|||
<div class="col-12 d-flex"> |
|||
<label for="wait-time" class="pr-2 mt-auto mb-auto">Wait Before Retry (ms) </label> |
|||
<input type="text" class="flex-grow-1" id="wait-time" [(ngModel)]="displaySettings.waitBeforeRetry" > |
|||
</div> |
|||
</div> |
|||
<div class="row mt-3"> |
|||
<div class="col-12 d-flex"> |
|||
<label for="web-prefix" class="pr-2 mt-auto mb-auto">Web URL Prefix</label> |
|||
<input type="text" class="flex-grow-1" id="web-prefix" [(ngModel)]="displaySettings.webTemplatePrefix" > |
|||
</div> |
|||
</div> |
|||
<div class="row mt-3"> |
|||
<div class="col-12 d-flex"> |
|||
<label for="web-route" class="pr-2 mt-auto mb-auto">Web Route</label> |
|||
<input type="text" class="flex-grow-1" id="web-route" [(ngModel)]="displaySettings.webTemplateRoute" > |
|||
</div> |
|||
</div> |
|||
<div class="row mt-3"> |
|||
<div class="col-12 d-flex"> |
|||
<button type="button" (click)="updateGlobalSettings()" class="btn btn-primary">Submit</button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div> |
@ -0,0 +1,25 @@ |
|||
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; |
|||
|
|||
import { GlobalSettingsComponent } from './global-settings.component'; |
|||
|
|||
describe('GlobalSettingsComponent', () => { |
|||
let component: GlobalSettingsComponent; |
|||
let fixture: ComponentFixture<GlobalSettingsComponent>; |
|||
|
|||
beforeEach(async(() => { |
|||
TestBed.configureTestingModule({ |
|||
declarations: [ GlobalSettingsComponent ] |
|||
}) |
|||
.compileComponents(); |
|||
})); |
|||
|
|||
beforeEach(() => { |
|||
fixture = TestBed.createComponent(GlobalSettingsComponent); |
|||
component = fixture.componentInstance; |
|||
fixture.detectChanges(); |
|||
}); |
|||
|
|||
it('should create', () => { |
|||
expect(component).toBeTruthy(); |
|||
}); |
|||
}); |
@ -0,0 +1,35 @@ |
|||
import { Component, OnInit } from '@angular/core'; |
|||
import { GlobalSettings, GlobalSettingsService } from '../global-settings.service'; |
|||
|
|||
@Component({ |
|||
selector: 'app-global-settings', |
|||
templateUrl: './global-settings.component.html', |
|||
styleUrls: ['./global-settings.component.css'] |
|||
}) |
|||
export class GlobalSettingsComponent implements OnInit { |
|||
|
|||
tempSettings = new GlobalSettings(); |
|||
displaySettings; |
|||
|
|||
updateGlobalSettings() { |
|||
this.tempSettings.secretRegistrationCode = this.displaySettings.secretRegistrationCode; |
|||
this.tempSettings.webTemplatePrefix = this.displaySettings.webTemplatePrefix; |
|||
this.tempSettings.webTemplateRoute = this.displaySettings.webTemplateRoute; |
|||
this.tempSettings.threadsPerCampaign = parseInt(this.displaySettings.threadsPerCampaign, 10) + 0; |
|||
this.tempSettings.bcryptCost = parseInt(this.displaySettings.bcryptCost, 10); |
|||
this.tempSettings.maxRequestRetries = parseInt(this.displaySettings.maxRequestRetries, 10); |
|||
this.tempSettings.waitBeforeRetry = parseInt(this.displaySettings.waitBeforeRetry, 10); |
|||
|
|||
this.globalSettingsService.updateGlobalSettings(this.tempSettings); |
|||
} |
|||
|
|||
constructor(private globalSettingsService: GlobalSettingsService) { } |
|||
|
|||
ngOnInit() { |
|||
this.globalSettingsService.getGlobalSettingsObs().subscribe(settings => { |
|||
console.log(settings); |
|||
this.displaySettings = settings; |
|||
}); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,94 @@ |
|||
<div class="row p-2"> |
|||
<div class="col-12"> |
|||
<div class="row mt-3"> |
|||
<div class="col-12 d-flex"> |
|||
<label for="campaignName" class="pr-2 mt-auto mb-auto">Web Template Name</label> |
|||
<input type="text" class="flex-grow-1" id="campaignName" [(ngModel)]="newWebTemplate.name" placeholder="Web Template Name"> |
|||
</div> |
|||
</div> |
|||
<div class="row mt-3"> |
|||
<div class="col-12 d-flex"> |
|||
<label for="web-template-type">Web Template Type</label> |
|||
<select class="form-control" [(ngModel)]="newWebTemplate.templateType" id="web-template-type"> |
|||
<option></option> |
|||
<option *ngFor="let templateEnum of webTemplateService.wTemplateEnum" [ngValue]="templateEnum.tag">{{templateEnum.name}}</option> |
|||
</select> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- for redirect --> |
|||
<ng-container *ngIf="newWebTemplate.templateType === 'redirect'"> |
|||
<div class="row mt-3"> |
|||
<div class="col-12 d-flex"> |
|||
<label for="redirect-url" class="pr-2 mt-auto mb-auto">Redirect URL</label> |
|||
<input type="text" class="flex-grow-1" id="redirect-url" [(ngModel)]="newWebTemplate.redirectUrl" placeholder="https://www.attacker.com"> |
|||
</div> |
|||
</div> |
|||
<div class="row mt-3"> |
|||
<div class="col-12 d-flex"> |
|||
<label for="positive-redirect" class="pr-2 mt-auto mb-auto">Positive Redirect UA (Comma separated, leave blank if unused)</label> |
|||
<input type="text" class="flex-grow-1" id="positive-redirect" [(ngModel)]="newWebTemplate.redirectAgent" placeholder="Windows NT"> |
|||
</div> |
|||
</div> |
|||
<div class="row mt-3"> |
|||
<div class="col-12 d-flex"> |
|||
<label for="negative-redirect" class="pr-2 mt-auto mb-auto">Negative Redirect UA (Comma Seperated, leave blank if unused)</label> |
|||
<input type="text" class="flex-grow-1" id="negative-redirect" [(ngModel)]="newWebTemplate.redirectNegAgent" placeholder="Android,iPhone"> |
|||
</div> |
|||
</div> |
|||
<div class="row"> |
|||
<div class="col-12 d-flex"> |
|||
<p><small><em>Use only either 'Positive' or 'Negative' redirect. DO NOT USE BOTH.</em></small></p> |
|||
</div> |
|||
</div> |
|||
<div class="row mt-3"> |
|||
<div class="col-12 d-flex"> |
|||
<label for="placeholder-html" class="pr-2 mt-auto mb-auto">Placeholder HTML</label> |
|||
<textarea class="form-control flex" [(ngModel)]="newWebTemplate.redirectPlaceholderHtml" id="placeholder-html" rows="6"></textarea> |
|||
</div> |
|||
</div> |
|||
</ng-container> |
|||
|
|||
<ng-container *ngIf="newWebTemplate.templateType === 'harvester'"> |
|||
<div class="row mt-3"> |
|||
<div class="col-12 d-flex"> |
|||
<label for="placeholder-harvest-html" class="pr-2 mt-auto mb-auto">Placeholder HTML</label> |
|||
<textarea class="form-control flex" [(ngModel)]="newWebTemplate.harvesterBeforeHtml" id="placeholder-harvest-html" rows="6"></textarea> |
|||
</div> |
|||
</div> |
|||
<div class="row mt-3"> |
|||
<div class="col-12 d-flex"> |
|||
<label for="after-html" class="pr-2 mt-auto mb-auto">After HTML</label> |
|||
<textarea class="form-control flex" [(ngModel)]="newWebTemplate.harvesterAfterHtml" id="after-html" rows="6"></textarea> |
|||
</div> |
|||
</div> |
|||
</ng-container> |
|||
|
|||
<div class="row mt-3"> |
|||
<div class="col-12 d-flex"> |
|||
<button type="button" (click)="submitNewWebTemplate()" class="btn btn-primary ml-2">Save Web Template</button> |
|||
<button type="button" *ngIf="router.url !== '/web-template/new'" class="btn btn-danger ml-auto" data-toggle="modal" data-target="#completeModal">Delete</button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="modal fade" id="completeModal" tabindex="-1" role="dialog" aria-labelledby="completeModal" aria-hidden="true"> |
|||
<div class="modal-dialog" role="document"> |
|||
<div class="modal-content"> |
|||
<div class="modal-header"> |
|||
<h5 class="modal-title" id="exampleModalLabel">{{ newWebTemplate.name }}</h5> |
|||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> |
|||
<span aria-hidden="true">×</span> |
|||
</button> |
|||
</div> |
|||
<div class="modal-body"> |
|||
<p>Are you sure you want to delete the text template?</p> |
|||
</div> |
|||
<div class="modal-footer"> |
|||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> |
|||
<button type="button" class="btn btn-danger" (click)="deleteWebTemplate()" data-dismiss="modal">Delete Web Template</button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
@ -0,0 +1,25 @@ |
|||
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; |
|||
|
|||
import { WebTemplateNewComponent } from './web-template-new.component'; |
|||
|
|||
describe('WebTemplateNewComponent', () => { |
|||
let component: WebTemplateNewComponent; |
|||
let fixture: ComponentFixture<WebTemplateNewComponent>; |
|||
|
|||
beforeEach(async(() => { |
|||
TestBed.configureTestingModule({ |
|||
declarations: [ WebTemplateNewComponent ] |
|||
}) |
|||
.compileComponents(); |
|||
})); |
|||
|
|||
beforeEach(() => { |
|||
fixture = TestBed.createComponent(WebTemplateNewComponent); |
|||
component = fixture.componentInstance; |
|||
fixture.detectChanges(); |
|||
}); |
|||
|
|||
it('should create', () => { |
|||
expect(component).toBeTruthy(); |
|||
}); |
|||
}); |
@ -0,0 +1,46 @@ |
|||
import { Component, OnInit } from '@angular/core'; |
|||
import { WebTemplateService, WebTemplate } from '../web-template.service'; |
|||
import { Router, ActivatedRoute, ParamMap } from '@angular/router'; |
|||
|
|||
@Component({ |
|||
selector: 'app-web-template-new', |
|||
templateUrl: './web-template-new.component.html', |
|||
styleUrls: ['./web-template-new.component.css'] |
|||
}) |
|||
export class WebTemplateNewComponent implements OnInit { |
|||
|
|||
newWebTemplate: WebTemplate = new WebTemplate(); |
|||
id = 0; |
|||
|
|||
submitNewWebTemplate() { |
|||
if (this.router.url === '/web-template/new') { |
|||
this.webTemplateService.addWebTemplate(this.newWebTemplate); |
|||
} else { |
|||
this.editWebTemplate(); |
|||
} |
|||
} |
|||
|
|||
deleteWebTemplate() { |
|||
this.webTemplateService.deleteWebTemplate(this.newWebTemplate); |
|||
} |
|||
|
|||
editWebTemplate() { |
|||
this.webTemplateService.editWebTemplate(this.newWebTemplate); |
|||
} |
|||
|
|||
constructor(private webTemplateService: WebTemplateService, private router: Router, private route: ActivatedRoute) { } |
|||
|
|||
ngOnInit() { |
|||
// if page is edit
|
|||
if (this.router.url !== '/web-template/new') { |
|||
const idParam = 'id'; |
|||
this.route.params.subscribe( params => { |
|||
this.id = parseInt(params[idParam], 10); |
|||
this.webTemplateService.getWebTemplateObs(this.id).subscribe(currWT => { |
|||
this.newWebTemplate = currWT; |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
} |
@ -0,0 +1,12 @@ |
|||
import { TestBed } from '@angular/core/testing'; |
|||
|
|||
import { WebTemplateService } from './web-template.service'; |
|||
|
|||
describe('WebTemplateService', () => { |
|||
beforeEach(() => TestBed.configureTestingModule({})); |
|||
|
|||
it('should be created', () => { |
|||
const service: WebTemplateService = TestBed.get(WebTemplateService); |
|||
expect(service).toBeTruthy(); |
|||
}); |
|||
}); |
@ -0,0 +1,97 @@ |
|||
import { Injectable } from '@angular/core'; |
|||
import { Router } from '@angular/router'; |
|||
import { HttpClient, HttpHeaders } from '@angular/common/http'; |
|||
import { NotificationService } from './notification.service'; |
|||
|
|||
export class WebTemplate { |
|||
id: number; |
|||
name: string; |
|||
templateType: string; // enum redirect, harvester
|
|||
redirectAgent: string; |
|||
redirectNegAgent: string; |
|||
redirectPlaceholderHtml: string; |
|||
redirectUrl: string; |
|||
harvesterBeforeHtml: string; |
|||
harvesterAfterHtml: string; |
|||
createDate: Date; |
|||
} |
|||
|
|||
export class WebTemplateNotification { |
|||
resultType: string; |
|||
text: string; |
|||
payload: WebTemplate; |
|||
} |
|||
|
|||
@Injectable({ |
|||
providedIn: 'root' |
|||
}) |
|||
export class WebTemplateService { |
|||
|
|||
wTemplateEnum = [ |
|||
{name: 'Redirect Based On User Agent', tag: 'redirect'}, |
|||
{name: 'Credentials Harvesting', tag: 'harvester'}, |
|||
]; |
|||
|
|||
templateUrl = '/api/web-template'; |
|||
httpOptions = { |
|||
headers: new HttpHeaders({ |
|||
'Content-Type': 'application/json', |
|||
}), |
|||
}; |
|||
|
|||
webTemplates: WebTemplate[] = []; |
|||
|
|||
getWebTemplates() { |
|||
this.http.get<WebTemplate[]>(this.templateUrl).subscribe(templates => { |
|||
if (templates === null) { |
|||
this.webTemplates = []; |
|||
} else { |
|||
this.webTemplates = templates; |
|||
} |
|||
}); |
|||
} |
|||
addWebTemplate(newWebTemplate: WebTemplate) { |
|||
this.http.post<WebTemplateNotification>(this.templateUrl, newWebTemplate, this.httpOptions).subscribe(templateNotification => { |
|||
this.notificationService.addNotification(templateNotification.resultType, templateNotification.text); |
|||
this.webTemplates.push(templateNotification.payload); |
|||
if (templateNotification.payload !== null) { |
|||
this.router.navigate(['/web-template']); |
|||
} |
|||
}, |
|||
err => { |
|||
this.notificationService.addNotification('failure', 'Error in creating template'); |
|||
}); |
|||
} |
|||
|
|||
deleteWebTemplate(webTemplate: WebTemplate) { |
|||
this.http.delete<WebTemplateNotification>(this.templateUrl + '/' + webTemplate.id.toString(), this.httpOptions) |
|||
.subscribe(templateNotification => { |
|||
this.notificationService.addNotification(templateNotification.resultType, templateNotification.text); |
|||
this.router.navigate(['/web-template']); |
|||
}, |
|||
err => { |
|||
this.notificationService.addNotification('failure', 'Error in deleting web template'); |
|||
}); |
|||
} |
|||
|
|||
editWebTemplate(webTemplate: WebTemplate) { |
|||
this.http.put<WebTemplateNotification>(this.templateUrl + '/' + webTemplate.id.toString(), webTemplate, this.httpOptions) |
|||
.subscribe(templateNotification => { |
|||
this.notificationService.addNotification(templateNotification.resultType, templateNotification.text); |
|||
if (templateNotification.payload !== null) { |
|||
this.router.navigate(['/web-template']); |
|||
} |
|||
}, |
|||
err => { |
|||
this.notificationService.addNotification('failure', 'Error in editing web template'); |
|||
}); |
|||
} |
|||
|
|||
getWebTemplateObs(id: number) { |
|||
return this.http.get<WebTemplate>(this.templateUrl + '/' + id.toString()); |
|||
} |
|||
|
|||
constructor(private http: HttpClient, private router: Router, private notificationService: NotificationService) { |
|||
this.getWebTemplates(); |
|||
} |
|||
} |
@ -0,0 +1,29 @@ |
|||
<div class="row"> |
|||
<div class="col-12"> |
|||
<button class="btn btn-primary" routerLink="/web-template/new">New Web Template</button> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="row mt-2"> |
|||
<div class="col-12"> |
|||
<table class="table table-hover"> |
|||
<thead class="thead-dark"> |
|||
<tr> |
|||
<th scope="col">Name</th> |
|||
<th scope="col">Type</th> |
|||
<th scope="col">Create Date</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
<ng-container *ngFor="let webTemplate of webTemplateService.webTemplates"> |
|||
<tr routerLink="/web-template/{{ webTemplate.id }}/edit"> |
|||
<td>{{ webTemplate.name }}</td> |
|||
<td>{{ webTemplate.templateType }}</td> |
|||
<td>{{ webTemplate.createDate | date:'dd-MMM-yyyy'}}</td> |
|||
</tr> |
|||
</ng-container> |
|||
<p *ngIf="webTemplateService.webTemplates.length === 0">No web template created yet. Create templates by clicking <a routerLink="/web-template/new">here</a></p> |
|||
</tbody> |
|||
</table> |
|||
</div> |
|||
</div> |
@ -0,0 +1,25 @@ |
|||
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; |
|||
|
|||
import { WebTemplateComponent } from './web-template.component'; |
|||
|
|||
describe('WebTemplateComponent', () => { |
|||
let component: WebTemplateComponent; |
|||
let fixture: ComponentFixture<WebTemplateComponent>; |
|||
|
|||
beforeEach(async(() => { |
|||
TestBed.configureTestingModule({ |
|||
declarations: [ WebTemplateComponent ] |
|||
}) |
|||
.compileComponents(); |
|||
})); |
|||
|
|||
beforeEach(() => { |
|||
fixture = TestBed.createComponent(WebTemplateComponent); |
|||
component = fixture.componentInstance; |
|||
fixture.detectChanges(); |
|||
}); |
|||
|
|||
it('should create', () => { |
|||
expect(component).toBeTruthy(); |
|||
}); |
|||
}); |
@ -0,0 +1,17 @@ |
|||
import { Component, OnInit } from '@angular/core'; |
|||
import { WebTemplateService } from '../web-template.service'; |
|||
|
|||
@Component({ |
|||
selector: 'app-web-template', |
|||
templateUrl: './web-template.component.html', |
|||
styleUrls: ['./web-template.component.css'] |
|||
}) |
|||
export class WebTemplateComponent implements OnInit { |
|||
|
|||
constructor(private webTemplateService: WebTemplateService) { } |
|||
|
|||
ngOnInit() { |
|||
this.webTemplateService.getWebTemplates(); |
|||
} |
|||
|
|||
} |
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 7.2 KiB |
After Width: | Height: | Size: 2.2 KiB |
Loading…
Reference in new issue