in traffic_ops/traffic_ops_golang/user/current.go [338:489]
func ReplaceCurrent(w http.ResponseWriter, r *http.Request) {
inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
tx := inf.Tx.Tx
if userErr != nil || sysErr != nil {
api.HandleErr(w, r, tx, errCode, userErr, sysErr)
return
}
defer inf.Close()
var userRequest tc.CurrentUserUpdateRequest
if err := json.NewDecoder(r.Body).Decode(&userRequest); err != nil {
errCode = http.StatusBadRequest
userErr = fmt.Errorf("couldn't parse request: %w", err)
api.HandleErr(w, r, tx, errCode, userErr, nil)
return
}
if userRequest.User == nil {
errCode = http.StatusBadRequest
userErr = fmt.Errorf("missing required 'user' object")
api.HandleErr(w, r, tx, errCode, userErr, nil)
return
}
user, exists, err := dbhelpers.GetUserByID(inf.User.ID, tx)
if err != nil {
sysErr = fmt.Errorf("getting user by ID %d: %w", inf.User.ID, err)
errCode = http.StatusInternalServerError
api.HandleErr(w, r, tx, errCode, nil, sysErr)
return
}
if !exists {
sysErr = fmt.Errorf("current user (#%d) doesn't exist... ??", inf.User.ID)
errCode = http.StatusInternalServerError
api.HandleErr(w, r, tx, errCode, nil, sysErr)
return
}
if err := userRequest.User.UnmarshalAndValidate(&user); err != nil {
errCode = http.StatusBadRequest
userErr = fmt.Errorf("couldn't parse request: %w", err)
api.HandleErr(w, r, tx, errCode, userErr, nil)
return
}
changePasswd := false
changeConfirmPasswd := false
// obfuscate passwords (UnmarshalAndValidate checks for equality with ConfirmLocalPassword)
// TODO: check for valid password via bad password list like Perl did? User creation doesn't...
if user.LocalPassword != nil && *user.LocalPassword != "" {
if ok, err := auth.IsGoodPassword(*user.LocalPassword); !ok {
errCode = http.StatusBadRequest
if err != nil {
userErr = err
} else {
userErr = errors.New("unacceptable password")
}
api.HandleErr(w, r, tx, errCode, userErr, nil)
return
}
hashPass, err := auth.DerivePassword(*user.LocalPassword)
if err != nil {
sysErr = fmt.Errorf("hashing new password: %w", err)
errCode = http.StatusInternalServerError
api.HandleErr(w, r, tx, errCode, nil, sysErr)
return
}
changePasswd = true
user.LocalPassword = util.StrPtr(hashPass)
}
// Perl did this although it serves no known purpose
if user.ConfirmLocalPassword != nil && *user.ConfirmLocalPassword != "" {
hashPass, err := auth.DerivePassword(*user.ConfirmLocalPassword)
if err != nil {
sysErr = fmt.Errorf("hashing new 'confirm' password: %w", err)
errCode = http.StatusInternalServerError
api.HandleErr(w, r, tx, errCode, nil, sysErr)
return
}
user.ConfirmLocalPassword = util.StrPtr(hashPass)
changeConfirmPasswd = true
}
if *user.Role != inf.User.Role {
privLevel, exists, err := dbhelpers.GetPrivLevelFromRoleID(tx, *user.Role)
if err != nil {
sysErr = fmt.Errorf("getting privLevel for Role #%d: %w", *user.Role, err)
errCode = http.StatusInternalServerError
api.HandleErr(w, r, tx, errCode, nil, sysErr)
return
}
if !exists {
userErr = fmt.Errorf("role: no such role: %d", *user.Role)
errCode = http.StatusNotFound
api.HandleErr(w, r, tx, errCode, userErr, nil)
return
}
if privLevel > inf.User.PrivLevel {
userErr = errors.New("role: cannot have greater permissions than user's current role")
errCode = http.StatusForbidden
api.HandleErr(w, r, tx, errCode, userErr, nil)
return
}
}
if ok, err := tenant.IsResourceAuthorizedToUserTx(*user.TenantID, inf.User, tx); err != nil {
if errors.Is(err, sql.ErrNoRows) {
userErr = errors.New("no such tenant")
errCode = http.StatusNotFound
} else {
sysErr = fmt.Errorf("checking user %s permissions on tenant #%d: %w", inf.User.UserName, *user.TenantID, err)
errCode = http.StatusInternalServerError
}
api.HandleErr(w, r, tx, errCode, userErr, sysErr)
return
} else if !ok {
// unlike Perl, this endpoint will not disclose the existence of tenants over which the current
// user has no permission - in keeping with the behavior of the '/tenants' endpoint.
userErr = errors.New("no such tenant")
errCode = http.StatusNotFound
api.HandleErr(w, r, tx, errCode, userErr, sysErr)
return
}
if *user.Username != inf.User.UserName {
if ok, err := dbhelpers.UsernameExists(*user.Username, tx); err != nil {
sysErr = fmt.Errorf("checking existence of user %s: %w", *user.Username, err)
errCode = http.StatusInternalServerError
api.HandleErr(w, r, tx, errCode, nil, sysErr)
return
} else if ok {
// TODO users are tenanted, so theoretically I should be hiding the existence of the
// conflicting user - but then how do I tell the client how to fix their request?
userErr = fmt.Errorf("username %s already exists", *user.Username)
errCode = http.StatusConflict
api.HandleErr(w, r, tx, errCode, userErr, nil)
return
}
}
if err = updateLegacyUser(&user, tx, changePasswd, changeConfirmPasswd); err != nil {
userErr, sysErr, statusCode := api.ParseDBError(err)
sysErr = fmt.Errorf("updating legacy user: %w", err)
api.HandleErr(w, r, tx, statusCode, userErr, sysErr)
return
}
api.WriteRespAlertObj(w, r, tc.SuccessLevel, "User profile was successfully updated", user)
}