forked from mandiant/gocrack
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathusers.go
More file actions
274 lines (239 loc) · 7.79 KB
/
users.go
File metadata and controls
274 lines (239 loc) · 7.79 KB
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
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
package web
import (
"errors"
"fmt"
"net/http"
"strings"
"time"
"github.com/fireeye/gocrack/server/authentication"
"github.com/fireeye/gocrack/server/storage"
"github.com/gin-gonic/gin"
)
const badPassword = "The password you entered does not meet the requirements. It must have a length greater than or equal to 8, contain at least 1 special character, and 1 number."
// UserDetailedItem returns all information about a user, minus sensitive information. This should mimick storage.User
type UserDetailedItem struct {
UserUUID string `json:"user_uuid"`
Username string `json:"username"`
Password string `json:"-"`
Enabled *bool `json:"enabled,omitempty"`
EmailAddress string `json:"email_address"`
IsSuperUser bool `json:"is_admin"`
CreatedAt time.Time `json:"created_at"`
}
// UserListingItem is an item returned in a user listing API and should mimick storage.User
type UserListingItem struct {
UserUUID string `json:"user_uuid"`
Username string `json:"username"`
CreatedAt time.Time `json:"created_at"`
}
// EditUserRequest must match `storage.UserModifyRequest`
type EditUserRequest struct {
CurrentPassword *string `json:"current_password"`
Password *string `json:"new_password"`
UserIsAdmin *bool `json:"user_is_admin"`
Email *string `json:"email"`
}
// EditUserResponse is returned on a successful update
type EditUserResponse struct {
Modified bool `json:"modified"`
EditUserRequest
}
// CreateUserRequest describes the payload sent by the user to create a new user
type CreateUserRequest struct {
Username string `json:"username"`
Password string `json:"password"`
Email string `json:"email"`
}
// Validate returns any errors that are associated with the user input of CreateUserRequest
func (s CreateUserRequest) Validate() error {
if s.Username == "" || len(s.Username) <= 4 {
return errors.New("Username must be at least 4 characters or more")
}
// Super dumb email validation to check for the existence of an @ and period. Email validation is tough these days!
if s.Email == "" || !strings.ContainsAny(s.Email, "@ & .") {
return errors.New("Email must not be a valid email address")
}
if solidPassword := authentication.CheckPasswordRequirement(s.Password); !solidPassword {
return errors.New(badPassword)
}
return nil
}
func (s *Server) webGetUsers(c *gin.Context) *WebAPIError {
users, err := s.stor.GetUsers()
if err != nil {
if err == storage.ErrNotFound {
c.JSON(http.StatusOK, []UserListingItem{})
return nil
}
return &WebAPIError{
StatusCode: http.StatusInternalServerError,
Err: err,
UserError: "The server was unable to process your request. Please try again later",
}
}
resp := make([]UserListingItem, len(users))
for i, user := range users {
resp[i] = UserListingItem{
Username: user.Username,
UserUUID: user.UserUUID,
CreatedAt: user.CreatedAt,
}
}
c.JSON(http.StatusOK, resp)
return nil
}
func (s *Server) webEditUser(c *gin.Context) *WebAPIError {
var (
req EditUserRequest
userid = c.Param("user_uuid")
)
claim := getClaimInformation(c)
if err := c.BindJSON(&req); err != nil {
return &WebAPIError{
StatusCode: http.StatusBadRequest,
Err: err,
UserError: "Invalid JSON",
}
}
userrec, err := s.stor.GetUserByID(userid)
if err != nil {
if err == storage.ErrNotFound {
return &WebAPIError{
StatusCode: http.StatusNotFound,
UserError: "User not found or you do not have permission to view this record",
}
}
return &WebAPIError{
StatusCode: http.StatusInternalServerError,
Err: err,
}
}
// The request to modify a user does not match their own record and the requester is not an admin, deny
if userrec.UserUUID != claim.UserUUID && !claim.IsAdmin {
return &WebAPIError{
StatusCode: http.StatusNotFound,
Err: fmt.Errorf("%s attempted to modify %s's user record and did not have the proper rights", claim.UserUUID, userrec.UserUUID),
UserError: "User not found or you do not have permission to view this record",
}
}
// User is unable to modify password because the record
if (req.Password != nil && !s.auth.UserCanChangePassword()) || (req.CurrentPassword == nil && !claim.IsAdmin) {
req.Password = nil
} else if req.Password != nil && s.auth.UserCanChangePassword() {
// Validate that the current password is correct if they're not an administrator
if !claim.IsAdmin {
if _, err := s.auth.Login(claim.Username, *req.CurrentPassword, claim.APIOnly); err != nil {
return &WebAPIError{
StatusCode: http.StatusBadRequest,
Err: err,
UserError: "Your current password is incorrect",
}
}
}
// Generate the secure password to set in our database
securePassword, err := s.auth.GenerateSecurePassword(*req.Password)
if err != nil {
if err == authentication.ErrFailsRequirements || err == authentication.ErrPasswordEmpty {
return &WebAPIError{
StatusCode: http.StatusBadRequest,
UserError: badPassword,
}
}
return &WebAPIError{
StatusCode: http.StatusInternalServerError,
Err: err,
UserError: "We were unable to edit the user",
}
}
req.Password = &securePassword
}
// User is unable to modify their admin flag if they arent an admin!
if req.UserIsAdmin != nil && !claim.IsAdmin {
req.UserIsAdmin = nil
}
if err = s.stor.EditUser(userid, storage.UserModifyRequest{
Password: req.Password,
Email: req.Email,
UserIsAdmin: req.UserIsAdmin,
}); err != nil {
return &WebAPIError{
StatusCode: http.StatusInternalServerError,
Err: err,
UserError: "We were unable to edit the user",
}
}
c.JSON(http.StatusOK, &EditUserResponse{
Modified: true,
EditUserRequest: req,
})
return nil
}
func (s *Server) webGetUser(c *gin.Context) *WebAPIError {
var userid = c.Param("user_uuid")
claim := getClaimInformation(c)
userrec, err := s.stor.GetUserByID(userid)
if err != nil {
if err == storage.ErrNotFound {
return &WebAPIError{
StatusCode: http.StatusNotFound,
UserError: "User not found or you do not have permission to view this record",
}
}
return &WebAPIError{
StatusCode: http.StatusInternalServerError,
Err: err,
}
}
// The request to modify a user does not match their own record and the requester is not an admin, deny
if userrec.UserUUID != claim.UserUUID && !claim.IsAdmin {
return &WebAPIError{
StatusCode: http.StatusNotFound,
Err: fmt.Errorf("%s attempted to view %s's user record and did not have the proper rights", claim.UserUUID, userrec.UserUUID),
UserError: "User not found or you do not have permission to view this record",
}
}
c.JSON(http.StatusOK, UserDetailedItem(*userrec))
return nil
}
func (s *Server) webRegisterNewUser(c *gin.Context) *WebAPIError {
var req CreateUserRequest
if !s.auth.CanUsersRegister() {
return &WebAPIError{
StatusCode: http.StatusBadRequest,
UserError: "Registration is not allowed. Please contact your site administrator for more details",
}
}
if err := c.BindJSON(&req); err != nil {
return &WebAPIError{
StatusCode: http.StatusBadRequest,
Err: err,
UserError: "Invalid JSON",
}
}
if err := req.Validate(); err != nil {
return &WebAPIError{
StatusCode: http.StatusBadRequest,
Err: err,
CanErrorBeShownToUser: true,
}
}
if err := s.auth.CreateUser(storage.User{
Username: req.Username,
EmailAddress: req.Email,
Password: req.Password,
}); err != nil {
if err == storage.ErrAlreadyExists {
return &WebAPIError{
StatusCode: http.StatusBadRequest,
UserError: "The username you picked is not available",
}
}
return &WebAPIError{
StatusCode: http.StatusInternalServerError,
Err: err,
UserError: "We were unable to create the user",
}
}
c.Status(http.StatusCreated)
return nil
}