Skip to content

Commit

Permalink
Implement User Management API (gophish#1473)
Browse files Browse the repository at this point in the history
This implements the first pass for a user management API allowing users with the `ModifySystem` permission to create, modify, and delete users. In addition to this, any user is able to use the API to view or modify their own account information.
  • Loading branch information
jordan-wright authored May 31, 2019
1 parent faadf0c commit 84096b8
Show file tree
Hide file tree
Showing 32 changed files with 3,595 additions and 532 deletions.
3 changes: 3 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"presets": ["@babel/preset-env"]
}
83 changes: 5 additions & 78 deletions auth/auth.go
Original file line number Diff line number Diff line change
@@ -1,49 +1,24 @@
package auth

import (
"encoding/gob"
"errors"
"fmt"
"io"
"net/http"

"crypto/rand"

ctx "github.com/gophish/gophish/context"
log "github.com/gophish/gophish/logger"
"github.com/gophish/gophish/models"
"github.com/gorilla/securecookie"
"github.com/gorilla/sessions"
"github.com/jinzhu/gorm"
"golang.org/x/crypto/bcrypt"
)

//init registers the necessary models to be saved in the session later
func init() {
gob.Register(&models.User{})
gob.Register(&models.Flash{})
Store.Options.HttpOnly = true
// This sets the maxAge to 5 days for all cookies
Store.MaxAge(86400 * 5)
}

// Store contains the session information for the request
var Store = sessions.NewCookieStore(
[]byte(securecookie.GenerateRandomKey(64)), //Signing key
[]byte(securecookie.GenerateRandomKey(32)))

// ErrInvalidPassword is thrown when a user provides an incorrect password.
var ErrInvalidPassword = errors.New("Invalid Password")

// ErrEmptyPassword is thrown when a user provides a blank password to the register
// ErrPasswordMismatch is thrown when a user provides a blank password to the register
// or change password functions
var ErrEmptyPassword = errors.New("Password cannot be blank")

// ErrPasswordMismatch is thrown when a user provides passwords that do not match
var ErrPasswordMismatch = errors.New("Passwords must match")
var ErrPasswordMismatch = errors.New("Password cannot be blank")

// ErrUsernameTaken is thrown when a user attempts to register a username that is taken.
var ErrUsernameTaken = errors.New("Username already taken")
// ErrEmptyPassword is thrown when a user provides a blank password to the register
// or change password functions
var ErrEmptyPassword = errors.New("No password provided")

// Login attempts to login the user given a request.
func Login(r *http.Request) (bool, models.User, error) {
Expand All @@ -61,54 +36,6 @@ func Login(r *http.Request) (bool, models.User, error) {
return true, u, nil
}

// Register attempts to register the user given a request.
func Register(r *http.Request) (bool, error) {
username := r.FormValue("username")
newPassword := r.FormValue("password")
confirmPassword := r.FormValue("confirm_password")
u, err := models.GetUserByUsername(username)
// If the given username already exists, throw an error and return false
if err == nil {
return false, ErrUsernameTaken
}

// If we have an error which is not simply indicating that no user was found, report it
if err != nil && err != gorm.ErrRecordNotFound {
log.Warn(err)
return false, err
}

u = models.User{}
// If we've made it here, we should have a valid username given
// Check that the passsword isn't blank
if newPassword == "" {
return false, ErrEmptyPassword
}
// Make sure passwords match
if newPassword != confirmPassword {
return false, ErrPasswordMismatch
}
// Let's create the password hash
h, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
if err != nil {
return false, err
}
u.Username = username
u.Hash = string(h)
u.ApiKey = GenerateSecureKey()
err = models.PutUser(&u)
return true, nil
}

// GenerateSecureKey creates a secure key to use
// as an API key
func GenerateSecureKey() string {
// Inspired from gorilla/securecookie
k := make([]byte, 32)
io.ReadFull(rand.Reader, k)
return fmt.Sprintf("%x", k)
}

// ChangePassword verifies the current password provided in the request and,
// if it's valid, changes the password for the authenticated user.
func ChangePassword(r *http.Request) error {
Expand Down
11 changes: 11 additions & 0 deletions controllers/api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type APISuite struct {
apiKey string
config *config.Config
apiServer *Server
admin models.User
}

func (s *APISuite) SetupSuite() {
Expand All @@ -37,6 +38,7 @@ func (s *APISuite) SetupSuite() {
u, err := models.GetUser(1)
s.Nil(err)
s.apiKey = u.ApiKey
s.admin = u
// Move our cwd up to the project root for help with resolving
// static assets
err = os.Chdir("../")
Expand All @@ -49,6 +51,15 @@ func (s *APISuite) TearDownTest() {
for _, campaign := range campaigns {
models.DeleteCampaign(campaign.Id)
}
// Cleanup all users except the original admin
users, _ := models.GetUsers()
for _, user := range users {
if user.Id == 1 {
continue
}
err := models.DeleteUser(user.Id)
s.Nil(err)
}
}

func (s *APISuite) SetupTest() {
Expand Down
4 changes: 2 additions & 2 deletions controllers/api/reset.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ package api
import (
"net/http"

"github.com/gophish/gophish/auth"
ctx "github.com/gophish/gophish/context"
"github.com/gophish/gophish/models"
"github.com/gophish/gophish/util"
)

// Reset (/api/reset) resets the currently authenticated user's API key
func (as *Server) Reset(w http.ResponseWriter, r *http.Request) {
switch {
case r.Method == "POST":
u := ctx.Get(r, "user").(models.User)
u.ApiKey = auth.GenerateSecureKey()
u.ApiKey = util.GenerateSecureKey()
err := models.PutUser(&u)
if err != nil {
http.Error(w, "Error setting API Key", http.StatusInternalServerError)
Expand Down
3 changes: 3 additions & 0 deletions controllers/api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"net/http"

mid "github.com/gophish/gophish/middleware"
"github.com/gophish/gophish/models"
"github.com/gophish/gophish/worker"
"github.com/gorilla/mux"
)
Expand Down Expand Up @@ -64,6 +65,8 @@ func (as *Server) registerRoutes() {
router.HandleFunc("/pages/{id:[0-9]+}", as.Page)
router.HandleFunc("/smtp/", as.SendingProfiles)
router.HandleFunc("/smtp/{id:[0-9]+}", as.SendingProfile)
router.HandleFunc("/users/", mid.Use(as.Users, mid.RequirePermission(models.PermissionModifySystem)))
router.HandleFunc("/users/{id:[0-9]+}", mid.Use(as.User))
router.HandleFunc("/util/send_test_email", as.SendTestEmail)
router.HandleFunc("/import/group", as.ImportGroup)
router.HandleFunc("/import/email", as.ImportEmail)
Expand Down
Loading

0 comments on commit 84096b8

Please sign in to comment.