func ReplaceCurrent()

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)
}