mirror of
https://github.com/gravitl/netmaker.git
synced 2025-10-05 08:47:35 +08:00
Don't expose user hashed password (#2419)
This commit is contained in:

committed by
GitHub

parent
6e39ae4178
commit
11933fc07e
@@ -19,6 +19,9 @@ var (
|
|||||||
upgrader = websocket.Upgrader{}
|
upgrader = websocket.Upgrader{}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// verifyJWT makes logic.VerifyJWT fakeable/mockable in tests
|
||||||
|
var verifyJWT = logic.VerifyJWT
|
||||||
|
|
||||||
func userHandlers(r *mux.Router) {
|
func userHandlers(r *mux.Router) {
|
||||||
|
|
||||||
r.HandleFunc("/api/users/adm/hasadmin", hasAdmin).Methods(http.MethodGet)
|
r.HandleFunc("/api/users/adm/hasadmin", hasAdmin).Methods(http.MethodGet)
|
||||||
@@ -152,7 +155,7 @@ func getUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
var params = mux.Vars(r)
|
var params = mux.Vars(r)
|
||||||
usernameFetched := params["username"]
|
usernameFetched := params["username"]
|
||||||
user, err := logic.GetUser(usernameFetched)
|
user, err := logic.GetReturnUser(usernameFetched)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Log(0, usernameFetched, "failed to fetch user: ", err.Error())
|
logger.Log(0, usernameFetched, "failed to fetch user: ", err.Error())
|
||||||
@@ -230,7 +233,7 @@ func createAdmin(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.Log(1, admin.UserName, "was made a new admin")
|
logger.Log(1, admin.UserName, "was made a new admin")
|
||||||
json.NewEncoder(w).Encode(admin)
|
json.NewEncoder(w).Encode(logic.ToReturnUser(admin))
|
||||||
}
|
}
|
||||||
|
|
||||||
// swagger:route POST /api/users/{username} user createUser
|
// swagger:route POST /api/users/{username} user createUser
|
||||||
@@ -264,7 +267,7 @@ func createUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.Log(1, user.UserName, "was created")
|
logger.Log(1, user.UserName, "was created")
|
||||||
json.NewEncoder(w).Encode(user)
|
json.NewEncoder(w).Encode(logic.ToReturnUser(user))
|
||||||
}
|
}
|
||||||
|
|
||||||
// swagger:route PUT /api/users/networks/{username} user updateUserNetworks
|
// swagger:route PUT /api/users/networks/{username} user updateUserNetworks
|
||||||
@@ -314,12 +317,13 @@ func updateUserNetworks(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
logger.Log(1, username, "status was updated")
|
logger.Log(1, username, "status was updated")
|
||||||
// re-read and return the new user struct
|
// re-read and return the new user struct
|
||||||
if userChange, err = logic.GetUser(username); err != nil {
|
returnUser, err := logic.GetReturnUser(username)
|
||||||
|
if err != nil {
|
||||||
logger.Log(0, username, "failed to fetch user: ", err.Error())
|
logger.Log(0, username, "failed to fetch user: ", err.Error())
|
||||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
json.NewEncoder(w).Encode(userChange)
|
json.NewEncoder(w).Encode(returnUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
// swagger:route PUT /api/users/{username} user updateUser
|
// swagger:route PUT /api/users/{username} user updateUser
|
||||||
@@ -337,7 +341,7 @@ func updateUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
var params = mux.Vars(r)
|
var params = mux.Vars(r)
|
||||||
// start here
|
// start here
|
||||||
jwtUser, _, isadmin, err := logic.VerifyJWT(r.Header.Get("Authorization"))
|
jwtUser, _, isadmin, err := verifyJWT(r.Header.Get("Authorization"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Log(0, "verifyJWT error", err.Error())
|
logger.Log(0, "verifyJWT error", err.Error())
|
||||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||||
@@ -385,7 +389,7 @@ func updateUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.Log(1, username, "was updated")
|
logger.Log(1, username, "was updated")
|
||||||
json.NewEncoder(w).Encode(user)
|
json.NewEncoder(w).Encode(logic.ToReturnUser(*user))
|
||||||
}
|
}
|
||||||
|
|
||||||
// swagger:route PUT /api/users/{username}/adm user updateUserAdm
|
// swagger:route PUT /api/users/{username}/adm user updateUserAdm
|
||||||
@@ -409,7 +413,7 @@ func updateUserAdm(w http.ResponseWriter, r *http.Request) {
|
|||||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if auth.IsOauthUser(user) != nil {
|
if auth.IsOauthUser(user) == nil {
|
||||||
err := fmt.Errorf("cannot update user info for oauth user %s", username)
|
err := fmt.Errorf("cannot update user info for oauth user %s", username)
|
||||||
logger.Log(0, err.Error())
|
logger.Log(0, err.Error())
|
||||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden"))
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden"))
|
||||||
@@ -436,7 +440,7 @@ func updateUserAdm(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.Log(1, username, "was updated (admin)")
|
logger.Log(1, username, "was updated (admin)")
|
||||||
json.NewEncoder(w).Encode(user)
|
json.NewEncoder(w).Encode(logic.ToReturnUser(*user))
|
||||||
}
|
}
|
||||||
|
|
||||||
// swagger:route DELETE /api/users/{username} user deleteUser
|
// swagger:route DELETE /api/users/{username} user deleteUser
|
||||||
|
@@ -1,6 +1,12 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/go-jose/go-jose/v3/json"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -19,6 +25,123 @@ func deleteAllUsers(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetUserNoHashedPassword(t *testing.T) {
|
||||||
|
// prepare existing user base
|
||||||
|
user := models.User{UserName: "freddie", Password: "password"}
|
||||||
|
haveOnlyOneUser(t, user)
|
||||||
|
|
||||||
|
// prepare request
|
||||||
|
rec, req := prepareUserRequest(t, models.User{}, user.UserName)
|
||||||
|
|
||||||
|
// test response
|
||||||
|
getUser(rec, req)
|
||||||
|
assertUserNameButNoPassword(t, rec.Body, user.UserName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateAdminNoHashedPassword(t *testing.T) {
|
||||||
|
// prepare existing user base
|
||||||
|
deleteAllUsers(t)
|
||||||
|
|
||||||
|
// prepare request
|
||||||
|
user := models.User{UserName: "jonathan", Password: "password"}
|
||||||
|
rec, req := prepareUserRequest(t, user, "")
|
||||||
|
|
||||||
|
// test response
|
||||||
|
createAdmin(rec, req)
|
||||||
|
assertUserNameButNoPassword(t, rec.Body, user.UserName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateUserNoHashedPassword(t *testing.T) {
|
||||||
|
// prepare existing user base
|
||||||
|
deleteAllUsers(t)
|
||||||
|
|
||||||
|
// prepare request
|
||||||
|
user := models.User{UserName: "jonathan", Password: "password"}
|
||||||
|
rec, req := prepareUserRequest(t, user, "")
|
||||||
|
|
||||||
|
// test response
|
||||||
|
createUser(rec, req)
|
||||||
|
assertUserNameButNoPassword(t, rec.Body, user.UserName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateUserNetworksNoHashedPassword(t *testing.T) {
|
||||||
|
// prepare existing user base
|
||||||
|
user1 := models.User{UserName: "joestar", Password: "jonathan"}
|
||||||
|
haveOnlyOneUser(t, user1)
|
||||||
|
|
||||||
|
// prepare request
|
||||||
|
user2 := models.User{UserName: "joestar", Password: "joseph"}
|
||||||
|
rec, req := prepareUserRequest(t, user2, user1.UserName)
|
||||||
|
|
||||||
|
// test response
|
||||||
|
updateUserNetworks(rec, req)
|
||||||
|
assertUserNameButNoPassword(t, rec.Body, user1.UserName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateUserNoHashedPassword(t *testing.T) {
|
||||||
|
// prepare existing user base
|
||||||
|
user1 := models.User{UserName: "dio", Password: "brando"}
|
||||||
|
haveOnlyOneUser(t, user1)
|
||||||
|
|
||||||
|
// prepare request
|
||||||
|
user2 := models.User{UserName: "giorno", Password: "giovanna"}
|
||||||
|
rec, req := prepareUserRequest(t, user2, user1.UserName)
|
||||||
|
|
||||||
|
// mock the jwt verification
|
||||||
|
oldVerify := verifyJWT
|
||||||
|
verifyJWT = func(bearerToken string) (username string, networks []string, isadmin bool, err error) {
|
||||||
|
return user1.UserName, user1.Networks, user1.IsAdmin, nil
|
||||||
|
}
|
||||||
|
defer func() { verifyJWT = oldVerify }()
|
||||||
|
|
||||||
|
// test response
|
||||||
|
updateUser(rec, req)
|
||||||
|
assertUserNameButNoPassword(t, rec.Body, user2.UserName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateUserAdmNoHashedPassword(t *testing.T) {
|
||||||
|
// prepare existing user base
|
||||||
|
user1 := models.User{UserName: "dio", Password: "brando", IsAdmin: true}
|
||||||
|
haveOnlyOneUser(t, user1)
|
||||||
|
|
||||||
|
// prepare request
|
||||||
|
user2 := models.User{UserName: "giorno", Password: "giovanna"}
|
||||||
|
rec, req := prepareUserRequest(t, user2, user1.UserName)
|
||||||
|
|
||||||
|
// test response
|
||||||
|
updateUserAdm(rec, req)
|
||||||
|
assertUserNameButNoPassword(t, rec.Body, user2.UserName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareUserRequest(t *testing.T, userForBody models.User, userNameForParam string) (*httptest.ResponseRecorder, *http.Request) {
|
||||||
|
bits, err := json.Marshal(userForBody)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
body := bytes.NewReader(bits)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
req := httptest.NewRequest("ANY", "https://example.com", body) // only the body matters here
|
||||||
|
req = mux.SetURLVars(req, map[string]string{"username": userNameForParam})
|
||||||
|
return rec, req
|
||||||
|
}
|
||||||
|
|
||||||
|
func haveOnlyOneUser(t *testing.T, user models.User) {
|
||||||
|
deleteAllUsers(t)
|
||||||
|
var err error
|
||||||
|
if user.IsAdmin {
|
||||||
|
err = logic.CreateAdmin(&user)
|
||||||
|
} else {
|
||||||
|
err = logic.CreateUser(&user)
|
||||||
|
}
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertUserNameButNoPassword(t *testing.T, r io.Reader, userName string) {
|
||||||
|
var resp models.User
|
||||||
|
err := json.NewDecoder(r).Decode(&resp)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, userName, resp.UserName)
|
||||||
|
assert.Empty(t, resp.Password)
|
||||||
|
}
|
||||||
|
|
||||||
func TestHasAdmin(t *testing.T) {
|
func TestHasAdmin(t *testing.T) {
|
||||||
// delete all current users
|
// delete all current users
|
||||||
users, _ := logic.GetUsers()
|
users, _ := logic.GetUsers()
|
||||||
|
@@ -42,20 +42,6 @@ func HasAdmin() (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetReturnUser - gets a user
|
|
||||||
func GetReturnUser(username string) (models.ReturnUser, error) {
|
|
||||||
|
|
||||||
var user models.ReturnUser
|
|
||||||
record, err := database.FetchRecord(database.USERS_TABLE_NAME, username)
|
|
||||||
if err != nil {
|
|
||||||
return user, err
|
|
||||||
}
|
|
||||||
if err = json.Unmarshal([]byte(record), &user); err != nil {
|
|
||||||
return models.ReturnUser{}, err
|
|
||||||
}
|
|
||||||
return user, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUsers - gets users
|
// GetUsers - gets users
|
||||||
func GetUsers() ([]models.ReturnUser, error) {
|
func GetUsers() ([]models.ReturnUser, error) {
|
||||||
|
|
||||||
|
@@ -26,6 +26,30 @@ func GetUser(username string) (*models.User, error) {
|
|||||||
return &user, err
|
return &user, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetReturnUser - gets a user
|
||||||
|
func GetReturnUser(username string) (models.ReturnUser, error) {
|
||||||
|
|
||||||
|
var user models.ReturnUser
|
||||||
|
record, err := database.FetchRecord(database.USERS_TABLE_NAME, username)
|
||||||
|
if err != nil {
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal([]byte(record), &user); err != nil {
|
||||||
|
return models.ReturnUser{}, err
|
||||||
|
}
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToReturnUser - gets a user as a return user
|
||||||
|
func ToReturnUser(user models.User) models.ReturnUser {
|
||||||
|
return models.ReturnUser{
|
||||||
|
UserName: user.UserName,
|
||||||
|
Networks: user.Networks,
|
||||||
|
IsAdmin: user.IsAdmin,
|
||||||
|
Groups: user.Groups,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetGroupUsers - gets users in a group
|
// GetGroupUsers - gets users in a group
|
||||||
func GetGroupUsers(group string) ([]models.ReturnUser, error) {
|
func GetGroupUsers(group string) ([]models.ReturnUser, error) {
|
||||||
var returnUsers []models.ReturnUser
|
var returnUsers []models.ReturnUser
|
||||||
|
Reference in New Issue
Block a user