changeset 5345:95dafb72a288 extented-report

Added a PATCH endpoint for /api/users/{user}
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Sun, 20 Jun 2021 04:17:53 +0200
parents 7df6062a1371
children 72469b713705
files pkg/controllers/routes.go pkg/controllers/user.go pkg/models/user.go
diffstat 3 files changed, 149 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/pkg/controllers/routes.go	Fri Jun 18 21:27:41 2021 +0200
+++ b/pkg/controllers/routes.go	Sun Jun 20 04:17:53 2021 +0200
@@ -68,6 +68,11 @@
 		Handle: updateUser,
 	})).Methods(http.MethodPut)
 
+	api.Handle("/users/{user:.+}", any(&mw.JSONHandler{
+		Input:  func(*http.Request) interface{} { return new(models.UserPatch) },
+		Handle: patchUser,
+	})).Methods(http.MethodPatch)
+
 	api.Handle("/users/{user:.+}", sysAdmin(&mw.JSONHandler{
 		Handle: deleteUser,
 	})).Methods(http.MethodDelete)
--- a/pkg/controllers/user.go	Fri Jun 18 21:27:41 2021 +0200
+++ b/pkg/controllers/user.go	Sun Jun 20 04:17:53 2021 +0200
@@ -21,6 +21,8 @@
 	"fmt"
 	"log"
 	"net/http"
+	"strconv"
+	"strings"
 	"text/template"
 	"time"
 
@@ -245,6 +247,137 @@
 	return
 }
 
+func patchUser(req *http.Request) (jr mw.JSONResult, err error) {
+
+	user := models.UserName(mux.Vars(req)["user"])
+	if !user.IsValid() {
+		err = mw.JSONError{
+			Code:    http.StatusBadRequest,
+			Message: "error: user invalid",
+		}
+		return
+	}
+
+	s, ok := auth.GetSession(req)
+	if !ok {
+		err = mw.JSONError{
+			Code:    http.StatusUnauthorized,
+			Message: "error: not logged in",
+		}
+		return
+	}
+
+	priv := s.Roles.Has("sys_admin")
+
+	if !priv && s.User != string(user) {
+		err = mw.JSONError{
+			Code:    http.StatusUnauthorized,
+			Message: "error: not allowed to modify someone else",
+		}
+		return
+	}
+
+	var (
+		columns   []string
+		positions []string
+		args      []interface{}
+	)
+
+	update := func(column string, value interface{}) {
+		columns = append(columns, column)
+		positions = append(positions, "$"+strconv.Itoa(len(positions)+1))
+		args = append(args, value)
+	}
+
+	updateBox := func(column string, extent *models.BoundingBox) {
+		columns = append(columns, column)
+		pos := len(positions)
+		position := fmt.Sprintf("ST_MakeBox2D(ST_Point($%d, $%d), ST_Point($%d, $%d))",
+			pos+1, pos+2, pos+3, pos+4)
+		positions = append(positions, position)
+		args = append(args, extent.X1, extent.Y1, extent.X2, extent.Y2)
+	}
+
+	patch := mw.JSONInput(req).(*models.UserPatch)
+
+	if patch.User != nil && priv {
+		update("user", *patch.User)
+	}
+	if patch.Role != nil && priv {
+		update("rolname", *patch.Role)
+	}
+	if patch.Password != nil {
+		update("pw", *patch.Password)
+	}
+	if patch.Email != nil {
+		update("email_address", *patch.Email)
+	}
+	if patch.Country != nil && priv {
+		update("country", *patch.Country)
+	}
+	if patch.Reports != nil && priv {
+		update("report_reciever", *patch.Reports)
+	}
+	if patch.Extent != nil {
+		updateBox("map_extent", patch.Extent)
+	}
+
+	var colsS, posS string
+
+	switch len(columns) {
+	case 0: // Nothing to do
+		jr = mw.JSONResult{
+			Code: http.StatusCreated,
+			Result: struct {
+				Result string `json:"result"`
+			}{"success"},
+		}
+		return
+	case 1: // No brackets if there is only one argument.
+		colsS = columns[0]
+		posS = positions[0]
+	default:
+		colsS = "(" + strings.Join(columns, ",") + ")"
+		posS = "(" + strings.Join(positions, ",") + ")"
+	}
+
+	stmt := fmt.Sprintf(
+		`UPDATE users.list_users SET %s = %s WHERE username = $%d`,
+		colsS,
+		posS,
+		len(positions)+1)
+
+	args = append(args, user)
+
+	db := mw.JSONConn(req)
+
+	var res sql.Result
+	if res, err = db.ExecContext(req.Context(), stmt, args...); err != nil {
+		return
+	}
+
+	if n, err2 := res.RowsAffected(); err2 == nil && n == 0 {
+		err = mw.JSONError{
+			Code:    http.StatusNotFound,
+			Message: fmt.Sprintf("Cannot find user %s.", user),
+		}
+		return
+	}
+
+	if patch.User != nil && *patch.User != user {
+		// Running in a go routine should not be necessary.
+		go func() { auth.Sessions.Logout(string(user)) }()
+	}
+
+	jr = mw.JSONResult{
+		Code: http.StatusCreated,
+		Result: struct {
+			Result string `json:"result"`
+		}{"success"},
+	}
+	return
+}
+
 func createUser(req *http.Request) (jr mw.JSONResult, err error) {
 
 	user := mw.JSONInput(req).(*models.User)
--- a/pkg/models/user.go	Fri Jun 18 21:27:41 2021 +0200
+++ b/pkg/models/user.go	Sun Jun 20 04:17:53 2021 +0200
@@ -50,6 +50,17 @@
 		Extent   *BoundingBox `json:"extent"`
 	}
 
+	// UserPatch is used to send only partial updates.
+	UserPatch struct {
+		User     *UserName    `json:"user,omitempty"`
+		Role     *Role        `json:"role,omitempty"`
+		Password *string      `json:"password,omitempty"`
+		Email    *Email       `json:"email,omitempty"`
+		Country  *Country     `json:"country,omitempty"`
+		Reports  *bool        `json:"reports,omitempty"`
+		Extent   *BoundingBox `json:"extent,omitempty"`
+	}
+
 	// PWResetUser is send to request a password reset for a user.
 	PWResetUser struct {
 		User string `json:"user"`