mirror of
https://github.com/gophish/gophish
synced 2024-11-14 00:07:19 +00:00
Initial commit of RBAC support. (#1366)
* Initial commit of RBAC support. Closes #1333
This commit is contained in:
parent
4ec9f07859
commit
ba8ceb81da
24 changed files with 549 additions and 299 deletions
|
@ -23,7 +23,7 @@ import (
|
|||
"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) {
|
||||
switch {
|
||||
case r.Method == "POST":
|
||||
|
|
|
@ -103,12 +103,13 @@ func (as *AdminServer) registerRoutes() {
|
|||
router.HandleFunc("/users", Use(as.Users, mid.RequireLogin))
|
||||
router.HandleFunc("/landing_pages", Use(as.LandingPages, 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("/register", Use(as.Register, mid.RequireLogin, mid.RequirePermission(models.PermissionModifySystem)))
|
||||
// Create the API routes
|
||||
api := router.PathPrefix("/api").Subrouter()
|
||||
api = api.StrictSlash(true)
|
||||
api.Use(mid.RequireAPIKey)
|
||||
api.Use(mid.EnforceViewOnly)
|
||||
api.HandleFunc("/reset", as.APIReset)
|
||||
api.HandleFunc("/campaigns/", as.APICampaigns)
|
||||
api.HandleFunc("/campaigns/summary", as.APICampaignsSummary)
|
||||
|
@ -160,20 +161,24 @@ func Use(handler http.HandlerFunc, mid ...func(http.Handler) http.HandlerFunc) h
|
|||
}
|
||||
|
||||
type templateParams struct {
|
||||
Title string
|
||||
Flashes []interface{}
|
||||
User models.User
|
||||
Token string
|
||||
Version string
|
||||
Title string
|
||||
Flashes []interface{}
|
||||
User models.User
|
||||
Token string
|
||||
Version string
|
||||
ModifySystem bool
|
||||
}
|
||||
|
||||
// newTemplateParams returns the default template parameters for a user and
|
||||
// the CSRF token.
|
||||
func newTemplateParams(r *http.Request) templateParams {
|
||||
user := ctx.Get(r, "user").(models.User)
|
||||
modifySystem, _ := user.HasPermission(models.PermissionModifySystem)
|
||||
return templateParams{
|
||||
Token: csrf.Token(r),
|
||||
User: ctx.Get(r, "user").(models.User),
|
||||
Version: config.Version,
|
||||
Token: csrf.Token(r),
|
||||
User: user,
|
||||
ModifySystem: modifySystem,
|
||||
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 {
|
||||
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 {
|
||||
log.Error(err)
|
||||
}
|
||||
|
|
85
db/db_mysql/migrations/20190105192341_0.8.0_rbac.sql
Normal file
85
db/db_mysql/migrations/20190105192341_0.8.0_rbac.sql
Normal file
|
@ -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`
|
77
db/db_sqlite3/migrations/20190105192341_0.8.0_rbac.sql
Normal file
77
db/db_sqlite3/migrations/20190105192341_0.8.0_rbac.sql
Normal file
|
@ -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")
|
||||
return
|
||||
}
|
||||
r = ctx.Set(r, "user", u)
|
||||
r = ctx.Set(r, "user_id", u.Id)
|
||||
r = ctx.Set(r, "api_key", ak)
|
||||
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.
|
||||
func RequireLogin(handler http.Handler) http.HandlerFunc {
|
||||
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
|
||||
// status code and message
|
||||
func JSONError(w http.ResponseWriter, c int, m string) {
|
||||
|
|
104
middleware/middleware_test.go
Normal file
104
middleware/middleware_test.go
Normal file
|
@ -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))
|
||||
}
|
|
@ -192,9 +192,8 @@ func (s *ModelsSuite) TestMailLogSuccess(ch *check.C) {
|
|||
|
||||
func (s *ModelsSuite) TestGenerateMailLog(ch *check.C) {
|
||||
campaign := Campaign{
|
||||
Id: 1,
|
||||
UserId: 1,
|
||||
LaunchDate: time.Now().UTC(),
|
||||
Id: 1,
|
||||
UserId: 1,
|
||||
}
|
||||
result := Result{
|
||||
RId: "abc1234",
|
||||
|
|
|
@ -111,10 +111,17 @@ func Setup(c *config.Config) error {
|
|||
// Create the admin user if it doesn't exist
|
||||
var userCount int64
|
||||
db.Model(&User{}).Count(&userCount)
|
||||
adminRole, err := GetRoleBySlug(RoleAdmin)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
if userCount == 0 {
|
||||
initUser := User{
|
||||
Username: "admin",
|
||||
Hash: "$2a$10$IYkPp0.QsM81lYYPrQx6W.U6oQGw7wMpozrKhKAHUBVL4mkm/EvAS", //gophish
|
||||
Role: adminRole,
|
||||
RoleID: adminRole.ID,
|
||||
}
|
||||
initUser.ApiKey = generateSecureKey()
|
||||
err = db.Save(&initUser).Error
|
||||
|
|
88
models/rbac.go
Normal file
88
models/rbac.go
Normal file
|
@ -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
|
||||
}
|
57
models/rbac_test.go
Normal file
57
models/rbac_test.go
Normal file
|
@ -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"`
|
||||
Hash string `json:"-"`
|
||||
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
|
||||
// error is thrown.
|
||||
func GetUser(id int64) (User, error) {
|
||||
u := User{}
|
||||
err := db.Where("id=?", id).First(&u).Error
|
||||
err := db.Preload("Role").Where("id=?", id).First(&u).Error
|
||||
return u, err
|
||||
}
|
||||
|
||||
|
@ -20,7 +22,7 @@ func GetUser(id int64) (User, error) {
|
|||
// error is thrown.
|
||||
func GetUserByAPIKey(key string) (User, error) {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -28,7 +30,7 @@ func GetUserByAPIKey(key string) (User, error) {
|
|||
// error is thrown.
|
||||
func GetUserByUsername(username string) (User, error) {
|
||||
u := User{}
|
||||
err := db.Where("username = ?", username).First(&u).Error
|
||||
err := db.Preload("Role").Where("username = ?", username).First(&u).Error
|
||||
return u, err
|
||||
}
|
||||
|
||||
|
|
2
static/js/dist/app/gophish.min.js
vendored
2
static/js/dist/app/gophish.min.js
vendored
|
@ -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
|
||||
$(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');
|
||||
// Setup tooltips
|
||||
$('[data-toggle="tooltip"]').tooltip()
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<link href='https://fonts.googleapis.com/css?family=Roboto:700,500' rel='stylesheet' type='text/css'>
|
||||
<link href='https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,300,600,700' rel='stylesheet' type='text/css'>
|
||||
<script>
|
||||
{{if .User}}
|
||||
{{if .User}}
|
||||
var user = {
|
||||
api_key : {{ .User.ApiKey }},
|
||||
username : {{ .User.Username }}
|
||||
|
@ -62,7 +62,7 @@
|
|||
<div class="btn-group" id="navbar-dropdown">
|
||||
<a class="btn btn-primary" href="/settings"><i class="fa fa-user"></i> {{.User.Username}}</a>
|
||||
<a class="btn btn-primary dropdown-toggle" href="/logout">
|
||||
<i class="fa fa-sign-out"></i>
|
||||
<i class="fa fa-sign-out"></i>
|
||||
</a>
|
||||
</div>
|
||||
{{else}}
|
||||
|
@ -75,6 +75,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "nav" .}}
|
||||
{{template "body" .}}
|
||||
<!-- Placed at the end of the document so the pages load faster -->
|
||||
<script src="/js/dist/vendor.min.js"></script>
|
||||
|
@ -83,4 +84,4 @@
|
|||
</body>
|
||||
|
||||
</html>
|
||||
{{ end }}
|
||||
{{ end }}
|
|
@ -1,42 +1,4 @@
|
|||
{{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 id="loading">
|
||||
<i class="fa fa-spinner fa-spin fa-4x"></i>
|
||||
|
|
|
@ -1,42 +1,4 @@
|
|||
{{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="row">
|
||||
<h1 class="page-header">
|
||||
|
|
|
@ -1,42 +1,4 @@
|
|||
{{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">
|
||||
<h1 class="page-header">
|
||||
Dashboard
|
||||
|
|
|
@ -1,34 +1,4 @@
|
|||
{{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">
|
||||
<h1 class="page-header">
|
||||
Landing Pages
|
||||
|
|
39
templates/nav.html
Normal file
39
templates/nav.html
Normal file
|
@ -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" />
|
||||
<h2 class="form-signin-heading">Please register below</h2>
|
||||
{{template "flashes" .Flashes}}
|
||||
<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="confirm_password" class="form-control bottom-input" placeholder="Confirm Password" autocomplete="off" required/>
|
||||
<input type="hidden" name="csrf_token" value="{{.Token}}"/>
|
||||
<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="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>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -57,4 +59,4 @@
|
|||
</body>
|
||||
|
||||
</html>
|
||||
{{ end }}
|
||||
{{ end }}
|
|
@ -1,34 +1,4 @@
|
|||
{{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">
|
||||
<h1 class="page-header">
|
||||
Sending Profiles
|
||||
|
|
|
@ -1,34 +1,4 @@
|
|||
{{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="row">
|
||||
<h1 class="page-header">Settings</h1>
|
||||
|
@ -44,6 +14,7 @@
|
|||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane active" id="mainSettings">
|
||||
<br />
|
||||
{{if .ModifySystem }}
|
||||
<div class="row">
|
||||
<label class="col-sm-2 control-label form-label">Gophish version</label>
|
||||
<div class="col-md-6">
|
||||
|
@ -58,6 +29,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<br />
|
||||
{{end}}
|
||||
<div class="row">
|
||||
<label for="api_key" class="col-sm-2 control-label form-label">API Key:</label>
|
||||
<div class="col-md-6">
|
||||
|
|
|
@ -1,34 +1,4 @@
|
|||
{{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="row">
|
||||
<h1 class="page-header">
|
||||
|
|
|
@ -1,42 +1,4 @@
|
|||
{{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="row">
|
||||
<h1 class="page-header">
|
||||
|
|
Loading…
Reference in a new issue