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