mirror of https://github.com/gophish/gophish
Initial commit of RBAC support. (#1366)
* Initial commit of RBAC support. Closes #1333pull/1367/head
parent
4ec9f07859
commit
ba8ceb81da
|
@ -23,7 +23,7 @@ import (
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// APIReset (/api/reset) resets a user's API key
|
// APIReset (/api/reset) resets the currently authenticated user's API key
|
||||||
func (as *AdminServer) APIReset(w http.ResponseWriter, r *http.Request) {
|
func (as *AdminServer) APIReset(w http.ResponseWriter, r *http.Request) {
|
||||||
switch {
|
switch {
|
||||||
case r.Method == "POST":
|
case r.Method == "POST":
|
||||||
|
|
|
@ -103,12 +103,13 @@ func (as *AdminServer) registerRoutes() {
|
||||||
router.HandleFunc("/users", Use(as.Users, mid.RequireLogin))
|
router.HandleFunc("/users", Use(as.Users, mid.RequireLogin))
|
||||||
router.HandleFunc("/landing_pages", Use(as.LandingPages, mid.RequireLogin))
|
router.HandleFunc("/landing_pages", Use(as.LandingPages, mid.RequireLogin))
|
||||||
router.HandleFunc("/sending_profiles", Use(as.SendingProfiles, mid.RequireLogin))
|
router.HandleFunc("/sending_profiles", Use(as.SendingProfiles, mid.RequireLogin))
|
||||||
router.HandleFunc("/register", Use(as.Register, mid.RequireLogin))
|
|
||||||
router.HandleFunc("/settings", Use(as.Settings, mid.RequireLogin))
|
router.HandleFunc("/settings", Use(as.Settings, mid.RequireLogin))
|
||||||
|
router.HandleFunc("/register", Use(as.Register, mid.RequireLogin, mid.RequirePermission(models.PermissionModifySystem)))
|
||||||
// Create the API routes
|
// Create the API routes
|
||||||
api := router.PathPrefix("/api").Subrouter()
|
api := router.PathPrefix("/api").Subrouter()
|
||||||
api = api.StrictSlash(true)
|
api = api.StrictSlash(true)
|
||||||
api.Use(mid.RequireAPIKey)
|
api.Use(mid.RequireAPIKey)
|
||||||
|
api.Use(mid.EnforceViewOnly)
|
||||||
api.HandleFunc("/reset", as.APIReset)
|
api.HandleFunc("/reset", as.APIReset)
|
||||||
api.HandleFunc("/campaigns/", as.APICampaigns)
|
api.HandleFunc("/campaigns/", as.APICampaigns)
|
||||||
api.HandleFunc("/campaigns/summary", as.APICampaignsSummary)
|
api.HandleFunc("/campaigns/summary", as.APICampaignsSummary)
|
||||||
|
@ -165,14 +166,18 @@ type templateParams struct {
|
||||||
User models.User
|
User models.User
|
||||||
Token string
|
Token string
|
||||||
Version string
|
Version string
|
||||||
|
ModifySystem bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// newTemplateParams returns the default template parameters for a user and
|
// newTemplateParams returns the default template parameters for a user and
|
||||||
// the CSRF token.
|
// the CSRF token.
|
||||||
func newTemplateParams(r *http.Request) templateParams {
|
func newTemplateParams(r *http.Request) templateParams {
|
||||||
|
user := ctx.Get(r, "user").(models.User)
|
||||||
|
modifySystem, _ := user.HasPermission(models.PermissionModifySystem)
|
||||||
return templateParams{
|
return templateParams{
|
||||||
Token: csrf.Token(r),
|
Token: csrf.Token(r),
|
||||||
User: ctx.Get(r, "user").(models.User),
|
User: user,
|
||||||
|
ModifySystem: modifySystem,
|
||||||
Version: config.Version,
|
Version: config.Version,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -354,7 +359,7 @@ func (as *AdminServer) Logout(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
func getTemplate(w http.ResponseWriter, tmpl string) *template.Template {
|
func getTemplate(w http.ResponseWriter, tmpl string) *template.Template {
|
||||||
templates := template.New("template")
|
templates := template.New("template")
|
||||||
_, err := templates.ParseFiles("templates/base.html", "templates/"+tmpl+".html", "templates/flashes.html")
|
_, err := templates.ParseFiles("templates/base.html", "templates/nav.html", "templates/"+tmpl+".html", "templates/flashes.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
|
||||||
|
-- +goose Up
|
||||||
|
-- SQL in section 'Up' is executed when this migration is applied
|
||||||
|
CREATE TABLE IF NOT EXISTS `roles` (
|
||||||
|
`id` INTEGER PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
`slug` VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
`name` VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
`description` VARCHAR(255)
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE `users` ADD COLUMN `role_id` INTEGER;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `permissions` (
|
||||||
|
`id` INTEGER PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
`slug` VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
`name` VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
`description` VARCHAR(255)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `role_permissions` (
|
||||||
|
`role_id` INTEGER NOT NULL,
|
||||||
|
`permission_id` INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `roles` (`slug`, `name`, `description`)
|
||||||
|
VALUES
|
||||||
|
("admin", "Admin", "System administrator with full permissions"),
|
||||||
|
("user", "User", "User role with edit access to objects and campaigns");
|
||||||
|
|
||||||
|
INSERT INTO `permissions` (`slug`, `name`, `description`)
|
||||||
|
VALUES
|
||||||
|
("view_objects", "View Objects", "View objects in Gophish"),
|
||||||
|
("modify_objects", "Modify Objects", "Create and edit objects in Gophish"),
|
||||||
|
("modify_system", "Modify System", "Manage system-wide configuration");
|
||||||
|
|
||||||
|
-- Our rules for generating the admin user are:
|
||||||
|
-- * The user with the name `admin`
|
||||||
|
-- * OR the first user, if no `admin` user exists
|
||||||
|
-- MySQL apparently makes these queries gross. Thanks MySQL.
|
||||||
|
UPDATE `users` SET `role_id`=(
|
||||||
|
SELECT `id` FROM `roles` WHERE `slug`="admin")
|
||||||
|
WHERE `id`=(
|
||||||
|
SELECT `id` FROM (
|
||||||
|
SELECT * FROM `users`
|
||||||
|
) as u WHERE `username`="admin"
|
||||||
|
OR `id`=(
|
||||||
|
SELECT MIN(`id`) FROM (
|
||||||
|
SELECT * FROM `users`
|
||||||
|
) as u
|
||||||
|
) LIMIT 1);
|
||||||
|
|
||||||
|
-- Every other user will be considered a standard user account. The admin user
|
||||||
|
-- will be able to change the role of any other user at any time.
|
||||||
|
UPDATE `users` SET `role_id`=(
|
||||||
|
SELECT `id` FROM `roles` AS role_id WHERE `slug`="user")
|
||||||
|
WHERE role_id IS NULL;
|
||||||
|
|
||||||
|
-- Our default permission set will:
|
||||||
|
-- * Allow admins the ability to do anything
|
||||||
|
-- * Allow users to modify objects
|
||||||
|
|
||||||
|
-- Allow any user to view objects
|
||||||
|
INSERT INTO `role_permissions` (`role_id`, `permission_id`)
|
||||||
|
SELECT r.id, p.id FROM roles AS r, `permissions` AS p
|
||||||
|
WHERE r.id IN (SELECT `id` FROM roles WHERE `slug`="admin" OR `slug`="user")
|
||||||
|
AND p.id=(SELECT `id` FROM `permissions` WHERE `slug`="view_objects");
|
||||||
|
|
||||||
|
-- Allow admins and users to modify objects
|
||||||
|
INSERT INTO `role_permissions` (`role_id`, `permission_id`)
|
||||||
|
SELECT r.id, p.id FROM roles AS r, `permissions` AS p
|
||||||
|
WHERE r.id IN (SELECT `id` FROM roles WHERE `slug`="admin" OR `slug`="user")
|
||||||
|
AND p.id=(SELECT `id` FROM `permissions` WHERE `slug`="modify_objects");
|
||||||
|
|
||||||
|
-- Allow admins to modify system level configuration
|
||||||
|
INSERT INTO `role_permissions` (`role_id`, `permission_id`)
|
||||||
|
SELECT r.id, p.id FROM roles AS r, `permissions` AS p
|
||||||
|
WHERE r.id IN (SELECT `id` FROM roles WHERE `slug`="admin")
|
||||||
|
AND p.id=(SELECT `id` FROM `permissions` WHERE `slug`="modify_system");
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- SQL section 'Down' is executed when this migration is rolled back
|
||||||
|
DROP TABLE `roles`
|
||||||
|
DROP TABLE `user_roles`
|
||||||
|
DROP TABLE `permissions`
|
|
@ -0,0 +1,77 @@
|
||||||
|
|
||||||
|
-- +goose Up
|
||||||
|
-- SQL in section 'Up' is executed when this migration is applied
|
||||||
|
CREATE TABLE "roles" (
|
||||||
|
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"slug" VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
"name" VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
"description" VARCHAR(255)
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE "users" ADD COLUMN "role_id" INTEGER;
|
||||||
|
|
||||||
|
CREATE TABLE "permissions" (
|
||||||
|
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"slug" VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
"name" VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
"description" VARCHAR(255)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE "role_permissions" (
|
||||||
|
"role_id" INTEGER NOT NULL,
|
||||||
|
"permission_id" INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO "roles" ("slug", "name", "description")
|
||||||
|
VALUES
|
||||||
|
("admin", "Admin", "System administrator with full permissions"),
|
||||||
|
("user", "User", "User role with edit access to objects and campaigns");
|
||||||
|
|
||||||
|
INSERT INTO "permissions" ("slug", "name", "description")
|
||||||
|
VALUES
|
||||||
|
("view_objects", "View Objects", "View objects in Gophish"),
|
||||||
|
("modify_objects", "Modify Objects", "Create and edit objects in Gophish"),
|
||||||
|
("modify_system", "Modify System", "Manage system-wide configuration");
|
||||||
|
|
||||||
|
-- Our rules for generating the admin user are:
|
||||||
|
-- * The user with the name "admin"
|
||||||
|
-- * OR the first user, if no "admin" user exists
|
||||||
|
UPDATE "users" SET "role_id"=(
|
||||||
|
SELECT "id" FROM "roles" WHERE "slug"="admin")
|
||||||
|
WHERE "id"=(
|
||||||
|
SELECT "id" FROM "users" WHERE "username"="admin" OR "id"=(SELECT MIN("id") FROM "users") LIMIT 1);
|
||||||
|
|
||||||
|
-- Every other user will be considered a standard user account. The admin user
|
||||||
|
-- will be able to change the role of any other user at any time.
|
||||||
|
UPDATE "users" SET "role_id"=(
|
||||||
|
SELECT "id" FROM "roles" WHERE "slug"="user")
|
||||||
|
WHERE role_id IS NULL;
|
||||||
|
|
||||||
|
-- Our default permission set will:
|
||||||
|
-- * Allow admins the ability to do anything
|
||||||
|
-- * Allow users to modify objects
|
||||||
|
|
||||||
|
-- Allow any user to view objects
|
||||||
|
INSERT INTO "role_permissions" ("role_id", "permission_id")
|
||||||
|
SELECT r.id, p.id FROM roles AS r, "permissions" AS p
|
||||||
|
WHERE r.id IN (SELECT "id" FROM roles WHERE "slug"="admin" OR "slug"="user")
|
||||||
|
AND p.id=(SELECT "id" FROM "permissions" WHERE "slug"="view_objects");
|
||||||
|
|
||||||
|
-- Allow admins and users to modify objects
|
||||||
|
INSERT INTO "role_permissions" ("role_id", "permission_id")
|
||||||
|
SELECT r.id, p.id FROM roles AS r, "permissions" AS p
|
||||||
|
WHERE r.id IN (SELECT "id" FROM roles WHERE "slug"="admin" OR "slug"="user")
|
||||||
|
AND p.id=(SELECT "id" FROM "permissions" WHERE "slug"="modify_objects");
|
||||||
|
|
||||||
|
-- Allow admins to modify system level configuration
|
||||||
|
INSERT INTO "role_permissions" ("role_id", "permission_id")
|
||||||
|
SELECT r.id, p.id FROM roles AS r, "permissions" AS p
|
||||||
|
WHERE r.id IN (SELECT "id" FROM roles WHERE "slug"="admin")
|
||||||
|
AND p.id=(SELECT "id" FROM "permissions" WHERE "slug"="modify_system");
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- SQL section 'Down' is executed when this migration is rolled back
|
||||||
|
DROP TABLE "roles"
|
||||||
|
DROP TABLE "user_roles"
|
||||||
|
DROP TABLE "permissions"
|
|
@ -94,13 +94,14 @@ func RequireAPIKey(handler http.Handler) http.Handler {
|
||||||
JSONError(w, http.StatusUnauthorized, "Invalid API Key")
|
JSONError(w, http.StatusUnauthorized, "Invalid API Key")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
r = ctx.Set(r, "user", u)
|
||||||
r = ctx.Set(r, "user_id", u.Id)
|
r = ctx.Set(r, "user_id", u.Id)
|
||||||
r = ctx.Set(r, "api_key", ak)
|
r = ctx.Set(r, "api_key", ak)
|
||||||
handler.ServeHTTP(w, r)
|
handler.ServeHTTP(w, r)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequireLogin is a simple middleware which checks to see if the user is currently logged in.
|
// RequireLogin checks to see if the user is currently logged in.
|
||||||
// If not, the function returns a 302 redirect to the login page.
|
// If not, the function returns a 302 redirect to the login page.
|
||||||
func RequireLogin(handler http.Handler) http.HandlerFunc {
|
func RequireLogin(handler http.Handler) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -114,6 +115,50 @@ func RequireLogin(handler http.Handler) http.HandlerFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EnforceViewOnly is a global middleware that limits the ability to edit
|
||||||
|
// objects to accounts with the PermissionModifyObjects permission.
|
||||||
|
func EnforceViewOnly(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// If the request is for any non-GET HTTP method, e.g. POST, PUT,
|
||||||
|
// or DELETE, we need to ensure the user has the appropriate
|
||||||
|
// permission.
|
||||||
|
if r.Method != http.MethodGet && r.Method != http.MethodHead && r.Method != http.MethodOptions {
|
||||||
|
user := ctx.Get(r, "user").(models.User)
|
||||||
|
access, err := user.HasPermission(models.PermissionModifyObjects)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !access {
|
||||||
|
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequirePermission checks to see if the user has the requested permission
|
||||||
|
// before executing the handler. If the request is unauthorized, a JSONError
|
||||||
|
// is returned.
|
||||||
|
func RequirePermission(perm string) func(http.Handler) http.HandlerFunc {
|
||||||
|
return func(next http.Handler) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
user := ctx.Get(r, "user").(models.User)
|
||||||
|
access, err := user.HasPermission(perm)
|
||||||
|
if err != nil {
|
||||||
|
JSONError(w, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !access {
|
||||||
|
JSONError(w, http.StatusForbidden, http.StatusText(http.StatusForbidden))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// JSONError returns an error in JSON format with the given
|
// JSONError returns an error in JSON format with the given
|
||||||
// status code and message
|
// status code and message
|
||||||
func JSONError(w http.ResponseWriter, c int, m string) {
|
func JSONError(w http.ResponseWriter, c int, m string) {
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gophish/gophish/config"
|
||||||
|
ctx "github.com/gophish/gophish/context"
|
||||||
|
"github.com/gophish/gophish/models"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
var successHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte("success"))
|
||||||
|
})
|
||||||
|
|
||||||
|
type MiddlewareSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MiddlewareSuite) SetupSuite() {
|
||||||
|
conf := &config.Config{
|
||||||
|
DBName: "sqlite3",
|
||||||
|
DBPath: ":memory:",
|
||||||
|
MigrationsPath: "../db/db_sqlite3/migrations/",
|
||||||
|
}
|
||||||
|
err := models.Setup(conf)
|
||||||
|
if err != nil {
|
||||||
|
s.T().Fatalf("Failed creating database: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MiddlewarePermissionTest maps an expected HTTP Method to an expected HTTP
|
||||||
|
// status code
|
||||||
|
type MiddlewarePermissionTest map[string]int
|
||||||
|
|
||||||
|
// TestEnforceViewOnly ensures that only users with the ModifyObjects
|
||||||
|
// permission have the ability to send non-GET requests.
|
||||||
|
func (s *MiddlewareSuite) TestEnforceViewOnly() {
|
||||||
|
permissionTests := map[string]MiddlewarePermissionTest{
|
||||||
|
models.RoleAdmin: MiddlewarePermissionTest{
|
||||||
|
http.MethodGet: http.StatusOK,
|
||||||
|
http.MethodHead: http.StatusOK,
|
||||||
|
http.MethodOptions: http.StatusOK,
|
||||||
|
http.MethodPost: http.StatusOK,
|
||||||
|
http.MethodPut: http.StatusOK,
|
||||||
|
http.MethodDelete: http.StatusOK,
|
||||||
|
},
|
||||||
|
models.RoleUser: MiddlewarePermissionTest{
|
||||||
|
http.MethodGet: http.StatusOK,
|
||||||
|
http.MethodHead: http.StatusOK,
|
||||||
|
http.MethodOptions: http.StatusOK,
|
||||||
|
http.MethodPost: http.StatusOK,
|
||||||
|
http.MethodPut: http.StatusOK,
|
||||||
|
http.MethodDelete: http.StatusOK,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for r, checks := range permissionTests {
|
||||||
|
role, err := models.GetRoleBySlug(r)
|
||||||
|
s.Nil(err)
|
||||||
|
|
||||||
|
for method, expected := range checks {
|
||||||
|
req := httptest.NewRequest(method, "/", nil)
|
||||||
|
response := httptest.NewRecorder()
|
||||||
|
|
||||||
|
req = ctx.Set(req, "user", models.User{
|
||||||
|
Role: role,
|
||||||
|
RoleID: role.ID,
|
||||||
|
})
|
||||||
|
|
||||||
|
EnforceViewOnly(successHandler).ServeHTTP(response, req)
|
||||||
|
s.Equal(response.Code, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MiddlewareSuite) TestRequirePermission() {
|
||||||
|
middleware := RequirePermission(models.PermissionModifySystem)
|
||||||
|
handler := middleware(successHandler)
|
||||||
|
|
||||||
|
permissionTests := map[string]int{
|
||||||
|
models.RoleUser: http.StatusForbidden,
|
||||||
|
models.RoleAdmin: http.StatusOK,
|
||||||
|
}
|
||||||
|
|
||||||
|
for role, expected := range permissionTests {
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
response := httptest.NewRecorder()
|
||||||
|
// Test that with the requested permission, the request succeeds
|
||||||
|
role, err := models.GetRoleBySlug(role)
|
||||||
|
s.Nil(err)
|
||||||
|
req = ctx.Set(req, "user", models.User{
|
||||||
|
Role: role,
|
||||||
|
RoleID: role.ID,
|
||||||
|
})
|
||||||
|
handler.ServeHTTP(response, req)
|
||||||
|
s.Equal(response.Code, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMiddlewareSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(MiddlewareSuite))
|
||||||
|
}
|
|
@ -194,7 +194,6 @@ func (s *ModelsSuite) TestGenerateMailLog(ch *check.C) {
|
||||||
campaign := Campaign{
|
campaign := Campaign{
|
||||||
Id: 1,
|
Id: 1,
|
||||||
UserId: 1,
|
UserId: 1,
|
||||||
LaunchDate: time.Now().UTC(),
|
|
||||||
}
|
}
|
||||||
result := Result{
|
result := Result{
|
||||||
RId: "abc1234",
|
RId: "abc1234",
|
||||||
|
|
|
@ -111,10 +111,17 @@ func Setup(c *config.Config) error {
|
||||||
// Create the admin user if it doesn't exist
|
// Create the admin user if it doesn't exist
|
||||||
var userCount int64
|
var userCount int64
|
||||||
db.Model(&User{}).Count(&userCount)
|
db.Model(&User{}).Count(&userCount)
|
||||||
|
adminRole, err := GetRoleBySlug(RoleAdmin)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
if userCount == 0 {
|
if userCount == 0 {
|
||||||
initUser := User{
|
initUser := User{
|
||||||
Username: "admin",
|
Username: "admin",
|
||||||
Hash: "$2a$10$IYkPp0.QsM81lYYPrQx6W.U6oQGw7wMpozrKhKAHUBVL4mkm/EvAS", //gophish
|
Hash: "$2a$10$IYkPp0.QsM81lYYPrQx6W.U6oQGw7wMpozrKhKAHUBVL4mkm/EvAS", //gophish
|
||||||
|
Role: adminRole,
|
||||||
|
RoleID: adminRole.ID,
|
||||||
}
|
}
|
||||||
initUser.ApiKey = generateSecureKey()
|
initUser.ApiKey = generateSecureKey()
|
||||||
err = db.Save(&initUser).Error
|
err = db.Save(&initUser).Error
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
/*
|
||||||
|
Design:
|
||||||
|
|
||||||
|
Gophish implements simple Role-Based-Access-Control (RBAC) to control access to
|
||||||
|
certain resources.
|
||||||
|
|
||||||
|
By default, Gophish has two separate roles, with each user being assigned to
|
||||||
|
a single role:
|
||||||
|
|
||||||
|
* Admin - Can modify all objects as well as system-level configuration
|
||||||
|
* User - Can modify all objects
|
||||||
|
|
||||||
|
It's important to note that these are global roles. In the future, we'll likely
|
||||||
|
add the concept of teams, which will include their own roles and permission
|
||||||
|
system similar to these global permissions.
|
||||||
|
|
||||||
|
Each role maps to one or more permissions, making it easy to add more granular
|
||||||
|
permissions over time.
|
||||||
|
|
||||||
|
This is supported through a simple API on a user object,
|
||||||
|
`HasPermission(Permission)`, which returns a boolean and an error.
|
||||||
|
This API checks the role associated with the user to see if that role has the
|
||||||
|
requested permission.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const (
|
||||||
|
// RoleAdmin is used for Gophish system administrators. Users with this
|
||||||
|
// role have the ability to manage all objects within Gophish, as well as
|
||||||
|
// system-level configuration, such as users and URLs.
|
||||||
|
RoleAdmin = "admin"
|
||||||
|
// RoleUser is used for standard Gophish users. Users with this role can
|
||||||
|
// create, manage, and view Gophish objects and campaigns.
|
||||||
|
RoleUser = "user"
|
||||||
|
|
||||||
|
// PermissionViewObjects determines if a role can view standard Gophish
|
||||||
|
// objects such as campaigns, groups, landing pages, etc.
|
||||||
|
PermissionViewObjects = "view_objects"
|
||||||
|
// PermissionModifyObjects determines if a role can create and modify
|
||||||
|
// standard Gophish objects.
|
||||||
|
PermissionModifyObjects = "modify_objects"
|
||||||
|
// PermissionModifySystem determines if a role can manage system-level
|
||||||
|
// configuration.
|
||||||
|
PermissionModifySystem = "modify_system"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Role represents a user role within Gophish. Each user has a single role
|
||||||
|
// which maps to a set of permissions.
|
||||||
|
type Role struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Slug string `json:"slug"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Permissions []Permission `json:"-" gorm:"many2many:role_permissions;"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Permission determines what a particular role can do. Each role may have one
|
||||||
|
// or more permissions.
|
||||||
|
type Permission struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Slug string `json:"slug"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRoleBySlug returns a role that can be assigned to a user.
|
||||||
|
func GetRoleBySlug(slug string) (Role, error) {
|
||||||
|
role := Role{}
|
||||||
|
err := db.Where("slug=?", slug).First(&role).Error
|
||||||
|
return role, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasPermission checks to see if the user has a role with the requested
|
||||||
|
// permission.
|
||||||
|
func (u *User) HasPermission(slug string) (bool, error) {
|
||||||
|
perm := []Permission{}
|
||||||
|
err := db.Model(Role{ID: u.RoleID}).Where("slug=?", slug).Association("Permissions").Find(&perm).Error
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
// Gorm doesn't return an ErrRecordNotFound whe scanning into a slice, so
|
||||||
|
// we need to check the length (ref jinzhu/gorm#228)
|
||||||
|
if len(perm) == 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
check "gopkg.in/check.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PermissionCheck map[string]bool
|
||||||
|
|
||||||
|
func (s *ModelsSuite) TestHasPermission(c *check.C) {
|
||||||
|
|
||||||
|
permissionTests := map[string]PermissionCheck{
|
||||||
|
RoleAdmin: PermissionCheck{
|
||||||
|
PermissionModifySystem: true,
|
||||||
|
PermissionModifyObjects: true,
|
||||||
|
PermissionViewObjects: true,
|
||||||
|
},
|
||||||
|
RoleUser: PermissionCheck{
|
||||||
|
PermissionModifySystem: false,
|
||||||
|
PermissionModifyObjects: true,
|
||||||
|
PermissionViewObjects: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for r, checks := range permissionTests {
|
||||||
|
// Create the user with the provided role
|
||||||
|
role, err := GetRoleBySlug(r)
|
||||||
|
c.Assert(err, check.Equals, nil)
|
||||||
|
user := User{
|
||||||
|
Username: fmt.Sprintf("test-%s", r),
|
||||||
|
Hash: "12345",
|
||||||
|
ApiKey: fmt.Sprintf("%s-key", r),
|
||||||
|
RoleID: role.ID,
|
||||||
|
}
|
||||||
|
PutUser(&user)
|
||||||
|
|
||||||
|
// Perform the permission checks
|
||||||
|
for permission, expected := range checks {
|
||||||
|
access, err := user.HasPermission(permission)
|
||||||
|
fmt.Printf("Checking %s -> %s\n", r, permission)
|
||||||
|
c.Assert(err, check.Equals, nil)
|
||||||
|
c.Assert(access, check.Equals, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ModelsSuite) TestGetRoleBySlug(c *check.C) {
|
||||||
|
roles := []string{RoleAdmin, RoleUser}
|
||||||
|
for _, role := range roles {
|
||||||
|
got, err := GetRoleBySlug(role)
|
||||||
|
c.Assert(err, check.Equals, nil)
|
||||||
|
c.Assert(got.Slug, check.Equals, role)
|
||||||
|
}
|
||||||
|
_, err := GetRoleBySlug("bogus")
|
||||||
|
c.Assert(err, check.NotNil)
|
||||||
|
}
|
|
@ -6,13 +6,15 @@ type User struct {
|
||||||
Username string `json:"username" sql:"not null;unique"`
|
Username string `json:"username" sql:"not null;unique"`
|
||||||
Hash string `json:"-"`
|
Hash string `json:"-"`
|
||||||
ApiKey string `json:"api_key" sql:"not null;unique"`
|
ApiKey string `json:"api_key" sql:"not null;unique"`
|
||||||
|
Role Role `json:"role" gorm:"association_autoupdate:false;association_autocreate:false"`
|
||||||
|
RoleID int64 `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUser returns the user that the given id corresponds to. If no user is found, an
|
// GetUser returns the user that the given id corresponds to. If no user is found, an
|
||||||
// error is thrown.
|
// error is thrown.
|
||||||
func GetUser(id int64) (User, error) {
|
func GetUser(id int64) (User, error) {
|
||||||
u := User{}
|
u := User{}
|
||||||
err := db.Where("id=?", id).First(&u).Error
|
err := db.Preload("Role").Where("id=?", id).First(&u).Error
|
||||||
return u, err
|
return u, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +22,7 @@ func GetUser(id int64) (User, error) {
|
||||||
// error is thrown.
|
// error is thrown.
|
||||||
func GetUserByAPIKey(key string) (User, error) {
|
func GetUserByAPIKey(key string) (User, error) {
|
||||||
u := User{}
|
u := User{}
|
||||||
err := db.Where("api_key = ?", key).First(&u).Error
|
err := db.Preload("Role").Where("api_key = ?", key).First(&u).Error
|
||||||
return u, err
|
return u, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +30,7 @@ func GetUserByAPIKey(key string) (User, error) {
|
||||||
// error is thrown.
|
// error is thrown.
|
||||||
func GetUserByUsername(username string) (User, error) {
|
func GetUserByUsername(username string) (User, error) {
|
||||||
u := User{}
|
u := User{}
|
||||||
err := db.Where("username = ?", username).First(&u).Error
|
err := db.Preload("Role").Where("username = ?", username).First(&u).Error
|
||||||
return u, err
|
return u, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
function errorFlash(e){$("#flashes").empty(),$("#flashes").append('<div style="text-align:center" class="alert alert-danger"> <i class="fa fa-exclamation-circle"></i> '+e+"</div>")}function successFlash(e){$("#flashes").empty(),$("#flashes").append('<div style="text-align:center" class="alert alert-success"> <i class="fa fa-check-circle"></i> '+e+"</div>")}function modalError(e){$("#modal\\.flashes").empty().append('<div style="text-align:center" class="alert alert-danger"> <i class="fa fa-exclamation-circle"></i> '+e+"</div>")}function query(e,t,n,r){return $.ajax({url:"/api"+e+"?api_key="+user.api_key,async:r,method:t,data:JSON.stringify(n),dataType:"json",contentType:"application/json"})}function escapeHtml(e){return $("<div/>").text(e).html()}function unescapeHtml(e){return $("<div/>").html(e).text()}var capitalize=function(e){return e.charAt(0).toUpperCase()+e.slice(1)},api={campaigns:{get:function(){return query("/campaigns/","GET",{},!1)},post:function(e){return query("/campaigns/","POST",e,!1)},summary:function(){return query("/campaigns/summary","GET",{},!1)}},campaignId:{get:function(e){return query("/campaigns/"+e,"GET",{},!0)},delete:function(e){return query("/campaigns/"+e,"DELETE",{},!1)},results:function(e){return query("/campaigns/"+e+"/results","GET",{},!0)},complete:function(e){return query("/campaigns/"+e+"/complete","GET",{},!0)},summary:function(e){return query("/campaigns/"+e+"/summary","GET",{},!0)}},groups:{get:function(){return query("/groups/","GET",{},!1)},post:function(e){return query("/groups/","POST",e,!1)},summary:function(){return query("/groups/summary","GET",{},!0)}},groupId:{get:function(e){return query("/groups/"+e,"GET",{},!1)},put:function(e){return query("/groups/"+e.id,"PUT",e,!1)},delete:function(e){return query("/groups/"+e,"DELETE",{},!1)}},templates:{get:function(){return query("/templates/","GET",{},!1)},post:function(e){return query("/templates/","POST",e,!1)}},templateId:{get:function(e){return query("/templates/"+e,"GET",{},!1)},put:function(e){return query("/templates/"+e.id,"PUT",e,!1)},delete:function(e){return query("/templates/"+e,"DELETE",{},!1)}},pages:{get:function(){return query("/pages/","GET",{},!1)},post:function(e){return query("/pages/","POST",e,!1)}},pageId:{get:function(e){return query("/pages/"+e,"GET",{},!1)},put:function(e){return query("/pages/"+e.id,"PUT",e,!1)},delete:function(e){return query("/pages/"+e,"DELETE",{},!1)}},SMTP:{get:function(){return query("/smtp/","GET",{},!1)},post:function(e){return query("/smtp/","POST",e,!1)}},SMTPId:{get:function(e){return query("/smtp/"+e,"GET",{},!1)},put:function(e){return query("/smtp/"+e.id,"PUT",e,!1)},delete:function(e){return query("/smtp/"+e,"DELETE",{},!1)}},import_email:function(e){return query("/import/email","POST",e,!1)},clone_site:function(e){return query("/import/site","POST",e,!1)},send_test_email:function(e){return query("/util/send_test_email","POST",e,!0)},reset:function(){return query("/reset","POST",{},!0)}};$(document).ready(function(){$.fn.dataTable.moment("MMMM Do YYYY, h:mm:ss a"),$('[data-toggle="tooltip"]').tooltip()});
|
function errorFlash(e){$("#flashes").empty(),$("#flashes").append('<div style="text-align:center" class="alert alert-danger"> <i class="fa fa-exclamation-circle"></i> '+e+"</div>")}function successFlash(e){$("#flashes").empty(),$("#flashes").append('<div style="text-align:center" class="alert alert-success"> <i class="fa fa-check-circle"></i> '+e+"</div>")}function modalError(e){$("#modal\\.flashes").empty().append('<div style="text-align:center" class="alert alert-danger"> <i class="fa fa-exclamation-circle"></i> '+e+"</div>")}function query(e,t,n,r){return $.ajax({url:"/api"+e+"?api_key="+user.api_key,async:r,method:t,data:JSON.stringify(n),dataType:"json",contentType:"application/json"})}function escapeHtml(e){return $("<div/>").text(e).html()}function unescapeHtml(e){return $("<div/>").html(e).text()}var capitalize=function(e){return e.charAt(0).toUpperCase()+e.slice(1)},api={campaigns:{get:function(){return query("/campaigns/","GET",{},!1)},post:function(e){return query("/campaigns/","POST",e,!1)},summary:function(){return query("/campaigns/summary","GET",{},!1)}},campaignId:{get:function(e){return query("/campaigns/"+e,"GET",{},!0)},delete:function(e){return query("/campaigns/"+e,"DELETE",{},!1)},results:function(e){return query("/campaigns/"+e+"/results","GET",{},!0)},complete:function(e){return query("/campaigns/"+e+"/complete","GET",{},!0)},summary:function(e){return query("/campaigns/"+e+"/summary","GET",{},!0)}},groups:{get:function(){return query("/groups/","GET",{},!1)},post:function(e){return query("/groups/","POST",e,!1)},summary:function(){return query("/groups/summary","GET",{},!0)}},groupId:{get:function(e){return query("/groups/"+e,"GET",{},!1)},put:function(e){return query("/groups/"+e.id,"PUT",e,!1)},delete:function(e){return query("/groups/"+e,"DELETE",{},!1)}},templates:{get:function(){return query("/templates/","GET",{},!1)},post:function(e){return query("/templates/","POST",e,!1)}},templateId:{get:function(e){return query("/templates/"+e,"GET",{},!1)},put:function(e){return query("/templates/"+e.id,"PUT",e,!1)},delete:function(e){return query("/templates/"+e,"DELETE",{},!1)}},pages:{get:function(){return query("/pages/","GET",{},!1)},post:function(e){return query("/pages/","POST",e,!1)}},pageId:{get:function(e){return query("/pages/"+e,"GET",{},!1)},put:function(e){return query("/pages/"+e.id,"PUT",e,!1)},delete:function(e){return query("/pages/"+e,"DELETE",{},!1)}},SMTP:{get:function(){return query("/smtp/","GET",{},!1)},post:function(e){return query("/smtp/","POST",e,!1)}},SMTPId:{get:function(e){return query("/smtp/"+e,"GET",{},!1)},put:function(e){return query("/smtp/"+e.id,"PUT",e,!1)},delete:function(e){return query("/smtp/"+e,"DELETE",{},!1)}},import_email:function(e){return query("/import/email","POST",e,!1)},clone_site:function(e){return query("/import/site","POST",e,!1)},send_test_email:function(e){return query("/util/send_test_email","POST",e,!0)},reset:function(){return query("/reset","POST",{},!0)}};$(document).ready(function(){var e=location.pathname;$(".nav-sidebar li").each(function(){var t=$(this);t.find("a").attr("href")===e&&t.addClass("active")}),$.fn.dataTable.moment("MMMM Do YYYY, h:mm:ss a"),$('[data-toggle="tooltip"]').tooltip()});
|
|
@ -212,6 +212,15 @@ var api = {
|
||||||
|
|
||||||
// Register our moment.js datatables listeners
|
// Register our moment.js datatables listeners
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
|
// Setup nav highlighting
|
||||||
|
var path = location.pathname;
|
||||||
|
$('.nav-sidebar li').each(function () {
|
||||||
|
var $this = $(this);
|
||||||
|
// if the current path is like this link, make it active
|
||||||
|
if ($this.find("a").attr('href') === path) {
|
||||||
|
$this.addClass('active');
|
||||||
|
}
|
||||||
|
})
|
||||||
$.fn.dataTable.moment('MMMM Do YYYY, h:mm:ss a');
|
$.fn.dataTable.moment('MMMM Do YYYY, h:mm:ss a');
|
||||||
// Setup tooltips
|
// Setup tooltips
|
||||||
$('[data-toggle="tooltip"]').tooltip()
|
$('[data-toggle="tooltip"]').tooltip()
|
||||||
|
|
|
@ -75,6 +75,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{template "nav" .}}
|
||||||
{{template "body" .}}
|
{{template "body" .}}
|
||||||
<!-- Placed at the end of the document so the pages load faster -->
|
<!-- Placed at the end of the document so the pages load faster -->
|
||||||
<script src="/js/dist/vendor.min.js"></script>
|
<script src="/js/dist/vendor.min.js"></script>
|
||||||
|
|
|
@ -1,42 +1,4 @@
|
||||||
{{define "body"}}
|
{{define "body"}}
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-3 col-md-2 sidebar">
|
|
||||||
<ul class="nav nav-sidebar">
|
|
||||||
<li>
|
|
||||||
<a href="/">Dashboard</a>
|
|
||||||
</li>
|
|
||||||
<li class="active">
|
|
||||||
<a href="/campaigns">Campaigns</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/users">Users & Groups</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/templates">Email Templates</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/landing_pages">Landing Pages</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/sending_profiles">Sending Profiles</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/settings">Settings</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<hr>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://docs.getgophish.com/user-guide/">User Guide</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://docs.getgophish.com/api-documentation/">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">
|
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
|
||||||
<div id="loading">
|
<div id="loading">
|
||||||
<i class="fa fa-spinner fa-spin fa-4x"></i>
|
<i class="fa fa-spinner fa-spin fa-4x"></i>
|
||||||
|
|
|
@ -1,42 +1,4 @@
|
||||||
{{define "body"}}
|
{{define "body"}}
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-3 col-md-2 sidebar">
|
|
||||||
<ul class="nav nav-sidebar">
|
|
||||||
<li>
|
|
||||||
<a href="/">Dashboard</a>
|
|
||||||
</li>
|
|
||||||
<li class="active">
|
|
||||||
<a href="/campaigns">Campaigns</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/users">Users & Groups</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/templates">Email Templates</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/landing_pages">Landing Pages</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/sending_profiles">Sending Profiles</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/settings">Settings</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<hr>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://docs.getgophish.com/user-guide/">User Guide</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://docs.getgophish.com/api-documentation/">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">
|
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<h1 class="page-header">
|
<h1 class="page-header">
|
||||||
|
|
|
@ -1,42 +1,4 @@
|
||||||
{{define "body"}}
|
{{define "body"}}
|
||||||
<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">Email Templates</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/landing_pages">Landing Pages</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/sending_profiles">Sending Profiles</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/settings">Settings</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<hr>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://docs.getgophish.com/user-guide/">User Guide</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://docs.getgophish.com/api-documentation/">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">
|
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
|
||||||
<h1 class="page-header">
|
<h1 class="page-header">
|
||||||
Dashboard
|
Dashboard
|
||||||
|
|
|
@ -1,34 +1,4 @@
|
||||||
{{define "body"}}
|
{{define "body"}}
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-3 col-md-2 sidebar">
|
|
||||||
<ul class="nav nav-sidebar">
|
|
||||||
<li><a href="/">Dashboard</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="/campaigns">Campaigns</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="/users">Users & Groups</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="/templates">Email Templates</a>
|
|
||||||
</li>
|
|
||||||
<li class="active"><a href="/landing_pages">Landing Pages</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="/sending_profiles">Sending Profiles</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="/settings">Settings</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<hr>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://docs.getgophish.com/user-guide/">User Guide</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="https://docs.getgophish.com/api-documentation/">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">
|
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
|
||||||
<h1 class="page-header">
|
<h1 class="page-header">
|
||||||
Landing Pages
|
Landing Pages
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
{{define "nav"}}
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-3 col-md-2 sidebar">
|
||||||
|
<ul class="nav nav-sidebar">
|
||||||
|
<li>
|
||||||
|
<a href="/">Dashboard</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/campaigns">Campaigns</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/users">Users & Groups</a>
|
||||||
|
</li>
|
||||||
|
<li> <a href="/templates">Email Templates</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/landing_pages">Landing Pages</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/sending_profiles">Sending Profiles</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/settings">Settings <span class="badge pull-right">Admin</span></a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<hr>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://docs.getgophish.com/user-guide/">User Guide</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://docs.getgophish.com/api-documentation/">API Documentation</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
|
@ -45,10 +45,12 @@
|
||||||
<img id="logo" src="/images/logo_purple.png" />
|
<img id="logo" src="/images/logo_purple.png" />
|
||||||
<h2 class="form-signin-heading">Please register below</h2>
|
<h2 class="form-signin-heading">Please register below</h2>
|
||||||
{{template "flashes" .Flashes}}
|
{{template "flashes" .Flashes}}
|
||||||
<input type="text" name="username" class="form-control top-input" placeholder="Username" required autofocus/>
|
<input type="text" name="username" class="form-control top-input" placeholder="Username" required autofocus />
|
||||||
<input type="password" name="password" class="form-control middle-input" placeholder="Password" autocomplete="off" required/>
|
<input type="password" name="password" class="form-control middle-input" placeholder="Password"
|
||||||
<input type="password" name="confirm_password" class="form-control bottom-input" placeholder="Confirm Password" autocomplete="off" required/>
|
autocomplete="off" required />
|
||||||
<input type="hidden" name="csrf_token" value="{{.Token}}"/>
|
<input type="password" name="confirm_password" class="form-control bottom-input" placeholder="Confirm Password"
|
||||||
|
autocomplete="off" required />
|
||||||
|
<input type="hidden" name="csrf_token" value="{{.Token}}" />
|
||||||
<button class="btn btn-lg btn-primary btn-block" type="submit">Register</button>
|
<button class="btn btn-lg btn-primary btn-block" type="submit">Register</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,34 +1,4 @@
|
||||||
{{define "body"}}
|
{{define "body"}}
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-3 col-md-2 sidebar">
|
|
||||||
<ul class="nav nav-sidebar">
|
|
||||||
<li><a href="/">Dashboard</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="/campaigns">Campaigns</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="/users">Users & Groups</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="/templates">Email Templates</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="/landing_pages">Landing Pages</a>
|
|
||||||
</li>
|
|
||||||
<li class="active"><a href="/sending_profiles">Sending Profiles</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="/settings">Settings</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<hr>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://docs.getgophish.com/user-guide/">User Guide</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="https://docs.getgophish.com/api-documentation/">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">
|
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
|
||||||
<h1 class="page-header">
|
<h1 class="page-header">
|
||||||
Sending Profiles
|
Sending Profiles
|
||||||
|
|
|
@ -1,34 +1,4 @@
|
||||||
{{define "body"}}
|
{{define "body"}}
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-3 col-md-2 sidebar">
|
|
||||||
<ul class="nav nav-sidebar">
|
|
||||||
<li><a href="/">Dashboard</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="/campaigns">Campaigns</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="/users">Users & Groups</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="/templates">Email Templates</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="/landing_pages">Landing Pages</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="/sending_profiles">Sending Profiles</a>
|
|
||||||
</li>
|
|
||||||
<li class="active"><a href="/settings">Settings</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<hr>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://docs.getgophish.com/user-guide/">User Guide</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="https://docs.getgophish.com/api-documentation/">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">
|
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<h1 class="page-header">Settings</h1>
|
<h1 class="page-header">Settings</h1>
|
||||||
|
@ -44,6 +14,7 @@
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<div role="tabpanel" class="tab-pane active" id="mainSettings">
|
<div role="tabpanel" class="tab-pane active" id="mainSettings">
|
||||||
<br />
|
<br />
|
||||||
|
{{if .ModifySystem }}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label class="col-sm-2 control-label form-label">Gophish version</label>
|
<label class="col-sm-2 control-label form-label">Gophish version</label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
|
@ -58,6 +29,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
|
{{end}}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label for="api_key" class="col-sm-2 control-label form-label">API Key:</label>
|
<label for="api_key" class="col-sm-2 control-label form-label">API Key:</label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
|
|
|
@ -1,34 +1,4 @@
|
||||||
{{define "body"}}
|
{{define "body"}}
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-3 col-md-2 sidebar">
|
|
||||||
<ul class="nav nav-sidebar">
|
|
||||||
<li><a href="/">Dashboard</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="/campaigns">Campaigns</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="/users">Users & Groups</a>
|
|
||||||
</li>
|
|
||||||
<li class="active"><a href="/templates">Email Templates</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="/landing_pages">Landing Pages</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="/sending_profiles">Sending Profiles</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="/settings">Settings</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<hr>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://docs.getgophish.com/user-guide/">User Guide</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="https://docs.getgophish.com/api-documentation/">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">
|
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<h1 class="page-header">
|
<h1 class="page-header">
|
||||||
|
|
|
@ -1,42 +1,4 @@
|
||||||
{{define "body"}}
|
{{define "body"}}
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-3 col-md-2 sidebar">
|
|
||||||
<ul class="nav nav-sidebar">
|
|
||||||
<li>
|
|
||||||
<a href="/">Dashboard</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/campaigns">Campaigns</a>
|
|
||||||
</li>
|
|
||||||
<li class="active">
|
|
||||||
<a href="/users">Users & Groups</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/templates">Email Templates</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/landing_pages">Landing Pages</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/sending_profiles">Sending Profiles</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/settings">Settings</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<hr>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://docs.getgophish.com/user-guide/">User Guide</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://docs.getgophish.com/api-documentation/">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">
|
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<h1 class="page-header">
|
<h1 class="page-header">
|
||||||
|
|
Loading…
Reference in New Issue