forked from gophish/gophish
/
user.go
218 lines (207 loc) · 7.61 KB
/
user.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
package api
import (
"encoding/json"
"errors"
"net/http"
"strconv"
ctx "github.com/gophish/gophish/context"
log "github.com/gophish/gophish/logger"
"github.com/gophish/gophish/models"
"github.com/gophish/gophish/util"
"github.com/gorilla/mux"
"github.com/jinzhu/gorm"
)
// ErrEmptyPassword is thrown when a user provides a blank password to the register
// or change password functions
var ErrEmptyPassword = errors.New("No password provided")
// ErrUsernameTaken is thrown when a user attempts to register a username that is taken.
var ErrUsernameTaken = errors.New("Username already taken")
// ErrEmptyUsername is thrown when a user attempts to register a username that is taken.
var ErrEmptyUsername = errors.New("No username provided")
// ErrEmptyRole is throws when no role is provided when creating or modifying a user.
var ErrEmptyRole = errors.New("No role specified")
// ErrInsufficientPermission is thrown when a user attempts to change an
// attribute (such as the role) for which they don't have permission.
var ErrInsufficientPermission = errors.New("Permission denied")
// userRequest is the payload which represents the creation of a new user.
type userRequest struct {
Username string `json:"username"`
Password string `json:"password"`
Role string `json:"role"`
}
func (ur *userRequest) Validate(existingUser *models.User) error {
switch {
case ur.Username == "":
return ErrEmptyUsername
case ur.Role == "":
return ErrEmptyRole
}
// Verify that the username isn't already taken. We consider two cases:
// * We're creating a new user, in which case any match is a conflict
// * We're modifying a user, in which case any match with a different ID is
// a conflict.
possibleConflict, err := models.GetUserByUsername(ur.Username)
if err == nil {
if existingUser == nil {
return ErrUsernameTaken
}
if possibleConflict.Id != existingUser.Id {
return ErrUsernameTaken
}
}
// If we have an error which is not simply indicating that no user was found, report it
if err != nil && err != gorm.ErrRecordNotFound {
return err
}
return nil
}
// Users contains functions to retrieve a list of existing users or create a
// new user. Users with the ModifySystem permissions can view and create users.
func (as *Server) Users(w http.ResponseWriter, r *http.Request) {
switch {
case r.Method == "GET":
us, err := models.GetUsers()
if err != nil {
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
return
}
JSONResponse(w, us, http.StatusOK)
return
case r.Method == "POST":
ur := &userRequest{}
err := json.NewDecoder(r.Body).Decode(ur)
if err != nil {
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
return
}
err = ur.Validate(nil)
if err != nil {
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
return
}
if ur.Password == "" {
JSONResponse(w, models.Response{Success: false, Message: ErrEmptyPassword.Error()}, http.StatusBadRequest)
return
}
hash, err := util.NewHash(ur.Password)
if err != nil {
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
return
}
role, err := models.GetRoleBySlug(ur.Role)
if err != nil {
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
return
}
user := models.User{
Username: ur.Username,
Hash: hash,
ApiKey: util.GenerateSecureKey(),
Role: role,
RoleID: role.ID,
}
err = models.PutUser(&user)
if err != nil {
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
return
}
JSONResponse(w, user, http.StatusOK)
return
}
}
// User contains functions to retrieve or delete a single user. Users with
// the ModifySystem permission can view and modify any user. Otherwise, users
// may only view or delete their own account.
func (as *Server) User(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, _ := strconv.ParseInt(vars["id"], 0, 64)
// If the user doesn't have ModifySystem permissions, we need to verify
// that they're only taking action on their account.
currentUser := ctx.Get(r, "user").(models.User)
hasSystem, err := currentUser.HasPermission(models.PermissionModifySystem)
if err != nil {
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
return
}
if !hasSystem && currentUser.Id != id {
JSONResponse(w, models.Response{Success: false, Message: http.StatusText(http.StatusForbidden)}, http.StatusForbidden)
return
}
existingUser, err := models.GetUser(id)
if err != nil {
JSONResponse(w, models.Response{Success: false, Message: "User not found"}, http.StatusNotFound)
return
}
switch {
case r.Method == "GET":
JSONResponse(w, existingUser, http.StatusOK)
case r.Method == "DELETE":
err = models.DeleteUser(id)
if err != nil {
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
return
}
log.Infof("Deleted user account for %s", existingUser.Username)
JSONResponse(w, models.Response{Success: true, Message: "User deleted Successfully!"}, http.StatusOK)
case r.Method == "PUT":
ur := &userRequest{}
err = json.NewDecoder(r.Body).Decode(ur)
if err != nil {
log.Errorf("error decoding user request: %v", err)
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
return
}
err = ur.Validate(&existingUser)
if err != nil {
log.Errorf("invalid user request received: %v", err)
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
return
}
existingUser.Username = ur.Username
// Only users with the ModifySystem permission are able to update a
// user's role. This prevents a privilege escalation letting users
// upgrade their own account.
if !hasSystem && ur.Role != existingUser.Role.Slug {
JSONResponse(w, models.Response{Success: false, Message: ErrInsufficientPermission.Error()}, http.StatusBadRequest)
return
}
role, err := models.GetRoleBySlug(ur.Role)
if err != nil {
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
return
}
// If our user is trying to change the role of an admin, we need to
// ensure that it isn't the last user account with the Admin role.
if existingUser.Role.Slug == models.RoleAdmin && existingUser.Role.ID != role.ID {
err = models.EnsureEnoughAdmins()
if err != nil {
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
return
}
}
existingUser.Role = role
existingUser.RoleID = role.ID
// We don't force the password to be provided, since it may be an admin
// managing the user's account, and making a simple change like
// updating the username or role. However, if it _is_ provided, we'll
// update the stored hash.
//
// Note that we don't force the current password to be provided. The
// assumption here is that the API key is a proper bearer token proving
// authenticated access to the account.
if ur.Password != "" {
hash, err := util.NewHash(ur.Password)
if err != nil {
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
return
}
existingUser.Hash = hash
}
err = models.PutUser(&existingUser)
if err != nil {
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
return
}
JSONResponse(w, existingUser, http.StatusOK)
}
}